hans (empty) → 2.1.0.0
raw patch · 60 files changed
+9393/−0 lines, 60 filesdep +BoundedChandep +HALVMCoredep +RendezvousLibsetup-changed
Dependencies added: BoundedChan, HALVMCore, RendezvousLib, XenDevice, base, bytestring, cereal, communication, containers, fingertree, hans, monadLib, old-locale, random, time, unix
Files
- LICENSE +31/−0
- Setup.hs +7/−0
- cbits/tapdevice.c +36/−0
- example/WebServer.hs +153/−0
- example/test.hs +107/−0
- hans.cabal +138/−0
- src/Data/PrefixTree.hs +244/−0
- src/Hans/Address.hs +24/−0
- src/Hans/Address/IP4.hs +104/−0
- src/Hans/Address/Mac.hs +77/−0
- src/Hans/Channel.hs +34/−0
- src/Hans/Device/Ivc.hs +30/−0
- src/Hans/Device/Tap.hs +62/−0
- src/Hans/Device/Xen.hs +43/−0
- src/Hans/DhcpClient.hs +217/−0
- src/Hans/Layer.hs +140/−0
- src/Hans/Layer/Arp.hs +243/−0
- src/Hans/Layer/Arp/Table.hs +59/−0
- src/Hans/Layer/Ethernet.hs +182/−0
- src/Hans/Layer/IP4.hs +220/−0
- src/Hans/Layer/IP4/Fragmentation.hs +111/−0
- src/Hans/Layer/IP4/Routing.hs +87/−0
- src/Hans/Layer/Icmp4.hs +88/−0
- src/Hans/Layer/Tcp.hs +49/−0
- src/Hans/Layer/Tcp/Handlers.hs +74/−0
- src/Hans/Layer/Tcp/Monad.hs +92/−0
- src/Hans/Layer/Tcp/Socket.hs +189/−0
- src/Hans/Layer/Timer.hs +131/−0
- src/Hans/Layer/Udp.hs +127/−0
- src/Hans/Message/Arp.hs +74/−0
- src/Hans/Message/Dhcp4.hs +568/−0
- src/Hans/Message/Dhcp4Codec.hs +90/−0
- src/Hans/Message/Dhcp4Options.hs +869/−0
- src/Hans/Message/EthernetFrame.hs +44/−0
- src/Hans/Message/Icmp4.hs +398/−0
- src/Hans/Message/Ip4.hs +280/−0
- src/Hans/Message/Tcp.hs +446/−0
- src/Hans/Message/Types.hs +8/−0
- src/Hans/Message/Udp.hs +59/−0
- src/Hans/Ports.hs +56/−0
- src/Hans/Setup.hs +96/−0
- src/Hans/Simple.hs +59/−0
- src/Hans/Utils.hs +32/−0
- src/Hans/Utils/Checksum.hs +70/−0
- src/Network/TCP/Aux/Misc.hs +372/−0
- src/Network/TCP/Aux/Output.hs +213/−0
- src/Network/TCP/Aux/Param.hs +120/−0
- src/Network/TCP/Aux/SockMonad.hs +127/−0
- src/Network/TCP/LTS/In.hs +200/−0
- src/Network/TCP/LTS/InActive.hs +170/−0
- src/Network/TCP/LTS/InData.hs +385/−0
- src/Network/TCP/LTS/InMisc.hs +36/−0
- src/Network/TCP/LTS/InPassive.hs +192/−0
- src/Network/TCP/LTS/Out.hs +225/−0
- src/Network/TCP/LTS/User.hs +293/−0
- src/Network/TCP/Type/Base.hs +271/−0
- src/Network/TCP/Type/Datagram.hs +217/−0
- src/Network/TCP/Type/Socket.hs +199/−0
- src/Network/TCP/Type/Syscall.hs +57/−0
- src/Network/TCP/Type/Timer.hs +68/−0
+ LICENSE view
@@ -0,0 +1,31 @@+Copyright (c) 2006-2011 Galois Inc.++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions +are met:++ * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution.++ * Neither the name of Galois, Inc. nor the names of its contributors + may be used to endorse or promote products derived from this + software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER+OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,7 @@+module Main (main) where++import Distribution.Simple++main :: IO ()+main = defaultMain+
+ cbits/tapdevice.c view
@@ -0,0 +1,36 @@++#include <string.h>+#include <unistd.h>+#include <fcntl.h>++#include <sys/types.h>+#include <sys/socket.h>+#include <sys/ioctl.h>+#include <linux/if.h>+#include <linux/if_tun.h>++int init_tap_device(char *name) {+ int fd, ret;+ struct ifreq ifr;++ if(name == NULL) {+ return -1;+ }++ fd = open("/dev/net/tun", O_RDWR);+ if(fd < 0) {+ return -2;+ }++ memset(&ifr, 0x0, sizeof(struct ifreq));+ ifr.ifr_flags = IFF_TAP | IFF_NO_PI;+ strncpy(ifr.ifr_name, name, IFNAMSIZ);++ ret = ioctl(fd, TUNSETIFF, (void*) &ifr);+ if(ret != 0) {+ close(fd);+ return -3;+ }++ return fd;+}
+ example/WebServer.hs view
@@ -0,0 +1,153 @@+module WebServer where++import Control.Concurrent (forkIO,threadDelay)+import Data.Time.Calendar (Day(..))+import Data.Time.Clock (UTCTime(..),addUTCTime)+import Data.Time.Clock.POSIX (POSIXTime,getPOSIXTime,posixSecondsToUTCTime)+import Data.Time.Format (formatTime)+import Hans.Layer.Tcp.Socket+ (Socket,readLine,sendSocket,acceptSocket,listenPort,closeSocket+ ,SocketError(..))+import Hans.Message.Tcp (TcpPort)+import Hans.Setup (NetworkStack(nsTcp))+import System.Exit (exitFailure)+import System.Locale (defaultTimeLocale)+import qualified Control.Exception as X+import qualified Data.ByteString as S++webserver :: NetworkStack -> TcpPort -> IO ()+webserver ns port = body `X.catch` \se -> print (se :: X.SomeException)+ where+ body = do+ start <- getPOSIXTime+ sock <- initServer ns port+ serverLoop start sock++accept :: Socket -> IO Socket+accept sock = loop+ where+ loop = X.catch (acceptSocket sock) $ \se -> do+ case se of+ AcceptError err -> putStrLn ("Accept error: " ++ err)+ _ -> putStrLn ("Socket error: " ++ show se)+ loop++serverLoop :: POSIXTime -> Socket -> IO ()+serverLoop start sock = loop+ where+ loop = do+ client <- accept sock+ _ <- forkIO (handleClient start client)+ loop++initServer :: NetworkStack -> TcpPort -> IO Socket+initServer ns port = listenPort (nsTcp ns) port `X.catch` h+ where+ h ListenError{} = do+ putStrLn ("Unable to listen on port: " ++ show port)+ exitFailure+ h se = do+ print se+ exitFailure++handleClient :: POSIXTime -> Socket -> IO ()+handleClient start client = body `X.catch` \se -> print (se :: X.SomeException)+ where+ body = do+ mb <- processRequest client+ case mb of+ Nothing -> closeSocket client+ Just (url,req) -> do+ sendSocket client =<< makeResponse start url req+ threadDelay 1000000+ closeSocket client++processRequest :: Socket -> IO (Maybe (String,[S.ByteString]))+processRequest sock = do+ ls <- readRequest sock+ case ls of+ [] -> return Nothing+ l:_ -> return (Just (parseUrl l, ls))++crlf :: S.ByteString+crlf = S.pack [0x0d, 0x0a]++readRequest :: Socket -> IO [S.ByteString]+readRequest sock = loop+ where+ loop = do+ line <- readLine sock+ if line == crlf+ then return []+ else do+ rest <- loop+ return (line:rest)++parseUrl :: S.ByteString -> String+parseUrl = head . drop 1 . words . toString++fromString :: String -> S.ByteString+fromString = S.pack . map (toEnum . fromEnum)++toString :: S.ByteString -> String+toString = map (toEnum . fromEnum) . S.unpack++status200 :: S.ByteString+status200 = fromString "HTTP/1.1 200 OK\r\n"++contentLength :: Int -> S.ByteString+contentLength len = fromString ("Content-Length: " ++ show len)++contentType :: String -> S.ByteString+contentType ty = fromString ("Content-Type: " ++ ty)++response404 :: S.ByteString+response404 = fromString $ concat+ [ "HTTP/1.1 404 Not Found\r\n"+ , "Content-Length: 0\r\n"+ , "\r\n"+ ]++connectionClose :: S.ByteString+connectionClose = fromString "Connection: close"++makeResponse :: POSIXTime -> String -> [S.ByteString] -> IO S.ByteString+makeResponse start url req+ | url == "/favicon.ico" = return response404+ | otherwise = do+ uptime <- timePassed start+ let date = posixSecondsToUTCTime start+ body = fromString (concat+ [ "<html><head><title>HaLVM</title></head><body>"+ , "<h1>Welcome to the HaLVM!</h1><br />\r\n\r\n"+ , "Started on: "+ , formatDate date+ , ", and up for "+ , uptime+ , "\r\n<h2>HTTP Request:</h2>\r\n<pre>"+ ]) `S.append` S.concat req+ `S.append` fromString "</pre></body></html>"+ return $! S.concat+ [ status200+ , contentLength (S.length body), crlf+ , contentType "text/html", crlf+ , connectionClose, crlf+ , crlf+ , body+ ]++formatDate :: UTCTime -> String+formatDate = formatTime defaultTimeLocale "%c"++zeroUTCTime :: UTCTime+zeroUTCTime = UTCTime (ModifiedJulianDay 0) 0++timePassed :: POSIXTime -> IO String+timePassed start = do+ now <- getPOSIXTime+ let date@(UTCTime day _) = addUTCTime (now - start) zeroUTCTime+ return $ concat+ [ show (toModifiedJulianDay day)+ , " days, "+ , formatTime defaultTimeLocale "%k hours, %M minutes, %S seconds." date+ ]
+ example/test.hs view
@@ -0,0 +1,107 @@+{-# LANGUAGE CPP #-}++module Main where++import WebServer++import Hans.Address+import Hans.Address.IP4+import Hans.Address.Mac+import Hans.DhcpClient (dhcpDiscover)+import Hans.Layer.Ethernet+import Hans.Message.Tcp (TcpPort(..))+import Hans.Setup++import System.Exit (exitFailure)+import qualified Data.ByteString as S++#ifdef xen_HOST_OS+import Communication.IVC (InChannelEx,OutChannelEx,Bin)+import Hans.Device.Ivc+import Hans.Device.Xen+import Hypervisor.Debug+import Hypervisor.Kernel+import RendezvousLib.PeerToPeer (p2pConnection)+import XenDevice.NIC+#else+import Hans.Device.Tap+import System.Environment (getArgs)+#endif++output :: String -> IO ()+outputBS :: S.ByteString -> IO ()++#ifdef xen_HOST_OS+output str = writeDebugConsole (showString str "\n")+outputBS = output . map (toEnum . fromEnum) . S.unpack++_buildInput :: IO (OutChannelEx Bin Bytes)+buildInput :: IO (InChannelEx Bin Bytes)+(_buildInput,buildInput) = p2pConnection "ethernet_dev_input"++_buildOutput :: IO (InChannelEx Bin Bytes)+buildOutput :: IO (OutChannelEx Bin Bytes)+(_buildOutput,buildOutput) = p2pConnection "ethernet_dev_output"++#else+output = putStrLn+outputBS = S.putStrLn+#endif++++initEthernetDevice :: NetworkStack -> IO Mac+#ifdef xen_HOST_OS+initEthernetDevice ns = do+ Just nic <- openXenDevice ""+ let mac = read (getNICName nic)+ print mac+ addEthernetDevice (nsEthernet ns) mac (xenSend nic) (xenReceiveLoop nic)+ --let mac = Mac 0x52 0x54 0x00 0x12 0x34 0x56+ --putStrLn "Waiting for input channel..."+ --input <- buildInput+ --putStrLn "Waiting for output channel..."+ --output <- buildOutput+ --addEthernetDevice (nsEthernet ns) mac (ivcSend output) (ivcReceiveLoop input)+ return mac+#else+initEthernetDevice ns = do+ let mac = Mac 0x52 0x54 0x00 0x12 0x34 0x56+ Just dev <- openTapDevice "tap0"+ addEthernetDevice (nsEthernet ns) mac (tapSend dev) (tapReceiveLoop dev)+ return mac+#endif++main :: IO ()+#ifdef xen_HOST_OS+main = halvm_kernel [dNICs] $ \ args -> do+#else+main = do+ args <- getArgs+#endif+ ns <- setup+ mac <- initEthernetDevice ns+ startEthernetDevice (nsEthernet ns) mac+ setAddress args mac ns+ webserver ns (TcpPort 8000)++setAddress :: [String] -> Mac -> NetworkStack -> IO ()+setAddress args mac ns =+ case args of+ ["dhcp"] -> dhcpDiscover ns mac print+ [ip,gw] -> apply (addrOptions ip gw mac) ns+ _ -> do+ putStrLn "Usage: <prog> dhcp"+ putStrLn " <prog> <ip> <gateway>"+ exitFailure++addrOptions :: String -> String -> Mac -> [SomeOption]+addrOptions ip gw mac =+ [ SomeOption (LocalEthernet (ip4 `withMask` 24) mac)+ , SomeOption (Route (IP4 0 0 0 0 `withMask` 0) gw4)+ ]+ where+ ip4 :: IP4+ ip4 = read ip+ gw4 :: IP4+ gw4 = read gw
+ hans.cabal view
@@ -0,0 +1,138 @@+name: hans+version: 2.1.0.0+cabal-version: >= 1.8+license: BSD3+license-file: LICENSE+author: Galois Inc., Peng Li, Stephan Zdancewic+maintainer: halvm-devel@community.galois.com+category: Networking+synopsis: IPv4 Network Stack+build-type: Simple+description:+ HaNS is a lightweight, pure Haskell network stack that can be used for Haskell+ networking in the context of the HaLVM, or with a Linux tap device. Currently,+ HaNS supports 802.3, IPv4, ARP, DHCP (partially), ICMP, UDP, and TCP.++source-repository head+ type: git+ location: git://src.galois.com/srv/git/HaNS.git++flag bounded-channels+ default: False+ description: Use bounded channels for message passing++flag example+ default: False+ description: Build the example program++flag halvm+ default: False+ description: Build for the HaLVM++library+ if(flag(halvm))+ build-depends: XenDevice, communication+ exposed-modules: Hans.Device.Xen,+ Hans.Device.Ivc+ else+ build-depends: unix+ include-dirs: cbits+ c-sources: cbits/tapdevice.c+ exposed-modules: Hans.Device.Tap++ if flag(bounded-channels)+ build-depends: BoundedChan+ cpp-options: -DBOUNDED_CHANNELS++ ghc-options: -Wall+ hs-source-dirs: src+ build-depends: base == 4.*,+ cereal == 0.3.*,+ bytestring == 0.9.1.*,+ containers >= 0.4.0.0 && < 0.5.0.0,+ monadLib == 3.6.*,+ time >= 1.2.0.0 && < 1.3.0.0,+ fingertree == 0.0.1.*,+ random == 1.0.0.*+ exposed-modules: Data.PrefixTree,+ Hans.Address,+ Hans.Utils.Checksum,+ Hans.Layer,+ Hans.Ports,+ Hans.Setup,+ Hans.Utils,+ Hans.Channel,+ Hans.DhcpClient,+ Hans.Simple,+ Hans.Layer.Tcp,+ Hans.Layer.Arp.Table,+ Hans.Layer.Tcp.Handlers,+ Hans.Layer.Tcp.Monad,+ Hans.Layer.Tcp.Socket,+ Hans.Layer.IP4,+ Hans.Layer.Udp,+ Hans.Layer.Arp,+ Hans.Layer.IP4.Routing,+ Hans.Layer.IP4.Fragmentation,+ Hans.Layer.Timer,+ Hans.Layer.Ethernet,+ Hans.Layer.Icmp4,+ Hans.Message.Tcp,+ Hans.Message.Types,+ Hans.Message.Udp,+ Hans.Message.Ip4,+ Hans.Message.EthernetFrame,+ Hans.Message.Arp,+ Hans.Message.Dhcp4Codec,+ Hans.Message.Dhcp4,+ Hans.Message.Dhcp4Options,+ Hans.Message.Icmp4,+ Hans.Address.Mac,+ Hans.Address.IP4,+ Network.TCP.Aux.SockMonad,+ Network.TCP.Aux.Param,+ Network.TCP.Aux.Misc,+ Network.TCP.Aux.Output,+ Network.TCP.LTS.User,+ Network.TCP.LTS.Out,+ Network.TCP.LTS.InPassive,+ Network.TCP.LTS.InActive,+ Network.TCP.LTS.InMisc,+ Network.TCP.LTS.In,+ Network.TCP.LTS.InData,+ Network.TCP.Type.Socket,+ Network.TCP.Type.Syscall,+ Network.TCP.Type.Datagram,+ Network.TCP.Type.Base,+ Network.TCP.Type.Timer++executable test+ if flag(example)+ buildable: True+ else+ buildable: False++ if(flag(bounded-channels))+ build-depends: BoundedChan+ cpp-options: -DBOUNDED_CHANNELS++ main-is: test.hs+ other-modules: WebServer+ ghc-options: -Wall+ hs-source-dirs: example+ build-depends: base == 4.*,+ cereal == 0.3.0.*,+ bytestring == 0.9.1.*,+ containers >= 0.4.0.0 && < 0.5.0.0,+ monadLib == 3.6.*,+ time >= 1.2.0.0 && < 1.3.0.0,+ old-locale == 1.0.0.*,+ hans++ if flag(halvm)+ build-depends: XenDevice == 1.0.0,+ RendezvousLib == 1.0.0,+ HALVMCore == 1.0.0,+ communication == 1.0.0+ else+ ghc-options: -threaded
+ src/Data/PrefixTree.hs view
@@ -0,0 +1,244 @@+{-# LANGUAGE CPP #-}++module Data.PrefixTree (+ PrefixTree++ -- * Construction+ , empty+ , singleton+ , insert+ , delete+ , toList+ , fromList++ -- * Querying+ , lookup+ , member+ , matches+ , match+ , elems+ , keys+ , key+ ) where++import Prelude hiding (lookup)+import Data.Maybe (isJust,listToMaybe)++#ifdef TESTS+import Data.List (nub)+import Test.QuickCheck+#endif++data PrefixTree a+ = Empty+ | Prefix Key (Maybe a) (PrefixTree a)+ | Branch (PrefixTree a) (PrefixTree a)+ deriving Show++type Key = [Bool]++-- Prefix Manipulation ---------------------------------------------------------++matchPrefix :: Key -> Key -> (Key,Key,Key)+matchPrefix = loop id+ where+ loop k (a:as) (b:bs) | a == b = loop (k . (a:)) as bs+ loop k as bs = (k [], as, bs)+++-- Construction ----------------------------------------------------------------++empty :: PrefixTree a+empty = Empty++singleton :: Key -> a -> PrefixTree a+singleton ks a = Prefix ks (Just a) empty++fromList :: [(Key,a)] -> PrefixTree a+fromList = foldr (uncurry insert) empty++toList :: PrefixTree a -> [([Bool], a)]+toList t =+ case t of+ Empty -> []++ Prefix ls mb t' ->+ case mb of+ Nothing -> map (prefix ls) (toList t')+ Just a -> (ls,a) : map (prefix ls) (toList t')++ Branch l r -> toList l ++ toList r++ where+ prefix ls (ks,a) = (ls ++ ks, a)++elems :: PrefixTree a -> [a]+elems t =+ case t of+ Empty -> []+ Prefix _ (Just a) t' -> a : elems t'+ Prefix _ _ t' -> elems t'+ Branch l r -> elems l ++ elems r++insert :: Key -> a -> PrefixTree a -> PrefixTree a+insert ks a t =+ case t of++ Empty -> singleton ks a++ Prefix ls mb t' ->+ case matchPrefix ks ls of+ -- empty node+ ([],[],[]) -> Prefix [] (Just a) t'++ -- empty key+ ([],[],_) -> Prefix [] (Just a) t++ -- empty node, full key+ ([],_,[]) -> Prefix [] mb (insert ks a t')++ -- no common prefix, branch.+ ([], k:_, _)+ | k -> Branch (singleton ks a) t+ | otherwise -> Branch t (singleton ks a)++ -- complete match, replace the value+ (_, [], []) -> Prefix ks (Just a) t'++ -- complete prefix match, but partial key match+ (_ ,ks',[]) -> Prefix ls mb (insert ks' a t')++ -- complete key match, partial prefix match+ (_,[],ls') -> Prefix ks (Just a) (Prefix ls' mb t')++ -- partial common prefix, but not the full key+ (ps,ks'@(k:_),ls') -> Prefix ps Nothing br+ where+ t1 = singleton ks' a+ t2 = Prefix ls' mb t'+ br | k = Branch t1 t2+ | otherwise = Branch t2 t1++ Branch l r ->+ case ks of+ [] -> Prefix [] (Just a) t+ b:_ | b -> Branch (insert ks a l) r+ | otherwise -> Branch l (insert ks a r)+++delete :: Key -> PrefixTree a -> PrefixTree a+delete ks t =+ case t of++ Empty -> Empty++ Prefix ls mb t' ->+ case matchPrefix ks ls of+ (_,[],[]) -> compact (Prefix ls Nothing t')+ ([],ks',[]) -> compact (Prefix ls mb (delete ks' t'))+ _ -> t++ Branch l r ->+ case ks of+ [] -> t+ b:bs | b -> compact (Branch (delete bs l) r)+ | otherwise -> compact (Branch l (delete bs r))++compact :: PrefixTree a -> PrefixTree a+compact t =+ case t of++ Prefix ls Nothing (Prefix ks mb t') -> Prefix (ls ++ ks) mb t'++ Branch l Empty -> l+ Branch Empty r -> r++ _ -> t+++-- Querying --------------------------------------------------------------------++member :: Key -> PrefixTree a -> Bool+member ks t =+ case t of++ Empty -> False++ Prefix ls mb t' ->+ case matchPrefix ks ls of+ (_,[], []) -> isJust mb+ (_,ks',[]) -> member ks' t'+ _ -> False++ Branch l r ->+ case ks of+ [] -> False+ b:_ | b -> member ks l+ | otherwise -> member ks r++matches :: Key -> PrefixTree a -> [a]+matches = loop []+ where+ loop ms ks t =+ case t of++ Empty -> ms++ Prefix ls mb t' ->+ case matchPrefix ks ls of+ (_,[], []) -> maybe ms (:ms) mb+ (_,ks',[]) -> loop (maybe ms (:ms) mb) ks' t'+ _ -> ms++ Branch l r ->+ case ks of+ [] -> ms+ b:_ | b -> loop ms ks l+ | otherwise -> loop ms ks r++match :: Key -> PrefixTree a -> Maybe a+match k t = listToMaybe (matches k t)++lookup :: Key -> PrefixTree a -> Maybe a+lookup = match++keys :: Key -> PrefixTree a -> [Key]+keys = keys' [] []+ where+ keys' as p ks t =+ case t of++ Empty -> as++ Prefix ls _ t' ->+ case matchPrefix ks ls of+ (ps,ks',[]) -> keys' (p':as) p' ks' t'+ where p' = p ++ ps+ _ -> as++ Branch l r -> keys' ls p ks r+ where ls = keys' as p ks l++key :: Key -> PrefixTree a -> Maybe Key+key ks t = listToMaybe (keys ks t)+++-- Tests -----------------------------------------------------------------------++#ifdef TESTS+forAllUniqueLists :: (Testable prop, Arbitrary a, Show a, Eq a)+ => ([a] -> prop) -> Property+forAllUniqueLists = forAll (nub `fmap` arbitrary)++prop_toList_fromList = forAllUniqueLists p+ where+ p :: [([Bool],())] -> Bool+ p bs = length bs == length bs' && all (`elem` bs) bs'+ where bs' = toList (fromList bs)++prop_matchesOrder bs = and (map (f . fst) bs)+ where+ t1 = fromList bs+ t2 = fromList (reverse bs)+ f k = matches k t1 == matches k t2+#endif
+ src/Hans/Address.hs view
@@ -0,0 +1,24 @@+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE FunctionalDependencies #-}+{-# LANGUAGE FlexibleContexts #-}++module Hans.Address where++import Data.Serialize (Serialize)+import Data.Word (Word8)++class (Ord a, Serialize a) => Address a where+ addrSize :: a -> Word8+ toBits :: a -> [Bool]+++class Address addr => Mask mask addr | addr -> mask, mask -> addr where+ masksAddress :: mask -> addr -> Bool+ withMask :: addr -> Int -> mask+ getMaskComponents :: mask -> (addr,Int)+ getMaskRange :: mask -> (addr,addr)+ broadcastAddress :: mask -> addr+++isBroadcast :: (Eq addr, Mask mask addr) => mask -> addr -> Bool+isBroadcast m a = broadcastAddress m == a
+ src/Hans/Address/IP4.hs view
@@ -0,0 +1,104 @@+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE MultiParamTypeClasses #-}++module Hans.Address.IP4 where++import Hans.Address+import Hans.Utils (Endo)++import Control.Monad (guard,liftM2)+import Data.Serialize (Serialize(..))+import Data.Serialize.Get (getWord32be)+import Data.Serialize.Put (putWord32be)+import Data.Bits (Bits((.&.),(.|.),shiftL,shiftR))+import Data.Data (Data)+import Data.List (intersperse)+import Data.Typeable (Typeable)+import Data.Word (Word8,Word32)+import Numeric (readDec)+++data IP4 = IP4+ {-# UNPACK #-} !Word8+ {-# UNPACK #-} !Word8+ {-# UNPACK #-} !Word8+ {-# UNPACK #-} !Word8+ deriving (Ord,Eq,Typeable,Data)++instance Address IP4 where+ addrSize _ = 4++ toBits (IP4 a b c d) = f 0x80 a (f 0x80 b (f 0x80 c (f 0x80 d [])))+ where+ f 0 _ xs = xs+ f m i xs = (i .&. m == 0) : f (m `shiftR` 1) i xs++instance Serialize IP4 where+ get = do+ n <- getWord32be+ return $! convertFromWord32 n+ put ip = putWord32be (convertToWord32 ip)++instance Show IP4 where+ showsPrec _ (IP4 a b c d) = foldl (.) id+ $ intersperse (showChar '.')+ [shows a, shows b, shows c, shows d]++instance Read IP4 where+ readsPrec _ rest0 = do+ (a, '.':rest1) <- readDec rest0+ (b, '.':rest2) <- readDec rest1+ (c, '.':rest3) <- readDec rest2+ (d, rest4) <- readDec rest3+ return (IP4 a b c d, rest4)++convertToWord32 :: IP4 -> Word32+convertToWord32 (IP4 a b c d)+ = fromIntegral a `shiftL` 24+ + fromIntegral b `shiftL` 16+ + fromIntegral c `shiftL` 8+ + fromIntegral d++convertFromWord32 :: Word32 -> IP4+convertFromWord32 n = IP4 a b c d+ where+ a = fromIntegral (n `shiftR` 24)+ b = fromIntegral (n `shiftR` 16)+ c = fromIntegral (n `shiftR` 8)+ d = fromIntegral n+++data IP4Mask = IP4Mask+ {-# UNPACK #-} !IP4+ {-# UNPACK #-} !Word8+ deriving (Eq,Ord,Typeable,Data,Show)++instance Serialize IP4Mask where+ put (IP4Mask i m) = put i >> put m+ get = liftM2 IP4Mask get get++instance Read IP4Mask where+ readsPrec x rest0 = do+ (addr,'/':rest1) <- readsPrec x rest0+ (bits, rest2) <- readsPrec x rest1+ guard (bits >= 0 && bits <= 32)+ return (IP4Mask addr bits, rest2)++instance Mask IP4Mask IP4 where+ masksAddress mask@(IP4Mask _ bits) a2 =+ clearHostBits mask == clearHostBits (IP4Mask a2 bits)+ getMaskRange x = (clearHostBits x, setHostBits x)+ withMask addr bits = IP4Mask addr (fromIntegral bits)+ getMaskComponents (IP4Mask addr bits) = (addr,fromIntegral bits)+ broadcastAddress = setHostBits++modifyAsWord32 :: Endo Word32 -> Endo IP4+modifyAsWord32 f = convertFromWord32 . f . convertToWord32++clearHostBits :: IP4Mask -> IP4+clearHostBits (IP4Mask addr bits) = modifyAsWord32 (.&. mask) addr+ where mask = -2 ^ (32 - bits)++setHostBits :: IP4Mask -> IP4+setHostBits (IP4Mask addr bits) = modifyAsWord32 (.|. mask) addr+ where mask = 2 ^ (32 - bits) - 1
+ src/Hans/Address/Mac.hs view
@@ -0,0 +1,77 @@++module Hans.Address.Mac (+ Mac(..)+ , showsMac+ , macMask+ ) where++import Hans.Address+import Hans.Utils (showPaddedHex)++import Data.Serialize (Serialize(..))+import Data.Serialize.Get (getWord16be,getWord32be)+import Data.Serialize.Put (putByteString)+import Data.Bits (Bits(shiftR,testBit,complement))+import Data.List (intersperse)+import Data.Word (Word8)+import Numeric (readHex)+import qualified Data.ByteString as S+++-- | Mac addresses.+data Mac = Mac+ {-# UNPACK #-} !Word8+ {-# UNPACK #-} !Word8+ {-# UNPACK #-} !Word8+ {-# UNPACK #-} !Word8+ {-# UNPACK #-} !Word8+ {-# UNPACK #-} !Word8+ deriving ( Eq, Ord )+++-- | Show a Mac address.+showsMac :: Mac -> ShowS+showsMac (Mac a b c d e f) = foldl1 (.)+ $ intersperse (showChar ':')+ $ map showPaddedHex [a,b,c,d,e,f]++-- | Generates a mask tailored to the given MAC address.+macMask :: Mac -> Mac+macMask (Mac a b c d e f) =+ Mac (complement a)+ (complement b)+ (complement c)+ (complement d)+ (complement e)+ (complement f)++instance Show Mac where+ showsPrec _ = showsMac++instance Read Mac where+ readsPrec _ = loop 6 []+ where+ loop :: Int -> [Word8] -> String -> [(Mac,String)]+ loop 0 [f,e,d,c,b,a] str = [(Mac a b c d e f,str)]+ loop 0 _ _ = []+ loop n acc str = case readHex str of+ [(a,':':rest)] -> loop (n-1) (a:acc) rest+ [(a, rest)] -> loop 0 (a:acc) rest+ _ -> []+++instance Address Mac where+ addrSize _ = 6++ toBits (Mac a b c d e f) = concatMap k [a,b,c,d,e,f]+ where k i = map (testBit i) [0 .. 7]++instance Serialize Mac where+ get = do+ n <- getWord32be+ m <- getWord16be+ let f x d = fromIntegral (x `shiftR` d)+ return $! Mac (f n 24) (f n 16) (f n 8) (fromIntegral n)+ (f m 8) (fromIntegral m)++ put (Mac a b c d e f) = putByteString (S.pack [a,b,c,d,e,f])
+ src/Hans/Channel.hs view
@@ -0,0 +1,34 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE CPP #-}++module Hans.Channel where++#ifdef BOUNDED_CHANNELS+import Control.Concurrent.BoundedChan as C++type Channel a = BoundedChan a++newChannel :: IO (Channel a)+newChannel = newBoundedChan 20++receive :: Channel a -> IO a+receive c = readChan c++send :: Channel a -> a -> IO ()+send c a = writeChan c $! a++#else+import Control.Concurrent.Chan as C++type Channel a = Chan a++newChannel :: IO (Channel a)+newChannel = newChan++receive :: Channel a -> IO a+receive = readChan++send :: Channel a -> a -> IO ()+send c a = writeChan c $! a++#endif
+ src/Hans/Device/Ivc.hs view
@@ -0,0 +1,30 @@+{-# LANGUAGE MultiParamTypeClasses #-}++module Hans.Device.Ivc (+ ivcSend+ , ivcReceiveLoop+ , Bytes(getBytes)+ ) where++import Hans.Layer.Ethernet (EthernetHandle,queueEthernet)++import Communication.IVC as IVC (put,OutChannelEx,get,InChannelEx,Bin)+import Control.Monad (forever,when)+import Data.Serialize (Serialize(get,put),getByteString,putByteString,remaining)+import qualified Data.ByteString as S++newtype Bytes = Bytes+ { getBytes :: S.ByteString+ } deriving Show++instance Serialize Bytes where+ get = Bytes `fmap` (getByteString =<< remaining)+ put = putByteString . getBytes++ivcSend :: OutChannelEx Bin Bytes -> S.ByteString -> IO ()+ivcSend chan = IVC.put chan . Bytes++ivcReceiveLoop :: InChannelEx Bin Bytes -> EthernetHandle -> IO ()+ivcReceiveLoop chan eth = forever $ do+ Bytes bs <- IVC.get chan+ when (S.length bs > 14) (queueEthernet eth bs)
+ src/Hans/Device/Tap.hs view
@@ -0,0 +1,62 @@+{-# LANGUAGE ForeignFunctionInterface #-}++module Hans.Device.Tap where++import Hans.Layer.Ethernet+import Hans.Utils++import Control.Concurrent (threadWaitRead)+import Control.Monad (forever)+import Data.Word (Word8)+import Foreign.C.String (CString,withCString)+import Foreign.C.Types (CLong,CSize)+import Foreign.ForeignPtr (withForeignPtr)+import Foreign.Ptr (Ptr)+import System.Posix.Types (Fd)+import qualified Data.ByteString as S+import qualified Data.ByteString.Internal as S++++-- | Open a device by name.+openTapDevice :: DeviceName -> IO (Maybe Fd)+openTapDevice "" = return Nothing+openTapDevice name = withCString name $ \str -> do+ ret <- c_init_tap_device str+ if ret < 0 then return Nothing else return (Just ret)+++-- | Send an ethernet frame via a tap device.+tapSend :: Fd -> Packet -> IO ()+tapSend fd packet = do+ let (fptr, 0, len) = S.toForeignPtr packet+ _res <- withForeignPtr fptr $ \ptr -> c_write fd ptr (fromIntegral len)+ -- XXX: make sure to continue sending if res < len+ return ()+++-- | Fork a reciever loop, and return an IO action to kill the running thread.+tapReceiveLoop :: Fd -> EthernetHandle -> IO ()+tapReceiveLoop fd eh = forever (k =<< tapReceive fd)+ where k pkt = queueEthernet eh pkt+++-- | Recieve an ethernet frame from a tap device.+tapReceive :: Fd -> IO Packet+tapReceive fd = do+ threadWaitRead fd+ let packet ptr = fromIntegral `fmap` c_read fd ptr 1514+ bs <- S.createAndTrim 1514 packet+ if S.length bs <= 14+ then tapReceive fd+ else return bs+++foreign import ccall unsafe "init_tap_device"+ c_init_tap_device :: CString -> IO Fd++foreign import ccall unsafe "write"+ c_write :: Fd -> Ptr Word8 -> CSize -> IO CLong++foreign import ccall unsafe "read"+ c_read :: Fd -> Ptr Word8 -> CSize -> IO CLong
+ src/Hans/Device/Xen.hs view
@@ -0,0 +1,43 @@+module Hans.Device.Xen where++import Hans.Layer.Ethernet+import Hans.Utils++import Data.Maybe (listToMaybe)+import XenDevice.NIC as Xen+import qualified Data.ByteString as S+import qualified Data.ByteString.Lazy as L+++-- Utilities -------------------------------------------------------------------++infixl 1 >>=?+(>>=?) :: Monad m => m (Maybe a) -> (a -> m (Maybe b)) -> m (Maybe b)+m >>=? f = do+ mb <- m+ case mb of+ Nothing -> return Nothing+ Just a -> f a++returnJust :: Monad m => a -> m (Maybe a)+returnJust = return . Just+++-- Xen NIC ---------------------------------------------------------------------+++openXenDevice :: String -> IO (Maybe NIC)+openXenDevice _ =+ listToMaybe `fmap` Xen.potentialNICs >>=? \ dev ->+ initializeNIC dev Nothing+++xenSend :: NIC -> S.ByteString -> IO ()+xenSend nic bs = void (Xen.transmitPacket nic (L.fromChunks [bs]))+++xenReceiveLoop :: NIC -> EthernetHandle -> IO ()+xenReceiveLoop nic eh = Xen.setReceiveHandler nic k+ where+ k bs | L.length bs <= 14 = return ()+ | otherwise = queueEthernet eh (S.concat (L.toChunks bs))
+ src/Hans/DhcpClient.hs view
@@ -0,0 +1,217 @@+module Hans.DhcpClient (+ dhcpDiscover+ ) where++import Hans.Address+import Hans.Address.IP4+import Hans.Address.Mac+import Hans.Layer.Ethernet (sendEthernet,addEthernetHandler)+import Hans.Layer.IP4 (connectEthernet)+import Hans.Layer.Timer (delay)+import Hans.Layer.Udp (addUdpHandler,removeUdpHandler,queueUdp)+import Hans.Message.Dhcp4+import Hans.Message.Dhcp4Codec+import Hans.Message.Dhcp4Options+import Hans.Message.EthernetFrame+import Hans.Message.Ip4+import Hans.Message.Udp+import Hans.Setup++import Control.Monad (guard)+import Data.Serialize (runGet,runPut)+import Data.Maybe (fromMaybe)+import System.Random (randomIO)+import qualified Data.ByteString as S+++-- Protocol Constants ----------------------------------------------------------++-- | BOOTP server port.+bootps :: UdpPort+bootps = UdpPort 67++-- | BOOTP client port.+bootpc :: UdpPort+bootpc = UdpPort 68++currentNetwork :: IP4+currentNetwork = IP4 0 0 0 0++broadcastIP4 :: IP4+broadcastIP4 = IP4 255 255 255 255++broadcastMac :: Mac+broadcastMac = Mac 0xff 0xff 0xff 0xff 0xff 0xff++udpProtocol :: IP4Protocol+udpProtocol = IP4Protocol 0x11++ethernetIp4 :: EtherType+ethernetIp4 = EtherType 0x0800++defaultRoute :: IP4Mask+defaultRoute = IP4 0 0 0 0 `withMask` 0+++-- DHCP ------------------------------------------------------------------------++type AckHandler = IP4 -> IO ()++-- | Discover a dhcp server, and request an address.+dhcpDiscover :: NetworkStack -> Mac -> AckHandler -> IO ()+dhcpDiscover ns mac h = do+ w32 <- randomIO+ let xid = Xid (fromIntegral (w32 :: Int))++ addEthernetHandler (nsEthernet ns) ethernetIp4 (dhcpIP4Handler ns)+ addUdpHandler (nsUdp ns) bootpc (handleOffer ns (Just h))++ let disc = discoverToMessage (mkDiscover xid mac)+ sendMessage ns disc currentNetwork broadcastIP4 broadcastMac++-- | Restore the connection between the Ethernet and IP4 layers.+restoreIp4 :: NetworkStack -> IO ()+restoreIp4 ns = connectEthernet (nsIp4 ns) (nsEthernet ns)++-- | Handle IP4 messages from the Ethernet layer, passing all relevant DHCP+-- messages to the UDP layer.+dhcpIP4Handler :: NetworkStack -> S.ByteString -> IO ()+dhcpIP4Handler ns bytes =+ case runGet parseIP4Packet bytes of+ Left err -> putStrLn err >> return ()+ Right (hdr,ihl,len)+ | ip4Protocol hdr == udpProtocol -> queue+ | otherwise -> return ()+ where+ queue = queueUdp (nsUdp ns) (ip4SourceAddr hdr) (ip4DestAddr hdr)+ $ S.take (len - ihl)+ $ S.drop ihl bytes++-- | Handle a DHCP Offer message.+--+-- * Remove the current UDP handler+-- * Install an DHCP Ack handler+-- * Send a DHCP Request+handleOffer :: NetworkStack -> Maybe AckHandler -> IP4 -> UdpPort+ -> S.ByteString -> IO ()+handleOffer ns mbh _src _srcPort bytes =+ case runGet (getDhcp4Message) bytes of+ Right msg -> case parseDhcpMessage msg of++ Just (Right (OfferMessage offer)) -> do+ removeUdpHandler (nsUdp ns) bootpc+ let req = requestToMessage (offerToRequest offer)+ addUdpHandler (nsUdp ns) bootpc (handleAck ns offer mbh)+ sendMessage ns req currentNetwork broadcastIP4 broadcastMac++ msg1 -> do+ putStrLn (show msg)+ putStrLn (show msg1)++ Left err -> putStrLn err++-- | Handle a DHCP Ack message.+--+-- * Remove the custom IP4 handler+-- * Restore the connection between the Ethernet and IP4 layers+-- * Remove the bootpc UDP listener+-- * Configure the network stack with options from the Ack+-- * Install a timer that renews the address after 50% of the lease time+-- has passed+handleAck :: NetworkStack -> Offer -> Maybe AckHandler -> IP4 -> UdpPort+ -> S.ByteString -> IO ()+handleAck ns offer mbh _src _srcPort bytes =+ case runGet (getDhcp4Message) bytes of+ Right msg -> case parseDhcpMessage msg of++ Just (Right (AckMessage ack)) -> do+ removeUdpHandler (nsUdp ns) bootpc+ restoreIp4 ns+ apply (ackNsOptions ack) ns+ let ms = fromIntegral (ackLeaseTime ack) * 500+ delay (nsTimers ns) ms (dhcpRenew ns offer)+ putStrLn ("Bound to: " ++ show (ackYourAddr ack))++ case mbh of+ Nothing -> return ()+ Just h -> h (ackYourAddr ack)++ msg1 -> do+ putStrLn (show msg)+ putStrLn (show msg1)++ Left err -> putStrLn err++-- | Perform a DHCP Renew.+--+-- * Re-install the DHCP IP4 handler+-- * Add a UDP handler for an Ack message+-- * Re-send a renquest message, generated from the offer given.+dhcpRenew :: NetworkStack -> Offer -> IO ()+dhcpRenew ns offer = do+ addEthernetHandler (nsEthernet ns) ethernetIp4 (dhcpIP4Handler ns)++ let req = requestToMessage (offerToRequest offer)+ addUdpHandler (nsUdp ns) bootpc (handleAck ns offer Nothing)+ sendMessage ns req currentNetwork broadcastIP4 broadcastMac+++-- NetworkStack Config ---------------------------------------------------------++lookupGateway :: [Dhcp4Option] -> Maybe IP4+lookupGateway = foldr p Nothing+ where+ p (OptRouters rs) _ = guard (not (null rs)) >> Just (head rs)+ p _ a = a++lookupSubnet :: [Dhcp4Option] -> Maybe Int+lookupSubnet = foldr p Nothing+ where+ p (OptSubnetMask (SubnetMask i)) _ = Just i+ p _ a = a++-- | Produce options for the network stack from a DHCP Ack.+ackNsOptions :: Ack -> [SomeOption]+ackNsOptions ack =+ [ toOption (LocalEthernet (addr `withMask` mask) mac)+ , toOption (Route defaultRoute gateway)+ ]+ where+ mac = ackClientHardwareAddr ack+ addr = ackYourAddr ack+ opts = ackOptions ack+ mask = fromMaybe 24 (lookupSubnet opts)+ gateway = fromMaybe (ackRelayAddr ack) (lookupGateway opts)+++-- Packet Helpers --------------------------------------------------------------++sendMessage :: NetworkStack -> Dhcp4Message -> IP4 -> IP4 -> Mac -> IO ()+sendMessage ns resp src dst hwdst = do+ ipBytes <- mkIpBytes src dst bootpc bootps+ (runPut (putDhcp4Message resp))+ let mac = dhcp4ClientHardwareAddr resp+ let frame = EthernetFrame+ { etherDest = hwdst+ , etherSource = mac+ , etherType = ethernetIp4+ , etherData = ipBytes+ }+ putStrLn (show mac ++ " -> " ++ show hwdst)++ sendEthernet (nsEthernet ns) frame++mkIpBytes :: IP4 -> IP4 -> UdpPort -> UdpPort -> S.ByteString -> IO S.ByteString+mkIpBytes srcAddr dstAddr srcPort dstPort payload = do+ udpBytes <- do+ let udpHdr = UdpHeader srcPort dstPort 0+ udp = UdpPacket udpHdr payload+ mk = mkIP4PseudoHeader srcAddr dstAddr udpProtocol+ renderUdpPacket udp mk++ ipBytes <- do+ let ipHdr = emptyIP4Header udpProtocol srcAddr dstAddr+ ip = IP4Packet ipHdr udpBytes+ renderIP4Packet ip++ return ipBytes
+ src/Hans/Layer.hs view
@@ -0,0 +1,140 @@+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE FunctionalDependencies #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE Rank2Types #-}++module Hans.Layer where++import Hans.Utils (just)++import Control.Applicative (Applicative(..),Alternative(..))+import Control.Monad (ap,MonadPlus(mzero,mplus))+import Data.Monoid (Monoid(..))+import Data.Time.Clock.POSIX+import MonadLib (StateM(get,set))+import qualified Control.Exception as X+import qualified Data.Map as Map++data LayerState i = LayerState+ { lsNow :: POSIXTime+ , lsState :: i+ }++data Action = Nop | Action (IO ())++instance Monoid Action where+ mempty = Nop++ mappend (Action a) (Action b) = Action (a >> b)+ mappend Nop b = b+ mappend a _ = a++runAction :: Action -> IO ()+runAction Nop = return ()+runAction (Action m) = m `X.catch` \ se -> print (se :: X.SomeException)++data Result i a+ = Error Action+ | Result (LayerState i) a Action++-- | Failure continuation+type Failure i r = Action -> Result i r++-- | Success continuation+type Success a i r = a -> LayerState i -> Action -> Result i r++newtype Layer i a = Layer+ { getLayer :: forall r. LayerState i -> Action+ -> Failure i r -> Success a i r+ -> Result i r }++runLayer :: LayerState i -> Layer i a -> Result i a+runLayer i0 m = getLayer m i0 mempty Error success+ where success a i o = Result i a o++loopLayer :: i -> IO msg -> (msg -> Layer i ()) -> IO ()+loopLayer i0 msg k = loop (LayerState 0 i0)+ where+ loop i = do+ a <- msg+ now <- getPOSIXTime+ let res = runLayer (i {lsNow = now }) (k a)+ X.evaluate res `X.catch` \ se -> print (se :: X.SomeException) >> return res+ case res of+ Error m -> runAction m >> loop i+ Result i' () m -> runAction m >> loop i'++instance Functor (Layer i) where+ fmap g m = Layer (\i0 o0 f k -> getLayer m i0 o0 f (\a i o -> k (g a) i o))++instance Applicative (Layer i) where+ pure = return+ (<*>) = ap++instance Alternative (Layer i) where+ empty = Layer (\_ o0 f _ -> f o0)+ a <|> b = Layer (\i o f k -> getLayer a i o (\_ -> getLayer b i o f k) k)++instance Monad (Layer i) where+ return x = Layer (\i o _ k -> k x i o)+ m >>= g = Layer $ \i0 o0 f k -> getLayer m i0 o0 f $ \a i o ->+ getLayer (g a) i o f k++instance MonadPlus (Layer i) where+ mzero = empty+ mplus = (<|>)++instance StateM (Layer i) i where+ get = Layer (\i0 o0 _ k -> k (lsState i0) i0 o0)+ set i = Layer (\i0 o0 _ k -> k () (i0 { lsState = i }) o0)+++-- Utilities -------------------------------------------------------------------++dropPacket :: Layer i a+dropPacket = empty++time :: Layer i POSIXTime+time = Layer $ \i0 o0 _ k -> k (lsNow i0) i0 o0++output :: IO () -> Layer i ()+output m = Layer $ \i0 o0 _ k -> k () i0 (o0 `mappend` Action m)++liftRight :: Either String b -> Layer i b+liftRight (Right b) = return b+liftRight (Left err) = do+ output (putStrLn err)+ dropPacket++-- Handler Generalization ------------------------------------------------------++type Handlers k a = Map.Map k a++emptyHandlers :: Handlers k a+emptyHandlers = Map.empty+++class ProvidesHandlers i k a | i -> k a where+ getHandlers :: i -> Handlers k a+ setHandlers :: Handlers k a -> i -> i+++getHandler :: (Ord k, ProvidesHandlers i k a) => k -> Layer i a+getHandler k = do+ state <- get+ just (Map.lookup k (getHandlers state))+++addHandler :: (Ord k, ProvidesHandlers i k a) => k -> a -> Layer i ()+addHandler k a = do+ state <- get+ let hs' = Map.insert k a (getHandlers state)+ hs' `seq` set (setHandlers hs' state)+++removeHandler :: (Ord k, ProvidesHandlers i k a) => k -> Layer i ()+removeHandler k = do+ state <- get+ let hs' = Map.delete k (getHandlers state)+ hs' `seq` set (setHandlers hs' state)
+ src/Hans/Layer/Arp.hs view
@@ -0,0 +1,243 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE FlexibleContexts #-}++module Hans.Layer.Arp (+ ArpHandle+ , runArpLayer++ -- External Interface+ , arpWhoHas+ , arpIP4Packet+ , addLocalAddress+ ) where++import Hans.Address.IP4+import Hans.Address.Mac+import Hans.Channel+import Hans.Layer+import Hans.Layer.Arp.Table+import Hans.Layer.Ethernet+import Hans.Layer.Timer+import Hans.Message.Arp+import Hans.Message.EthernetFrame+import Hans.Utils++import Control.Concurrent (forkIO,takeMVar,putMVar,newEmptyMVar)+import Control.Monad (forM_,mplus,guard,unless,when)+import Data.Serialize (decode,encode)+import MonadLib (BaseM(inBase),set,get)+import qualified Data.Map as Map++-- | A handle to a running arp layer.+type ArpHandle = Channel (Arp ())+++-- | Start an arp layer.+runArpLayer :: ArpHandle -> EthernetHandle -> TimerHandle -> IO ()+runArpLayer h eth th = do+ addEthernetHandler eth (EtherType 0x0806) (send h . handleIncoming)+ let i = emptyArpState h eth th+ void (forkIO (loopLayer i (receive h) id))+++-- | Lookup the hardware address associated with an IP address.+arpWhoHas :: BaseM m IO => ArpHandle -> IP4 -> m (Maybe Mac)+arpWhoHas h !ip = inBase $ do+ var <- newEmptyMVar+ send h (whoHas ip (putMVar var))+ takeMVar var+++-- | Send an IP packet via the arp layer, to resolve the underlying hardware+-- addresses.+arpIP4Packet :: ArpHandle -> IP4 -> IP4 -> Packet -> IO ()+arpIP4Packet h !src !dst !pkt = send h (handleOutgoing src dst pkt)+++addLocalAddress :: ArpHandle -> IP4 -> Mac -> IO ()+addLocalAddress h !ip !mac = send h (handleAddAddress ip mac)+++-- Message Handling ------------------------------------------------------------++type Arp = Layer ArpState++data ArpState = ArpState+ { arpTable :: ArpTable+ , arpAddrs :: Map.Map IP4 Mac -- this layer's addresses+ , arpWaiting :: Map.Map IP4 [Maybe Mac -> IO ()]+ , arpEthernet :: EthernetHandle+ , arpTimers :: TimerHandle+ , arpSelf :: ArpHandle+ }++emptyArpState :: ArpHandle -> EthernetHandle -> TimerHandle -> ArpState+emptyArpState h eth ts = ArpState+ { arpTable = Map.empty+ , arpAddrs = Map.empty+ , arpWaiting = Map.empty+ , arpEthernet = eth+ , arpTimers = ts+ , arpSelf = h+ }++ethernetHandle :: Arp EthernetHandle+ethernetHandle = arpEthernet `fmap` get++timerHandle :: Arp TimerHandle+timerHandle = arpTimers `fmap` get++addEntry :: IP4 -> Mac -> Arp ()+addEntry spa sha = do+ state <- get+ now <- time+ let table' = addArpEntry now spa sha (arpTable state)+ table' `seq` set state { arpTable = table' }+ runWaiting spa (Just sha)++addWaiter :: IP4 -> (Maybe Mac -> IO ()) -> Arp ()+addWaiter addr cont = do+ state <- get+ set state { arpWaiting = Map.alter f addr (arpWaiting state) }+ where+ f Nothing = Just [cont]+ f (Just ks) = Just (cont:ks)++runWaiting :: IP4 -> Maybe Mac -> Arp ()+runWaiting spa sha = do+ state <- get+ let (mb,waiting') = Map.updateLookupWithKey f spa (arpWaiting state)+ where f _ _ = Nothing+ -- run the callbacks associated with this protocol address+ let run cb = output (cb sha)+ mapM_ run (maybe [] reverse mb)+ waiting' `seq` set state { arpWaiting = waiting' }++updateExistingEntry :: IP4 -> Mac -> Arp Bool+updateExistingEntry spa sha = do+ state <- get+ let update = do+ guard (spa `Map.member` arpTable state)+ addEntry spa sha+ return True+ update `mplus` return False++localHwAddress :: IP4 -> Arp Mac+localHwAddress pa = do+ state <- get+ just (Map.lookup pa (arpAddrs state))++sendArpPacket :: ArpPacket Mac IP4 -> Arp ()+sendArpPacket msg = do+ eth <- ethernetHandle+ let frame = EthernetFrame+ { etherSource = arpSHA msg+ , etherDest = arpTHA msg+ , etherType = 0x0806+ , etherData = encode msg+ }+ output (sendEthernet eth frame)++advanceArpTable :: Arp ()+advanceArpTable = do+ now <- time+ state <- get+ let (table', timedOut) = stepArpTable now (arpTable state)+ set state { arpTable = table' }+ forM_ timedOut $ \ x -> runWaiting x Nothing++-- | Handle a who-has request+whoHas :: IP4 -> (Maybe Mac -> IO ()) -> Arp ()+whoHas ip k = (k' =<< localHwAddress ip) `mplus` query+ where+ k' addr = output (k (Just addr))++ query = do+ advanceArpTable+ state <- get+ case lookupArpEntry ip (arpTable state) of+ KnownAddress mac -> k' mac+ Pending -> addWaiter ip k+ Unknown -> do+ let addrs = Map.toList (arpAddrs state)+ msg (spa,sha) = ArpPacket+ { arpHwType = 0x1+ , arpPType = 0x0800+ , arpSHA = sha+ , arpSPA = spa+ , arpTHA = Mac 0xff 0xff 0xff 0xff 0xff 0xff+ , arpTPA = ip+ , arpOper = ArpRequest+ }+ now <- time+ let table' = addPending now ip (arpTable state)+ set state { arpTable = table' }+ addWaiter ip k+ mapM_ (sendArpPacket . msg) addrs+ th <- timerHandle+ output (delay th 10000 (send (arpSelf state) advanceArpTable))++-- Message Handling ------------------------------------------------------------++-- | Process an incoming arp packet+handleIncoming :: Packet -> Arp ()+handleIncoming bs = do+ msg <- liftRight (decode bs)+ -- ?Do I have the hardware type in ar$hrd+ -- Yes: (This check is enforced by the type system)+ -- [optionally check the hardware length ar$hln]+ -- ?Do I speak the protocol in ar$pro?+ -- Yes: (This check is also enforced by the type system)+ -- [optionally check the protocol length ar$pln]+ -- Merge_flag := false+ -- If the pair <protocol type, sender protocol address> is+ -- already in my translation table, update the sender+ -- hardware address field of the entry with the new+ -- information in the packet and set Merge_flag to true. + let sha = arpSHA msg+ let spa = arpSPA msg+ merge <- updateExistingEntry spa sha+ -- ?Am I the target protocol address?+ let tpa = arpTPA msg+ lha <- localHwAddress tpa+ -- Yes:+ -- If Merge_flag is false, add the triplet <protocol type,+ -- sender protocol address, sender hardware address> to+ -- the translation table.+ unless merge (addEntry spa sha)+ -- ?Is the opcode ares_op$REQUEST? (NOW look at the opcode!!)+ -- Yes:+ when (arpOper msg == ArpRequest) $ do+ -- Swap hardware and protocol fields, putting the local+ -- hardware and protocol addresses in the sender fields.+ let msg' = msg { arpSHA = lha , arpSPA = tpa+ , arpTHA = sha , arpTPA = spa+ -- Set the ar$op field to ares_op$REPLY+ , arpOper = ArpReply }+ -- Send the packet to the (new) target hardware address on+ -- the same hardware on which the request was received.+ sendArpPacket msg'+++-- | Handle a request to associate an ip with a mac address for a local device+handleAddAddress :: IP4 -> Mac -> Arp ()+handleAddAddress ip mac = do+ state <- get+ let addrs' = Map.insert ip mac (arpAddrs state)+ addrs' `seq` set state { arpAddrs = addrs' }+++-- | Output a packet to the ethernet layer.+handleOutgoing :: IP4 -> IP4 -> Packet -> Arp ()+handleOutgoing src dst bs = do+ eth <- ethernetHandle+ lha <- localHwAddress src+ let frame dha = EthernetFrame+ { etherDest = dha+ , etherSource = lha+ , etherType = 0x0800+ , etherData = bs+ }+ whoHas dst $ \ res -> case res of+ Nothing -> return ()+ Just dha -> sendEthernet eth (frame dha)
+ src/Hans/Layer/Arp/Table.hs view
@@ -0,0 +1,59 @@+module Hans.Layer.Arp.Table where++import Hans.Address.Mac+import Hans.Address.IP4++import Control.Arrow (second)+import Data.Time.Clock.POSIX (POSIXTime)+import qualified Data.Map as Map+++-- Arp Table -------------------------------------------------------------------++arpEntryTimeout :: POSIXTime+arpEntryTimeout = 60++data ArpEntry+ = ArpEntry { arpMac :: Mac+ , arpTimeout :: POSIXTime+ }+ | ArpPending { arpTimeout :: POSIXTime+ }+ deriving Show++data ArpResult+ = KnownAddress Mac+ | Pending+ | Unknown++type ArpTable = Map.Map IP4 ArpEntry++stepArpTable :: POSIXTime -> ArpTable -> (ArpTable, [IP4])+stepArpTable now tab = second (Map.keys) (Map.partition p tab)+ where+ p ent = arpTimeout ent >= now++addArpEntry :: POSIXTime -> IP4 -> Mac -> ArpTable -> ArpTable+addArpEntry now ip mac = Map.insert ip ent where+ ent = ArpEntry+ { arpMac = mac+ , arpTimeout = now + arpEntryTimeout+ }++-- | Assumption: there is not already a pending ARP query recorded in the+-- ARP table for the given IP address.+addPending :: POSIXTime -> IP4 -> ArpTable -> ArpTable+addPending now ip = Map.insert ip ent where+ ent = ArpPending+ { arpTimeout = now + arpEntryTimeout -- FIXME: should queries stay longer?+ }++-- | If the ARP table has a fully realized entry for the given IP address,+-- then return it. Otherwise return Pending if we're waiting for this info,+-- or Unknown if nothing is currently known about it.+lookupArpEntry :: IP4 -> ArpTable -> ArpResult+lookupArpEntry ip arp =+ case Map.lookup ip arp of+ Just (ArpEntry mac _) -> KnownAddress mac+ Just (ArpPending _) -> Pending+ _ -> Unknown
+ src/Hans/Layer/Ethernet.hs view
@@ -0,0 +1,182 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE TypeSynonymInstances #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE FlexibleContexts #-}++module Hans.Layer.Ethernet (+ EthernetHandle+ , runEthernetLayer++ -- * External Interface+ , Tx+ , Rx+ , sendEthernet+ , queueEthernet+ , addEthernetDevice+ , removeEthernetDevice+ , addEthernetHandler+ , removeEthernetHandler+ , startEthernetDevice+ , stopEthernetDevice+ ) where++import Hans.Address.Mac+import Hans.Channel+import Hans.Layer+import Hans.Message.EthernetFrame+import Hans.Utils (Packet,void,just)++import Control.Concurrent (forkIO,ThreadId,killThread)+import Control.Monad (mplus)+import Data.Serialize (decode,encode)+import MonadLib (get,set)+import qualified Data.Map as Map+++-- Messages --------------------------------------------------------------------++type Handler = Packet -> IO ()++type Tx = Packet -> IO ()+type Rx = EthernetHandle -> IO ()++type EthernetHandle = Channel (Eth ())++-- | Run the ethernet layer.+runEthernetLayer :: EthernetHandle -> IO ()+runEthernetLayer h =+ void (forkIO (loopLayer (emptyEthernetState h) (receive h) id))++sendEthernet :: EthernetHandle -> EthernetFrame -> IO ()+sendEthernet h !frame = send h (handleOutgoing frame)++queueEthernet :: EthernetHandle -> Packet -> IO ()+queueEthernet h !pkt = send h (handleIncoming pkt)++startEthernetDevice :: EthernetHandle -> Mac -> IO ()+startEthernetDevice h !m = send h (startDevice m)++stopEthernetDevice :: EthernetHandle -> Mac -> IO ()+stopEthernetDevice h !m = send h (stopDevice m)++addEthernetDevice :: EthernetHandle -> Mac -> Tx -> Rx -> IO ()+addEthernetDevice h !mac tx rx = send h (addDevice mac tx rx)++removeEthernetDevice :: EthernetHandle -> Mac -> IO ()+removeEthernetDevice h !mac = send h (delDevice mac)++addEthernetHandler :: EthernetHandle -> EtherType -> Handler -> IO ()+addEthernetHandler h !et k = send h (addHandler et k)++removeEthernetHandler :: EthernetHandle -> EtherType -> IO ()+removeEthernetHandler h !et = send h (removeHandler et)+++-- Ethernet Message Monad ------------------------------------------------------++data EthernetDevice = EthernetDevice+ { devTx :: Tx+ , devRx :: IO ()+ , devUp :: Maybe ThreadId+ }++emptyDevice :: Tx -> IO () -> EthernetDevice+emptyDevice tx rx = EthernetDevice+ { devTx = tx+ , devRx = rx+ , devUp = Nothing+ }+++type Eth = Layer EthernetState++data EthernetState = EthernetState+ { ethHandlers :: Handlers EtherType Handler+ , ethDevices :: Map.Map Mac EthernetDevice+ , ethHandle :: EthernetHandle+ }++instance ProvidesHandlers EthernetState EtherType Handler where+ getHandlers = ethHandlers+ setHandlers hs i = i { ethHandlers = hs }++emptyEthernetState :: EthernetHandle -> EthernetState+emptyEthernetState h = EthernetState+ { ethHandlers = emptyHandlers+ , ethDevices = Map.empty+ , ethHandle = h+ }++self :: Eth EthernetHandle+self = ethHandle `fmap` get++-- Message Handling ------------------------------------------------------------++-- | Handle an incoming packet, from a device.+handleIncoming :: Packet -> Eth ()+handleIncoming pkt = do+ frame <- liftRight (decode pkt)+ h <- getHandler (etherType frame)+ output (h (etherData frame))+++-- | Get the device associated with a mac address.+getDevice :: Mac -> Eth EthernetDevice+getDevice mac = do+ state <- get+ just (Map.lookup mac (ethDevices state))+++-- | Set the device associated with a mac address.+setDevice :: Mac -> EthernetDevice -> Eth ()+setDevice mac dev = do+ state <- get+ let ds' = Map.insert mac dev (ethDevices state)+ ds' `seq` set state { ethDevices = ds' }+++-- | Send an outgoing ethernet frame via the device that it's associated with.+handleOutgoing :: EthernetFrame -> Eth ()+handleOutgoing frame = do+ dev <- getDevice (etherSource frame)+ output (devTx dev (encode frame))+++-- | Add an ethernet device to the state.+addDevice :: Mac -> Tx -> Rx -> Eth ()+addDevice mac tx rx = do+ stopDevice mac `mplus` return ()+ h <- self+ setDevice mac (emptyDevice tx (rx h))+++-- | Remove a device+delDevice :: Mac -> Eth ()+delDevice mac = do+ stopDevice mac+ state <- get+ let ds' = Map.delete mac (ethDevices state)+ ds' `seq` set state { ethDevices = ds' }+++-- | Stop an ethernet device.+stopDevice :: Mac -> Eth ()+stopDevice mac = do+ dev <- getDevice mac+ case devUp dev of+ Nothing -> return ()+ Just tid -> do+ output (killThread tid)+ setDevice mac dev { devUp = Nothing }+++-- | Start an ethernet device.+startDevice :: Mac -> Eth ()+startDevice mac = do+ dev <- getDevice mac+ case devUp dev of+ Just _ -> return ()+ -- XXX: add functionality to pipe the threadid back into the layer state.+ Nothing -> output (void (forkIO (devRx dev)))+ --setDevice mac dev { devUp = Just tid }
+ src/Hans/Layer/IP4.hs view
@@ -0,0 +1,220 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE TypeSynonymInstances #-}+{-# LANGUAGE FlexibleInstances #-}++module Hans.Layer.IP4 (+ IP4Handle+ , runIP4Layer+ , Rule(..)++ -- * External Interface+ , connectEthernet+ , withIP4Source+ , sendIP4Packet+ , addIP4RoutingRule+ , addIP4Handler+ , removeIP4Handler+ ) where++import Hans.Address+import Hans.Address.IP4+import Hans.Channel+import Hans.Layer+import Hans.Layer.Arp+import Hans.Layer.Ethernet+import Hans.Layer.IP4.Fragmentation+import Hans.Layer.IP4.Routing+import Hans.Message.EthernetFrame+import Hans.Message.Ip4+import Hans.Utils+import Hans.Utils.Checksum++import Control.Concurrent (forkIO)+import Control.Monad (guard,mplus,(<=<))+import Data.Serialize.Get (runGet)+import MonadLib (get,set)+import qualified Data.ByteString as S+++type Handler = IP4 -> IP4 -> Packet -> IO ()++type IP4Handle = Channel (IP ())++runIP4Layer :: IP4Handle -> ArpHandle -> EthernetHandle -> IO ()+runIP4Layer h arp eth = do+ void (forkIO (loopLayer (emptyIP4State arp) (receive h) id))+ connectEthernet h eth++connectEthernet :: IP4Handle -> EthernetHandle -> IO ()+connectEthernet h eth =+ addEthernetHandler eth (EtherType 0x0800) (send h . handleIncoming)++withIP4Source :: IP4Handle -> IP4 -> (IP4 -> IO ()) -> IO ()+withIP4Source h !dst k = send h (handleSource dst k)++addIP4RoutingRule :: IP4Handle -> Rule IP4Mask IP4 -> IO ()+addIP4RoutingRule h !rule = send h (handleAddRule rule)++sendIP4Packet :: IP4Handle -> IP4Protocol -> IP4 -> Packet -> IO ()+sendIP4Packet h !prot !dst !pkt = send h (handleOutgoing prot dst pkt)++addIP4Handler :: IP4Handle -> IP4Protocol -> Handler -> IO ()+addIP4Handler h !prot k = send h (addHandler prot k)++removeIP4Handler :: IP4Handle -> IP4Protocol -> IO ()+removeIP4Handler h !prot = send h (removeHandler prot)++-- IP4 State -------------------------------------------------------------------++type IP = Layer IP4State++data IP4State = IP4State+ { ip4Fragments :: FragmentationTable IP4+ , ip4Routes :: RoutingTable IP4+ , ip4Handlers :: Handlers IP4Protocol Handler+ , ip4NextIdent :: Ident+ , ip4ArpHandle :: ArpHandle+ }++instance ProvidesHandlers IP4State IP4Protocol Handler where+ getHandlers = ip4Handlers+ setHandlers hs i = i { ip4Handlers = hs }+++emptyIP4State :: ArpHandle -> IP4State+emptyIP4State arp = IP4State+ { ip4Fragments = emptyFragmentationTable+ , ip4Routes = emptyRoutingTable+ , ip4Handlers = emptyHandlers+ , ip4NextIdent = 0+ , ip4ArpHandle = arp+ }+++-- IP4 Utilities ---------------------------------------------------------------++arpHandle :: IP ArpHandle+arpHandle = ip4ArpHandle `fmap` get++sendBytes :: IP4Protocol -> IP4 -> Packet -> IP ()+sendBytes prot dst bs = do+ rule@(src,_,mtu) <- findRoute dst+ let hdr = emptyIP4Header prot src dst+ hdr' <- if fromIntegral (S.length bs) + 20 < mtu+ then return hdr+ else do+ i <- nextIdent+ return (setIdent i hdr)+ sendPacket' (IP4Packet hdr' bs) rule++sendPacket :: IP4Packet -> IP ()+sendPacket pkt = do+ rule@(src,_,_) <- findRoute (ip4DestAddr (ip4Header pkt))+ guard (src /= ip4SourceAddr (ip4Header pkt))+ sendPacket' pkt rule++-- | Send a packet using a given routing rule+sendPacket' :: IP4Packet -> (IP4,IP4,Mtu) -> IP ()+sendPacket' pkt (src,dst,mtu) = do+ arp <- arpHandle+ output $ do+ let frags = splitPacket mtu pkt+ mapM_ (arpIP4Packet arp src dst <=< renderIP4Packet) frags+++-- | Find a route to an address+findRoute :: IP4 -> IP (IP4,IP4,Mtu)+findRoute addr = do+ state <- get+ just (route addr (ip4Routes state))+++-- | Route a packet that is forwardable+forward :: IP4Packet -> IP ()+forward pkt = sendPacket pkt+++-- | Require that an address is local.+localAddress :: IP4 -> IP ()+localAddress ip = do+ state <- get+ guard (ip `elem` localAddrs (ip4Routes state))+++findSourceMask :: IP4 -> IP IP4Mask+findSourceMask ip = do+ state <- get+ just (sourceMask ip (ip4Routes state))+++broadcastDestination :: IP4 -> IP ()+broadcastDestination ip = do+ mask <- findSourceMask ip+ guard (isBroadcast mask ip)++-- | Route a message to a local handler+routeLocal :: IP4Packet -> IP ()+routeLocal pkt@(IP4Packet hdr _) = do+ let dest = ip4DestAddr hdr+ localAddress dest `mplus` broadcastDestination dest+ h <- getHandler (ip4Protocol hdr)+ mb <- handleFragments pkt+ case mb of+ Nothing -> return ()+ Just bs -> output (h (ip4SourceAddr hdr) (ip4DestAddr hdr) bs)+++handleFragments :: IP4Packet -> IP (Maybe Packet)+handleFragments pkt = do+ state <- get+ now <- time+ let (table',mb) = processIP4Packet now (ip4Fragments state) pkt+ table' `seq` set state { ip4Fragments = table' }+ return mb++nextIdent :: IP Ident+nextIdent = do+ state <- get+ let i = ip4NextIdent state+ set state { ip4NextIdent = i + 1 }+ return i++-- Message Handling ------------------------------------------------------------++-- | Incoming packet from the network+handleIncoming :: Packet -> IP ()+handleIncoming bs = do+ (hdr,hlen,plen) <- liftRight (runGet parseIP4Packet bs)+ let (header,rest) = S.splitAt hlen bs+ let payload = S.take plen rest+ let checksum = computeChecksum 0 header+ let pkt = IP4Packet hdr payload+ guard $ and+ [ S.length bs >= 20+ , hlen >= 20+ , checksum == 0+ , ip4Version hdr == 4+ ]++ -- forward?+ routeLocal pkt `mplus` forward pkt+++-- | Outgoing packet+handleOutgoing :: IP4Protocol -> IP4 -> Packet -> IP ()+handleOutgoing prot dst bs = do+ sendBytes prot dst bs+++handleAddRule :: Rule IP4Mask IP4 -> IP ()+handleAddRule rule = do+ state <- get+ let routes' = addRule rule (ip4Routes state)+ routes' `seq` set state { ip4Routes = routes' }+++handleSource :: IP4 -> (IP4 -> IO ()) -> IP ()+handleSource dst k = do+ (s,_,_) <- findRoute dst+ output (k s)
+ src/Hans/Layer/IP4/Fragmentation.hs view
@@ -0,0 +1,111 @@++module Hans.Layer.IP4.Fragmentation where++import Hans.Address+import Hans.Address.IP4+import Hans.Message.Ip4+import Hans.Utils++import Data.Ord (comparing)+import Data.Time.Clock.POSIX (POSIXTime)+import qualified Data.ByteString as S+import qualified Data.Map as Map+++type FragmentationTable addr = Map.Map (Ident,addr,addr) Fragments++emptyFragmentationTable :: FragmentationTable IP4+emptyFragmentationTable = Map.empty+++data Fragments = Fragments+ { startTime :: !POSIXTime+ , totalSize :: !Int+ , fragments :: [Fragment]+ } deriving Show++data Fragment = Fragment+ { fragmentOffset :: !Int+ , fragmentLength :: !Int+ , fragmentPayload :: !Packet+ } deriving (Eq,Show)++instance Ord Fragment where+ compare = comparing fragmentOffset+++-- | The end of a fragment.+fragmentEnd :: Fragment -> Int+fragmentEnd f = fragmentOffset f + fragmentLength f++-- | Check the ordering of two fragments.+comesBefore :: Fragment -> Fragment -> Bool+comesBefore f g = fragmentEnd f == fragmentOffset g++-- | Check the ordering of two fragments.+comesAfter :: Fragment -> Fragment -> Bool+comesAfter = flip comesBefore++-- | Merge two fragments.+--+-- Note: This doesn't do a validity check to make sure that they're actually+-- adjacent.+combineFragments :: Fragment -> Fragment -> Fragment+combineFragments f g = Fragment (fragmentOffset f) len pay+ where+ len = fragmentLength f + fragmentLength g+ pay = fragmentPayload f `S.append` fragmentPayload g+++-- | Given a group of fragments, a new fragment, and a possible total size,+-- create a new group of fragments that incorporates the new fragment.+expandGroup :: Fragments -> Fragment -> Int -> Fragments+expandGroup fs newfrag x = case totalSize fs of+ -1 | x >= 0 -> expandGroup fs{ totalSize = x } newfrag x+ _ -> fs { fragments = addFragment newfrag (fragments fs) }+++-- | Add a fragment to a list of fragments, in a position that is relative to+-- its offset and length.+addFragment :: Fragment -> [Fragment] -> [Fragment]+addFragment f fs = case fs of+ [] -> [f]+ g:rest | f `comesBefore` g -> addFragment (combineFragments f g) rest+ | f `comesAfter` g -> addFragment (combineFragments g f) rest+ | f < g -> f:fs+ | otherwise -> g:(addFragment f rest)+++-- | Process a packet fragment through the system, potentially returning a+-- fully-processed packet if this fragment completes an existing packet or+-- is itself a fully-complete packet.+processFragment :: Address addr+ => POSIXTime -> FragmentationTable addr -> Bool -> Int+ -> addr -> addr -> Ident -> Packet+ -> (FragmentationTable addr, Maybe Packet)+processFragment _ table False 0 _ _ _ bs = (table, Just bs)+processFragment now table areMore off src dest ident bs = case group of+ Fragments _ x [Fragment 0 y bs']+ | x == y -> (Map.delete entry table, Just bs')+ _ -> (Map.insert entry group table, Nothing)+ where+ entry = (ident,src,dest)+ group = case Map.lookup (ident,src,dest) table of+ Nothing -> Fragments now newTotalLen [cur]+ Just g -> expandGroup g cur newTotalLen+ curlen = fromIntegral (S.length bs)+ cur = Fragment off curlen bs+ newTotalLen | areMore = -1+ | otherwise = off + curlen+++processIP4Packet :: POSIXTime -> FragmentationTable IP4 -> IP4Packet+ -> (FragmentationTable IP4, Maybe Packet)+processIP4Packet now table (IP4Packet hdr bs) =+ processFragment now table areMore off src dest ident bs+ where+ off = fromIntegral (ip4FragmentOffset hdr)+ ident = fromIntegral (ip4Ident hdr)+ areMore = ip4MoreFragments hdr+ src = ip4SourceAddr hdr+ dest = ip4DestAddr hdr
+ src/Hans/Layer/IP4/Routing.hs view
@@ -0,0 +1,87 @@+module Hans.Layer.IP4.Routing (+ -- * Routing Rules+ Rule(..)+ , Mtu++ -- * Routing Table+ , RoutingTable+ , emptyRoutingTable+ , addRule+ , route+ , localAddrs+ , sourceMask+ ) where++import Data.PrefixTree as PT+import Hans.Address.IP4+import Hans.Address++import Data.Maybe (mapMaybe)+++-- Routing Rules ---------------------------------------------------------------++type Mtu = Int++data Rule mask addr+ = Direct mask addr Mtu+ | Indirect mask addr+ deriving Show+++-- Routing Table ---------------------------------------------------------------++type RoutingTable addr = PrefixTree (Dest addr)++data Dest addr+ = NextHop addr+ | Via addr Mtu+ deriving Show+++emptyRoutingTable :: Address addr => RoutingTable addr+emptyRoutingTable = PT.empty+++{-# SPECIALIZE addRule :: Rule IP4Mask IP4 -> RoutingTable IP4+ -> RoutingTable IP4 #-}+-- | Add a rule to the routing table.+addRule :: Mask mask addr+ => Rule mask addr -> RoutingTable addr -> RoutingTable addr+addRule rule table = case rule of+ Direct mask addr mtu -> k mask (Via addr mtu)+ Indirect mask addr -> k mask (NextHop addr)+ where+ k m d = insert ks d table+ where+ (addr,bits) = getMaskComponents m+ ks = take bits (toBits addr)+++{-# SPECIALIZE route :: IP4 -> RoutingTable IP4 -> Maybe (IP4,IP4,Mtu) #-}+-- | Discover the source and destination when trying to route an address.+route :: Address addr => addr -> RoutingTable addr -> Maybe (addr,addr,Mtu)+route addr t = do+ r <- match (toBits addr) t+ case r of+ Via s mtu -> return (s,addr,mtu)+ NextHop hop -> do+ Via s mtu <- match (toBits hop) t+ return (s,hop,mtu)+++{-# SPECIALIZE sourceMask :: IP4 -> RoutingTable IP4 -> Maybe IP4Mask #-}+-- | Find the mask that would be used to route an address.+sourceMask :: Mask mask addr => addr -> RoutingTable addr -> Maybe mask+sourceMask addr table = do+ src <- key (toBits addr) table+ let bits = fromIntegral (addrSize addr * 8) - length src+ return (addr `withMask` bits)+++-- | Dump all local addresses.+localAddrs :: Address addr => RoutingTable addr -> [addr]+localAddrs table = mapMaybe p (PT.elems table)+ where+ p (Via s _) = Just s+ p _ = Nothing
+ src/Hans/Layer/Icmp4.hs view
@@ -0,0 +1,88 @@+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE TypeSynonymInstances #-}+{-# LANGUAGE FlexibleInstances #-}++module Hans.Layer.Icmp4 (+ Icmp4Handle+ , runIcmp4Layer+ , addIcmp4Handler+ ) where++import Hans.Address.IP4+import Hans.Channel+import Hans.Layer+import Hans.Layer.IP4+import Hans.Message.Icmp4+import Hans.Message.Ip4+import Hans.Utils++import Control.Concurrent (forkIO)+import Data.Serialize (decode,encode)+import MonadLib (get,set)++type Handler = Icmp4Packet -> IO ()++type Icmp4Handle = Channel (Icmp4 ())++icmpProtocol :: IP4Protocol+icmpProtocol = IP4Protocol 0x1++runIcmp4Layer :: Icmp4Handle -> IP4Handle -> IO ()+runIcmp4Layer h ip4 = do+ let handles = Icmp4Handles ip4 []+ addIP4Handler ip4 icmpProtocol+ $ \src dst bs -> send h (handleIncoming src dst bs)+ void (forkIO (loopLayer handles (receive h) id))++data Icmp4Handles = Icmp4Handles+ { icmpIp4 :: IP4Handle+ , icmpHandlers :: [Handler]+ }++type Icmp4 = Layer Icmp4Handles++ip4Handle :: Icmp4 IP4Handle+ip4Handle = icmpIp4 `fmap` get++sendPacket :: IP4 -> Icmp4Packet -> Icmp4 ()+sendPacket dst pkt = do+ ip4 <- ip4Handle+ output $ sendIP4Packet ip4 icmpProtocol dst (encode pkt)++-- | Add a handler for Icmp4 messages that match the provided predicate.+addIcmp4Handler :: Icmp4Handle -> Handler -> IO ()+addIcmp4Handler h k = send h (handleAdd k)++-- Message Handling ------------------------------------------------------------++-- | Handle incoming ICMP packets+handleIncoming :: IP4 -> IP4 -> Packet -> Icmp4 ()+handleIncoming src _dst bs = do+ pkt <- liftRight (decode bs)+ matchHandlers pkt+ case pkt of+ -- XXX: Only echo-request is handled at the moment+ Echo ident seqNum dat -> handleEchoRequest src ident seqNum dat+ _ty -> do+ --output (putStrLn ("Unhandled ICMP message type: " ++ show ty))+ dropPacket+++-- | Add an icmp packet handler.+handleAdd :: Handler -> Icmp4 ()+handleAdd k = do+ s <- get+ set s { icmpHandlers = k : icmpHandlers s }+++-- | Respond to an echo request+handleEchoRequest :: IP4 -> Identifier -> SequenceNumber -> Packet -> Icmp4 ()+handleEchoRequest src ident seqNum dat = do+ sendPacket src (EchoReply ident seqNum dat)+++-- | Output the IO actions for each handler that's registered.+matchHandlers :: Icmp4Packet -> Icmp4 ()+matchHandlers pkt = do+ s <- get+ output (mapM_ ($ pkt) (icmpHandlers s))
+ src/Hans/Layer/Tcp.hs view
@@ -0,0 +1,49 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE DeriveDataTypeable #-}++module Hans.Layer.Tcp (+ TcpHandle+ , runTcpLayer+ , queueTcp++ , module Exports+ ) where++import Hans.Address.IP4+import Hans.Channel+import Hans.Layer+import Hans.Layer.IP4+import Hans.Layer.Tcp.Monad (Tcp,TcpHandle,TcpState(..),emptyTcpState)+import Hans.Layer.Tcp.Socket as Exports+import Hans.Layer.Timer (TimerHandle,udelay)+import Hans.Message.Tcp (tcpProtocol)+import Hans.Layer.Tcp.Handlers (handleIncomingTcp,handleOutgoing)+import Hans.Utils (void)++import Network.TCP.Type.Base (posixtime_to_time)+import Network.TCP.Type.Socket (update_host_time)++import Control.Concurrent (forkIO)+import MonadLib (get,set)+import qualified Data.ByteString as S+++runTcpLayer :: TcpHandle -> IP4Handle -> TimerHandle -> IO ()+runTcpLayer tcp ip4 t = do+ let s0 = emptyTcpState tcp ip4 t+ void (forkIO (loopLayer s0 (receive tcp) updateTimeAndRun))+ addIP4Handler ip4 tcpProtocol (queueTcp tcp)++-- | Queue a tcp packet.+queueTcp :: TcpHandle -> IP4 -> IP4 -> S.ByteString -> IO ()+queueTcp tcp !src !dst !bs = send tcp (handleIncomingTcp src dst bs)++-- | Pull the time out of the Layer monad, and convert it to a value that can be+-- used with the TCP layer.+updateTimeAndRun :: Tcp () -> Tcp ()+updateTimeAndRun body = do+ now <- time+ s <- get+ set $! s { tcpHost = update_host_time (posixtime_to_time now) (tcpHost s) }+ body+ handleOutgoing
+ src/Hans/Layer/Tcp/Handlers.hs view
@@ -0,0 +1,74 @@+module Hans.Layer.Tcp.Handlers (+ handleIncomingTcp+ , handleOutgoing+ ) where++import Hans.Address.IP4 (IP4,convertFromWord32)+import Hans.Channel (send)+import Hans.Layer (output,liftRight)+import Hans.Layer.IP4 (sendIP4Packet,withIP4Source)+import Hans.Layer.Tcp.Monad+ (Tcp,TcpState(..),ip4Handle,ip4Handle,ip4Handle,ip4Handle)+import Hans.Layer.Timer (udelay)+import Hans.Message.Tcp+ (tcpProtocol,renderWithTcpChecksumIP4,TcpPacket(..),getTcpPacket+ ,recreateTcpChecksumIP4,TcpHeader(..))++import Network.TCP.LTS.In (tcp_deliver_in_packet)+import Network.TCP.Type.Base (get_ip,bufferchain_collapse,IPAddr(..))+import Network.TCP.Type.Datagram+ (ICMPDatagram(..),UDPDatagram(..),TCPSegment(..),IPMessage(..)+ ,mkTCPSegment)+import Network.TCP.Type.Socket (Host(..))++import Control.Monad (unless,guard)+import Data.Serialize (runGet)+import MonadLib (get,set)+import qualified Data.ByteString as S+++-- | Handle a TCP message from the IP4 layer.+handleIncomingTcp :: IP4 -> IP4 -> S.ByteString -> Tcp ()+handleIncomingTcp src dst bytes = do+ let cs = recreateTcpChecksumIP4 src dst bytes+ pkt@(TcpPacket hdr _body) <- liftRight (runGet getTcpPacket bytes)+ guard (tcpChecksum hdr == cs)+ tcp_deliver_in_packet (mkTCPSegment src dst pkt)++-- | Force packets out of the pure layer.+handleOutgoing :: Tcp ()+handleOutgoing = do+ s <- get+ let h = tcpHost s+ set (s { tcpHost = h { output_queue = [], ready_list = [] } })+ let msgs = output_queue h+ unless (null msgs) (mapM_ deliverIPMessage msgs)+ let ready = ready_list h+ unless (null ready) (mapM_ output ready)++deliverIPMessage :: IPMessage -> Tcp ()+deliverIPMessage msg =+ case msg of+ TCPMessage seg -> deliverTCPSegment seg+ ICMPMessage icmp -> deliverICMPDatagram icmp+ UDPMessage udp -> deliverUDPDatagram udp++deliverTCPSegment :: TCPSegment -> Tcp ()+deliverTCPSegment seg = do+ let hdr = tcp_header seg+ IPAddr dst = get_ip (tcp_dst seg)+ dstAddr = convertFromWord32 dst+ ip4 <- ip4Handle+ output $ withIP4Source ip4 dstAddr $ \ srcAddr -> do+ body <- bufferchain_collapse (tcp_data seg)+ let pkt = renderWithTcpChecksumIP4 srcAddr dstAddr (TcpPacket hdr body)+ sendIP4Packet ip4 tcpProtocol dstAddr pkt++deliverICMPDatagram :: ICMPDatagram -> Tcp ()+deliverICMPDatagram _icmp = do+ output (putStrLn "Ignoring TCP icmp packet")++deliverUDPDatagram :: UDPDatagram -> Tcp ()+deliverUDPDatagram _udp = do+ output (putStrLn "Ignoring TCP udp packet")+
+ src/Hans/Layer/Tcp/Monad.hs view
@@ -0,0 +1,92 @@+module Hans.Layer.Tcp.Monad where++import Hans.Channel+import Hans.Layer+import Hans.Layer.IP4+import Hans.Layer.Timer++import MonadLib+import Network.TCP.Type.Datagram (IPMessage)+import Network.TCP.Type.Socket (Host(..),empty_host,TCPSocket)+import Network.TCP.Type.Syscall (SocketID)+import qualified Data.Map as Map+++-- TCP Monad -------------------------------------------------------------------++type TcpHandle = Channel (Tcp ())++type Tcp = Layer (TcpState (IO ()))++data TcpState t = TcpState+ { tcpSelf :: TcpHandle+ , tcpIP4 :: IP4Handle+ , tcpTimers :: TimerHandle+ , tcpHost :: Host t+ }++emptyTcpState :: TcpHandle -> IP4Handle -> TimerHandle -> TcpState t+emptyTcpState tcp ip4 timer = TcpState+ { tcpSelf = tcp+ , tcpIP4 = ip4+ , tcpTimers = timer+ , tcpHost = empty_host+ }++-- | The handle to this layer.+self :: Tcp TcpHandle+self = tcpSelf `fmap` get++-- | Get the handle to the IP4 layer.+ip4Handle :: Tcp IP4Handle+ip4Handle = tcpIP4 `fmap` get++-- | Get the handle to the Timer layer.+timerHandle :: Tcp TimerHandle+timerHandle = tcpTimers `fmap` get+++-- Compatibility Layer ---------------------------------------------------------++type HMonad t = Layer (TcpState t)++get_host :: HMonad t (Host t)+get_host = tcpHost `fmap` get++put_host :: Host t -> HMonad t ()+put_host h = do+ s <- get+ set $! s { tcpHost = h }++modify_host :: (Host t -> Host t) -> HMonad t ()+modify_host f = do+ h <- get_host+ put_host $! f h++emit_segs :: [IPMessage] -> HMonad t ()+emit_segs segs = modify_host (\h -> h { output_queue = output_queue h ++ segs })++emit_ready :: [t] -> HMonad t ()+emit_ready ts = modify_host (\h -> h { ready_list = ready_list h ++ ts })++has_sock :: SocketID -> HMonad t Bool+has_sock sid = (Map.member sid . sock_map) `fmap` get_host++lookup_sock :: SocketID -> HMonad t (TCPSocket t)+lookup_sock sid = do+ h <- get_host+ case Map.lookup sid (sock_map h) of+ Nothing -> fail "lookup_sock: sid not found"+ Just res -> return res++delete_sock :: SocketID -> HMonad t ()+delete_sock sid =+ modify_host (\h -> h { sock_map = Map.delete sid (sock_map h) } )++update_sock :: SocketID -> (TCPSocket t -> TCPSocket t) -> HMonad t ()+update_sock sid f =+ modify_host (\h -> h { sock_map = Map.adjust f sid (sock_map h) })++insert_sock :: SocketID -> TCPSocket t -> HMonad t ()+insert_sock sid sock = do+ modify_host (\h -> h { sock_map = Map.insert sid sock (sock_map h) })
+ src/Hans/Layer/Tcp/Socket.hs view
@@ -0,0 +1,189 @@+{-# LANGUAGE DeriveDataTypeable #-}++module Hans.Layer.Tcp.Socket (+ -- * Socket Layer+ Socket()+ , SocketError(..)+ , listenPort+ , acceptSocket+ , connect+ , sendSocket+ , closeSocket+ , readBytes+ , readLine+ ) where++import Hans.Address.IP4+import Hans.Channel+import Hans.Layer+import Hans.Layer.Tcp.Monad+import Hans.Message.Tcp (TcpPort(..))++import Network.TCP.LTS.User (tcp_process_user_request)+import Network.TCP.Type.Base+ (IPAddr(..),SocketID,TCPAddr(..))+import Network.TCP.Type.Syscall (SockReq(..),SockRsp(..))++import Control.Exception (throwIO,Exception)+import Control.Concurrent (MVar,newMVar,newEmptyMVar,takeMVar,putMVar)+import Data.Typeable (Typeable)+import qualified Data.ByteString as S+import qualified Data.ByteString.Lazy as L++-- Socket Layer ----------------------------------------------------------------++data Socket = Socket+ { socketTcpHandle :: TcpHandle+ , socketId :: !SocketID+ , socketBuffer :: MVar L.ByteString+ }++data SocketResult a+ = SocketResult a+ | SocketError SocketError++data SocketError+ = ListenError String+ | AcceptError String+ | ConnectError String+ | SendError String+ | RecvError String+ | CloseError String+ deriving (Typeable,Show)++instance Exception SocketError++-- | Block on a socket operation, waiting for the TCP layer to finish an action.+blockResult :: TcpHandle -> (MVar (SocketResult a) -> Tcp ()) -> IO a+blockResult tcp k = do+ var <- newEmptyMVar+ send tcp (k var)+ sr <- takeMVar var+ case sr of+ SocketResult a -> return a+ SocketError se -> throwIO se++-- | Call @output@ if the @Tcp@ action returns a @Just@.+maybeOutput :: Tcp (Maybe (IO ())) -> Tcp ()+maybeOutput body = do+ mb <- body+ case mb of+ Just m -> output m+ Nothing -> return ()++-- | Listen on a port.+listenPort :: TcpHandle -> TcpPort -> IO Socket+listenPort tcp (TcpPort port) = blockResult tcp $ \ res -> do+ let mkError = SocketError . ListenError+ k rsp = case rsp of+ SockNew sid -> do+ buf <- newMVar L.empty+ putMVar res (SocketResult (Socket tcp sid buf))+ SockError err -> putMVar res (mkError err)+ _ -> putMVar res (mkError "Unexpected response")+ maybeOutput (tcp_process_user_request (SockListen port,k))++-- | Accept a client connection on a @Socket@.+acceptSocket :: Socket -> IO Socket+acceptSocket sock = blockResult (socketTcpHandle sock) $ \ res -> do+ let mkError = SocketError . AcceptError+ k rsp = case rsp of+ SockNew sid -> do+ buf <- newMVar L.empty+ putMVar res (SocketResult (Socket (socketTcpHandle sock) sid buf))+ SockError err -> putMVar res (mkError err)+ _ -> putMVar res (mkError "Unexpected response")+ maybeOutput (tcp_process_user_request (SockAccept (socketId sock),k))++-- | Connect to a remote server.+connect :: TcpHandle -> IP4 -> IP4 -> TcpPort -> IO Socket+connect tcp src dst (TcpPort port) = blockResult tcp $ \ res -> do+ let us = IPAddr (convertToWord32 src)+ them = TCPAddr (IPAddr (convertToWord32 dst), port)+ mkError = SocketError . ConnectError+ k rsp = case rsp of+ SockNew sid -> do+ buf <- newMVar L.empty+ putMVar res (SocketResult (Socket tcp sid buf))+ SockError err -> putMVar res (mkError err)+ _ -> putMVar res (mkError "Unexpected response")+ maybeOutput (tcp_process_user_request (SockConnect us them,k))++-- | Send on a @Socket@.+sendSocket :: Socket -> S.ByteString -> IO ()+sendSocket sock bytes = blockResult (socketTcpHandle sock) $ \ res -> do+ let mkError = SocketError . SendError+ k rsp = putMVar res $! case rsp of+ SockOK -> SocketResult ()+ SockError err -> mkError err+ _ -> mkError "Unexpected response"+ maybeOutput (tcp_process_user_request (SockSend (socketId sock) bytes,k))++-- | Receive from a @Socket@.+recvSocket :: Socket -> IO S.ByteString+recvSocket sock = blockResult (socketTcpHandle sock) $ \ res -> do+ let mkError = SocketError . RecvError+ k rsp = putMVar res $! case rsp of+ SockData bs -> SocketResult bs+ SockError err -> mkError err+ _ -> mkError "Unexpected response"+ maybeOutput (tcp_process_user_request (SockRecv (socketId sock),k))++-- | Close a socket.+closeSocket :: Socket -> IO ()+closeSocket sock =+ blockResult (socketTcpHandle sock) $ \ res -> do+ let mkError = SocketError . CloseError+ k rsp = putMVar res $! case rsp of+ SockOK -> SocketResult ()+ SockError err -> mkError err+ _ -> mkError "Unexpected response"+ maybeOutput (tcp_process_user_request (SockClose (socketId sock),k))+++-- Derived Interaction ---------------------------------------------------------++-- | Read n bytes from a @Socket@.+readBytes :: Socket -> Int -> IO S.ByteString+readBytes sock goal = do+ buf <- takeMVar (socketBuffer sock)+ loop buf (fromIntegral (L.length buf))+ where+ loop buf len+ | goal <= len = finish buf+ | otherwise = do+ bytes <- recvSocket sock+ if S.null bytes+ then finish buf+ else loop (buf `L.append` L.fromChunks [bytes]) (len + S.length bytes)++ finish buf = do+ let (as,bs) = L.splitAt (fromIntegral goal) buf+ putMVar (socketBuffer sock) bs+ return (S.concat (L.toChunks as))++-- | Read until a CRLF, LF or CR are read.+readLine :: Socket -> IO S.ByteString+readLine sock = do+ buf <- takeMVar (socketBuffer sock)+ loop False 0 buf+ where+ loop cr ix buf+ | L.null buf = fillBuffer cr ix buf+ | otherwise =+ case L.index buf ix of+ 0x0d -> loop True (ix+1) buf+ 0x0a -> finish (ix+1) buf+ _ | cr -> finish ix buf+ | otherwise -> loop False (ix+1) buf++ fillBuffer cr ix buf = do+ bytes <- recvSocket sock+ if S.null bytes+ then finish ix buf+ else loop cr ix (buf `L.append` L.fromChunks [bytes])++ finish ix buf = do+ let (as,bs) = L.splitAt ix buf+ putMVar (socketBuffer sock) bs+ return (S.concat (L.toChunks as))
+ src/Hans/Layer/Timer.hs view
@@ -0,0 +1,131 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE TypeSynonymInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# OPTIONS -fno-warn-orphans #-}++module Hans.Layer.Timer (+ TimerHandle+ , runTimerLayer++ , Milliseconds+ , delay+ , Microseconds+ , udelay+ ) where++import Hans.Channel++import Control.Concurrent (forkIO,threadDelay,MVar,newMVar,takeMVar,putMVar)+import Data.FingerTree as FT+import Data.Monoid (Monoid(..))+import Data.Time.Clock.POSIX (POSIXTime,getPOSIXTime)+import MonadLib+++-- The Timers Structure --------------------------------------------------------++data Timer a = Timer+ { timerAt :: POSIXTime+ , timerValue :: a+ }++instance Monoid POSIXTime where+ mempty = 0+ mappend a b | a > b = a+ | otherwise = b++instance Measured POSIXTime (Timer a) where+ measure t = timerAt t++type Timers a = FingerTree POSIXTime (Timer a)+++-- | Add an action to happen sometime in the future.+at :: POSIXTime -> a -> Timers a -> Timers a+at fut a ts = as' >< bs+ where+ (as,bs) = runnable fut ts+ as' = as |> Timer fut a+++-- | Partition the actions into runnable and deferred.+runnable :: POSIXTime -> Timers a -> (Timers a, Timers a)+runnable now ts = FT.split (> now) ts+++-- | Step through the timers in a group.+stepTimers :: Timers a -> Maybe (a, Timers a)+stepTimers ts = case viewl ts of+ EmptyL -> Nothing+ r :< rs -> Just (timerValue r,rs)+++-- | Run all timers, when the action is an IO action.+runTimers :: Timers (IO a) -> IO (Timers (IO a))+runTimers ts = do+ now <- getPOSIXTime+ let loop (Just (a,as)) = a >> loop (stepTimers as)+ loop Nothing = return ()+ (rs,rest) = runnable now ts+ loop (stepTimers rs)+ return rest+++-- The External Message Interface ----------------------------------------------++type ActionTimers = Timers (IO ())++-- | Mesages handled by the timer thread.+data TimerMessage = AddTimer POSIXTime (IO ())++-- | A channel to the timer thread.+type TimerHandle = Channel TimerMessage+++-- | Start the timer thread.+runTimerLayer :: TimerHandle -> IO ()+runTimerLayer h = do+ timers <- newMVar FT.empty+ _ <- forkIO (actionHandler timers)+ _ <- forkIO (messageHandler h timers)+ return ()+++type Milliseconds = Int++-- | Add a message to happen after some number of milliseconds.+delay :: TimerHandle -> Milliseconds -> IO () -> IO ()+delay h !off = udelay h (off * 1000)++type Microseconds = Int++-- | Add a message to happen after some number of microseconds.+udelay :: TimerHandle -> Microseconds -> IO () -> IO ()+udelay h !micros k = do+ now <- getPOSIXTime+ -- the granularity for a NominalTimeDiff is 10^-12+ let off = fromIntegral micros / 1000000+ send h (AddTimer (now + off) k)+++-- Internal Loops --------------------------------------------------------------++-- | The delay granularity of the timer layer.+timerStep :: Microseconds+timerStep = 100+++-- | Loop, running available timer actions.+actionHandler :: MVar ActionTimers -> IO ()+actionHandler timers = forever $ do+ threadDelay timerStep+ putMVar timers =<< runTimers =<< takeMVar timers+++-- | Loop, processing timer add requests.+messageHandler :: TimerHandle -> MVar ActionTimers -> IO ()+messageHandler h timers = forever $ do+ AddTimer t k <- receive h+ ts <- takeMVar timers+ putMVar timers $! at t k ts
+ src/Hans/Layer/Udp.hs view
@@ -0,0 +1,127 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE TypeSynonymInstances #-}+{-# LANGUAGE FlexibleInstances #-}++module Hans.Layer.Udp (+ UdpHandle+ , runUdpLayer++ , queueUdp+ , sendUdp+ , addUdpHandler+ , removeUdpHandler+ ) where++import Hans.Address.IP4+import Hans.Channel+import Hans.Layer+import Hans.Layer.IP4+import Hans.Layer.Icmp4+import Hans.Message.Ip4+import Hans.Message.Udp+import Hans.Ports+import Hans.Utils++import Control.Concurrent (forkIO)+import Data.Serialize.Get (runGet)+import MonadLib (get,set)+++type Handler = IP4 -> UdpPort -> Packet -> IO ()++type UdpHandle = Channel (Udp ())++udpProtocol :: IP4Protocol+udpProtocol = IP4Protocol 0x11++runUdpLayer :: UdpHandle -> IP4Handle -> Icmp4Handle -> IO ()+runUdpLayer h ip4 icmp4 = do+ addIP4Handler ip4 udpProtocol (queueUdp h)+ void (forkIO (loopLayer (emptyUdp4State ip4 icmp4) (receive h) id))++sendUdp :: UdpHandle -> IP4 -> Maybe UdpPort -> UdpPort -> Packet -> IO ()+sendUdp h !dst mb !dp !bs = send h (handleOutgoing dst mb dp bs)++queueUdp :: UdpHandle -> IP4 -> IP4 -> Packet -> IO ()+queueUdp h !src !dst !bs = send h (handleIncoming src dst bs)++addUdpHandler :: UdpHandle -> UdpPort -> Handler -> IO ()+addUdpHandler h !sp k = send h (handleAddHandler sp k)++removeUdpHandler :: UdpHandle -> UdpPort -> IO ()+removeUdpHandler h !sp = send h (handleRemoveHandler sp)+++-- Udp State -------------------------------------------------------------------++type Udp = Layer UdpState++data UdpState = UdpState+ { udpPorts :: PortManager UdpPort+ , udpHandlers :: Handlers UdpPort Handler+ , udpIp4Handle :: IP4Handle+ , udpIcmp4Handle :: Icmp4Handle+ }++emptyUdp4State :: IP4Handle -> Icmp4Handle -> UdpState+emptyUdp4State ip4 icmp4 = UdpState+ { udpPorts = emptyPortManager [maxBound, maxBound - 1 .. 1 ]+ , udpHandlers = emptyHandlers+ , udpIp4Handle = ip4+ , udpIcmp4Handle = icmp4+ }++instance ProvidesHandlers UdpState UdpPort Handler where+ getHandlers = udpHandlers+ setHandlers hs s = s { udpHandlers = hs }+++-- Utilities -------------------------------------------------------------------++ip4Handle :: Udp IP4Handle+ip4Handle = udpIp4Handle `fmap` get++--icmp4Handle :: Udp Icmp4Handle+--icmp4Handle = udpIcmp4Handle `fmap` get++maybePort :: Maybe UdpPort -> Udp UdpPort+maybePort (Just p) = return p+maybePort Nothing = do+ state <- get+ (p,pm') <- nextPort (udpPorts state)+ pm' `seq` set state { udpPorts = pm' }+ return p++-- Message Handling ------------------------------------------------------------++handleAddHandler :: UdpPort -> Handler -> Udp ()+handleAddHandler sp k = do+ state <- get+ pm' <- reserve sp (udpPorts state)+ pm' `seq` set state { udpPorts = pm' }+ addHandler sp k++handleRemoveHandler :: UdpPort -> Udp ()+handleRemoveHandler sp = do+ state <- get+ pm' <- unreserve sp (udpPorts state)+ pm' `seq` set state { udpPorts = pm' }+ removeHandler sp+++handleIncoming :: IP4 -> IP4 -> Packet -> Udp ()+handleIncoming src _dst bs = do+ UdpPacket hdr pkt <- liftRight (runGet parseUdpPacket bs)+ h <- getHandler (udpDestPort hdr)+ output (h src (udpSourcePort hdr) pkt)+++handleOutgoing :: IP4 -> Maybe UdpPort -> UdpPort -> Packet -> Udp ()+handleOutgoing dst mb dp bs = do+ sp <- maybePort mb+ ip4 <- ip4Handle+ let udp = UdpPacket (UdpHeader sp dp 0) bs+ output $ withIP4Source ip4 dst $ \ src -> do+ pkt <- renderUdpPacket udp (mkIP4PseudoHeader src dst udpProtocol)+ sendIP4Packet ip4 udpProtocol dst pkt
+ src/Hans/Message/Arp.hs view
@@ -0,0 +1,74 @@+module Hans.Message.Arp where++import Hans.Address++import Data.Serialize (Serialize(..))+import Data.Serialize.Get (getWord8,getWord16be)+import Data.Serialize.Put (putWord16be,putWord8)+import Data.Word (Word16)+++-- Arp Packets -----------------------------------------------------------------++data ArpPacket hw p = ArpPacket+ { arpHwType :: !Word16+ , arpPType :: !Word16+ , arpOper :: ArpOper+ , arpSHA :: hw+ , arpSPA :: p+ , arpTHA :: hw+ , arpTPA :: p+ }++-- | Decode an Arp message.+instance (Address hw, Address p) => Serialize (ArpPacket hw p) where+ get = do+ hty <- getWord16be+ pty <- getWord16be+ _ <- getWord8+ _ <- getWord8+ oper <- get+ sha <- get+ spa <- get+ tha <- get+ tpa <- get+ return $! ArpPacket+ { arpHwType = hty+ , arpPType = pty+ , arpOper = oper+ , arpSHA = sha+ , arpSPA = spa+ , arpTHA = tha+ , arpTPA = tpa+ }++ put msg = do+ putWord16be (arpHwType msg)+ putWord16be (arpPType msg)+ putWord8 (addrSize (arpSHA msg))+ putWord8 (addrSize (arpSPA msg))+ put (arpOper msg)+ put (arpSHA msg)+ put (arpSPA msg)+ put (arpTHA msg)+ put (arpTPA msg)+++-- Arp Opcodes -----------------------------------------------------------------++data ArpOper+ = ArpRequest -- ^ 0x1+ | ArpReply -- ^ 0x2+ deriving (Eq)+++instance Serialize ArpOper where+ get = do+ b <- getWord16be+ case b of+ 0x1 -> return ArpRequest+ 0x2 -> return ArpReply+ _ -> fail "invalid Arp opcode"++ put ArpRequest = putWord16be 0x1+ put ArpReply = putWord16be 0x2
+ src/Hans/Message/Dhcp4.hs view
@@ -0,0 +1,568 @@+{- | The 'Hans.Message.Dhcp4' module defines the various messages and+ transitions used in the DHCPv4 protocol. This module provides both+ a high-level view of the message types as well as a low-level+ intermediate form which is closely tied to the binary format.++ References:+ RFC 2131 - Dynamic Host Configuration Protocol+ http://www.faqs.org/rfcs/rfc2131.html+-}+module Hans.Message.Dhcp4+ (+ -- ** High-level client types+ RequestMessage(..)+ , Request(..)+ , Discover(..)++ -- ** High-level server types+ , ServerSettings(..)+ , ReplyMessage(..)+ , Ack(..)+ , Offer(..)++ -- ** Low-level message types+ , Dhcp4Message(..)+ , Xid(..)++ -- ** Server message transition logic+ , requestToAck+ , discoverToOffer++ -- ** Client message transition logic+ , mkDiscover+ , offerToRequest++ -- ** Convert high-level message types to low-level format+ , requestToMessage+ , ackToMessage+ , offerToMessage+ , discoverToMessage++ -- ** Convert low-level message type to high-level format+ , parseDhcpMessage++ -- ** Convert low-level message type to binary format+ , getDhcp4Message+ , putDhcp4Message++ ) where++import Hans.Address.IP4 (IP4(..))+import Hans.Address.Mac (Mac)+import Hans.Message.Dhcp4Codec+import Hans.Message.Dhcp4Options++import Control.Applicative ((<*), (<$>))+import Control.Monad (unless)+import Data.Bits (testBit,bit)+import Data.Maybe (mapMaybe)+import Data.Serialize.Get (Get, getByteString, isolate, remaining, label, skip)+import Data.Serialize.Put (Put, putByteString)+import Data.Word (Word8,Word16,Word32)+import Numeric (showHex)+import qualified Data.ByteString as BS+import qualified Data.ByteString.Char8 as BS8++-- DHCP Static Server Settings ---------------------------------------------++-- |'ServerSettings' define all of the information that would be needed to+-- act as a DHCP server for one client. The server is defined to be able to+-- issue a single "lease" whose parameters are defined below.+data ServerSettings = Settings+ { staticServerAddr :: IP4 -- ^ The IPv4 address of the DHCP server+ , staticTimeOffset :: Word32 -- ^ Lease: timezone offset in seconds from UTC+ , staticClientAddr :: IP4 -- ^ Lease: client IPv4 address on network+ , staticLeaseTime :: Word32 -- ^ Lease: duration in seconds+ , staticSubnet :: SubnetMask -- ^ Lease: subnet mask on network+ , staticBroadcast :: IP4 -- ^ Lease: broadcast address on network+ , staticRouters :: [IP4] -- ^ Lease: gateway routers on network+ , staticDomainName :: String -- ^ Lease: client's assigned domain name+ , staticDNS :: [IP4] -- ^ Lease: network DNS servers+ }+ deriving (Show)++-- Structured DHCP Messages ------------------------------------------------++-- |'RequestMessage' is a sum of the client request messages.+data RequestMessage = RequestMessage Request+ | DiscoverMessage Discover+ deriving (Show)++-- |'ReplyMessage' is a sum of the server response messages.+data ReplyMessage = AckMessage Ack+ | OfferMessage Offer+ deriving (Show)++-- |'Request' is used by the client to accept an offered lease.+data Request = Request+ { requestXid :: Xid -- ^ Transaction ID of offer+ , requestBroadcast :: Bool -- ^ Set 'True' to instruct server to send to broadcast hardware address+ , requestClientHardwareAddress :: Mac -- ^ Hardware address of the client+ , requestParameters :: [Dhcp4OptionTag] -- ^ Used to specify the information that client needs+ , requestAddress :: Maybe IP4 -- ^ Used to specify the address which was accepted+ }+ deriving (Show)++-- |'Discover' is used by the client to discover what servers are available.+-- This message is sent to the IPv4 broadcast.+data Discover = Discover+ { discoverXid :: Xid -- ^ Transaction ID of this and subsequent messages+ , discoverBroadcast :: Bool -- ^ Set 'True' to instruct the server to send to broadcast hardware address+ , discoverClientHardwareAddr :: Mac -- ^ Hardware address of the client+ , discoverParameters :: [Dhcp4OptionTag]-- ^ Used to specify the information that client needs in the offers+ }+ deriving (Show)++-- |'Ack' is sent by the DHCPv4 server to acknowledge a sucessful 'Request'+-- message. Upon receiving this message the client has completed the+-- exchange and has successfully obtained a lease.+data Ack = Ack+ { ackHops :: Word8 -- ^ The maximum number of relays this message can use.+ , ackXid :: Xid -- ^ Transaction ID for this exchange+ , ackYourAddr :: IP4 -- ^ Lease: assigned client address+ , ackServerAddr :: IP4 -- ^ DHCP server's IPv4 address+ , ackRelayAddr :: IP4 -- ^ DHCP relay server's address+ , ackClientHardwareAddr :: Mac -- ^ Client's hardware address+ , ackLeaseTime :: Word32 -- ^ Lease: duration of lease in seconds+ , ackOptions :: [Dhcp4Option] -- ^ Subset of information requested in previous 'Request'+ }+ deriving (Show)++-- |'Offer' is sent by the DHCPv4 server in response to a 'Discover'.+-- This offer is only valid for a short period of time as the client+-- might receive many offers. The client must next request a lease+-- from a specific server using the information in that server's offer.+data Offer = Offer+ { offerHops :: Word8 -- ^ The maximum number of relays this message can use.+ , offerXid :: Xid -- ^ Transaction ID of this exchange+ , offerYourAddr :: IP4 -- ^ The IPv4 address that this server is willing to lease+ , offerServerAddr :: IP4 -- ^ The IPv4 address of the DHCPv4 server+ , offerRelayAddr :: IP4 -- ^ The IPv4 address of the DHCPv4 relay server+ , offerClientHardwareAddr :: Mac -- ^ The hardware address of the client+ , offerOptions :: [Dhcp4Option] -- ^ The options that this server would include in a lease+ }+ deriving (Show)++-- |'requestToAck' creates 'Ack' messages suitable for responding to 'Request'+-- messages given a static 'ServerSettings' configuration.+requestToAck :: ServerSettings -- ^ DHCPv4 server settings+ -> Request -- ^ Client's request message+ -> Ack+requestToAck settings request = Ack+ { ackHops = 1+ , ackXid = requestXid request+ , ackYourAddr = staticClientAddr settings+ , ackServerAddr = staticServerAddr settings+ , ackRelayAddr = staticServerAddr settings+ , ackClientHardwareAddr = requestClientHardwareAddress request+ , ackLeaseTime = staticLeaseTime settings+ , ackOptions = mapMaybe lookupOption (requestParameters request)+ }+ where+ lookupOption tag = case tag of+ OptTagSubnetMask+ -> Just (OptSubnetMask (staticSubnet settings))+ OptTagBroadcastAddress+ -> Just (OptBroadcastAddress (staticBroadcast settings))+ OptTagTimeOffset+ -> Just (OptTimeOffset (staticTimeOffset settings))+ OptTagRouters+ -> Just (OptRouters (staticRouters settings))+ OptTagDomainName+ -> Just (OptDomainName (NVTAsciiString (staticDomainName settings)))+ OptTagNameServers+ -> Just (OptNameServers (staticDNS settings))+ _ -> Nothing++-- |'discoverToOffer' creates a suitable 'Offer' in response to a client's+-- 'Discover' message using the configuration settings specified in the+-- given 'ServerSettings'.+discoverToOffer :: ServerSettings -- ^ DHCPv4 server settings+ -> Discover -- ^ Client's discover message+ -> Offer+discoverToOffer settings discover = Offer+ { offerHops = 1+ , offerXid = discoverXid discover+ , offerYourAddr = staticClientAddr settings+ , offerServerAddr = staticServerAddr settings+ , offerRelayAddr = staticServerAddr settings+ , offerClientHardwareAddr = discoverClientHardwareAddr discover+ , offerOptions = mapMaybe lookupOption (discoverParameters discover)+ }+ where+ lookupOption tag = case tag of+ OptTagSubnetMask+ -> Just (OptSubnetMask (staticSubnet settings))+ OptTagBroadcastAddress+ -> Just (OptBroadcastAddress (staticBroadcast settings))+ OptTagTimeOffset+ -> Just (OptTimeOffset (staticTimeOffset settings))+ OptTagRouters+ -> Just (OptRouters (staticRouters settings))+ OptTagDomainName+ -> Just (OptDomainName (NVTAsciiString (staticDomainName settings)))+ OptTagNameServers+ -> Just (OptNameServers (staticDNS settings))+ _ -> Nothing++-- Unstructured DHCP Message -----------------------------------------------++-- |'Dhcp4Message' is a low-level message container that is very close to+-- the binary representation of DHCPv4 message. It is suitable for containing+-- any DHCPv4 message. Values of this type should only be created using the+-- publicly exported functions.+data Dhcp4Message = Dhcp4Message+ { dhcp4Op :: Dhcp4Op -- ^ Message op code / message type. 1 = BOOTREQUEST, 2 = BOOTREPLY+ , dhcp4Hops :: Word8 -- ^ Client sets to zero, optionally used by relay agents when booting via a relay agent.+ , dhcp4Xid :: Xid -- ^ Transaction ID, a random number chosen by the client, used by the client and server to associate messages and responses between a client and a server.+ , dhcp4Secs :: Word16 -- ^ Filled in by client, seconds elapsed since client began address acquisition or renewal process.+ , dhcp4Broadcast :: Bool -- ^ Client requests messages be sent to hardware broadcast address+ , dhcp4ClientAddr :: IP4 -- ^ Client IP address; only filled in if client is in BOUND, RENEW or REBINDING state and can respond to ARP requests.+ , dhcp4YourAddr :: IP4 -- ^ 'your' (client) address+ , dhcp4ServerAddr :: IP4 -- ^ IP address of next server to use in bootstrap; returned in DHCPOFFER, DHCPACK by server+ , dhcp4RelayAddr :: IP4 -- ^ Relay agent IP address, used in booting via a relay agent+ , dhcp4ClientHardwareAddr :: Mac -- ^ Client hardware address+ , dhcp4ServerHostname :: String -- ^ Optional server host name, null terminated string+ , dhcp4BootFilename :: String -- ^ Boot file name, full terminated string; "generic" name of null in DHCPDISCOVER, fully qualified directory-path name in DHCPOFFER+ , dhcp4Options :: [Dhcp4Option] -- ^ Optional parameters field.+ } deriving (Eq,Show)++-- |'getDhcp4Message' is the binary decoder for parsing 'Dhcp4Message' values.+getDhcp4Message :: Get Dhcp4Message+getDhcp4Message = do+ op <- getAtom+ hwtype <- getAtom+ len <- getAtom+ unless (len == hardwareTypeAddressLength hwtype)+ (fail "Hardware address length does not match hardware type.")+ hops <- label "hops" getAtom+ xid <- label "xid" getAtom+ secs <- label "secs" getAtom+ flags <- label "flags" getAtom+ ciaddr <- label "ciaddr" getAtom+ yiaddr <- label "yiaddr" getAtom+ siaddr <- label "siaddr" getAtom+ giaddr <- label "giaddr" getAtom+ chaddr <- label "chaddr" $ isolate 16 $ getAtom <* (skip =<< remaining)+ snameBytes <- label "sname field" (getByteString 64)+ fileBytes <- label "file field" (getByteString 128)+ (sname, file, opts) <- getDhcp4Options snameBytes fileBytes+ return $! Dhcp4Message+ { dhcp4Op = op+ , dhcp4Hops = hops+ , dhcp4Xid = xid+ , dhcp4Secs = secs+ , dhcp4Broadcast = broadcastFlag flags+ , dhcp4ClientAddr = ciaddr+ , dhcp4YourAddr = yiaddr+ , dhcp4ServerAddr = siaddr+ , dhcp4RelayAddr = giaddr+ , dhcp4ClientHardwareAddr = chaddr+ , dhcp4ServerHostname = sname+ , dhcp4BootFilename = file+ , dhcp4Options = opts+ }++-- |'getDhcp4Message' is the binary encoder for rendering 'Dhcp4Message' values.+putDhcp4Message :: Dhcp4Message -> Put+putDhcp4Message dhcp = do+ putAtom (dhcp4Op dhcp)+ let hwType = Ethernet+ putAtom hwType+ putAtom (hardwareTypeAddressLength hwType)+ putAtom (dhcp4Hops dhcp)+ putAtom (dhcp4Xid dhcp)+ putAtom (dhcp4Secs dhcp)+ putAtom Flags { broadcastFlag = dhcp4Broadcast dhcp }+ putAtom (dhcp4ClientAddr dhcp)+ putAtom (dhcp4YourAddr dhcp)+ putAtom (dhcp4ServerAddr dhcp)+ putAtom (dhcp4RelayAddr dhcp)+ putAtom (dhcp4ClientHardwareAddr dhcp)+ putByteString $ BS.replicate (16 {- chaddr field length -}+ - fromIntegral (hardwareTypeAddressLength hwType)) 0+ putPaddedByteString 64 (BS8.pack (dhcp4ServerHostname dhcp))+ putPaddedByteString 128 (BS8.pack (dhcp4BootFilename dhcp))+ putDhcp4Options (dhcp4Options dhcp)++-- Transaction ID --------------------------------------------------------------++-- |'Xid' is a Transaction ID, a random number chosen by the client,+-- used by the client and server to associate messages and responses between a+-- client and a server.+newtype Xid = Xid Word32+ deriving (Eq, Show)++instance CodecAtom Xid where+ getAtom = Xid <$> getAtom+ putAtom (Xid xid) = putAtom xid+ atomSize _ = atomSize (0 :: Word32)++-- Opcodes ---------------------------------------------------------------------++data Dhcp4Op+ = BootRequest+ | BootReply+ deriving (Eq,Show)++instance CodecAtom Dhcp4Op where+ getAtom = do+ b <- getAtom+ case b :: Word8 of+ 1 -> return BootRequest+ 2 -> return BootReply+ _ -> fail ("Unknown DHCP op 0x" ++ showHex b "")++ putAtom BootRequest = putAtom (0x1 :: Word8)+ putAtom BootReply = putAtom (0x2 :: Word8)++ atomSize _ = atomSize (0 :: Word8)++-- | HardwareType is an enumeration of the supported hardware types as assigned+-- in the ARP RFC http://www.iana.org/assignments/arp-parameters/+data HardwareType+ = Ethernet+ deriving (Eq, Show)++instance CodecAtom HardwareType where+ getAtom = getAtom >>= \ b -> case b :: Word8 of+ 1 -> return Ethernet+ _ -> fail ("Unsupported hardware type 0x" ++ showHex b "")++ putAtom Ethernet = putAtom (1 :: Word8)++ atomSize _ = atomSize (1 :: Word8)++hardwareTypeAddressLength :: HardwareType -> Word8+hardwareTypeAddressLength Ethernet = 6++-- RFC 2131, Section 2, Page 11:+-- 1 1 1 1 1 1+-- 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5+-- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-++-- |B| MBZ |+-- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-++-- B: BROADCAST flag+-- MBZ: MUST BE ZERO (reserved for future use)+-- Figure 2: Format of the ’flags’ field++data Flags = Flags { broadcastFlag :: Bool }+ deriving (Show, Eq)++instance CodecAtom Flags where+ getAtom = do+ b <- getAtom :: Get Word16+ return Flags { broadcastFlag = testBit b 15 }++ putAtom flags = putAtom $ if broadcastFlag flags then bit 15 :: Word16+ else 0++ atomSize _ = atomSize (0 :: Word16)++putPaddedByteString :: Int -> BS.ByteString -> Put+putPaddedByteString n bs = do+ putByteString $ BS.take n bs+ putByteString $ BS.replicate (n - BS.length bs) 0+++selectKnownTags :: [OptionTagOrError] -> [Dhcp4OptionTag]+selectKnownTags = mapMaybe aux+ where+ aux (KnownTag t) = Just t+ aux _ = Nothing++--+-- Unstructured to Structured logic+--+-- |'parseDhcpMessage' attempts to find a valid high-level message+-- contained in a low-level message. The 'Dhcp4Message' is a large+-- type and can encode invalid combinations of options.+parseDhcpMessage :: Dhcp4Message -> Maybe (Either RequestMessage ReplyMessage)+parseDhcpMessage msg = do+ messageType <- lookupMessageType (dhcp4Options msg)+ case dhcp4Op msg of++ BootRequest -> Left <$> case messageType of++ Dhcp4Request -> RequestMessage <$> do+ params <- lookupParams (dhcp4Options msg)+ let params' = selectKnownTags params+ let addr = lookupRequestAddr (dhcp4Options msg)+ return Request+ { requestXid = dhcp4Xid msg+ , requestBroadcast = dhcp4Broadcast msg+ , requestClientHardwareAddress = dhcp4ClientHardwareAddr msg+ , requestParameters = params'+ , requestAddress = addr+ }++ Dhcp4Discover -> DiscoverMessage <$> do+ params <- lookupParams (dhcp4Options msg)+ let params' = selectKnownTags params+ return Discover+ { discoverXid = dhcp4Xid msg+ , discoverBroadcast = dhcp4Broadcast msg+ , discoverClientHardwareAddr = dhcp4ClientHardwareAddr msg+ , discoverParameters = params'+ }++ _ -> Nothing++ BootReply -> Right <$> case messageType of++ Dhcp4Ack -> AckMessage <$> do+ leaseTime <- lookupLeaseTime (dhcp4Options msg)+ return Ack+ { ackHops = dhcp4Hops msg+ , ackXid = dhcp4Xid msg+ , ackYourAddr = dhcp4YourAddr msg+ , ackServerAddr = dhcp4ServerAddr msg+ , ackRelayAddr = dhcp4RelayAddr msg+ , ackClientHardwareAddr = dhcp4ClientHardwareAddr msg+ , ackLeaseTime = leaseTime+ , ackOptions = dhcp4Options msg+ }++ Dhcp4Offer -> OfferMessage <$> do+ return Offer+ { offerHops = dhcp4Hops msg+ , offerXid = dhcp4Xid msg+ , offerYourAddr = dhcp4YourAddr msg+ , offerServerAddr = dhcp4ServerAddr msg+ , offerRelayAddr = dhcp4RelayAddr msg+ , offerClientHardwareAddr = dhcp4ClientHardwareAddr msg+ , offerOptions = dhcp4Options msg+ }++ _ -> Nothing++--+-- Structured to unstrucured logic+--+-- |'discoverToMessage' embeds 'Discover' messages in the low-level+-- 'Dhcp4Message' type, typically for the purpose of serialization.+discoverToMessage :: Discover -> Dhcp4Message+discoverToMessage discover = Dhcp4Message+ { dhcp4Op = BootRequest+ , dhcp4Hops = 0+ , dhcp4Xid = discoverXid discover+ , dhcp4Secs = 0+ , dhcp4Broadcast = False+ , dhcp4ClientAddr = IP4 0 0 0 0+ , dhcp4YourAddr = IP4 0 0 0 0+ , dhcp4ServerAddr = IP4 0 0 0 0+ , dhcp4RelayAddr = IP4 0 0 0 0+ , dhcp4ClientHardwareAddr = discoverClientHardwareAddr discover+ , dhcp4ServerHostname = ""+ , dhcp4BootFilename = ""+ , dhcp4Options = [ OptMessageType Dhcp4Discover+ , OptParameterRequestList+ $ map KnownTag+ $ discoverParameters discover+ ]+ }++-- |'ackToMessage' embeds 'Ack' messages in the low-level+-- 'Dhcp4Message' type, typically for the purpose of serialization.+ackToMessage :: Ack -> Dhcp4Message+ackToMessage ack = Dhcp4Message+ { dhcp4Op = BootReply+ , dhcp4Hops = ackHops ack+ , dhcp4Xid = ackXid ack+ , dhcp4Secs = 0+ , dhcp4Broadcast = False+ , dhcp4ClientAddr = IP4 0 0 0 0+ , dhcp4YourAddr = ackYourAddr ack+ , dhcp4ServerAddr = ackServerAddr ack+ , dhcp4RelayAddr = ackRelayAddr ack+ , dhcp4ClientHardwareAddr = ackClientHardwareAddr ack+ , dhcp4ServerHostname = ""+ , dhcp4BootFilename = ""+ , dhcp4Options = OptMessageType Dhcp4Ack+ : OptServerIdentifier (ackServerAddr ack)+ : OptIPAddressLeaseTime (ackLeaseTime ack)+ : ackOptions ack+ }++-- |'offerToMessage' embeds 'Offer' messages in the low-level+-- 'Dhcp4Message' type, typically for the purpose of serialization.+offerToMessage :: Offer -> Dhcp4Message+offerToMessage offer = Dhcp4Message+ { dhcp4Op = BootReply+ , dhcp4Hops = offerHops offer+ , dhcp4Xid = offerXid offer+ , dhcp4Secs = 0+ , dhcp4Broadcast = False+ , dhcp4ClientAddr = IP4 0 0 0 0+ , dhcp4YourAddr = offerYourAddr offer+ , dhcp4ServerAddr = offerServerAddr offer+ , dhcp4RelayAddr = offerRelayAddr offer+ , dhcp4ClientHardwareAddr = offerClientHardwareAddr offer+ , dhcp4ServerHostname = ""+ , dhcp4BootFilename = ""+ , dhcp4Options = OptMessageType Dhcp4Offer+ : OptServerIdentifier (offerServerAddr offer)+ : offerOptions offer+ }++-- |'requestToMessage' embeds 'Request' messages in the low-level+-- 'Dhcp4Message' type, typically for the purpose of serialization.+requestToMessage :: Request -> Dhcp4Message+requestToMessage request = Dhcp4Message+ { dhcp4Op = BootRequest+ , dhcp4Hops = 0+ , dhcp4Xid = requestXid request+ , dhcp4Secs = 0+ , dhcp4Broadcast = requestBroadcast request+ , dhcp4ClientAddr = IP4 0 0 0 0+ , dhcp4YourAddr = IP4 0 0 0 0+ , dhcp4ServerAddr = IP4 0 0 0 0+ , dhcp4RelayAddr = IP4 0 0 0 0+ , dhcp4ClientHardwareAddr = requestClientHardwareAddress request+ , dhcp4ServerHostname = ""+ , dhcp4BootFilename = ""+ , dhcp4Options = [ OptMessageType Dhcp4Request+ , OptParameterRequestList+ $ map KnownTag+ $ requestParameters request+ ] ++ maybe [] (\x -> [OptRequestIPAddress x])+ (requestAddress request)+ }++-- |'mkDiscover' creates a new 'Discover' message with a set+-- of options suitable for configuring a basic network stack.+mkDiscover :: Xid -- ^ New randomly generated transaction ID+ -> Mac -- ^ The client's hardware address+ -> Discover+mkDiscover xid mac = Discover+ { discoverXid = xid+ , discoverBroadcast = False+ , discoverClientHardwareAddr = mac+ , discoverParameters = [ OptTagSubnetMask+ , OptTagBroadcastAddress+ , OptTagTimeOffset+ , OptTagRouters+ , OptTagDomainName+ , OptTagNameServers+ , OptTagHostName+ ]+ }++-- |'offerToRequest' creates a 'Request' message suitable for accepting+-- an 'Offer' from the DHCPv4 server.+offerToRequest :: Offer -- ^ The offer as received from the server+ -> Request+offerToRequest offer = Request+ { requestXid = offerXid offer+ , requestBroadcast = False+ , requestClientHardwareAddress = offerClientHardwareAddr offer+ , requestParameters = [ OptTagSubnetMask+ , OptTagBroadcastAddress+ , OptTagTimeOffset+ , OptTagRouters+ , OptTagDomainName+ , OptTagNameServers+ , OptTagHostName+ ]+ , requestAddress = Just (offerYourAddr offer)+ }
+ src/Hans/Message/Dhcp4Codec.hs view
@@ -0,0 +1,90 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+module Hans.Message.Dhcp4Codec where++import Control.Applicative+import Data.List (find)+import qualified Data.Serialize+import Data.Serialize.Get+import Data.Serialize.Put+import Data.Word (Word8, Word16, Word32)++import Hans.Address.IP4 (IP4,IP4Mask)+import Hans.Address.Mac (Mac)+import Hans.Address (Mask(..))++class CodecAtom a where+ getAtom :: Get a+ putAtom :: a -> Put+ atomSize :: a -> Int++instance (CodecAtom a, CodecAtom b) => CodecAtom (a,b) where+ getAtom = (,) <$> getAtom <*> getAtom+ putAtom (a,b) = putAtom a *> putAtom b+ atomSize (a,b)= atomSize a + atomSize b++instance CodecAtom Word8 where+ getAtom = getWord8+ putAtom n = putWord8 n+ atomSize _ = 1++instance CodecAtom Word16 where+ getAtom = getWord16be+ putAtom n = putWord16be n+ atomSize _ = 2++instance CodecAtom Word32 where+ getAtom = getWord32be+ putAtom n = putWord32be n+ atomSize _ = 4++instance CodecAtom Bool where+ getAtom = do b <- getWord8+ case b of+ 0 -> return False+ 1 -> return True+ _ -> fail "Expected 0/1 in boolean option"+ putAtom False = putWord8 0+ putAtom True = putWord8 1+ atomSize _ = 1++instance CodecAtom IP4 where+ getAtom = Data.Serialize.get+ putAtom = Data.Serialize.put+ atomSize _ = 4++instance CodecAtom IP4Mask where+ getAtom = withMask <$> getAtom <*> (unmask <$> getAtom)+ putAtom ip4mask = putAtom addr *> putAtom (SubnetMask mask)+ where (addr, mask) = getMaskComponents ip4mask+ atomSize _ = atomSize (undefined :: IP4)+ + atomSize (undefined :: SubnetMask)++instance CodecAtom Mac where+ getAtom = Data.Serialize.get+ putAtom = Data.Serialize.put+ atomSize _ = 6++-----------------------------------------------------------------------+-- Subnet parser/unparser operations ----------------------------------+-----------------------------------------------------------------------++newtype SubnetMask = SubnetMask { unmask :: Int}+ deriving (Show, Eq)++word32ToSubnetMask :: Word32 -> Maybe SubnetMask+word32ToSubnetMask mask =+ SubnetMask <$> find (\ i -> computeMask i == mask) [0..32]++subnetMaskToWord32 :: SubnetMask -> Word32+subnetMaskToWord32 (SubnetMask n) = computeMask n++computeMask :: Int -> Word32+computeMask n = 0-2^(32-n)++instance CodecAtom SubnetMask where+ getAtom = do x <- getAtom+ case word32ToSubnetMask x of+ Just mask -> return mask+ Nothing -> fail "Invalid subnet mask"+ putAtom = putAtom . subnetMaskToWord32+ atomSize _ = atomSize (undefined :: Word32)
+ src/Hans/Message/Dhcp4Options.hs view
@@ -0,0 +1,869 @@+module Hans.Message.Dhcp4Options where++import Control.Monad (unless)+import Control.Applicative+import Data.Maybe (fromMaybe)+import Data.Foldable (traverse_)+import Data.Traversable (sequenceA)+import Data.Word (Word8, Word16, Word32)+import Data.Serialize.Get+import Data.Serialize.Put+import Data.ByteString (ByteString)+import qualified Data.ByteString as BS+import qualified Data.ByteString.Char8 as BS8+import Numeric (showHex)++import Hans.Address.IP4 (IP4,IP4Mask)+import Hans.Message.Dhcp4Codec++-----------------------------------------------------------------------+-- Magic constants ----------------------------------------------------+-----------------------------------------------------------------------++data MagicCookie = MagicCookie++dhcp4MagicCookie :: Word32+dhcp4MagicCookie = 0x63825363++instance CodecAtom MagicCookie where+ getAtom = do cookie <- getAtom+ unless (cookie == dhcp4MagicCookie)+ (fail "Incorrect magic cookie.")+ return MagicCookie+ putAtom MagicCookie = putAtom dhcp4MagicCookie+ atomSize MagicCookie = atomSize dhcp4MagicCookie+++-----------------------------------------------------------------------+-- DHCP option type and operations ------------------------------------+-----------------------------------------------------------------------++data Dhcp4Option+ = OptSubnetMask SubnetMask+ | OptTimeOffset Word32+ | OptRouters [IP4]+ | OptTimeServers [IP4]+ | OptIEN116NameServers [IP4]+ | OptNameServers [IP4]+ | OptLogServers [IP4]+ | OptCookieServers [IP4]+ | OptLPRServers [IP4]+ | OptImpressServers [IP4]+ | OptResourceLocationServers [IP4]+ | OptHostName NVTAsciiString+ | OptBootFileSize Word16+ | OptMeritDumpFile NVTAsciiString+ | OptDomainName NVTAsciiString+ | OptSwapServer IP4+ | OptRootPath NVTAsciiString+ | OptExtensionsPath NVTAsciiString+ | OptEnableIPForwarding Bool+ | OptEnableNonLocalSourceRouting Bool+ | OptPolicyFilters [IP4Mask]+ | OptMaximumDatagramReassemblySize Word16+ | OptDefaultTTL Word8+ | OptPathMTUAgingTimeout Word32+ | OptPathMTUPlateauTable [Word16]+ | OptInterfaceMTU Word16+ | OptAllSubnetsAreLocal Bool+ | OptBroadcastAddress IP4+ | OptPerformMaskDiscovery Bool+ | OptShouldSupplyMasks Bool+ | OptShouldPerformRouterDiscovery Bool+ | OptRouterSolicitationAddress IP4+ | OptStaticRoutes [(IP4,IP4)]+ | OptShouldNegotiateArpTrailers Bool+ | OptArpCacheTimeout Word32+ | OptUseRFC1042EthernetEncapsulation Bool+ | OptTcpDefaultTTL Word8+ | OptTcpKeepaliveInterval Word32+ | OptTcpKeepaliveUseGarbage Bool+ | OptNisDomainName NVTAsciiString+ | OptNisServers [IP4]+ | OptNtpServers [IP4]+ | OptVendorSpecific ByteString+ | OptNetBiosNameServers [IP4]+ | OptNetBiosDistributionServers [IP4]+ | OptNetBiosNodeType NetBiosNodeType+ | OptNetBiosScope NVTAsciiString+ | OptXWindowsFontServer [IP4]+ | OptXWindowsDisplayManagers [IP4]+ | OptNisPlusDomain NVTAsciiString+ | OptNisPlusServers [IP4]+ | OptSmtpServers [IP4]+ | OptPopServers [IP4]+ | OptNntpServers [IP4]+ | OptWwwServers [IP4]+ | OptFingerServers [IP4]+ | OptIrcServers [IP4]+ | OptStreetTalkServers [IP4]+ | OptStreetTalkDirectoryAssistanceServers [IP4]+ | OptFQDN NVTAsciiString -- RFC 4702+ | OptRequestIPAddress IP4+ | OptIPAddressLeaseTime Word32+ | OptOverload OverloadOption+ | OptTftpServer NVTAsciiString+ | OptBootfileName NVTAsciiString+ | OptMessageType Dhcp4MessageType+ | OptServerIdentifier IP4+ | OptParameterRequestList [OptionTagOrError]+ | OptErrorMessage NVTAsciiString+ | OptMaxDHCPMessageSize Word16+ | OptRenewalTime Word32+ | OptRebindingTime Word32+ | OptVendorClass NVTAsciiString+ | OptClientIdentifier ByteString+ | OptNetWareDomainName NVTAsciiString -- RFC 2242+ | OptNetWareInfo ByteString -- RFC 2242+ | OptAutoconfiguration Bool -- RFC 2563+ deriving (Show,Eq)++getDhcp4Option :: Get (Either ControlTag Dhcp4Option)+getDhcp4Option = do+ mb_tag <- getOptionTag+ case mb_tag of+ UnknownTag t -> do xs <- getBytes =<< remaining+ fail ("getDhcp4Option failed tag (" ++ show t ++ ") " ++ show xs)+ KnownTag tag -> do+ let r con = Right . con <$> getOption+ case tag of+ OptTagPad -> Left <$> pure ControlPad+ OptTagEnd -> Left <$> pure ControlEnd+ OptTagSubnetMask -> r OptSubnetMask+ OptTagTimeOffset -> r OptTimeOffset+ OptTagRouters -> r OptRouters+ OptTagTimeServers -> r OptTimeServers+ OptTagIEN116NameServers -> r OptIEN116NameServers+ OptTagNameServers -> r OptNameServers+ OptTagLogServers -> r OptLogServers+ OptTagCookieServers -> r OptCookieServers+ OptTagLPRServers -> r OptLPRServers+ OptTagImpressServers -> r OptImpressServers+ OptTagResourceLocationServers -> r OptResourceLocationServers+ OptTagHostName -> r OptHostName+ OptTagBootFileSize -> r OptBootFileSize+ OptTagMeritDumpFile -> r OptMeritDumpFile+ OptTagDomainName -> r OptDomainName+ OptTagSwapServer -> r OptSwapServer+ OptTagRootPath -> r OptRootPath+ OptTagExtensionsPath -> r OptExtensionsPath+ OptTagEnableIPForwarding -> r OptEnableIPForwarding+ OptTagEnableNonLocalSourceRouting -> r OptEnableNonLocalSourceRouting+ OptTagPolicyFilters -> r OptPolicyFilters+ OptTagMaximumDatagramReassemblySize -> r OptMaximumDatagramReassemblySize+ OptTagDefaultTTL -> r OptDefaultTTL+ OptTagPathMTUAgingTimeout -> r OptPathMTUAgingTimeout+ OptTagPathMTUPlateauTable -> r OptPathMTUPlateauTable+ OptTagInterfaceMTU -> r OptInterfaceMTU+ OptTagAllSubnetsAreLocal -> r OptAllSubnetsAreLocal+ OptTagBroadcastAddress -> r OptBroadcastAddress+ OptTagPerformMaskDiscovery -> r OptPerformMaskDiscovery+ OptTagShouldSupplyMasks -> r OptShouldSupplyMasks+ OptTagShouldPerformRouterDiscovery -> r OptShouldPerformRouterDiscovery+ OptTagRouterSolicitationAddress -> r OptRouterSolicitationAddress+ OptTagStaticRoutes -> r OptStaticRoutes+ OptTagShouldNegotiateArpTrailers -> r OptShouldNegotiateArpTrailers+ OptTagArpCacheTimeout -> r OptArpCacheTimeout+ OptTagUseRFC1042EthernetEncapsulation -> r OptUseRFC1042EthernetEncapsulation+ OptTagTcpDefaultTTL -> r OptTcpDefaultTTL+ OptTagTcpKeepaliveInterval -> r OptTcpKeepaliveInterval+ OptTagTcpKeepaliveUseGarbage -> r OptTcpKeepaliveUseGarbage+ OptTagNisDomainName -> r OptNisDomainName+ OptTagNisServers -> r OptNisServers+ OptTagNtpServers -> r OptNtpServers+ OptTagVendorSpecific -> r OptVendorSpecific+ OptTagNetBiosNameServers -> r OptNetBiosNameServers+ OptTagNetBiosDistributionServers -> r OptNetBiosDistributionServers+ OptTagNetBiosNodeType -> r OptNetBiosNodeType+ OptTagNetBiosScope -> r OptNetBiosScope+ OptTagXWindowsFontServer -> r OptXWindowsFontServer+ OptTagXWindowsDisplayManagers -> r OptXWindowsDisplayManagers+ OptTagNisPlusDomain -> r OptNisPlusDomain+ OptTagNisPlusServers -> r OptNisPlusServers+ OptTagSmtpServers -> r OptSmtpServers+ OptTagPopServers -> r OptPopServers+ OptTagNntpServers -> r OptNntpServers+ OptTagWwwServers -> r OptWwwServers+ OptTagFingerServers -> r OptFingerServers+ OptTagIrcServers -> r OptIrcServers+ OptTagStreetTalkServers -> r OptStreetTalkServers+ OptTagStreetTalkDirectoryAssistanceServers -> r OptStreetTalkDirectoryAssistanceServers+ OptTagFQDN -> r OptFQDN+ OptTagRequestIPAddress -> r OptRequestIPAddress+ OptTagIPAddressLeaseTime -> r OptIPAddressLeaseTime+ OptTagOverload -> r OptOverload+ OptTagTftpServer -> r OptTftpServer+ OptTagBootfileName -> r OptBootfileName+ OptTagMessageType -> r OptMessageType+ OptTagServerIdentifier -> r OptServerIdentifier+ OptTagParameterRequestList -> r OptParameterRequestList+ OptTagErrorMessage -> r OptErrorMessage+ OptTagMaxDHCPMessageSize -> r OptMaxDHCPMessageSize+ OptTagRenewalTime -> r OptRenewalTime+ OptTagRebindingTime -> r OptRebindingTime+ OptTagVendorClass -> r OptVendorClass+ OptTagClientIdentifier -> r OptClientIdentifier+ OptTagNetWareDomainName -> r OptNetWareDomainName+ OptTagNetWareInfo -> r OptNetWareInfo+ OptTagAutoconfiguration -> r OptAutoconfiguration++putDhcp4Option :: Dhcp4Option -> Put+putDhcp4Option opt =+ let p tag val = putAtom (KnownTag tag) *> putOption val in+ case opt of+ OptSubnetMask mask -> p OptTagSubnetMask mask+ OptTimeOffset offset -> p OptTagTimeOffset offset+ OptRouters routers -> p OptTagRouters routers+ OptTimeServers servers -> p OptTagTimeServers servers+ OptIEN116NameServers servers -> p OptTagIEN116NameServers servers+ OptNameServers servers -> p OptTagNameServers servers+ OptLogServers servers -> p OptTagLogServers servers+ OptCookieServers servers -> p OptTagCookieServers servers+ OptLPRServers servers -> p OptTagLPRServers servers+ OptImpressServers servers -> p OptTagImpressServers servers+ OptResourceLocationServers servers -> p OptTagResourceLocationServers servers+ OptHostName hostname -> p OptTagHostName hostname+ OptBootFileSize sz -> p OptTagBootFileSize sz+ OptMeritDumpFile file -> p OptTagMeritDumpFile file+ OptDomainName domainname -> p OptTagDomainName domainname+ OptSwapServer server -> p OptTagSwapServer server+ OptRootPath path -> p OptTagRootPath path+ OptExtensionsPath path -> p OptTagExtensionsPath path+ OptEnableIPForwarding enabled -> p OptTagEnableIPForwarding enabled+ OptEnableNonLocalSourceRouting enab -> p OptTagEnableNonLocalSourceRouting enab+ OptPolicyFilters filters -> p OptTagPolicyFilters filters+ OptMaximumDatagramReassemblySize n -> p OptTagMaximumDatagramReassemblySize n+ OptDefaultTTL ttl -> p OptTagDefaultTTL ttl+ OptPathMTUAgingTimeout timeout -> p OptTagPathMTUAgingTimeout timeout+ OptPathMTUPlateauTable mtus -> p OptTagPathMTUPlateauTable mtus+ OptInterfaceMTU mtu -> p OptTagInterfaceMTU mtu+ OptAllSubnetsAreLocal arelocal -> p OptTagAllSubnetsAreLocal arelocal+ OptBroadcastAddress addr -> p OptTagBroadcastAddress addr+ OptPerformMaskDiscovery perform -> p OptTagPerformMaskDiscovery perform+ OptShouldSupplyMasks should -> p OptTagShouldSupplyMasks should+ OptShouldPerformRouterDiscovery b -> p OptTagShouldPerformRouterDiscovery b+ OptRouterSolicitationAddress addr -> p OptTagRouterSolicitationAddress addr+ OptStaticRoutes routes -> p OptTagStaticRoutes routes+ OptShouldNegotiateArpTrailers b -> p OptTagShouldNegotiateArpTrailers b+ OptArpCacheTimeout timeout -> p OptTagArpCacheTimeout timeout+ OptUseRFC1042EthernetEncapsulation b-> p OptTagUseRFC1042EthernetEncapsulation b+ OptTcpDefaultTTL ttl -> p OptTagTcpDefaultTTL ttl+ OptTcpKeepaliveInterval interval -> p OptTagTcpKeepaliveInterval interval+ OptTcpKeepaliveUseGarbage use -> p OptTagTcpKeepaliveUseGarbage use+ OptNisDomainName domainname -> p OptTagNisDomainName domainname+ OptNisServers servers -> p OptTagNisServers servers+ OptNtpServers servers -> p OptTagNtpServers servers+ OptVendorSpecific bs -> p OptTagVendorSpecific bs+ OptNetBiosNameServers servers -> p OptTagNetBiosNameServers servers+ OptNetBiosDistributionServers srvs -> p OptTagNetBiosDistributionServers srvs+ OptNetBiosNodeType node -> p OptTagNetBiosNodeType node+ OptNetBiosScope scope -> p OptTagNetBiosScope scope+ OptXWindowsFontServer servers -> p OptTagXWindowsFontServer servers+ OptXWindowsDisplayManagers servers -> p OptTagXWindowsDisplayManagers servers+ OptNisPlusDomain domain -> p OptTagNisPlusDomain domain+ OptNisPlusServers servers -> p OptTagNisPlusServers servers+ OptSmtpServers servers -> p OptTagSmtpServers servers+ OptPopServers servers -> p OptTagPopServers servers+ OptNntpServers servers -> p OptTagNntpServers servers+ OptWwwServers servers -> p OptTagWwwServers servers+ OptFingerServers servers -> p OptTagFingerServers servers+ OptIrcServers servers -> p OptTagIrcServers servers+ OptStreetTalkServers servers -> p OptTagStreetTalkServers servers+ OptStreetTalkDirectoryAssistanceServers servers -> p OptTagStreetTalkDirectoryAssistanceServers servers+ OptFQDN fqdn -> p OptTagFQDN fqdn+ OptRequestIPAddress addr -> p OptTagRequestIPAddress addr+ OptIPAddressLeaseTime time -> p OptTagIPAddressLeaseTime time+ OptOverload overload -> p OptTagOverload overload+ OptTftpServer server -> p OptTagTftpServer server+ OptBootfileName filename -> p OptTagBootfileName filename+ OptMessageType t -> p OptTagMessageType t+ OptServerIdentifier server -> p OptTagServerIdentifier server+ OptParameterRequestList ps -> p OptTagParameterRequestList ps+ OptErrorMessage msg -> p OptTagErrorMessage msg+ OptMaxDHCPMessageSize maxsz -> p OptTagMaxDHCPMessageSize maxsz+ OptRenewalTime time -> p OptTagRenewalTime time+ OptRebindingTime time -> p OptTagRebindingTime time+ OptVendorClass str -> p OptTagVendorClass str+ OptClientIdentifier client -> p OptTagClientIdentifier client+ OptNetWareDomainName name -> p OptTagNetWareDomainName name+ OptNetWareInfo info -> p OptTagNetWareInfo info+ OptAutoconfiguration autoconf -> p OptTagAutoconfiguration autoconf++-----------------------------------------------------------------------+-- Message Type type and operations -----------------------------------+-----------------------------------------------------------------------++data Dhcp4MessageType+ = Dhcp4Discover+ | Dhcp4Offer+ | Dhcp4Request+ | Dhcp4Decline+ | Dhcp4Ack+ | Dhcp4Nak+ | Dhcp4Release+ | Dhcp4Inform+ deriving (Eq,Show)++instance Option Dhcp4MessageType where+ getOption = defaultFixedGetOption+ putOption = defaultFixedPutOption++instance CodecAtom Dhcp4MessageType where+ getAtom = do+ b <- getAtom+ case b :: Word8 of+ 1 -> return Dhcp4Discover+ 2 -> return Dhcp4Offer+ 3 -> return Dhcp4Request+ 4 -> return Dhcp4Decline+ 5 -> return Dhcp4Ack+ 6 -> return Dhcp4Nak+ 7 -> return Dhcp4Release+ 8 -> return Dhcp4Inform+ _ -> fail ("Unknown DHCP Message Type 0x" ++ showHex b "")++ putAtom t = putAtom $ case t of+ Dhcp4Discover -> 1 :: Word8+ Dhcp4Offer -> 2+ Dhcp4Request -> 3+ Dhcp4Decline -> 4+ Dhcp4Ack -> 5+ Dhcp4Nak -> 6+ Dhcp4Release -> 7+ Dhcp4Inform -> 8++ atomSize _ = 1++-----------------------------------------------------------------------+-- Control tag type and operations ------------------------------------+-----------------------------------------------------------------------++data ControlTag+ = ControlPad+ | ControlEnd+ deriving (Eq, Show)++putControlOption :: ControlTag -> Put+putControlOption opt = case opt of+ ControlPad -> putAtom (KnownTag OptTagPad)+ ControlEnd -> putAtom (KnownTag OptTagEnd)++-----------------------------------------------------------------------+-- Option tag type and operations -------------------------------------+-----------------------------------------------------------------------++data Dhcp4OptionTag+ = OptTagPad+ | OptTagEnd+ | OptTagSubnetMask+ | OptTagTimeOffset+ | OptTagRouters+ | OptTagTimeServers+ | OptTagIEN116NameServers+ | OptTagNameServers+ | OptTagLogServers+ | OptTagCookieServers+ | OptTagLPRServers+ | OptTagImpressServers+ | OptTagResourceLocationServers+ | OptTagHostName+ | OptTagBootFileSize+ | OptTagMeritDumpFile+ | OptTagDomainName+ | OptTagSwapServer+ | OptTagRootPath+ | OptTagExtensionsPath+ | OptTagEnableIPForwarding+ | OptTagEnableNonLocalSourceRouting+ | OptTagPolicyFilters+ | OptTagMaximumDatagramReassemblySize+ | OptTagDefaultTTL+ | OptTagPathMTUAgingTimeout+ | OptTagPathMTUPlateauTable+ | OptTagInterfaceMTU+ | OptTagAllSubnetsAreLocal+ | OptTagBroadcastAddress+ | OptTagPerformMaskDiscovery+ | OptTagShouldSupplyMasks+ | OptTagShouldPerformRouterDiscovery+ | OptTagRouterSolicitationAddress+ | OptTagStaticRoutes+ | OptTagShouldNegotiateArpTrailers+ | OptTagArpCacheTimeout+ | OptTagUseRFC1042EthernetEncapsulation+ | OptTagTcpDefaultTTL+ | OptTagTcpKeepaliveInterval+ | OptTagTcpKeepaliveUseGarbage+ | OptTagNisDomainName+ | OptTagNisServers+ | OptTagNtpServers+ | OptTagVendorSpecific+ | OptTagNetBiosNameServers+ | OptTagNetBiosDistributionServers+ | OptTagNetBiosNodeType+ | OptTagNetBiosScope+ | OptTagXWindowsFontServer+ | OptTagXWindowsDisplayManagers+ | OptTagNisPlusDomain+ | OptTagNisPlusServers+ | OptTagSmtpServers+ | OptTagPopServers+ | OptTagNntpServers+ | OptTagWwwServers+ | OptTagFingerServers+ | OptTagIrcServers+ | OptTagStreetTalkServers+ | OptTagStreetTalkDirectoryAssistanceServers+ | OptTagFQDN+ | OptTagRequestIPAddress+ | OptTagIPAddressLeaseTime+ | OptTagOverload+ | OptTagTftpServer+ | OptTagBootfileName+ | OptTagMessageType+ | OptTagServerIdentifier+ | OptTagParameterRequestList+ | OptTagErrorMessage+ | OptTagMaxDHCPMessageSize+ | OptTagRenewalTime+ | OptTagRebindingTime+ | OptTagVendorClass+ | OptTagClientIdentifier+ | OptTagNetWareDomainName+ | OptTagNetWareInfo+ | OptTagAutoconfiguration+ deriving (Show,Eq)++data OptionTagOrError = UnknownTag Word8 | KnownTag Dhcp4OptionTag+ deriving (Show,Eq)++getOptionTag :: Get OptionTagOrError+getOptionTag = f =<< getWord8+ where+ r = return . KnownTag+ f 0 = r OptTagPad+ f 1 = r OptTagSubnetMask+ f 2 = r OptTagTimeOffset+ f 3 = r OptTagRouters+ f 4 = r OptTagTimeServers+ f 5 = r OptTagIEN116NameServers+ f 6 = r OptTagNameServers+ f 7 = r OptTagLogServers+ f 8 = r OptTagCookieServers+ f 9 = r OptTagLPRServers+ f 10 = r OptTagImpressServers+ f 11 = r OptTagResourceLocationServers+ f 12 = r OptTagHostName+ f 13 = r OptTagBootFileSize+ f 14 = r OptTagMeritDumpFile+ f 15 = r OptTagDomainName+ f 16 = r OptTagSwapServer+ f 17 = r OptTagRootPath+ f 18 = r OptTagExtensionsPath+ f 19 = r OptTagEnableIPForwarding+ f 20 = r OptTagEnableNonLocalSourceRouting+ f 21 = r OptTagPolicyFilters+ f 22 = r OptTagMaximumDatagramReassemblySize+ f 23 = r OptTagDefaultTTL+ f 24 = r OptTagPathMTUAgingTimeout+ f 25 = r OptTagPathMTUPlateauTable+ f 26 = r OptTagInterfaceMTU+ f 27 = r OptTagAllSubnetsAreLocal+ f 28 = r OptTagBroadcastAddress+ f 29 = r OptTagPerformMaskDiscovery+ f 30 = r OptTagShouldSupplyMasks+ f 31 = r OptTagShouldPerformRouterDiscovery+ f 32 = r OptTagRouterSolicitationAddress+ f 33 = r OptTagStaticRoutes+ f 34 = r OptTagShouldNegotiateArpTrailers+ f 35 = r OptTagArpCacheTimeout+ f 36 = r OptTagUseRFC1042EthernetEncapsulation+ f 37 = r OptTagTcpDefaultTTL+ f 38 = r OptTagTcpKeepaliveInterval+ f 39 = r OptTagTcpKeepaliveUseGarbage+ f 40 = r OptTagNisDomainName+ f 41 = r OptTagNisServers+ f 42 = r OptTagNtpServers+ f 43 = r OptTagVendorSpecific+ f 44 = r OptTagNetBiosNameServers+ f 45 = r OptTagNetBiosDistributionServers+ f 46 = r OptTagNetBiosNodeType+ f 47 = r OptTagNetBiosScope+ f 48 = r OptTagXWindowsFontServer+ f 49 = r OptTagXWindowsDisplayManagers+ f 50 = r OptTagRequestIPAddress+ f 51 = r OptTagIPAddressLeaseTime+ f 52 = r OptTagOverload+ f 53 = r OptTagMessageType+ f 54 = r OptTagServerIdentifier+ f 55 = r OptTagParameterRequestList+ f 56 = r OptTagErrorMessage+ f 57 = r OptTagMaxDHCPMessageSize+ f 58 = r OptTagRenewalTime+ f 59 = r OptTagRebindingTime+ f 60 = r OptTagVendorClass+ f 61 = r OptTagClientIdentifier+ f 62 = r OptTagNetWareDomainName+ f 63 = r OptTagNetWareInfo+ f 64 = r OptTagNisPlusDomain+ f 65 = r OptTagNisPlusServers+ f 66 = r OptTagTftpServer+ f 67 = r OptTagBootfileName+ f 69 = r OptTagSmtpServers+ f 70 = r OptTagPopServers+ f 71 = r OptTagNntpServers+ f 72 = r OptTagWwwServers+ f 73 = r OptTagFingerServers+ f 74 = r OptTagIrcServers+ f 75 = r OptTagStreetTalkServers+ f 76 = r OptTagStreetTalkDirectoryAssistanceServers+ f 81 = r OptTagFQDN+ f 116 = r OptTagAutoconfiguration+ f 255 = r OptTagEnd+ f t = return (UnknownTag t)++putOptionTag :: OptionTagOrError -> Put+putOptionTag (UnknownTag t) = putAtom t+putOptionTag (KnownTag t) = putAtom (f t)+ where+ f :: Dhcp4OptionTag -> Word8+ f OptTagPad = 0+ f OptTagEnd = 255+ f OptTagSubnetMask = 1+ f OptTagTimeOffset = 2+ f OptTagRouters = 3+ f OptTagTimeServers = 4+ f OptTagIEN116NameServers = 5+ f OptTagNameServers = 6+ f OptTagLogServers = 7+ f OptTagCookieServers = 8+ f OptTagLPRServers = 9+ f OptTagImpressServers = 10+ f OptTagResourceLocationServers = 11+ f OptTagHostName = 12+ f OptTagBootFileSize = 13+ f OptTagMeritDumpFile = 14+ f OptTagDomainName = 15+ f OptTagSwapServer = 16+ f OptTagRootPath = 17+ f OptTagExtensionsPath = 18+ f OptTagEnableIPForwarding = 19+ f OptTagEnableNonLocalSourceRouting = 20+ f OptTagPolicyFilters = 21+ f OptTagMaximumDatagramReassemblySize = 22+ f OptTagDefaultTTL = 23+ f OptTagPathMTUAgingTimeout = 24+ f OptTagPathMTUPlateauTable = 25+ f OptTagInterfaceMTU = 26+ f OptTagAllSubnetsAreLocal = 27+ f OptTagBroadcastAddress = 28+ f OptTagPerformMaskDiscovery = 29+ f OptTagShouldSupplyMasks = 30+ f OptTagShouldPerformRouterDiscovery = 31+ f OptTagRouterSolicitationAddress = 32+ f OptTagStaticRoutes = 33+ f OptTagShouldNegotiateArpTrailers = 34+ f OptTagArpCacheTimeout = 35+ f OptTagUseRFC1042EthernetEncapsulation = 36+ f OptTagTcpDefaultTTL = 37+ f OptTagTcpKeepaliveInterval = 38+ f OptTagTcpKeepaliveUseGarbage = 39+ f OptTagNisDomainName = 40+ f OptTagNisServers = 41+ f OptTagNtpServers = 42+ f OptTagVendorSpecific = 43+ f OptTagNetBiosNameServers = 44+ f OptTagNetBiosDistributionServers = 45+ f OptTagNetBiosNodeType = 46+ f OptTagNetBiosScope = 47+ f OptTagXWindowsFontServer = 48+ f OptTagXWindowsDisplayManagers = 49+ f OptTagRequestIPAddress = 50+ f OptTagIPAddressLeaseTime = 51+ f OptTagOverload = 52+ f OptTagMessageType = 53+ f OptTagServerIdentifier = 54+ f OptTagParameterRequestList = 55+ f OptTagErrorMessage = 56+ f OptTagMaxDHCPMessageSize = 57+ f OptTagRenewalTime = 58+ f OptTagRebindingTime = 59+ f OptTagVendorClass = 60+ f OptTagClientIdentifier = 61+ f OptTagNetWareDomainName = 62+ f OptTagNetWareInfo = 63+ f OptTagNisPlusDomain = 64+ f OptTagNisPlusServers = 65+ f OptTagTftpServer = 66+ f OptTagBootfileName = 67+ f OptTagSmtpServers = 69+ f OptTagPopServers = 70+ f OptTagNntpServers = 71+ f OptTagWwwServers = 72+ f OptTagFingerServers = 73+ f OptTagIrcServers = 74+ f OptTagStreetTalkServers = 75+ f OptTagStreetTalkDirectoryAssistanceServers = 76+ f OptTagFQDN = 81+ f OptTagAutoconfiguration = 116++-----------------------------------------------------------------------+-- NetBIOS node type and operations -----------------------------------+-----------------------------------------------------------------------++data NetBiosNodeType+ = BNode+ | PNode+ | MNode+ | HNode+ deriving (Show,Eq)++instance Option NetBiosNodeType where+ getOption = defaultFixedGetOption+ putOption = defaultFixedPutOption++instance CodecAtom NetBiosNodeType where+ getAtom = do+ b <- getAtom+ case b :: Word8 of+ 0x1 -> return BNode+ 0x2 -> return PNode+ 0x4 -> return MNode+ 0x8 -> return HNode+ _ -> fail "Unknown NetBIOS node type"++ putAtom t = putAtom $ case t of+ BNode -> 0x1 :: Word8+ PNode -> 0x2+ MNode -> 0x4+ HNode -> 0x8++ atomSize _ = 1++-----------------------------------------------------------------------+-- Overload option type and operations --------------------------------+-----------------------------------------------------------------------++data OverloadOption+ = UsedFileField+ | UsedSNameField+ | UsedBothFields+ deriving (Show, Eq)++instance Option OverloadOption where+ getOption = defaultFixedGetOption+ putOption = defaultFixedPutOption++instance CodecAtom OverloadOption where+ getAtom = do b <- getAtom+ case b :: Word8 of+ 1 -> return UsedFileField+ 2 -> return UsedSNameField+ 3 -> return UsedBothFields+ _ -> fail ("Bad overload value 0x" ++ showHex b "")+ putAtom t = putAtom $ case t of+ UsedFileField -> 1 :: Word8+ UsedSNameField -> 2+ UsedBothFields -> 3++ atomSize _ = atomSize (undefined :: Word8)++-----------------------------------------------------------------------+-- Options list operations --------------------------------------------+-----------------------------------------------------------------------++getDhcp4Options :: ByteString -> ByteString+ -> Get (String, String, [Dhcp4Option])+getDhcp4Options sname file = do+ MagicCookie <- getAtom+ options0 <- remainingAsOptions+ case lookupOverload options0 of+ Nothing -> return (nullTerminated sname, nullTerminated file, options0)++ Just UsedFileField -> do+ options1 <- localParse file remainingAsOptions+ let options = options0 ++ options1+ NVTAsciiString fileString+ = fromMaybe (NVTAsciiString "") (lookupFile options)+ return (nullTerminated sname, fileString, options)++ Just UsedSNameField -> do+ options1 <- localParse sname remainingAsOptions+ let options = options0 ++ options1+ NVTAsciiString snameString+ = fromMaybe (NVTAsciiString "") (lookupSname options)+ return (snameString, nullTerminated file, options)++ Just UsedBothFields -> do++ -- The file field MUST be interpreted for options before the sname field.+ -- RFC 2131, Section 4.1, Page 24+ options1 <- localParse file remainingAsOptions+ options2 <- localParse sname remainingAsOptions+ let options = options0 ++ options1 ++ options2+ NVTAsciiString snameString+ = fromMaybe (NVTAsciiString "") (lookupSname options)+ NVTAsciiString fileString+ = fromMaybe (NVTAsciiString "") (lookupFile options)+ return (snameString, fileString, options)++ where+ remainingAsOptions = scrubControls =<< repeatedly getDhcp4Option++ localParse bs m = case runGet m bs of+ Right x -> return x+ Left err -> fail err+++putDhcp4Options :: [Dhcp4Option] -> Put+putDhcp4Options opts = putAtom MagicCookie+ *> traverse_ putDhcp4Option opts+ *> putControlOption ControlEnd++scrubControls :: (Applicative m, Monad m)+ => [Either ControlTag Dhcp4Option] -> m [Dhcp4Option]+scrubControls [] = fail "No END option found"+scrubControls (Left ControlPad : xs) = scrubControls xs+scrubControls (Left ControlEnd : xs) = [] <$ traverse_ eatPad xs+scrubControls (Right o : xs) = (o :) <$> scrubControls xs++-- | 'eatPad' fails on any non 'ControlPad' option with an error message.+eatPad :: Monad m => Either ControlTag Dhcp4Option -> m ()+eatPad (Left ControlPad) = return ()+eatPad _ = fail "Unexpected option after END option"++replicateA :: Applicative f => Int -> f a -> f [a]+replicateA n f = sequenceA (replicate n f)++repeatedly :: Get a -> Get [a]+repeatedly m = do+ done <- isEmpty+ if done then return []+ else (:) <$> m <*> repeatedly m++nullTerminated :: ByteString -> String+nullTerminated = takeWhile (/= '\NUL') . BS8.unpack++lookupOverload :: [Dhcp4Option] -> Maybe OverloadOption+lookupOverload = foldr f Nothing+ where f (OptOverload o) _ = Just o+ f _ a = a++lookupFile :: [Dhcp4Option] -> Maybe NVTAsciiString+lookupFile = foldr f Nothing+ where f (OptBootfileName fn) _ = Just fn+ f _ a = a++lookupSname :: [Dhcp4Option] -> Maybe NVTAsciiString+lookupSname = foldr f Nothing+ where f (OptTftpServer n) _ = Just n+ f _ a = a++lookupParams :: [Dhcp4Option] -> Maybe [OptionTagOrError]+lookupParams = foldr f Nothing+ where f (OptParameterRequestList n) _ = Just n+ f _ a = a++lookupMessageType :: [Dhcp4Option] -> Maybe Dhcp4MessageType+lookupMessageType = foldr f Nothing+ where f (OptMessageType n) _ = Just n+ f _ a = a++lookupRequestAddr :: [Dhcp4Option] -> Maybe IP4+lookupRequestAddr = foldr f Nothing+ where f (OptRequestIPAddress n) _ = Just n+ f _ a = a++lookupLeaseTime :: [Dhcp4Option] -> Maybe Word32+lookupLeaseTime = foldr f Nothing+ where f (OptIPAddressLeaseTime t) _ = Just t+ f _ a = a++-----------------------------------------------------------------------+-- Protected parser and unparser monad --------------------------------+-----------------------------------------------------------------------++class Option a where+ getOption :: Get a+ putOption :: a -> Put++instance CodecAtom a => Option [a] where+ getOption = do+ let (n, m) = getRecord+ len <- getLen+ let (count, remainder) = divMod len n+ unless (remainder == 0) (fail ("Length was not a multiple of " ++ show n))+ unless (count > 0) (fail "Minimum length not met")+ replicateA count $ label "List of fixed-length values" $ isolate n m+ putOption xs = do putLen (atomSize (head xs) * length xs)+ traverse_ putAtom xs++instance (CodecAtom a, CodecAtom b) => Option (a,b) where+ getOption = defaultFixedGetOption+ putOption = defaultFixedPutOption+instance Option Bool where+ getOption = defaultFixedGetOption+ putOption = defaultFixedPutOption+instance Option Word8 where+ getOption = defaultFixedGetOption+ putOption = defaultFixedPutOption+instance Option Word16 where+ getOption = defaultFixedGetOption+ putOption = defaultFixedPutOption+instance Option Word32 where+ getOption = defaultFixedGetOption+ putOption = defaultFixedPutOption+instance Option IP4 where+ getOption = defaultFixedGetOption+ putOption = defaultFixedPutOption+instance Option SubnetMask where+ getOption = defaultFixedGetOption+ putOption = defaultFixedPutOption+instance Option ByteString where+ getOption = do len <- getLen+ getByteString len+ putOption bs = do putLen (BS.length bs)+ putByteString bs++defaultFixedGetOption :: CodecAtom a => Get a+defaultFixedGetOption = fixedLen n m+ where (n,m) = getRecord++defaultFixedPutOption :: CodecAtom a => a -> Put+defaultFixedPutOption x = do+ putLen (atomSize x)+ putAtom x++fixedLen :: Int -> Get a -> Get a+fixedLen expectedLen m = do+ len <- getLen+ unless (len == expectedLen) (fail "Bad length on \"fixed-length\" option.")+ label "Fixed length field" (isolate expectedLen m)++getRecord :: CodecAtom a => (Int, Get a)+getRecord = (atomSize undef, m)+ where+ (undef, m) = (undefined, getAtom) :: CodecAtom a => (a, Get a)+++instance CodecAtom OptionTagOrError where+ getAtom = getOptionTag+ putAtom x = putOptionTag x+ atomSize _ = 1++newtype NVTAsciiString = NVTAsciiString String+ deriving (Eq, Show)++instance Option NVTAsciiString where+ getOption = do len <- getLen+ bs <- getByteString len+ return (NVTAsciiString (nullTerminated bs))+ putOption (NVTAsciiString str) = do+ putLen (length str)+ putByteString (BS8.pack str)++getLen :: Get Int+getLen = fromIntegral <$> getWord8++putLen :: Int -> Put+putLen n = putWord8 $ fromIntegral n
+ src/Hans/Message/EthernetFrame.hs view
@@ -0,0 +1,44 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++module Hans.Message.EthernetFrame (+ EtherType(..)+ , EthernetFrame(..)+ ) where++import Hans.Address.Mac (Mac)++import Data.Serialize (Serialize(..))+import Data.Serialize.Get (Get,getBytes,remaining)+import Data.Serialize.Put (Put,putByteString)+import Data.ByteString (ByteString)+import Data.Word (Word16)+import Numeric (showHex)++newtype EtherType = EtherType { getEtherType :: Word16 }+ deriving (Eq,Num,Ord,Serialize)++instance Show EtherType where+ showsPrec _ (EtherType et) = showString "EtherType 0x" . showHex et++data EthernetFrame = EthernetFrame+ { etherDest :: !Mac+ , etherSource :: !Mac+ , etherType :: !EtherType+ , etherData :: ByteString+ } deriving (Eq,Show)++instance Serialize EthernetFrame where+ get = parseEthernetFrame+ put = renderEthernetFrame++parseEthernetFrame :: Get EthernetFrame+parseEthernetFrame = do+ dst <- get+ src <- get+ ty <- get+ body <- getBytes =<< remaining+ return $! EthernetFrame dst src ty body++renderEthernetFrame :: EthernetFrame -> Put+renderEthernetFrame (EthernetFrame s d t da) =+ put s >> put d >> put t >> putByteString da
+ src/Hans/Message/Icmp4.hs view
@@ -0,0 +1,398 @@+{-# 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
+ src/Hans/Message/Ip4.hs view
@@ -0,0 +1,280 @@+{-# 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)
+ src/Hans/Message/Tcp.hs view
@@ -0,0 +1,446 @@+{-# LANGUAGE FlexibleInstances #-}++module Hans.Message.Tcp where++import Hans.Address.IP4 (IP4)+import Hans.Message.Ip4 (mkIP4PseudoHeader,IP4Protocol(..))+import Hans.Utils.Checksum (computePartialChecksum,computeChecksum,pokeChecksum)++import Control.Monad (when,unless,ap)+import Data.Bits ((.&.),setBit,testBit,shiftL,shiftR)+import Data.List (foldl',find)+import Data.Serialize+ (Get,Put,Putter,getWord16be,putWord16be,getWord32be,putWord32be,getWord8+ ,putWord8,putByteString,getBytes,remaining,label,isolate,skip,runPut)+import Data.Word (Word8,Word16,Word32)+import System.IO.Unsafe (unsafePerformIO)+import qualified Data.ByteString as S+++-- Tcp Support Types -----------------------------------------------------------++tcpProtocol :: IP4Protocol+tcpProtocol = IP4Protocol 0x6++newtype TcpPort = TcpPort+ { getPort :: Word16+ } deriving Show++putTcpPort :: Putter TcpPort+putTcpPort (TcpPort w16) = putWord16be w16++getTcpPort :: Get TcpPort+getTcpPort = TcpPort `fmap` getWord16be+++newtype TcpSeqNum = TcpSeqNum+ { getSeqNum :: Word32+ } deriving (Eq,Ord,Show)++putTcpSeqNum :: Putter TcpSeqNum+putTcpSeqNum (TcpSeqNum w32) = putWord32be w32++getTcpSeqNum :: Get TcpSeqNum+getTcpSeqNum = TcpSeqNum `fmap` getWord32be+++newtype TcpAckNum = TcpAckNum+ { getAckNum :: Word32+ } deriving (Eq,Ord,Show)++putTcpAckNum :: Putter TcpAckNum+putTcpAckNum (TcpAckNum w32) = putWord32be w32++getTcpAckNum :: Get TcpAckNum+getTcpAckNum = TcpAckNum `fmap` getWord32be+++-- Tcp Header ------------------------------------------------------------------++-- 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+-- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-++-- | Source Port | Destination Port |+-- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-++-- | Sequence Number |+-- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-++-- | Acknowledgment Number |+-- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-++-- | Data | |C|E|U|A|P|R|S|F| |+-- | Offset| Res. |W|C|R|C|S|S|Y|I| Window |+-- | | |R|E|G|K|H|T|N|N| |+-- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-++-- | Checksum | Urgent Pointer |+-- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-++-- | Options | Padding |+-- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-++-- | data |+-- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-++data TcpHeader = TcpHeader+ { tcpSourcePort :: !TcpPort+ , tcpDestPort :: !TcpPort+ , tcpSeqNum :: !TcpSeqNum+ , tcpAckNum :: !TcpAckNum+ , tcpCwr :: !Bool+ , tcpEce :: !Bool+ , tcpUrg :: !Bool+ , tcpAck :: !Bool+ , tcpPsh :: !Bool+ , tcpRst :: !Bool+ , tcpSyn :: !Bool+ , tcpFin :: !Bool+ , tcpWindow :: !Word16+ , tcpChecksum :: !Word16+ , tcpUrgentPointer :: !Word16+ , tcpOptions :: [TcpOption]+ } deriving Show++instance HasTcpOptions TcpHeader where+ findTcpOption tag hdr = findTcpOption tag (tcpOptions hdr)+ setTcpOption opt hdr = hdr { tcpOptions = setTcpOption opt (tcpOptions hdr) }++emptyTcpHeader :: TcpHeader+emptyTcpHeader = TcpHeader+ { tcpSourcePort = TcpPort 0+ , tcpDestPort = TcpPort 0+ , tcpSeqNum = TcpSeqNum 0+ , tcpAckNum = TcpAckNum 0+ , tcpCwr = False+ , tcpEce = False+ , tcpUrg = False+ , tcpAck = False+ , tcpPsh = False+ , tcpRst = False+ , tcpSyn = False+ , tcpFin = False+ , tcpWindow = 0+ , tcpChecksum = 0+ , tcpUrgentPointer = 0+ , tcpOptions = []+ }++-- | The length of the fixed part of the TcpHeader, in 4-byte octets.+tcpFixedHeaderLength :: Int+tcpFixedHeaderLength = 5++-- | Calculate the length of a TcpHeader, in 4-byte octets.+tcpHeaderLength :: TcpHeader -> Int+tcpHeaderLength hdr =+ tcpFixedHeaderLength + tcpOptionsLength (tcpOptions hdr)++-- | Render a TcpHeader. The checksum value is never rendered, as it is+-- expected to be calculated and poked in afterwords.+putTcpHeader :: Putter TcpHeader+putTcpHeader hdr = do+ putTcpPort (tcpSourcePort hdr)+ putTcpPort (tcpDestPort hdr)+ putTcpSeqNum (tcpSeqNum hdr)+ putTcpAckNum (tcpAckNum hdr)+ putWord8 (fromIntegral (tcpHeaderLength hdr) `shiftL` 4)+ putTcpControl hdr+ putWord16be (tcpWindow hdr)+ putWord16be 0+ putWord16be (tcpUrgentPointer hdr)+ putTcpOptions (tcpOptions hdr)++-- | Parse out a TcpHeader, and its length. The resulting length is in bytes,+-- and is derived from the data offset.+getTcpHeader :: Get (TcpHeader,Int)+getTcpHeader = do+ src <- getTcpPort+ dst <- getTcpPort+ seqNum <- getTcpSeqNum+ ackNum <- getTcpAckNum+ b <- getWord8+ let len = fromIntegral ((b `shiftR` 4) .&. 0xf)+ cont <- getWord8+ win <- getWord16be+ cs <- getWord16be+ urgent <- getWord16be+ let optsLen = len - tcpFixedHeaderLength+ opts <- getTcpOptions optsLen+ let hdr = setTcpControl cont emptyTcpHeader+ { tcpSourcePort = src+ , tcpDestPort = dst+ , tcpSeqNum = seqNum+ , tcpAckNum = ackNum+ , tcpWindow = win+ , tcpChecksum = cs+ , tcpUrgentPointer = urgent+ , tcpOptions = opts+ }+ return (hdr,len * 4)++-- | Render out the @Word8@ that contains the Control field of the TcpHeader.+putTcpControl :: Putter TcpHeader+putTcpControl c =+ putWord8 $ putBit 7 tcpCwr+ $ putBit 6 tcpEce+ $ putBit 5 tcpUrg+ $ putBit 4 tcpAck+ $ putBit 3 tcpPsh+ $ putBit 2 tcpRst+ $ putBit 1 tcpSyn+ $ putBit 0 tcpFin+ 0+ where+ putBit n prj w | prj c = setBit w n+ | otherwise = w++-- | Parse out the control flags from the octet that contains them.+setTcpControl :: Word8 -> TcpHeader -> TcpHeader+setTcpControl w hdr = hdr+ { tcpCwr = testBit w 7+ , tcpEce = testBit w 6+ , tcpUrg = testBit w 5+ , tcpAck = testBit w 4+ , tcpPsh = testBit w 3+ , tcpRst = testBit w 2+ , tcpSyn = testBit w 1+ , tcpFin = testBit w 0+ }+++-- Tcp Options -----------------------------------------------------------------++class HasTcpOptions a where+ findTcpOption :: TcpOptionTag -> a -> Maybe TcpOption+ setTcpOption :: TcpOption -> a -> a++data TcpOptionTag+ = OptTagEndOfOptions+ | OptTagNoOption+ | OptTagMaxSegmentSize+ | OptTagWindowScaling+ | OptTagTimestamp+ | OptTagUnknown !Word8+ deriving (Eq,Show)++getTcpOptionTag :: Get TcpOptionTag+getTcpOptionTag = do+ ty <- getWord8+ return $! case ty of+ 0 -> OptTagEndOfOptions+ 1 -> OptTagNoOption+ 2 -> OptTagMaxSegmentSize+ 3 -> OptTagWindowScaling+ 8 -> OptTagTimestamp+ _ -> OptTagUnknown ty++putTcpOptionTag :: Putter TcpOptionTag+putTcpOptionTag tag =+ putWord8 $ case tag of+ OptTagEndOfOptions -> 0+ OptTagNoOption -> 1+ OptTagMaxSegmentSize -> 2+ OptTagWindowScaling -> 3+ OptTagTimestamp -> 8+ OptTagUnknown ty -> ty++instance HasTcpOptions [TcpOption] where+ findTcpOption tag = find p+ where+ p opt = tag == tcpOptionTag opt++ setTcpOption opt = loop+ where+ tag = tcpOptionTag opt+ loop [] = [opt]+ loop (o:opts)+ | tcpOptionTag o == tag = opt : opts+ | otherwise = o : loop opts+++data TcpOption+ = OptEndOfOptions+ | OptNoOption+ | OptMaxSegmentSize !Word16+ | OptWindowScaling !Word8+ | OptTimestamp !Word32 !Word32+ | OptUnknown !Word8 !Word8 !S.ByteString+ deriving Show++tcpOptionTag :: TcpOption -> TcpOptionTag+tcpOptionTag opt = case opt of+ OptEndOfOptions{} -> OptTagEndOfOptions+ OptNoOption{} -> OptTagNoOption+ OptMaxSegmentSize{} -> OptTagMaxSegmentSize+ OptWindowScaling{} -> OptTagWindowScaling+ OptTimestamp{} -> OptTagTimestamp+ OptUnknown ty _ _ -> OptTagUnknown ty++-- | Get the length of a TcpOptions, in 4-byte words. This rounds up to the+-- nearest 4-byte word.+tcpOptionsLength :: [TcpOption] -> Int+tcpOptionsLength opts+ | left == 0 = len+ | otherwise = len + 1+ where+ (len,left) = foldl' step 0 opts `quotRem` 4+ step acc opt = tcpOptionLength opt + acc++tcpOptionLength :: TcpOption -> Int+tcpOptionLength OptEndOfOptions{} = 1+tcpOptionLength OptNoOption{} = 1+tcpOptionLength OptMaxSegmentSize{} = 4+tcpOptionLength OptWindowScaling{} = 3+tcpOptionLength OptTimestamp{} = 10+tcpOptionLength (OptUnknown _ len _) = fromIntegral len+++-- | Render out the tcp options, and pad with zeros if they don't fall on a+-- 4-byte boundary.+putTcpOptions :: Putter [TcpOption]+putTcpOptions opts = do+ let len = tcpOptionsLength opts+ left = len `rem` 4+ padding+ | left == 0 = 0+ | otherwise = 4 - left+ mapM_ putTcpOption opts+ when (padding > 0) (putByteString (S.replicate padding 0))++putTcpOption :: Putter TcpOption+putTcpOption opt =+ case opt of+ OptEndOfOptions -> putWord8 0+ OptNoOption -> putWord8 1+ OptMaxSegmentSize mss -> putMaxSegmentSize mss+ OptWindowScaling w -> putWindowScaling w+ OptTimestamp v r -> putTimestamp v r+ OptUnknown ty len bs -> putUnknown ty len bs++-- | Parse in known tcp options.+getTcpOptions :: Int -> Get [TcpOption]+getTcpOptions len = label ("Tcp Options (" ++ show len ++ ")")+ $ isolate (len * 4) loop+ where+ loop = do+ left <- remaining+ if left <= 0 then return [] else body++ body = do+ opt <- getTcpOption+ case opt of+ OptNoOption -> loop++ OptEndOfOptions -> do+ skip =<< remaining+ return []++ _ -> do+ rest <- loop+ return (opt:rest)++getTcpOption :: Get TcpOption+getTcpOption = do+ tag <- getTcpOptionTag+ case tag of+ OptTagEndOfOptions -> return OptEndOfOptions+ OptTagNoOption -> return OptNoOption+ OptTagMaxSegmentSize -> getMaxSegmentSize+ OptTagWindowScaling -> getWindowScaling+ OptTagTimestamp -> getTimestamp+ OptTagUnknown ty -> getUnknown ty++getMaxSegmentSize :: Get TcpOption+getMaxSegmentSize = label "Max Segment Size" $ isolate 3 $ do+ len <- getWord8+ unless (len == 4) (fail ("Unexpected length: " ++ show len))+ OptMaxSegmentSize `fmap` getWord16be++putMaxSegmentSize :: Putter Word16+putMaxSegmentSize w16 = do+ putWord8 4+ putWord16be w16++getWindowScaling :: Get TcpOption+getWindowScaling = label "Window Scaling" $ isolate 2 $ do+ len <- getWord8+ unless (len == 3) (fail ("Unexpected length: " ++ show len))+ OptWindowScaling `fmap` getWord8++putWindowScaling :: Putter Word8+putWindowScaling w = do+ putWord8 3+ putWord8 w++getTimestamp :: Get TcpOption+getTimestamp = label "Timestamp" $ isolate 9 $ do+ len <- getWord8+ unless (len == 10) (fail ("Unexpected length: " ++ show len))+ OptTimestamp `fmap` getWord32be `ap` getWord32be++putTimestamp :: Word32 -> Word32 -> Put+putTimestamp v r = do+ putWord8 8+ putWord8 10+ putWord32be v+ putWord32be r++getUnknown :: Word8 -> Get TcpOption+getUnknown ty = do+ len <- getWord8+ body <- isolate (fromIntegral len - 2) (getBytes =<< remaining)+ return (OptUnknown ty len body)++putUnknown :: Word8 -> Word8 -> S.ByteString -> Put+putUnknown ty len body = do+ putWord8 ty+ putWord8 len+ putByteString body+++-- Tcp Packet ------------------------------------------------------------------++data TcpPacket = TcpPacket+ { tcpHeader :: !TcpHeader+ , tcpBody :: !S.ByteString+ } deriving Show++-- | Parse a TcpPacket.+getTcpPacket :: Get TcpPacket+getTcpPacket = do+ pktLen <- remaining+ (hdr,hdrLen) <- getTcpHeader+ body <- getBytes (pktLen - hdrLen)+ return (TcpPacket hdr body)++-- | Render out a TcpPacket, without calculating its checksum.+putTcpPacket :: Putter TcpPacket+putTcpPacket (TcpPacket hdr body) = do+ putTcpHeader hdr+ putByteString body++-- | Calculate the checksum of a TcpHeader, and its body.+renderWithTcpChecksumIP4 :: IP4 -> IP4 -> TcpPacket -> S.ByteString+renderWithTcpChecksumIP4 src dst pkt@(TcpPacket _ body) = hdrbs `S.append` body+ where+ (hdrbs,_) = computeTcpChecksumIP4 src dst pkt++-- | Calculate the checksum of a tcp packet, and return its rendered header.+computeTcpChecksumIP4 :: IP4 -> IP4 -> TcpPacket -> (S.ByteString,Word16)+computeTcpChecksumIP4 src dst (TcpPacket hdr body) =+ -- this is safe, as the header bytestring that gets modified is modified at+ -- its creation time.+ (cs `seq` unsafePerformIO (pokeChecksum cs hdrbs 16), cs)+ where+ hdrbs = runPut (putTcpHeader hdr { tcpChecksum = 0 })+ phcs = computePartialChecksum 0+ $ mkIP4PseudoHeader src dst tcpProtocol+ $ S.length hdrbs + S.length body+ hdrcs = computePartialChecksum phcs hdrbs+ cs = computeChecksum hdrcs body++-- | Re-create the checksum, minimizing duplication of the original, rendered+-- TCP packet.+recreateTcpChecksumIP4 :: IP4 -> IP4 -> S.ByteString -> Word16+recreateTcpChecksumIP4 src dst bytes = computeChecksum hdrcs rest+ where+ phcs = computePartialChecksum 0+ $ mkIP4PseudoHeader src dst tcpProtocol+ $ S.length bytes+ (hdrbs,rest) = S.splitAt 18 bytes+ hdrbs' = unsafePerformIO (pokeChecksum 0 (S.copy hdrbs) 16)+ hdrcs = computePartialChecksum phcs hdrbs'+
+ src/Hans/Message/Types.hs view
@@ -0,0 +1,8 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+module Hans.Message.Types where++import Data.Serialize (Serialize)+import Data.Word (Word16)++newtype Lifetime = Lifetime Word16+ deriving (Show,Eq,Ord,Num,Serialize)
+ src/Hans/Message/Udp.hs view
@@ -0,0 +1,59 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++module Hans.Message.Udp where++import Hans.Utils+import Hans.Utils.Checksum++import Data.Serialize (Serialize(..))+import Data.Serialize.Get (Get,getWord16be,getByteString,isolate,label)+import Data.Serialize.Put (runPut,putWord16be,putByteString)+import Data.Word (Word16)+import qualified Data.ByteString as S++newtype UdpPort = UdpPort { getUdpPort :: Word16 }+ deriving (Eq,Ord,Num,Show,Serialize,Enum,Bounded)++data UdpPacket = UdpPacket+ { udpHeader :: !UdpHeader+ , udpPayload :: S.ByteString+ } deriving Show++data UdpHeader = UdpHeader+ { udpSourcePort :: !UdpPort+ , udpDestPort :: !UdpPort+ , udpChecksum :: !Word16+ } deriving Show++parseUdpPacket :: Get UdpPacket+parseUdpPacket = do+ src <- get+ dst <- get+ b16 <- getWord16be+ let len = fromIntegral b16+ label "UDPPacket" $ isolate (len - 6) $ do+ cs <- getWord16be+ bs <- getByteString (len - 8)+ let hdr = UdpHeader+ { udpSourcePort = src+ , udpDestPort = dst+ , udpChecksum = cs+ }+ return $! UdpPacket hdr bs++-- | Given a way to make the pseudo header, render the UDP packet.+renderUdpPacket :: UdpPacket -> MkPseudoHeader -> IO Packet+renderUdpPacket (UdpPacket hdr bs) mk = do+ let hdrSize = 8+ let len = S.length bs + hdrSize+ let ph = mk len+ let pcs = computePartialChecksum 0 ph+ let bytes = runPut $ do+ put (udpSourcePort hdr)+ put (udpDestPort hdr)+ putWord16be (fromIntegral len)+ putWord16be 0 -- initial checksum+ putByteString bs+ -- the checksum is 6 bytes into the rendered packet+ let cs = computeChecksum pcs bytes+ pokeChecksum cs bytes 6
+ src/Hans/Ports.hs view
@@ -0,0 +1,56 @@++module Hans.Ports (+ -- * Port Management+ PortManager+ , emptyPortManager+ , isReserved+ , reserve+ , unreserve+ , nextPort+ ) where++import Control.Monad (MonadPlus(mzero),guard)+import Data.List (delete)+import qualified Data.Set as Set+++-- Port Management -------------------------------------------------------------++data PortManager i = PortManager+ { portNext :: [i]+ , portActive :: Set.Set i+ }++emptyPortManager :: [i] -> PortManager i+emptyPortManager range = PortManager+ { portNext = range+ , portActive = Set.empty+ }++isReserved :: (Eq i, Ord i) => i -> PortManager i -> Bool+isReserved i pm = i `Set.member` portActive pm++reserve :: (MonadPlus m, Eq i, Ord i) => i -> PortManager i -> m (PortManager i)+reserve i pm = do+ guard (not (isReserved i pm))+ return $! pm+ { portNext = delete i (portNext pm)+ , portActive = Set.insert i (portActive pm)+ }++unreserve :: (MonadPlus m, Eq i, Ord i)+ => i -> PortManager i -> m (PortManager i)+unreserve i pm = do+ guard (isReserved i pm)+ return $! pm+ { portNext = i : portNext pm+ , portActive = Set.delete i (portActive pm)+ }++nextPort :: (MonadPlus m, Eq i, Ord i)+ => PortManager i -> m (i, PortManager i)+nextPort pm = case portNext pm of+ [] -> mzero+ i:_ -> do+ pm' <- reserve i pm+ return $! (i,pm')
+ src/Hans/Setup.hs view
@@ -0,0 +1,96 @@+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE FlexibleInstances #-}++module Hans.Setup where++import Hans.Address+import Hans.Address.IP4+import Hans.Address.Mac+import Hans.Channel+import Hans.Layer.Arp+import Hans.Layer.Ethernet+import Hans.Layer.IP4+import Hans.Layer.Icmp4+import Hans.Layer.Tcp+import Hans.Layer.Timer+import Hans.Layer.Udp+++data NetworkStack = NetworkStack+ { nsArp :: ArpHandle+ , nsEthernet :: EthernetHandle+ , nsIp4 :: IP4Handle+ , nsIcmp4 :: Icmp4Handle+ , nsTimers :: TimerHandle+ , nsUdp :: UdpHandle+ , nsTcp :: TcpHandle+ }+++setup :: IO NetworkStack+setup = do+ eth <- newChannel+ arp <- newChannel+ ip4 <- newChannel+ icmp <- newChannel+ th <- newChannel+ udp <- newChannel+ tcp <- newChannel++ runTimerLayer th+ runEthernetLayer eth+ runArpLayer arp eth th+ runIP4Layer ip4 arp eth+ runIcmp4Layer icmp ip4+ runUdpLayer udp ip4 icmp+ runTcpLayer tcp ip4 th++ return NetworkStack+ { nsArp = arp+ , nsEthernet= eth+ , nsIp4 = ip4+ , nsIcmp4 = icmp+ , nsTimers = th+ , nsUdp = udp+ , nsTcp = tcp+ }+++data SomeOption = forall o. Option o => SomeOption o++instance Show SomeOption where+ showsPrec p (SomeOption o) = parens (showsPrec 11 o)+ where+ parens body | p > 10 = showString "(SomeOption " . body . showChar ')'+ | otherwise = showString "SomeOption " . body++toOption :: Option o => o -> SomeOption+toOption = SomeOption++class Show o => Option o where+ apply :: o -> NetworkStack -> IO ()+++instance Option SomeOption where+ apply (SomeOption o) ns = apply o ns++instance Option o => Option [o] where+ apply os ns = mapM_ (`apply` ns) os+++data OptEthernet mask = LocalEthernet mask Mac+ deriving Show++instance Option (OptEthernet IP4Mask) where+ apply (LocalEthernet mask mac) ns = do+ let (addr,_) = getMaskComponents mask+ addLocalAddress (nsArp ns) addr mac+ addIP4RoutingRule (nsIp4 ns) (Direct mask addr 1500)+++data OptRoute mask addr = Route mask addr+ deriving Show++instance Option (OptRoute IP4Mask IP4) where+ apply (Route mask addr) ns = addIP4RoutingRule (nsIp4 ns) (Indirect mask addr)
+ src/Hans/Simple.hs view
@@ -0,0 +1,59 @@+module Hans.Simple (+ -- * UDP Messages+ renderUdp+ , renderIp4++ -- * Ident+ , Ident+ , newIdent+ , nextIdent+ ) where++import Hans.Address.IP4 (IP4)+import Hans.Message.Ip4 (IP4Packet(..),IP4Header(..),IP4Protocol(..)+ ,mkIP4PseudoHeader,splitPacket,renderIP4Packet+ ,emptyIP4Header)+import Hans.Message.Udp (UdpHeader(..),UdpPort,UdpPacket(..),renderUdpPacket)+import qualified Hans.Message.Ip4 as IP4++import Control.Concurrent (MVar,newMVar,modifyMVar)+import Data.Word (Word16)+import qualified Data.ByteString as S++newtype Ident = Ident (MVar IP4.Ident)++newIdent :: IO Ident+newIdent = Ident `fmap` newMVar 0++nextIdent :: Ident -> IO IP4.Ident+nextIdent (Ident var) = modifyMVar var (\i -> return (i+1, i))++type MTU = Word16++fromMTU :: Maybe MTU -> Int+fromMTU = maybe ip4Max (min ip4Max . fromIntegral)+ where ip4Max = 0xffff++-- | Render a UDP message to an unfragmented IP4 packet.+renderUdp :: Ident -> Maybe MTU -> IP4 -> IP4 -> UdpPort -> UdpPort+ -> S.ByteString+ -> IO [S.ByteString]+renderUdp i mb source dest srcPort destPort payload = do+ let prot = IP4Protocol 0x11+ let mk = mkIP4PseudoHeader source dest prot+ let hdr = UdpHeader+ { udpSourcePort = srcPort+ , udpDestPort = destPort+ , udpChecksum = 0+ }+ udp <- renderUdpPacket (UdpPacket hdr payload) mk+ renderIp4 i mb prot source dest udp+++-- | Render an IP4 packet.+renderIp4 :: Ident -> Maybe MTU -> IP4Protocol -> IP4 -> IP4 -> S.ByteString+ -> IO [S.ByteString]+renderIp4 ident mb prot source dest payload = do+ i <- nextIdent ident+ let hdr = (emptyIP4Header prot source dest) { ip4Ident = i }+ mapM renderIP4Packet (splitPacket (fromMTU mb) (IP4Packet hdr payload))
+ src/Hans/Utils.hs view
@@ -0,0 +1,32 @@+module Hans.Utils where++import Control.Monad (MonadPlus(mzero))+import Data.ByteString (ByteString)+import Numeric (showHex)++type DeviceName = String++type Packet = ByteString++type MkPseudoHeader = Int -> Packet++type Endo a = a -> a++-- | Discard the result of a monadic computation.+void :: Monad m => m a -> m ()+void m = m >> return ()++-- | Show a single hex number, padded with a leading 0.+showPaddedHex :: (Integral a) => a -> ShowS+showPaddedHex x+ | x < 0x10 = showChar '0' . base+ | otherwise = base+ where base = showHex x++-- | Lift a maybe into MonadPlus+just :: MonadPlus m => Maybe a -> m a+just = maybe mzero return++-- | Make a singleton list.+singleton :: a -> [a]+singleton x = [x]
+ src/Hans/Utils/Checksum.hs view
@@ -0,0 +1,70 @@+{-# LANGUAGE BangPatterns #-}+-- BANNERSTART+-- - Copyright 2006-2008, Galois, Inc.+-- - This software is distributed under a standard, three-clause BSD license.+-- - Please see the file LICENSE, distributed with this software, for specific+-- - terms and conditions.+-- Author: Adam Wick <awick@galois.com>+-- BANNEREND+-- |A module providing checksum computations to other parts of Hans. The+-- checksum here is the standard Internet 16-bit checksum (the one's +-- complement of the one's complement sum of the data).+module Hans.Utils.Checksum(+ computeChecksum+ , computePartialChecksum+ , clearChecksum+ , pokeChecksum+ )+ where++import Control.Exception (assert)+import Data.Bits (Bits(shiftL,shiftR,complement,clearBit,(.&.),rotate))+import Data.Word (Word8,Word16,Word32)+import Foreign.Storable (pokeByteOff)+import qualified Data.ByteString as S+import qualified Data.ByteString.Unsafe as S+++-- | Clear the two bytes at the checksum offset of a rendered packet.+clearChecksum :: S.ByteString -> Int -> IO S.ByteString+clearChecksum b off = S.unsafeUseAsCStringLen b $ \(ptr,len) -> do+ assert (len > off + 1) (pokeByteOff ptr off (0 :: Word16))+ return b++-- | Poke a checksum into a bytestring.+pokeChecksum :: Word16 -> S.ByteString -> Int -> IO S.ByteString+pokeChecksum cs b off = S.unsafeUseAsCStringLen b $ \(ptr,len) -> do+ assert (off < len + 1) (pokeByteOff ptr off (rotate cs 8))+ return b++-- | Compute the final checksum, using the given initial value.+computeChecksum :: Word32 -> S.ByteString -> Word16+computeChecksum c0 =+ complement . fromIntegral . fold32 . fold32 . computePartialChecksum c0++-- | Compute a partial checksum, yielding a value suitable to be passed to+-- computeChecksum.+computePartialChecksum :: Word32 -> S.ByteString -> Word32+computePartialChecksum base b = result+ where+ !n' = S.length b++ !result+ | odd n' = step most hi 0+ | otherwise = most+ where hi = S.unsafeIndex b (n'-1)++ !most = loop (fromIntegral base) 0++ loop !acc off+ | off < n = loop (step acc hi lo) (off + 2)+ | otherwise = acc+ where hi = S.unsafeIndex b off+ lo = S.unsafeIndex b (off+1)+ n = clearBit n' 0++step :: Word32 -> Word8 -> Word8 -> Word32+step acc hi lo = acc + fromIntegral hi `shiftL` 8 + fromIntegral lo++fold32 :: Word32 -> Word32+fold32 x = (x .&. 0xFFFF) + (x `shiftR` 16)
+ src/Network/TCP/Aux/Misc.hs view
@@ -0,0 +1,372 @@+{-# LANGUAGE ScopedTypeVariables #-}+{--+Copyright (c) 2006, Peng Li+ 2006, Stephan A. Zdancewic+All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are+met:++* Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++* Redistributions in binary form must reproduce the above copyright+ notice, this list of conditions and the following disclaimer in the+ documentation and/or other materials provided with the distribution.++* Neither the name of the copyright owners nor the names of its+ contributors may be used to endorse or promote products derived from+ this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.+--}++module Network.TCP.Aux.Misc where++import Network.TCP.Type.Base+import Network.TCP.Type.Timer+import Network.TCP.Type.Datagram+import Network.TCP.Type.Socket+import Network.TCP.Aux.Param+import Foreign+import Data.Map as Map+import Data.List as List+import Data.Maybe+import Data.List as List+import System.IO.Unsafe+import Control.Exception++debug :: (Monad m) => String -> m a+debug s = seq (unsafePerformIO $ putStrLn s) return undefined+++bound_ports :: Map SocketID (TCPSocket threadt) -> [Port]+bound_ports sockmap = List.map get_local_port (keys sockmap)++-- not considering SO_REUSEADDR+-- bound_port_allowed :: Map SocketID (TCPSocket threadt) -> Port -> Bool+-- bound_port_allowed m p = not $ List.elem p (bound_ports m)++-- lookup_socketid_by_seg :: Map SocketID (TCPSocket threadt) -> TCPSegment -> Maybe SocketID+-- lookup_socketid_by_seg m s =+-- let fakeid = (tcp_dst s, tcp_src s) in+-- if (member fakeid m) then+-- Just fakeid+-- else+-- Nothing+-- +create_timer (curr_time :: Time) (offset :: Time) = curr_time + offset+slow_timer = create_timer++create_timewindow (curr_time :: Time) (offset :: Time) a = Just (Timed a (create_timer curr_time offset))++-- queues++-- enqueue_message msg q = addToQueue q msg+-- enqueue_messages msgs q = foldl addToQueue q msgs+-- +accept_incoming_q0 :: SocketListen -> Bool+accept_incoming_q0 lis =+ (length $ lis_q lis) < (backlog_fudge (lis_qlimit lis))+accept_incoming_q lis = + (length $ lis_q lis) < 3 * (backlog_fudge (lis_qlimit lis `div` 2))+drop_from_q0 lis = + (length $ lis_q0 lis) >= tcp_q0maxlimit++do_tcp_options :: Time -> Bool -> (TimeWindow Timestamp) -> Timestamp -> Maybe (Timestamp,Timestamp)+do_tcp_options curr_time cb_tf_doing_tstmp cb_ts_recent cb_ts_val =+ if cb_tf_doing_tstmp then+ let ts_ecr' = case timewindow_val curr_time cb_ts_recent of+ Just x -> x+ Nothing -> Timestamp 0+ in Just(cb_ts_val, ts_ecr')+ else+ Nothing++calculate_tcp_options_len cb_tf_doing_tstmp =+ if cb_tf_doing_tstmp then 12 else 0++rounddown bs v = if v < bs then v else (v `div` bs) * bs+roundup bs v = ((v+(bs-1)) `div` bs) * bs++calculate_buf_sizes (cb_t_maxseg :: Int) + (seg_mss :: Maybe Int)+ (bw_delay_product_for_rt :: Maybe Int)+ (is_local_conn :: Bool)+ (rcvbufsize :: Int)+ (sndbufsize :: Int)+ (cb_tf_doing_tstmp :: Bool)+ = let t_maxseg' =+ let maxseg = (min cb_t_maxseg (max 64 $ (case seg_mss of Nothing -> mssdflt; Just x-> x))) in+ -- BSD+ maxseg - (calculate_tcp_options_len cb_tf_doing_tstmp)+ in+ let t_maxseg'' = rounddown mclbytes (t_maxseg') in+ let rcvbufsize' = case bw_delay_product_for_rt of Nothing->rcvbufsize; Just x->x in+ let (rcvbufsize'', t_maxseg''') = ( if rcvbufsize' < t_maxseg''+ then (rcvbufsize', rcvbufsize')+ else (min (sb_max) (roundup (t_maxseg'') rcvbufsize'), + t_maxseg'')) in + let sndbufsize' = case bw_delay_product_for_rt of Nothing->sndbufsize; Just x->x in+ let sndbufsize'' = (if sndbufsize' < t_maxseg'''+ then sndbufsize'+ else min (sb_max) (roundup (t_maxseg'') sndbufsize')) in+ let snd_cwnd = t_maxseg''' * ((if is_local_conn then ss_fltsz_local else ss_fltsz)) in+ (rcvbufsize'', sndbufsize'', t_maxseg''', snd_cwnd)+++calculate_bsd_rcv_wnd :: TCPSocket t -> Int +calculate_bsd_rcv_wnd (tcp_sock :: TCPSocket t)=+ let cb = cb_rcv tcp_sock in+ assert ((rcv_adv cb) >= (rcv_nxt cb)) $ -- assertion for debugging+ max (seq_diff (rcv_adv cb) (rcv_nxt cb))+ (freebsd_so_rcvbuf - (bufc_length $ rcvq cb))++send_queue_space sndq_max sndq_size = (sndq_max - sndq_size)++++update_idle (curr_time :: Time) tcp_sock =+ let tt_keep' = if not (st tcp_sock == SYN_RECEIVED && tf_needfin (cb tcp_sock)) then+ Just (slow_timer curr_time tcptv_keep_idle)+ else+ tt_keep $ cb_time tcp_sock+ tt_fin_wait_2' = if st tcp_sock == FIN_WAIT_2 then+ Just (slow_timer curr_time tcptv_maxidle )+ else+ tt_fin_wait_2 $ cb_time tcp_sock+ in+ (tt_keep', tt_fin_wait_2')++-- tcp timing and rtt+++tcp_backoffs = tcp_bsd_backoffs+tcp_syn_backoffs = tcp_syn_backoffs++mode_of :: Maybe (Timed (RexmtMode,Int)) -> Maybe RexmtMode+mode_of (Just (Timed (x,_) _)) = Just x+mode_of Nothing = Nothing++shift_of :: Maybe (Timed (RexmtMode,Int)) -> Int+shift_of (Just (Timed (_,shift) _ )) = shift++-- todo: check types!++-- compute the retransmit timeout to use+computed_rto :: [Int] -> Int -> Rttinf -> Time+computed_rto (backoffs :: [Int]) (shift :: Int) (ri::Rttinf) =+ (to_Int64 $ backoffs !! shift ) * (max (t_rttmin ri) ((t_srtt ri) + 4*(t_rttvar ri)))++-- compute the last-used rxtcur+computed_rxtcur (ri :: Rttinf) =+ max (t_rttmin ri)+ (min (tcptv_rexmtmax)+ ((computed_rto ( if t_wassyn ri then tcp_syn_backoffs else tcp_backoffs )+ (t_lastshift ri) ri )))++start_tt_rexmt_gen (mode :: RexmtMode) (backoffs :: [Int]) (shift :: Int) + (wantmin :: Bool) (ri :: Rttinf) (curr_time :: Time) =+ let rxtcur = max (if wantmin+ then max (t_rttmin ri) (t_lastrtt ri + (2*1000*1000 `div` 100)) -- 2s/100+ else t_rttmin ri )+ ( min (tcptv_rexmtmax )+ ( computed_rto backoffs shift ri) )+ in+ Just ( Timed (mode,shift) (create_timer curr_time rxtcur ) )++start_tt_rexmt = start_tt_rexmt_gen Rexmt tcp_backoffs+start_tt_rexmtsyn = start_tt_rexmt_gen RexmtSyn tcp_syn_backoffs++start_tt_persist (shift :: Int) (ri::Rttinf) (curr_time :: Time) =+ let cur = max (tcptv_persmin)+ (min (tcptv_persmax)+ (computed_rto tcp_backoffs shift ri) )+ in+ Just ( Timed (Persist, shift) (create_timer curr_time cur))++update_rtt :: Time -> Rttinf -> Rttinf+update_rtt rtt ri =+ let (t_srtt'', t_rttvar'')+ = if tf_srtt_valid ri then+ let delta = (rtt - 1000*10) - (t_srtt ri) -- 1000*10 = 1/HZ+ vardelta = (abs delta) - (t_rttvar ri)+ t_srtt' = max (1000*1000 `div` (32*100)) (t_srtt ri + (delta `div` 8))+ t_rttvar'=max (1000*1000 `div` (16*100)) (t_rttvar ri + (vardelta `div` 4))+ in (t_srtt', t_rttvar')+ else+ let t_srtt' = rtt+ t_rttvar' = rtt `div` 2+ in (t_srtt',t_rttvar')+ in+ ri { t_rttupdated = t_rttupdated ri + 1+ , tf_srtt_valid = True+ , t_srtt = t_srtt''+ , t_rttvar = t_rttvar''+ , t_lastrtt = rtt+ , t_lastshift = 0+ , t_wassyn = False+ }++expand_cwnd ssthresh maxseg maxwin cwnd+ = min maxwin (cwnd + (if cwnd > ssthresh then (maxseg * maxseg) `div` cwnd else maxseg))++-- Path MTU Discovery++mtu_tab = [65535, 32000, 17914, 8166, 4352, 2002, 1492, 1006, 508, 296, 88]++next_smaller :: [Int] -> Int -> Int+next_smaller (x:xs) value = if value >= x then x else next_smaller xs value+++initial_cb_time = TCBTiming+ { tt_keep = Nothing+ , tt_conn_est = Nothing+ , tt_fin_wait_2 = Nothing+ , tt_2msl = Nothing+ , t_idletime = 0+ , ts_recent = Nothing+ , t_badrxtwin = Nothing+ }++initial_cb_snd = TCBSending+ { sndq = bufferchain_empty+ , snd_una = SeqLocal 0+ , snd_wnd = 0+ , snd_wl1 = SeqForeign 0+ , snd_wl2 = SeqLocal 0+ , snd_cwnd = tcp_maxwin `shiftL` tcp_maxwinscale+ , snd_nxt = SeqLocal 0+ , snd_max = SeqLocal 0+ , t_dupacks = 0+ , t_rttinf = Rttinf { t_rttupdated = 0+ , tf_srtt_valid = False+ , t_srtt = tcptv_rtobase+ , t_rttvar = tcptv_rttvarbase+ , t_rttmin = tcptv_min+ , t_lastrtt = 0+ , t_lastshift = 0+ , t_wassyn = False+ }+ , t_rttseg = Nothing+ , tt_rexmt = Nothing+ }++{-# INLINE hasfin #-}+{-# INLINE tcp_reass #-}+{-# INLINE tcp_reass_prune #-}++hasfin seg = case trs_FIN seg of True -> 1; False -> 0++-- returns (1) the string +-- (2) the SEQ for the next byte+-- (3) whether FIN has been reached+-- (4) remaining...+-- this is a very SLOW algorithm and should be replaced ....++tcp_reass :: SeqForeign -> [TCPReassSegment] -> (BufferChain, SeqForeign, Bool, [TCPReassSegment])+tcp_reass seq rsegq =+ let searchpkt rseg = + let seq1 = (trs_seq rseg)+ seq2 = seq1 `seq_plus` (bufc_length $ trs_data rseg) `seq_plus` (hasfin rseg)+ in (seq >= seq1 && seq < seq2)+ in+ case List.find searchpkt rsegq of+ Nothing -> + (bufferchain_empty, seq, False, rsegq)+ Just rseg -> + let data_to_trim = seq `seq_diff` (trs_seq rseg) in+ let result_buf = bufferchain_drop data_to_trim (trs_data rseg) in+ let next_seq = (trs_seq rseg) `seq_plus` (bufc_length $ trs_data rseg) `seq_plus` (hasfin rseg) in+ let new_rsegq = tcp_reass_prune next_seq rsegq in+ if trs_FIN rseg then+ (result_buf+ , next_seq+ , True+ , new_rsegq+ )+ else+ let (bufc2, next_seq2, hasfin2, rsegq2) = tcp_reass next_seq new_rsegq in+ ( bufferchain_concat result_buf bufc2+ , next_seq2+ , hasfin2+ , rsegq2+ )++tcp_reass_prune :: SeqForeign -> [TCPReassSegment] -> [TCPReassSegment]+tcp_reass_prune seq rsegq = + List.filter (\seg -> + let nxtseq = (trs_seq seg) `seq_plus` (bufc_length $ trs_data seg) `seq_plus` (hasfin seg)+ in nxtseq > seq + ) rsegq++initial_cb_rcv = TCBReceiving+ { last_ack_sent = SeqForeign 0+ , tf_rxwin0sent = False+ , tf_shouldacknow = False+ , tt_delack = False+ , rcv_adv = SeqForeign 0+ , rcv_wnd = 0+ , rcv_nxt = SeqForeign 0+ , rcvq = bufferchain_empty+ , t_segq = []+ }++initial_cb_misc = TCBMisc+ { -- retransmission+ snd_ssthresh = tcp_maxwin `shiftL` tcp_maxwinscale+ , snd_cwnd_prev = 0+ , snd_ssthresh_prev = 0+ , snd_recover = SeqLocal 0+ -- some tags+ , cantsndmore = False+ , cantrcvmore = False+ , bsd_cantconnect = False+ -- initialization parameters+ , self_id = SocketID (0,TCPAddr (IPAddr 0,0))+ , parent_id = SocketID (0,TCPAddr (IPAddr 0,0))+ , local_addr = TCPAddr (IPAddr 0,0)+ , remote_addr = TCPAddr (IPAddr 0,0)+ , t_maxseg = mssdflt+ , t_advmss = Nothing+ , tf_doing_ws = False + , tf_doing_tstmp = False+ , tf_req_tstmp = False+ , request_r_scale = Nothing+ , snd_scale = 0+ , rcv_scale = 0+ , iss = SeqLocal 0+ , irs = SeqForeign 0+ -- other things i don't use for the moment+ , sndurp = Nothing+ , rcvurp = Nothing+ , iobc = NO_OOBDATA+ , rcv_up = SeqForeign 0+ , tf_needfin = False+ }++initial_tcp_socket = TCPSocket+ { st = CLOSED+ , cb_time = initial_cb_time+ , cb_snd = initial_cb_snd+ , cb_rcv = initial_cb_rcv+ , cb = initial_cb_misc+ , sock_listen = SocketListen [] [] 0+ , waiting_list = []+ }++empty_sid :: SocketID+empty_sid = SocketID (0,TCPAddr (IPAddr 0,0))++
+ src/Network/TCP/Aux/Output.hs view
@@ -0,0 +1,213 @@+{-# LANGUAGE ScopedTypeVariables #-}+{--+Copyright (c) 2006, Peng Li+ 2006, Stephan A. Zdancewic+All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are+met:++* Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++* Redistributions in binary form must reproduce the above copyright+ notice, this list of conditions and the following disclaimer in the+ documentation and/or other materials provided with the distribution.++* Neither the name of the copyright owners nor the names of its+ contributors may be used to endorse or promote products derived from+ this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.+--}++module Network.TCP.Aux.Output where++import Hans.Message.Tcp++import Network.TCP.Type.Base+import Network.TCP.Type.Timer+import Network.TCP.Type.Datagram as Datagram+import Network.TCP.Type.Socket+import Network.TCP.Type.Syscall+import Network.TCP.Aux.Param+import Network.TCP.Aux.Misc+import Hans.Layer.Tcp.Monad+import Foreign+import Control.Exception+import Control.Monad++make_syn_segment :: Time -> TCPSocket t -> Timestamp -> TCPSegment+make_syn_segment curr_time sock (ts_val::Timestamp) = + let ws = request_r_scale $ cb sock -- should assert it's <= tcp_maxwinscale ?+ mss = t_advmss $ cb $ sock+ ts = do_tcp_options curr_time (tf_req_tstmp $ cb sock) (ts_recent $ cb_time $ sock ) ts_val+ hdr =+ set_tcp_mss mss $+ set_tcp_ws ws $+ set_tcp_ts ts $ emptyTcpHeader+ { tcpSeqNum = TcpSeqNum (seq_val (iss (cb sock)))+ , tcpAckNum = TcpAckNum 0+ , tcpSyn = True+ , tcpWindow = fromIntegral (rcv_wnd (cb_rcv sock))+ }+ in mkTCPSegment'+ (local_addr (cb sock)) (remote_addr (cb sock)) hdr bufferchain_empty++make_syn_ack_segment curr_time sock (addrfrom::TCPAddr) (addrto::TCPAddr) (ts_val::Timestamp) = + let urp_any = 0+ tcb = cb sock+ win = rcv_wnd (cb_rcv sock) -- `shiftR` (rcv_scale $ cb sock)+ ws = if tf_doing_ws tcb then Just (rcv_scale tcb) else Nothing+ mss = t_advmss tcb + ts = do_tcp_options curr_time (tf_req_tstmp tcb) (ts_recent $ cb_time sock) ts_val+ hdr =+ set_tcp_mss mss $+ set_tcp_ws ws $+ set_tcp_ts ts $ emptyTcpHeader+ { tcpSeqNum = TcpSeqNum (seq_val (iss tcb))+ , tcpAckNum = TcpAckNum (fseq_val (rcv_nxt (cb_rcv sock)))+ , tcpAck = True+ , tcpSyn = True+ , tcpWindow = fromIntegral win+ , tcpUrgentPointer = urp_any+ }+ in mkTCPSegment' addrfrom addrto hdr bufferchain_empty++make_ack_segment curr_time sock (fin::Bool) (ts_val::Timestamp) = + let urp_garbage = 0+ tcb = cb sock+ win = (rcv_wnd $ cb_rcv sock) `shiftR` (rcv_scale tcb)+ ts = do_tcp_options curr_time (tf_req_tstmp tcb) (ts_recent $ cb_time sock) ts_val+ sn | fin = snd_una (cb_snd sock)+ | otherwise = snd_nxt (cb_snd sock)+ hdr =+ set_tcp_ts ts $ emptyTcpHeader+ { tcpSeqNum = TcpSeqNum (seq_val sn)+ , tcpAckNum = TcpAckNum (fseq_val (rcv_nxt (cb_rcv sock)))+ , tcpAck = True+ , tcpFin = fin+ , tcpWindow = fromIntegral win+ , tcpUrgentPointer = urp_garbage+ }+ in mkTCPSegment' (local_addr tcb) (remote_addr tcb) hdr bufferchain_empty++bsd_make_phantom_segment curr_time sock (addrfrom::TCPAddr) (addrto::TCPAddr) (ts_val::Timestamp) (cantsendmore::Bool) = + let urp_garbage = 0+ tcb = cb sock+ scb = cb_snd sock+ rcb = cb_rcv sock+ win = (rcv_wnd rcb) `shiftR` (rcv_scale tcb)+ fin = (cantsendmore && seq_lt (snd_una scb) (seq_minus (snd_max scb) 1))+ ts = do_tcp_options curr_time (tf_req_tstmp tcb) (ts_recent $ cb_time sock) ts_val+ sn | fin = snd_una scb+ | otherwise = snd_max scb+ hdr =+ set_tcp_ts ts emptyTcpHeader+ { tcpSourcePort = TcpPort 0+ , tcpDestPort = TcpPort 0+ , tcpSeqNum = TcpSeqNum (seq_val sn)+ , tcpAckNum = TcpAckNum (fseq_val (rcv_nxt rcb))+ , tcpFin = fin+ , tcpWindow = fromIntegral win+ , tcpUrgentPointer = urp_garbage+ }+ in+ mkTCPSegment' addrfrom addrto hdr bufferchain_empty++make_rst_segment_from_cb sock (addrfrom::TCPAddr) (addrto::TCPAddr) = + let hdr = emptyTcpHeader+ { tcpSourcePort = TcpPort 0+ , tcpDestPort = TcpPort 0+ , tcpSeqNum = TcpSeqNum (seq_val (snd_nxt (cb_snd sock)))+ , tcpAckNum = TcpAckNum (fseq_val (rcv_nxt (cb_rcv sock)))+ , tcpAck = False+ , tcpRst = False+ }+ in mkTCPSegment' addrfrom addrto hdr bufferchain_empty++make_rst_segment_from_seg (seg::TCPSegment) =+ let tcp_ACK' = not (tcp_ACK seg)+ seq' = if tcp_ACK seg then tcp_ack seg else 0+ ack' = if tcp_ACK'+ then let s1 = tcp_seq seg+ in s1 `seq_plus` bufc_length (tcp_data seg)+ `seq_plus` (if tcp_SYN seg then 1 else 0)+ else 0+++ hdr = emptyTcpHeader+ { tcpSeqNum = TcpSeqNum seq'+ , tcpAckNum = TcpAckNum ack'+ , tcpAck = tcp_ACK'+ , tcpRst = True+ }+ in+ mkTCPSegment' (tcp_src seg) (tcp_dst seg) hdr bufferchain_empty+++dropwithreset (seg::TCPSegment) =+ if tcp_RST seg then []+ else let seg' = make_rst_segment_from_seg seg+ in [TCPMessage seg']++dropwithreset_ignore_or_fail = dropwithreset++tcp_close_temp sock =+ sock { cb = (cb sock) { cantrcvmore = True+ , cantsndmore = True+ , local_addr = TCPAddr (IPAddr 0,0)+ , remote_addr = TCPAddr (IPAddr 0,0)+ , bsd_cantconnect = True+ }+ , st = CLOSED+ , cb_snd = (cb_snd sock) { sndq = bufferchain_empty }+ }+++tcp_close :: SocketID -> HMonad t ()+tcp_close sid = + do b <- has_sock sid+ when b $ do+ sock <- lookup_sock sid+ let pending_tasks = waiting_list sock+ has_parent = (get_local_port $ parent_id $ cb sock) /= 0+ let result = map (\(_,cont) -> cont (SockError "tcpclose")) pending_tasks+ emit_ready result+ delete_sock sid+ when (not has_parent) $ free_local_port $ get_local_port sid++tcp_drop_and_close :: SocketID -> HMonad t ()+tcp_drop_and_close sid = + do b <- has_sock sid+ when b $ do+ sock <- lookup_sock sid+ let outsegs = if st sock `notElem` [CLOSED,LISTEN,SYN_SENT] + then [TCPMessage $ make_rst_segment_from_cb + (sock) (local_addr $ cb sock) (remote_addr $ cb sock)]+ else []+ emit_segs outsegs+ tcp_close sid++alloc_local_port :: HMonad t (Maybe Port)+alloc_local_port = do+ h <- get_host+ case local_ports h of+ [] -> return Nothing+ port:rest -> do put_host $ h { local_ports = rest }+ return $ Just port++free_local_port port =+ modify_host $ \h -> h { local_ports = port:(local_ports h) }+
+ src/Network/TCP/Aux/Param.hs view
@@ -0,0 +1,120 @@+{-# LANGUAGE ScopedTypeVariables #-}+{--+Copyright (c) 2006, Peng Li+ 2006, Stephan A. Zdancewic+All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are+met:++* Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++* Redistributions in binary form must reproduce the above copyright+ notice, this list of conditions and the following disclaimer in the+ documentation and/or other materials provided with the distribution.++* Neither the name of the copyright owners nor the names of its+ contributors may be used to endorse or promote products derived from+ this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.+--}++module Network.TCP.Aux.Param where++import Network.TCP.Type.Base++dschedmax = seconds_to_time 1+dinput_queuemax = seconds_to_time 1+doutput_queuemax = seconds_to_time 1++hz = 100+ +tickintvlmin = seconds_to_time $ 100/(105*hz)+tickintvlmax = seconds_to_time $ 105/(100*hz)++slow_timer_intvl = seconds_to_time $ 1/2+-- slow_timer_model_intvl = seconds_to_time $ 1/1000++fast_timer_intvl = seconds_to_time $ 1/5+-- fast_timer_model_intvl = seconds_to_time $ 1/1000++kern_timer_intvl = tickintvlmax+-- kern_timer_model_intvl = +++-- listen queue length+somaxconn ::Int= 128++-- buffers+mclbytes ::Int= 2048+msize ::Int= 256+sb_max ::Int= 256*1024++-- rfc limits++dtsinval::Time = seconds_to_time $ 24*24*60*60+tcp_maxwin :: Int = 65535+tcp_maxwinscale :: Int = 14+++--default+freebsd_so_rcvbuf :: Int = 42080+freebsd_so_sndbuf :: Int = 9216++-- tcp parameters++mssdflt :: Int = 1400 -- 512+ss_fltsz_local :: Int = 4+ss_fltsz :: Int = 1+tcp_do_newreno = True+tcp_q0minlimit :: Int = 30+tcp_q0maxlimit :: Int = 512*30++backlog_fudge :: Int -> Int+backlog_fudge n = min somaxconn n++-- time values (TCP only)++tcptv_delack = seconds_to_time 0.1+tcptv_rtobase = seconds_to_time 3+tcptv_rttvarbase = seconds_to_time 0+tcptv_min = seconds_to_time 1+tcptv_rexmtmax = seconds_to_time 64+tcptv_msl = seconds_to_time 1 -- this is too stringent... good for testing+tcptv_persmin = seconds_to_time 5+tcptv_persmax = seconds_to_time 60+tcptv_keep_init = seconds_to_time 75+tcptv_keep_idle = seconds_to_time $ 120*60+tcptv_keepintvl = seconds_to_time $ 75+tcptv_keepcnt = seconds_to_time $ 8+tcptv_maxidle = tcptv_keepintvl*8+++-- timing related parameters (TCP only)++tcp_bsd_backoffs :: [Int]= [1,2,4,8,16,32,64, 64,64,64, 64,64,64]+tcp_linux_backoffs = [1,2,4,8,16,32,64, 128,256,512, 512]+tcp_winxp_backoffs = [1,2,4,8,16]++--tcp_maxrxtshift = 12+tcp_maxrxtshift :: Int = 3 -- this is not right... for testing only+tcp_synackmaxrxtshift :: Int = 3++tcp_syn_bsd_backoffs :: [Int] = [1,1,1,1,1,2,4,8,16,32,64,64,64]+tcp_syn_linux_backoffs =[1,2,4,8,16]+tcp_syn_winxp_backoffs=[1,2]++listen_qlimit :: Int = 100
+ src/Network/TCP/Aux/SockMonad.hs view
@@ -0,0 +1,127 @@+{--+Copyright (c) 2006, Peng Li+ 2006, Stephan A. Zdancewic+All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are+met:++* Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++* Redistributions in binary form must reproduce the above copyright+ notice, this list of conditions and the following disclaimer in the+ documentation and/or other materials provided with the distribution.++* Neither the name of the copyright owners nor the names of its+ contributors may be used to endorse or promote products derived from+ this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.+--}++module Network.TCP.Aux.SockMonad where++import Network.TCP.Type.Base+import Network.TCP.Type.Socket+import Network.TCP.Aux.Misc+import Hans.Layer.Tcp.Monad+import Control.Exception++data HState t = HState + { hs_host :: !(Host t)+ , hs_sock :: !(TCPSocket t)+ } ++newtype SMonad t a = SMonad (HState t -> (a, HState t))++instance Monad (SMonad t) where+ return a = SMonad $ \s -> (a,s)+ x >>= f = bindSMonad x f+ {-# INLINE return #-}+ {-# INLINE (>>=) #-}++bindSMonad :: SMonad t a -> (a -> SMonad t b) -> SMonad t b+bindSMonad (SMonad x) f = + SMonad $ \s -> let (res, s') = x s+ (SMonad z) = f res in z s'+{-# INLINE bindSMonad #-}++get_host_ :: SMonad t (Host t)+get_host_ = SMonad $ \s -> (hs_host s,s)++modify_host_ f = SMonad $ \s -> ((), s { hs_host = f (hs_host s)})+emit_segs_ segs = modify_host_ $ \h -> h { output_queue = (output_queue h)++ segs}+emit_ready_ threads = modify_host_ $ \h -> h { ready_list = (ready_list h)++ threads}++{-# INLINE get_host_ #-}+{-# INLINE modify_host_ #-}+{-# INLINE emit_segs_ #-}+{-# INLINE emit_ready_ #-}++--------------------------------------------------+-- get_sid = do+-- SMonad $ \s -> (hs_sid s,s)+-- {-# INLINE get_sid #-}++get_sock = do+ SMonad $ \s -> (hs_sock s,s)++put_sock sock = do+ SMonad $ \s -> ((), s { hs_sock = sock})++modify_sock f = do+ SMonad $ \s -> ((), s { hs_sock = f (hs_sock s)})+modify_cb f =+ SMonad $ \s-> let sock=hs_sock s in ((), s { hs_sock=sock { cb =f (cb sock) }})+modify_cb_snd f =+ SMonad $ \s-> let sock=hs_sock s in ((), s { hs_sock=sock { cb_snd=f (cb_snd sock) }})+modify_cb_rcv f =+ SMonad $ \s-> let sock=hs_sock s in ((), s { hs_sock=sock { cb_rcv=f (cb_rcv sock) }})+modify_cb_time f=+ SMonad $ \s-> let sock=hs_sock s in ((), s { hs_sock=sock {cb_time=f (cb_time sock)}})+{-# INLINE get_sock #-}+{-# INLINE put_sock #-}+{-# INLINE modify_sock #-}+{-# INLINE modify_cb #-}+{-# INLINE modify_cb_snd #-}+{-# INLINE modify_cb_rcv #-}+{-# INLINE modify_cb_time #-}+++-----------------------------------------------------+-- has_sock_ :: SocketID -> SMonad t Bool+-- has_sock_ sid = do +-- h <- get_host_+-- return $ Map.member sid (sock_map h)+-- +-- lookup_sock_ sid = do+-- h <- get_host_+-- res <- Map.lookup sid (sock_map h)+-- return res+-- +-- {-# INLINE has_sock_ #-}+-- {-# INLINE lookup_sock_ #-}+-- +------------------++runSMonad :: SocketID -> (SMonad t a) -> HMonad t a+runSMonad sid (SMonad m) = do+ h <- get_host+ sock <- lookup_sock sid+ let initstate = HState h sock + let (res, finalstate) = m initstate+ put_host $ hs_host finalstate+ update_sock sid $ \_ -> hs_sock finalstate+ return res
+ src/Network/TCP/LTS/In.hs view
@@ -0,0 +1,200 @@+{--+Copyright (c) 2006, Peng Li+ 2006, Stephan A. Zdancewic+All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are+met:++* Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++* Redistributions in binary form must reproduce the above copyright+ notice, this list of conditions and the following disclaimer in the+ documentation and/or other materials provided with the distribution.++* Neither the name of the copyright owners nor the names of its+ contributors may be used to endorse or promote products derived from+ this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.+--}++module Network.TCP.LTS.In+ ( tcp_deliver_in_packet+ )+where++import Hans.Layer.Tcp.Monad+import Hans.Message.Tcp++import Foreign+import Foreign.C+import Data.List as List+import Control.Exception+import Control.Monad++import Network.TCP.Type.Base+import Network.TCP.Type.Syscall+import Network.TCP.Type.Timer+import Network.TCP.Type.Socket+import Network.TCP.Type.Datagram+import Network.TCP.Aux.Param++import Network.TCP.Aux.Misc+import Network.TCP.Aux.Output+import Network.TCP.Aux.SockMonad+import Network.TCP.Aux.Output++import Network.TCP.LTS.InPassive+import Network.TCP.LTS.InActive+import Network.TCP.LTS.InData+import Network.TCP.LTS.User+import Network.TCP.LTS.Out++tcp_deliver_in_packet seg = do+ let sid = SocketID (get_port (tcp_dst seg), tcp_src seg)+ ok <- has_sock sid+ if ok+ then tcp_deliver_packet_to_sock sid seg+ else if tcp_SYN seg && (not $ tcp_ACK seg) && (not $ tcp_RST seg) + then tcp_deliver_syn_packet seg+ else emit_segs $ dropwithreset seg++-- Note: if there exists a socket in TIME_WAIT state, and an SYN+-- packet matches it, the SYN packet will always be delivered to this+-- socket; it will never be delivered to a listening socket. This+-- makes the implementation simpler...++--pre-condition: sid exists+tcp_deliver_packet_to_sock :: SocketID -> TCPSegment -> HMonad t () +tcp_deliver_packet_to_sock sid seg =+ do h <- get_host+ sock <- lookup_sock sid+ let tcb = cb sock+ rcb = cb_rcv sock+ scb = cb_snd sock+ seqnum = SeqForeign (tcp_seq seg)+ acknum = SeqLocal (tcp_ack seg)++ success <- header_prediction seg h sid sock tcb rcb scb seqnum acknum + when (not success) $+ case st sock of+ CLOSED -> assert (False) return ()+ LISTEN -> assert (False) return ()+ SYN_SENT -> let goodack = (iss tcb) < acknum && acknum <= (snd_max scb) in+ if tcp_RST seg then + when (tcp_ACK seg && goodack) $ tcp_close sid + else + if tcp_SYN seg && tcp_ACK seg then+ if goodack then runSMonad sid $ deliver_in_2 seg+ else emit_segs $ dropwithreset seg+ else return ()+ SYN_RECEIVED ->+ let invalidack = acknum <= snd_una scb || acknum > snd_max scb in+ if tcp_RST seg then + tcp_close sid+ else if tcp_SYN seg || not (tcp_ACK seg) then -- check with spec?+ return ()+ else if invalidack || (seqnum < (irs tcb)) then+ return ()+ else do+ sock <- runSMonad sid $ deliver_in_3 seg+ if st sock == CLOSED then+ tcp_close sid+ else when (st sock /= SYN_RECEIVED) $+ di3_socks_update sid+ _ -> if tcp_RST seg then + when (st sock /= TIME_WAIT) $ tcp_close sid+ else if tcp_SYN seg then + when (st sock==TIME_WAIT) $ emit_segs $ dropwithreset seg+ else + if st sock `elem` [FIN_WAIT_1, CLOSING, LAST_ACK, FIN_WAIT_2, TIME_WAIT] + && seqnum `seq_plus` (bufc_length $ tcp_data seg) > (rcv_nxt rcb)+ then return () -- data coming into closing socket?+ else do sock <- runSMonad sid $ deliver_in_3 seg+ --debug $ (show $ st sock)+ when (st sock == CLOSED) $ tcp_close sid++{-# INLINE header_prediction #-}+header_prediction seg h sid sock tcb rcb scb seqnum acknum =+ if st sock == ESTABLISHED+ && not (tcp_SYN seg) + && not (tcp_FIN seg)+ && not (tcp_URG seg)+ && not (tcp_RST seg)+ && tcp_ACK seg+ && seqnum == rcv_nxt rcb+ && snd_wnd scb == fromIntegral (tcp_win seg) `shiftL` snd_scale tcb+ && snd_max scb == snd_nxt scb+ then if bufc_length (tcp_data seg) == 0+ && acknum > (snd_una scb)+ && acknum <= (snd_max scb)+ && snd_cwnd scb >= snd_wnd scb+ && t_dupacks scb < 3+ then do -- pure ack for outstanding data+ --------------------------------------------------------------------------------+ --debug $ "prediction 2.1!"+ let emission_time = case (tcp_ts seg, t_rttseg scb) of+ (Just (ts_val, ts_ecr), _ ) -> Just (ts_ecr `seq_minus` 1)+ (Nothing, Just (ts0, seq0)) -> if acknum > seq0 then Just ts0 else Nothing+ (Nothing, Nothing) -> Nothing+ let t_rttinf' = case emission_time of+ Just emtime -> assert ((ticks h) >= emtime) $+ update_rtt ( ((ticks h) `seq_diff` emtime)*10000 ) (t_rttinf scb)+ Nothing -> t_rttinf scb+ let tt_rexmt' = if acknum == snd_max scb then+ Nothing+ else case mode_of (tt_rexmt scb) of+ Nothing -> start_tt_rexmt 0 True t_rttinf' (clock h)+ Just Rexmt -> start_tt_rexmt 0 True t_rttinf' (clock h)+ _ -> tt_rexmt scb+ let acked = acknum `seq_diff` (snd_una scb)+ let snd_wnd' = snd_wnd scb - acked+ let sndq' = bufferchain_drop acked (sndq scb)+ runSMonad sid $ do+ modify_sock $ \s -> s { cb_snd = scb + { sndq = sndq'+ , t_dupacks = 0+ , t_rttinf = t_rttinf'+ , tt_rexmt = tt_rexmt'+ , t_rttseg = if emission_time == Nothing then t_rttseg scb else Nothing+ , snd_cwnd = expand_cwnd (snd_ssthresh tcb) + (t_maxseg tcb) + (tcp_maxwin `shiftL` (snd_scale tcb))+ (snd_cwnd scb)+ , snd_wnd = snd_wnd'+ , snd_una = acknum+ --, snd_nxt = max acknum (snd_nxt scb)+ }+ }+ tcp_wakeup+ tcp_output_all+ return True+ --------------------------------------------------------------------------------+ else if acknum == snd_una scb+ && List.null (t_segq rcb)+ && bufc_length (tcp_data seg) < (freebsd_so_rcvbuf - (bufc_length $ rcvq rcb))+ then do -- pure in-sequence data packet + --------------------------------------------------------------------------------+ return False+ --------------------------------------------------------------------------------+ else do+ -- debug $ "predictions 2.1, 2.2 fail!"+ return False+ else do+ -- debug $ "prediction 1 fail!" ++ (show $ snd_wnd tcb) + -- ++ " " ++ (show (tcp_win seg)) ++ " " ++ (show $ snd_scale tcb)+ return False+
+ src/Network/TCP/LTS/InActive.hs view
@@ -0,0 +1,170 @@+{--+Copyright (c) 2006, Peng Li+ 2006, Stephan A. Zdancewic+All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are+met:++* Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++* Redistributions in binary form must reproduce the above copyright+ notice, this list of conditions and the following disclaimer in the+ documentation and/or other materials provided with the distribution.++* Neither the name of the copyright owners nor the names of its+ contributors may be used to endorse or promote products derived from+ this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.+--}++module Network.TCP.LTS.InActive where++import Foreign+import Foreign.C+import Data.Maybe+import Network.TCP.Type.Base+import Network.TCP.Type.Syscall+import Network.TCP.Aux.Misc+import Network.TCP.Aux.Param+import Network.TCP.Aux.Output+import Network.TCP.Type.Socket+import Network.TCP.Type.Datagram+import Network.TCP.Aux.SockMonad+import Network.TCP.LTS.User++deliver_in_2 seg = do + sock <- get_sock+ h <- get_host_+ --debug $ "deliver_in_3 " ++ (show seg)+ let tcb = cb sock+ scb = cb_snd sock+ rcb = cb_rcv sock+ acknum = SeqLocal (tcp_ack seg)+ seqnum = SeqForeign (tcp_seq seg)+ let { + -- window scaling+ (rcv_scale', snd_scale', tf_doing_ws') = + ( case (request_r_scale tcb, tcp_ws seg) of+ (Just rs, Just ss) -> (rs, ss, True)+ _ -> (0,0,False)+ );++ -- timestamping++ tf_rcvd_tstmp' = isJust $ tcp_ts seg;+ tf_doing_tstmp' = tf_rcvd_tstmp' && (tf_req_tstmp tcb);+ -- mss negotiation+ ourmss = ( case (t_advmss tcb) of+ Nothing -> (t_maxseg tcb)+ Just v -> v+ );++ (rcvbufsize', sndbufsize', t_maxseg'', snd_cwnd') = + calculate_buf_sizes ourmss (tcp_mss seg) Nothing False + (freebsd_so_rcvbuf) (freebsd_so_sndbuf) tf_doing_tstmp';++ rcv_window = min tcp_maxwin freebsd_so_rcvbuf;++ emission_time = + ( case tcp_ts seg of+ Just (ts_val, ts_ecr) -> Just (ts_ecr `seq_minus` 1)+ Nothing -> case t_rttseg scb of+ Just (ts0, seq0) -> if acknum > seq0 then Just ts0 else Nothing+ Nothing -> Nothing;+ );++ t_rttseg' = ( case emission_time of+ Nothing -> Nothing+ Just _ -> t_rttseg scb );++ t_rttinf' = ( case emission_time of+ Just emtime -> update_rtt ( ((ticks h) `seq_diff` emtime)*10*1000 ) (t_rttinf scb)+ Nothing -> t_rttinf scb );+ + tt_rexmt' = if acknum == snd_max scb then Nothing else tt_rexmt scb;+ + fin' = tcp_FIN seg;+ rcvq' = tcp_data seg;+ rcv_nxt' = seqnum `seq_plus` 1 `seq_plus` (if fin' then 1 else 0);+ rcv_wnd' = rcv_window - (bufc_length $ tcp_data seg);++ cantrcvmore' = if fin' then True else cantrcvmore tcb;++ new_st = if fin' then + if cantsndmore tcb then LAST_ACK else CLOSE_WAIT+ else + if cantsndmore tcb then+ if snd_max scb > iss tcb `seq_plus` 1 && acknum >= snd_max scb then+ FIN_WAIT_2+ else+ FIN_WAIT_1+ else+ ESTABLISHED;++ newsock = sock+ { st = new_st+ , cb_time = (cb_time sock) + { t_idletime = clock h+ , tt_keep = Just (create_timer (clock h) tcptv_keep_idle)+ , tt_conn_est = Nothing+ , ts_recent = case tcp_ts seg of+ Nothing -> ts_recent $ cb_time sock+ Just (ts_val, ts_ecr) -> create_timewindow (clock h) dtsinval (ts_val)+ }+ , cb_snd = scb+ { tt_rexmt = tt_rexmt'+ , snd_una = acknum+ , snd_nxt = if cantsndmore tcb then acknum else snd_nxt scb+ , snd_max = if cantsndmore tcb && acknum > snd_max scb then acknum else snd_max scb+ , snd_wl1 = seqnum `seq_plus` 1+ , snd_wl2 = acknum+ , snd_wnd = fromIntegral (tcp_win seg) `shiftL` snd_scale'+ , snd_cwnd = if acknum > (iss tcb `seq_plus` 1) + then min snd_cwnd' (tcp_maxwin `shiftL` snd_scale')+ else snd_cwnd'+ , t_rttseg = t_rttseg'+ , t_rttinf = t_rttinf'+ }+ , cb_rcv = rcb+ { rcvq = rcvq'+ , tt_delack = False+ , rcv_nxt = rcv_nxt'+ , rcv_wnd = rcv_wnd'+ , tf_rxwin0sent = (rcv_wnd' == 0)+ , rcv_adv = rcv_nxt' `seq_plus` (( rcv_wnd' `shiftR` rcv_scale') `shiftL` rcv_scale')+ , last_ack_sent = rcv_nxt'+ }+ , cb = tcb+ { --local_addr = tcp_dst seg+ rcv_scale = rcv_scale'+ , snd_scale = snd_scale'+ , tf_doing_ws = tf_doing_ws'+ , irs = seqnum+ , t_maxseg = t_maxseg''+ , tf_req_tstmp = tf_doing_tstmp'+ , tf_doing_tstmp = tf_doing_tstmp'+ , cantrcvmore = cantrcvmore'+ }+ };+ }+ put_sock newsock+ emit_segs_ [ TCPMessage $ make_ack_segment (clock h) newsock + (cantsndmore tcb && acknum < (iss tcb `seq_plus` 2)) (ticks h)]+ tcp_wakeup+ return ()++
+ src/Network/TCP/LTS/InData.hs view
@@ -0,0 +1,385 @@+{--+Copyright (c) 2006, Peng Li+ 2006, Stephan A. Zdancewic+All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are+met:++* Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++* Redistributions in binary form must reproduce the above copyright+ notice, this list of conditions and the following disclaimer in the+ documentation and/or other materials provided with the distribution.++* Neither the name of the copyright owners nor the names of its+ contributors may be used to endorse or promote products derived from+ this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.+--}++module Network.TCP.LTS.InData where++import Foreign+import Foreign.C+import Control.Exception+import Control.Monad+import Data.List as List++import Network.TCP.Type.Base+import Network.TCP.Type.Timer+import Network.TCP.Type.Socket+import Network.TCP.Type.Datagram as Datagram+import Network.TCP.Type.Syscall++import Network.TCP.Aux.Param+import Network.TCP.Aux.Misc+import Network.TCP.Aux.Output+import Hans.Layer.Tcp.Monad+import Network.TCP.Aux.SockMonad++import Network.TCP.LTS.User+import Network.TCP.LTS.Out++deliver_in_3 seg = + do sock <- get_sock+ h <- get_host_+ --debug $ "deliver_in_3 " ++ (show seg)+ let tcb = cb sock+ scb = cb_snd sock+ acknum = SeqLocal (tcp_ack seg)+ seqnum = SeqForeign (tcp_seq seg) `seq_plus`+ if tcp_SYN seg then 1 else 0+ seg_win = fromIntegral (tcp_win seg) `shiftL` (snd_scale tcb)+ let wesentafin = (snd_max scb) > (snd_una scb `seq_plus` (bufc_length $ sndq scb))+ ourfinisacked = wesentafin && tcp_ACK seg && acknum >= (snd_max scb)++ -- update idle time+ -- seqnum bound checking+ drop_it <- di3_topstuff seg seqnum acknum h + when (not drop_it) $ do+ -- acknum bound checking+ -- fast retransmit+ -- correct bad retransmit+ -- update send queue+ ack_ok <- di3_ackstuff seg seqnum acknum seg_win h ourfinisacked+ when ack_ok $ do+ -- update send window+ -- receive data+ fin_reass <- di3_datastuff seg seqnum acknum seg_win h ourfinisacked+ -- update socket state+ di3_ststuff fin_reass h ourfinisacked acknum+ tcp_wakeup+ tcp_output_all+ get_sock++{-# INLINE di3_topstuff #-}+di3_topstuff seg seqnum acknum h = + do sock <- get_sock+ let tcb = cb sock+ scb = cb_snd sock+ rcb = cb_rcv sock+ let rseq = seqnum `seq_plus` (bufc_length $ tcp_data seg)+ let seg_ts = tcp_ts seg+ -- PAWS check: -- todo+ let paws_failed = False+ let rcv_wnd' = calculate_bsd_rcv_wnd sock+ let segment_off_right_hand_edge =+ (seqnum >= (rcv_nxt rcb `seq_plus` rcv_wnd'))+ && (rseq > (rcv_nxt rcb `seq_plus` rcv_wnd'))+ && (rcv_wnd' /= 0)+ let drop_it = paws_failed || segment_off_right_hand_edge+ let Just seg_ts_val = seg_ts+ let (tt_keep', tt_fin_wait_2') = update_idle (clock h) sock+ let ts_recent'' = if not drop_it && seg_ts /= Nothing && seqnum <= (last_ack_sent rcb)+ then create_timewindow (clock h) dtsinval (fst $ seg_ts_val)+ else ts_recent $ cb_time sock+ modify_cb_time $ \t -> t { tt_keep = tt_keep'+ , tt_fin_wait_2 = tt_fin_wait_2'+ , t_idletime = clock h+ , ts_recent = ts_recent''+ }+ return drop_it++{-# INLINE di3_ackstuff #-}+di3_ackstuff seg seqnum acknum seg_win h ourfinisacked = + do sock <- get_sock+ let scb = cb_snd sock+ if acknum > snd_max scb then return False+ else if acknum > snd_una scb + then di3_newackstuff sock seg acknum h ourfinisacked+ else di3_oldackstuff sock seg seqnum acknum seg_win h++{-# INLINE di3_oldackstuff #-}+di3_oldackstuff sock seg seqnum acknum seg_win h = + let tcb = cb sock+ scb = cb_snd sock+ rcb = cb_rcv sock in+ let has_data = bufc_length (tcp_data seg) > 0+ && (rcv_nxt rcb) < (seqnum `seq_plus` (bufc_length $ tcp_data seg))+ && seqnum < ( (rcv_nxt rcb) `seq_plus` (rcv_wnd rcb)) in+ let maybe_dup_ack = not has_data + && seg_win == (snd_wnd scb)+ && mode_of (tt_rexmt scb) == Just Rexmt in+ if not maybe_dup_ack then do+ modify_cb_snd $ \c -> c { t_dupacks = 0 }+ return True+ else+ let t_dupacks' = t_dupacks scb + 1 in+ if acknum < (snd_una scb) then+ do modify_cb_snd $ \c -> c { t_dupacks = 0}+ return False + else if t_dupacks' < 3 then+ do modify_cb_snd $ \c -> c { t_dupacks = t_dupacks'}+ return True -- in case FIN is set+ else if t_dupacks' > 3 || (t_dupacks' == 3 && tcp_do_newreno && acknum < (snd_recover tcb)) then+ do modify_cb_snd $ \c -> c { t_dupacks = if t_dupacks' == 3 then 0 else t_dupacks'+ , snd_cwnd = (snd_cwnd scb) + (t_maxseg tcb)+ }+ tcp_output False+ return False+ else -- t_dupacks' == 3 && not (tcp_do_newreno && acknum < (snd_recover tcb))+ do modify_cb_snd $ \c -> c { t_dupacks = t_dupacks'+ , tt_rexmt = Nothing+ , t_rttseg = Nothing+ , snd_nxt = acknum+ , snd_cwnd = t_maxseg tcb+ } + modify_cb $ \c -> c { snd_ssthresh = (max 2 ( (min (snd_wnd scb) (snd_cwnd scb)) + `div` 2 `div` (t_maxseg tcb))) + * (t_maxseg tcb)+ , snd_recover = if tcp_do_newreno then snd_max scb else snd_recover c+ }+ tcp_output False+ modify_cb_snd $ \c -> c { snd_cwnd = (snd_ssthresh tcb) + (t_maxseg tcb) * t_dupacks'+ , snd_nxt = max (snd_nxt scb) (snd_nxt c)+ }+ return False+ +{-# INLINE di3_newackstuff #-}+di3_newackstuff sock seg acknum h ourfinisacked =+ do let seg_ts = tcp_ts seg+ let tcb = cb sock+ scb = cb_snd sock+ rcb = cb_rcv sock+ if (not tcp_do_newreno) || t_dupacks scb < 3 then+ modify_cb_snd $ \c->c { t_dupacks = 0+ , snd_cwnd = if t_dupacks c >= 3 + then min (snd_cwnd c) (snd_ssthresh tcb)+ else snd_cwnd c }+ -- below: tcp_do_newreno && t_dupacks scb >= 3+ else if acknum < (snd_recover tcb) then+ do modify_cb_snd $ \c -> c { tt_rexmt = Nothing+ , t_rttseg = Nothing+ , snd_nxt = acknum+ , snd_cwnd = t_maxseg tcb }+ tcp_output False+ modify_cb_snd $ \c->c{ snd_cwnd = (snd_cwnd c-(acknum `seq_diff` (snd_una c))+(t_maxseg tcb))+ , snd_nxt = snd_nxt scb }+ else --acknum >= snd_recover tcb+ modify_cb_snd $ \c -> c { t_dupacks = 0+ , snd_cwnd = if snd_max c `seq_diff` acknum < (snd_ssthresh tcb) + then snd_max c `seq_diff` acknum + (t_maxseg tcb)+ else snd_ssthresh tcb }++ let revert_rexmt = mode_of (tt_rexmt scb) `elem` [ Just Rexmt, Just RexmtSyn ] + && shift_of (tt_rexmt scb) == 1+ && timewindow_open (clock h) (t_badrxtwin $ cb_time sock)+ when revert_rexmt $ do+ modify_cb_snd $ \c -> c { snd_cwnd = snd_cwnd_prev tcb+ , snd_nxt = snd_max scb+ }+ modify_cb_time $ \c -> c { t_badrxtwin = Nothing }+ modify_cb $ \c -> c { snd_ssthresh = snd_ssthresh_prev tcb }++ -- to understand: timestamping+ let emission_time = case (seg_ts, t_rttseg scb) of+ (Just (ts_val, ts_ecr), _ ) -> Just (ts_ecr `seq_minus` 1)+ (Nothing, Just (ts0, seq0)) -> if acknum > seq0 then Just ts0 else Nothing+ (Nothing, Nothing) -> Nothing+ -- to understand: rtt update+ let t_rttinf' = case emission_time of+ Just emtime -> assert ((ticks h) >= emtime) $+ update_rtt ( ((ticks h) `seq_diff` emtime)*10*1000 ) (t_rttinf scb)+ Nothing -> t_rttinf scb+ let tt_rexmt' = if acknum == snd_max scb then+ Nothing+ else case mode_of (tt_rexmt scb) of+ Nothing -> start_tt_rexmt 0 True t_rttinf' (clock h)+ Just Rexmt -> start_tt_rexmt 0 True t_rttinf' (clock h)+ _ -> tt_rexmt scb+ let (snd_wnd', sndq') = if ourfinisacked then+ (snd_wnd scb - (bufc_length $ sndq scb), bufferchain_empty)+ else+ (snd_wnd scb - (acknum `seq_diff` (snd_una scb)),+ bufferchain_drop (acknum `seq_diff` (snd_una scb)) (sndq scb))++ modify_cb_snd $ \c -> c { t_rttinf = t_rttinf'+ , tt_rexmt = tt_rexmt'+ , t_rttseg = if emission_time == Nothing then t_rttseg c else Nothing+ , snd_cwnd = if not tcp_do_newreno || t_dupacks scb == 0 then+ expand_cwnd (snd_ssthresh tcb) + (t_maxseg tcb) + (tcp_maxwin `shiftL` (snd_scale tcb))+ (snd_cwnd c)+ else snd_cwnd c+ , snd_wnd = snd_wnd'+ , snd_una = acknum+ , snd_nxt = max acknum (snd_nxt c)+ , sndq = sndq'+ }++ when (st sock == TIME_WAIT) $+ modify_cb_time $ \c -> c { tt_2msl = Just (create_timer (clock h) (2*tcptv_msl))}++ if (st sock == LAST_ACK) && ourfinisacked then do+ modify_sock tcp_close_temp+ return False+ else return True++{-# INLINE di3_datastuff #-}+di3_datastuff seg seqnum acknum seg_win h ourfinisacked = do + sock <- get_sock+ let tcb = cb sock+ scb = cb_snd sock+ rcb = cb_rcv sock+ let update_send_window = + tcp_ACK seg+ && seqnum <= ( (rcv_nxt rcb) `seq_plus` (rcv_wnd rcb) )+ && ( snd_wl1 scb < seqnum + || ( snd_wl1 scb == seqnum + && ( snd_wl2 scb < acknum+ || ( snd_wl2 scb == acknum && seg_win > snd_wnd scb )+ )+ )+ || (st sock == SYN_RECEIVED && not (tcp_FIN seg) )+ )+ let seq_trimmed = max seqnum (min (rcv_nxt rcb) (seqnum `seq_plus` (bufc_length $ tcp_data seg)))+ when update_send_window $+ --debug $ "send window updated"+ modify_cb_snd $ \c -> c { snd_wnd = seg_win+ , snd_wl1 = seq_trimmed+ , snd_wl2 = acknum+ }+ if st sock == TIME_WAIT || (st sock == CLOSING && ourfinisacked) + then do modify_cb $ \c -> c { rcv_up = max (rcv_up c) (rcv_nxt rcb) }+ return False+ else di3_datastuff_really seg seqnum acknum seg_win h++{-# INLINE di3_datastuff_really #-}+di3_datastuff_really seg seqnum acknum seg_win h = + do let dat = tcp_data seg+ sock <- get_sock+ let tcb = cb sock+ scb = cb_snd sock+ rcb = cb_rcv sock+ + let trim_amt_left = if rcv_nxt rcb > seqnum+ then min (rcv_nxt rcb `seq_diff` seqnum) (bufc_length dat)+ else 0+ data_trimmed_left = bufferchain_drop trim_amt_left dat + seq_trimmed = seqnum `seq_plus` trim_amt_left+ let data_trimmed_left_right = bufferchain_take (rcv_wnd rcb) data_trimmed_left+ fin_trimmed = if bufc_length data_trimmed_left_right == + bufc_length data_trimmed_left then tcp_FIN seg else False+ let rseg = TCPReassSegment { trs_seq = seq_trimmed+ , trs_FIN = fin_trimmed+ , trs_data = data_trimmed_left_right+ }+ -- processing incoming data+ if seq_trimmed == rcv_nxt rcb+ && seq_trimmed `seq_plus` (bufc_length data_trimmed_left_right) + `seq_plus` (if fin_trimmed then 1 else 0) > (rcv_nxt rcb)+ && rcv_wnd rcb > 0 + then do+ -- case 1: reassambling possible+ let have_stuff_to_ack = bufc_length data_trimmed_left_right >0 || fin_trimmed+ let delay_ack = st sock `elem` [ESTABLISHED, CLOSE_WAIT, FIN_WAIT_1, FIN_WAIT_2, CLOSING, LAST_ACK]+ && have_stuff_to_ack && not fin_trimmed && List.null (t_segq rcb)+ && not (tf_rxwin0sent rcb)+ && tt_delack rcb == False+ let rsegq = rseg:(t_segq rcb)+ let (data_reass, rcv_nxt', fin_reass0, t_segq') = tcp_reass (rcv_nxt rcb) rsegq+ let rcvq' = bufferchain_concat (rcvq rcb) data_reass+ let rcv_wnd' = rcv_wnd rcb - (bufc_length data_reass)+ modify_cb_rcv $ \c -> c + { tt_delack = if delay_ack then True else tt_delack c+ , tf_shouldacknow = if have_stuff_to_ack then not delay_ack else tf_shouldacknow c+ , t_segq = t_segq'+ , rcv_nxt = rcv_nxt'+ , rcv_wnd = rcv_wnd'+ , rcvq = rcvq'+ }+ return fin_reass0+ else if seq_trimmed > (rcv_nxt rcb)+ && seq_trimmed < ((rcv_nxt rcb) `seq_plus` (rcv_wnd rcb))+ && bufc_length data_trimmed_left_right + (if fin_trimmed then 1 else 0) > 0 + && rcv_wnd rcb > 0 + then do+ -- case 2: wait for future reassambling+ modify_cb_rcv $ \c -> c { t_segq = rseg:(t_segq c)+ , tf_shouldacknow = True+ }+ return False+ else if tcp_ACK seg && seq_trimmed == rcv_nxt rcb + && bufc_length dat + (if tcp_FIN seg then 1 else 0) == 0 then+ -- case 3: no data+ return False+ else do+ -- case 4: other cases... maybe windows is closed+ modify_cb_rcv $ \c -> c { tf_shouldacknow = True }+ return False++{-# INLINE di3_ststuff #-}+di3_ststuff fin_reass h ourfinisacked acknum =+ do sock <- get_sock+ let tcb = cb sock+ let enter_TIME_WAIT = do+ modify_sock $ \s -> s { st = TIME_WAIT }+ modify_cb_time $ \c -> c { tt_2msl = Just (create_timer (clock h) (2*tcptv_msl))+ , tt_keep = Nothing+ , tt_conn_est = Nothing+ , tt_fin_wait_2 = Nothing+ }+ modify_cb_snd $ \c -> c { tt_rexmt = Nothing }+ modify_cb_rcv $ \c -> c { tt_delack = False }++ when fin_reass $+ modify_cb $ \s -> s { cantrcvmore = True }+ + case (st sock, fin_reass) of+ (SYN_RECEIVED,False) -> when (acknum >= (iss tcb) `seq_plus` 1 ) $ + modify_sock $ \s -> s + { st = if not (cantsndmore tcb) then ESTABLISHED else+ if ourfinisacked then FIN_WAIT_2 else FIN_WAIT_1+ }+ (SYN_RECEIVED, True) -> modify_sock $ \s -> s { st = CLOSE_WAIT }+ (ESTABLISHED, False) -> return ()+ (ESTABLISHED, True) -> modify_sock $ \s -> s { st = CLOSE_WAIT }+ (CLOSE_WAIT, _ ) -> return ()+ (FIN_WAIT_1, False) -> when ourfinisacked $ do+ modify_sock $ \s -> s { st = FIN_WAIT_2 }+ when (cantrcvmore tcb) $+ modify_cb_time $ \c -> c { tt_fin_wait_2 =+ Just (create_timer (clock h) (tcptv_maxidle)) }+ (FIN_WAIT_1, True) -> if ourfinisacked then enter_TIME_WAIT + else modify_sock $ \s->s { st=CLOSING }+ (FIN_WAIT_2, False) -> return ()+ (FIN_WAIT_2, True) -> enter_TIME_WAIT+ (CLOSING, _) -> when ourfinisacked enter_TIME_WAIT+ (LAST_ACK, False) -> return ()+ (LAST_ACK, True) -> error "di3_ststuff"+ (TIME_WAIT, _ ) -> return ()
+ src/Network/TCP/LTS/InMisc.hs view
@@ -0,0 +1,36 @@+{--+Copyright (c) 2006, Peng Li+ 2006, Stephan A. Zdancewic+All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are+met:++* Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++* Redistributions in binary form must reproduce the above copyright+ notice, this list of conditions and the following disclaimer in the+ documentation and/or other materials provided with the distribution.++* Neither the name of the copyright owners nor the names of its+ contributors may be used to endorse or promote products derived from+ this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.+--}++module Network.TCP.LTS.InMisc where++
+ src/Network/TCP/LTS/InPassive.hs view
@@ -0,0 +1,192 @@+{--+Copyright (c) 2006, Peng Li+ 2006, Stephan A. Zdancewic+All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are+met:++* Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++* Redistributions in binary form must reproduce the above copyright+ notice, this list of conditions and the following disclaimer in the+ documentation and/or other materials provided with the distribution.++* Neither the name of the copyright owners nor the names of its+ contributors may be used to endorse or promote products derived from+ this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.+--}++module Network.TCP.LTS.InPassive where+import Data.List as List+import Control.Exception+import Control.Monad++--import Foreign.C+import Network.TCP.Type.Base+import Network.TCP.Type.Syscall+import Network.TCP.Type.Timer+import Network.TCP.Type.Socket+import Network.TCP.Type.Datagram+import Network.TCP.Aux.Misc+import Network.TCP.Aux.Param+import Network.TCP.Aux.Output+import Hans.Layer.Tcp.Monad+import Network.TCP.Aux.SockMonad++import Network.TCP.LTS.Out+import Network.TCP.LTS.User++tcp_deliver_syn_packet seg = do+ -- precondition: sid does not exist+ -- try if seg matches a listening socket...+ let sidlisten = SocketID ((get_port $ tcp_dst seg), TCPAddr (IPAddr 0,0))+ h <- get_host+ haslisten <- has_sock sidlisten+ if not haslisten then return () else do+ -- matches a socket...+ sock <- lookup_sock sidlisten+ if st sock /= LISTEN then return () else do+ -- now we find a listening socket maching incoming SYN=1 ACK=0 RST=0+ if accept_incoming_q0 (sock_listen sock)+ then deliver_in_1 sidlisten sock seg+ else return ()+ +deliver_in_1 sid sock seg = + do let newsid = SocketID ((get_port $ tcp_dst seg), tcp_src seg)+ h <- get_host+ -- at this point, newsid is an unique socket id in the system...++ -- drop the first sid from q0 if needed, append newsid to q0.+ let lis1 = sock_listen sock+ should_drop = drop_from_q0 lis1+ drop_sid = head $ lis_q0 lis1+ oldq = lis_q0 lis1+ newq = if should_drop then tail oldq else oldq+ lis2 = lis1 { lis_q0 = newq++[newsid]}+ -- update listening socket (sid)+ update_sock sid $ \_ -> sock { sock_listen = lis2 }+ -- delete old socket if needed+ when should_drop $ tcp_close drop_sid + + -- Create a new socket+ let advmss = mssdflt -- todo: lookup interface mss+ advmss' = Nothing -- not advertising MSS (todo: change it)++ tf_rcvd_tstmp = case tcp_ts seg of Just _ -> True; Nothing -> False+ tf_doing_tstmp' = False -- not doing timestamping (todo: change it)++ (rcvbufsize', sndbufsize', t_maxseg', snd_cwnd') =+ calculate_buf_sizes advmss (tcp_mss seg) Nothing False+ (freebsd_so_rcvbuf) (freebsd_so_sndbuf) tf_doing_tstmp'++ tf_doing_ws' = False -- not doing window scaling (todo: change it)+ rcv_scale' = 0+ snd_scale' = 0+ rcv_window = min tcp_maxwin freebsd_so_rcvbuf++ newiss = SeqLocal 1000 -- beginning iss. (todo: add more randomness)+ t_rttseg' = Just (ticks h, newiss)+ seqnum = SeqForeign (tcp_seq seg)+ acknum = SeqLocal (tcp_ack seg)+ ack' = seqnum `seq_plus` 1+ cb_time' = (cb_time sock)+ { tt_keep = Just (create_timer (clock h) tcptv_keep_idle)+ , ts_recent = case (tcp_ts seg) of + Nothing -> ts_recent (cb_time sock) + Just (ts_val, ts_ecr) -> create_timewindow (clock h) (dtsinval) ts_val+ }+ cb_snd' = (cb_snd sock)+ { tt_rexmt = start_tt_rexmt 0 False (t_rttinf (cb_snd sock)) (clock h)+ , snd_una = newiss+ , snd_max = newiss `seq_plus` 1+ , snd_nxt = newiss `seq_plus` 1+ , snd_cwnd = snd_cwnd'+ , t_rttseg = t_rttseg'+ }+ cb_rcv' = (cb_rcv sock)+ { rcv_wnd = rcv_window+ , tf_rxwin0sent = (rcv_window == 0)+ , last_ack_sent = ack'+ , rcv_adv = ack' `seq_plus` rcv_window+ , rcv_nxt = ack'+ }+ cb' = (cb sock)+ { iss = newiss+ , irs = seqnum+ , rcv_up = seqnum `seq_plus` 1+ , t_maxseg = t_maxseg'+ , t_advmss = advmss'+ , rcv_scale = rcv_scale'+ , snd_scale = snd_scale'+ , tf_doing_ws = tf_doing_ws'+ , tf_req_tstmp = tf_doing_tstmp'+ , tf_doing_tstmp = tf_doing_tstmp'+ , local_addr = tcp_dst seg+ , remote_addr = tcp_src seg+ , self_id = newsid+ , parent_id = sid+ }+ -- create new socket (newsid)+ let newsock = initial_tcp_socket + { st = SYN_RECEIVED+ , cb = cb'+ , cb_time = cb_time'+ , cb_snd = cb_snd'+ , cb_rcv = cb_rcv'+ }+ insert_sock newsid newsock+ -- emit [SYN,ACK] packet+ emit_segs [TCPMessage $ make_syn_ack_segment (clock h) newsock + (tcp_dst seg) (tcp_src seg) (ticks h) ]++-- After receiving ACK on SYN_RECEIVED, a connection is established.+-- Now we need to update the queues of the listening socket...+di3_socks_update sid = do+ h <- get_host+ -- precondition: sid exists+ newsock <- lookup_sock sid+ let tcb = cb newsock+ rcb = cb_rcv newsock+ sidlisten = parent_id tcb+ haslisten <- has_sock sidlisten+ assert (haslisten) return ()+ listensock <- lookup_sock sidlisten+ let lis1 = sock_listen listensock+ assert (sid `elem` (lis_q0 lis1)) return ()+ -- found the listening socket!+ if accept_incoming_q lis1 then do+ -- delete socket from q0+ -- move into completed queue+ let lis2 = lis1 { lis_q0 = List.delete sid (lis_q0 lis1)+ , lis_q = sid : (lis_q lis1)+ }+ let rcv_window = calculate_bsd_rcv_wnd newsock+ let newcb = (cb_rcv newsock) { rcv_wnd = rcv_window+ , rcv_adv = (rcv_nxt rcb) `seq_plus` (rcv_wnd rcb)+ }+ update_sock sidlisten $ \_ -> listensock { sock_listen = lis2 }+ update_sock sid $ \_ -> newsock { cb_rcv = newcb }+ runSMonad sidlisten $ tcp_wakeup+ else do+ -- delete socket from q0, backlog full -> delete socket+ let lis2 = lis1 { lis_q0 = List.delete sid (lis_q0 lis1) }+ update_sock sidlisten $ \_ -> listensock { sock_listen = lis2 }+ tcp_close sid+ --endif++
+ src/Network/TCP/LTS/Out.hs view
@@ -0,0 +1,225 @@+{-# LANGUAGE ScopedTypeVariables #-}+{--+Copyright (c) 2006, Peng Li+ 2006, Stephan A. Zdancewic+All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are+met:++* Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++* Redistributions in binary form must reproduce the above copyright+ notice, this list of conditions and the following disclaimer in the+ documentation and/or other materials provided with the distribution.++* Neither the name of the copyright owners nor the names of its+ contributors may be used to endorse or promote products derived from+ this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.+--}++module Network.TCP.LTS.Out+ ( tcp_output_all+ , tcp_output+ , tcp_close+ , tcp_drop_and_close+ )+where++import Hans.Message.Tcp++import Data.List as List+import Network.TCP.Aux.Output+import Network.TCP.Aux.Misc+import Network.TCP.Type.Base+import Network.TCP.Type.Syscall+import Network.TCP.Type.Socket+import Hans.Layer.Tcp.Monad+import Network.TCP.Aux.SockMonad+import Control.Monad+import Control.Exception+import Foreign+import Network.TCP.Type.Timer+import Network.TCP.Type.Datagram as Datagram+import Network.TCP.Aux.Param++tcp_output_all :: SMonad t () +tcp_output_all = do+ h <- get_host_+ sock <- get_sock+ let scb = cb_snd sock+ tcb = cb sock+ when ((st sock `elem` [ESTABLISHED, CLOSE_WAIT, FIN_WAIT_1, + FIN_WAIT_2, CLOSING, LAST_ACK, TIME_WAIT]+ && (snd_una scb /= iss tcb)) -- does this make sense?+ || ( st sock `elem` [SYN_SENT, SYN_RECEIVED] && + cantsndmore tcb && (tf_shouldacknow $ cb_rcv sock))) $+ output_loop h sock+ +output_loop h sock =+ let (sock1, outsegs) = tcp_output_really (clock h) False (ticks h) sock in+ if List.null outsegs then + put_sock sock1+ else do+ --debug $ "tcp_output_all: " ++ (show outsegs)+ emit_segs_ $! outsegs+ output_loop h sock1++{-# INLINE tcp_output_all #-}+{-# INLINE output_loop #-}+++{-# INLINE tcp_output_really #-}++tcp_output_really (curr_time :: Time) (window_probe::Bool) (ts_val'::Timestamp) tcp_sock =+ let tcb = cb tcp_sock+ scb = cb_snd tcp_sock+ rcb = cb_rcv tcp_sock+ in+ assert ((rcv_adv rcb) >= (rcv_nxt rcb)) $+ assert ((snd_nxt scb) >= (snd_una scb)) $+ let snd_cwnd' = if snd_max scb == snd_una scb && + (t_idletime $ cb_time tcp_sock) - curr_time + >= (computed_rxtcur $ t_rttinf scb)+ then (t_maxseg tcb) * ss_fltsz -- has been idle for a while, slowstart+ else snd_cwnd scb+ win0 = min (snd_wnd scb) snd_cwnd'+ win = if window_probe && win0==0 then 1 else win0+ snd_wnd_unused ::Int = win - ((snd_nxt scb) `seq_diff` (snd_una scb))+ syn_not_acked = (st tcp_sock `elem` [SYN_SENT, SYN_RECEIVED])+ fin_required = (cantsndmore tcb && st tcp_sock `notElem` [FIN_WAIT_2, TIME_WAIT])+ last_sndq_data_seq = (snd_una scb) `seq_plus` (bufc_length $ sndq scb)+ last_sndq_data_and_fin_seq = last_sndq_data_seq `seq_plus` + (if fin_required then 1 else 0) `seq_plus` + (if syn_not_acked then 1 else 0)+ have_data_to_send = (snd_nxt scb) < last_sndq_data_seq+ have_data_or_fin_to_send = (snd_nxt scb) < last_sndq_data_and_fin_seq+ window_update_delta = (min (tcp_maxwin `shiftL` (rcv_scale tcb))+ (freebsd_so_rcvbuf - (bufc_length $ rcvq rcb))+ ) - ( (rcv_adv rcb) `seq_diff` (rcv_nxt rcb))+ need_to_send_a_window_update = (window_update_delta >= 2 * (t_maxseg tcb)) ||+ (2*window_update_delta >= freebsd_so_rcvbuf)+ do_output = ( have_data_or_fin_to_send && (if have_data_to_send then snd_wnd_unused>0 else True) )+ || need_to_send_a_window_update -- sndurp tcp_sock /= Nothing+ || tf_shouldacknow rcb+ cant_send = (not do_output) &&+ (bufc_length (sndq scb) > 0 ) &&+ mode_of (tt_rexmt scb) == Nothing+ window_shrunk = win==0 &&+ snd_wnd_unused <0 &&+ st tcp_sock /= SYN_SENT+ tcp_sock0 = if cant_send then + tcp_sock { cb_snd = scb {tt_rexmt = start_tt_persist 0 (t_rttinf scb) curr_time}}+ else if window_shrunk then + tcp_sock { cb_snd = scb { + tt_rexmt = case tt_rexmt scb of+ Just(Timed (Persist, shift) d ) -> Just (Timed (Persist, 0) d)+ _ -> start_tt_persist 0 (t_rttinf scb) curr_time+ , snd_nxt = snd_una scb+ }}+ else tcp_sock+ in+ if (not do_output) then (tcp_sock0, []) else+ ------------ really do it ---------------------------------------------+ let tcp_sock = tcp_sock0+ scb = cb_snd tcp_sock++ data' = bufferchain_drop (snd_nxt scb `seq_diff` (snd_una scb)) (sndq scb)+ data_to_send = bufferchain_take (min (snd_wnd_unused) ( t_maxseg tcb)) data'+ bFIN = fin_required && (snd_nxt scb) `seq_plus` (bufc_length data_to_send) >= last_sndq_data_seq+ bACK = if bFIN && st tcp_sock == SYN_SENT then False else True+ snd_nxt' = if bFIN && + ((snd_nxt scb `seq_plus` (bufc_length data_to_send) == + last_sndq_data_seq `seq_plus` 1 && snd_una scb /= iss tcb )+ || (snd_nxt scb) `seq_diff` (iss tcb) == 2) + then snd_nxt scb `seq_minus` 1+ else snd_nxt scb+ bPSH = bufc_length data_to_send > 0 && + snd_nxt scb `seq_plus` (bufc_length data_to_send) == last_sndq_data_seq+ rcv_wnd'' = calculate_bsd_rcv_wnd tcp_sock+ rcv_wnd' = max (rcv_adv rcb `seq_diff` (rcv_nxt rcb))+ (min (tcp_maxwin `shiftL` (rcv_scale tcb))+ (if rcv_wnd'' < (freebsd_so_rcvbuf `div` 4) && rcv_wnd'' < (t_maxseg tcb) + then 0 else rcv_wnd''))+ want_tstmp = if st tcp_sock == SYN_SENT then tf_req_tstmp tcb else tf_doing_tstmp tcb+ ts_ = do_tcp_options curr_time want_tstmp (ts_recent $ cb_time tcp_sock) ts_val'+ in+ let win_ = rcv_wnd' `shiftR` (rcv_scale tcb)+ hdr = set_tcp_ts ts_ emptyTcpHeader+ { tcpSeqNum = TcpSeqNum (seq_val snd_nxt')+ , tcpAckNum = TcpAckNum (fseq_val (rcv_nxt rcb))+ , tcpAck = bACK+ , tcpPsh = bPSH+ , tcpFin = bFIN+ , tcpWindow = fromIntegral win_+ }+ seg = mkTCPSegment' (local_addr tcb) (remote_addr tcb) hdr data_to_send+ st' = if bFIN then+ case st tcp_sock of+ ESTABLISHED -> FIN_WAIT_1+ CLOSE_WAIT -> LAST_ACK+ xxx -> xxx+ else+ st tcp_sock+ snd_nxt'' = snd_nxt' `seq_plus` (bufc_length data_to_send) `seq_plus` (if bFIN then 1 else 0)+ snd_max' = max (snd_max scb) snd_nxt''+ tt_rexmt' = if (mode_of (tt_rexmt scb) == Nothing ||+ (mode_of (tt_rexmt scb) == Just Persist && not window_probe)) &&+ snd_nxt'' > (snd_una scb) then+ start_tt_rexmt 0 False (t_rttinf scb) curr_time+ else if (window_probe {-- || sndurp tcp_sock /= Nothing --} ) && win0 /= 0 && + mode_of (tt_rexmt scb) == Just Persist then+ Nothing+ else+ tt_rexmt scb+ t_rttseg' = if t_rttseg scb == Nothing && (bufc_length data_to_send > 0 || bFIN) &&+ snd_nxt'' > (snd_max scb) && not window_probe then+ Just (ts_val', snd_nxt')+ else+ t_rttseg scb+ tcp_sock' = tcp_sock+ { st = st'+ , cb_snd = scb { tt_rexmt = tt_rexmt'+ , snd_cwnd = snd_cwnd'+ , t_rttseg = t_rttseg'+ , snd_max = snd_max'+ , snd_nxt = snd_nxt''+ }+ , cb_rcv = rcb { last_ack_sent = rcv_nxt rcb+ , rcv_adv = rcv_nxt rcb `seq_plus` rcv_wnd'+ , tt_delack = False+ , rcv_wnd = rcv_wnd'+ , tf_rxwin0sent = (rcv_wnd' == 0)+ , tf_shouldacknow = False+ }+ }+ outsegs' = [TCPMessage seg]+ in+ (tcp_sock', outsegs')++{-# INLINE tcp_output #-}+tcp_output :: Bool -> SMonad t ()+tcp_output win_probe =+ do sock <- get_sock+ h <- get_host_+ let (newsock, segs) = tcp_output_really (clock h) win_probe (ticks h) sock+ put_sock newsock+ emit_segs_ segs+ --if List.null segs then return () else debug $ "tcp_output: " ++ (show segs)+++
+ src/Network/TCP/LTS/User.hs view
@@ -0,0 +1,293 @@+{--+Copyright (c) 2006, Peng Li+ 2006, Stephan A. Zdancewic+All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are+met:++* Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++* Redistributions in binary form must reproduce the above copyright+ notice, this list of conditions and the following disclaimer in the+ documentation and/or other materials provided with the distribution.++* Neither the name of the copyright owners nor the names of its+ contributors may be used to endorse or promote products derived from+ this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.+--}++module Network.TCP.LTS.User + ( tcp_process_user_request+ , tcp_wakeup+ )+where++import Foreign.C+import Data.List as List+import Control.Monad+import Data.Maybe+import Network.TCP.Type.Base+import Network.TCP.Type.Datagram+import Network.TCP.Type.Syscall+import Network.TCP.Type.Socket+import Hans.Layer.Tcp.Monad+import Network.TCP.Aux.SockMonad+import Network.TCP.Aux.Misc+import Network.TCP.Aux.Param+import Network.TCP.Aux.Output++import Network.TCP.LTS.Out++---------------------------------------------------------------------------------------+-- input: a list of sock request+-- output: threads that have taken the completed requests (back in running state)+-- side effect: host state changes+-- blocked threads goes into the wait queue of each socket+-- tcp_process_user_requests :: (Monad m) => [(SockReq,SockRsp->t)] -> HMonad t m [t]+-- tcp_process_user_requests reqs = +-- do r <- mapM tcp_process_user_request reqs+-- return $ concat r++tcp_process_user_request :: (SockReq,SockRsp->t) -> HMonad t (Maybe t)+tcp_process_user_request (req, cont) =+ case req of+ SockListen addr -> process_listen addr cont+ SockClose sid -> process_close sid cont+ SockConnect local addr -> process_connect local addr cont+ SockAccept sid -> process_accept sid cont+ SockSend sid d -> process_send sid d cont+ SockRecv sid -> process_recv sid cont++tcp_wakeup_request req cont = + case req of + SockConnect local addr -> wakeup_connect cont+ SockAccept sid -> wakeup_accept sid cont+ SockSend sid d -> wakeup_send sid d cont+ SockRecv sid -> wakeup_recv sid cont+ +-- pre-cond: sock is set+-- post-cond: sock is set, tcp_output needed+tcp_wakeup =+ do sock <- get_sock+ case waiting_list sock of+ [] -> return ()+ (req,cont):reqs -> do+ res <- tcp_wakeup_request req cont+ case res of+ Nothing -> return ()+ Just th -> do+ emit_ready_ [th]+ modify_sock $ \s -> s {waiting_list = reqs}++-- pre-cond: sock not set+-- post-cond: sock not set+process_listen :: Port -> (SockRsp->t) -> HMonad t (Maybe t)+process_listen port cont =+ do let sock_id = SocketID (port, TCPAddr (IPAddr 0,0))+ h <- get_host+ -- check if port has been used...+ if port `elem` (local_ports h) then+ do let listen = SocketListen [] [] listen_qlimit+ let newsock = initial_tcp_socket + { cb = (cb initial_tcp_socket) { local_addr = TCPAddr (IPAddr 0,port), self_id=sock_id }+ , st = LISTEN+ , sock_listen = listen+ }+ insert_sock sock_id newsock+ modify_host $ \h -> h { local_ports = List.delete port (local_ports h) } + return $ Just $ cont $ SockNew sock_id+ else+ return $ Just $ cont $ SockError "Port not available"++process_close :: SocketID -> (SockRsp->t) -> HMonad t (Maybe t)+process_close sid cont =+ do ok <- has_sock sid+ if not ok then + return $ Just $ cont $ SockError "Socket not found"+ else do+ sock <- lookup_sock sid+ if st sock `elem` [CLOSED,SYN_SENT,SYN_RECEIVED] then do+ -- close_7 : delete sid+ tcp_close sid+ return $ Just $ cont $ SockOK+ else if st sock /= LISTEN then runSMonad sid $ do+ -- close_1 : change the flags so FIN can be sent later+ modify_sock $ \sock-> sock { cb = (cb sock) { cantsndmore=True, cantrcvmore=True}+ , cb_rcv = (cb_rcv sock) { rcvq=bufferchain_empty }+ }+ tcp_output_all+ return $ Just $ cont $ SockOK+ else do+ -- close_8 : closing a LISTEN socket+ -- todo: not implemented yet+ return $ Just $ cont $ SockError "not implemented yet: close_8 : closing a LISTEN socket"++-- pre-cond: sock not set+-- post-cond: sock not set+process_accept :: SocketID -> (SockRsp->t) -> HMonad t (Maybe t)+process_accept sid cont = + do ok <- has_sock sid+ if not ok then + return $ Just $ cont $ SockError "Socket not found"+ else runSMonad sid $ do+ res <- try_accept cont+ when (isNothing res) $+ -- put thread in waiting list+ modify_sock $ \sock -> sock {waiting_list = (waiting_list sock)++[(SockAccept sid, cont)] }+ return res+wakeup_accept sid cont+ = try_accept cont++-- pre-cond: sock is set+-- post-cond: sock is set, listening queue updated+try_accept :: (SockRsp->t) -> SMonad t (Maybe t)+try_accept cont =+ do sock <- get_sock+ if st sock /= LISTEN then + return $ Just $ cont $ SockError "Socket not in LISTEN state"+ else do+ -- find the listen queue+ let listen = sock_listen sock+ case lis_q listen of + [] -> return Nothing -- no connection to accept, can't proceed+ (sid2:qs) -> do -- try to accept sid2, either success or fail + modify_sock $ \sock -> sock { sock_listen = listen { lis_q = qs } }+ return $ Just $ cont $ SockNew sid2++process_recv :: SocketID -> (SockRsp->t) -> HMonad t (Maybe t)+process_recv sid cont =+ do ok <- has_sock sid+ if not ok then+ return $ Just $ cont $ SockError "Socket not found"+ else runSMonad sid $ do+ res <- try_recv cont+ when (isNothing res) $ + -- put thread in waiting list+ modify_sock $ \sock -> sock {waiting_list = (waiting_list sock)++[(SockRecv sid, cont)] }+ return res+ +wakeup_recv sid cont =+ try_recv cont++try_recv :: (SockRsp->t) -> SMonad t (Maybe t)+try_recv cont = + do sock <- get_sock+ let q = rcvq $ cb_rcv sock+ if st sock `elem` [ CLOSED, SYN_SENT, SYN_RECEIVED] then+ return $ Just $ cont $ SockError "Socket not in synchronized state"+ else if bufc_length q == 0 then+ if cantrcvmore $ cb sock + then return $ Just $ cont $ SockData buffer_empty -- EOF+ else return Nothing -- no data, can't proceed+ else do+ -- let rcvnum = min size (length q)+ -- (q1,q2) = splitAt rcvnum q+ put_sock $ sock { cb_rcv = (cb_rcv sock) {rcvq = bufferchain_tail q }}+ return $ Just $ cont $ SockData $ bufferchain_head q++process_send :: SocketID -> Buffer -> (SockRsp->t) -> HMonad t (Maybe t)+process_send sid d cont = + do ok <- has_sock sid+ if not ok then+ return $ Just $ cont $ SockError "Socket not found"+ else runSMonad sid $ do+ (res,remain) <- try_send d cont+ when (isNothing res) $+ -- put thread in waiting list+ modify_sock $ \sock -> sock {waiting_list = (waiting_list sock)++[(SockSend sid remain, cont)] }+ return res+ +wakeup_send sid d cont =+ do (res,remain) <- try_send d cont+ when (isNothing res) $+ -- put thread in waiting list again+ modify_sock $ \sock -> sock {waiting_list = (tail $ waiting_list sock)++[(SockSend sid remain, cont)] }+ return res++try_send :: Buffer -> (SockRsp->t) -> SMonad t (Maybe t, Buffer)+try_send d cont = + do sock <- get_sock+ if st sock `notElem` [ ESTABLISHED, CLOSE_WAIT] then+ return (Just $ cont $ SockError "Socket not in synchronized state", buffer_empty )+ else if cantsndmore $ cb sock then+ return (Just $ cont $ SockError "Socket cantsndmore=true, cannot send...", buffer_empty)+ else do+ let max_can_send = freebsd_so_sndbuf - (bufc_length $ sndq $ cb_snd sock)+ num_to_send = min max_can_send (buf_len d)+ (d1,d2) = buffer_split num_to_send d+ modify_cb_snd $ \c -> c { sndq = (sndq c) `bufferchain_append` d1 }+ --if (bufc_length (sndq $ cb_snd $ sock) == 0) then+ -- modify_cb_rcv $ \c -> c { tt_delack = True }+ -- else+ tcp_output_all+ if buf_len d2 == 0 then return (Just $ cont $ SockOK, d2)+ else return (Nothing, d2)+ ++process_connect :: IPAddr -> TCPAddr -> (SockRsp->t) -> HMonad t (Maybe t)+process_connect local addr cont = do+ h <- get_host+ m_port <- alloc_local_port+ if m_port == Nothing then return $ Just $ cont $ SockError "cannot allocate local port" else do+ let (Just port) = m_port+ sock_id = SocketID (port, addr)+ newiss = SeqLocal 1000 -- beginning iss. (todo: add more randomness)+ request_r_scale' = 0+ rcv_wnd' = freebsd_so_rcvbuf+ adv_mss = Just mssdflt+ tf_req_tstmp' = False -- todo: change it+ t_rttseg' = Just (ticks h, newiss)+ let { newsock = initial_tcp_socket + { st = SYN_SENT+ , cb_time = initial_cb_time+ { tt_conn_est = Just (create_timer (clock h) tcptv_keep_init)+ } + , cb_snd = initial_cb_snd+ { tt_rexmt = start_tt_rexmtsyn 0 False (t_rttinf initial_cb_snd) (clock h)+ , snd_una = newiss+ , snd_nxt = newiss `seq_plus` 1+ , snd_max = newiss `seq_plus` 1+ , t_rttseg = t_rttseg'+ }+ , cb_rcv = initial_cb_rcv+ { rcv_wnd = rcv_wnd'+ , rcv_adv = (rcv_nxt initial_cb_rcv) `seq_plus` rcv_wnd'+ , tf_rxwin0sent = (rcv_wnd' == 0)+ } + , cb = initial_cb_misc + { local_addr = TCPAddr (local,port)+ , remote_addr = addr+ , self_id=sock_id + , cantsndmore = False+ , cantrcvmore = False+ , iss = newiss+ , request_r_scale = Just request_r_scale'+ , t_advmss = adv_mss+ , tf_req_tstmp = tf_req_tstmp'+ }+ }}+ insert_sock sock_id newsock+ emit_segs $ [TCPMessage $ make_syn_segment (clock h) newsock (ticks h)]+ return $ Nothing++wakeup_connect :: (SockRsp->t) -> SMonad t (Maybe t)+wakeup_connect cont = do + sock <- get_sock+ if st sock == SYN_SENT + then return Nothing + else return $ Just $ cont $ SockNew $ self_id $ cb sock
+ src/Network/TCP/Type/Base.hs view
@@ -0,0 +1,271 @@+{-# OPTIONS_GHC -fglasgow-exts #-}++{--+Copyright (c) 2006, Peng Li+ 2006, Stephan A. Zdancewic+All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are+met:++* Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++* Redistributions in binary form must reproduce the above copyright+ notice, this list of conditions and the following disclaimer in the+ documentation and/or other materials provided with the distribution.++* Neither the name of the copyright owners nor the names of its+ contributors may be used to endorse or promote products derived from+ this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.+--}++module Network.TCP.Type.Base where++import Data.Time.Clock.POSIX (POSIXTime,getPOSIXTime)+import Foreign+import Foreign.C+import System.IO.Unsafe+import Control.Exception+import qualified Data.ByteString as S+import qualified Data.ByteString.Lazy as L+++to_Int x = (fromIntegral x)::Int+to_Int8 x = (fromIntegral x)::Int8+to_Int16 x = (fromIntegral x)::Int16+to_Int32 x = (fromIntegral x)::Int32+to_Int64 x = (fromIntegral x)::Int64++to_Word x = (fromIntegral x)::Word+to_Word8 x = (fromIntegral x)::Word8+to_Word16 x = (fromIntegral x)::Word16+to_Word32 x = (fromIntegral x)::Word32+to_Word64 x = (fromIntegral x)::Word64+++{-# INLINE to_Int #-}+{-# INLINE to_Int8 #-}+{-# INLINE to_Int16 #-}+{-# INLINE to_Int32 #-}+{-# INLINE to_Int64 #-}+{-# INLINE to_Word #-}+{-# INLINE to_Word8 #-}+{-# INLINE to_Word16 #-}+{-# INLINE to_Word32 #-}+{-# INLINE to_Word64 #-}++-- Port numbers, IP addresses++type Port = Word16+newtype IPAddr = IPAddr Word32 deriving (Eq,Ord)+newtype TCPAddr = TCPAddr (IPAddr, Port) deriving (Eq,Ord)+newtype SocketID = SocketID (Port, TCPAddr) deriving (Eq,Ord,Show)++instance Show IPAddr where+ show (IPAddr w) = (show $ w .&. 255) ++ "." +++ (show $ (w `shiftR` 8) .&. 255) ++ "." +++ (show $ (w `shiftR` 16) .&. 255) ++ "." +++ (show $ (w `shiftR` 24) .&. 255)+instance Show TCPAddr where+ show (TCPAddr (ip,pt)) = (show ip) ++ ":" ++ (show pt)+++get_ip :: TCPAddr -> IPAddr+get_ip (TCPAddr (i,p)) = i++get_port :: TCPAddr -> Port+get_port (TCPAddr (i,p)) = p++get_remote_addr :: SocketID -> TCPAddr+get_remote_addr (SocketID (p,a)) = a++get_local_port :: SocketID -> Port+get_local_port (SocketID (p,a)) = p++{-# INLINE get_ip #-}+{-# INLINE get_port #-}+{-# INLINE get_remote_addr #-}+{-# INLINE get_local_port #-}++-- TCP Sequence numbers++class (Eq a) => Seq32 a where+ seq_val :: a -> Word32+ seq_lt :: a -> a -> Bool+ seq_leq :: a -> a -> Bool+ seq_gt :: a -> a -> Bool+ seq_geq :: a -> a -> Bool+ seq_plus :: (Integral n) => a -> n -> a+ seq_minus :: (Integral n) => a -> n -> a+ seq_diff :: (Integral n) => a -> a -> n++instance Seq32 Word32 where+ seq_val w = w+ seq_lt x y = (to_Int32 (x-y)) < 0+ seq_leq x y = (to_Int32 (x-y)) <= 0+ seq_gt x y = (to_Int32 (x-y)) > 0+ seq_geq x y = (to_Int32 (x-y)) >= 0+ seq_plus s i = assert (i>=0) $ s + (to_Word32 i)+ seq_minus s i = assert (i>=0) $ s - (to_Word32 i)+ seq_diff s t = let res=fromIntegral $ to_Int32 (s-t) in assert (res>=0) res+ {-# INLINE seq_val #-}+ {-# INLINE seq_lt #-}+ {-# INLINE seq_leq #-}+ {-# INLINE seq_gt #-}+ {-# INLINE seq_geq #-}+ {-# INLINE seq_plus #-}+ {-# INLINE seq_minus #-}+ {-# INLINE seq_diff #-}++newtype SeqLocal = SeqLocal Word32 deriving (Eq,Show,Seq32)+newtype SeqForeign = SeqForeign Word32 deriving (Eq,Show,Seq32)+newtype Timestamp = Timestamp Word32 deriving (Eq,Show,Seq32)++instance Ord SeqLocal where+ (<) = seq_lt+ (>) = seq_gt+ (<=) = seq_leq+ (>=) = seq_geq+ {-# INLINE (<) #-} + {-# INLINE (>) #-} + {-# INLINE (<=) #-} + {-# INLINE (>=) #-} +instance Ord SeqForeign where+ (<) = seq_lt+ (>) = seq_gt+ (<=) = seq_leq+ (>=) = seq_geq+ {-# INLINE (<) #-} + {-# INLINE (>) #-} + {-# INLINE (<=) #-} + {-# INLINE (>=) #-} +instance Ord Timestamp where+ (<) = seq_lt+ (>) = seq_gt+ (<=) = seq_leq+ (>=) = seq_geq+ {-# INLINE (<) #-} + {-# INLINE (>) #-} + {-# INLINE (<=) #-} + {-# INLINE (>=) #-} ++seq_flip_ltof (SeqLocal w) = SeqForeign w+seq_flip_ftol (SeqForeign w) = SeqLocal w++fseq_val :: SeqForeign -> Word32+fseq_val (SeqForeign w32) = w32++{-# INLINE seq_flip_ltof #-}+{-# INLINE seq_flip_ftol #-}+++-- | Clock time, in microseconds.+type Time = Int64++seconds_to_time :: RealFrac a => a -> Time+seconds_to_time f = round (f * 1000*1000)++{-# INLINE seconds_to_time #-}++get_current_time :: IO Time+get_current_time = posixtime_to_time `fmap` getPOSIXTime++posixtime_to_time :: POSIXTime -> Time+posixtime_to_time = seconds_to_time . toRational++---------------------------------------------------------------------------++type Buffer = S.ByteString++buf_len :: Buffer -> Int+buf_len = S.length++buffer_ok :: Buffer -> Bool+buffer_ok _ = True++buffer_empty :: Buffer+buffer_empty = S.empty++buffer_to_string :: Buffer -> IO String+buffer_to_string = return . map (toEnum . fromEnum) . S.unpack++string_to_buffer :: String -> IO Buffer+string_to_buffer = return . S.pack . map (toEnum . fromEnum)++buffer_split :: Int -> Buffer -> (Buffer,Buffer)+buffer_split = S.splitAt++buffer_take = S.take+buffer_drop = S.drop++buffer_merge :: Buffer -> Buffer -> [Buffer]+buffer_merge bs1 bs2+ | S.length bs1 == 0 = [bs2]+ | S.length bs2 == 0 = [bs1]+ | otherwise = [bs1,bs2]+++type BufferChain = L.ByteString++bufc_length :: BufferChain -> Int+bufc_length = fromIntegral . L.length++bufferchain_empty = L.empty+bufferchain_singleton b+ | S.null b = L.empty+ | otherwise = L.fromChunks [b]++bufferchain_add bs bc = bufferchain_singleton bs `L.append` bc++bufferchain_get :: BufferChain -> Int -> BufferChain+bufferchain_get bc ix = L.take 1 (L.drop (fromIntegral ix) bc)++bufferchain_append bc bs = bc `L.append` bufferchain_singleton bs++bufferchain_concat :: BufferChain -> BufferChain -> BufferChain+bufferchain_concat = L.append++bufferchain_head :: BufferChain -> Buffer+bufferchain_head = head . L.toChunks++bufferchain_tail :: BufferChain -> BufferChain+bufferchain_tail = L.fromChunks . tail . L.toChunks++bufferchain_take :: Int -> BufferChain -> BufferChain+bufferchain_take = L.take . fromIntegral++bufferchain_drop :: Int -> BufferChain -> BufferChain+bufferchain_drop = L.drop . fromIntegral++bufferchain_split_at :: Int -> BufferChain -> (BufferChain,BufferChain)+bufferchain_split_at = L.splitAt . fromIntegral++bufferchain_collapse :: BufferChain -> IO Buffer+bufferchain_collapse = return . S.concat . L.toChunks++-- bufferchain_output bc@(BufferChain lst len) (ptr::Ptr CChar) =+-- copybuf ptr lst+-- where copybuf ptrDest [] = return ()+-- copybuf ptrDest (x:xs) =+-- withForeignPtr (buf_ptr x)+-- (\ptrSrc -> do+-- copyArray ptrDest (ptrSrc `plusPtr` (buf_offset x)) (buf_len x)+-- copybuf (ptrDest `plusPtr` (buf_len x)) xs+-- )++bufferchain_ok :: BufferChain -> Bool+bufferchain_ok _ = True
+ src/Network/TCP/Type/Datagram.hs view
@@ -0,0 +1,217 @@+{--+Copyright (c) 2006, Peng Li+ 2006, Stephan A. Zdancewic+All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are+met:++* Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++* Redistributions in binary form must reproduce the above copyright+ notice, this list of conditions and the following disclaimer in the+ documentation and/or other materials provided with the distribution.++* Neither the name of the copyright owners nor the names of its+ contributors may be used to endorse or promote products derived from+ this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.+--}++module Network.TCP.Type.Datagram +( TCPSegment (..)+, UDPDatagram (..)+, Protocol (..)+, ICMPType (..)+, ICMPDatagram (..)+, IPMessage (..)+, tcp_ws+, set_tcp_ws+, tcp_mss+, set_tcp_mss+, tcp_ts+, set_tcp_ts+, tcp_seq+, tcp_ack+, tcp_URG+, tcp_ACK+, tcp_PSH+, tcp_RST+, tcp_SYN+, tcp_FIN+, tcp_win+, tcp_urp+, mkTCPSegment+, mkTCPSegment'+)+where++import Hans.Address.IP4 (IP4,convertToWord32)+import Hans.Message.Tcp+ (TcpHeader(..),TcpPacket(..),TcpPort(..),TcpAckNum(..),TcpSeqNum(..)+ ,findTcpOption,setTcpOption,TcpOptionTag(..),TcpOption(..))++import Network.TCP.Type.Base++data TCPSegment = TCPSegment+ { tcp_src :: !TCPAddr+ , tcp_dst :: !TCPAddr+ , tcp_header :: !TcpHeader+ , tcp_data :: !BufferChain+ }++mkTCPSegment :: IP4 -> IP4 -> TcpPacket -> TCPSegment+mkTCPSegment src dst (TcpPacket hdr body) = TCPSegment+ { tcp_src = TCPAddr (IPAddr (convertToWord32 src),srcP)+ , tcp_dst = TCPAddr (IPAddr (convertToWord32 dst),dstP)+ , tcp_header = hdr+ , tcp_data = bufferchain_singleton body+ }+ where+ TcpPort srcP = tcpSourcePort hdr+ TcpPort dstP = tcpDestPort hdr++mkTCPSegment' :: TCPAddr -> TCPAddr -> TcpHeader -> BufferChain -> TCPSegment+mkTCPSegment' s@(TCPAddr (_, srcP)) d@(TCPAddr (_, dstP)) hdr body =+ TCPSegment+ { tcp_src = s+ , tcp_dst = d+ , tcp_header = hdr+ { tcpSourcePort = TcpPort srcP+ , tcpDestPort = TcpPort dstP+ }+ , tcp_data = body+ }+ where++tcp_seq = getSeqNum . tcpSeqNum . tcp_header+tcp_ack = getAckNum . tcpAckNum . tcp_header+tcp_URG = tcpUrg . tcp_header+tcp_ACK = tcpAck . tcp_header+tcp_PSH = tcpPsh . tcp_header+tcp_RST = tcpRst . tcp_header+tcp_SYN = tcpSyn . tcp_header+tcp_FIN = tcpFin . tcp_header+tcp_win = tcpWindow . tcp_header+tcp_urp = tcpUrgentPointer . tcp_header++tcp_ws :: TCPSegment -> Maybe Int+tcp_ws = fmap prj . findTcpOption OptTagWindowScaling . tcp_header+ where+ prj (OptWindowScaling ws) = fromIntegral ws++set_tcp_ws :: Maybe Int -> TcpHeader -> TcpHeader+set_tcp_ws Nothing = id+set_tcp_ws (Just ws) = setTcpOption (OptWindowScaling (fromIntegral ws))++tcp_mss :: TCPSegment -> Maybe Int+tcp_mss = fmap prj . findTcpOption OptTagMaxSegmentSize . tcp_header+ where+ prj (OptMaxSegmentSize mss) = fromIntegral mss++set_tcp_mss :: Maybe Int -> TcpHeader -> TcpHeader+set_tcp_mss Nothing = id+set_tcp_mss (Just mss) = setTcpOption (OptMaxSegmentSize (fromIntegral mss))++tcp_ts :: TCPSegment -> Maybe (Timestamp,Timestamp)+tcp_ts = fmap prj . findTcpOption OptTagTimestamp . tcp_header+ where+ prj (OptTimestamp v r) = (Timestamp v, Timestamp r)++set_tcp_ts :: Maybe (Timestamp,Timestamp) -> TcpHeader -> TcpHeader+set_tcp_ts Nothing = id+set_tcp_ts (Just (Timestamp v, Timestamp r)) = setTcpOption (OptTimestamp v r)++{-+data TCPSegment = TCPSegment+ { tcp_src :: !TCPAddr+ , tcp_dst :: !TCPAddr+ , tcp_seq :: !SeqLocal+ , tcp_ack :: !SeqForeign+ , tcp_URG :: !Bool+ , tcp_ACK :: !Bool+ , tcp_PSH :: !Bool+ , tcp_RST :: !Bool+ , tcp_SYN :: !Bool+ , tcp_FIN :: !Bool+ , tcp_win :: !Int+ , tcp_urp :: !Int+ , tcp_data :: !BufferChain+ -- option: window scaling+ , tcp_ws :: !(Maybe Int)+ -- option: max segment size+ , tcp_mss :: !(Maybe Int)+ -- option: RFC1323+ , tcp_ts :: !(Maybe (Timestamp, Timestamp))+ } -}++instance Show TCPSegment where+ show seg = + let part1 = + if (get_port $ tcp_src seg) > 9999 || (get_port $ tcp_dst seg) == 8888 then+ "<==" ++ (show $ tcp_src seg) + ++ " ack=" ++(show $ seq_val $ tcp_ack seg)+ ++ " seq=" ++(show $ seq_val $ tcp_seq seg)+ else+ "==>" ++ (show $ tcp_dst seg) + ++ " seq=" ++(show $ seq_val $ tcp_seq seg)+ ++ " ack=" ++(show $ seq_val $ tcp_ack seg)+ in+ let part2 =+ " ["+++ (if tcp_URG seg then " URG(urp="++ (show $ tcp_urp seg) ++ ")" else "") +++ (if tcp_SYN seg then " SYN" else "") +++ (if tcp_FIN seg then " FIN" else "") +++ (if tcp_RST seg then " RST" else "") +++ (if tcp_ACK seg then " ACK" else "") +++ (if tcp_PSH seg then " PSH" else "") +++ " ]"+ in+ part1 + ++ " WIN=" ++(show $ tcp_win seg)+ ++ " LEN=" ++(show $ bufc_length $ tcp_data seg)+ ++ part2++data UDPDatagram = UDPDatagram+ { udp_src :: TCPAddr+ , udp_dst :: TCPAddr+ , udp_data :: [Char]+ } deriving (Show, Eq)++data Protocol = PROTO_TCP | PROTO_UDP deriving (Show, Eq)++data ICMPType = + ICMP_UNREACH Int+ | ICMP_SOURCE_QUENCE Int+ | ICMP_REDIRECT Int+ | ICMP_TIME_EXCEEDED Int+ | ICMP_PARAMPROB Int+ deriving (Show, Eq)++data ICMPDatagram = ICMPDatagram+ { icmp_send :: IPAddr+ , icmp_recv :: IPAddr+ , icmp_src :: Maybe TCPAddr+ , icmp_dst :: Maybe TCPAddr+ , icmp_proto :: Protocol+ , icmp_seq :: Maybe SeqLocal+ , icmp_t :: ICMPType+ } deriving (Show, Eq)++data IPMessage = TCPMessage !TCPSegment+ | ICMPMessage !ICMPDatagram+ | UDPMessage !UDPDatagram+ deriving (Show)
+ src/Network/TCP/Type/Socket.hs view
@@ -0,0 +1,199 @@+{--+Copyright (c) 2006, Peng Li+ 2006, Stephan A. Zdancewic+All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are+met:++* Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++* Redistributions in binary form must reproduce the above copyright+ notice, this list of conditions and the following disclaimer in the+ documentation and/or other materials provided with the distribution.++* Neither the name of the copyright owners nor the names of its+ contributors may be used to endorse or promote products derived from+ this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.+--}++module Network.TCP.Type.Socket +where++import Network.TCP.Type.Base+import Network.TCP.Type.Timer+import Network.TCP.Type.Datagram+import Network.TCP.Type.Syscall+import Data.Map as Map++data TCPState = CLOSED+ | LISTEN+ | SYN_SENT+ | SYN_RECEIVED+ | ESTABLISHED+ | CLOSE_WAIT+ | FIN_WAIT_1+ | FIN_WAIT_2+ | CLOSING+ | LAST_ACK+ | TIME_WAIT+ deriving (Show,Eq)++data TCPReassSegment = TCPReassSegment + { trs_seq :: !SeqForeign+ , trs_FIN :: !Bool+ , trs_data :: !BufferChain+ } deriving (Show)++data RexmtMode = RexmtSyn+ | Rexmt+ | Persist+ deriving (Show,Eq)++data Rttinf = Rttinf+ { t_rttupdated :: !Int+ , tf_srtt_valid :: !Bool+ , t_srtt :: !Time+ , t_rttvar :: !Time+ , t_rttmin :: !Time+ , t_lastrtt :: !Time+ , t_lastshift :: !Int+ , t_wassyn :: !Bool+ } deriving (Show)+++data IOBC = NO_OOBDATA | OOBDATA Buffer | HAD_OOBDATA deriving (Show)++data SocketListen = SocketListen+ { lis_q0 :: ![SocketID] -- q0 + , lis_q :: ![SocketID] -- q+ , lis_qlimit :: !Int+ } deriving (Show,Eq)++data TCBTiming = TCBTiming+ { tt_keep :: !(Maybe Time)+ , tt_conn_est :: !(Maybe Time)+ , tt_fin_wait_2 :: !(Maybe Time)+ , tt_2msl :: !(Maybe Time)+ , t_idletime :: !Time + , ts_recent :: !(TimeWindow Timestamp)+ , t_badrxtwin :: !(TimeWindow ()) + } deriving (Show)+data TCBSending = TCBSending+ { sndq :: !BufferChain+ , snd_una :: !SeqLocal+ , snd_wnd :: !Int+ , snd_wl1 :: !SeqForeign+ , snd_wl2 :: !SeqLocal+ , snd_cwnd :: !Int+ , snd_nxt :: !SeqLocal+ , snd_max :: !SeqLocal+ , t_dupacks :: !Int+ , t_rttinf :: !Rttinf+ , t_rttseg :: !(Maybe (Timestamp, SeqLocal))+ , tt_rexmt :: !(Maybe (Timed (RexmtMode, Int)))+ } deriving (Show)+data TCBReceiving = TCBReceiving+ { last_ack_sent :: !SeqForeign+ , tf_rxwin0sent :: !Bool+ , tf_shouldacknow :: !Bool+ , tt_delack :: !Bool+ , rcv_adv :: !SeqForeign+ , rcv_wnd :: !Int+ , rcv_nxt :: !SeqForeign+ , rcvq :: !BufferChain+ , t_segq :: ![TCPReassSegment]+ } deriving (Show)+data TCBMisc = TCBMisc+ { -- retransmission+ snd_ssthresh :: !Int+ , snd_cwnd_prev :: !Int+ , snd_ssthresh_prev :: !Int+ , snd_recover :: !SeqLocal+ -- some tags+ , cantsndmore :: !Bool+ , cantrcvmore :: !Bool+ , bsd_cantconnect :: !Bool -- not very useful...+ -- initialization parameters+ , self_id :: !SocketID+ , parent_id :: !SocketID+ , local_addr :: !TCPAddr+ , remote_addr :: !TCPAddr+ , t_maxseg :: !Int+ , t_advmss :: !(Maybe Int)+ , tf_doing_ws :: !Bool + , tf_doing_tstmp :: !Bool+ , tf_req_tstmp :: !Bool+ , request_r_scale :: !(Maybe Int)+ , snd_scale :: !Int+ , rcv_scale :: !Int+ , iss :: !SeqLocal+ , irs :: !SeqForeign+ -- other things i don't use for the moment+ , sndurp :: !(Maybe Int)+ , rcvurp :: !(Maybe Int)+ , iobc :: !IOBC+ , rcv_up :: !SeqForeign+ , tf_needfin :: !Bool+ } deriving (Show)++data TCPSocket threadt = TCPSocket+ { st :: !TCPState+ , cb_time :: !TCBTiming+ , cb_snd :: !TCBSending+ , cb_rcv :: !TCBReceiving+ , cb :: !TCBMisc+ , sock_listen :: !SocketListen+ -- suspended commands (threads)+ , waiting_list :: ![(SockReq, SockRsp -> threadt)]+ } ++instance Show (TCPSocket t) where+ show (TCPSocket s cb1 cb2 cb3 cb4 lis wl) = + "TCPSocket state ="++(show s) ++ "\n" +++ " " ++ (show cb1) ++ "\n" ++ + " " ++ (show cb2) ++ "\n" ++ + " " ++ (show cb3) ++ "\n" ++ + " " ++ (show cb4) ++ "\n" ++ + " " ++ (show lis) ++ "\n" +++ " waiting: " ++ (show $ length wl)++data Host threadt = Host+ { sock_map :: !(Map SocketID (TCPSocket threadt))+ , output_queue :: ![IPMessage]+ , ready_list :: ![threadt]+ , ticks :: !Timestamp+ , clock :: !Time+ , next_timers :: !(Time,Time) -- fast timer, slow timer+ , local_ports :: ![Port]+ }++empty_host :: Host t+empty_host = Host+ { sock_map = Map.empty+ , output_queue = []+ , ready_list = []+ , ticks = Timestamp 0+ , clock = 0+ , next_timers = (0,0)+ , local_ports = [0..65535]+ }++update_host_time :: Time -> Host t -> Host t+update_host_time now h = h+ { clock = now+ }
+ src/Network/TCP/Type/Syscall.hs view
@@ -0,0 +1,57 @@+{--+Copyright (c) 2006, Peng Li+ 2006, Stephan A. Zdancewic+All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are+met:++* Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++* Redistributions in binary form must reproduce the above copyright+ notice, this list of conditions and the following disclaimer in the+ documentation and/or other materials provided with the distribution.++* Neither the name of the copyright owners nor the names of its+ contributors may be used to endorse or promote products derived from+ this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.+--}++module Network.TCP.Type.Syscall +( SocketID+, TCPAddr+, Buffer (..)+, SockReq (..)+, SockRsp (..)+)++where++import Network.TCP.Type.Base++data SockReq = SockConnect !IPAddr !TCPAddr -- create a client socket and connect (return: SockNew sock)+ | SockListen !Port -- create a listening Socket (return: SockNew sock)+ | SockAccept !SocketID -- accept connection from a listening socket (return: SockNew sock)+ | SockSend !SocketID !Buffer -- (return: SockOK)+ | SockRecv !SocketID -- (return: SockData)+ | SockClose !SocketID -- (return: SockOK)++data SockRsp = SockOK+ | SockError !String+ | SockNew !SocketID+ | SockData !Buffer+ deriving Show
+ src/Network/TCP/Type/Timer.hs view
@@ -0,0 +1,68 @@+{--+Copyright (c) 2006, Peng Li+ 2006, Stephan A. Zdancewic+All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are+met:++* Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++* Redistributions in binary form must reproduce the above copyright+ notice, this list of conditions and the following disclaimer in the+ documentation and/or other materials provided with the distribution.++* Neither the name of the copyright owners nor the names of its+ contributors may be used to endorse or promote products derived from+ this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.+--}++module Network.TCP.Type.Timer ++where++import Network.TCP.Type.Base++data Timed a = Timed { timed_val :: a+ , timed_exp :: Time + } deriving (Show, Eq)++timed_expires :: Time -> Timed a -> Bool+timed_expires t (Timed x tm) = t >= tm++timer_expires :: Time -> Time -> Bool+timer_expires t tm = t >= tm++maybe_timed_expires :: Time -> Maybe (Timed a) -> Bool+maybe_timed_expires _ Nothing = False+maybe_timed_expires curr_time (Just t) = timed_expires curr_time t++maybe_timer_expires :: Time -> Maybe Time -> Bool+maybe_timer_expires _ Nothing = False+maybe_timer_expires curr_time (Just t) = curr_time >= t++type TimeWindow a = Maybe (Timed a)++timewindow_open :: Time -> TimeWindow a -> Bool+timewindow_open = maybe_timed_expires++timewindow_val :: Time -> TimeWindow a -> Maybe a+timewindow_val t Nothing = Nothing+timewindow_val t (Just tmd) = + if timed_expires t tmd then Nothing else Just (timed_val tmd)++