packages feed

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 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);+}