diff options
-rw-r--r-- | LLCSNAP.hs | 49 | ||||
-rw-r--r-- | WifiHeader.hs | 335 | ||||
-rw-r--r-- | readpackets.hs | 286 | ||||
-rw-r--r-- | src/Hans/Checksum.hs | 136 |
4 files changed, 769 insertions, 37 deletions
diff --git a/LLCSNAP.hs b/LLCSNAP.hs new file mode 100644 index 00000000..417c36ba --- /dev/null +++ b/LLCSNAP.hs | |||
@@ -0,0 +1,49 @@ | |||
1 | module LLCSNAP where | ||
2 | |||
3 | import Data.Bits | ||
4 | import Data.Word | ||
5 | |||
6 | import Net.PacketParsing | ||
7 | |||
8 | |||
9 | -- 802.2 LLC Header : SNAP extension | ||
10 | -- -------------------------------------------------------------------------- | ||
11 | -- DSAP : SSAP : Control : OUI : Protocol ID | ||
12 | -- 1 octet : 1 octet : 1 or 2 octets : 3 octets : 2 octets | ||
13 | |||
14 | data LLCSNAP = LLCSNAP | ||
15 | { dsap :: Word8 | ||
16 | , ssap :: Word8 | ||
17 | , control :: Word16 -- 1 or 2 bytes | ||
18 | , oui :: Word32 -- 3 bytes | ||
19 | , protoid :: Word16 | ||
20 | } | ||
21 | deriving (Eq,Ord,Read,Show) | ||
22 | |||
23 | instance Parse LLCSNAP where | ||
24 | parse = do | ||
25 | -- LLC | ||
26 | dsap <- word8 | ||
27 | ssap <- word8 | ||
28 | control <- fromIntegral <$> word8 | ||
29 | -- Based on "Understanding Logical Link Control" at | ||
30 | -- https://www.cisco.com/c/en/us/support/docs/ibm-technologies/logical-link-control-llc/12247-45.html | ||
31 | -- | ||
32 | -- Unless the low two bits are set, there's | ||
33 | -- another byte of the control field. | ||
34 | nr <- if 0x0003 == control .&. 0x0003 | ||
35 | then return 0 | ||
36 | else fromIntegral <$> word8 | ||
37 | -- SNAP | ||
38 | ouilo <- fromIntegral <$> word16 | ||
39 | ouihi <- fromIntegral <$> word8 | ||
40 | protoid <- word16 | ||
41 | return LLCSNAP | ||
42 | { dsap = dsap | ||
43 | , ssap = ssap | ||
44 | , control = control + 256 * nr | ||
45 | , oui = ouilo + 65536 * ouihi | ||
46 | , protoid = protoid | ||
47 | } | ||
48 | |||
49 | |||
diff --git a/WifiHeader.hs b/WifiHeader.hs new file mode 100644 index 00000000..90e77003 --- /dev/null +++ b/WifiHeader.hs | |||
@@ -0,0 +1,335 @@ | |||
1 | {-# LANGUAGE FlexibleInstances #-} | ||
2 | {-# LANGUAGE GeneralizedNewtypeDeriving #-} | ||
3 | {-# LANGUAGE PatternSynonyms #-} | ||
4 | module WifiHeader where | ||
5 | |||
6 | import Data.Bits | ||
7 | import Data.Word | ||
8 | |||
9 | import Net.Ethernet as Mac (Addr(..)) | ||
10 | import Net.Packet | ||
11 | import Net.PacketParsing | ||
12 | |||
13 | import LLCSNAP | ||
14 | |||
15 | -- 802.11 Mac header | ||
16 | -- | ||
17 | -- 23 bytes | ||
18 | |||
19 | -- Wikipedia: | ||
20 | -- | ||
21 | -- Frames are divided into very specific and standardized sections. Each frame | ||
22 | -- consists of a MAC header, payload, and frame check sequence (FCS). | ||
23 | -- | ||
24 | -- Data frames carry packets from web pages, files, etc. within the body.[51] | ||
25 | -- The body begins with an IEEE 802.2 header, with the Destination Service | ||
26 | -- Access Point (DSAP) specifying the protocol; however, if the DSAP is hex AA, | ||
27 | -- the 802.2 header is followed by a Subnetwork Access Protocol (SNAP) header, | ||
28 | -- with the Organizationally Unique Identifier (OUI) and protocol ID (PID) | ||
29 | -- fields specifying the protocol. If the OUI is all zeroes, the protocol ID | ||
30 | -- field is an EtherType value.[52] Almost all 802.11 data frames use 802.2 and | ||
31 | -- SNAP headers, and most use an OUI of 00:00:00 and an EtherType value. | ||
32 | -- | ||
33 | -- [51] http://www.wi-fiplanet.com/tutorials/article.php/1447501 | ||
34 | |||
35 | -- The first two bytes of the MAC header form a frame control field... | ||
36 | -- | ||
37 | -- The next two bytes are reserved for the Duration ID field | ||
38 | -- | ||
39 | -- An 802.11 frame can have up to four address fields. Each field can carry a | ||
40 | -- MAC address. The first 4 bits are used for the fragmentation number, and | ||
41 | -- the last 12 bits are the sequence number. | ||
42 | -- | ||
43 | -- The Sequence Control field is a two-byte section used for identifying | ||
44 | -- message order as well as eliminating duplicate frames. | ||
45 | -- | ||
46 | -- An optional two-byte Quality of Service control field that was added with | ||
47 | -- 802.11e. | ||
48 | -- | ||
49 | -- The payload or frame body field is variable in size, from 0 to 2304 bytes | ||
50 | -- plus any overhead from security encapsulation, and contains information from | ||
51 | -- higher layers | ||
52 | -- | ||
53 | -- The Frame Check Sequence (FCS) is the last four bytes in the standard 802.11 | ||
54 | -- frame. Often referred to as the Cyclic Redundancy Check (CRC), it allows for | ||
55 | -- integrity check of retrieved frames. | ||
56 | |||
57 | -- 2 bits | ||
58 | data Type = Management | ||
59 | | Control | ||
60 | | Data | ||
61 | | Reserved | ||
62 | deriving (Eq,Ord,Enum,Read,Show) | ||
63 | |||
64 | -- 4 bits | ||
65 | data SubType = AssociationRequest | ||
66 | | SubType1 | ||
67 | | SubType2 | ||
68 | | SubType3 | ||
69 | | ProbeRequest | ||
70 | | SubType5 | ||
71 | | SubType6 | ||
72 | | SubType7 | ||
73 | |||
74 | -- Any of the following used with type 'Data' means the optional | ||
75 | -- QoS field is present. | ||
76 | | Beacon | ||
77 | | SubType9 | ||
78 | | Disassociation | ||
79 | | SubTypeB | ||
80 | | SubTypeC | ||
81 | | SubTypeD | ||
82 | | SubTypeE | ||
83 | | SubTypeF | ||
84 | deriving (Eq,Ord,Enum,Read,Show) | ||
85 | |||
86 | -- Byte 0 (Frame Control Field 1) | ||
87 | wifiTypeBits :: Word8 -> Maybe (Type,SubType) | ||
88 | wifiTypeBits b | ||
89 | | version/=0 = Nothing | ||
90 | | otherwise = Just (typ,subtyp) | ||
91 | where | ||
92 | version = b .&. 0x03 | ||
93 | typ = toEnum $ fromIntegral $ 0x03 .&. shiftR b 2 | ||
94 | subtyp = toEnum $ fromIntegral $ 0x0F .&. shiftR b 4 | ||
95 | |||
96 | |||
97 | |||
98 | -- Byte 1 (Frame Control Field 2) | ||
99 | newtype Flags = Flags Word8 | ||
100 | deriving (Eq,Ord,Bits,Read,Show) | ||
101 | |||
102 | pattern ToDS = Flags 0x01 | ||
103 | pattern FromDS = Flags 0x02 | ||
104 | pattern MoreFragments = Flags 0x04 | ||
105 | pattern Retry = Flags 0x08 | ||
106 | pattern PowerManagement = Flags 0x10 | ||
107 | pattern MoreData = Flags 0x20 | ||
108 | pattern Protected = Flags 0x40 | ||
109 | pattern Order = Flags 0x80 | ||
110 | |||
111 | flagNames :: [(Flags, String)] | ||
112 | flagNames = | ||
113 | [( ToDS, "ToDS" ) | ||
114 | ,( FromDS, "FromDS" ) | ||
115 | ,( MoreFragments, "MoreFragments" ) | ||
116 | ,( Retry, "Retry" ) | ||
117 | ,( PowerManagement, "PowerManagement" ) | ||
118 | ,( MoreData, "MoreData" ) | ||
119 | ,( Protected, "Protected" ) | ||
120 | ,( Order, "Order" )] | ||
121 | |||
122 | -- Bytes 2,3 | ||
123 | -- Duration/ID field | ||
124 | -- | ||
125 | -- This field is of size 16 bits. It carries following fields. | ||
126 | -- | ||
127 | -- • In control type frames of subtype Power Save (PS)-Poll, the Duration/ID | ||
128 | -- field carries the association identity (AID) of the station that transmitted | ||
129 | -- the frame in the 14 least significant bits (LSB), with the 2 most | ||
130 | -- significant bits (MSB) both set to 1. AID value varies between values from 1 | ||
131 | -- to 2007. | ||
132 | -- | ||
133 | -- • In all the other frames, this field contains duration value as specified | ||
134 | -- for each of the frame. For all the frames transmitted during the CFP | ||
135 | -- (contention free time period) this field is set to 32,768. If the content of | ||
136 | -- this field is less than 32768, it is used to update NAV (Network Allocation | ||
137 | -- Vector). | ||
138 | newtype DurationID = DurationID Word16 | ||
139 | deriving (Eq,Ord,Read,Show) | ||
140 | |||
141 | instance Parse DurationID where parse = DurationID <$> parse -- (big endian) | ||
142 | |||
143 | |||
144 | |||
145 | -- Bytes 4-(21/27) (+18 or +24 bytes long) | ||
146 | -- | ||
147 | -- Wikipedia claimed: Address 1 is the receiver, Address 2 is the transmitter, | ||
148 | -- Address 3 is used for filtering purposes by the receiver.[dubious – discuss] | ||
149 | -- | ||
150 | -- But http://www.rfwireless-world.com/Articles/WLAN-MAC-layer-protocol.html | ||
151 | -- says: | ||
152 | -- | ||
153 | -- ToDS FromDS addr1 addr2 addr3 addr4 | ||
154 | -- ---- ------ ----- ----- ----- ----- | ||
155 | -- 0 0 DA SA BSSID - | ||
156 | -- 0 1 DA BSSID SA - | ||
157 | -- 1 0 BSSID SA DA - | ||
158 | -- 1 1 RA TA DA SA | ||
159 | -- | ||
160 | -- DataFrames: | ||
161 | -- | ||
162 | -- http://www.rfwireless-world.com/images/WLAN-MAC-Data-Frame.jpg | ||
163 | -- | ||
164 | -- Note 1: The address-1 always holds the RA(Receiver Address) of the intended | ||
165 | -- receiver or receivers(e.g. in multicast operation). Note 2 : The address-2 | ||
166 | -- always holds the address of STA which is transmitting the MAC frame. | ||
167 | |||
168 | data Addresses = Addresses | ||
169 | { bssid :: Maybe Mac.Addr -- BSSID | ||
170 | , srcAddr :: Mac.Addr -- SA | ||
171 | , dstAddr :: Mac.Addr -- DA | ||
172 | , txAddr :: Maybe Mac.Addr -- TA | ||
173 | , rxAddr :: Maybe Mac.Addr -- RA | ||
174 | } | ||
175 | deriving (Eq,Ord,Show) | ||
176 | |||
177 | parseAddresses :: Flags -> PacketParser Addresses | ||
178 | parseAddresses fc = case fc .&. (ToDS .|. FromDS) of | ||
179 | Flags 0 -> do | ||
180 | -- 0 0 DA SA BSSID - | ||
181 | _da <- parse | ||
182 | _sa <- parse | ||
183 | _bssid <- parse | ||
184 | return Addresses | ||
185 | { bssid = Just _bssid | ||
186 | , srcAddr = _sa | ||
187 | , dstAddr = _da | ||
188 | , txAddr = Nothing | ||
189 | , rxAddr = Nothing | ||
190 | } | ||
191 | FromDS -> do | ||
192 | -- 0 1 DA BSSID SA - | ||
193 | _da <- parse | ||
194 | _bssid <- parse | ||
195 | _sa <- parse | ||
196 | return Addresses | ||
197 | { bssid = Just _bssid | ||
198 | , srcAddr = _sa | ||
199 | , dstAddr = _da | ||
200 | , txAddr = Nothing | ||
201 | , rxAddr = Nothing | ||
202 | } | ||
203 | ToDS -> do | ||
204 | -- 1 0 BSSID SA DA - | ||
205 | _bssid <- parse | ||
206 | _sa <- parse | ||
207 | _da <- parse | ||
208 | return Addresses | ||
209 | { bssid = Just _bssid | ||
210 | , srcAddr = _sa | ||
211 | , dstAddr = _da | ||
212 | , txAddr = Nothing | ||
213 | , rxAddr = Nothing | ||
214 | } | ||
215 | Flags 0x03 -> do | ||
216 | -- 1 1 RA TA DA SA | ||
217 | _ra <- parse | ||
218 | _ta <- parse | ||
219 | _da <- parse | ||
220 | -- http://www.rfwireless-world.com/Articles/WLAN-MAC-layer-protocol-p2.html | ||
221 | -- indicates that there is a SequenceControl field here. | ||
222 | _sa <- parse | ||
223 | return Addresses | ||
224 | { bssid = Nothing | ||
225 | , srcAddr = _sa | ||
226 | , dstAddr = _da | ||
227 | , txAddr = Just _ta | ||
228 | , rxAddr = Just _ra | ||
229 | } | ||
230 | |||
231 | -- Bytes 22/28-23/29 (2 bytes) | ||
232 | -- | ||
233 | -- The Sequence Control field. | ||
234 | -- | ||
235 | -- A two-byte section used for identifying message order as well as eliminating | ||
236 | -- duplicate frames. The first 4 bits are used for the fragmentation number, | ||
237 | -- and the last 12 bits are the sequence number. | ||
238 | newtype SequenceControl = SequenceControl Word16 | ||
239 | deriving (Eq,Ord,Read,Show) | ||
240 | |||
241 | instance Parse SequenceControl where parse = SequenceControl <$> parse -- (big endian) | ||
242 | |||
243 | -- Optional: Bytes 24/30 - 25/31 | ||
244 | -- | ||
245 | -- An optional two-byte Quality of Service control field that was added with 802.11e. | ||
246 | newtype QoS = QoS Word16 | ||
247 | deriving (Eq,Ord,Read,Show) | ||
248 | |||
249 | -- Bytes 24/30 (802.11e maybe 26/32) - ? variable length | ||
250 | -- | ||
251 | -- Frame Body | ||
252 | |||
253 | -- Last 4 bytes. | ||
254 | -- | ||
255 | -- FCS | ||
256 | -- | ||
257 | -- Wikipedia cites https://wayback.archive.org/web/20090124151617/http://wifi.cs.st-andrews.ac.uk/wififrame.html | ||
258 | -- | ||
259 | -- From http://www.rfwireless-world.com/Articles/WLAN-MAC-layer-protocol.html | ||
260 | -- | ||
261 | -- The WLAN FCS field is a 32-bit field containing a 32-bit CRC. The FCS is | ||
262 | -- calculated over all the fields of the MAC header and the Frame Body field. | ||
263 | -- These are referred to as the calculation fields. | ||
264 | -- | ||
265 | -- The FCS is calculated using the following standard generator polynomial of | ||
266 | -- degree 32: G(x) = x³² + x²⁶ + x²³ + x²² + x¹⁶ + x¹² + x¹¹ + x¹⁰ + x⁸ + x⁷ + | ||
267 | -- x⁵ + x⁴ + x² + x + 1 | ||
268 | -- | ||
269 | -- The FCS is the 1's complement of the sum (modulo 2) of the following: | ||
270 | -- | ||
271 | -- • The remainder of xk * (x³¹+x³⁰+x²⁹+...+x² + x + 1) divided (modulo 2) by | ||
272 | -- G(x), where k is the number of bits in the calculation fields. | ||
273 | -- | ||
274 | -- • The remainder after multiplication of the contents (treated as a | ||
275 | -- polynomial) of the calculation fields by x³² and then division by G(x) | ||
276 | newtype FCS = FCS Word32 | ||
277 | deriving (Eq,Ord,Read,Show) | ||
278 | |||
279 | data Packet a = Packet | ||
280 | { wifiType :: Type | ||
281 | , subtype :: SubType | ||
282 | , flags :: Flags | ||
283 | , duration :: DurationID | ||
284 | , addresses :: Addresses | ||
285 | , sequenceControl :: SequenceControl | ||
286 | , qos :: Maybe QoS | ||
287 | , llcsnap :: Maybe LLCSNAP | ||
288 | , content :: a | ||
289 | , fcs :: Maybe FCS | ||
290 | } | ||
291 | deriving (Eq,Ord,Show) | ||
292 | |||
293 | -- Hardcoded flag to disable parsing FCS checksum. | ||
294 | hasFCS :: Bool | ||
295 | hasFCS = False -- DLT_IEEE802_11 has no trailing 4-byte FCS checksum | ||
296 | -- | ||
297 | -- Other link types might include this: | ||
298 | -- See: DLT_IEEE802_11_RADIO | ||
299 | -- or DLT_IEEE802_11_RADIO_AVS | ||
300 | |||
301 | instance Parse (Packet InPacket) where | ||
302 | parse = do | ||
303 | Just (typ,subtyp) <- wifiTypeBits <$> word8 | ||
304 | flgs <- Flags <$> word8 | ||
305 | durid <- DurationID <$> word16 | ||
306 | addrs <- parseAddresses flgs | ||
307 | sc <- SequenceControl <$> word16 -- TODO: Sometimes this precedes the 4th address. | ||
308 | qos <- if typ == Data && subtyp > SubType7 | ||
309 | then Just . QoS <$> parse | ||
310 | else return Nothing | ||
311 | llcsnap <- case typ of | ||
312 | Data -> Just <$> parse | ||
313 | _ -> return Nothing | ||
314 | rst <- therest | ||
315 | let (payload,fcs) = if hasFCS | ||
316 | then | ||
317 | let payload = takeInPack (len rst - 4) rst | ||
318 | fcshi = rst `wordAt` (len rst - 4) | ||
319 | fcslo = rst `wordAt` (len rst - 2) | ||
320 | fcs = FCS $ fromIntegral fcshi * 65536 | ||
321 | + fromIntegral fcslo | ||
322 | in (payload,Just fcs) | ||
323 | else (rst,Nothing) | ||
324 | return Packet | ||
325 | { wifiType = typ | ||
326 | , subtype = subtyp | ||
327 | , flags = flgs | ||
328 | , duration = durid | ||
329 | , addresses = addrs | ||
330 | , sequenceControl = sc | ||
331 | , qos = qos | ||
332 | , llcsnap = llcsnap | ||
333 | , content = payload | ||
334 | , fcs = fcs | ||
335 | } | ||
diff --git a/readpackets.hs b/readpackets.hs index f02df538..690aa91e 100644 --- a/readpackets.hs +++ b/readpackets.hs | |||
@@ -1,6 +1,9 @@ | |||
1 | {-# LANGUAGE PackageImports #-} | 1 | {-# LANGUAGE PackageImports #-} |
2 | {-# LANGUAGE StandaloneDeriving #-} | ||
2 | module Main where | 3 | module Main where |
3 | 4 | ||
5 | import Control.Arrow | ||
6 | import Data.Maybe | ||
4 | import Data.Binary.Get (runGet) | 7 | import Data.Binary.Get (runGet) |
5 | import qualified Data.ByteString as BS | 8 | import qualified Data.ByteString as BS |
6 | import qualified Data.ByteString as B | 9 | import qualified Data.ByteString as B |
@@ -8,30 +11,63 @@ import qualified Data.ByteString.Lazy as LZ | |||
8 | import qualified Data.ByteString.Lazy.Char8 as L8 | 11 | import qualified Data.ByteString.Lazy.Char8 as L8 |
9 | import Data.IORef | 12 | import Data.IORef |
10 | import Data.List | 13 | import Data.List |
14 | import Data.Word | ||
11 | import Debug.Trace | 15 | import Debug.Trace |
12 | import Text.Printf | 16 | import Text.Printf |
13 | import Text.Show.Pretty as PP | 17 | import Text.Show.Pretty as PP |
14 | import "network-house" Net.Packet | 18 | import "network-house" Net.Packet as House |
15 | import qualified "network-house" Net.IPv4 as IP4 | 19 | import qualified "network-house" Net.IPv4 as IP4 |
16 | import qualified "network-house" Net.IPv6 as IP6 | 20 | import qualified "network-house" Net.IPv6 as IP6 |
17 | import "network-house" Net.PacketParsing | 21 | import "network-house" Net.PacketParsing as House |
22 | import "network-house" Net.PortNumber as TCP | ||
23 | import "network-house" Net.ICMP as ICMP | ||
24 | import "network-house" Net.TCP as TCP | ||
18 | import "network-house" Net.UDP as UDP | 25 | import "network-house" Net.UDP as UDP |
19 | import "pcap" Network.Pcap | 26 | import "pcap" Network.Pcap |
20 | import qualified Data.Serialize as S | 27 | import qualified Data.Serialize as S |
21 | import qualified Network.Socket as HS | 28 | import qualified Network.Socket as HS |
22 | import Control.Applicative | 29 | import Control.Applicative |
30 | import Control.Monad | ||
31 | import System.Environment | ||
32 | import Control.Exception | ||
33 | |||
34 | import Hans.Checksum | ||
35 | import WifiHeader as Wifi | ||
23 | 36 | ||
24 | import Crypto.Tox | 37 | import Crypto.Tox |
25 | import Network.Tox.DHT.Transport as Tox | 38 | import Network.Tox.DHT.Transport as Tox |
26 | import Data.BEncode as BE | 39 | import Data.BEncode as BE |
27 | import Data.BEncode.Pretty | 40 | import Data.BEncode.Pretty |
28 | -- import Data.IKE.Message | 41 | -- import Data.IKE.Message |
42 | import Text.XXD | ||
29 | 43 | ||
30 | -- traceM string = trace string $ return () | 44 | -- traceM string = trace string $ return () |
31 | 45 | ||
46 | |||
47 | -- IPv4 : rfc 791 | ||
48 | -- IPv6 : rfc 2460 (8200) | ||
49 | -- TCP : rfc 793 | ||
50 | -- UDP : rfc 768 | ||
51 | -- ICMP : rfc 792 | ||
52 | -- ICMPv6 : rfc 4443 | ||
53 | -- ARP : rfc 826 | ||
54 | -- SNAP : rfc 1042 | ||
55 | |||
56 | deriving instance Show IP6.Word4 | ||
57 | deriving instance Show IP6.Word20 | ||
58 | deriving instance Show IP6.Addr | ||
59 | deriving instance Show a => Show (IP6.Packet a) | ||
60 | |||
32 | bs2chunk :: BS.ByteString -> UArray Int Word8 | 61 | bs2chunk :: BS.ByteString -> UArray Int Word8 |
33 | bs2chunk bs = listArray (0,subtract 33 $ BS.length bs) $ drop 32 $ BS.unpack bs | 62 | bs2chunk bs = listArray (0,subtract 33 $ BS.length bs) $ drop 32 $ BS.unpack bs |
63 | -- 23 + 9 = 32 | ||
64 | |||
65 | mkchunk :: Int -> BS.ByteString -> Maybe (UArray Int Word8) | ||
66 | mkchunk n bs | ||
67 | | (BS.length bs < n) = Nothing | ||
68 | | otherwise = Just $ listArray (0,subtract (n+1) $ BS.length bs) $ drop n $ BS.unpack bs | ||
34 | 69 | ||
70 | {- | ||
35 | hex :: BS.ByteString -> String | 71 | hex :: BS.ByteString -> String |
36 | hex = concatMap (printf "%02x") . B.unpack | 72 | hex = concatMap (printf "%02x") . B.unpack |
37 | 73 | ||
@@ -40,43 +76,219 @@ hexlines bs = ss | |||
40 | where xs = zip [0..] $ hex bs | 76 | where xs = zip [0..] $ hex bs |
41 | ls = groupBy (\(n,_) (m,_)-> n `div` 32 == m `div` 32) xs | 77 | ls = groupBy (\(n,_) (m,_)-> n `div` 32 == m `div` 32) xs |
42 | ss = map (map snd) ls | 78 | ss = map (map snd) ls |
79 | -} | ||
80 | |||
81 | udpPacket :: PktHdr -> BS.ByteString -> Maybe (IO ()) | ||
82 | udpPacket hdr buf = do | ||
83 | chunk <- mkchunk 32 buf | ||
84 | udp <- House.doParse $ House.toInPack chunk | ||
85 | -- traceM $ "got udp: " ++ show udp | ||
86 | -- traceM $ "got udp content: " ++ show (content udp) | ||
87 | let plen = House.len $ UDP.content udp | ||
88 | bs <- fmap BS.pack . parseInPacket (bytes plen) $ UDP.content udp | ||
89 | -- traceM $ "Got bs " ++ show bs | ||
90 | (checksum,blob) <- Just $ BS.splitAt 2 bs -- extra 2 bytes in pcap capture, i'm assuming its a checksum | ||
91 | -- (first4,truncated) = BS.splitAt 4 blob | ||
92 | -- -- First 4 bytes being zero is how we distinguish between | ||
93 | -- -- ESP and IKEv2 packets on port 4500. | ||
94 | -- dta = if destPort udp /= Port 500 && first4==BS.pack [0,0,0,0] | ||
95 | -- then truncated | ||
96 | -- else blob | ||
97 | Just $ do | ||
98 | putStrLn $ "UDP." ++ show udp | ||
99 | mapM_ putStrLn $ xxd2 0 blob | ||
100 | |||
101 | parseIP4 :: InPacket -> Maybe (IP4.Packet InPacket) | ||
102 | parseIP4 inpkt = do | ||
103 | ip <- House.doParse inpkt | ||
104 | 4 <- Just $ IP4.version ip | ||
105 | return ip | ||
106 | |||
107 | ipPacket4 :: PktHdr -> BS.ByteString -> Maybe (IO ()) | ||
108 | ipPacket4 hdr buf = do | ||
109 | chunk <- mkchunk 23 buf | ||
110 | ip <- parseIP4 $ House.toInPack chunk | ||
111 | Just $ do | ||
112 | putStrLn $ "-- ipPacket4 --" | ||
113 | putStrLn $ "IP4." ++ show ip | ||
114 | |||
115 | parseIP6 :: InPacket -> Maybe (IP6.Packet InPacket) | ||
116 | parseIP6 inpkt = do | ||
117 | ip <- House.doParse inpkt | ||
118 | IP6.Word4 6 <- Just $ IP6.version ip | ||
119 | return ip | ||
120 | |||
121 | ipPacket6 :: PktHdr -> BS.ByteString -> Maybe (IO ()) | ||
122 | ipPacket6 hdr buf = do | ||
123 | chunk <- mkchunk 23 buf | ||
124 | ip <- parseIP6 $ House.toInPack chunk | ||
125 | Just $ do | ||
126 | putStrLn $ "-- ipPacket6 --" | ||
127 | putStrLn $ "IP6." ++ show ip | ||
128 | |||
129 | showTruncated hdr s | ||
130 | | hdrCaptureLength hdr == hdrWireLength hdr | ||
131 | = s | ||
132 | | otherwise = unwords | ||
133 | [ s | ||
134 | , "(truncated" | ||
135 | , show $ hdrCaptureLength hdr | ||
136 | , "of" | ||
137 | , show (hdrWireLength hdr) ++ ")" | ||
138 | ] | ||
139 | |||
140 | wifiPacket hdr buf = do | ||
141 | (Data,_) <- wifiTypeBits $ BS.index buf 0 -- Filter all but data packets. | ||
142 | guard (BS.length buf >= 28) | ||
143 | chunk <- mkchunk 0 buf | ||
144 | wifi <- House.doParse $ House.toInPack chunk | ||
145 | Just $ do | ||
146 | -- putStrLn $ "-- wifiPacket --" | ||
147 | -- putStrLn $ "Wifi." ++ show wifi | ||
148 | let parseip = do | ||
149 | Wifi.Data <- Just $ wifiType wifi | ||
150 | c <- Just $ Wifi.content wifi | ||
151 | (Left <$> parseIP4 c) <|> (Right <$> parseIP6 c) | ||
152 | let mdta = parseip >>= ipPacket | ||
153 | forM_ mdta $ \dta -> do | ||
154 | let src = saddr (left fst $ right fst $ ipAddrs dta) | ||
155 | (fromMaybe 0 $ fst <$> ipPorts dta) | ||
156 | dst = saddr (left snd $ right snd $ ipAddrs dta) | ||
157 | (fromMaybe 0 $ snd <$> ipPorts dta) | ||
158 | srcmac = Wifi.srcAddr (Wifi.addresses wifi) | ||
159 | dstmac = Wifi.dstAddr (Wifi.addresses wifi) | ||
160 | r = [ maybe "" (("bssid "++) . show) $ Wifi.bssid (Wifi.addresses wifi) | ||
161 | , "<-- " ++ show src ++ " (" ++ show srcmac ++ ")" | ||
162 | , "--> " ++ show dst ++ " (" ++ show dstmac ++ ")" | ||
163 | , showTruncated hdr (ipProtocol dta) | ||
164 | ] | ||
165 | case ipPayload dta of | ||
166 | Nothing -> mapM_ putStrLn r | ||
167 | Just bs -> mapM_ putStrLn $ sideBySide (xxd2 0 bs) r | ||
168 | putStrLn "" | ||
169 | |||
170 | sideBySide (x:xs) (y:ys) = (take 72 (x ++ repeat ' ') ++ y) : sideBySide xs ys | ||
171 | sideBySide [] (y:ys) = (replicate 72 ' ' ++ y) : sideBySide [] ys | ||
172 | sideBySide xs [] = xs | ||
173 | |||
174 | saddr (Left ip4) port = HS.SockAddrInet (fromIntegral port) addr | ||
175 | where IP4.Addr a b c d = ip4 | ||
176 | addr = fromIntegral a | ||
177 | + fromIntegral b * 256 | ||
178 | + fromIntegral c * 65536 | ||
179 | + fromIntegral d * 16777216 | ||
180 | saddr (Right ip6) port = HS.SockAddrInet6 (fromIntegral port) 0 addr 0 | ||
181 | where IP6.Addr ah al bh bl ch cl dh dl = ip6 | ||
182 | addr = ( fromIntegral ah * 65536 + fromIntegral al | ||
183 | , fromIntegral bh * 65536 + fromIntegral bl | ||
184 | , fromIntegral ch * 65536 + fromIntegral cl | ||
185 | , fromIntegral dh * 65536 + fromIntegral dl | ||
186 | ) | ||
187 | |||
188 | data IPData = IPData | ||
189 | { ipPayload :: Maybe BS.ByteString | ||
190 | , ipProtocol :: String | ||
191 | , ipPorts :: Maybe (Word16,Word16) | ||
192 | , ipAddrs :: Either (IP4.Addr,IP4.Addr) | ||
193 | (IP6.Addr,IP6.Addr) | ||
194 | } | ||
195 | |||
196 | ip4addrs pkt = (IP4.source pkt, IP4.dest pkt) | ||
197 | |||
198 | ip6addrs pkt = (IP6.source pkt, IP6.dest pkt) | ||
199 | |||
200 | ipPacket ip = do | ||
201 | let udp = do | ||
202 | IP4.UDP <- Just $ either IP4.protocol IP6.next_header ip | ||
203 | pkt <- House.doParse $ either IP4.content IP6.content ip | ||
204 | let ulen = House.len $ UDP.content pkt | ||
205 | mbs = fmap BS.pack . parseInPacket (bytes ulen) $ UDP.content pkt | ||
206 | let UDP.Port sport = UDP.sourcePort pkt | ||
207 | UDP.Port dport = UDP.destPort pkt | ||
208 | return IPData | ||
209 | { ipPayload = mbs | ||
210 | , ipProtocol = "UDP" | ||
211 | , ipPorts = Just (sport,dport) | ||
212 | , ipAddrs = either (Left . ip4addrs) (Right . ip6addrs) ip | ||
213 | } | ||
214 | tcp = do | ||
215 | IP4.TCP <- Just $ either IP4.protocol IP6.next_header ip | ||
216 | pkt <- House.doParse $ either IP4.content IP6.content ip | ||
217 | let ulen = House.len $ TCP.content pkt | ||
218 | mbs = fmap BS.pack . parseInPacket (bytes ulen) $ TCP.content pkt | ||
219 | let TCP.Port sport = TCP.sourcePort pkt | ||
220 | TCP.Port dport = TCP.destPort pkt | ||
221 | return IPData | ||
222 | { ipPayload = mbs | ||
223 | , ipProtocol = "TCP" | ||
224 | , ipPorts = Just (sport,dport) | ||
225 | , ipAddrs = either (Left . ip4addrs) (Right . ip6addrs) ip | ||
226 | } | ||
227 | icmp = do | ||
228 | IP4.ICMP <- Just $ either IP4.protocol IP6.next_header ip | ||
229 | pkt <- House.doParse $ either IP4.content IP6.content ip | ||
230 | let ping = do | ||
231 | EchoRequest msg <- Just pkt | ||
232 | return ("Ping", echoData msg) | ||
233 | pong = do | ||
234 | EchoReply msg <- Just pkt | ||
235 | return ("Pong", echoData msg) | ||
236 | other = do | ||
237 | Other { type_ = typ, content = chunk } <- Just pkt | ||
238 | return (show typ, chunk) | ||
239 | (typ,chunk) <- ping <|> pong <|> other | ||
240 | let p = House.toInPack chunk | ||
241 | ulen = House.len p | ||
242 | mbs = fmap BS.pack . parseInPacket (bytes ulen) $ p | ||
243 | return IPData | ||
244 | { ipPayload = mbs | ||
245 | , ipProtocol = "ICMP "++typ | ||
246 | , ipPorts = Nothing | ||
247 | , ipAddrs = either (Left . ip4addrs) (Right . ip6addrs) ip | ||
248 | } | ||
249 | unknown = do | ||
250 | IP4.Unknown proto <- Just $ either IP4.protocol IP6.next_header ip | ||
251 | pkt <- Just $ either IP4.content IP6.content ip | ||
252 | let ulen = House.len pkt | ||
253 | mbs = fmap BS.pack . parseInPacket (bytes ulen) $ pkt | ||
254 | return IPData | ||
255 | { ipPayload = mbs | ||
256 | , ipProtocol = "proto "++show proto | ||
257 | , ipPorts = Nothing | ||
258 | , ipAddrs = either (Left . ip4addrs) (Right . ip6addrs) ip | ||
259 | } | ||
260 | udp <|> icmp <|> tcp <|> unknown | ||
261 | |||
43 | 262 | ||
44 | parsePacket :: IORef Int -> PktHdr -> BS.ByteString -> IO () | 263 | parsePacket :: IORef Int -> PktHdr -> BS.ByteString -> IO () |
45 | parsePacket cnt hdr buf = do | 264 | parsePacket cnt hdr buf = do |
46 | print hdr | 265 | sequence_ $ wifiPacket hdr buf |
47 | let -- mb :: Maybe (BS.ByteString, Message Encrypted) | ||
48 | mb = do | ||
49 | udp <- doParse $ toInPack $ bs2chunk buf | ||
50 | -- traceM $ "got udp: " ++ show udp | ||
51 | -- traceM $ "got udp content: " ++ show (content udp) | ||
52 | let plen = Net.Packet.len $ content udp | ||
53 | bs <- fmap BS.pack . parseInPacket (bytes plen) $ content udp | ||
54 | -- traceM $ "Got bs " ++ show bs | ||
55 | let (checksum,blob) = BS.splitAt 2 bs -- extra 2 bytes in pcap capture, i'm assuming its a checksum | ||
56 | -- (first4,truncated) = BS.splitAt 4 blob | ||
57 | -- -- First 4 bytes being zero is how we distinguish between | ||
58 | -- -- ESP and IKEv2 packets on port 4500. | ||
59 | -- dta = if destPort udp /= Port 500 && first4==BS.pack [0,0,0,0] | ||
60 | -- then truncated | ||
61 | -- else blob | ||
62 | dta = blob | ||
63 | let d = BE.decode {- runGet getMessage $ LZ.fromStrict -} dta | ||
64 | saddr = HS.SockAddrInet 0 0 -- TODO | ||
65 | -- e = S.decode dta :: Either String (DHTMessage Encrypted8) | ||
66 | e = case Tox.parseDHTAddr (dta,saddr) :: Either (DHTMessage Encrypted8,NodeInfo) (BS.ByteString,HS.SockAddr) of | ||
67 | Left toxpkt -> Right toxpkt | ||
68 | Right _ -> Left "tox parse fail" | ||
69 | return $ (udp, bs, fmap Left d <|> fmap Right e) | ||
70 | flip (maybe $ return ()) mb $ \(udp, bs,m) -> do | ||
71 | putStrLn $ show udp | ||
72 | mapM_ putStrLn $ hexlines bs | ||
73 | -- putStrLn $ PP.ppShow m | ||
74 | either putStrLn (L8.putStrLn . either showBEncode (L8.pack . show)) m | ||
75 | modifyIORef' cnt (+1) | 266 | modifyIORef' cnt (+1) |
76 | 267 | ||
268 | sampleHdr :: PktHdr | ||
269 | sampleHdr = PktHdr {hdrSeconds = 1511875620, hdrUseconds = 464364, hdrCaptureLength = 347, hdrWireLength = 347} | ||
270 | |||
271 | sampleBytes :: BS.ByteString | ||
272 | sampleBytes = "\136\n0\NULT\242\SOH\243\ENQ\153\NUL\rg\144\162\a\244>\157\ETXV\213\240 \ETX\NUL\170\170\ETX\NUL\NUL\NUL\b\NULE\NUL\SOH9\191\170@\NUL@\ACK\174\176\192,D\ENQ\n\235\188G\NULP\202\220R\199)\154R\199+\247\128\DC1\b\NUL\204\196\NUL\NUL\SOH\SOH\b\n\NUL\243\150\&6\NUL\243\150\&1HTTP/1.1 302 Found \r\nLocation: https://wifilogin.xfinity.com/start.php?h=kozHorIQv1nb851zZcMfnWdD4wtjyIymVSIwanl8KXaQLMHBm0oJhcubjREusjojI%2B8f0UNFoG1p2lD33OXGlpw3uvJ5SeKeX37jANRRu%2FPZOwWw%2F70foAx70wBVohtmhWksbshKhR4lAWuv2FAB0%2BPVkpfSjNIE0HwQO%2BnRZR8%3D\r\n\r\n" | ||
273 | |||
274 | sampleHdr2 = PktHdr {hdrSeconds = 1511841845, hdrUseconds = 681965, hdrCaptureLength = 10, hdrWireLength = 10} | ||
275 | sampleBytes2 = | ||
276 | [ 0xc4, 0x00, 0x8a, 0x00 | ||
277 | , 0x8c, 0xf5, 0xa3, 0x12, 0xdf, 0x80 ] | ||
278 | |||
77 | main = do | 279 | main = do |
78 | cnt <- newIORef 0 | 280 | args <- getArgs |
79 | pcap <- openOffline "packets.pcap" | 281 | forM_ args $ \fname -> do |
80 | loopResult <- loopBS pcap (-1) $ parsePacket cnt | 282 | cnt <- newIORef 0 |
81 | pktcnt <- readIORef cnt | 283 | parsePacket cnt sampleHdr sampleBytes |
82 | putStrLn $ "read "++show pktcnt ++" packets." | 284 | pcap <- openOffline fname -- "packets.pcap" |
285 | linktype <- datalink pcap | ||
286 | putStrLn $ unwords ["Opened:",fname,"Link:",show linktype] | ||
287 | -- Opened: opnwep.dat-08.cap Link: DLT_IEEE802_11 | ||
288 | -- Opened: packets.pcap Link: DLT_EN10MB | ||
289 | -- DLT_IEEE802_11 | ||
290 | -- DLT_IEEE802_11_RADIO | ||
291 | -- DLT_IEEE802_11_RADIO_AVS | ||
292 | loopResult <- loopBS pcap (-1) $ parsePacket cnt | ||
293 | pktcnt <- readIORef cnt | ||
294 | putStrLn $ "read "++show pktcnt ++" packets from " ++ fname ++ "." | ||
diff --git a/src/Hans/Checksum.hs b/src/Hans/Checksum.hs new file mode 100644 index 00000000..7afc93c7 --- /dev/null +++ b/src/Hans/Checksum.hs | |||
@@ -0,0 +1,136 @@ | |||
1 | {-# LANGUAGE RecordWildCards #-} | ||
2 | {-# LANGUAGE BangPatterns #-} | ||
3 | |||
4 | -- BANNERSTART | ||
5 | -- - Copyright 2006-2008, Galois, Inc. | ||
6 | -- - This software is distributed under a standard, three-clause BSD license. | ||
7 | -- - Please see the file LICENSE, distributed with this software, for specific | ||
8 | -- - terms and conditions. | ||
9 | -- Author: Adam Wick <awick@galois.com> | ||
10 | -- BANNEREND | ||
11 | -- |A module providing checksum computations to other parts of Hans. The | ||
12 | -- checksum here is the standard Internet 16-bit checksum (the one's | ||
13 | -- complement of the one's complement sum of the data). | ||
14 | |||
15 | module Hans.Checksum( | ||
16 | -- * Checksums | ||
17 | computeChecksum, | ||
18 | Checksum(..), | ||
19 | PartialChecksum(), | ||
20 | emptyPartialChecksum, | ||
21 | finalizeChecksum, | ||
22 | stepChecksum, | ||
23 | |||
24 | Pair8(..), | ||
25 | ) where | ||
26 | |||
27 | import Data.Bits (Bits(shiftL,shiftR,complement,clearBit,(.&.))) | ||
28 | import Data.List (foldl') | ||
29 | import Data.Word (Word8,Word16,Word32) | ||
30 | import qualified Data.ByteString as S | ||
31 | import qualified Data.ByteString.Lazy as L | ||
32 | import qualified Data.ByteString.Short as Sh | ||
33 | import qualified Data.ByteString.Unsafe as S | ||
34 | |||
35 | |||
36 | data PartialChecksum = PartialChecksum { pcAccum :: {-# UNPACK #-} !Word32 | ||
37 | , pcCarry :: !(Maybe Word8) | ||
38 | } deriving (Eq,Show) | ||
39 | |||
40 | emptyPartialChecksum :: PartialChecksum | ||
41 | emptyPartialChecksum = PartialChecksum | ||
42 | { pcAccum = 0 | ||
43 | , pcCarry = Nothing | ||
44 | } | ||
45 | |||
46 | finalizeChecksum :: PartialChecksum -> Word16 | ||
47 | finalizeChecksum pc = complement (fromIntegral (fold32 (fold32 result))) | ||
48 | where | ||
49 | fold32 :: Word32 -> Word32 | ||
50 | fold32 x = (x .&. 0xFFFF) + (x `shiftR` 16) | ||
51 | |||
52 | result = case pcCarry pc of | ||
53 | Nothing -> pcAccum pc | ||
54 | Just prev -> stepChecksum (pcAccum pc) prev 0 | ||
55 | {-# INLINE finalizeChecksum #-} | ||
56 | |||
57 | |||
58 | computeChecksum :: Checksum a => a -> Word16 | ||
59 | computeChecksum a = finalizeChecksum (extendChecksum a emptyPartialChecksum) | ||
60 | {-# INLINE computeChecksum #-} | ||
61 | |||
62 | -- | Incremental checksum computation interface. | ||
63 | class Checksum a where | ||
64 | extendChecksum :: a -> PartialChecksum -> PartialChecksum | ||
65 | |||
66 | |||
67 | data Pair8 = Pair8 !Word8 !Word8 | ||
68 | |||
69 | instance Checksum Pair8 where | ||
70 | extendChecksum (Pair8 hi lo) = \ PartialChecksum { .. } -> | ||
71 | case pcCarry of | ||
72 | Nothing -> PartialChecksum { pcAccum = stepChecksum pcAccum hi lo | ||
73 | , pcCarry = Nothing } | ||
74 | Just c -> PartialChecksum { pcAccum = stepChecksum pcAccum c hi | ||
75 | , pcCarry = Just lo } | ||
76 | {-# INLINE extendChecksum #-} | ||
77 | |||
78 | instance Checksum Word16 where | ||
79 | extendChecksum w = \pc -> extendChecksum (Pair8 hi lo) pc | ||
80 | where | ||
81 | lo = fromIntegral w | ||
82 | hi = fromIntegral (w `shiftR` 8) | ||
83 | {-# INLINE extendChecksum #-} | ||
84 | |||
85 | instance Checksum Word32 where | ||
86 | extendChecksum w = \pc -> | ||
87 | extendChecksum (fromIntegral w :: Word16) $ | ||
88 | extendChecksum (fromIntegral (w `shiftR` 16) :: Word16) pc | ||
89 | {-# INLINE extendChecksum #-} | ||
90 | |||
91 | instance Checksum a => Checksum [a] where | ||
92 | extendChecksum as = \pc -> foldl' (flip extendChecksum) pc as | ||
93 | {-# INLINE extendChecksum #-} | ||
94 | |||
95 | instance Checksum L.ByteString where | ||
96 | extendChecksum lbs = \pc -> extendChecksum (L.toChunks lbs) pc | ||
97 | {-# INLINE extendChecksum #-} | ||
98 | |||
99 | -- XXX this could be faster if we could mirror the structure of the instance for | ||
100 | -- S.ByteString | ||
101 | instance Checksum Sh.ShortByteString where | ||
102 | extendChecksum shb = \ pc -> extendChecksum (Sh.fromShort shb) pc | ||
103 | |||
104 | |||
105 | instance Checksum S.ByteString where | ||
106 | extendChecksum b pc | ||
107 | | S.null b = pc | ||
108 | | otherwise = case pcCarry pc of | ||
109 | Nothing -> result | ||
110 | Just prev -> extendChecksum (S.tail b) PartialChecksum | ||
111 | { pcCarry = Nothing | ||
112 | , pcAccum = stepChecksum (pcAccum pc) prev (S.unsafeIndex b 0) | ||
113 | } | ||
114 | where | ||
115 | |||
116 | n' = S.length b | ||
117 | n = clearBit n' 0 -- aligned to two | ||
118 | |||
119 | result = PartialChecksum | ||
120 | { pcAccum = loop (pcAccum pc) 0 | ||
121 | , pcCarry = carry | ||
122 | } | ||
123 | |||
124 | carry | ||
125 | | odd n' = Just $! S.unsafeIndex b n | ||
126 | | otherwise = Nothing | ||
127 | |||
128 | loop !acc off | ||
129 | | off < n = loop (stepChecksum acc hi lo) (off + 2) | ||
130 | | otherwise = acc | ||
131 | where hi = S.unsafeIndex b off | ||
132 | lo = S.unsafeIndex b (off+1) | ||
133 | |||
134 | stepChecksum :: Word32 -> Word8 -> Word8 -> Word32 | ||
135 | stepChecksum acc hi lo = acc + fromIntegral hi `shiftL` 8 + fromIntegral lo | ||
136 | {-# INLINE stepChecksum #-} | ||