summaryrefslogtreecommitdiff
path: root/src/Network/BitTorrent/Address.hs
diff options
context:
space:
mode:
Diffstat (limited to 'src/Network/BitTorrent/Address.hs')
-rw-r--r--src/Network/BitTorrent/Address.hs242
1 files changed, 6 insertions, 236 deletions
diff --git a/src/Network/BitTorrent/Address.hs b/src/Network/BitTorrent/Address.hs
index 560ac1ef..f364abbe 100644
--- a/src/Network/BitTorrent/Address.hs
+++ b/src/Network/BitTorrent/Address.hs
@@ -13,6 +13,7 @@
13{-# LANGUAGE FlexibleInstances #-} 13{-# LANGUAGE FlexibleInstances #-}
14{-# LANGUAGE FlexibleContexts #-} 14{-# LANGUAGE FlexibleContexts #-}
15{-# LANGUAGE RecordWildCards #-} 15{-# LANGUAGE RecordWildCards #-}
16{-# LANGUAGE ScopedTypeVariables #-}
16{-# LANGUAGE StandaloneDeriving #-} 17{-# LANGUAGE StandaloneDeriving #-}
17{-# LANGUAGE ViewPatterns #-} 18{-# LANGUAGE ViewPatterns #-}
18{-# LANGUAGE GeneralizedNewtypeDeriving #-} 19{-# LANGUAGE GeneralizedNewtypeDeriving #-}
@@ -60,13 +61,10 @@ module Network.BitTorrent.Address
60 -- * Node 61 -- * Node
61 -- ** Id 62 -- ** Id
62 , NodeId 63 , NodeId
63 , nodeIdSize
64 , testIdBit 64 , testIdBit
65 , genNodeId 65 , genNodeId
66 , bucketRange 66 , bucketRange
67 , genBucketSample 67 , genBucketSample
68 , bep42
69 , bep42s
70 68
71 -- ** Info 69 -- ** Info
72 , NodeAddr (..) 70 , NodeAddr (..)
@@ -129,47 +127,15 @@ import System.Locale (defaultTimeLocale)
129#endif 127#endif
130import System.Entropy 128import System.Entropy
131import Data.Digest.CRC32C 129import Data.Digest.CRC32C
132import qualified Network.RPC as RPC 130import Network.RPC as RPC
133import Network.KRPC.Message (KMessageOf) 131import Network.KRPC.Message (KMessageOf)
134import Network.DHT.Mainline 132-- import Network.DHT.Mainline
135 133
136-- import Paths_bittorrent (version) 134-- import Paths_bittorrent (version)
137 135
138{-----------------------------------------------------------------------
139-- Address
140-----------------------------------------------------------------------}
141
142instance Pretty UTCTime where 136instance Pretty UTCTime where
143 pPrint = PP.text . show 137 pPrint = PP.text . show
144 138
145class (Eq a, Serialize a, Typeable a, Hashable a, Pretty a)
146 => Address a where
147 toSockAddr :: a -> SockAddr
148 fromSockAddr :: SockAddr -> Maybe a
149
150fromAddr :: (Address a, Address b) => a -> Maybe b
151fromAddr = fromSockAddr . toSockAddr
152
153-- | Note that port is zeroed.
154instance Address IPv4 where
155 toSockAddr = SockAddrInet 0 . toHostAddress
156 fromSockAddr (SockAddrInet _ h) = Just (fromHostAddress h)
157 fromSockAddr _ = Nothing
158
159-- | Note that port is zeroed.
160instance Address IPv6 where
161 toSockAddr h = SockAddrInet6 0 0 (toHostAddress6 h) 0
162 fromSockAddr (SockAddrInet6 _ _ h _) = Just (fromHostAddress6 h)
163 fromSockAddr _ = Nothing
164
165-- | Note that port is zeroed.
166instance Address IP where
167 toSockAddr (IPv4 h) = toSockAddr h
168 toSockAddr (IPv6 h) = toSockAddr h
169 fromSockAddr sa =
170 IPv4 <$> fromSockAddr sa
171 <|> IPv6 <$> fromSockAddr sa
172
173setPort :: PortNumber -> SockAddr -> SockAddr 139setPort :: PortNumber -> SockAddr -> SockAddr
174setPort port (SockAddrInet _ h ) = SockAddrInet port h 140setPort port (SockAddrInet _ h ) = SockAddrInet port h
175setPort port (SockAddrInet6 _ f h s) = SockAddrInet6 port f h s 141setPort port (SockAddrInet6 _ f h s) = SockAddrInet6 port f h s
@@ -388,21 +354,6 @@ instance BEncode PortNumber where
388 = pure $ fromIntegral n 354 = pure $ fromIntegral n
389 | otherwise = decodingError $ "PortNumber: " ++ show n 355 | otherwise = decodingError $ "PortNumber: " ++ show n
390#endif 356#endif
391
392instance Serialize PortNumber where
393 get = fromIntegral <$> getWord16be
394 {-# INLINE get #-}
395 put = putWord16be . fromIntegral
396 {-# INLINE put #-}
397
398instance Hashable PortNumber where
399 hashWithSalt s = hashWithSalt s . fromEnum
400 {-# INLINE hashWithSalt #-}
401
402instance Pretty PortNumber where
403 pPrint = PP.int . fromEnum
404 {-# INLINE pPrint #-}
405
406{----------------------------------------------------------------------- 357{-----------------------------------------------------------------------
407-- IP addr 358-- IP addr
408-----------------------------------------------------------------------} 359-----------------------------------------------------------------------}
@@ -457,51 +408,6 @@ instance BEncode IPv6 where
457 {-# INLINE fromBEncode #-} 408 {-# INLINE fromBEncode #-}
458#endif 409#endif
459 410
460-- | When 'get'ing an IP it must be 'isolate'd to the appropriate
461-- number of bytes since we have no other way of telling which
462-- address type we are trying to parse
463instance Serialize IP where
464 put (IPv4 ip) = put ip
465 put (IPv6 ip) = put ip
466
467 get = do
468 n <- remaining
469 case n of
470 4 -> IPv4 <$> get
471 16 -> IPv6 <$> get
472 _ -> fail (show n ++ " is the wrong number of remaining bytes to parse IP")
473
474instance Serialize IPv4 where
475 put = putWord32host . toHostAddress
476 get = fromHostAddress <$> getWord32host
477
478instance Serialize IPv6 where
479 put ip = put $ toHostAddress6 ip
480 get = fromHostAddress6 <$> get
481
482instance Pretty IPv4 where
483 pPrint = PP.text . show
484 {-# INLINE pPrint #-}
485
486instance Pretty IPv6 where
487 pPrint = PP.text . show
488 {-# INLINE pPrint #-}
489
490instance Pretty IP where
491 pPrint = PP.text . show
492 {-# INLINE pPrint #-}
493
494instance Hashable IPv4 where
495 hashWithSalt = hashUsing toHostAddress
496 {-# INLINE hashWithSalt #-}
497
498instance Hashable IPv6 where
499 hashWithSalt s a = hashWithSalt s (toHostAddress6 a)
500
501instance Hashable IP where
502 hashWithSalt s (IPv4 h) = hashWithSalt s h
503 hashWithSalt s (IPv6 h) = hashWithSalt s h
504
505{----------------------------------------------------------------------- 411{-----------------------------------------------------------------------
506-- Peer addr 412-- Peer addr
507-----------------------------------------------------------------------} 413-----------------------------------------------------------------------}
@@ -666,13 +572,6 @@ testIdBit :: FiniteBits bs => bs -> Word -> Bool
666testIdBit bs i = testBit bs (fromIntegral (finiteBitSize bs - fromIntegral i)) 572testIdBit bs i = testBit bs (fromIntegral (finiteBitSize bs - fromIntegral i))
667{-# INLINE testIdBit #-} 573{-# INLINE testIdBit #-}
668 574
669-- TODO WARN is the 'system' random suitable for this?
670-- | Generate random NodeID used for the entire session.
671-- Distribution of ID's should be as uniform as possible.
672--
673genNodeId :: IO NodeId
674genNodeId = NodeId . either error id . S.decode <$> getEntropy nodeIdSize
675
676------------------------------------------------------------------------ 575------------------------------------------------------------------------
677 576
678-- | Accepts a depth/index of a bucket and whether or not it is the last one, 577-- | Accepts a depth/index of a bucket and whether or not it is the last one,
@@ -693,54 +592,8 @@ bucketRange depth is_last = (q,m,b)
693 m = 2^(7-r) - 1 592 m = 2^(7-r) - 1
694 b = if is_last then 0 else 2^(7-r) 593 b = if is_last then 0 else 2^(7-r)
695 594
696-- | Generate a random 'NodeId' within a range suitable for a bucket. To
697-- obtain a sample for bucket number /index/ where /is_last/ indicates if this
698-- is for the current deepest bucket in our routing table:
699--
700-- > sample <- genBucketSample nid (bucketRange index is_last)
701genBucketSample :: NodeId -> (Int,Word8,Word8) -> IO NodeId
702genBucketSample n qmb = genBucketSample' getEntropy n qmb
703
704-- | Generalizion of 'genBucketSample' that accepts a byte generator
705-- function to use instead of the system entropy.
706genBucketSample' :: Applicative m =>
707 (Int -> m ByteString) -> NodeId -> (Int,Word8,Word8) -> m NodeId
708genBucketSample' gen (NodeId self) (q,m,b)
709 | q <= 0 = NodeId . either error id . S.decode <$> gen nodeIdSize
710 | q >= nodeIdSize = pure (NodeId self)
711 | otherwise = NodeId . either error id . S.decode . build <$> gen (nodeIdSize - q + 1)
712 where
713 build tl = BS.init hd <> BS.cons (h .|. t) (BS.tail tl)
714 where
715 hd = BS.take q $ S.encode self
716 h = xor b (complement m .&. BS.last hd)
717 t = m .&. BS.head tl
718
719------------------------------------------------------------------------ 595------------------------------------------------------------------------
720 596
721data NodeAddr a = NodeAddr
722 { nodeHost :: !a
723 , nodePort :: {-# UNPACK #-} !PortNumber
724 } deriving (Eq, Ord, Typeable, Functor, Foldable, Traversable)
725
726instance Show a => Show (NodeAddr a) where
727 showsPrec i NodeAddr {..}
728 = showsPrec i nodeHost <> showString ":" <> showsPrec i nodePort
729
730instance Read (NodeAddr IPv4) where
731 readsPrec i x = [ (fromPeerAddr a, s) | (a, s) <- readsPrec i x ]
732
733-- | @127.0.0.1:6882@
734instance Default (NodeAddr IPv4) where
735 def = "127.0.0.1:6882"
736
737-- | KRPC compatible encoding.
738instance Serialize a => Serialize (NodeAddr a) where
739 get = NodeAddr <$> get <*> get
740 {-# INLINE get #-}
741 put NodeAddr {..} = put nodeHost >> put nodePort
742 {-# INLINE put #-}
743
744#ifdef VERSION_bencoding 597#ifdef VERSION_bencoding
745-- | Torrent file compatible encoding. 598-- | Torrent file compatible encoding.
746instance BEncode a => BEncode (NodeAddr a) where 599instance BEncode a => BEncode (NodeAddr a) where
@@ -750,20 +603,6 @@ instance BEncode a => BEncode (NodeAddr a) where
750 {-# INLINE fromBEncode #-} 603 {-# INLINE fromBEncode #-}
751#endif 604#endif
752 605
753instance Hashable a => Hashable (NodeAddr a) where
754 hashWithSalt s NodeAddr {..} = hashWithSalt s (nodeHost, nodePort)
755 {-# INLINE hashWithSalt #-}
756
757instance Pretty ip => Pretty (NodeAddr ip) where
758 pPrint NodeAddr {..} = pPrint nodeHost <> ":" <> pPrint nodePort
759
760-- | Example:
761--
762-- @nodePort \"127.0.0.1:6881\" == 6881@
763--
764instance IsString (NodeAddr IPv4) where
765 fromString = fromPeerAddr . fromString
766
767fromPeerAddr :: PeerAddr a -> NodeAddr a 606fromPeerAddr :: PeerAddr a -> NodeAddr a
768fromPeerAddr PeerAddr {..} = NodeAddr 607fromPeerAddr PeerAddr {..} = NodeAddr
769 { nodeHost = peerHost 608 { nodeHost = peerHost
@@ -772,45 +611,10 @@ fromPeerAddr PeerAddr {..} = NodeAddr
772 611
773------------------------------------------------------------------------ 612------------------------------------------------------------------------
774 613
775data NodeInfo dht addr u = NodeInfo
776 { nodeId :: !(RPC.NodeId dht)
777 , nodeAddr :: !(NodeAddr addr)
778 , nodeAnnotation :: u
779 } deriving (Functor, Foldable, Traversable)
780
781deriving instance ( Show (RPC.NodeId dht)
782 , Show addr
783 , Show u ) => Show (NodeInfo dht addr u)
784
785mapAddress :: (addr -> b) -> NodeInfo dht addr u -> NodeInfo dht b u
786mapAddress f ni = ni { nodeAddr = fmap f (nodeAddr ni) }
787
788traverseAddress :: Applicative f => (addr -> f b) -> NodeInfo dht addr u -> f (NodeInfo dht b u)
789traverseAddress f ni = fmap (\addr -> ni { nodeAddr = addr }) $ traverse f (nodeAddr ni)
790
791-- Warning: Eq and Ord only look at the nodeId field.
792instance Eq (RPC.NodeId dht) => Eq (NodeInfo dht a u) where
793 a == b = (nodeId a == nodeId b)
794
795instance Ord (RPC.NodeId dht) => Ord (NodeInfo dht a u) where
796 compare = comparing nodeId
797
798-- | KRPC 'compact list' compatible encoding: contact information for
799-- nodes is encoded as a 26-byte string. Also known as "Compact node
800-- info" the 20-byte Node ID in network byte order has the compact
801-- IP-address/port info concatenated to the end.
802instance Serialize a => Serialize (NodeInfo KMessageOf a ()) where
803 get = (\a b -> NodeInfo a b ()) <$> get <*> get
804 put NodeInfo {..} = put nodeId >> put nodeAddr
805
806instance Pretty ip => Pretty (NodeInfo KMessageOf ip ()) where
807 pPrint NodeInfo {..} = pPrint nodeId <> "@(" <> pPrint nodeAddr <> ")"
808
809instance Pretty ip => Pretty [NodeInfo KMessageOf ip ()] where
810 pPrint = PP.vcat . PP.punctuate "," . L.map pPrint
811
812-- | Order by closeness: nearest nodes first. 614-- | Order by closeness: nearest nodes first.
813rank :: (x -> NodeId) -> NodeId -> [x] -> [x] 615rank :: ( Ord (NodeId dht)
616 , Bits (NodeId dht)
617 ) => (x -> NodeId dht) -> NodeId dht -> [x] -> [x]
814rank f nid = L.sortBy (comparing (RPC.distance nid . f)) 618rank f nid = L.sortBy (comparing (RPC.distance nid . f))
815 619
816{----------------------------------------------------------------------- 620{-----------------------------------------------------------------------
@@ -1219,40 +1023,6 @@ fingerprint pid = either (const def) id $ runGet getCI (getPeerId pid)
1219 return $ Version (catMaybes $ L.map decodeShadowVerNr str) [] 1023 return $ Version (catMaybes $ L.map decodeShadowVerNr str) []
1220 1024
1221 1025
1222-- | Yields all 8 DHT neighborhoods available to you given a particular ip
1223-- address.
1224bep42s :: Address a => a -> NodeId -> [NodeId]
1225bep42s addr (NodeId r) = mapMaybe (bep42 addr) rs
1226 where
1227 rs = L.map (NodeId . change3bits r) [0..7]
1228
1229-- change3bits :: ByteString -> Word8 -> ByteString
1230-- change3bits bs n = BS.snoc (BS.init bs) (BS.last bs .&. 0xF8 .|. n)
1231
1232change3bits :: (Num b, Bits b) => b -> b -> b
1233change3bits bs n = (bs .&. complement 7) .|. n
1234
1235-- | Modifies a purely random 'NodeId' to one that is related to a given
1236-- routable address in accordance with BEP 42.
1237bep42 :: Address a => a -> NodeId -> Maybe NodeId
1238bep42 addr (NodeId r)
1239 | Just ip <- fmap S.encode (fromAddr addr :: Maybe IPv4)
1240 <|> fmap S.encode (fromAddr addr :: Maybe IPv6)
1241 = genBucketSample' retr (NodeId $ crc $ applyMask ip) (3,0x07,0)
1242 | otherwise
1243 = Nothing
1244 where
1245 ip4mask = "\x03\x0f\x3f\xff" :: ByteString
1246 ip6mask = "\x01\x03\x07\x0f\x1f\x3f\x7f\xff" :: ByteString
1247 nbhood_select = (fromIntegral r :: Word8) .&. 7
1248 retr n = pure $ BS.drop (nodeIdSize - n) $ S.encode r
1249 crc = flip shiftL (finiteBitSize (NodeId undefined) - 32) . fromIntegral . crc32c . BS.pack
1250 applyMask ip = case BS.zipWith (.&.) msk ip of
1251 (b:bs) -> (b .|. shiftL nbhood_select 5) : bs
1252 bs -> bs
1253 where msk | BS.length ip == 4 = ip4mask
1254 | otherwise = ip6mask
1255
1256 1026
1257-- | Given a string specifying a port (numeric or service name) 1027-- | Given a string specifying a port (numeric or service name)
1258-- and a flag indicating whether you want to support IPv6, this 1028-- and a flag indicating whether you want to support IPv6, this