diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/Erebos/Discovery.hs | 129 | ||||
| -rw-r--r-- | src/Erebos/Network.hs | 51 | ||||
| -rw-r--r-- | src/Erebos/Network/ifaddrs.c | 110 | ||||
| -rw-r--r-- | src/Erebos/Network/ifaddrs.h | 13 | 
4 files changed, 236 insertions, 67 deletions
| diff --git a/src/Erebos/Discovery.hs b/src/Erebos/Discovery.hs index e6b5f48..6422a59 100644 --- a/src/Erebos/Discovery.hs +++ b/src/Erebos/Discovery.hs @@ -10,11 +10,12 @@ import Control.Monad  import Control.Monad.Except  import Control.Monad.Reader +import Data.IP qualified as IP  import Data.Map.Strict (Map) -import qualified Data.Map.Strict as M +import Data.Map.Strict qualified as M  import Data.Maybe  import Data.Text (Text) -import qualified Data.Text as T +import Data.Text qualified as T  import Network.Socket @@ -27,14 +28,10 @@ import Erebos.Service  import Erebos.Storage -keepaliveSeconds :: Int -keepaliveSeconds = 20 - - -data DiscoveryService = DiscoverySelf Text Int +data DiscoveryService = DiscoverySelf [ Text ] (Maybe Int)                        | DiscoveryAcknowledged Text                        | DiscoverySearch Ref -                      | DiscoveryResult Ref (Maybe Text) +                      | DiscoveryResult Ref [ Text ]                        | DiscoveryConnectionRequest DiscoveryConnection                        | DiscoveryConnectionResponse DiscoveryConnection @@ -58,15 +55,15 @@ emptyConnection dconnSource dconnTarget = DiscoveryConnection {..}  instance Storable DiscoveryService where      store' x = storeRec $ do          case x of -            DiscoverySelf addr priority -> do -                storeText "self" addr -                storeInt "priority" priority +            DiscoverySelf addrs priority -> do +                mapM_ (storeText "self") addrs +                mapM_ (storeInt "priority") priority              DiscoveryAcknowledged addr -> do                  storeText "ack" addr              DiscoverySearch ref -> storeRawRef "search" ref              DiscoveryResult ref addr -> do                  storeRawRef "result" ref -                storeMbText "address" addr +                mapM_ (storeText "address") addr              DiscoveryConnectionRequest conn -> storeConnection "request" conn              DiscoveryConnectionResponse conn -> storeConnection "response" conn @@ -80,15 +77,17 @@ instance Storable DiscoveryService where  #endif      load' = loadRec $ msum -            [ DiscoverySelf -                <$> loadText "self" -                <*> loadInt "priority" +            [ do +                addrs <- loadTexts "self" +                guard (not $ null addrs) +                DiscoverySelf addrs +                    <$> loadMbInt "priority"              , DiscoveryAcknowledged                  <$> loadText "ack"              , DiscoverySearch <$> loadRawRef "search"              , DiscoveryResult                  <$> loadRawRef "result" -                <*> loadMbText "address" +                <*> loadTexts "address"              , loadConnection "request" DiscoveryConnectionRequest              , loadConnection "response" DiscoveryConnectionResponse              ] @@ -106,25 +105,25 @@ instance Storable DiscoveryService where  data DiscoveryPeer = DiscoveryPeer      { dpPriority :: Int      , dpPeer :: Maybe Peer -    , dpAddress :: Maybe Text +    , dpAddress :: [ Text ]  #ifdef ENABLE_ICE_SUPPORT      , dpIceSession :: Maybe IceSession  #endif      }  instance Service DiscoveryService where -    serviceID _ = mkServiceID "dd59c89c-69cc-4703-b75b-4ddcd4b3c23b" +    serviceID _ = mkServiceID "dd59c89c-69cc-4703-b75b-4ddcd4b3c23c"      type ServiceGlobalState DiscoveryService = Map RefDigest DiscoveryPeer      emptyServiceGlobalState _ = M.empty      serviceHandler msg = case fromStored msg of -        DiscoverySelf addr priority -> do +        DiscoverySelf addrs priority -> do              pid <- asks svcPeerIdentity              peer <- asks svcPeer              let insertHelper new old | dpPriority new > dpPriority old = new                                       | otherwise                       = old -            mbaddr <- case words (T.unpack addr) of +            mbaddr <- fmap (listToMaybe . catMaybes) $ forM addrs $ \addr -> case words (T.unpack addr) of                  [ipaddr, port] | DatagramAddress paddr <- peerAddress peer -> do                      saddr <- liftIO $ head <$> getAddrInfo (Just $ defaultHints { addrSocketType = Datagram }) (Just ipaddr) (Just port)                      return $ if paddr == addrAddress saddr @@ -133,70 +132,71 @@ instance Service DiscoveryService where                  _ -> return Nothing              forM_ (idDataF =<< unfoldOwners pid) $ \s ->                  svcModifyGlobal $ M.insertWith insertHelper (refDigest $ storedRef s) DiscoveryPeer -                    { dpPriority = priority +                    { dpPriority = fromMaybe 0 priority                      , dpPeer = Just peer -                    , dpAddress = mbaddr +                    , dpAddress = addrs  #ifdef ENABLE_ICE_SUPPORT                      , dpIceSession = Nothing  #endif                      }              replyPacket $ DiscoveryAcknowledged $ fromMaybe (T.pack "ICE") mbaddr -        DiscoveryAcknowledged addr -> do -            when (addr == T.pack "ICE") $ do -                -- keep-alive packet from behind NAT -                peer <- asks svcPeer -                liftIO $ void $ forkIO $ do -                    threadDelay (keepaliveSeconds * 1000 * 1000) -                    res <- runExceptT $ sendToPeer peer $ DiscoverySelf addr 0 -                    case res of -                        Right _ -> return () -                        Left err -> putStrLn $ "Discovery: failed to send keep-alive: " ++ err +        DiscoveryAcknowledged _ -> do +            return ()          DiscoverySearch ref -> do -            addr <- M.lookup (refDigest ref) <$> svcGetGlobal -            replyPacket $ DiscoveryResult ref $ fromMaybe (T.pack "ICE") . dpAddress <$> addr +            dpeer <- M.lookup (refDigest ref) <$> svcGetGlobal +            replyPacket $ DiscoveryResult ref $ maybe [] dpAddress dpeer -        DiscoveryResult ref Nothing -> do +        DiscoveryResult ref [] -> do              svcPrint $ "Discovery: " ++ show (refDigest ref) ++ " not found" -        DiscoveryResult ref (Just addr) -> do +        DiscoveryResult ref addrs -> do              -- TODO: check if we really requested that              server <- asks svcServer -            if addr == T.pack "ICE" -               then do +            self <- svcSelf +            discoveryPeer <- asks svcPeer +            let runAsService = runPeerService @DiscoveryService discoveryPeer + +            liftIO $ void $ forkIO $ forM_ addrs $ \addr -> if +                | addr == T.pack "ICE" -> do  #ifdef ENABLE_ICE_SUPPORT -                    self <- svcSelf -                    peer <- asks svcPeer -                    ice <- liftIO $ iceCreate PjIceSessRoleControlling $ \ice -> do +                    ice <- iceCreate PjIceSessRoleControlling $ \ice -> do                          rinfo <- iceRemoteInfo ice -                        res <- runExceptT $ sendToPeer peer $ +                        res <- runExceptT $ sendToPeer discoveryPeer $                              DiscoveryConnectionRequest (emptyConnection (storedRef $ idData self) ref) { dconnIceSession = Just rinfo }                          case res of                              Right _ -> return ()                              Left err -> putStrLn $ "Discovery: failed to send connection request: " ++ err -                    svcModifyGlobal $ M.insert (refDigest ref) $ -                        DiscoveryPeer 0 Nothing Nothing (Just ice) +                    runAsService $ do +                        svcModifyGlobal $ M.insert (refDigest ref) DiscoveryPeer +                            { dpPriority = 0 +                            , dpPeer = Nothing +                            , dpAddress = [] +                            , dpIceSession = Just ice +                            }  #else                      return ()  #endif -               else do -                    case words (T.unpack addr) of -                        [ipaddr, port] -> do -                            saddr <- liftIO $ head <$> -                                getAddrInfo (Just $ defaultHints { addrSocketType = Datagram }) (Just ipaddr) (Just port) -                            peer <- liftIO $ serverPeer server (addrAddress saddr) -                            svcModifyGlobal $ M.insert (refDigest ref) DiscoveryPeer -                                { dpPriority = 0 -                                , dpPeer = Just peer -                                , dpAddress = Nothing + +                | [ ipaddr, port ] <- words (T.unpack addr) -> do +                    saddr <- head <$> +                        getAddrInfo (Just $ defaultHints { addrSocketType = Datagram }) (Just ipaddr) (Just port) +                    peer <- serverPeer server (addrAddress saddr) +                    runAsService $ do +                        svcModifyGlobal $ M.insert (refDigest ref) DiscoveryPeer +                            { dpPriority = 0 +                            , dpPeer = Just peer +                            , dpAddress = []  #ifdef ENABLE_ICE_SUPPORT -                                , dpIceSession = Nothing +                            , dpIceSession = Nothing  #endif -                                } +                        } -                        _ -> svcPrint $ "Discovery: invalid address in result: " ++ T.unpack addr +                | otherwise -> do +                    runAsService $ do +                        svcPrint $ "Discovery: invalid address in result: " ++ T.unpack addr          DiscoveryConnectionRequest conn -> do  #ifdef ENABLE_ICE_SUPPORT @@ -222,7 +222,7 @@ instance Service DiscoveryService where                      mbdp <- M.lookup (refDigest $ dconnTarget conn) <$> svcGetGlobal                      case mbdp of                          Nothing -> replyPacket $ DiscoveryConnectionResponse rconn -                        Just dp | Just addr <- dpAddress dp -> do +                        Just dp | addr : _ <- dpAddress dp -> do                                      replyPacket $ DiscoveryConnectionResponse rconn { dconnAddress = Just addr }                                  | Just dpeer <- dpPeer dp -> do                                      sendToPeer dpeer $ DiscoveryConnectionRequest conn @@ -245,7 +245,7 @@ instance Service DiscoveryService where                                  getAddrInfo (Just $ defaultHints { addrSocketType = Datagram }) (Just ipaddr) (Just port)                              peer <- liftIO $ serverPeer server (addrAddress saddr)                              svcModifyGlobal $ M.insert (refDigest $ dconnTarget conn) $ -                                DiscoveryPeer 0 (Just peer) Nothing Nothing +                                DiscoveryPeer 0 (Just peer) [] Nothing                          | Just dp <- M.lookup (refDigest $ dconnTarget conn) dpeers                          , Just ice <- dpIceSession dp @@ -262,3 +262,14 @@ instance Service DiscoveryService where  #else              return ()  #endif + +    serviceNewPeer = do +        server <- asks svcServer +        peer <- asks svcPeer + +        let addrToText saddr = do +                ( addr, port ) <- IP.fromSockAddr saddr +                Just $ T.pack $ show addr <> " " <> show port +        addrs <- catMaybes . map addrToText <$> liftIO (getServerAddresses server) + +        sendToPeer peer $ DiscoverySelf addrs Nothing diff --git a/src/Erebos/Network.hs b/src/Erebos/Network.hs index 2064d1c..e9a4182 100644 --- a/src/Erebos/Network.hs +++ b/src/Erebos/Network.hs @@ -6,6 +6,7 @@ module Erebos.Network (      stopServer,      getCurrentPeerList,      getNextPeerChange, +    getServerAddresses,      ServerOptions(..), serverIdentity, defaultServerOptions,      Peer, peerServer, peerStorage, @@ -46,17 +47,17 @@ import Data.Maybe  import Data.Typeable  import Data.Word +import Foreign.C.Types +import Foreign.Marshal.Alloc +import Foreign.Marshal.Array  import Foreign.Ptr -import Foreign.Storable +import Foreign.Storable as F  import GHC.Conc.Sync (unsafeIOToSTM)  import Network.Socket hiding (ControlMessage)  import qualified Network.Socket.ByteString as S -import Foreign.C.Types -import Foreign.Marshal.Alloc -  import Erebos.Channel  #ifdef ENABLE_ICE_SUPPORT  import Erebos.ICE @@ -83,6 +84,7 @@ announceIntervalSeconds = 60  data Server = Server      { serverStorage :: Storage +    , serverOptions :: ServerOptions      , serverOrigHead :: Head LocalState      , serverIdentity_ :: MVar UnifiedIdentity      , serverThreads :: MVar [ThreadId] @@ -229,7 +231,7 @@ forkServerThread server act = do          return (t:ts)  startServer :: ServerOptions -> Head LocalState -> (String -> IO ()) -> [SomeService] -> IO Server -startServer opt serverOrigHead logd' serverServices = do +startServer serverOptions serverOrigHead logd' serverServices = do      let serverStorage = headStorage serverOrigHead      serverIdentity_ <- newMVar $ headLocalIdentity serverOrigHead      serverThreads <- newMVar [] @@ -265,7 +267,7 @@ startServer opt serverOrigHead logd' serverServices = do              return sock          loop sock = do -            when (serverLocalDiscovery opt) $ forkServerThread server $ do +            when (serverLocalDiscovery serverOptions) $ forkServerThread server $ do                  announceAddreses <- fmap concat $ sequence $                      [ map (SockAddrInet6 discoveryPort 0 discoveryMulticastGroup) <$> joinMulticast sock                      , getBroadcastAddresses discoveryPort @@ -377,7 +379,7 @@ startServer opt serverOrigHead logd' serverServices = do                , addrFamily = AF_INET6                , addrSocketType = Datagram                } -        addr:_ <- getAddrInfo (Just hints) Nothing (Just $ show $ serverPort opt) +        addr:_ <- getAddrInfo (Just hints) Nothing (Just $ show $ serverPort serverOptions)          bracket (open addr) close loop      forkServerThread server $ forever $ do @@ -954,8 +956,28 @@ runPeerServiceOn mbservice peer handler = liftIO $ do  foreign import ccall unsafe "Network/ifaddrs.h join_multicast" cJoinMulticast :: CInt -> Ptr CSize -> IO (Ptr Word32) +foreign import ccall unsafe "Network/ifaddrs.h local_addresses" cLocalAddresses :: Ptr CSize -> IO (Ptr InetAddress)  foreign import ccall unsafe "Network/ifaddrs.h broadcast_addresses" cBroadcastAddresses :: IO (Ptr Word32) -foreign import ccall unsafe "stdlib.h free" cFree :: Ptr Word32 -> IO () +foreign import ccall unsafe "stdlib.h free" cFree :: Ptr a -> IO () + +data InetAddress = InetAddress { fromInetAddress :: IP.IP } + +instance F.Storable InetAddress where +    sizeOf _ = sizeOf (undefined :: CInt) + 16 +    alignment _ = 8 + +    peek ptr = (unpackFamily <$> peekByteOff ptr 0) >>= \case +        AF_INET -> InetAddress . IP.IPv4 . IP.fromHostAddress <$> peekByteOff ptr (sizeOf (undefined :: CInt)) +        AF_INET6 -> InetAddress . IP.IPv6 . IP.toIPv6b . map fromIntegral <$> peekArray 16 (ptr `plusPtr` sizeOf (undefined :: CInt) :: Ptr Word8) +        _ -> fail "InetAddress: unknown family" + +    poke ptr (InetAddress addr) = case addr of +        IP.IPv4 ip -> do +            pokeByteOff ptr 0 (packFamily AF_INET) +            pokeByteOff ptr (sizeOf (undefined :: CInt)) (IP.toHostAddress ip) +        IP.IPv6 ip -> do +            pokeByteOff ptr 0 (packFamily AF_INET6) +            pokeArray (ptr `plusPtr` sizeOf (undefined :: CInt) :: Ptr Word8) (map fromIntegral $ IP.fromIPv6b ip)  joinMulticast :: Socket -> IO [ Word32 ]  joinMulticast sock = @@ -966,6 +988,19 @@ joinMulticast sock =          forM [ 0 .. count - 1 ] $ \i ->              peekElemOff ptr i +getServerAddresses :: Server -> IO [ SockAddr ] +getServerAddresses Server {..} = do +    alloca $ \pcount -> do +        ptr <- cLocalAddresses pcount +        if ptr == nullPtr +          then do +            return [] +          else do +            count <- fromIntegral <$> peek pcount +            res <- peekArray count ptr +            cFree ptr +            return $ map (IP.toSockAddr . (, serverPort serverOptions ) . fromInetAddress) res +  getBroadcastAddresses :: PortNumber -> IO [SockAddr]  getBroadcastAddresses port = do      ptr <- cBroadcastAddresses diff --git a/src/Erebos/Network/ifaddrs.c b/src/Erebos/Network/ifaddrs.c index 637716e..ff4382a 100644 --- a/src/Erebos/Network/ifaddrs.c +++ b/src/Erebos/Network/ifaddrs.c @@ -9,6 +9,7 @@  #ifndef _WIN32  #include <arpa/inet.h>  #include <net/if.h> +#include <netinet/in.h>  #include <ifaddrs.h>  #include <endian.h>  #include <sys/types.h> @@ -85,8 +86,73 @@ uint32_t * join_multicast(int fd, size_t * count)  	return interfaces;  } +static bool copy_local_address( struct InetAddress * dst, const struct sockaddr * src ) +{ +	int family = src->sa_family; + +	if( family == AF_INET ){ +		struct in_addr * addr = & (( struct sockaddr_in * ) src)->sin_addr; +		if (! ((ntohl( addr->s_addr ) & 0xff000000) == 0x7f000000) && // loopback +				! ((ntohl( addr->s_addr ) & 0xffff0000) == 0xa9fe0000) // link-local +		   ){ +			dst->family = family; +			memcpy( & dst->addr, addr, sizeof( * addr )); +			return true; +		} +	} + +	if( family == AF_INET6 ){ +		struct in6_addr * addr = & (( struct sockaddr_in6 * ) src)->sin6_addr; +		if (! IN6_IS_ADDR_LOOPBACK( addr ) && +				! IN6_IS_ADDR_LINKLOCAL( addr ) +		   ){ +			dst->family = family; +			memcpy( & dst->addr, addr, sizeof( * addr )); +			return true; +		} +	} + +	return false; +} +  #ifndef _WIN32 +struct InetAddress * local_addresses( size_t * count ) +{ +	struct ifaddrs * addrs; +	if( getifaddrs( &addrs ) < 0 ) +		return 0; + +	* count = 0; +	size_t capacity = 16; +	struct InetAddress * ret = malloc( sizeof(* ret) * capacity ); + +	for( struct ifaddrs * ifa = addrs; ifa; ifa = ifa->ifa_next ){ +		if ( ifa->ifa_addr ){ +			int family = ifa->ifa_addr->sa_family; +			if( family == AF_INET || family == AF_INET6 ){ +				if( (* count) >= capacity ){ +					capacity *= 2; +					struct InetAddress * nret = realloc( ret, sizeof(* ret) * capacity ); +					if (nret) { +						ret = nret; +					} else { +						free( ret ); +						freeifaddrs( addrs ); +						return 0; +					} +				} + +				if( copy_local_address( & ret[ * count ], ifa->ifa_addr )) +					(* count)++; +			} +		} +	} + +	freeifaddrs(addrs); +	return ret; +} +  uint32_t * broadcast_addresses(void)  {  	struct ifaddrs * addrs; @@ -106,6 +172,7 @@ uint32_t * broadcast_addresses(void)  					ret = nret;  				} else {  					free(ret); +					freeifaddrs(addrs);  					return 0;  				}  			} @@ -124,9 +191,52 @@ uint32_t * broadcast_addresses(void)  #include <winsock2.h>  #include <ws2tcpip.h> +#include <iptypes.h> +#include <iphlpapi.h>  #pragma comment(lib, "ws2_32.lib") +struct InetAddress * local_addresses( size_t * count ) +{ +	* count = 0; +	struct InetAddress * ret = NULL; + +	ULONG bufsize = 15000; +	IP_ADAPTER_ADDRESSES * buf = NULL; + +	DWORD rv = 0; + +	do { +		buf = realloc( buf, bufsize ); +		rv = GetAdaptersAddresses( AF_UNSPEC, 0, NULL, buf, & bufsize ); + +		if( rv == ERROR_BUFFER_OVERFLOW ) +			continue; +	} while (0); + +	if( rv == NO_ERROR ){ +		size_t capacity = 16; +		ret = malloc( sizeof( * ret ) * capacity ); + +		for( IP_ADAPTER_ADDRESSES * cur = (IP_ADAPTER_ADDRESSES *) buf; +				cur && (* count) < capacity; +				cur = cur->Next ){ + +			for( IP_ADAPTER_UNICAST_ADDRESS * curAddr = cur->FirstUnicastAddress; +					curAddr && (* count) < capacity; +					curAddr = curAddr->Next ){ + +				if( copy_local_address( & ret[ * count ], curAddr->Address.lpSockaddr )) +					(* count)++; +			} +		} +	} + +cleanup: +	free( buf ); +	return ret; +} +  uint32_t * broadcast_addresses(void)  {  	uint32_t * ret = NULL; diff --git a/src/Erebos/Network/ifaddrs.h b/src/Erebos/Network/ifaddrs.h index 8852ec6..2ee45a7 100644 --- a/src/Erebos/Network/ifaddrs.h +++ b/src/Erebos/Network/ifaddrs.h @@ -1,5 +1,18 @@  #include <stddef.h>  #include <stdint.h> +#ifndef _WIN32 +#include <sys/socket.h> +#else +#include <winsock2.h> +#endif + +struct InetAddress +{ +	int family; +	uint8_t addr[16]; +} __attribute__((packed)); +  uint32_t * join_multicast(int fd, size_t * count); +struct InetAddress * local_addresses( size_t * count );  uint32_t * broadcast_addresses(void); |