diff options
Diffstat (limited to 'src/Network/BitTorrent/Core')
-rw-r--r-- | src/Network/BitTorrent/Core/PeerAddr.hs | 134 |
1 files changed, 109 insertions, 25 deletions
diff --git a/src/Network/BitTorrent/Core/PeerAddr.hs b/src/Network/BitTorrent/Core/PeerAddr.hs index 94510bba..1da4c81a 100644 --- a/src/Network/BitTorrent/Core/PeerAddr.hs +++ b/src/Network/BitTorrent/Core/PeerAddr.hs | |||
@@ -12,12 +12,18 @@ | |||
12 | {-# LANGUAGE StandaloneDeriving #-} | 12 | {-# LANGUAGE StandaloneDeriving #-} |
13 | {-# LANGUAGE GeneralizedNewtypeDeriving #-} | 13 | {-# LANGUAGE GeneralizedNewtypeDeriving #-} |
14 | {-# LANGUAGE DeriveDataTypeable #-} | 14 | {-# LANGUAGE DeriveDataTypeable #-} |
15 | {-# LANGUAGE FlexibleInstances #-} | ||
16 | {-# LANGUAGE DeriveFunctor #-} | ||
15 | {-# OPTIONS -fno-warn-orphans #-} -- for PortNumber instances | 17 | {-# OPTIONS -fno-warn-orphans #-} -- for PortNumber instances |
16 | module Network.BitTorrent.Core.PeerAddr | 18 | module Network.BitTorrent.Core.PeerAddr |
17 | ( -- * Peer address | 19 | ( -- * Peer address |
18 | PeerAddr(..) | 20 | PeerAddr(..) |
19 | , defaultPorts | 21 | , defaultPorts |
20 | , peerSockAddr | 22 | , peerSockAddr |
23 | , mergeIPLists | ||
24 | , splitIPList | ||
25 | , IP, IPv4, IPv6 --re-export Data.IP constructors | ||
26 | , IPAddress () | ||
21 | ) where | 27 | ) where |
22 | 28 | ||
23 | import Control.Applicative | 29 | import Control.Applicative |
@@ -38,10 +44,14 @@ import Data.String | |||
38 | import Data.Typeable | 44 | import Data.Typeable |
39 | import Data.Word | 45 | import Data.Word |
40 | import Data.IP | 46 | import Data.IP |
47 | import Data.Maybe | ||
48 | import Data.Foldable | ||
49 | import Data.Either | ||
41 | import Network.Socket | 50 | import Network.Socket |
42 | import Text.PrettyPrint | 51 | import Text.PrettyPrint |
43 | import Text.PrettyPrint.Class | 52 | import Text.PrettyPrint.Class |
44 | import Text.Read (readMaybe) | 53 | import Text.Read (readMaybe) |
54 | import qualified Text.ParserCombinators.ReadP as RP | ||
45 | import System.IO.Unsafe | 55 | import System.IO.Unsafe |
46 | 56 | ||
47 | import Data.Torrent.JSON | 57 | import Data.Torrent.JSON |
@@ -61,32 +71,74 @@ instance Serialize PortNumber where | |||
61 | put = putWord16be . fromIntegral | 71 | put = putWord16be . fromIntegral |
62 | {-# INLINE put #-} | 72 | {-# INLINE put #-} |
63 | 73 | ||
74 | class (Show i, Read i) => IPAddress i where | ||
75 | showIp :: i -> String | ||
76 | showIp = show | ||
77 | |||
78 | readIp :: String -> i | ||
79 | readIp = read | ||
80 | |||
81 | toHostAddr :: i -> Either HostAddress HostAddress6 | ||
82 | |||
83 | instance IPAddress IPv4 where | ||
84 | toHostAddr = Left . toHostAddress | ||
85 | |||
86 | instance IPAddress IPv6 where | ||
87 | toHostAddr = Right . toHostAddress6 | ||
88 | |||
89 | instance IPAddress IP where | ||
90 | toHostAddr (IPv4 ip) = toHostAddr ip | ||
91 | toHostAddr (IPv6 ip) = toHostAddr ip | ||
92 | |||
93 | |||
94 | deriving instance Typeable IP | ||
95 | deriving instance Typeable IPv4 | ||
96 | deriving instance Typeable IPv6 | ||
97 | |||
98 | ipToBEncode ip = BString $ BS8.pack $ showIp ip | ||
99 | ipFromBEncode (BString ip) = return $ readIp $ BS8.unpack ip | ||
100 | |||
101 | instance BEncode IP where | ||
102 | toBEncode = ipToBEncode | ||
103 | fromBEncode = ipFromBEncode | ||
104 | |||
105 | instance BEncode IPv4 where | ||
106 | toBEncode = ipToBEncode | ||
107 | fromBEncode = ipFromBEncode | ||
108 | |||
109 | instance BEncode IPv6 where | ||
110 | toBEncode = ipToBEncode | ||
111 | fromBEncode = ipFromBEncode | ||
112 | |||
113 | instance Serialize IPv4 where | ||
114 | put ip = put $ toHostAddress ip | ||
115 | get = fromHostAddress <$> get | ||
116 | |||
117 | instance Serialize IPv6 where | ||
118 | put ip = put $ toHostAddress6 ip | ||
119 | get = fromHostAddress6 <$> get | ||
120 | |||
64 | -- TODO check semantic of ord and eq instances | 121 | -- TODO check semantic of ord and eq instances |
65 | -- TODO use SockAddr instead of peerIP and peerPort | 122 | -- TODO use SockAddr instead of peerIP and peerPort |
66 | 123 | ||
67 | -- | Peer address info normally extracted from peer list or peer | 124 | -- | Peer address info normally extracted from peer list or peer |
68 | -- compact list encoding. | 125 | -- compact list encoding. |
69 | data PeerAddr = PeerAddr | 126 | data PeerAddr a = PeerAddr |
70 | { peerId :: !(Maybe PeerId) | 127 | { peerId :: !(Maybe PeerId) |
71 | , peerIP :: {-# UNPACK #-} !IP | 128 | , peerAddr :: a |
72 | , peerPort :: {-# UNPACK #-} !PortNumber | 129 | , peerPort :: {-# UNPACK #-} !PortNumber |
73 | } deriving (Show, Eq, Typeable) | 130 | } deriving (Show, Eq, Typeable, Functor) |
74 | |||
75 | instance BEncode IP where | ||
76 | toBEncode ip = toBEncode $ BS8.pack $ show ip | ||
77 | fromBEncode (BString ip) = return $ fromString $ BS8.unpack ip | ||
78 | 131 | ||
79 | peer_id_key, peer_ip_key, peer_port_key :: BKey | 132 | peer_id_key, peer_ip_key, peer_port_key :: BKey |
80 | peer_id_key = "peer id" | 133 | peer_id_key = "peer id" |
81 | peer_ip_key = "ip" | 134 | peer_ip_key = "ip" |
82 | peer_port_key = "port" | 135 | peer_port_key = "port" |
83 | 136 | ||
84 | -- FIXME do we need to byteswap peerIP in bencode instance? | ||
85 | -- | The tracker's 'announce response' compatible encoding. | 137 | -- | The tracker's 'announce response' compatible encoding. |
86 | instance BEncode PeerAddr where | 138 | instance (Typeable a, BEncode a) => BEncode (PeerAddr a) where |
87 | toBEncode PeerAddr {..} = toDict $ | 139 | toBEncode PeerAddr {..} = toDict $ |
88 | peer_id_key .=? peerId | 140 | peer_id_key .=? peerId |
89 | .: peer_ip_key .=! BS8.pack (show peerIP) | 141 | .: peer_ip_key .=! peerAddr |
90 | .: peer_port_key .=! peerPort | 142 | .: peer_port_key .=! peerPort |
91 | .: endDict | 143 | .: endDict |
92 | 144 | ||
@@ -95,19 +147,32 @@ instance BEncode PeerAddr where | |||
95 | <*>! peer_ip_key | 147 | <*>! peer_ip_key |
96 | <*>! peer_port_key | 148 | <*>! peer_port_key |
97 | 149 | ||
150 | mergeIPLists :: [PeerAddr IPv4] -> Maybe [PeerAddr IPv6] -> [PeerAddr IP] | ||
151 | mergeIPLists v4 v6 = (fmap IPv4 `L.map` v4) | ||
152 | ++ (fmap IPv6 `L.map` Data.Foldable.concat v6) | ||
153 | |||
154 | splitIPList :: [PeerAddr IP] -> ([PeerAddr IPv4],[PeerAddr IPv6]) | ||
155 | splitIPList xs = partitionEithers $ toEither <$> xs | ||
156 | where | ||
157 | toEither :: PeerAddr IP -> Either (PeerAddr IPv4) (PeerAddr IPv6) | ||
158 | toEither pa@(PeerAddr _ (IPv4 _) _) = Left (ipv4 <$> pa) | ||
159 | toEither pa@(PeerAddr _ (IPv6 _) _) = Right (ipv6 <$> pa) | ||
160 | |||
161 | |||
98 | -- | The tracker's 'compact peer list' compatible encoding. The | 162 | -- | The tracker's 'compact peer list' compatible encoding. The |
99 | -- 'peerId' is always 'Nothing'. | 163 | -- 'peerId' is always 'Nothing'. |
100 | -- | 164 | -- |
101 | -- For more info see: <http://www.bittorrent.org/beps/bep_0023.html> | 165 | -- For more info see: <http://www.bittorrent.org/beps/bep_0023.html> |
102 | -- | 166 | -- |
103 | instance Serialize PeerAddr where -- TODO do it properly | 167 | -- TODO: test byte order |
104 | put PeerAddr {..} = (putWord32host $ toHostAddress $ ipv4 peerIP) >> put peerPort | 168 | instance (Serialize a) => Serialize (PeerAddr a) where |
105 | {-# INLINE put #-} | 169 | put PeerAddr {..} = |
106 | get = PeerAddr Nothing <$> (IPv4 . fromHostAddress <$> getWord32host) <*> get | 170 | put peerAddr >> put peerPort |
107 | {-# INLINE get #-} | 171 | get = |
172 | PeerAddr Nothing <$> get <*> get | ||
108 | 173 | ||
109 | -- | @127.0.0.1:6881@ | 174 | -- | @127.0.0.1:6881@ |
110 | instance Default PeerAddr where | 175 | instance Default (PeerAddr IPv4) where |
111 | def = "127.0.0.1:6881" | 176 | def = "127.0.0.1:6881" |
112 | 177 | ||
113 | -- inet_addr is pure; so it is safe to throw IO | 178 | -- inet_addr is pure; so it is safe to throw IO |
@@ -122,30 +187,49 @@ unsafeCatchIO m = unsafePerformIO $ | |||
122 | -- | 187 | -- |
123 | -- @peerPort \"127.0.0.1:6881\" == 6881@ | 188 | -- @peerPort \"127.0.0.1:6881\" == 6881@ |
124 | -- | 189 | -- |
125 | instance IsString PeerAddr where | 190 | instance IsString (PeerAddr IPv4) where |
126 | fromString str -- TODO IPv6 | 191 | fromString str |
127 | | [hostAddrStr, portStr] <- splitWhen (== ':') str | 192 | | [hostAddrStr, portStr] <- splitWhen (== ':') str |
128 | , Just hostAddr <- read hostAddrStr | 193 | , hostAddr <- read hostAddrStr |
129 | , Just portNum <- toEnum <$> readMaybe portStr | 194 | , Just portNum <- toEnum <$> readMaybe portStr |
130 | = PeerAddr Nothing hostAddr portNum | 195 | = PeerAddr Nothing hostAddr portNum |
131 | | otherwise = error $ "fromString: unable to parse PeerAddr: " ++ str | 196 | | otherwise = error $ "fromString: unable to parse (PeerAddr IPv4): " ++ str |
197 | |||
198 | readsIPv6_port :: String -> [((IPv6, PortNumber), String)] | ||
199 | readsIPv6_port = RP.readP_to_S $ do | ||
200 | ip <- RP.char '[' *> (RP.readS_to_P reads) <* RP.char ']' | ||
201 | RP.char ':' | ||
202 | port <- toEnum <$> read <$> (RP.many1 $ RP.satisfy isDigit) <* RP.eof | ||
203 | return (ip,port) | ||
204 | |||
205 | instance IsString (PeerAddr IPv6) where | ||
206 | fromString str | ||
207 | | [((ip,port),"")] <- readsIPv6_port str = | ||
208 | PeerAddr Nothing ip port | ||
209 | | otherwise = error $ "fromString: unable to parse (PeerAddr IPv6): " ++ str | ||
132 | 210 | ||
133 | -- | fingerprint + "at" + dotted.host.inet.addr:port | 211 | -- | fingerprint + "at" + dotted.host.inet.addr:port |
134 | instance Pretty PeerAddr where | 212 | -- TODO: instances for IPv6, HostName |
213 | instance Pretty (PeerAddr IP) where | ||
135 | pretty p @ PeerAddr {..} | 214 | pretty p @ PeerAddr {..} |
136 | | Just pid <- peerId = pretty (fingerprint pid) <+> "at" <+> paddr | 215 | | Just pid <- peerId = pretty (fingerprint pid) <+> "at" <+> paddr |
137 | | otherwise = paddr | 216 | | otherwise = paddr |
138 | where | 217 | where |
139 | paddr = text (show (peerSockAddr p)) | 218 | paddr = text (show peerAddr ++ ":" ++ show peerPort) |
140 | 219 | ||
141 | -- | Ports typically reserved for bittorrent P2P listener. | 220 | -- | Ports typically reserved for bittorrent P2P listener. |
142 | defaultPorts :: [PortNumber] | 221 | defaultPorts :: [PortNumber] |
143 | defaultPorts = [6881..6889] | 222 | defaultPorts = [6881..6889] |
144 | 223 | ||
224 | resolvePeerAddr :: (IPAddress i) => PeerAddr HostName -> PeerAddr i | ||
225 | resolvePeerAddr = undefined | ||
226 | |||
145 | -- | Convert peer info from tracker response to socket address. Used | 227 | -- | Convert peer info from tracker response to socket address. Used |
146 | -- for establish connection between peers. | 228 | -- for establish connection between peers. |
147 | -- | 229 | -- |
148 | peerSockAddr :: PeerAddr -> SockAddr | 230 | peerSockAddr :: (IPAddress i) => PeerAddr i -> SockAddr |
149 | peerSockAddr PeerAddr {..} | 231 | peerSockAddr PeerAddr {..} |
150 | | IPv4 v4 <- peerIP = SockAddrInet peerPort (toHostAddress v4) | 232 | | Left hAddr <- toHostAddr peerAddr = |
151 | | IPv6 v6 <- peerIP = SockAddrInet6 peerPort 0 (toHostAddress6 v6) 0 | 233 | SockAddrInet peerPort hAddr |
234 | | Right hAddr <- toHostAddr peerAddr = | ||
235 | SockAddrInet6 peerPort 0 hAddr 0 | ||