hans-2.1.0.0: src/Hans/Message/Icmp4.hs
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module Hans.Message.Icmp4 where
import Hans.Address.IP4 (IP4)
import Hans.Message.Types (Lifetime)
import Hans.Utils (Packet)
import Hans.Utils.Checksum (pokeChecksum, computeChecksum)
import Control.Monad (liftM2, unless, when, replicateM)
import Data.Serialize (Serialize(..))
import Data.Serialize.Get (getWord8, getByteString, remaining, skip, Get, label,
lookAhead, getBytes, isEmpty)
import Data.Serialize.Put (putWord8,putByteString, Put, runPut)
import Data.Int (Int32)
import Data.Word (Word8,Word16,Word32)
import System.IO.Unsafe (unsafePerformIO)
-- General ICMP Packets --------------------------------------------------------
data Icmp4Packet
-- RFC 792 - Internet Control Message Protocol
= EchoReply Identifier SequenceNumber Packet
| DestinationUnreachable DestinationUnreachableCode Packet
| SourceQuench Packet
| Redirect RedirectCode IP4 Packet
| Echo Identifier SequenceNumber Packet
| RouterAdvertisement Lifetime [RouterAddress]
| RouterSolicitation
| TimeExceeded TimeExceededCode Packet
| ParameterProblem Word8 Packet
| Timestamp Identifier SequenceNumber Word32 Word32 Word32
| TimestampReply Identifier SequenceNumber Word32 Word32 Word32
| Information Identifier SequenceNumber
| InformationReply Identifier SequenceNumber
-- rfc 1393 - Traceroute Using an IP Option
| TraceRoute TraceRouteCode Identifier Word16 Word16 Word32 Word32
-- rfc 950 - Internet Standard Subnetting Procedure
| AddressMask Identifier SequenceNumber
| AddressMaskReply Identifier SequenceNumber Word32
deriving Show
noCode :: String -> Get ()
noCode str = do
code <- getWord8
unless (code == 0)
(fail (str ++ " expects code 0"))
instance Serialize Icmp4Packet where
get = label "ICMP" $ do
rest <- lookAhead (getBytes =<< remaining)
unless (computeChecksum 0 rest == 0)
(fail "Bad checksum")
ty <- get
let firstGet :: Serialize a => String -> (a -> Get b) -> Get b
firstGet labelString f = label labelString $ do
code <- get
skip 2 -- checksum
f code
case (ty :: Word8) of
0 -> firstGet "Echo Reply" $ \ NoCode -> do
ident <- get
seqNum <- get
dat <- getByteString =<< remaining
return $! EchoReply ident seqNum dat
3 -> firstGet "DestinationUnreachable" $ \ code -> do
skip 4 -- unused
dat <- getByteString =<< remaining
return $! DestinationUnreachable code dat
4 -> firstGet "Source Quence" $ \ NoCode -> do
skip 4 -- unused
dat <- getByteString =<< remaining
return $! SourceQuench dat
5 -> firstGet "Redirect" $ \ code -> do
gateway <- get
dat <- getByteString =<< remaining
return $! Redirect code gateway dat
8 -> firstGet "Echo" $ \ NoCode -> do
ident <- get
seqNum <- get
dat <- getByteString =<< remaining
return $! Echo ident seqNum dat
9 -> firstGet "Router Advertisement" $ \ NoCode -> do
n <- getWord8
sz <- getWord8
unless (sz == 2)
(fail ("Expected size 2, got: " ++ show sz))
lifetime <- get
addrs <- replicateM (fromIntegral n) get
return $! RouterAdvertisement lifetime addrs
10 -> firstGet "Router Solicitation" $ \ NoCode -> do
skip 4 -- reserved
return RouterSolicitation
11 -> firstGet "Time Exceeded" $ \ code -> do
skip 4 -- unused
dat <- getByteString =<< remaining
return $! TimeExceeded code dat
12 -> firstGet "Parameter Problem" $ \ NoCode -> do
ptr <- getWord8
skip 3 -- unused
dat <- getByteString =<< remaining
return $! ParameterProblem ptr dat
13 -> firstGet "Timestamp" $ \ NoCode -> do
ident <- get
seqNum <- get
origTime <- get
recvTime <- get
tranTime <- get
return $! Timestamp ident seqNum origTime recvTime tranTime
14 -> firstGet "Timestamp Reply" $ \ NoCode -> do
ident <- get
seqNum <- get
origTime <- get
recvTime <- get
tranTime <- get
return $! TimestampReply ident seqNum origTime recvTime tranTime
15 -> firstGet "Information" $ \ NoCode -> do
ident <- get
seqNum <- get
return $! Information ident seqNum
16 -> firstGet "Information Reply" $ \ NoCode -> do
ident <- get
seqNum <- get
return $! InformationReply ident seqNum
17 -> firstGet "Address Mask" $ \ NoCode -> do
ident <- get
seqNum <- get
skip 4 -- address mask
return $! AddressMask ident seqNum
18 -> firstGet "Address Mask Reply" $ \ NoCode -> do
ident <- get
seqNum <- get
mask <- get
return $! AddressMaskReply ident seqNum mask
30 -> firstGet "Trace Route" $ \ code -> do
ident <- get
skip 2 -- unused
outHop <- get
retHop <- get
speed <- get
mtu <- get
return $! TraceRoute code ident outHop retHop speed mtu
_ -> fail ("Unknown type: " ++ show ty)
put = putByteString . unsafePerformIO . setChecksum . runPut . put'
-- Argument for safety: The bytestring being
-- destructively modified here is only accessible
-- through the composition and will never escape
where
setChecksum pkt = pokeChecksum (computeChecksum 0 pkt) pkt 2
firstPut :: Serialize a => Word8 -> a -> Put
firstPut ty code
= do put ty
put code
put (0 :: Word16)
put' (EchoReply ident seqNum dat)
= do firstPut 0 NoCode
put ident
put seqNum
putByteString dat
put' (DestinationUnreachable code dat)
= do firstPut 3 code
put (0 :: Word32) -- unused
putByteString dat
put' (SourceQuench dat)
= do firstPut 4 NoCode
put (0 :: Word32) -- unused
putByteString dat
put' (Redirect code gateway dat)
= do firstPut 5 code
put gateway
putByteString dat
put' (Echo ident seqNum dat)
= do firstPut 8 NoCode
put ident
put seqNum
putByteString dat
put' (RouterAdvertisement lifetime addrs)
= do let len = length addrs
addrSize :: Word8
addrSize = 2
when (len > 255)
(fail "Too many routers in Router Advertisement")
firstPut 9 NoCode
put (fromIntegral len :: Word8)
put addrSize
put lifetime
mapM_ put addrs
put' RouterSolicitation
= do firstPut 10 NoCode
put (0 :: Word32) -- RESERVED
put' (TimeExceeded code dat)
= do firstPut 11 code
put (0 :: Word32) -- unused
putByteString dat
put' (ParameterProblem ptr dat)
= do firstPut 12 NoCode
put ptr
put (0 :: Word8) -- unused
put (0 :: Word16) -- unused
putByteString dat
put' (Timestamp ident seqNum origTime recvTime tranTime)
= do firstPut 13 NoCode
put ident
put seqNum
put origTime
put recvTime
put tranTime
put' (TimestampReply ident seqNum origTime recvTime tranTime)
= do firstPut 14 NoCode
put ident
put seqNum
put origTime
put recvTime
put tranTime
put' (Information ident seqNum)
= do firstPut 15 NoCode
put ident
put seqNum
put' (InformationReply ident seqNum)
= do firstPut 16 NoCode
put ident
put seqNum
put' (AddressMask ident seqNum)
= do firstPut 17 NoCode
put ident
put seqNum
put (0 :: Word32) -- address mask
put' (AddressMaskReply ident seqNum mask)
= do firstPut 17 NoCode
put ident
put seqNum
put mask
put' (TraceRoute code ident outHop retHop speed mtu)
= do firstPut 30 code
put ident
put (0 :: Word16) -- unused
put outHop
put retHop
put speed
put mtu
data NoCode = NoCode
instance Serialize NoCode where
get = do b <- getWord8
unless (b == 0)
(fail ("Expected code 0, got code: " ++ show b))
return NoCode
put NoCode = putWord8 0
data DestinationUnreachableCode
= NetUnreachable
| HostUnreachable
| ProtocolUnreachable
| PortUnreachable
| FragmentationUnreachable
| SourceRouteFailed
deriving Show
instance Serialize DestinationUnreachableCode where
get = do b <- getWord8
case b of
0 -> return NetUnreachable
1 -> return HostUnreachable
2 -> return ProtocolUnreachable
3 -> return PortUnreachable
4 -> return FragmentationUnreachable
5 -> return SourceRouteFailed
_ -> fail "Invalid code for Destination Unreachable"
put NetUnreachable = putWord8 0
put HostUnreachable = putWord8 1
put ProtocolUnreachable = putWord8 2
put PortUnreachable = putWord8 3
put FragmentationUnreachable = putWord8 4
put SourceRouteFailed = putWord8 5
data TimeExceededCode
= TimeToLiveExceededInTransit
| FragmentReassemblyTimeExceeded
deriving Show
instance Serialize TimeExceededCode where
get = do b <- getWord8
case b of
0 -> return TimeToLiveExceededInTransit
1 -> return FragmentReassemblyTimeExceeded
_ -> fail "Invalid code for Time Exceeded"
put TimeToLiveExceededInTransit = putWord8 0
put FragmentReassemblyTimeExceeded = putWord8 1
data RedirectCode
= RedirectForNetwork
| RedirectForHost
| RedirectForTypeOfServiceAndNetwork
| RedirectForTypeOfServiceAndHost
deriving Show
instance Serialize RedirectCode where
get = do b <- getWord8
case b of
0 -> return RedirectForNetwork
1 -> return RedirectForHost
2 -> return RedirectForTypeOfServiceAndNetwork
3 -> return RedirectForTypeOfServiceAndHost
_ -> fail "Invalid code for Time Exceeded"
put RedirectForNetwork = putWord8 0
put RedirectForHost = putWord8 1
put RedirectForTypeOfServiceAndNetwork = putWord8 2
put RedirectForTypeOfServiceAndHost = putWord8 3
data TraceRouteCode
= TraceRouteForwarded
| TraceRouteDiscarded
deriving Show
instance Serialize TraceRouteCode where
get = do b <- getWord8
case b of
0 -> return TraceRouteForwarded
1 -> return TraceRouteDiscarded
_ -> fail "Invalid code for Trace Route"
put TraceRouteForwarded = putWord8 0
put TraceRouteDiscarded = putWord8 1
-- Router Discovery Data -------------------------------------------------------
newtype PreferenceLevel = PreferenceLevel Int32
deriving (Show,Eq,Ord,Num,Serialize)
data RouterAddress = RouterAddress
{ raAddr :: IP4
, raPreferenceLevel :: PreferenceLevel
} deriving Show
instance Serialize RouterAddress where
get = liftM2 RouterAddress get get
put ra = do
put (raAddr ra)
put (raPreferenceLevel ra)
newtype Identifier = Identifier Word16
deriving (Show, Eq, Ord, Num, Serialize)
newtype SequenceNumber = SequenceNumber Word16
deriving (Show, Eq, Ord, Num, Serialize)
getUntilDone :: Serialize a => Get [a]
getUntilDone = do
empty <- isEmpty
if empty then return []
else liftM2 (:) get getUntilDone