wireguard-hs (empty) → 0.1.0
raw patch · 4 files changed
+2432/−0 lines, 4 filesdep +aesondep +basedep +base64
Dependencies added: aeson, base, base64, beam-core, bytestring, clock, cryptonite, ip, memory, network, text, time, vector, vector-sized, wide-word
Files
- System/Network/Wireguard.hsc +578/−0
- System/Network/linkstatus.c +27/−0
- wireguard-hs.cabal +75/−0
- wireguard/wireguard.c +1752/−0
+ System/Network/Wireguard.hsc view
@@ -0,0 +1,578 @@+module System.Network.Wireguard where++import Control.Exception (throw, bracket, mask)+import Control.Monad (unless)+import Control.Monad.IO.Class (MonadIO(..))++import Crypto.Error (CryptoFailable(..))+import Crypto.PubKey.Curve25519 (generateSecretKey, toPublic, secretKey)++#ifdef WITH_AESON+import Control.Applicative ((<|>))+import Data.Aeson (ToJSON(..), FromJSON(..))+import qualified Data.Text.Encoding as TE+import qualified Data.ByteString.Base64 as B64+#endif+import Data.Bits+import Data.Coerce (coerce)+import qualified Data.ByteArray as BA+import Data.Maybe (fromMaybe)+import Data.String (fromString)+import qualified Data.Text as T+import Data.Time (UTCTime)+import Data.Proxy (Proxy(..))+import qualified Data.Vector.Storable as V+import qualified Data.Vector.Storable.Sized as VS+import Data.WideWord.Word128 (Word128(..))+import Data.Word++#ifdef WITH_BEAM+import Database.Beam+import Database.Beam.Backend.SQL+import qualified Data.ByteString as BS+#endif++import GHC.Generics (Generic)++import Foreign.C.Error+import Foreign.C.String+import Foreign.C.Types+import Foreign.ForeignPtr (withForeignPtr)+import Foreign.Marshal.Alloc (malloc, mallocBytes, free, alloca)+import Foreign.Ptr+import Foreign.Storable++import qualified Net.IP as IP+import Net.IPv4 (IPv4(..), IPv4Range(..))+import Net.IPv6 (IPv6(..), IPv6Range(..))+import qualified Net.IPv6 as IPv6+import Network.Socket (SockAddr(..))+import Network.Socket.Address (SocketAddress(..))++import System.Clock (TimeSpec(..))+import System.IO.Unsafe (unsafePerformIO)++#include "wireguard.h"+#include "string.h"++foreign import ccall unsafe "strncpy" c_strncpy :: Ptr CChar -> Ptr CChar -> CInt -> IO (Ptr CChar)++data IPRange+ = IPRange4 !IPv4Range+ | IPRange6 !IPv6Range+ deriving (Show, Eq, Ord)++#ifdef WITH_AESON+instance ToJSON IPRange where+ toJSON (IPRange4 x) = toJSON x+ toJSON (IPRange6 x) = toJSON x++instance FromJSON IPRange where+ parseJSON x = fmap IPRange4 (parseJSON x) <|> fmap IPRange6 (parseJSON x)+#endif++-- | Linux network interface status+data LinkStatus+ = LinkStatusUp | LinkStatusDown+ deriving (Show, Eq, Ord)++linkStatus2Text :: LinkStatus -> T.Text+linkStatus2Text LinkStatusUp = "up"+linkStatus2Text LinkStatusDown = "down"++text2LinkStatus :: T.Text -> Maybe LinkStatus+text2LinkStatus "up" = pure LinkStatusUp+text2LinkStatus "down" = pure LinkStatusDown+text2LinkStatus _ = Nothing++#ifdef WITH_AESON+instance ToJSON LinkStatus where+ toJSON = toJSON . linkStatus2Text++instance FromJSON LinkStatus where+ parseJSON p = maybe (fail "Expected 'up' or 'down' for link status")+ pure =<<+ fmap text2LinkStatus (parseJSON p)+#endif++#ifdef WITH_BEAM+instance HasSqlValueSyntax syntax T.Text => HasSqlValueSyntax syntax LinkStatus where+ sqlValueSyntax = sqlValueSyntax . linkStatus2Text++instance (FromBackendRow be T.Text, BeamSqlBackend be) => FromBackendRow be LinkStatus where+ fromBackendRow = do+ txt <- fromBackendRow+ case text2LinkStatus txt of+ Nothing -> fail "Expected up, down for link status"+ Just ls -> pure ls+#endif++-- | Type-level representation of wireguard key+data KeyType = PublicKey | PrivateKey++class KnownKeyType (ty :: KeyType) where+ keyTypeVal :: Proxy ty -> KeyType+instance KnownKeyType 'PublicKey where+ keyTypeVal _ = PublicKey+instance KnownKeyType 'PrivateKey where+ keyTypeVal _ = PrivateKey++newtype WgKey (ty :: KeyType) = WgKey (VS.Vector 32 Word8)+ deriving newtype (Storable, Eq, Ord)++#ifdef WITH_BEAM+instance HasSqlEqualityCheck be BS.ByteString => HasSqlEqualityCheck be (WgKey ty)+#endif++wgEmptyKey :: WgKey a+wgEmptyKey = WgKey (VS.replicate 0)++-- | Convert any 'WgKey' into a public one. This is unsafe, and only used for testing+unsafeMkPublic :: WgKey a -> WgKey 'PublicKey+unsafeMkPublic (WgKey x) = WgKey x++instance KnownKeyType ty => Show (WgKey ty) where+ show (WgKey x) = case keyTypeVal (Proxy @ty) of+ PrivateKey -> "<private key>"+ PublicKey -> "WgKey (" ++ show x ++ ")"++instance BA.ByteArrayAccess (WgKey ty) where+ length (WgKey v) = VS.length v -- Always 32, but updates if it changes?+ withByteArray (WgKey v) withPtr =+ let (fPtr, _) = V.unsafeToForeignPtr0 (VS.fromSized v)+ in withForeignPtr fPtr (withPtr . castPtr)++#ifdef WITH_AESON+instance KnownKeyType ty => ToJSON (WgKey ty) where+ toJSON (WgKey key) =+ case keyTypeVal (Proxy @ty) of+ PrivateKey -> toJSON (id @T.Text "(redacted)")+ PublicKey -> toJSON . B64.encodeBase64 . BS.pack . VS.toList $ key++instance FromJSON (WgKey ty) where+ parseJSON x = do+ b64Encoded <- parseJSON x+ let eRes = B64.decodeBase64 (TE.encodeUtf8 b64Encoded)+ case eRes of+ Left err -> fail ("Could not decode key: " ++ T.unpack err)+ Right key ->+ case VS.toSized (V.fromList (BS.unpack key)) of+ Nothing -> fail ("key is not the right size")+ Just keySized -> pure (WgKey keySized)+#endif++#ifdef WITH_BEAM+instance FromBackendRow be BS.ByteString => FromBackendRow be (WgKey ty) where+ fromBackendRow = do+ bs <- fromBackendRow+ case VS.toSized (V.fromList (BS.unpack bs)) of+ Nothing -> fail "key is not the right size"+ Just keySized -> pure (WgKey keySized)++instance HasSqlValueSyntax syntax BS.ByteString => HasSqlValueSyntax syntax (WgKey ty) where+ sqlValueSyntax (WgKey key) = sqlValueSyntax (BS.pack . V.toList . VS.fromSized $ key)+#endif++newtype WgDeviceFlags = WgDeviceFlags Word32+ deriving (Show, Eq, Ord)+ deriving newtype (Storable, Bits)++instance Semigroup WgDeviceFlags where+ (<>) = coerce ((.|.) :: Word32 -> Word32 -> Word32)++instance Monoid WgDeviceFlags where+ mempty = WgDeviceFlags 0++wgDeviceReplacePeers, wgDeviceHasPrivateKey, wgDeviceHasPublicKey,+ wgDeviceHasListenPort, wgDeviceHasFwMark :: WgDeviceFlags+wgDeviceReplacePeers = WgDeviceFlags (#const WGDEVICE_REPLACE_PEERS)+wgDeviceHasPrivateKey = WgDeviceFlags (#const WGDEVICE_HAS_PRIVATE_KEY)+wgDeviceHasPublicKey = WgDeviceFlags (#const WGDEVICE_HAS_PUBLIC_KEY)+wgDeviceHasListenPort = WgDeviceFlags (#const WGDEVICE_HAS_LISTEN_PORT)+wgDeviceHasFwMark = WgDeviceFlags (#const WGDEVICE_HAS_FWMARK)++data WgDevice+ = WgDevice+ { wgDeviceName :: T.Text+ , wgDeviceIndex :: Word32+ , wgDeviceFlags :: WgDeviceFlags+ , wgDevicePubKey :: WgKey 'PublicKey+ , wgDevicePrivKey :: WgKey 'PrivateKey+ , wgDeviceFwMark :: Word32+ , wgDeviceListenPort :: Word16+ , wgDevicePeers :: [ WgPeer ]+ } deriving Show++foreign import ccall unsafe "ntohl" c_ntohl :: Word32 -> Word32+foreign import ccall unsafe "htonl" c_htonl :: Word32 -> Word32++instance Storable WgDevice where+ sizeOf _ = (#size wg_device)+ alignment _ = alignment (undefined :: Word32)++ peek p = do+ nm <- fromString <$> peekCString ((#ptr wg_device, name) p)+ ix <- (#peek wg_device, ifindex) p+ flags <- (#peek wg_device, flags) p+ pubkey <- (#peek wg_device, public_key) p+ privkey <- (#peek wg_device, private_key) p+ fwmark <- (#peek wg_device, fwmark) p+ listenport <- (#peek wg_device, listen_port) p+ peerPtr <- (#peek wg_device, first_peer) p+ peers <- readWgPeers peerPtr+ pure WgDevice { wgDeviceName = nm+ , wgDeviceIndex = ix+ , wgDeviceFlags = flags+ , wgDevicePubKey = pubkey+ , wgDevicePrivKey = privkey+ , wgDeviceFwMark = fwmark+ , wgDeviceListenPort = listenport+ , wgDevicePeers = peers }++ poke p d = do+ let ifName = wgDeviceName d+ ifNameStr = T.unpack ifName++ _ <- withCString ifNameStr $ \ifnamePtr ->+ c_strncpy ((#ptr wg_device, name) p) ifnamePtr (#const IFNAMSIZ)++ (#poke wg_device, ifindex) p (wgDeviceIndex d)+ (#poke wg_device, flags) p (wgDeviceFlags d)+ (#poke wg_device, public_key) p (wgDevicePubKey d)+ (#poke wg_device, private_key) p (wgDevicePrivKey d)+ (#poke wg_device, fwmark) p (wgDeviceFwMark d)+ (#poke wg_device, listen_port) p (wgDeviceListenPort d)++ (firstPeer, lastPeer) <- writeWgPeers (wgDevicePeers d)+ (#poke wg_device, first_peer) p firstPeer+ (#poke wg_device, last_peer) p lastPeer++newtype WgPeerFlags = WgPeerFlags Word32+ deriving (Show, Eq, Ord)+ deriving newtype (Storable, Bits)+#ifdef WITH_AESON+ deriving newtype (ToJSON, FromJSON)+#endif++instance Semigroup WgPeerFlags where+ (<>) = coerce ((.|.) :: Word32 -> Word32 -> Word32)++instance Monoid WgPeerFlags where+ mempty = WgPeerFlags 0++wgPeerRemoveMe, wgPeerReplaceAllowedIps, wgPeerHasPublicKey,+ wgPeerHasPresharedKey, wgPeerHasPersistentKeepaliveInterval :: WgPeerFlags+wgPeerRemoveMe = WgPeerFlags (#const WGPEER_REMOVE_ME)+wgPeerReplaceAllowedIps = WgPeerFlags (#const WGPEER_REPLACE_ALLOWEDIPS)+wgPeerHasPublicKey = WgPeerFlags (#const WGPEER_HAS_PUBLIC_KEY)+wgPeerHasPresharedKey = WgPeerFlags (#const WGPEER_HAS_PRESHARED_KEY)+wgPeerHasPersistentKeepaliveInterval = WgPeerFlags (#const WGPEER_HAS_PERSISTENT_KEEPALIVE_INTERVAL)++newtype WgSockAddr = WgSockAddr { unWgSockAddr :: SockAddr }+ deriving Show++#ifdef WITH_AESON+instance ToJSON WgSockAddr where+ toJSON (WgSockAddr a) = toJSON (show a)++instance FromJSON WgSockAddr where+ parseJSON = error "FromJSON{WgSockAddr}"+#endif++data WgPeer+ = WgPeer+ { wgPeerFlags :: WgPeerFlags+ , wgPeerKey :: WgKey 'PublicKey+ , wgPeerPsk :: WgKey 'PublicKey++ , wgPeerEndpoint :: WgSockAddr++ , wgPeerLastHandshakeTime :: TimeSpec+ , wgPeerRxBytes, wgPeerTxBytes :: Word64+ , wgPeerPersistentKeepaliveInterval :: Word16++ , wgPeerAllowedIPs :: [ IPRange ]+ } deriving (Show, Generic)++-- | Template for adding a new peer+wgNewPeer :: WgPeer+wgNewPeer = WgPeer+ { wgPeerFlags = mempty+ , wgPeerKey = wgEmptyKey+ , wgPeerPsk = wgEmptyKey+ , wgPeerEndpoint = WgSockAddr (SockAddrInet 0 0)+ , wgPeerLastHandshakeTime = 0+ , wgPeerRxBytes = 0+ , wgPeerTxBytes = 0+ , wgPeerPersistentKeepaliveInterval = 0+ , wgPeerAllowedIPs = []+ }++readWgPeers :: Ptr WgPeer -> IO [ WgPeer ]+readWgPeers = go pure+ where+ go a peerPtr+ | peerPtr == nullPtr = a []+ | otherwise = do+ peer <- peek peerPtr+ nextPeer <- (#peek wg_peer, next_peer) peerPtr+ go (a . (peer:)) nextPeer++writeWgPeers :: [WgPeer] -> IO (Ptr WgPeer, Ptr WgPeer)+writeWgPeers = go Nothing Nothing+ where+ go !firstPeer !lastPeer peers =+ case peers of+ [] -> pure ( fromMaybe nullPtr firstPeer+ , fromMaybe nullPtr lastPeer)++ (x:xs) -> do++ peerPtr <- malloc+ poke peerPtr x -- Store current peer+ (#poke wg_peer, next_peer) peerPtr nullPtr++ -- Update previous next pointer+ case lastPeer of+ Nothing -> pure ()+ Just lastPeerPtr ->+ (#poke wg_peer, next_peer) lastPeerPtr peerPtr++ go (Just (fromMaybe peerPtr firstPeer)) (Just peerPtr) xs++readAllowedIps :: Ptr IPRange -> IO [ IPRange ]+readAllowedIps = go pure+ where+ go a ipPtr+ | ipPtr == nullPtr = a []+ | otherwise = do+ family :: Word16 <- (#peek wg_allowedip, family) ipPtr+ addr <-+ case family of+ (#const AF_INET) -> do+ IPv4 ip4Network <- (#peek wg_allowedip, ip4) ipPtr+ pure (IP.fromIPv4 $ IPv4 (c_ntohl ip4Network))+ (#const AF_INET6) -> do+ let bytes = (#ptr wg_allowedip, ip6) ipPtr+ [a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15] <-+ mapM (peekElemOff bytes) [0..15]+ pure (IP.fromIPv6 (IPv6.fromOctets a0 a1 a2 a3+ a4 a5 a6 a7+ a8 a9 a10 a11+ a12 a13 a14 a15))+ _ -> fail "Bad IP in WgPeer ip range"+ cidr :: Word8 <- (#peek wg_allowedip, cidr) ipPtr++ nextPtr <- (#peek wg_allowedip, next_allowedip) ipPtr++ let ipr = IP.case_ (\addr -> IPRange4 $ IPv4Range addr cidr)+ (\addr -> IPRange6 $ IPv6Range addr cidr)+ addr++ go (a . (ipr :)) nextPtr++bytes64 :: Word64 -> ( Word8, Word8, Word8, Word8+ , Word8, Word8, Word8, Word8 )+bytes64 a = ( f 56, f 48, f 40, f 32+ , f 24, f 16, f 8, f 0 )+ where+ f l = fromIntegral ((a `shiftR` l) .&. 0xFF)++writeAllowedIps :: [ IPRange ] -> IO (Ptr IPRange, Ptr IPRange)+writeAllowedIps = go Nothing Nothing+ where+ continue !firstIp !lastIp !ipPtr xs = do+ -- Now set the next allowed ip to null+ (#poke wg_allowedip, next_allowedip) ipPtr nullPtr++ case lastIp of+ Nothing -> pure ()+ Just lastIpPtr ->+ (#poke wg_allowedip, next_allowedip) lastIpPtr ipPtr++ go (Just (fromMaybe ipPtr firstIp)) (Just ipPtr) xs++ go !firstIp !lastIp peers =+ case peers of+ [] -> pure ( fromMaybe nullPtr firstIp+ , fromMaybe nullPtr lastIp )++ (IPRange4 (IPv4Range (IPv4 ip4Network) cidr):xs) -> do+ ipPtr <- mallocBytes (#size wg_allowedip)+ (#poke wg_allowedip, family) ipPtr ((#const AF_INET) :: Word16)++ -- Write ip4 and convert to network byte order+ (#poke wg_allowedip, ip4) ipPtr ip4Network+ networkHost <- (#peek wg_allowedip, ip4) ipPtr+ (#poke wg_allowedip, ip4) ipPtr (c_htonl networkHost)++ (#poke wg_allowedip, cidr) ipPtr (cidr :: Word8)++ continue firstIp lastIp ipPtr xs++ (IPRange6 (IPv6Range (IPv6 (Word128 ipa ipb)) cidr):xs) -> do+ ipPtr <- mallocBytes (#size wg_allowedip)+ (#poke wg_allowedip, family) ipPtr ((#const AF_INET6) :: Word16)++ let (a0, a1, a2, a3, a4, a5, a6, a7) = bytes64 ipa+ (a8, a9, a10, a11, a12, a13, a14, a15) = bytes64 ipb+ bytes = (#ptr wg_allowedip, ip6) ipPtr+ mapM_ (uncurry (pokeElemOff bytes)) (zip [0..] [ a0, a1, a2, a3, a4, a5, a6, a7, a8+ , a9, a10, a11, a12, a13, a14, a15 ])++ (#poke wg_allowedip, cidr) ipPtr (cidr :: Word8)+ continue firstIp lastIp ipPtr xs++instance Storable WgPeer where+ sizeOf _ = (#size wg_peer)+ alignment _ = alignment (undefined :: Word32)++ peek p = do+ flags <- (#peek wg_peer, flags) p+ key <- (#peek wg_peer, public_key) p+ psk <- (#peek wg_peer, preshared_key) p+ ep <- peekSocketAddress ((#ptr wg_peer, endpoint) p)+ lasthstm <- (#peek wg_peer, last_handshake_time) p+ rx <- (#peek wg_peer, rx_bytes) p+ tx <- (#peek wg_peer, tx_bytes) p+ pkai <- (#peek wg_peer, persistent_keepalive_interval) p+ ipsPtr <- (#peek wg_peer, first_allowedip) p+ ips <- readAllowedIps ipsPtr+ pure WgPeer { wgPeerFlags = flags+ , wgPeerKey = key+ , wgPeerPsk = psk+ , wgPeerEndpoint = WgSockAddr ep+ , wgPeerLastHandshakeTime = lasthstm+ , wgPeerRxBytes = rx+ , wgPeerTxBytes = tx+ , wgPeerPersistentKeepaliveInterval = pkai+ , wgPeerAllowedIPs = ips }++ poke p peer = do+ (#poke wg_peer, flags) p (wgPeerFlags peer)+ (#poke wg_peer, public_key) p (wgPeerKey peer)+ (#poke wg_peer, preshared_key) p (wgPeerPsk peer)+ pokeSocketAddress ((#ptr wg_peer, endpoint) p) (unWgSockAddr (wgPeerEndpoint peer))++ -- The following three fields are not strictly necesary...+ (#poke wg_peer, last_handshake_time) p (wgPeerLastHandshakeTime peer)+ (#poke wg_peer, rx_bytes) p (wgPeerRxBytes peer)+ (#poke wg_peer, tx_bytes) p (wgPeerTxBytes peer)++ (#poke wg_peer, persistent_keepalive_interval) p (wgPeerPersistentKeepaliveInterval peer)++ (firstAllowedIp, lastAllowedIp) <- writeAllowedIps (wgPeerAllowedIPs peer)+ (#poke wg_peer, first_allowedip) p firstAllowedIp+ (#poke wg_peer, last_allowedip) p lastAllowedIp++readStringList :: ([String] -> [String]) -> CString -> IO [String]+readStringList a ptr = do+ s <- peekCString ptr+ if null s then pure (a [])+ else do+ readStringList (a . (s:)) (ptr `plusPtr` (length s + 1))++foreign import ccall unsafe "wg_list_device_names" c_wg_list_device_names :: IO CString++wgListDeviceNames :: MonadIO m => m [T.Text]+wgListDeviceNames = liftIO $ do+ devs <- c_wg_list_device_names+ res <- fmap (map fromString) (readStringList id devs)+ free devs++ errno <- getErrno+ if errno == eOK+ then pure res+ else throwErrno "wgListDeviceNames"++foreign import ccall unsafe "wg_get_device" c_wg_get_device :: Ptr (Ptr WgDevice) -> CString -> IO CInt+foreign import ccall unsafe "wg_free_device" c_wg_free_device :: Ptr WgDevice -> IO ()++wgGetDevice :: MonadIO m => T.Text -> m WgDevice+wgGetDevice iface =+ liftIO $+ alloca $ \wgPtrPtr ->+ withCString (T.unpack iface) $ \ifaceStr -> do+ _ <- c_wg_get_device wgPtrPtr ifaceStr+ errno <- getErrno+ if errno == eOK+ then do+ wgPtr <- peek wgPtrPtr+ wg <- peek wgPtr+ c_wg_free_device wgPtr+ pure wg+ else throwErrno "wgGetDevice"++foreign import ccall unsafe "wg_add_device" c_wg_add_device :: Ptr CChar -> IO CInt++wgAddDevice :: MonadIO m => T.Text -> m ()+wgAddDevice iface =+ liftIO $+ withCString (T.unpack iface) $ \ifaceStr -> do+ _ <- c_wg_add_device ifaceStr+ errno <- getErrno+ unless (errno == eOK) (throwErrno "wgAddDevice")++foreign import ccall unsafe "wg_del_device" c_wg_del_device :: Ptr CChar -> IO CInt++wgDelDevice :: MonadIO m => T.Text -> m ()+wgDelDevice iface =+ liftIO $+ withCString (T.unpack iface) $ \ifaceStr -> do+ _ <- c_wg_del_device ifaceStr+ errno <- getErrno+ unless (errno == eOK) (throwErrno "wgDelDevice")++foreign import ccall unsafe "wg_set_device" c_wg_set_device :: Ptr WgDevice -> IO CInt++wgSetDevice :: MonadIO m => WgDevice -> m ()+wgSetDevice dev =+ liftIO $+ bracket (mask $ \_ -> do -- This should not cause any exception...+ wgPtr <- malloc+ poke wgPtr dev+ pure wgPtr)++ c_wg_free_device $ \devPtr -> do+ _ <- c_wg_set_device devPtr+ errno <- getErrno+ unless (errno == eOK) (throwErrno "wgSetDevice");++-- Link status++foreign import ccall unsafe "wg_get_link_status" c_wg_get_link_status :: Ptr CChar -> Ptr CInt -> IO CInt++data LinkFlags+ = LinkFlags+ { lfStatus :: LinkStatus+ } deriving Show++wgGetLinkStatus :: MonadIO m => T.Text -> m LinkFlags+wgGetLinkStatus ifaceNm =+ liftIO $+ withCString (T.unpack ifaceNm) $ \ifaceStr ->+ alloca $ \flagsV -> do+ res <- c_wg_get_link_status ifaceStr flagsV+ if res == 0+ then do+ flags <- peek flagsV+ pure LinkFlags+ { lfStatus = if (flags .&. (#const IFF_UP)) > 0+ then LinkStatusUp+ else LinkStatusDown+ }+ else throwErrno "wgGetLinkStatus"++-- | Generate a wireguard private key+wgGeneratePrivateKey :: MonadIO m => m (WgKey 'PrivateKey)+wgGeneratePrivateKey = liftIO $ do+ key <- generateSecretKey+ BA.withByteArray key peek++-- | Derive public key from private one+wgGetPublicKey :: WgKey 'PrivateKey -> WgKey 'PublicKey+wgGetPublicKey key =+ case secretKey key of+ CryptoFailed err -> throw err+ CryptoPassed privKey ->+ unsafePerformIO $ BA.withByteArray (toPublic privKey) peek
+ System/Network/linkstatus.c view
@@ -0,0 +1,27 @@+#include <unistd.h>+#include <string.h>+#include <sys/ioctl.h>+#include <net/if.h>++int wg_get_link_status(const char *ifname, int *flags) {+ struct ifreq ifr;+ int fd;++ /* Copy name */+ memset(ifr.ifr_name, 0, sizeof(ifr.ifr_name));+ strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));++ fd = socket(AF_INET, SOCK_STREAM, 0);+ if ( fd < 0 )+ return -1;++ if ( ioctl(fd, SIOCGIFFLAGS, &ifr) < 0 )+ return -1;++ *flags = ifr.ifr_flags;++ close(fd);++ return 0;+}+
+ wireguard-hs.cabal view
@@ -0,0 +1,75 @@+cabal-version: 1.12++-- This file has been generated from package.yaml by hpack version 0.34.5.+--+-- see: https://github.com/sol/hpack++name: wireguard-hs+version: 0.1.0+synopsis: API for Linux Kernel Wireguard device management+category: Networking+license: LGPL+build-type: Simple++flag aeson+ description: Enable ToJSON/FromJSON instances with Aeson+ manual: True+ default: False++flag beam+ description: Enable Beam serializers/deserializers+ manual: True+ default: False++library+ exposed-modules:+ System.Network.Wireguard+ other-modules:+ Paths_wireguard_hs+ default-extensions:+ BangPatterns+ ForeignFunctionInterface+ CPP+ DerivingStrategies+ GeneralizedNewtypeDeriving+ DerivingVia+ DeriveAnyClass+ ScopedTypeVariables+ KindSignatures+ DataKinds+ TypeApplications+ DeriveGeneric+ OverloadedStrings+ include-dirs:+ wireguard/+ c-sources:+ wireguard/wireguard.c+ System/Network/linkstatus.c+ build-depends:+ base ==4.15.*+ , clock >=0.8.2 && <0.9+ , cryptonite ==0.29.*+ , ip ==1.7.*+ , memory ==0.16.*+ , network ==3.1.*+ , text ==1.2.*+ , time ==1.9.*+ , vector ==0.12.*+ , vector-sized ==1.4.*+ , wide-word ==0.1.*+ if flag(aeson)+ cpp-options: -DWITH_AESON=1+ build-depends:+ aeson ==2.0.*+ , base64 ==0.4.*+ if flag(beam)+ default-extensions:+ FlexibleInstances+ FlexibleContexts+ MultiParamTypeClasses+ UndecidableInstances+ cpp-options: -DWITH_BEAM=1+ build-depends:+ beam-core ==0.9.*+ , bytestring ==0.10.*+ default-language: Haskell2010
+ wireguard/wireguard.c view
@@ -0,0 +1,1752 @@+// SPDX-License-Identifier: LGPL-2.1++/*+ * Copyright (C) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.+ * Copyright (C) 2008-2012 Pablo Neira Ayuso <pablo@netfilter.org>.+ */++#define _GNU_SOURCE++#include <errno.h>+#include <linux/genetlink.h>+#include <linux/if_link.h>+#include <linux/netlink.h>+#include <linux/rtnetlink.h>+#include <netinet/in.h>+#include <stdbool.h>+#include <stdio.h>+#include <stdlib.h>+#include <string.h>+#include <sys/socket.h>+#include <time.h>+#include <unistd.h>+#include <fcntl.h>+#include <assert.h>++#include "wireguard.h"++/* wireguard.h netlink uapi: */++#define WG_GENL_NAME "wireguard"+#define WG_GENL_VERSION 1++enum wg_cmd {+ WG_CMD_GET_DEVICE,+ WG_CMD_SET_DEVICE,+ __WG_CMD_MAX+};++enum wgdevice_flag {+ WGDEVICE_F_REPLACE_PEERS = 1U << 0+};+enum wgdevice_attribute {+ WGDEVICE_A_UNSPEC,+ WGDEVICE_A_IFINDEX,+ WGDEVICE_A_IFNAME,+ WGDEVICE_A_PRIVATE_KEY,+ WGDEVICE_A_PUBLIC_KEY,+ WGDEVICE_A_FLAGS,+ WGDEVICE_A_LISTEN_PORT,+ WGDEVICE_A_FWMARK,+ WGDEVICE_A_PEERS,+ __WGDEVICE_A_LAST+};++enum wgpeer_flag {+ WGPEER_F_REMOVE_ME = 1U << 0,+ WGPEER_F_REPLACE_ALLOWEDIPS = 1U << 1+};+enum wgpeer_attribute {+ WGPEER_A_UNSPEC,+ WGPEER_A_PUBLIC_KEY,+ WGPEER_A_PRESHARED_KEY,+ WGPEER_A_FLAGS,+ WGPEER_A_ENDPOINT,+ WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL,+ WGPEER_A_LAST_HANDSHAKE_TIME,+ WGPEER_A_RX_BYTES,+ WGPEER_A_TX_BYTES,+ WGPEER_A_ALLOWEDIPS,+ WGPEER_A_PROTOCOL_VERSION,+ __WGPEER_A_LAST+};++enum wgallowedip_attribute {+ WGALLOWEDIP_A_UNSPEC,+ WGALLOWEDIP_A_FAMILY,+ WGALLOWEDIP_A_IPADDR,+ WGALLOWEDIP_A_CIDR_MASK,+ __WGALLOWEDIP_A_LAST+};++/* libmnl mini library: */++#define MNL_SOCKET_AUTOPID 0+#define MNL_SOCKET_BUFFER_SIZE (sysconf(_SC_PAGESIZE) < 8192L ? sysconf(_SC_PAGESIZE) : 8192L)+#define MNL_ALIGNTO 4+#define MNL_ALIGN(len) (((len)+MNL_ALIGNTO-1) & ~(MNL_ALIGNTO-1))+#define MNL_NLMSG_HDRLEN MNL_ALIGN(sizeof(struct nlmsghdr))+#define MNL_ATTR_HDRLEN MNL_ALIGN(sizeof(struct nlattr))++enum mnl_attr_data_type {+ MNL_TYPE_UNSPEC,+ MNL_TYPE_U8,+ MNL_TYPE_U16,+ MNL_TYPE_U32,+ MNL_TYPE_U64,+ MNL_TYPE_STRING,+ MNL_TYPE_FLAG,+ MNL_TYPE_MSECS,+ MNL_TYPE_NESTED,+ MNL_TYPE_NESTED_COMPAT,+ MNL_TYPE_NUL_STRING,+ MNL_TYPE_BINARY,+ MNL_TYPE_MAX,+};++#define mnl_attr_for_each(attr, nlh, offset) \+ for ((attr) = mnl_nlmsg_get_payload_offset((nlh), (offset)); \+ mnl_attr_ok((attr), (char *)mnl_nlmsg_get_payload_tail(nlh) - (char *)(attr)); \+ (attr) = mnl_attr_next(attr))++#define mnl_attr_for_each_nested(attr, nest) \+ for ((attr) = mnl_attr_get_payload(nest); \+ mnl_attr_ok((attr), (char *)mnl_attr_get_payload(nest) + mnl_attr_get_payload_len(nest) - (char *)(attr)); \+ (attr) = mnl_attr_next(attr))++#define mnl_attr_for_each_payload(payload, payload_size) \+ for ((attr) = (payload); \+ mnl_attr_ok((attr), (char *)(payload) + payload_size - (char *)(attr)); \+ (attr) = mnl_attr_next(attr))++#define MNL_CB_ERROR -1+#define MNL_CB_STOP 0+#define MNL_CB_OK 1++typedef int (*mnl_attr_cb_t)(const struct nlattr *attr, void *data);+typedef int (*mnl_cb_t)(const struct nlmsghdr *nlh, void *data);++#ifndef MNL_ARRAY_SIZE+#define MNL_ARRAY_SIZE(a) (sizeof(a)/sizeof((a)[0]))+#endif++static size_t mnl_nlmsg_size(size_t len)+{+ return len + MNL_NLMSG_HDRLEN;+}++static struct nlmsghdr *mnl_nlmsg_put_header(void *buf)+{+ int len = MNL_ALIGN(sizeof(struct nlmsghdr));+ struct nlmsghdr *nlh = buf;++ memset(buf, 0, len);+ nlh->nlmsg_len = len;+ return nlh;+}++static void *mnl_nlmsg_put_extra_header(struct nlmsghdr *nlh, size_t size)+{+ char *ptr = (char *)nlh + nlh->nlmsg_len;+ size_t len = MNL_ALIGN(size);+ nlh->nlmsg_len += len;+ memset(ptr, 0, len);+ return ptr;+}++static void *mnl_nlmsg_get_payload(const struct nlmsghdr *nlh)+{+ return (void *)nlh + MNL_NLMSG_HDRLEN;+}++static void *mnl_nlmsg_get_payload_offset(const struct nlmsghdr *nlh, size_t offset)+{+ return (void *)nlh + MNL_NLMSG_HDRLEN + MNL_ALIGN(offset);+}+++static bool mnl_nlmsg_ok(const struct nlmsghdr *nlh, int len)+{+ return len >= (int)sizeof(struct nlmsghdr) &&+ nlh->nlmsg_len >= sizeof(struct nlmsghdr) &&+ (int)nlh->nlmsg_len <= len;+}++static struct nlmsghdr *mnl_nlmsg_next(const struct nlmsghdr *nlh, int *len)+{+ *len -= MNL_ALIGN(nlh->nlmsg_len);+ return (struct nlmsghdr *)((void *)nlh + MNL_ALIGN(nlh->nlmsg_len));+}++static void *mnl_nlmsg_get_payload_tail(const struct nlmsghdr *nlh)+{+ return (void *)nlh + MNL_ALIGN(nlh->nlmsg_len);+}++static bool mnl_nlmsg_seq_ok(const struct nlmsghdr *nlh, unsigned int seq)+{+ return nlh->nlmsg_seq && seq ? nlh->nlmsg_seq == seq : true;+}++static bool mnl_nlmsg_portid_ok(const struct nlmsghdr *nlh, unsigned int portid)+{+ return nlh->nlmsg_pid && portid ? nlh->nlmsg_pid == portid : true;+}++static uint16_t mnl_attr_get_type(const struct nlattr *attr)+{+ return attr->nla_type & NLA_TYPE_MASK;+}++static uint16_t mnl_attr_get_payload_len(const struct nlattr *attr)+{+ return attr->nla_len - MNL_ATTR_HDRLEN;+}++static void *mnl_attr_get_payload(const struct nlattr *attr)+{+ return (void *)attr + MNL_ATTR_HDRLEN;+}++static bool mnl_attr_ok(const struct nlattr *attr, int len)+{+ return len >= (int)sizeof(struct nlattr) &&+ attr->nla_len >= sizeof(struct nlattr) &&+ (int)attr->nla_len <= len;+}++static struct nlattr *mnl_attr_next(const struct nlattr *attr)+{+ return (struct nlattr *)((void *)attr + MNL_ALIGN(attr->nla_len));+}++static int mnl_attr_type_valid(const struct nlattr *attr, uint16_t max)+{+ if (mnl_attr_get_type(attr) > max) {+ errno = EOPNOTSUPP;+ return -1;+ }+ return 1;+}++static int __mnl_attr_validate(const struct nlattr *attr,+ enum mnl_attr_data_type type, size_t exp_len)+{+ uint16_t attr_len = mnl_attr_get_payload_len(attr);+ const char *attr_data = mnl_attr_get_payload(attr);++ if (attr_len < exp_len) {+ errno = ERANGE;+ return -1;+ }+ switch(type) {+ case MNL_TYPE_FLAG:+ if (attr_len > 0) {+ errno = ERANGE;+ return -1;+ }+ break;+ case MNL_TYPE_NUL_STRING:+ if (attr_len == 0) {+ errno = ERANGE;+ return -1;+ }+ if (attr_data[attr_len-1] != '\0') {+ errno = EINVAL;+ return -1;+ }+ break;+ case MNL_TYPE_STRING:+ if (attr_len == 0) {+ errno = ERANGE;+ return -1;+ }+ break;+ case MNL_TYPE_NESTED:++ if (attr_len == 0)+ break;++ if (attr_len < MNL_ATTR_HDRLEN) {+ errno = ERANGE;+ return -1;+ }+ break;+ default:++ break;+ }+ if (exp_len && attr_len > exp_len) {+ errno = ERANGE;+ return -1;+ }+ return 0;+}++static const size_t mnl_attr_data_type_len[MNL_TYPE_MAX] = {+ [MNL_TYPE_U8] = sizeof(uint8_t),+ [MNL_TYPE_U16] = sizeof(uint16_t),+ [MNL_TYPE_U32] = sizeof(uint32_t),+ [MNL_TYPE_U64] = sizeof(uint64_t),+ [MNL_TYPE_MSECS] = sizeof(uint64_t),+};++static int mnl_attr_validate(const struct nlattr *attr, enum mnl_attr_data_type type)+{+ int exp_len;++ if (type >= MNL_TYPE_MAX) {+ errno = EINVAL;+ return -1;+ }+ exp_len = mnl_attr_data_type_len[type];+ return __mnl_attr_validate(attr, type, exp_len);+}++static int mnl_attr_parse(const struct nlmsghdr *nlh, unsigned int offset,+ mnl_attr_cb_t cb, void *data)+{+ int ret = MNL_CB_OK;+ const struct nlattr *attr;++ mnl_attr_for_each(attr, nlh, offset)+ if ((ret = cb(attr, data)) <= MNL_CB_STOP)+ return ret;+ return ret;+}++static int mnl_attr_parse_nested(const struct nlattr *nested, mnl_attr_cb_t cb,+ void *data)+{+ int ret = MNL_CB_OK;+ const struct nlattr *attr;++ mnl_attr_for_each_nested(attr, nested)+ if ((ret = cb(attr, data)) <= MNL_CB_STOP)+ return ret;+ return ret;+}++static uint8_t mnl_attr_get_u8(const struct nlattr *attr)+{+ return *((uint8_t *)mnl_attr_get_payload(attr));+}++static uint16_t mnl_attr_get_u16(const struct nlattr *attr)+{+ return *((uint16_t *)mnl_attr_get_payload(attr));+}++static uint32_t mnl_attr_get_u32(const struct nlattr *attr)+{+ return *((uint32_t *)mnl_attr_get_payload(attr));+}+++static uint64_t mnl_attr_get_u64(const struct nlattr *attr)+{+ uint64_t tmp;+ memcpy(&tmp, mnl_attr_get_payload(attr), sizeof(tmp));+ return tmp;+}++static const char *mnl_attr_get_str(const struct nlattr *attr)+{+ return mnl_attr_get_payload(attr);+}++static void mnl_attr_put(struct nlmsghdr *nlh, uint16_t type, size_t len,+ const void *data)+{+ struct nlattr *attr = mnl_nlmsg_get_payload_tail(nlh);+ uint16_t payload_len = MNL_ALIGN(sizeof(struct nlattr)) + len;+ int pad;++ attr->nla_type = type;+ attr->nla_len = payload_len;+ memcpy(mnl_attr_get_payload(attr), data, len);+ nlh->nlmsg_len += MNL_ALIGN(payload_len);+ pad = MNL_ALIGN(len) - len;+ if (pad > 0)+ memset(mnl_attr_get_payload(attr) + len, 0, pad);+}++static void mnl_attr_put_u16(struct nlmsghdr *nlh, uint16_t type, uint16_t data)+{+ mnl_attr_put(nlh, type, sizeof(uint16_t), &data);+}++static void mnl_attr_put_u32(struct nlmsghdr *nlh, uint16_t type, uint32_t data)+{+ mnl_attr_put(nlh, type, sizeof(uint32_t), &data);+}++static void mnl_attr_put_strz(struct nlmsghdr *nlh, uint16_t type, const char *data)+{+ mnl_attr_put(nlh, type, strlen(data)+1, data);+}++static struct nlattr *mnl_attr_nest_start(struct nlmsghdr *nlh, uint16_t type)+{+ struct nlattr *start = mnl_nlmsg_get_payload_tail(nlh);++ start->nla_type = NLA_F_NESTED | type;+ nlh->nlmsg_len += MNL_ALIGN(sizeof(struct nlattr));+ return start;+}++static bool mnl_attr_put_check(struct nlmsghdr *nlh, size_t buflen,+ uint16_t type, size_t len, const void *data)+{+ if (nlh->nlmsg_len + MNL_ATTR_HDRLEN + MNL_ALIGN(len) > buflen)+ return false;+ mnl_attr_put(nlh, type, len, data);+ return true;+}++static bool mnl_attr_put_u8_check(struct nlmsghdr *nlh, size_t buflen,+ uint16_t type, uint8_t data)+{+ return mnl_attr_put_check(nlh, buflen, type, sizeof(uint8_t), &data);+}+++static bool mnl_attr_put_u16_check(struct nlmsghdr *nlh, size_t buflen,+ uint16_t type, uint16_t data)+{+ return mnl_attr_put_check(nlh, buflen, type, sizeof(uint16_t), &data);+}+++static bool mnl_attr_put_u32_check(struct nlmsghdr *nlh, size_t buflen,+ uint16_t type, uint32_t data)+{+ return mnl_attr_put_check(nlh, buflen, type, sizeof(uint32_t), &data);+}++static struct nlattr *mnl_attr_nest_start_check(struct nlmsghdr *nlh, size_t buflen,+ uint16_t type)+{+ if (nlh->nlmsg_len + MNL_ATTR_HDRLEN > buflen)+ return NULL;+ return mnl_attr_nest_start(nlh, type);+}++static void mnl_attr_nest_end(struct nlmsghdr *nlh, struct nlattr *start)+{+ start->nla_len = mnl_nlmsg_get_payload_tail(nlh) - (void *)start;+}++static void mnl_attr_nest_cancel(struct nlmsghdr *nlh, struct nlattr *start)+{+ nlh->nlmsg_len -= mnl_nlmsg_get_payload_tail(nlh) - (void *)start;+}++static int mnl_cb_noop(const struct nlmsghdr *nlh, void *data)+{+ return MNL_CB_OK;+}++static int mnl_cb_error(const struct nlmsghdr *nlh, void *data)+{+ const struct nlmsgerr *err = mnl_nlmsg_get_payload(nlh);++ if (nlh->nlmsg_len < mnl_nlmsg_size(sizeof(struct nlmsgerr))) {+ errno = EBADMSG;+ return MNL_CB_ERROR;+ }++ if (err->error < 0)+ errno = -err->error;+ else+ errno = err->error;++ return err->error == 0 ? MNL_CB_STOP : MNL_CB_ERROR;+}++static int mnl_cb_stop(const struct nlmsghdr *nlh, void *data)+{+ return MNL_CB_STOP;+}++static const mnl_cb_t default_cb_array[NLMSG_MIN_TYPE] = {+ [NLMSG_NOOP] = mnl_cb_noop,+ [NLMSG_ERROR] = mnl_cb_error,+ [NLMSG_DONE] = mnl_cb_stop,+ [NLMSG_OVERRUN] = mnl_cb_noop,+};++static int __mnl_cb_run(const void *buf, size_t numbytes,+ unsigned int seq, unsigned int portid,+ mnl_cb_t cb_data, void *data,+ const mnl_cb_t *cb_ctl_array,+ unsigned int cb_ctl_array_len)+{+ int ret = MNL_CB_OK, len = numbytes;+ const struct nlmsghdr *nlh = buf;++ while (mnl_nlmsg_ok(nlh, len)) {++ if (!mnl_nlmsg_portid_ok(nlh, portid)) {+ errno = ESRCH;+ return -1;+ }++ if (!mnl_nlmsg_seq_ok(nlh, seq)) {+ errno = EPROTO;+ return -1;+ }+++ if (nlh->nlmsg_flags & NLM_F_DUMP_INTR) {+ errno = EINTR;+ return -1;+ }+++ if (nlh->nlmsg_type >= NLMSG_MIN_TYPE) {+ if (cb_data){+ ret = cb_data(nlh, data);+ if (ret <= MNL_CB_STOP)+ goto out;+ }+ } else if (nlh->nlmsg_type < cb_ctl_array_len) {+ if (cb_ctl_array && cb_ctl_array[nlh->nlmsg_type]) {+ ret = cb_ctl_array[nlh->nlmsg_type](nlh, data);+ if (ret <= MNL_CB_STOP)+ goto out;+ }+ } else if (default_cb_array[nlh->nlmsg_type]) {+ ret = default_cb_array[nlh->nlmsg_type](nlh, data);+ if (ret <= MNL_CB_STOP)+ goto out;+ }+ nlh = mnl_nlmsg_next(nlh, &len);+ }+out:+ return ret;+}++static int mnl_cb_run2(const void *buf, size_t numbytes, unsigned int seq,+ unsigned int portid, mnl_cb_t cb_data, void *data,+ const mnl_cb_t *cb_ctl_array, unsigned int cb_ctl_array_len)+{+ return __mnl_cb_run(buf, numbytes, seq, portid, cb_data, data,+ cb_ctl_array, cb_ctl_array_len);+}++static int mnl_cb_run(const void *buf, size_t numbytes, unsigned int seq,+ unsigned int portid, mnl_cb_t cb_data, void *data)+{+ return __mnl_cb_run(buf, numbytes, seq, portid, cb_data, data, NULL, 0);+}++struct mnl_socket {+ int fd;+ struct sockaddr_nl addr;+};++static unsigned int mnl_socket_get_portid(const struct mnl_socket *nl)+{+ return nl->addr.nl_pid;+}++static struct mnl_socket *__mnl_socket_open(int bus, int flags)+{+ struct mnl_socket *nl;++ nl = calloc(1, sizeof(struct mnl_socket));+ if (nl == NULL)+ return NULL;++ nl->fd = socket(AF_NETLINK, SOCK_RAW | flags, bus);+ if (nl->fd == -1) {+ free(nl);+ return NULL;+ }++ return nl;+}++static struct mnl_socket *mnl_socket_open(int bus)+{+ return __mnl_socket_open(bus, 0);+}+++static int mnl_socket_bind(struct mnl_socket *nl, unsigned int groups, pid_t pid)+{+ int ret;+ socklen_t addr_len;++ nl->addr.nl_family = AF_NETLINK;+ nl->addr.nl_groups = groups;+ nl->addr.nl_pid = pid;++ ret = bind(nl->fd, (struct sockaddr *) &nl->addr, sizeof (nl->addr));+ if (ret < 0)+ return ret;++ addr_len = sizeof(nl->addr);+ ret = getsockname(nl->fd, (struct sockaddr *) &nl->addr, &addr_len);+ if (ret < 0)+ return ret;++ if (addr_len != sizeof(nl->addr)) {+ errno = EINVAL;+ return -1;+ }+ if (nl->addr.nl_family != AF_NETLINK) {+ errno = EINVAL;+ return -1;+ }+ return 0;+}+++static ssize_t mnl_socket_sendto(const struct mnl_socket *nl, const void *buf,+ size_t len)+{+ static const struct sockaddr_nl snl = {+ .nl_family = AF_NETLINK+ };+ return sendto(nl->fd, buf, len, 0,+ (struct sockaddr *) &snl, sizeof(snl));+}+++static ssize_t mnl_socket_recvfrom(const struct mnl_socket *nl, void *buf,+ size_t bufsiz)+{+ ssize_t ret;+ struct sockaddr_nl addr;+ struct iovec iov = {+ .iov_base = buf,+ .iov_len = bufsiz,+ };+ struct msghdr msg = {+ .msg_name = &addr,+ .msg_namelen = sizeof(struct sockaddr_nl),+ .msg_iov = &iov,+ .msg_iovlen = 1,+ .msg_control = NULL,+ .msg_controllen = 0,+ .msg_flags = 0,+ };+ ret = recvmsg(nl->fd, &msg, 0);+ if (ret == -1)+ return ret;++ if (msg.msg_flags & MSG_TRUNC) {+ errno = ENOSPC;+ return -1;+ }+ if (msg.msg_namelen != sizeof(struct sockaddr_nl)) {+ errno = EINVAL;+ return -1;+ }+ return ret;+}++static int mnl_socket_close(struct mnl_socket *nl)+{+ int ret = close(nl->fd);+ free(nl);+ return ret;+}++/* mnlg mini library: */++struct mnlg_socket {+ struct mnl_socket *nl;+ char *buf;+ uint16_t id;+ uint8_t version;+ unsigned int seq;+ unsigned int portid;+};++static struct nlmsghdr *__mnlg_msg_prepare(struct mnlg_socket *nlg, uint8_t cmd,+ uint16_t flags, uint16_t id,+ uint8_t version)+{+ struct nlmsghdr *nlh;+ struct genlmsghdr *genl;++ nlh = mnl_nlmsg_put_header(nlg->buf);+ nlh->nlmsg_type = id;+ nlh->nlmsg_flags = flags;+ nlg->seq = time(NULL);+ nlh->nlmsg_seq = nlg->seq;++ genl = mnl_nlmsg_put_extra_header(nlh, sizeof(struct genlmsghdr));+ genl->cmd = cmd;+ genl->version = version;++ return nlh;+}++static struct nlmsghdr *mnlg_msg_prepare(struct mnlg_socket *nlg, uint8_t cmd,+ uint16_t flags)+{+ return __mnlg_msg_prepare(nlg, cmd, flags, nlg->id, nlg->version);+}++static int mnlg_socket_send(struct mnlg_socket *nlg, const struct nlmsghdr *nlh)+{+ return mnl_socket_sendto(nlg->nl, nlh, nlh->nlmsg_len);+}++static int mnlg_cb_noop(const struct nlmsghdr *nlh, void *data)+{+ (void)nlh;+ (void)data;+ return MNL_CB_OK;+}++static int mnlg_cb_error(const struct nlmsghdr *nlh, void *data)+{+ const struct nlmsgerr *err = mnl_nlmsg_get_payload(nlh);+ (void)data;++ if (nlh->nlmsg_len < mnl_nlmsg_size(sizeof(struct nlmsgerr))) {+ errno = EBADMSG;+ return MNL_CB_ERROR;+ }+ /* Netlink subsystems returns the errno value with different signess */+ if (err->error < 0)+ errno = -err->error;+ else+ errno = err->error;++ return err->error == 0 ? MNL_CB_STOP : MNL_CB_ERROR;+}++static int mnlg_cb_stop(const struct nlmsghdr *nlh, void *data)+{+ (void)data;+ if (nlh->nlmsg_flags & NLM_F_MULTI && nlh->nlmsg_len == mnl_nlmsg_size(sizeof(int))) {+ int error = *(int *)mnl_nlmsg_get_payload(nlh);+ /* Netlink subsystems returns the errno value with different signess */+ if (error < 0)+ errno = -error;+ else+ errno = error;++ return error == 0 ? MNL_CB_STOP : MNL_CB_ERROR;+ }+ return MNL_CB_STOP;+}++static const mnl_cb_t mnlg_cb_array[] = {+ [NLMSG_NOOP] = mnlg_cb_noop,+ [NLMSG_ERROR] = mnlg_cb_error,+ [NLMSG_DONE] = mnlg_cb_stop,+ [NLMSG_OVERRUN] = mnlg_cb_noop,+};++static int mnlg_socket_recv_run(struct mnlg_socket *nlg, mnl_cb_t data_cb, void *data)+{+ int err;++ do {+ err = mnl_socket_recvfrom(nlg->nl, nlg->buf,+ MNL_SOCKET_BUFFER_SIZE);+ if (err <= 0)+ break;+ err = mnl_cb_run2(nlg->buf, err, nlg->seq, nlg->portid,+ data_cb, data, mnlg_cb_array, MNL_ARRAY_SIZE(mnlg_cb_array));+ } while (err > 0);++ return err;+}++static int get_family_id_attr_cb(const struct nlattr *attr, void *data)+{+ const struct nlattr **tb = data;+ int type = mnl_attr_get_type(attr);++ if (mnl_attr_type_valid(attr, CTRL_ATTR_MAX) < 0)+ return MNL_CB_ERROR;++ if (type == CTRL_ATTR_FAMILY_ID &&+ mnl_attr_validate(attr, MNL_TYPE_U16) < 0)+ return MNL_CB_ERROR;+ tb[type] = attr;+ return MNL_CB_OK;+}++static int get_family_id_cb(const struct nlmsghdr *nlh, void *data)+{+ uint16_t *p_id = data;+ struct nlattr *tb[CTRL_ATTR_MAX + 1] = { 0 };++ mnl_attr_parse(nlh, sizeof(struct genlmsghdr), get_family_id_attr_cb, tb);+ if (!tb[CTRL_ATTR_FAMILY_ID])+ return MNL_CB_ERROR;+ *p_id = mnl_attr_get_u16(tb[CTRL_ATTR_FAMILY_ID]);+ return MNL_CB_OK;+}++static struct mnlg_socket *mnlg_socket_open(const char *family_name, uint8_t version)+{+ struct mnlg_socket *nlg;+ struct nlmsghdr *nlh;+ int err;++ nlg = malloc(sizeof(*nlg));+ if (!nlg)+ return NULL;++ err = -ENOMEM;+ nlg->buf = malloc(MNL_SOCKET_BUFFER_SIZE);+ if (!nlg->buf)+ goto err_buf_alloc;++ nlg->nl = mnl_socket_open(NETLINK_GENERIC);+ if (!nlg->nl) {+ err = -errno;+ goto err_mnl_socket_open;+ }++ if (mnl_socket_bind(nlg->nl, 0, MNL_SOCKET_AUTOPID) < 0) {+ err = -errno;+ goto err_mnl_socket_bind;+ }++ nlg->portid = mnl_socket_get_portid(nlg->nl);++ nlh = __mnlg_msg_prepare(nlg, CTRL_CMD_GETFAMILY,+ NLM_F_REQUEST | NLM_F_ACK, GENL_ID_CTRL, 1);+ mnl_attr_put_strz(nlh, CTRL_ATTR_FAMILY_NAME, family_name);++ if (mnlg_socket_send(nlg, nlh) < 0) {+ err = -errno;+ goto err_mnlg_socket_send;+ }++ errno = 0;+ if (mnlg_socket_recv_run(nlg, get_family_id_cb, &nlg->id) < 0) {+ errno = errno == ENOENT ? EPROTONOSUPPORT : errno;+ err = errno ? -errno : -ENOSYS;+ goto err_mnlg_socket_recv_run;+ }++ nlg->version = version;+ errno = 0;+ return nlg;++err_mnlg_socket_recv_run:+err_mnlg_socket_send:+err_mnl_socket_bind:+ mnl_socket_close(nlg->nl);+err_mnl_socket_open:+ free(nlg->buf);+err_buf_alloc:+ free(nlg);+ errno = -err;+ return NULL;+}++static void mnlg_socket_close(struct mnlg_socket *nlg)+{+ mnl_socket_close(nlg->nl);+ free(nlg->buf);+ free(nlg);+}++/* wireguard-specific parts: */++struct string_list {+ char *buffer;+ size_t len;+ size_t cap;+};++static int string_list_add(struct string_list *list, const char *str)+{+ size_t len = strlen(str) + 1;++ if (len == 1)+ return 0;++ if (len >= list->cap - list->len) {+ char *new_buffer;+ size_t new_cap = list->cap * 2;++ if (new_cap < list->len +len + 1)+ new_cap = list->len + len + 1;+ new_buffer = realloc(list->buffer, new_cap);+ if (!new_buffer)+ return -errno;+ list->buffer = new_buffer;+ list->cap = new_cap;+ }+ memcpy(list->buffer + list->len, str, len);+ list->len += len;+ list->buffer[list->len] = '\0';+ return 0;+}++struct interface {+ const char *name;+ bool is_wireguard;+};++static int parse_linkinfo(const struct nlattr *attr, void *data)+{+ struct interface *interface = data;++ if (mnl_attr_get_type(attr) == IFLA_INFO_KIND && !strcmp(WG_GENL_NAME, mnl_attr_get_str(attr)))+ interface->is_wireguard = true;+ return MNL_CB_OK;+}++static int parse_infomsg(const struct nlattr *attr, void *data)+{+ struct interface *interface = data;++ if (mnl_attr_get_type(attr) == IFLA_LINKINFO)+ return mnl_attr_parse_nested(attr, parse_linkinfo, data);+ else if (mnl_attr_get_type(attr) == IFLA_IFNAME)+ interface->name = mnl_attr_get_str(attr);+ return MNL_CB_OK;+}++static int read_devices_cb(const struct nlmsghdr *nlh, void *data)+{+ struct string_list *list = data;+ struct interface interface = { 0 };+ int ret;++ ret = mnl_attr_parse(nlh, sizeof(struct ifinfomsg), parse_infomsg, &interface);+ if (ret != MNL_CB_OK)+ return ret;+ if (interface.name && interface.is_wireguard)+ ret = string_list_add(list, interface.name);+ if (ret < 0)+ return ret;+ if (nlh->nlmsg_type != NLMSG_DONE)+ return MNL_CB_OK + 1;+ return MNL_CB_OK;+}++static int fetch_device_names(struct string_list *list)+{+ struct mnl_socket *nl = NULL;+ char *rtnl_buffer = NULL;+ size_t message_len;+ unsigned int portid, seq;+ ssize_t len;+ int ret = 0;+ struct nlmsghdr *nlh;+ struct ifinfomsg *ifm;++ ret = -ENOMEM;+ rtnl_buffer = calloc(MNL_SOCKET_BUFFER_SIZE, 1);+ if (!rtnl_buffer)+ goto cleanup;++ nl = mnl_socket_open(NETLINK_ROUTE);+ if (!nl) {+ ret = -errno;+ goto cleanup;+ }++ if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) {+ ret = -errno;+ goto cleanup;+ }++ seq = time(NULL);+ portid = mnl_socket_get_portid(nl);+ nlh = mnl_nlmsg_put_header(rtnl_buffer);+ nlh->nlmsg_type = RTM_GETLINK;+ nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP;+ nlh->nlmsg_seq = seq;+ ifm = mnl_nlmsg_put_extra_header(nlh, sizeof(*ifm));+ ifm->ifi_family = AF_UNSPEC;+ message_len = nlh->nlmsg_len;++ if (mnl_socket_sendto(nl, rtnl_buffer, message_len) < 0) {+ ret = -errno;+ goto cleanup;+ }++another:+ if ((len = mnl_socket_recvfrom(nl, rtnl_buffer, MNL_SOCKET_BUFFER_SIZE)) < 0) {+ ret = -errno;+ goto cleanup;+ }+ if ((len = mnl_cb_run(rtnl_buffer, len, seq, portid, read_devices_cb, list)) < 0) {+ /* Netlink returns NLM_F_DUMP_INTR if the set of all tunnels changed+ * during the dump. That's unfortunate, but is pretty common on busy+ * systems that are adding and removing tunnels all the time. Rather+ * than retrying, potentially indefinitely, we just work with the+ * partial results. */+ if (errno != EINTR) {+ ret = -errno;+ goto cleanup;+ }+ }+ if (len == MNL_CB_OK + 1)+ goto another;+ ret = 0;++cleanup:+ free(rtnl_buffer);+ if (nl)+ mnl_socket_close(nl);+ return ret;+}++static int add_del_iface(const char *ifname, bool add)+{+ struct mnl_socket *nl = NULL;+ char *rtnl_buffer;+ ssize_t len;+ int ret;+ struct nlmsghdr *nlh;+ struct ifinfomsg *ifm;+ struct nlattr *nest;++ rtnl_buffer = calloc(MNL_SOCKET_BUFFER_SIZE, 1);+ if (!rtnl_buffer) {+ ret = -ENOMEM;+ goto cleanup;+ }++ nl = mnl_socket_open(NETLINK_ROUTE);+ if (!nl) {+ ret = -errno;+ goto cleanup;+ }++ if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) {+ ret = -errno;+ goto cleanup;+ }++ nlh = mnl_nlmsg_put_header(rtnl_buffer);+ nlh->nlmsg_type = add ? RTM_NEWLINK : RTM_DELLINK;+ nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | (add ? NLM_F_CREATE | NLM_F_EXCL : 0);+ nlh->nlmsg_seq = time(NULL);+ ifm = mnl_nlmsg_put_extra_header(nlh, sizeof(*ifm));+ ifm->ifi_family = AF_UNSPEC;+ mnl_attr_put_strz(nlh, IFLA_IFNAME, ifname);+ nest = mnl_attr_nest_start(nlh, IFLA_LINKINFO);+ mnl_attr_put_strz(nlh, IFLA_INFO_KIND, WG_GENL_NAME);+ mnl_attr_nest_end(nlh, nest);++ if (mnl_socket_sendto(nl, rtnl_buffer, nlh->nlmsg_len) < 0) {+ ret = -errno;+ goto cleanup;+ }+ if ((len = mnl_socket_recvfrom(nl, rtnl_buffer, MNL_SOCKET_BUFFER_SIZE)) < 0) {+ ret = -errno;+ goto cleanup;+ }+ if (mnl_cb_run(rtnl_buffer, len, nlh->nlmsg_seq, mnl_socket_get_portid(nl), NULL, NULL) < 0) {+ ret = -errno;+ goto cleanup;+ }+ ret = 0;++cleanup:+ free(rtnl_buffer);+ if (nl)+ mnl_socket_close(nl);+ return ret;+}++int wg_set_device(wg_device *dev)+{+ int ret = 0;+ wg_peer *peer = NULL;+ wg_allowedip *allowedip = NULL;+ struct nlattr *peers_nest, *peer_nest, *allowedips_nest, *allowedip_nest;+ struct nlmsghdr *nlh;+ struct mnlg_socket *nlg;++ nlg = mnlg_socket_open(WG_GENL_NAME, WG_GENL_VERSION);+ if (!nlg)+ return -errno;++again:+ nlh = mnlg_msg_prepare(nlg, WG_CMD_SET_DEVICE, NLM_F_REQUEST | NLM_F_ACK);+ mnl_attr_put_strz(nlh, WGDEVICE_A_IFNAME, dev->name);++ if (!peer) {+ uint32_t flags = 0;++ if (dev->flags & WGDEVICE_HAS_PRIVATE_KEY)+ mnl_attr_put(nlh, WGDEVICE_A_PRIVATE_KEY, sizeof(dev->private_key), dev->private_key);+ if (dev->flags & WGDEVICE_HAS_LISTEN_PORT)+ mnl_attr_put_u16(nlh, WGDEVICE_A_LISTEN_PORT, dev->listen_port);+ if (dev->flags & WGDEVICE_HAS_FWMARK)+ mnl_attr_put_u32(nlh, WGDEVICE_A_FWMARK, dev->fwmark);+ if (dev->flags & WGDEVICE_REPLACE_PEERS)+ flags |= WGDEVICE_F_REPLACE_PEERS;+ if (flags)+ mnl_attr_put_u32(nlh, WGDEVICE_A_FLAGS, flags);+ }+ if (!dev->first_peer)+ goto send;+ peers_nest = peer_nest = allowedips_nest = allowedip_nest = NULL;+ peers_nest = mnl_attr_nest_start(nlh, WGDEVICE_A_PEERS);+ for (peer = peer ? peer : dev->first_peer; peer; peer = peer->next_peer) {+ uint32_t flags = 0;++ peer_nest = mnl_attr_nest_start_check(nlh, MNL_SOCKET_BUFFER_SIZE, 0);+ if (!peer_nest)+ goto toobig_peers;+ if (!mnl_attr_put_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGPEER_A_PUBLIC_KEY, sizeof(peer->public_key), peer->public_key))+ goto toobig_peers;+ if (peer->flags & WGPEER_REMOVE_ME)+ flags |= WGPEER_F_REMOVE_ME;+ if (!allowedip) {+ if (peer->flags & WGPEER_REPLACE_ALLOWEDIPS)+ flags |= WGPEER_F_REPLACE_ALLOWEDIPS;+ if (peer->flags & WGPEER_HAS_PRESHARED_KEY) {+ if (!mnl_attr_put_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGPEER_A_PRESHARED_KEY, sizeof(peer->preshared_key), peer->preshared_key))+ goto toobig_peers;+ }+ if (peer->endpoint.addr.sa_family == AF_INET) {+ if (!mnl_attr_put_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGPEER_A_ENDPOINT, sizeof(peer->endpoint.addr4), &peer->endpoint.addr4))+ goto toobig_peers;+ } else if (peer->endpoint.addr.sa_family == AF_INET6) {+ if (!mnl_attr_put_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGPEER_A_ENDPOINT, sizeof(peer->endpoint.addr6), &peer->endpoint.addr6))+ goto toobig_peers;+ }+ if (peer->flags & WGPEER_HAS_PERSISTENT_KEEPALIVE_INTERVAL) {+ if (!mnl_attr_put_u16_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL, peer->persistent_keepalive_interval))+ goto toobig_peers;+ }+ }+ if (flags) {+ if (!mnl_attr_put_u32_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGPEER_A_FLAGS, flags))+ goto toobig_peers;+ }+ if (peer->first_allowedip) {+ if (!allowedip)+ allowedip = peer->first_allowedip;+ allowedips_nest = mnl_attr_nest_start_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGPEER_A_ALLOWEDIPS);+ if (!allowedips_nest)+ goto toobig_allowedips;+ for (; allowedip; allowedip = allowedip->next_allowedip) {+ allowedip_nest = mnl_attr_nest_start_check(nlh, MNL_SOCKET_BUFFER_SIZE, 0);+ if (!allowedip_nest)+ goto toobig_allowedips;+ if (!mnl_attr_put_u16_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGALLOWEDIP_A_FAMILY, allowedip->family))+ goto toobig_allowedips;+ if (allowedip->family == AF_INET) {+ if (!mnl_attr_put_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGALLOWEDIP_A_IPADDR, sizeof(allowedip->ip4), &allowedip->ip4))+ goto toobig_allowedips;+ } else if (allowedip->family == AF_INET6) {+ if (!mnl_attr_put_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGALLOWEDIP_A_IPADDR, sizeof(allowedip->ip6), &allowedip->ip6))+ goto toobig_allowedips;+ }+ if (!mnl_attr_put_u8_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGALLOWEDIP_A_CIDR_MASK, allowedip->cidr))+ goto toobig_allowedips;+ mnl_attr_nest_end(nlh, allowedip_nest);+ allowedip_nest = NULL;+ }+ mnl_attr_nest_end(nlh, allowedips_nest);+ allowedips_nest = NULL;+ }++ mnl_attr_nest_end(nlh, peer_nest);+ peer_nest = NULL;+ }+ mnl_attr_nest_end(nlh, peers_nest);+ peers_nest = NULL;+ goto send;+toobig_allowedips:+ if (allowedip_nest)+ mnl_attr_nest_cancel(nlh, allowedip_nest);+ if (allowedips_nest)+ mnl_attr_nest_end(nlh, allowedips_nest);+ mnl_attr_nest_end(nlh, peer_nest);+ mnl_attr_nest_end(nlh, peers_nest);+ goto send;+toobig_peers:+ if (peer_nest)+ mnl_attr_nest_cancel(nlh, peer_nest);+ mnl_attr_nest_end(nlh, peers_nest);+ goto send;+send:+ if (mnlg_socket_send(nlg, nlh) < 0) {+ ret = -errno;+ goto out;+ }+ errno = 0;+ if (mnlg_socket_recv_run(nlg, NULL, NULL) < 0) {+ ret = errno ? -errno : -EINVAL;+ goto out;+ }+ if (peer)+ goto again;++out:+ mnlg_socket_close(nlg);+ errno = -ret;+ return ret;+}++static int parse_allowedip(const struct nlattr *attr, void *data)+{+ wg_allowedip *allowedip = data;++ switch (mnl_attr_get_type(attr)) {+ case WGALLOWEDIP_A_UNSPEC:+ break;+ case WGALLOWEDIP_A_FAMILY:+ if (!mnl_attr_validate(attr, MNL_TYPE_U16))+ allowedip->family = mnl_attr_get_u16(attr);+ break;+ case WGALLOWEDIP_A_IPADDR:+ if (mnl_attr_get_payload_len(attr) == sizeof(allowedip->ip4))+ memcpy(&allowedip->ip4, mnl_attr_get_payload(attr), sizeof(allowedip->ip4));+ else if (mnl_attr_get_payload_len(attr) == sizeof(allowedip->ip6))+ memcpy(&allowedip->ip6, mnl_attr_get_payload(attr), sizeof(allowedip->ip6));+ break;+ case WGALLOWEDIP_A_CIDR_MASK:+ if (!mnl_attr_validate(attr, MNL_TYPE_U8))+ allowedip->cidr = mnl_attr_get_u8(attr);+ break;+ }++ return MNL_CB_OK;+}++static int parse_allowedips(const struct nlattr *attr, void *data)+{+ wg_peer *peer = data;+ wg_allowedip *new_allowedip = calloc(1, sizeof(wg_allowedip));+ int ret;++ if (!new_allowedip)+ return MNL_CB_ERROR;+ if (!peer->first_allowedip)+ peer->first_allowedip = peer->last_allowedip = new_allowedip;+ else {+ peer->last_allowedip->next_allowedip = new_allowedip;+ peer->last_allowedip = new_allowedip;+ }+ ret = mnl_attr_parse_nested(attr, parse_allowedip, new_allowedip);+ if (!ret)+ return ret;+ if (!((new_allowedip->family == AF_INET && new_allowedip->cidr <= 32) || (new_allowedip->family == AF_INET6 && new_allowedip->cidr <= 128))) {+ errno = EAFNOSUPPORT;+ return MNL_CB_ERROR;+ }+ return MNL_CB_OK;+}++bool wg_key_is_zero(const wg_key key)+{+ volatile uint8_t acc = 0;+ unsigned int i;++ for (i = 0; i < sizeof(wg_key); ++i) {+ acc |= key[i];+ __asm__ ("" : "=r" (acc) : "0" (acc));+ }+ return 1 & ((acc - 1) >> 8);+}++static int parse_peer(const struct nlattr *attr, void *data)+{+ wg_peer *peer = data;++ switch (mnl_attr_get_type(attr)) {+ case WGPEER_A_UNSPEC:+ break;+ case WGPEER_A_PUBLIC_KEY:+ if (mnl_attr_get_payload_len(attr) == sizeof(peer->public_key)) {+ memcpy(peer->public_key, mnl_attr_get_payload(attr), sizeof(peer->public_key));+ peer->flags |= WGPEER_HAS_PUBLIC_KEY;+ }+ break;+ case WGPEER_A_PRESHARED_KEY:+ if (mnl_attr_get_payload_len(attr) == sizeof(peer->preshared_key)) {+ memcpy(peer->preshared_key, mnl_attr_get_payload(attr), sizeof(peer->preshared_key));+ if (!wg_key_is_zero(peer->preshared_key))+ peer->flags |= WGPEER_HAS_PRESHARED_KEY;+ }+ break;+ case WGPEER_A_ENDPOINT: {+ struct sockaddr *addr;++ if (mnl_attr_get_payload_len(attr) < sizeof(*addr))+ break;+ addr = mnl_attr_get_payload(attr);+ if (addr->sa_family == AF_INET && mnl_attr_get_payload_len(attr) == sizeof(peer->endpoint.addr4))+ memcpy(&peer->endpoint.addr4, addr, sizeof(peer->endpoint.addr4));+ else if (addr->sa_family == AF_INET6 && mnl_attr_get_payload_len(attr) == sizeof(peer->endpoint.addr6))+ memcpy(&peer->endpoint.addr6, addr, sizeof(peer->endpoint.addr6));+ break;+ }+ case WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL:+ if (!mnl_attr_validate(attr, MNL_TYPE_U16))+ peer->persistent_keepalive_interval = mnl_attr_get_u16(attr);+ break;+ case WGPEER_A_LAST_HANDSHAKE_TIME:+ if (mnl_attr_get_payload_len(attr) == sizeof(peer->last_handshake_time))+ memcpy(&peer->last_handshake_time, mnl_attr_get_payload(attr), sizeof(peer->last_handshake_time));+ break;+ case WGPEER_A_RX_BYTES:+ if (!mnl_attr_validate(attr, MNL_TYPE_U64))+ peer->rx_bytes = mnl_attr_get_u64(attr);+ break;+ case WGPEER_A_TX_BYTES:+ if (!mnl_attr_validate(attr, MNL_TYPE_U64))+ peer->tx_bytes = mnl_attr_get_u64(attr);+ break;+ case WGPEER_A_ALLOWEDIPS:+ return mnl_attr_parse_nested(attr, parse_allowedips, peer);+ }++ return MNL_CB_OK;+}++static int parse_peers(const struct nlattr *attr, void *data)+{+ wg_device *device = data;+ wg_peer *new_peer = calloc(1, sizeof(wg_peer));+ int ret;++ if (!new_peer)+ return MNL_CB_ERROR;+ if (!device->first_peer)+ device->first_peer = device->last_peer = new_peer;+ else {+ device->last_peer->next_peer = new_peer;+ device->last_peer = new_peer;+ }+ ret = mnl_attr_parse_nested(attr, parse_peer, new_peer);+ if (!ret)+ return ret;+ if (!(new_peer->flags & WGPEER_HAS_PUBLIC_KEY)) {+ errno = ENXIO;+ return MNL_CB_ERROR;+ }+ return MNL_CB_OK;+}++static int parse_device(const struct nlattr *attr, void *data)+{+ wg_device *device = data;++ switch (mnl_attr_get_type(attr)) {+ case WGDEVICE_A_UNSPEC:+ break;+ case WGDEVICE_A_IFINDEX:+ if (!mnl_attr_validate(attr, MNL_TYPE_U32))+ device->ifindex = mnl_attr_get_u32(attr);+ break;+ case WGDEVICE_A_IFNAME:+ if (!mnl_attr_validate(attr, MNL_TYPE_STRING)) {+ strncpy(device->name, mnl_attr_get_str(attr), sizeof(device->name) - 1);+ device->name[sizeof(device->name) - 1] = '\0';+ }+ break;+ case WGDEVICE_A_PRIVATE_KEY:+ if (mnl_attr_get_payload_len(attr) == sizeof(device->private_key)) {+ memcpy(device->private_key, mnl_attr_get_payload(attr), sizeof(device->private_key));+ device->flags |= WGDEVICE_HAS_PRIVATE_KEY;+ }+ break;+ case WGDEVICE_A_PUBLIC_KEY:+ if (mnl_attr_get_payload_len(attr) == sizeof(device->public_key)) {+ memcpy(device->public_key, mnl_attr_get_payload(attr), sizeof(device->public_key));+ device->flags |= WGDEVICE_HAS_PUBLIC_KEY;+ }+ break;+ case WGDEVICE_A_LISTEN_PORT:+ if (!mnl_attr_validate(attr, MNL_TYPE_U16))+ device->listen_port = mnl_attr_get_u16(attr);+ break;+ case WGDEVICE_A_FWMARK:+ if (!mnl_attr_validate(attr, MNL_TYPE_U32))+ device->fwmark = mnl_attr_get_u32(attr);+ break;+ case WGDEVICE_A_PEERS:+ return mnl_attr_parse_nested(attr, parse_peers, device);+ }++ return MNL_CB_OK;+}++static int read_device_cb(const struct nlmsghdr *nlh, void *data)+{+ return mnl_attr_parse(nlh, sizeof(struct genlmsghdr), parse_device, data);+}++static void coalesce_peers(wg_device *device)+{+ wg_peer *old_next_peer, *peer = device->first_peer;++ while (peer && peer->next_peer) {+ if (memcmp(peer->public_key, peer->next_peer->public_key, sizeof(wg_key))) {+ peer = peer->next_peer;+ continue;+ }+ if (!peer->first_allowedip) {+ peer->first_allowedip = peer->next_peer->first_allowedip;+ peer->last_allowedip = peer->next_peer->last_allowedip;+ } else {+ peer->last_allowedip->next_allowedip = peer->next_peer->first_allowedip;+ peer->last_allowedip = peer->next_peer->last_allowedip;+ }+ old_next_peer = peer->next_peer;+ peer->next_peer = old_next_peer->next_peer;+ free(old_next_peer);+ }+}++int wg_get_device(wg_device **device, const char *device_name)+{+ int ret = 0;+ struct nlmsghdr *nlh;+ struct mnlg_socket *nlg;++try_again:+ *device = calloc(1, sizeof(wg_device));+ if (!*device)+ return -errno;++ nlg = mnlg_socket_open(WG_GENL_NAME, WG_GENL_VERSION);+ if (!nlg) {+ wg_free_device(*device);+ *device = NULL;+ return -errno;+ }++ nlh = mnlg_msg_prepare(nlg, WG_CMD_GET_DEVICE, NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP);+ mnl_attr_put_strz(nlh, WGDEVICE_A_IFNAME, device_name);+ if (mnlg_socket_send(nlg, nlh) < 0) {+ ret = -errno;+ goto out;+ }+ errno = 0;+ if (mnlg_socket_recv_run(nlg, read_device_cb, *device) < 0) {+ ret = errno ? -errno : -EINVAL;+ goto out;+ }+ coalesce_peers(*device);++out:+ if (nlg)+ mnlg_socket_close(nlg);+ if (ret) {+ wg_free_device(*device);+ if (ret == -EINTR)+ goto try_again;+ *device = NULL;+ }+ errno = -ret;+ return ret;+}++/* first\0second\0third\0forth\0last\0\0 */+char *wg_list_device_names(void)+{+ struct string_list list = { 0 };+ int ret = fetch_device_names(&list);++ errno = -ret;+ if (errno) {+ free(list.buffer);+ return NULL;+ }+ return list.buffer ?: strdup("\0");+}++int wg_add_device(const char *device_name)+{+ return add_del_iface(device_name, true);+}++int wg_del_device(const char *device_name)+{+ return add_del_iface(device_name, false);+}++void wg_free_device(wg_device *dev)+{+ wg_peer *peer, *np;+ wg_allowedip *allowedip, *na;++ if (!dev)+ return;+ for (peer = dev->first_peer, np = peer ? peer->next_peer : NULL; peer; peer = np, np = peer ? peer->next_peer : NULL) {+ for (allowedip = peer->first_allowedip, na = allowedip ? allowedip->next_allowedip : NULL; allowedip; allowedip = na, na = allowedip ? allowedip->next_allowedip : NULL)+ free(allowedip);+ free(peer);+ }+ free(dev);+}++static void encode_base64(char dest[static 4], const uint8_t src[static 3])+{+ const uint8_t input[] = { (src[0] >> 2) & 63, ((src[0] << 4) | (src[1] >> 4)) & 63, ((src[1] << 2) | (src[2] >> 6)) & 63, src[2] & 63 };+ unsigned int i;++ for (i = 0; i < 4; ++i)+ dest[i] = input[i] + 'A'+ + (((25 - input[i]) >> 8) & 6)+ - (((51 - input[i]) >> 8) & 75)+ - (((61 - input[i]) >> 8) & 15)+ + (((62 - input[i]) >> 8) & 3);++}++void wg_key_to_base64(wg_key_b64_string base64, const wg_key key)+{+ unsigned int i;++ for (i = 0; i < 32 / 3; ++i)+ encode_base64(&base64[i * 4], &key[i * 3]);+ encode_base64(&base64[i * 4], (const uint8_t[]){ key[i * 3 + 0], key[i * 3 + 1], 0 });+ base64[sizeof(wg_key_b64_string) - 2] = '=';+ base64[sizeof(wg_key_b64_string) - 1] = '\0';+}++static int decode_base64(const char src[static 4])+{+ int val = 0;+ unsigned int i;++ for (i = 0; i < 4; ++i)+ val |= (-1+ + ((((('A' - 1) - src[i]) & (src[i] - ('Z' + 1))) >> 8) & (src[i] - 64))+ + ((((('a' - 1) - src[i]) & (src[i] - ('z' + 1))) >> 8) & (src[i] - 70))+ + ((((('0' - 1) - src[i]) & (src[i] - ('9' + 1))) >> 8) & (src[i] + 5))+ + ((((('+' - 1) - src[i]) & (src[i] - ('+' + 1))) >> 8) & 63)+ + ((((('/' - 1) - src[i]) & (src[i] - ('/' + 1))) >> 8) & 64)+ ) << (18 - 6 * i);+ return val;+}++int wg_key_from_base64(wg_key key, const wg_key_b64_string base64)+{+ unsigned int i;+ int val;+ volatile uint8_t ret = 0;++ if (strlen(base64) != sizeof(wg_key_b64_string) - 1 || base64[sizeof(wg_key_b64_string) - 2] != '=') {+ errno = EINVAL;+ goto out;+ }++ for (i = 0; i < 32 / 3; ++i) {+ val = decode_base64(&base64[i * 4]);+ ret |= (uint32_t)val >> 31;+ key[i * 3 + 0] = (val >> 16) & 0xff;+ key[i * 3 + 1] = (val >> 8) & 0xff;+ key[i * 3 + 2] = val & 0xff;+ }+ val = decode_base64((const char[]){ base64[i * 4 + 0], base64[i * 4 + 1], base64[i * 4 + 2], 'A' });+ ret |= ((uint32_t)val >> 31) | (val & 0xff);+ key[i * 3 + 0] = (val >> 16) & 0xff;+ key[i * 3 + 1] = (val >> 8) & 0xff;+ errno = EINVAL & ~((ret - 1) >> 8);+out:+ return -errno;+}++typedef int64_t fe[16];++static __attribute__((noinline)) void memzero_explicit(void *s, size_t count)+{+ memset(s, 0, count);+ __asm__ __volatile__("": :"r"(s) :"memory");+}++static void carry(fe o)+{+ int i;++ for (i = 0; i < 16; ++i) {+ o[(i + 1) % 16] += (i == 15 ? 38 : 1) * (o[i] >> 16);+ o[i] &= 0xffff;+ }+}++static void cswap(fe p, fe q, int b)+{+ int i;+ int64_t t, c = ~(b - 1);++ for (i = 0; i < 16; ++i) {+ t = c & (p[i] ^ q[i]);+ p[i] ^= t;+ q[i] ^= t;+ }++ memzero_explicit(&t, sizeof(t));+ memzero_explicit(&c, sizeof(c));+ memzero_explicit(&b, sizeof(b));+}++static void pack(uint8_t *o, const fe n)+{+ int i, j, b;+ fe m, t;++ memcpy(t, n, sizeof(t));+ carry(t);+ carry(t);+ carry(t);+ for (j = 0; j < 2; ++j) {+ m[0] = t[0] - 0xffed;+ for (i = 1; i < 15; ++i) {+ m[i] = t[i] - 0xffff - ((m[i - 1] >> 16) & 1);+ m[i - 1] &= 0xffff;+ }+ m[15] = t[15] - 0x7fff - ((m[14] >> 16) & 1);+ b = (m[15] >> 16) & 1;+ m[14] &= 0xffff;+ cswap(t, m, 1 - b);+ }+ for (i = 0; i < 16; ++i) {+ o[2 * i] = t[i] & 0xff;+ o[2 * i + 1] = t[i] >> 8;+ }++ memzero_explicit(m, sizeof(m));+ memzero_explicit(t, sizeof(t));+ memzero_explicit(&b, sizeof(b));+}++static void add(fe o, const fe a, const fe b)+{+ int i;++ for (i = 0; i < 16; ++i)+ o[i] = a[i] + b[i];+}++static void subtract(fe o, const fe a, const fe b)+{+ int i;++ for (i = 0; i < 16; ++i)+ o[i] = a[i] - b[i];+}++static void multmod(fe o, const fe a, const fe b)+{+ int i, j;+ int64_t t[31] = { 0 };++ for (i = 0; i < 16; ++i) {+ for (j = 0; j < 16; ++j)+ t[i + j] += a[i] * b[j];+ }+ for (i = 0; i < 15; ++i)+ t[i] += 38 * t[i + 16];+ memcpy(o, t, sizeof(fe));+ carry(o);+ carry(o);++ memzero_explicit(t, sizeof(t));+}++static void invert(fe o, const fe i)+{+ fe c;+ int a;++ memcpy(c, i, sizeof(c));+ for (a = 253; a >= 0; --a) {+ multmod(c, c, c);+ if (a != 2 && a != 4)+ multmod(c, c, i);+ }+ memcpy(o, c, sizeof(fe));++ memzero_explicit(c, sizeof(c));+}++static void clamp_key(uint8_t *z)+{+ z[31] = (z[31] & 127) | 64;+ z[0] &= 248;+}++void wg_generate_public_key(wg_key public_key, const wg_key private_key)+{+ int i, r;+ uint8_t z[32];+ fe a = { 1 }, b = { 9 }, c = { 0 }, d = { 1 }, e, f;++ memcpy(z, private_key, sizeof(z));+ clamp_key(z);++ for (i = 254; i >= 0; --i) {+ r = (z[i >> 3] >> (i & 7)) & 1;+ cswap(a, b, r);+ cswap(c, d, r);+ add(e, a, c);+ subtract(a, a, c);+ add(c, b, d);+ subtract(b, b, d);+ multmod(d, e, e);+ multmod(f, a, a);+ multmod(a, c, a);+ multmod(c, b, e);+ add(e, a, c);+ subtract(a, a, c);+ multmod(b, a, a);+ subtract(c, d, f);+ multmod(a, c, (const fe){ 0xdb41, 1 });+ add(a, a, d);+ multmod(c, c, a);+ multmod(a, d, f);+ multmod(d, b, (const fe){ 9 });+ multmod(b, e, e);+ cswap(a, b, r);+ cswap(c, d, r);+ }+ invert(c, c);+ multmod(a, a, c);+ pack(public_key, a);++ memzero_explicit(&r, sizeof(r));+ memzero_explicit(z, sizeof(z));+ memzero_explicit(a, sizeof(a));+ memzero_explicit(b, sizeof(b));+ memzero_explicit(c, sizeof(c));+ memzero_explicit(d, sizeof(d));+ memzero_explicit(e, sizeof(e));+ memzero_explicit(f, sizeof(f));+}++void wg_generate_private_key(wg_key private_key)+{+ wg_generate_preshared_key(private_key);+ clamp_key(private_key);+}++void wg_generate_preshared_key(wg_key preshared_key)+{+ ssize_t ret;+ size_t i;+ int fd;+#if defined(__OpenBSD__) || (defined(__APPLE__) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_12) || (defined(__GLIBC__) && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 25)))+ if (!getentropy(preshared_key, sizeof(wg_key)))+ return;+#endif+#if defined(__NR_getrandom) && defined(__linux__)+ if (syscall(__NR_getrandom, preshared_key, sizeof(wg_key), 0) == sizeof(wg_key))+ return;+#endif+ fd = open("/dev/urandom", O_RDONLY);+ assert(fd >= 0);+ for (i = 0; i < sizeof(wg_key); i += ret) {+ ret = read(fd, preshared_key + i, sizeof(wg_key) - i);+ assert(ret > 0);+ }+ close(fd);+}