hans-2.1.0.0: src/Hans/Message/Ip4.hs
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module Hans.Message.Ip4 where
import Hans.Address.IP4 (IP4)
import Hans.Utils
import Hans.Utils.Checksum
import Control.Monad (unless)
import Data.Serialize (Serialize(..))
import Data.Serialize.Get (Get,getWord8,getWord16be,getByteString,isolate,label)
import Data.Serialize.Put (runPut,runPutM,putWord8,putWord16be,putByteString)
import Data.Bits (Bits((.&.),(.|.),testBit,setBit,shiftR,shiftL,bit))
import Data.Word (Word8,Word16)
import qualified Data.ByteString as S
-- IP4 Pseudo Header -----------------------------------------------------------
-- 0 7 8 15 16 23 24 31
-- +--------+--------+--------+--------+
-- | source address |
-- +--------+--------+--------+--------+
-- | destination address |
-- +--------+--------+--------+--------+
-- | zero |protocol| length |
-- +--------+--------+--------+--------+
mkIP4PseudoHeader :: IP4 -> IP4 -> IP4Protocol -> MkPseudoHeader
mkIP4PseudoHeader src dst prot len = runPut $ do
put src
put dst
putWord8 0 >> put prot >> putWord16be (fromIntegral len)
-- IP4 Packets -----------------------------------------------------------------
newtype Ident = Ident { getIdent :: Word16 }
deriving (Eq,Ord,Num,Show,Serialize,Integral,Real,Enum)
newtype IP4Protocol = IP4Protocol { getIP4Protocol :: Word8 }
deriving (Eq,Ord,Num,Show,Serialize)
data IP4Packet = IP4Packet
{ ip4Header :: !IP4Header
, ip4Payload :: S.ByteString
} deriving Show
data IP4Header = IP4Header
{ ip4Version :: !Word8
, ip4TypeOfService :: !Word8
, ip4Ident :: !Ident
, ip4MayFragment :: Bool
, ip4MoreFragments :: Bool
, ip4FragmentOffset :: !Word16
, ip4TimeToLive :: !Word8
, ip4Protocol :: !IP4Protocol
, ip4Checksum :: !Word16
, ip4SourceAddr :: !IP4
, ip4DestAddr :: !IP4
, ip4Options :: [IP4Option]
} deriving Show
emptyIP4Header :: IP4Protocol -> IP4 -> IP4 -> IP4Header
emptyIP4Header prot src dst = IP4Header
{ ip4Version = 4
, ip4TypeOfService = 0
, ip4Ident = 0
, ip4MayFragment = False
, ip4MoreFragments = False
, ip4FragmentOffset = 0
, ip4TimeToLive = 127
, ip4Protocol = prot
, ip4Checksum = 0
, ip4SourceAddr = src
, ip4DestAddr = dst
, ip4Options = []
}
noMoreFragments :: IP4Header -> IP4Header
noMoreFragments hdr = hdr { ip4MoreFragments = False }
moreFragments :: IP4Header -> IP4Header
moreFragments hdr = hdr { ip4MoreFragments = True }
addOffset :: Word16 -> IP4Header -> IP4Header
addOffset off hdr = hdr { ip4FragmentOffset = ip4FragmentOffset hdr + off }
setIdent :: Ident -> IP4Header -> IP4Header
setIdent i hdr = hdr { ip4Ident = i }
-- | Calculate the size of an IP4 packet
ip4PacketSize :: IP4Packet -> Int
ip4PacketSize (IP4Packet hdr bs) =
ip4HeaderSize hdr + fromIntegral (S.length bs)
-- | Calculate the size of an IP4 header
ip4HeaderSize :: IP4Header -> Int
ip4HeaderSize hdr = 20 + sum (map ip4OptionSize (ip4Options hdr))
-- | Fragment a single IP packet into one or more, given an MTU to fit into.
splitPacket :: Int -> IP4Packet -> [IP4Packet]
splitPacket mtu pkt
| ip4PacketSize pkt > mtu = fragmentPacket mtu' pkt
| otherwise = [pkt]
where
mtu' = fromIntegral (mtu - ip4HeaderSize (ip4Header pkt))
-- | Given a fragment size and a packet, fragment the packet into multiple
-- smaller ones.
fragmentPacket :: Int -> IP4Packet -> [IP4Packet]
fragmentPacket mtu pkt@(IP4Packet hdr bs)
| payloadLen <= mtu = [pkt { ip4Header = noMoreFragments hdr }]
| otherwise = frag : fragmentPacket mtu pkt'
where
payloadLen = S.length bs
(as,rest) = S.splitAt mtu bs
alen = fromIntegral (S.length as)
pkt' = pkt { ip4Header = hdr', ip4Payload = rest }
hdr' = addOffset alen hdr
frag = pkt { ip4Header = moreFragments hdr, ip4Payload = as }
-- 0 1 2 3
-- 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
-- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-- |Version| IHL |Type of Service| Total Length |
-- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-- | Identification |Flags| Fragment Offset |
-- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-- | Time to Live | Protocol | Header Checksum |
-- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-- | Source Address |
-- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-- | Destination Address |
-- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
parseIP4Packet :: Get (IP4Header, Int, Int)
parseIP4Packet = do
b0 <- getWord8
let ver = b0 `shiftR` 4
let ihl = fromIntegral ((b0 .&. 0xf) * 4)
label "IP4 Header" $ isolate (ihl - 1) $ do
tos <- getWord8
len <- getWord16be
ident <- get
b1 <- getWord16be
let flags = b1 `shiftR` 13
let off = b1 .&. 0x1fff
ttl <- getWord8
prot <- get
cs <- getWord16be
source <- get
dest <- get
let optlen = ihl - 20
opts <- label "IP4 Options"
$ isolate optlen
$ getOptions
$ fromIntegral optlen
let hdr = IP4Header
{ ip4Version = ver
, ip4TypeOfService = tos
, ip4Ident = ident
, ip4MayFragment = flags `testBit` 1
, ip4MoreFragments = flags `testBit` 0
, ip4FragmentOffset = off * 8
, ip4TimeToLive = ttl
, ip4Protocol = prot
, ip4Checksum = cs
, ip4SourceAddr = source
, ip4DestAddr = dest
, ip4Options = opts
}
return (hdr, fromIntegral ihl, fromIntegral len)
-- | The final step to render an IP header and its payload out as a bytestring.
renderIP4Packet :: IP4Packet -> IO Packet
renderIP4Packet (IP4Packet hdr pkt) = do
let (len,bs) = runPutM $ do
let (optbs,optlen) = renderOptions (ip4Options hdr)
let ihl = 20 + optlen
putWord8 (ip4Version hdr `shiftL` 4 .|. (ihl `div` 4))
putWord8 (ip4TypeOfService hdr)
putWord16be (fromIntegral (S.length pkt) + fromIntegral ihl)
put (ip4Ident hdr)
let frag | ip4MayFragment hdr = (`setBit` 1)
| otherwise = id
let morefrags | ip4MoreFragments hdr = (`setBit` 0)
| otherwise = id
let flags = frag (morefrags 0)
let off = ip4FragmentOffset hdr `div` 8
putWord16be (flags `shiftL` 13 .|. off .&. 0x1fff)
putWord8 (ip4TimeToLive hdr)
put (ip4Protocol hdr)
putWord16be 0 -- (ip4Checksum hdr)
put (ip4SourceAddr hdr)
put (ip4DestAddr hdr)
putByteString optbs
putByteString pkt
return ihl
let cs = computeChecksum 0 (S.take (fromIntegral len) bs)
pokeChecksum cs bs 10
-- IP4 Options -----------------------------------------------------------------
renderOptions :: [IP4Option] -> (S.ByteString,Word8)
renderOptions opts = case optlen `mod` 4 of
0 -> (optbs,fromIntegral optlen)
-- pad with no-ops
n -> (optbs `S.append` S.replicate n 0x1, fromIntegral (optlen + n))
where
optbs = runPut (mapM_ put opts)
optlen = S.length optbs
getOptions :: Int -> Get [IP4Option]
getOptions len
| len <= 0 = return []
| otherwise = do
o <- get
rest <- getOptions (len - ip4OptionSize o)
return $! (o : rest)
data IP4Option = IP4Option
{ ip4OptionCopied :: !Bool
, ip4OptionClass :: !Word8
, ip4OptionNum :: !Word8
, ip4OptionData :: S.ByteString
} deriving Show
ip4OptionSize :: IP4Option -> Int
ip4OptionSize opt = case ip4OptionNum opt of
0 -> 1
1 -> 1
_ -> 2 + fromIntegral (S.length (ip4OptionData opt))
instance Serialize IP4Option where
get = do
b <- getWord8
let optCopied = testBit b 7
let optClass = (b `shiftR` 5) .&. 0x3
let optNum = b .&. 0x1f
bs <- case optNum of
0 -> return S.empty
1 -> return S.empty
_ -> do
len <- getWord8
unless (len >= 2) (fail "Option length parameter is to small")
getByteString (fromIntegral (len - 2))
return $! IP4Option
{ ip4OptionCopied = optCopied
, ip4OptionClass = optClass
, ip4OptionNum = optNum
, ip4OptionData = bs
}
put opt = do
let copied | ip4OptionCopied opt = bit 7
| otherwise = 0
putWord8 (copied .|. ((ip4OptionClass opt .&. 0x3) `shiftL` 5)
.|. ip4OptionNum opt .&. 0x1f)
case ip4OptionNum opt of
0 -> return ()
1 -> return ()
_ -> do
putWord8 (fromIntegral (S.length (ip4OptionData opt)))
putByteString (ip4OptionData opt)