{-# LANGUAGE TypeSynonymInstances, TypeOperators, MultiParamTypeClasses, FunctionalDependencies, RecordWildCards #-} {-# LANGUAGE BangPatterns #-} {-| This module provides @Get@ values for parsing various IP packets and headers from ByteStrings into a byte-sequence-independent representation as Haskell datatypes. Warning: These are incomplete. The headers may not contain all the information that the protocols specify. For example, the Haskell representation of an IP Header only includes source and destination addresses and IP protocol number, even though an IP packet has many more header fields. More seriously, an IP header may have an optional extra headers section after the destination address. We assume this is not present. If it is present, then the transport protocol header will not be directly after the destination address, but will be after these options. Therefore functions that assume this, such as the getExactMatch function below, will give incorrect results when applied to such IP packets. The Haskell representations of the headers for the transport protocols are similarly incomplete. Again, the Get instances for the transport protocols may not parse through the end of the transport protocol header. -} module Network.Data.IPv4.IPPacket ( -- * IP Packet IPPacket , IPHeader(..) , DifferentiatedServicesCodePoint , FragOffset , IPProtocol , IPTypeOfService , TransportPort , ipProtocol , ipBodyLength , ipTypeTcp , ipTypeUdp , ipTypeIcmp , IPBody(..) -- * Parsers , getIPPacket , getIPHeader , ICMPHeader , ICMPType , ICMPCode , getICMP , TCPHeader , TCPPortNumber , getTCPHeader , UDPHeader , UDPPortNumber , getUDPHeader , putIP , csum16 ) where import Network.Data.IPv4.IPAddress -- import Network.Data.IPv4.DHCP import Network.Data.IPv4.UDP import Data.Bits import Data.Word import Data.Binary.Get import Data.Binary.Put import qualified Data.ByteString as B import qualified Data.ByteString.Lazy as L import Control.Exception (assert) -- | An IP packet consists of a header and a body. type IPPacket = (IPHeader, IPBody) -- | An IP Header includes various information about the packet, including the type of payload it contains. -- Warning: this definition does not include every header field included in an IP packet. data IPHeader = IPHeader { ipSrcAddress :: !IPAddress , ipDstAddress :: !IPAddress , headerLength :: !Int , totalLength :: !Int , dscp :: !DifferentiatedServicesCodePoint -- ^ differentiated services code point - 6 bit number , ecn :: !Word8 -- Explicit congestion notification (ECN); 2 bits. , ttl :: !Word8 -- ^ time-to-live. , ipChecksum :: !Word16 , ident :: !Word16 , flags :: !Word16 } deriving (Read,Show,Eq) type DifferentiatedServicesCodePoint = Word8 type FragOffset = Word16 type IPProtocol = Word8 type IPTypeOfService = Word8 type TransportPort = Word16 ipProtocol :: IPBody -> IPProtocol ipProtocol (TCPInIP _ _) = ipTypeTcp ipProtocol (UDPInIP _ _) = ipTypeUdp ipProtocol (ICMPInIP _ _ _) = ipTypeIcmp ipProtocol (UninterpretedIPBody proto) = proto {-# INLINE ipProtocol #-} ipBodyLength :: IPHeader -> Int ipBodyLength (IPHeader {..}) = totalLength - (4 * headerLength) {-# INLINE ipBodyLength #-} ipTypeTcp, ipTypeUdp, ipTypeIcmp :: IPProtocol ipTypeTcp = 6 ipTypeUdp = 17 ipTypeIcmp = 1 -- | The body of an IP packet can be either a TCP, UDP, ICMP or other packet. -- Packets other than TCP, UDP, ICMP are represented as unparsed @ByteString@ values. data IPBody = TCPInIP !TCPPortNumber !TCPPortNumber | UDPInIP !UDPPortNumber !UDPPortNumber | ICMPInIP !ICMPHeader B.ByteString Word16 | UninterpretedIPBody !IPProtocol deriving (Show,Eq) getIPHeader :: Get (IPHeader, IPProtocol) getIPHeader = do b1 <- getWord8 let version = shiftR b1 4 assert (version == 4) $ do diffServ <- getWord8 totalLen <- getWord16be ident <- getWord16be -- ident flags <- getWord16be -- flagsAndFragOffset ttl <- getWord8 -- ttl nwproto <- getIPProtocol ipChecksum <- getWord16be -- hdrChecksum nwsrc <- getIPAddress nwdst <- getIPAddress let hdrLen = fromIntegral (b1 .&. 0x0f) skip (max 0 (4 * (hdrLen - 5))) return (IPHeader { ipSrcAddress = nwsrc , ipDstAddress = nwdst , headerLength = hdrLen , totalLength = fromIntegral totalLen , dscp = shiftR diffServ 2 , ecn = diffServ .&. 3 , ttl = ttl , ipChecksum = ipChecksum , ident = ident , flags = flags }, nwproto) {-# INLINE getIPHeader #-} getIPProtocol :: Get IPProtocol getIPProtocol = getWord8 {-# INLINE getIPProtocol #-} getIPPacket :: Get IPPacket getIPPacket = getIPHeader >>= getIPBody {-# INLINE getIPPacket #-} getIPBody :: (IPHeader, IPProtocol) -> Get IPPacket getIPBody (hdr@(IPHeader {..}), nwproto) | nwproto == ipTypeTcp = do (s,d) <- getTCPHeader (ipBodyLength hdr) return (hdr, TCPInIP s d) | nwproto == ipTypeUdp = do (s,d) <- getUDPHeader skip $ ipBodyLength hdr - 4 let bdy = UDPInIP s d return (hdr, bdy) | nwproto == ipTypeIcmp = do (icmpHdr, bs, check) <- getICMP (ipBodyLength hdr) return (hdr, ICMPInIP icmpHdr bs check) | otherwise = return (hdr, UninterpretedIPBody nwproto) {-# INLINE getIPBody #-} -- ipChecksum_ :: IPHeader -> Word8 -> Word16 -- ipChecksum_ hdr nwproto = csum16 $ runPut $ putIPHeader hdr nwproto 0 csum16 :: L.ByteString -> Word16 csum16 bs = complement $ x + y where x, y :: Word16 x = fromIntegral (shiftR (z .&. 0xff00) 8) y = fromIntegral (z .&. 0x00ff) z :: Word32 z = foldl (+) 0 ws ws :: [Word32] ws = runGet (sequence $ replicate (fromIntegral (L.length bs) `div` 4) getWord32be) bs putIP :: IPPacket -> Put putIP (hdr, body) = do let nwproto = ipProtocol body putIPHeader hdr nwproto $ ipChecksum hdr --(ipChecksum hdr nwproto) putIPBody (ipBodyLength hdr) body putIPHeader :: IPHeader -> Word8 -> Word16 -> Put putIPHeader (IPHeader {..}) nwproto chksum = do putWord8 b1 putWord8 diffServ putWord16be $ fromIntegral totalLength putWord16be ident -- identification putWord16be flags -- flags and offset putWord8 ttl putWord8 nwproto putWord16be chksum putIPAddress ipSrcAddress putIPAddress ipDstAddress -- assume no options. where b1 = shiftL vERSION_4 4 .|. fromIntegral headerLength diffServ = shiftL dscp 2 .|. ecn vERSION_4 :: Word8 vERSION_4 = 4 putIPBody :: Int -> IPBody -> Put putIPBody _ (ICMPInIP (icmpType, icmpCode) bs check) = do putWord8 icmpType putWord8 icmpCode putWord16be check -- $ csum16 $ L.fromStrict bs to L.pack [icmpType, icmpCode] -- putWord16be $ csum16 $ L.append (L.pack [icmpType, icmpCode]) (L.fromStrict bs) -- L.fromStrict bs to L.pack [icmpType, icmpCode] putByteString bs putIPBody _ body = error $ "putIPBody: not yet handling IP body: " ++ show body -- Transport Header type ICMPHeader = (ICMPType, ICMPCode) type ICMPType = Word8 type ICMPCode = Word8 getICMP :: Int -> Get (ICMPHeader, B.ByteString, Word16) getICMP len = do icmp_type <- getWord8 icmp_code <- getWord8 check <- getWord16be bs <- getByteString $ len - 4 return ((icmp_type, icmp_code), bs, check) {-# INLINE getICMP #-} type TCPHeader = (TCPPortNumber, TCPPortNumber) type TCPPortNumber = Word16 getTCPHeader :: Int -> Get TCPHeader getTCPHeader len = do srcp <- getWord16be dstp <- getWord16be skip $ len - 4 return (srcp,dstp) {-# INLINE getTCPHeader #-}