diff options
author | Joe Crayne <joe@jerkface.net> | 2019-11-27 22:28:37 -0500 |
---|---|---|
committer | Joe Crayne <joe@jerkface.net> | 2020-01-01 22:50:28 -0500 |
commit | 3ebd7ae11d7a86798b31bdb17af9797ba5e09f1d (patch) | |
tree | c7290e69f27d5a32869ab20a88f48dba0af62b64 /dht/src | |
parent | 557b47bb3e9a39f74b35abcf4bb09cb85f211106 (diff) |
TCP Relay: use same crypto keys as Tox UDP node.
Diffstat (limited to 'dht/src')
-rw-r--r-- | dht/src/Network/Tox.hs | 32 | ||||
-rw-r--r-- | dht/src/Network/Tox/Relay.hs | 39 | ||||
-rw-r--r-- | dht/src/Network/Tox/TCP.hs | 25 |
3 files changed, 67 insertions, 29 deletions
diff --git a/dht/src/Network/Tox.hs b/dht/src/Network/Tox.hs index 97b97bad..69c56e24 100644 --- a/dht/src/Network/Tox.hs +++ b/dht/src/Network/Tox.hs | |||
@@ -278,10 +278,10 @@ newOnionClient crypto net r toks keydb orouter map_var store load = c | |||
278 | newTox :: TVar Onion.AnnouncedKeys -- ^ Store of announced keys we are a rendezvous for. | 278 | newTox :: TVar Onion.AnnouncedKeys -- ^ Store of announced keys we are a rendezvous for. |
279 | -> [String] -- ^ Bind-address to listen on. Must provide at least one. | 279 | -> [String] -- ^ Bind-address to listen on. Must provide at least one. |
280 | -> ( ContactInfo extra -> SockAddr -> Session -> IO () ) | 280 | -> ( ContactInfo extra -> SockAddr -> Session -> IO () ) |
281 | -> Maybe SecretKey -- ^ Optional DHT secret key to use. | 281 | -> (TransportCrypto, ContactInfo extra) |
282 | -> ( Int -> Onion.OnionMessage Encrypted -> IO () ) -- ^ TCP-bound onion responses. | 282 | -> ( Int -> Onion.OnionMessage Encrypted -> IO () ) -- ^ TCP-bound onion responses. |
283 | -> IO (Tox extra) | 283 | -> IO (Tox extra) |
284 | newTox keydb bindspecs onsess suppliedDHTKey tcp = do | 284 | newTox keydb bindspecs onsess crypto tcp = do |
285 | addrs <- mapM (`getBindAddress` True) bindspecs | 285 | addrs <- mapM (`getBindAddress` True) bindspecs |
286 | let tryBind addr next _ = udpTransport' addr `catchIOError` (next . Just) | 286 | let tryBind addr next _ = udpTransport' addr `catchIOError` (next . Just) |
287 | failedBind mbe = do | 287 | failedBind mbe = do |
@@ -291,21 +291,14 @@ newTox keydb bindspecs onsess suppliedDHTKey tcp = do | |||
291 | throwIO $ userError "Tox UDP listen port?" | 291 | throwIO $ userError "Tox UDP listen port?" |
292 | (udp,sock) <- foldr tryBind failedBind addrs Nothing | 292 | (udp,sock) <- foldr tryBind failedBind addrs Nothing |
293 | addr <- getSocketName sock | 293 | addr <- getSocketName sock |
294 | (relay,sendTCP) <- tcpRelay addr (\a x -> sendMessage udp a $ S.runPut $ Onion.putRequest x) | 294 | (relay,sendTCP) <- tcpRelay (fst crypto) addr (\a x -> sendMessage udp a $ S.runPut $ Onion.putRequest x) |
295 | tox <- newToxOverTransport keydb addr onsess suppliedDHTKey udp sendTCP | 295 | tox <- newToxOverTransport keydb addr onsess crypto udp sendTCP |
296 | return tox { toxAnnounceToLan = announceToLan sock (key2id $ transportPublic $ toxCryptoKeys tox) | 296 | return tox { toxAnnounceToLan = announceToLan sock (key2id $ transportPublic $ toxCryptoKeys tox) |
297 | , toxRelayServer = Just relay | 297 | , toxRelayServer = Just relay |
298 | } | 298 | } |
299 | 299 | ||
300 | -- | This version of 'newTox' is useful for automated tests using 'testPairTransport'. | 300 | newToxCrypto :: Maybe SecretKey -> IO (TransportCrypto, ContactInfo extra) |
301 | newToxOverTransport :: TVar Onion.AnnouncedKeys | 301 | newToxCrypto suppliedDHTKey = do |
302 | -> SockAddr | ||
303 | -> ( ContactInfo extra -> SockAddr -> Session -> IO () ) | ||
304 | -> Maybe SecretKey | ||
305 | -> Onion.UDPTransport | ||
306 | -> ( Int -> Onion.OnionMessage Encrypted -> IO () ) -- ^ TCP-bound onion responses. | ||
307 | -> IO (Tox extra) | ||
308 | newToxOverTransport keydb addr onNewSession suppliedDHTKey udp tcp = do | ||
309 | roster <- newContactInfo | 302 | roster <- newContactInfo |
310 | crypto0 <- newCrypto | 303 | crypto0 <- newCrypto |
311 | let -- patch in supplied DHT key | 304 | let -- patch in supplied DHT key |
@@ -316,12 +309,21 @@ newToxOverTransport keydb addr onNewSession suppliedDHTKey udp tcp = do | |||
316 | , transportPublic = toPublic k | 309 | , transportPublic = toPublic k |
317 | } | 310 | } |
318 | -- patch in newly allocated roster state. | 311 | -- patch in newly allocated roster state. |
319 | crypto = crypto1 { userKeys = myKeyPairs roster } | ||
320 | forM_ suppliedDHTKey $ \k -> do | 312 | forM_ suppliedDHTKey $ \k -> do |
321 | maybe (dput XMisc "failed to encode suppliedDHTKey") | 313 | maybe (dput XMisc "failed to encode suppliedDHTKey") |
322 | (dputB XMisc . C8.append "Using suppliedDHTKey: ") | 314 | (dputB XMisc . C8.append "Using suppliedDHTKey: ") |
323 | $ encodeSecret k | 315 | $ encodeSecret k |
316 | return (crypto1 { userKeys = myKeyPairs roster }, roster ) | ||
324 | 317 | ||
318 | -- | This version of 'newTox' is useful for automated tests using 'testPairTransport'. | ||
319 | newToxOverTransport :: TVar Onion.AnnouncedKeys | ||
320 | -> SockAddr | ||
321 | -> ( ContactInfo extra -> SockAddr -> Session -> IO () ) | ||
322 | -> (TransportCrypto, ContactInfo extra) | ||
323 | -> Onion.UDPTransport | ||
324 | -> ( Int -> Onion.OnionMessage Encrypted -> IO () ) -- ^ TCP-bound onion responses. | ||
325 | -> IO (Tox extra) | ||
326 | newToxOverTransport keydb addr onNewSession (crypto,roster) udp tcp = do | ||
325 | drg <- drgNew | 327 | drg <- drgNew |
326 | let lookupClose _ = return Nothing | 328 | let lookupClose _ = return Nothing |
327 | 329 | ||
diff --git a/dht/src/Network/Tox/Relay.hs b/dht/src/Network/Tox/Relay.hs index 2842fcc2..2ecd7ddf 100644 --- a/dht/src/Network/Tox/Relay.hs +++ b/dht/src/Network/Tox/Relay.hs | |||
@@ -30,6 +30,8 @@ import Network.SocketLike | |||
30 | import Network.StreamServer | 30 | import Network.StreamServer |
31 | import Network.Tox.Onion.Transport hiding (encrypt,decrypt) | 31 | import Network.Tox.Onion.Transport hiding (encrypt,decrypt) |
32 | 32 | ||
33 | import DPut | ||
34 | import DebugTag | ||
33 | 35 | ||
34 | 36 | ||
35 | hGetPrefixed :: Serialize a => Handle -> IO (Either String a) | 37 | hGetPrefixed :: Serialize a => Handle -> IO (Either String a) |
@@ -43,6 +45,7 @@ hGetSized h = runGet get <$> B.hGet h len -- We treat parse-fail the same as EOF | |||
43 | where | 45 | where |
44 | ConstSize len = size :: Size x | 46 | ConstSize len = size :: Size x |
45 | 47 | ||
48 | -- This type manages ConId assignments. | ||
46 | data RelaySession = RelaySession | 49 | data RelaySession = RelaySession |
47 | { indexPool :: IntSet -- ^ Ints that are either solicited or associated. | 50 | { indexPool :: IntSet -- ^ Ints that are either solicited or associated. |
48 | , solicited :: Map PublicKey Int -- ^ Reserved ids, not yet in associated. | 51 | , solicited :: Map PublicKey Int -- ^ Reserved ids, not yet in associated. |
@@ -70,17 +73,19 @@ disconnect cons who = join $ atomically $ do | |||
70 | in IntMap.foldrWithKey notifyPeer (return ()) cs | 73 | in IntMap.foldrWithKey notifyPeer (return ()) cs |
71 | 74 | ||
72 | relaySession :: TransportCrypto | 75 | relaySession :: TransportCrypto |
76 | -> TVar (IntMap (RelayPacket -> IO ())) | ||
73 | -> TVar (Map PublicKey (RelayPacket -> IO (),TVar RelaySession)) | 77 | -> TVar (Map PublicKey (RelayPacket -> IO (),TVar RelaySession)) |
74 | -> (SockAddr -> OnionRequest N1 -> IO ()) | 78 | -> (SockAddr -> OnionRequest N1 -> IO ()) |
75 | -> sock | 79 | -> sock |
76 | -> Int | 80 | -> Int |
77 | -> Handle | 81 | -> Handle |
78 | -> IO () | 82 | -> IO () |
79 | relaySession crypto cons sendOnion _ conid h = do | 83 | relaySession crypto clients cons sendOnion _ conid h = do |
80 | -- atomically $ modifyTVar' cons $ IntMap.insert conid h | 84 | -- atomically $ modifyTVar' cons $ IntMap.insert conid h |
81 | 85 | ||
82 | -- mhello <- fmap (>>= \h -> decryptPayload (computeSharedSecret me (helloFrom h) (helloNonce h)) h) $ hGetSized h | 86 | -- mhello <- fmap (>>= \h -> decryptPayload (computeSharedSecret me (helloFrom h) (helloNonce h)) h) $ hGetSized h |
83 | 87 | ||
88 | dput XOnion $ "Relay client session conid=" ++ show conid | ||
84 | (hGetSized h >>=) $ mapM_ $ \helloE -> do | 89 | (hGetSized h >>=) $ mapM_ $ \helloE -> do |
85 | 90 | ||
86 | let me = transportSecret crypto | 91 | let me = transportSecret crypto |
@@ -88,17 +93,24 @@ relaySession crypto cons sendOnion _ conid h = do | |||
88 | 93 | ||
89 | noncef <- lookupNonceFunction crypto me them | 94 | noncef <- lookupNonceFunction crypto me them |
90 | let mhello = decryptPayload (noncef $ helloNonce helloE) helloE | 95 | let mhello = decryptPayload (noncef $ helloNonce helloE) helloE |
96 | dput XOnion $ "Relay client (conid=" ++ show conid ++ ") decrypted hello = " ++ show mhello | ||
91 | forM_ mhello $ \hello -> do | 97 | forM_ mhello $ \hello -> do |
92 | let _ = hello :: Hello Identity | 98 | let _ = hello :: Hello Identity |
93 | 99 | ||
100 | dput XOnion $ "Relay client sent hello. conid=" ++ show conid | ||
94 | (me',welcome) <- atomically $ do | 101 | (me',welcome) <- atomically $ do |
95 | skey <- transportNewKey crypto | 102 | skey <- transportNewKey crypto |
96 | dta <- HelloData (toPublic skey) <$> transportNewNonce crypto | 103 | dta <- HelloData (toPublic skey) <$> transportNewNonce crypto |
97 | w24 <- transportNewNonce crypto | 104 | w24 <- transportNewNonce crypto |
98 | return (skey, Welcome w24 $ pure dta) | 105 | return (skey, Welcome w24 $ pure dta) |
99 | 106 | ||
107 | dput XOnion $ unlines [ "Relay client to receive welcome. conid=" ++ show conid | ||
108 | , show welcome | ||
109 | ] | ||
100 | B.hPut h $ encode $ encryptPayload (noncef $ welcomeNonce welcome) welcome | 110 | B.hPut h $ encode $ encryptPayload (noncef $ welcomeNonce welcome) welcome |
101 | 111 | ||
112 | dput XOnion $ "Relay client welcome sent. conid=" ++ show conid | ||
113 | |||
102 | noncef' <- let them' = sessionPublicKey (runIdentity $ helloData hello) | 114 | noncef' <- let them' = sessionPublicKey (runIdentity $ helloData hello) |
103 | in lookupNonceFunction crypto me' them' | 115 | in lookupNonceFunction crypto me' them' |
104 | 116 | ||
@@ -133,13 +145,20 @@ relaySession crypto cons sendOnion _ conid h = do | |||
133 | 145 | ||
134 | handlePacket cons conid (helloFrom hello) crypto sendOnion sendPacket session pkt0 | 146 | handlePacket cons conid (helloFrom hello) crypto sendOnion sendPacket session pkt0 |
135 | 147 | ||
148 | atomically $ modifyTVar' clients $ IntMap.insert conid $ | ||
149 | \p -> do | ||
150 | dput XOnion $ "Sending onion reply to TCP client conid="++show conid | ||
151 | sendPacket p | ||
152 | |||
136 | flip fix (incrementNonce24 base) $ \loop n24 -> do | 153 | flip fix (incrementNonce24 base) $ \loop n24 -> do |
137 | m <- readPacket n24 | 154 | m <- readPacket n24 |
138 | forM_ m $ \p -> do | 155 | forM_ m $ \p -> do |
139 | handlePacket cons conid (helloFrom hello) crypto sendOnion sendPacket session p | 156 | handlePacket cons conid (helloFrom hello) crypto sendOnion sendPacket session p |
140 | loop (incrementNonce24 n24) | 157 | loop (incrementNonce24 n24) |
141 | `finally` | 158 | `finally` do |
159 | atomically $ modifyTVar' clients $ IntMap.delete conid | ||
142 | disconnect cons (helloFrom hello) | 160 | disconnect cons (helloFrom hello) |
161 | dput XOnion $ "Relay client session closed. conid=" ++ show conid | ||
143 | 162 | ||
144 | handlePacket :: TVar (Map PublicKey (RelayPacket -> IO (), TVar RelaySession)) | 163 | handlePacket :: TVar (Map PublicKey (RelayPacket -> IO (), TVar RelaySession)) |
145 | -> Int | 164 | -> Int |
@@ -202,6 +221,7 @@ handlePacket cons thistcp me crypto sendOnion sendToMe session = \case | |||
202 | return $ sendToThem' $ RelayData bs | 221 | return $ sendToThem' $ RelayData bs |
203 | 222 | ||
204 | OnionPacket n24 (Addressed addr req) -> do | 223 | OnionPacket n24 (Addressed addr req) -> do |
224 | dput XOnion $ "Received onion request via TCP client conid="++show thistcp | ||
205 | rpath <- atomically $ do | 225 | rpath <- atomically $ do |
206 | sym <- transportSymmetric crypto | 226 | sym <- transportSymmetric crypto |
207 | n <- transportNewNonce crypto | 227 | n <- transportNewNonce crypto |
@@ -217,19 +237,24 @@ sendTCP_ st addr x = join $ atomically | |||
217 | Nothing -> return $ return () | 237 | Nothing -> return $ return () |
218 | Just send -> return $ send $ OnionPacketResponse x | 238 | Just send -> return $ send $ OnionPacketResponse x |
219 | 239 | ||
220 | tcpRelay :: SockAddr -> (SockAddr -> OnionRequest N1 -> IO ()) -> IO (ServerHandle, Int -> OnionMessage Encrypted -> IO ()) | 240 | tcpRelay :: TransportCrypto |
221 | tcpRelay udp_addr sendOnion = do | 241 | -> SockAddr -- ^ UDP bind address (this port may be tried for TCP if hardcoded defaults dont work). |
222 | crypto <- newCrypto | 242 | -> (SockAddr -> OnionRequest N1 -> IO ()) -- ^ This callback will be used to forward onion messages over udp. |
243 | -> IO ( ServerHandle -- Handle to the Tox Tcp-Relay server. | ||
244 | , Int -> OnionMessage Encrypted -> IO () -- forward onion response to tcp client. | ||
245 | ) | ||
246 | tcpRelay crypto udp_addr sendOnion = do | ||
223 | cons <- newTVarIO Map.empty | 247 | cons <- newTVarIO Map.empty |
224 | clients <- newTVarIO IntMap.empty | 248 | clients <- newTVarIO IntMap.empty |
225 | b443 <- getBindAddress "443" True | 249 | b443 <- getBindAddress "443" True |
226 | b80 <- getBindAddress "80" True | 250 | b80 <- getBindAddress "80" True |
251 | b3389 <- getBindAddress "3389" True | ||
227 | b33445 <- getBindAddress "33445" True | 252 | b33445 <- getBindAddress "33445" True |
228 | bany <- getBindAddress "" True | 253 | bany <- getBindAddress "" True |
229 | h <- streamServer ServerConfig | 254 | h <- streamServer ServerConfig |
230 | { serverWarn = hPutStrLn stderr | 255 | { serverWarn = hPutStrLn stderr |
231 | , serverSession = relaySession crypto cons sendOnion | 256 | , serverSession = relaySession crypto clients cons sendOnion |
232 | } | 257 | } |
233 | [b443,b80,udp_addr,b33445,bany] | 258 | [b443,b80,b3389,udp_addr,b33445,bany] |
234 | return (h,sendTCP_ clients) | 259 | return (h,sendTCP_ clients) |
235 | 260 | ||
diff --git a/dht/src/Network/Tox/TCP.hs b/dht/src/Network/Tox/TCP.hs index 13da804f..ca4ca817 100644 --- a/dht/src/Network/Tox/TCP.hs +++ b/dht/src/Network/Tox/TCP.hs | |||
@@ -48,7 +48,8 @@ import Network.Tox.DHT.Handlers (toxSpace) | |||
48 | import Network.Tox.Onion.Transport hiding (encrypt,decrypt) | 48 | import Network.Tox.Onion.Transport hiding (encrypt,decrypt) |
49 | import Network.Tox.Onion.Handlers (unwrapAnnounceResponse) | 49 | import Network.Tox.Onion.Handlers (unwrapAnnounceResponse) |
50 | import qualified Network.Tox.NodeId as UDP | 50 | import qualified Network.Tox.NodeId as UDP |
51 | 51 | import Text.XXD | |
52 | import Data.Proxy | ||
52 | 53 | ||
53 | withSize :: Sized x => (Size x -> m (p x)) -> m (p x) | 54 | withSize :: Sized x => (Size x -> m (p x)) -> m (p x) |
54 | withSize f = case size of len -> f len | 55 | withSize f = case size of len -> f len |
@@ -89,14 +90,23 @@ tcpStream crypto = StreamHandshake | |||
89 | noncef <- lookupNonceFunction crypto (transportSecret crypto) (UDP.id2key $ nodeId addr) | 90 | noncef <- lookupNonceFunction crypto (transportSecret crypto) (UDP.id2key $ nodeId addr) |
90 | dput XTCP $ "TCP:" ++ show addr ++ " <-- " ++ show hello | 91 | dput XTCP $ "TCP:" ++ show addr ++ " <-- " ++ show hello |
91 | hPut h $ encode $ encryptPayload (noncef $ helloNonce hello) hello | 92 | hPut h $ encode $ encryptPayload (noncef $ helloNonce hello) hello |
92 | welcomeE <- withSize $ fmap decode . hGet h . constSize | 93 | (welcomeE, wbs) <- do |
94 | let sz0 = size | ||
95 | sz = constSize sz0 | ||
96 | bs <- hGet h sz | ||
97 | return ( fmap (`asProxyTypeOf` sz0) $ decode bs, bs ) | ||
93 | let mwelcome = welcomeE >>= \w -> decryptPayload (noncef $ welcomeNonce w) w | 98 | let mwelcome = welcomeE >>= \w -> decryptPayload (noncef $ welcomeNonce w) w |
94 | nil = SessionProtocol | 99 | nil = SessionProtocol |
95 | { streamGoodbye = return () | 100 | { streamGoodbye = return () |
96 | , streamDecode = return Nothing | 101 | , streamDecode = return Nothing |
97 | , streamEncode = \y -> dput XTCP $ "TCP nil <-- " ++ show y | 102 | , streamEncode = \y -> dput XTCP $ "TCP nil <-- " ++ show y |
98 | } | 103 | } |
99 | either (\_ -> return nil) id $ mwelcome <&> \welcome -> do | 104 | either (\e -> do |
105 | dput XTCP $ "welcome: " ++ show (Data.ByteString.length wbs) ++ " bytes." | ||
106 | forM_ (xxd2 0 wbs) $ dput XTCP | ||
107 | dput XTCP $ "TCP(fail welcome): " ++ e | ||
108 | return nil | ||
109 | ) id $ mwelcome <&> \welcome -> do | ||
100 | dput XTCP $ "TCP:" ++ show addr ++ " --> " ++ show welcome | 110 | dput XTCP $ "TCP:" ++ show addr ++ " --> " ++ show welcome |
101 | noncef' <- lookupNonceFunction crypto skey (sessionPublicKey $ runIdentity $ welcomeData welcome) | 111 | noncef' <- lookupNonceFunction crypto skey (sessionPublicKey $ runIdentity $ welcomeData welcome) |
102 | nsend <- newMVar (sessionBaseNonce $ runIdentity $ helloData hello) | 112 | nsend <- newMVar (sessionBaseNonce $ runIdentity $ helloData hello) |
@@ -118,6 +128,7 @@ tcpStream crypto = StreamHandshake | |||
118 | dput XTCP $ "TCP: Failed to decode packet." | 128 | dput XTCP $ "TCP: Failed to decode packet." |
119 | return Nothing | 129 | return Nothing |
120 | Right x -> do | 130 | Right x -> do |
131 | dput XTCP $ "TCP:"++ show addr ++ " --> packet!" | ||
121 | m24 <- timeout 1000000 (takeMVar nread) | 132 | m24 <- timeout 1000000 (takeMVar nread) |
122 | fmap join $ forM m24 $ \n24 -> do | 133 | fmap join $ forM m24 $ \n24 -> do |
123 | let r = decrypt (noncef' n24) x >>= decodePlain | 134 | let r = decrypt (noncef' n24) x >>= decodePlain |
@@ -133,16 +144,16 @@ tcpStream crypto = StreamHandshake | |||
133 | dput XTCP $ "TCP exception: " ++ show e | 144 | dput XTCP $ "TCP exception: " ++ show e |
134 | return Nothing | 145 | return Nothing |
135 | , streamEncode = \y -> do | 146 | , streamEncode = \y -> do |
136 | dput XTCP $ "TCP(acquire nonce):" ++ show addr ++ " <-- " ++ show y | 147 | -- dput XTCP $ "TCP(acquire nonce):" ++ show addr ++ " <-- " ++ show y |
137 | n24 <- takeMVar nsend | 148 | n24 <- takeMVar nsend |
138 | dput XTCP $ "TCP(got nonce):" ++ show addr ++ " <-- " ++ show y | 149 | -- dput XTCP $ "TCP(got nonce):" ++ show addr ++ " <-- " ++ show y |
139 | let bs = encode $ encrypt (noncef' n24) $ encodePlain y | 150 | let bs = encode $ encrypt (noncef' n24) $ encodePlain y |
140 | ($ h) -- bracket (takeMVar hvar) (putMVar hvar) | 151 | ($ h) -- bracket (takeMVar hvar) (putMVar hvar) |
141 | $ \h -> hPut h (encode (fromIntegral $ Data.ByteString.length bs :: Word16) <> bs) | 152 | $ \h -> hPut h (encode (fromIntegral $ Data.ByteString.length bs :: Word16) <> bs) |
142 | `catchIOError` \e -> dput XTCP $ "TCP write exception: " ++ show e | 153 | `catchIOError` \e -> dput XTCP $ "TCP write exception: " ++ show e |
143 | dput XTCP $ "TCP(incrementing nonce): " ++ show addr ++ " <-- " ++ show y | 154 | -- dput XTCP $ "TCP(incrementing nonce): " ++ show addr ++ " <-- " ++ show y |
144 | putMVar nsend (incrementNonce24 n24) | 155 | putMVar nsend (incrementNonce24 n24) |
145 | dput XTCP $ "TCP(finished): " ++ show addr ++ " <-- " ++ show y | 156 | dput XTCP $ "TCP: " ++ show addr ++ " <-- " ++ show y |
146 | } | 157 | } |
147 | , streamAddr = nodeAddr | 158 | , streamAddr = nodeAddr |
148 | } | 159 | } |