packages feed

hans 2.6.0.0 → 3.0.2

raw patch · 134 files changed

Files

cbits/tapdevice.c view
@@ -9,8 +9,8 @@ #include <linux/if.h> #include <linux/if_tun.h> -int init_tap_device(char *name) {-  int fd, ret;+int init_tap_device(char *name, unsigned char *mac) {+  int fd, ret, sock;   struct ifreq ifr;    if(name == NULL) {@@ -32,6 +32,25 @@     return -3;   } +  // generate a MAC address based on the one in the linux side.+  sock = socket(AF_INET, SOCK_DGRAM, 0);+  if(sock == -1) {+    close(fd);+    return -4;+  }++  if(ioctl(sock,SIOCGIFHWADDR,&ifr) == -1) {+    close(fd);+    close(sock);+    return -5;+  }++  // flip the last bit of the mac to generate a new one on the same link+  memcpy(mac, ifr.ifr_hwaddr.sa_data, 6);+  mac[5] ^= 1;++  close(sock);+   return fd; } @@ -43,19 +62,24 @@ #include <stdio.h> #include <sys/ioctl.h> #include <net/if.h>+#include <net/if_dl.h>+#include <ifaddrs.h> #include <errno.h> #include <string.h> -int init_tap_device(char *name)+int init_tap_device(char *name, char *mac) {   char *pathname = alloca(32);   struct ifreq ifr;-  int fd, sock, flags;+  int fd, sock, flags, found, i;+  struct ifaddrs *iflist;+  struct ifaddrs *cur;+  struct sockaddr_dl *sdl;    snprintf(pathname, 32, "/dev/%s", name);   fd = open(pathname, O_RDWR);   if(fd < 0) {-    printf("Open failed (%s)\n", pathname);+    printf("Open failed (%s) (%d)\n", pathname, fd);     return -2;   }   printf("fd = %d\n", fd);@@ -78,6 +102,31 @@     printf("Set failed: %d (%s)\n", errno, strerror(errno));     return -errno;   }++  found = 0;+  if(getifaddrs(&iflist) == 0) {+    for(cur = iflist; cur; cur = cur->ifa_next) {+      if((cur->ifa_addr->sa_family == AF_LINK) &&+              cur->ifa_addr &&+              strcmp(cur->ifa_name, name) == 0) {+        sdl = (struct sockaddr_dl*)cur->ifa_addr;+        memcpy(mac, LLADDR(sdl), sdl->sdl_alen);++        // flip the last bit of the mac address to generate a new address.+        mac[5] ^= 1;+        found = 1;+        break;+      }+    }+  }++  if(found == 0) {+    return -2;+  }++++  close(sock);    flags = fcntl(fd, F_GETFL, 0);   if(flags == -1) {
− echo-client/Main.hs
@@ -1,73 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE CPP #-}-module Main where--import Control.Concurrent (threadDelay)-import Hans.Address.Mac-import Hans.DhcpClient-import Hans.NetworkStack-import System.Environment (getArgs)--#ifdef HaLVM_HOST_OS-import Hans.Device.Xen-import Hypervisor.Console-import Hypervisor.XenStore-import XenDevice.NIC-#else-import Hans.Device.Tap-#endif--main :: IO ()-main  = do--  [ip] <- getArgs-  let addr = read ip--  ns  <- newNetworkStack-  mac <- initEthernetDevice ns--  deviceUp ns mac-  putStrLn "Network stack running..."--  putStrLn "Discovering address"--  mbIP <- dhcpDiscover ns mac-  case mbIP of-    Nothing -> putStrLn "Couldn't get an IP address."-    Just self -> do-      putStrLn ("Bound to address: " ++ show self)--      sock <- connect ns addr 9001 Nothing-      putStrLn ("Connected to: " ++ show addr)--      putStrLn "Sending bytes..."-      sent <- sendBytes sock "Hello"--      putStrLn ("Sent " ++ show sent ++ " bytes")-      print =<< recvBytes sock 512--      threadDelay 1000000--      close sock--#ifdef HaLVM_HOST_OS-initEthernetDevice :: NetworkStack -> IO Mac-initEthernetDevice ns =-  do xs <- initXenStore-     _ <- initXenConsole -- should set up putStrLn-     nics <- listNICs xs-     case nics of-       [] -> fail "No NICs found to use!"-       (macstr:_) ->-         do let mac = read macstr-            nic <- openNIC xs macstr-            addDevice ns mac (xenSend nic) (xenReceiveLoop nic)-            return mac-#else-initEthernetDevice :: NetworkStack -> IO Mac-initEthernetDevice ns = do-  let mac = Mac 0x52 0x54 0x00 0x12 0x34 0x57-  Just dev <- openTapDevice "tap7"-  addDevice ns mac (tapSend dev) (tapReceiveLoop dev)-  return mac-#endif
− example/WebServer.hs
@@ -1,152 +0,0 @@-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.Message.Tcp (TcpPort)-import Hans.NetworkStack-    (NetworkStack,tcpHandle,Socket,readLine,sendSocket,acceptSocket,listenPort-    ,closeSocket,SocketError(..))-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 (tcpHandle 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
@@ -1,97 +0,0 @@-{-# LANGUAGE CPP #-}--module Main where--import WebServer--import Hans.Address-import Hans.Address.IP4-import Hans.Address.Mac-import Hans.DhcpClient (dhcpDiscover)-import Hans.Message.Tcp (TcpPort(..))-import Hans.NetworkStack--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-  addDevice mac (xenSend nic) (xenReceiveLoop nic) ns-  --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"-  addDevice 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  <- newNetworkStack-  mac <- initEthernetDevice ns-  deviceUp 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]  -> do-      addIP4Addr ns (read ip `withMask` 24) mac 1500-      routeVia ns (IP4 0 0 0 0 `withMask` 0) (read gw)-    _        -> do-      putStrLn "Usage: <prog> dhcp"-      putStrLn "       <prog> <ip> <gateway>"-      exitFailure
+ examples/echo-server/Main.hs view
@@ -0,0 +1,97 @@+{-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE OverloadedStrings #-}+module Main where++import Hans+import Hans.Dns+import Hans.Device+import Hans.Nat+import Hans.Socket+import Hans.IP4.Packet (pattern WildcardIP4)+import Hans.IP4.Dhcp.Client (DhcpLease(..),defaultDhcpConfig,dhcpClient)++import           Control.Concurrent (forkIO,threadDelay)+import           Control.Exception+import           Control.Monad (forever)+import qualified Data.ByteString.Char8 as S8+import qualified Data.ByteString.Lazy.Char8 as L8+import           System.Environment (getArgs)+import           System.Exit (exitFailure)+++main :: IO ()+main  =+  do args        <- getArgs+     (name,dhcp) <- case args of+                      [name,"dhcp"] -> return (S8.pack name,True)+                      name:_        -> return (S8.pack name,False)+                      _             -> fail "Expected a device name"++     ns  <- newNetworkStack defaultConfig+     dev <- addDevice ns name defaultDeviceConfig++     _ <- forkIO (showExceptions "processPackets" (processPackets ns))++     -- start receiving data+     startDevice dev++     if dhcp+        then+          do mbLease <- dhcpClient ns defaultDhcpConfig dev+             case mbLease of++               Just lease ->+                 do putStrLn ("Assigned IP: " ++ show (unpackIP4 (dhcpAddr lease)))+                    print =<< getHostByName ns "galois.com"++               Nothing ->+                 do putStrLn "Dhcp failed"+                    exitFailure++        else+          do putStrLn "Static IP: 192.168.71.11/24"++             addIP4Route ns False Route+               { routeNetwork = IP4Mask (packIP4 192 168 71 11) 24+               , routeType    = Direct+               , routeDevice  = dev+               }++             addIP4Route ns True Route+               { routeNetwork = IP4Mask (packIP4 192 168 71 11) 0+               , routeType    = Indirect (packIP4 192 168 71 1)+               , routeDevice  = dev+               }++     forwardTcpPort ns WildcardIP4 22 (packIP4 172 16 181 1) 22+     forwardUdpPort ns WildcardIP4 9090 (packIP4 172 16 181 128) 9090++     sock <- sListen ns defaultSocketConfig WildcardIP4 9001 10++     _    <- forkIO $ forever $+       do putStrLn "Waiting for a client"+          client <- sAccept (sock :: TcpListenSocket IP4)+          putStrLn "Got a client"+          _ <- forkIO (handleClient client)+          return ()++     threadDelay (300 * 1000000)++     dumpStats (devStats dev)+++showExceptions :: String -> IO a -> IO a+showExceptions l m = m `catch` \ e ->+  do print (l, e :: SomeException)+     throwIO e+++handleClient :: TcpSocket IP4 -> IO ()+handleClient sock = loop `finally` sClose sock+  where+  loop =+    do str <- sRead sock 1024+       if L8.null str+          then putStrLn "Closing client"+          else do _ <- sWrite sock str+                  loop
hans.cabal view
@@ -1,243 +1,159 @@ name:           hans-version:        2.6.0.0-cabal-version:  >= 1.8+version:        3.0.2+cabal-version:  >= 1.18 license:        BSD3 license-file:   LICENSE-author:         Galois Inc., Peng Li, Stephan Zdancewic+author:         Galois Inc. maintainer:     halvm-devel@community.galois.com category:       Networking-synopsis:       IPv4 Network Stack+synopsis:       Network Stack build-type:     Simple+tested-with:    GHC == 7.8.4, GHC == 7.10.2, GHC == 7.10.3, GHC == 8.0.1 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.+  networking in the context of the HaLVM, or with a Linux tap device.  source-repository head         type:           git         location:       git://github.com/GaloisInc/HaNS.git -flag bounded-channels+flag examples+        description:    Build example programs         default:        False-        description:    Use bounded channels for message passing -flag enable-tests-        default:        False-        description:    Enable building the test suite--flag example-        default:        False-        description:    Build the example program--flag web-server-        description:    Build a simple web-server example--flag word32-in-random-        default:        False-        description:    Word32 in Random- library-        if(os(HaLVM))-                build-depends:          XenDevice >= 2.0.0 && < 3.0.0,-                                        HALVMCore >= 2.0.0 && < 3.0.0-                exposed-modules:        Hans.Device.Xen,-                                        Hans.Device.Ivc-        else+        if os(HALVM)+                cpp-options:            -DHANS_TARGET_XEN+                build-depends:          XenDevice >= 2.0.0 && < 3,+                                        HALVMCore >= 2.0.0 && < 3+                other-modules:          Hans.Device.Xen++        if os(darwin) || os(linux)+                cpp-options:            -DHANS_TARGET_UNIX                 build-depends:          unix+                other-modules:          Hans.Device.Tap                 include-dirs:           cbits                 c-sources:              cbits/tapdevice.c-                exposed-modules:        Hans.Device.Tap -        if flag(bounded-channels)-                build-depends:  BoundedChan-                cpp-options:    -DBOUNDED_CHANNELS+        -- XXX: Tap devices are supported on windows, someone should add support -        ghc-options:            -Wall+        default-language:       Haskell2010++        ghc-options:            -Wall -O2         hs-source-dirs:         src         build-depends:          base       >= 4.0.0.0 && < 5,-                                cereal     >= 0.3.5.2,-                                bytestring >= 0.9.1.0,-                                containers >= 0.3.0.0,-                                monadLib   >= 3.6.0,-                                time       >= 1.1.0.0,-                                fingertree >= 0.0.1.0,-                                stm        >= 2.4+                                cereal     >= 0.5.0.0,+                                heaps      <= 0.3.3,+                                psqueues,+                                bytestring,+                                containers,+                                array,+                                time,+                                hashable,+                                array,+                                BoundedChan,+                                random,+                                monadLib,+                                SHA -        if flag(word32-in-random)-                build-depends:  random     >= 1.0.1.0-                cpp-options:    -DWORD32_IN_RANDOM-        else-                build-depends:  random     >= 1.0.0.0+        -- XXX: which of these should be exposed?+        exposed-modules:        Hans+                                Hans.Addr+                                Hans.Addr.Types+                                Hans.Checksum+                                Hans.Config+                                Hans.Device+                                Hans.Device.Loopback+                                Hans.Device.Types+                                Hans.Dns+                                Hans.Dns.Packet+                                Hans.Ethernet+                                Hans.Ethernet.Types+                                Hans.HashTable+                                Hans.IP4+                                Hans.IP4.ArpTable+                                Hans.IP4.Dhcp.Client+                                Hans.IP4.Dhcp.Codec+                                Hans.IP4.Dhcp.Packet+                                Hans.IP4.Dhcp.Options+                                Hans.IP4.Fragments+                                Hans.IP4.Icmp4+                                Hans.IP4.Input+                                Hans.IP4.Output+                                Hans.IP4.Packet+                                Hans.IP4.RoutingTable+                                Hans.IP4.State+                                Hans.Input+                                Hans.Lens+                                Hans.Monad+                                Hans.Nat+                                Hans.Network+                                Hans.Network.Types+                                Hans.Serialize+                                Hans.Socket+                                Hans.Socket.Handle+                                Hans.Tcp.Input+                                Hans.Tcp.Message+                                Hans.Tcp.Output+                                Hans.Tcp.Packet+                                Hans.Tcp.RecvWindow+                                Hans.Tcp.SendWindow+                                Hans.Tcp.State+                                Hans.Tcp.Tcb+                                Hans.Tcp.Timers+                                Hans.Time+                                Hans.Types+                                Hans.Udp.Input+                                Hans.Udp.Output+                                Hans.Udp.Packet+                                Hans.Udp.State -        exposed-modules:        Data.PrefixTree,-                                Hans.Address,-                                Hans.Address.IP4,-                                Hans.Address.Mac,-                                Hans.Channel,-                                Hans.DhcpClient,-                                Hans.Layer,-                                Hans.Layer.Arp,-                                Hans.Layer.Arp.Table,-                                Hans.Layer.Ethernet,-                                Hans.Layer.Dns,-                                Hans.Layer.IP4,-                                Hans.Layer.IP4.Fragmentation,-                                Hans.Layer.IP4.Routing,-                                Hans.Layer.Icmp4,-                                Hans.Layer.Tcp,-                                Hans.Layer.Tcp.Handlers,-                                Hans.Layer.Tcp.Messages,-                                Hans.Layer.Tcp.Monad,-                                Hans.Layer.Tcp.Socket,-                                Hans.Layer.Tcp.Timers,-                                Hans.Layer.Tcp.Types,-                                Hans.Layer.Tcp.WaitBuffer,-                                Hans.Layer.Tcp.Window,-                                Hans.Layer.Udp,-                                Hans.Message.Arp,-                                Hans.Message.Dhcp4,-                                Hans.Message.Dhcp4Codec,-                                Hans.Message.Dhcp4Options,-                                Hans.Message.Dns,-                                Hans.Message.EthernetFrame,-                                Hans.Message.Icmp4,-                                Hans.Message.Ip4,-                                Hans.Message.Tcp,-                                Hans.Message.Types,-                                Hans.Message.Udp,-                                Hans.NetworkStack,-                                Hans.Ports,-                                Hans.Simple,-                                Hans.Timers,-                                Hans.Utils,-                                Hans.Utils.Checksum+        other-modules:          Hans.Buffer.Datagram+                                Hans.Buffer.Signal+                                Hans.Buffer.Stream+                                Hans.Nat.Forward+                                Hans.Nat.State+                                Hans.Socket.Tcp+                                Hans.Socket.Types+                                Hans.Socket.Udp+                                Hans.Threads +executable echo-server+        if flag(examples)+                build-depends:  base >= 4.0.0.0 && < 5,+                                bytestring,+                                hans -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.0.0.0 && < 5,-                        cereal     >= 0.3.5.2,-                        bytestring >= 0.9.1.0,-                        containers >= 0.3.0.0,-                        monadLib   >= 3.6.0,-                        time       >= 1.1.0.0,-                        old-locale >= 1.0.0.0,-                        hans--        if os(HaLVM)-                build-depends:      XenDevice     >= 2.0.0 && < 3.0.0,-                                    HALVMCore     >= 2.0.0 && < 3.0.0-        else-                ghc-options: -threaded--executable web-server-        main-is:        Main.hs-        hs-source-dirs: web-server-        ghc-options:    -Wall -threaded -rtsopts -fno-warn-unused-do-bind-                        -fno-warn-orphans--        if os(HaLVM)-                buildable: False-        else-          if flag(web-server)-                  build-depends:  base       >= 4.0.0.0 && < 5,-                                  cereal     >= 0.3.5.2,-                                  bytestring >= 0.9.1.0,-                                  containers >= 0.3.0.0,-                                  monadLib   >= 3.6.0,-                                  time       >= 1.1.0.0,-                                  old-locale >= 1.0.0.0,-                                  HTTP       >= 4000.2.17,-                                  blaze-html >= 0.7.0.2,-                                  blaze-markup,-                                  hans--          else-                  buildable: False--executable tcp-test-        main-is:        Main.hs-        hs-source-dirs: tcp-test-        ghc-options:    -Wall -rtsopts -threaded-        build-depends:  base       >= 4.0.0.0 && < 5,-                        cereal     >= 0.3.5.2,-                        bytestring >= 0.9.1.0,-                        containers >= 0.3.0.0,-                        monadLib   >= 3.6.0,-                        time       >= 1.1.0.0,-                        old-locale >= 1.0.0.0,-                        hans-        if os(HaLVM)-                build-depends:      XenDevice     >= 2.0.0 && < 3.0.0,-                                    HALVMCore     >= 2.0.0 && < 3.0.0--executable echo-client-        main-is:        Main.hs-        hs-source-dirs: echo-client-        ghc-options:    -Wall -rtsopts -threaded-        build-depends:  base       >= 4.0.0.0 && < 5,-                        cereal     >= 0.3.5.2,-                        bytestring >= 0.9.1.0,-                        containers >= 0.3.0.0,-                        monadLib   >= 3.6.0,-                        time       >= 1.1.0.0,-                        old-locale >= 1.0.0.0,-                        hans-        if os(HaLVM)-                build-depends:      XenDevice     >= 2.0.0 && < 3.0.0,-                                    HALVMCore     >= 2.0.0 && < 3.0.0--executable tcp-test-client-        main-is:        Main.hs-        hs-source-dirs: tcp-test-client-        if os(HaLVM)-                buildable:          False-        else-                build-depends:      base,-                                    bytestring,-                                    network+        default-language:       Haskell2010+        main-is:                Main.hs+        hs-source-dirs:         examples/echo-server+        ghc-options:            -Wall -threaded -O2  -executable test-suite-        hs-source-dirs: tests-        main-is:        Main.hs--        if flag(enable-tests)-                buildable:      True-                other-modules:  Icmp4,-                                Icmp4.Packet,-                                IP4,-                                IP4.Addr,-                                IP4.Packet,-                                Tcp,-                                Tcp.Packet,-                                Tcp.Window,-                                Udp,-                                Udp.Packet,-                                Utils--                build-depends:  base                       >= 4 && < 5,-                                containers                 >= 0.3,-                                bytestring                 >= 0.9.1,-                                old-locale                 >= 1,-                                test-framework-quickcheck2 >= 0.2.12.2,-                                test-framework             >= 0.6,-                                QuickCheck                 >= 2.4.2,-                                random                     >= 1.0.1.1,-                                cereal                     >= 0.3.5.2,+test-suite hans-tests+        default-language:       Haskell2010+        type:                   exitcode-stdio-1.0+        build-depends:          base >= 4.4 && < 5,+                                bytestring,+                                tasty >= 0.11,+                                tasty-quickcheck,+                                tasty-ant-xml,+                                QuickCheck,+                                cereal,                                 hans -        else-                buildable:      False+        ghc-options:            -Wall -threaded++        hs-source-dirs:         tests+        main-is:                Main.hs+        other-modules:          Tests.Checksum+                                Tests.Ethernet+                                Tests.IP4+                                Tests.IP4.Fragmentation+                                Tests.IP4.Icmp4+                                Tests.IP4.Packet+                                Tests.Network+                                Tests.Utils
− src/Data/PrefixTree.hs
@@ -1,244 +0,0 @@-{-# 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.hs view
@@ -0,0 +1,111 @@+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE RecursiveDo #-}+{-# LANGUAGE CPP #-}++module Hans (+    -- * Network Stack+    NetworkStack(), Config(..), defaultConfig,+    newNetworkStack,+    processPackets,++    -- * Devices+    DeviceName,+    Device(), DeviceConfig(..), defaultDeviceConfig,+    addDevice,+    listDevices,+    closeDevice,+    startDevice,++    -- * Network Layer+    Addr(), sameFamily,+    NetworkAddr(..),+    Network(..),+    RouteInfo(..),++    -- ** IP4+    IP4.IP4(), IP4.packIP4, IP4.unpackIP4,+    IP4.IP4Mask(..),+    IP4.Route(..), IP4.RouteType(Direct,Indirect),+    addIP4Route,++  ) where++import           Hans.Addr (NetworkAddr(..),Addr(),sameFamily)+import           Hans.Config+import           Hans.Device+import           Hans.Device.Loopback+import qualified Hans.IP4.State as IP4+import qualified Hans.IP4.Packet as IP4+import qualified Hans.IP4.RoutingTable as IP4 (Route(..),RouteType(..))+import qualified Hans.IP4.Output as IP4 (responder)+import           Hans.Input+import           Hans.Network+import           Hans.Threads (forkNamed)+import           Hans.Types+import qualified Hans.Tcp.Output as Tcp (responder)+import qualified Hans.Tcp.Timers as Tcp+import qualified Hans.Udp.Output as Udp (responder)++import Control.Concurrent.BoundedChan (newBoundedChan)+import Data.IORef (newIORef,atomicModifyIORef')++#ifdef HANS_TARGET_XEN+import Hypervisor.XenStore (XenStore)+#endif+++-- | Create a network stack with no devices registered.+newNetworkStack :: Config -> IO NetworkStack+newNetworkStack nsConfig =+  do nsInput        <- newBoundedChan (cfgInputQueueSize nsConfig)+     nsNat          <- newNatState nsConfig+     nsDevices      <- newIORef []+     nsIP4State     <- newIP4State nsConfig+     nsUdpState     <- newUdpState nsConfig+     nsTcpState     <- newTcpState nsConfig+     nsNameServers4 <- newIORef []++     rec nsIP4Responder <- forkNamed "IP4.responder" (IP4.responder ns)+         nsTcpTimers    <- forkNamed "Tcp.tcpTimers" (Tcp.tcpTimers ns)+         nsTcpResponder <- forkNamed "Tcp.responder" (Tcp.responder ns)+         nsUdpResponder <- forkNamed "Udp.responder" (Udp.responder ns)+         let ns = NetworkStack { .. }++     registerLoopback ns++     return ns++-- | Create and register the loopback device. Additionally, add its routing+-- information.+registerLoopback :: NetworkStack -> IO ()+registerLoopback ns =+  do lo <- newLoopbackDevice ns+     atomicModifyIORef' (nsDevices ns) (\devs -> (lo : devs, ()))++     -- add the route for 127.0.0.0/8+     addIP4Route ns False+         IP4.Route { routeNetwork = IP4.IP4Mask (IP4.packIP4 127 0 0 1) 8+                   , routeType    = IP4.Direct+                   , routeDevice  = lo }++-- | Initialize and register a device with the network stack.+-- NOTE: this does not start the device.++#ifdef HANS_TARGET_XEN+addDevice :: XenStore -> NetworkStack -> DeviceName -> DeviceConfig -> IO Device+addDevice xs ns devName devConfig =+  do dev <- openDevice xs ns devName devConfig+     atomicModifyIORef' (nsDevices ns) (\devs -> (dev : devs, ()))+     return dev+#else+addDevice :: NetworkStack -> DeviceName -> DeviceConfig -> IO Device+addDevice ns devName devConfig =+  do dev <- openDevice ns devName devConfig+     atomicModifyIORef' (nsDevices ns) (\devs -> (dev : devs, ()))+     return dev+#endif++-- | Add a route to the IP4 layer.+addIP4Route :: NetworkStack -> Bool -> IP4.Route -> IO ()+addIP4Route NetworkStack { .. } = IP4.addRoute nsIP4State+{-# INLINE addIP4Route #-}
+ src/Hans/Addr.hs view
@@ -0,0 +1,71 @@+{-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE StandaloneDeriving #-}++module Hans.Addr (+    Addr(), sameFamily,+    NetworkAddr(..),+    putAddr,+    showAddr,+  ) where++import           Hans.Addr.Types+import qualified Hans.IP4.Packet as IP4++import Data.Hashable (Hashable)+import Data.Typeable (Typeable)+import GHC.Generics (Generic)+++class (Hashable addr, Show addr, Typeable addr, Eq addr, Generic addr)+  => NetworkAddr addr where+  -- | Forget what kind of address this is.+  toAddr :: addr -> Addr++  -- | Try to remember what this opaque address was.+  fromAddr :: Addr -> Maybe addr++  -- | Check to see if this address is the wildcard address.+  isWildcardAddr :: addr -> Bool++  -- | The wildcard address+  wildcardAddr :: addr -> addr++  -- | Check to see if this address is the broadcast address.+  isBroadcastAddr :: addr -> Bool++  -- | The broadcast address.+  broadcastAddr :: addr -> addr+++instance NetworkAddr Addr where+  toAddr                       = id+  fromAddr addr                = Just addr+  isWildcardAddr  (Addr4 addr) = isWildcardAddr addr+  wildcardAddr    (Addr4 addr) = Addr4 (wildcardAddr addr)+  isBroadcastAddr (Addr4 addr) = isBroadcastAddr addr+  broadcastAddr   (Addr4 addr) = Addr4 (broadcastAddr addr)+  {-# INLINE toAddr #-}+  {-# INLINE fromAddr #-}+  {-# INLINE isWildcardAddr #-}+  {-# INLINE wildcardAddr #-}+  {-# INLINE isBroadcastAddr #-}+  {-# INLINE broadcastAddr #-}+++instance NetworkAddr IP4.IP4 where+  toAddr                           = Addr4+  fromAddr (Addr4 addr)            = Just addr+  isWildcardAddr IP4.WildcardIP4   = True+  isWildcardAddr _                 = False+  wildcardAddr _                   = IP4.WildcardIP4+  isBroadcastAddr IP4.BroadcastIP4 = True+  isBroadcastAddr _                = False+  broadcastAddr _                  = IP4.BroadcastIP4+  {-# INLINE toAddr #-}+  {-# INLINE fromAddr #-}+  {-# INLINE isWildcardAddr #-}+  {-# INLINE wildcardAddr #-}+  {-# INLINE isBroadcastAddr #-}+  {-# INLINE broadcastAddr #-}
+ src/Hans/Addr/Types.hs view
@@ -0,0 +1,25 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DeriveDataTypeable #-}+module Hans.Addr.Types where++import Hans.IP4.Packet (IP4,putIP4,showIP4)++import Data.Hashable (Hashable)+import Data.Serialize (Put)+import Data.Typeable (Typeable)+import GHC.Generics (Generic)++data Addr = Addr4 !IP4+            deriving (Eq,Ord,Show,Generic,Typeable)++instance Hashable Addr++putAddr :: Addr -> Put+putAddr (Addr4 ip) = putIP4 ip++showAddr :: Addr -> ShowS+showAddr (Addr4 ip4) = showIP4 ip4+++sameFamily :: Addr -> Addr -> Bool+sameFamily Addr4{} Addr4{} = True
− src/Hans/Address.hs
@@ -1,24 +0,0 @@-{-# 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
@@ -1,113 +0,0 @@-{-# LANGUAGE DeriveDataTypeable #-}-{-# LANGUAGE DeriveGeneric #-}-{-# 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 (Get,getWord32be)-import Data.Serialize.Put (Putter,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 GHC.Generics (Generic)-import Numeric (readDec)---data IP4 = IP4-  {-# UNPACK #-} !Word8-  {-# UNPACK #-} !Word8-  {-# UNPACK #-} !Word8-  {-# UNPACK #-} !Word8-  deriving (Ord,Eq,Typeable,Data,Generic)--broadcastIP4 :: IP4-broadcastIP4  = IP4 255 255 255 255--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--parseIP4 :: Get IP4-parseIP4  = convertFromWord32 `fmap` getWord32be--renderIP4 :: Putter IP4-renderIP4  = putWord32be . convertToWord32--instance Serialize IP4 where-  get = parseIP4-  put = renderIP4--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
@@ -1,93 +0,0 @@-{-# LANGUAGE DeriveGeneric #-}--module Hans.Address.Mac (-    Mac(..)-  , parseMac-  , renderMac-  , broadcastMac-  , showsMac-  , macMask-  ) where--import Hans.Address-import Hans.Utils (showPaddedHex)--import Control.Applicative ((<*>),(<$>))-import Data.Serialize (Serialize(..))-import Data.Serialize.Get (Get,getWord8)-import Data.Serialize.Put (Putter,putByteString)-import Data.Bits (Bits(testBit,complement))-import Data.List (intersperse)-import Data.Word (Word8)-import GHC.Generics ( Generic )-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, Generic )----- | 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)---- | The broadcast mac address.-broadcastMac :: Mac-broadcastMac  = Mac 0xff 0xff 0xff 0xff 0xff 0xff--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]--parseMac :: Get Mac-parseMac  =  Mac-         <$> getWord8-         <*> getWord8-         <*> getWord8-         <*> getWord8-         <*> getWord8-         <*> getWord8--renderMac :: Putter Mac-renderMac (Mac a b c d e f) = putByteString (S.pack [a,b,c,d,e,f])--instance Serialize Mac where-  get = parseMac-  put = renderMac
+ src/Hans/Buffer/Datagram.hs view
@@ -0,0 +1,121 @@+{-# LANGUAGE RecordWildCards #-}++module Hans.Buffer.Datagram (+    Buffer,+    newBuffer,+    writeChunk,+    readChunk,+    tryReadChunk,+    isEmptyBuffer+  ) where++import Hans.Buffer.Signal++import           Control.Monad (when)+import qualified Data.ByteString as S+import           Data.IORef (IORef,newIORef,atomicModifyIORef',readIORef)+import qualified Data.Sequence as Seq+++-- Buffers ---------------------------------------------------------------------++data Buffer a = Buffer { bufContents :: {-# UNPACK #-} !(IORef (BufContents a))+                       , bufSignal   :: {-# UNPACK #-} !Signal+                         -- ^ The wait queue. Waiters queue up trying to take the+                         -- MVar, and are unblocked when there are chunks+                         -- available in the queue.+                       }++newBuffer :: Int -> IO (Buffer a)+newBuffer size =+  do bufContents <- newIORef (emptyBufContents size)+     bufSignal   <- newSignal+     return Buffer { .. }++-- | Write a chunk to the buffer. Returns 'False' if the chunk could not be+-- written.+writeChunk :: Buffer a -> a -> S.ByteString -> IO Bool+writeChunk Buffer { .. } a bytes =+  do (written,more) <- atomicModifyIORef' bufContents (queueChunk a bytes)+     when more (signal bufSignal)+     return written+++-- | Read a chunk from the buffer, blocking until one is ready. +readChunk :: Buffer a -> IO (a,S.ByteString)+readChunk Buffer { .. } = loop+  where+  loop =+    do mb <- atomicModifyIORef' bufContents dequeueChunk+       case mb of+         Just (c,more) ->+           do when more (signal bufSignal)+              return c++         Nothing ->+           do waitSignal bufSignal+              loop+++-- | Poll for a ready chunk.+tryReadChunk :: Buffer a -> IO (Maybe (a,S.ByteString))+tryReadChunk Buffer { .. } =+  do mb <- atomicModifyIORef' bufContents dequeueChunk+     case mb of+       Just (a,more) ->+         do when more (signal bufSignal)+            return (Just a)++       Nothing ->+            return Nothing++-- | See if the buffer is empty.+isEmptyBuffer :: Buffer a -> IO Bool+isEmptyBuffer Buffer { .. } =+  (Seq.null . bufChunks) `fmap` readIORef bufContents++-- Buffer State ----------------------------------------------------------------++data BufContents a = BufContents { bufAvail :: {-# UNPACK #-} !Int+                                   -- ^ Available space in the buffer, in bytes.++                                 , bufChunks  :: !(Seq.Seq (a,S.ByteString))+                                   -- ^ Chunks present in the buffer.+                                 }+++emptyBufContents :: Int -> BufContents a+emptyBufContents bufAvail = BufContents { bufChunks = Seq.empty, .. }++chunksAvailable :: BufContents a -> Bool+chunksAvailable BufContents { .. } = not (Seq.null bufChunks)++-- | The return value is as follows:+--+--  The first element is 'True' when the chunk has been written to the queue+--  The second element is 'True' when there is more data present in the queue+--+-- This covers a funny case where the queue was empty, and the chunk that was+-- given was too big for the whole buffer -- the buffer wasn't written, and the+-- queue is still empty.+queueChunk :: a -> S.ByteString -> BufContents a -> (BufContents a,(Bool,Bool))+queueChunk a chunk buf+  | bufAvail buf >= chunkLen =+    (BufContents { bufAvail  = bufAvail buf - chunkLen+                 , bufChunks = bufChunks buf Seq.|> (a,chunk) }, (True,True))++  | otherwise =+    (buf, (False,chunksAvailable buf))++  where+  chunkLen = S.length chunk++dequeueChunk :: BufContents a -> (BufContents a, Maybe ((a,S.ByteString),Bool))+dequeueChunk buf =+  case Seq.viewl (bufChunks buf) of+    c Seq.:< cs ->+      let buf' = BufContents { bufAvail  = bufAvail buf + S.length (snd c)+                             , bufChunks = cs }+       in (buf', Just (c, chunksAvailable buf'))++    _ -> (buf, Nothing)
+ src/Hans/Buffer/Signal.hs view
@@ -0,0 +1,23 @@+module Hans.Buffer.Signal where++import Control.Concurrent (MVar,newEmptyMVar,tryPutMVar,takeMVar,tryTakeMVar)++type Signal = MVar ()++newSignal :: IO Signal+newSignal  = newEmptyMVar++signal :: Signal -> IO ()+signal var =+  do _ <- tryPutMVar var ()+     return ()++waitSignal :: Signal -> IO ()+waitSignal  = takeMVar++tryWaitSignal :: Signal -> IO Bool+tryWaitSignal var =+  do mb <- tryTakeMVar var+     case mb of+       Just () -> return True+       Nothing -> return False
+ src/Hans/Buffer/Stream.hs view
@@ -0,0 +1,131 @@+{-# LANGUAGE RecordWildCards #-}++-- | The rationale behind this buffer not tracking any size parameter, is that+-- it will always be used as part of the receive window. As such, there should+-- only ever be at most a full window's worth of data queued, as other data will+-- be rejected.++module Hans.Buffer.Stream (+    Buffer(),+    newBuffer,+    closeBuffer,+    bytesAvailable,+    putBytes,+    takeBytes,+    tryTakeBytes,+  ) where++import Hans.Buffer.Signal++import           Control.Monad (when,unless)+import qualified Data.ByteString as S+import qualified Data.ByteString.Lazy as L+import           Data.IORef (IORef,newIORef,atomicModifyIORef',readIORef)+import qualified Data.Sequence as Seq+++-- Stream Buffers --------------------------------------------------------------++data Buffer = Buffer { bufState :: !(IORef State)+                       -- ^ The buffer and current size++                     , bufSignal :: !Signal+                       -- ^ Data available signal+                     }++newBuffer :: IO Buffer+newBuffer  =+  do bufState  <- newIORef emptyState+     bufSignal <- newSignal+     return Buffer { .. }++closeBuffer :: Buffer -> IO ()+closeBuffer Buffer { .. } =+  do atomicModifyIORef' bufState sClose+     signal bufSignal++bytesAvailable :: Buffer -> IO Bool+bytesAvailable Buffer { .. } =+  do st <- readIORef bufState+     return (not (sNull st))++putBytes :: S.ByteString -> Buffer -> IO ()+putBytes bytes Buffer { .. } =+  do unless (S.null bytes) (atomicModifyIORef' bufState (sPut bytes))+     signal bufSignal++-- | Take up to n bytes from the buffer, blocking until some data is ready.+takeBytes :: Int -> Buffer -> IO L.ByteString+takeBytes n Buffer { .. }+  | n <= 0    = return L.empty+  | otherwise = loop+  where+  loop =+    do (bytes,more,closed) <- atomicModifyIORef' bufState (sTake n)+       if L.null bytes && not closed+          then do waitSignal bufSignal+                  loop+          else do when (more || closed) (signal bufSignal)+                  return bytes++-- | Take up to n bytes from the buffer, returning immediately if no data is+-- available.+tryTakeBytes :: Int -> Buffer -> IO (Maybe L.ByteString)+tryTakeBytes n Buffer { .. }+  | n <= 0    = return Nothing+  | otherwise =+    do (bytes,more,closed) <- atomicModifyIORef' bufState (sTake n)+       when (more || closed) (signal bufSignal)+       if L.null bytes+          then return Nothing+          else return (Just bytes)+++-- Internal State --------------------------------------------------------------++data State = State { stBuf    :: !(Seq.Seq S.ByteString)+                   , stClosed :: !Bool+                   }++emptyState :: State+emptyState  = State { stBuf = Seq.empty, stClosed = False }++sNull :: State -> Bool+sNull State { .. } = Seq.null stBuf++sClose :: State -> (State, ())+sClose State { .. } = (State { stClosed = True, .. }, ())++-- | Remove up to n bytes of data from the internal state.+sTake :: Int -> State -> (State, (L.ByteString,Bool,Bool))+sTake n0 State { .. } = go [] n0 stBuf+  where++  go acc n mem+    | n > 0 =+      case Seq.viewl mem of++        buf Seq.:< mem'++          | S.length buf > n ->+            let (as,bs) = S.splitAt n buf+             in finalize (L.fromStrict as:acc) (bs Seq.<| mem')++          | otherwise ->+            go (L.fromStrict buf:acc) (n - S.length buf) mem'+++        Seq.EmptyL ->+          finalize acc Seq.empty++    | otherwise =+      finalize acc mem++  finalize acc mem =+    ( State { stBuf = mem, .. }+    , (L.concat (reverse acc), not (Seq.null mem), stClosed)+    )+++sPut :: S.ByteString -> State -> (State, ())+sPut bytes State { .. } = (State { stBuf = stBuf Seq.|> bytes, .. }, ())
− src/Hans/Channel.hs
@@ -1,34 +0,0 @@-{-# 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/Checksum.hs view
@@ -0,0 +1,136 @@+{-# LANGUAGE RecordWildCards #-}+{-# 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.Checksum(+    -- * Checksums+    computeChecksum,+    Checksum(..),+    PartialChecksum(),+    emptyPartialChecksum,+    finalizeChecksum,+    stepChecksum,++    Pair8(..),+  ) where++import           Data.Bits (Bits(shiftL,shiftR,complement,clearBit,(.&.)))+import           Data.List (foldl')+import           Data.Word (Word8,Word16,Word32)+import qualified Data.ByteString        as S+import qualified Data.ByteString.Lazy   as L+import qualified Data.ByteString.Short  as Sh+import qualified Data.ByteString.Unsafe as S+++data PartialChecksum = PartialChecksum { pcAccum :: {-# UNPACK #-} !Word32+                                       , pcCarry ::                !(Maybe Word8)+                                       } deriving (Eq,Show)++emptyPartialChecksum :: PartialChecksum+emptyPartialChecksum  = PartialChecksum+  { pcAccum = 0+  , pcCarry = Nothing+  }++finalizeChecksum :: PartialChecksum -> Word16+finalizeChecksum pc = complement (fromIntegral (fold32 (fold32 result)))+  where+  fold32 :: Word32 -> Word32+  fold32 x = (x .&. 0xFFFF) + (x `shiftR` 16)++  result = case pcCarry pc of+    Nothing   -> pcAccum pc+    Just prev -> stepChecksum (pcAccum pc) prev 0+{-# INLINE finalizeChecksum #-}+++computeChecksum :: Checksum a => a -> Word16+computeChecksum a = finalizeChecksum (extendChecksum a emptyPartialChecksum)+{-# INLINE computeChecksum #-}++-- | Incremental checksum computation interface.+class Checksum a where+  extendChecksum :: a -> PartialChecksum -> PartialChecksum+++data Pair8 = Pair8 !Word8 !Word8++instance Checksum Pair8 where+  extendChecksum (Pair8 hi lo) = \ PartialChecksum { .. } ->+    case pcCarry of+      Nothing -> PartialChecksum { pcAccum = stepChecksum pcAccum hi lo+                                 , pcCarry = Nothing }+      Just c  -> PartialChecksum { pcAccum = stepChecksum pcAccum c hi+                                 , pcCarry = Just lo }+  {-# INLINE extendChecksum #-}++instance Checksum Word16 where+  extendChecksum w = \pc -> extendChecksum (Pair8 hi lo) pc+    where+    lo = fromIntegral  w+    hi = fromIntegral (w `shiftR` 8)+  {-# INLINE extendChecksum #-}++instance Checksum Word32 where+  extendChecksum w = \pc ->+    extendChecksum (fromIntegral  w              :: Word16) $+    extendChecksum (fromIntegral (w `shiftR` 16) :: Word16) pc+  {-# INLINE extendChecksum #-}+  +instance Checksum a => Checksum [a] where+  extendChecksum as = \pc -> foldl' (flip extendChecksum) pc as+  {-# INLINE extendChecksum #-}++instance Checksum L.ByteString where+  extendChecksum lbs = \pc -> extendChecksum (L.toChunks lbs) pc+  {-# INLINE extendChecksum #-}++-- XXX this could be faster if we could mirror the structure of the instance for+-- S.ByteString+instance Checksum Sh.ShortByteString where+  extendChecksum shb = \ pc -> extendChecksum (Sh.fromShort shb) pc+++instance Checksum S.ByteString where+  extendChecksum b pc+    | S.null b  = pc+    | otherwise = case pcCarry pc of+        Nothing   -> result+        Just prev -> extendChecksum (S.tail b) PartialChecksum+          { pcCarry = Nothing+          , pcAccum = stepChecksum (pcAccum pc) prev (S.unsafeIndex b 0)+          }+    where++    n' = S.length b+    n  = clearBit n' 0 -- aligned to two++    result = PartialChecksum+      { pcAccum = loop (pcAccum pc) 0+      , pcCarry = carry+      }++    carry+      | odd n'    = Just $! S.unsafeIndex b n+      | otherwise = Nothing++    loop !acc off+      | off < n   = loop (stepChecksum acc hi lo) (off + 2)+      | otherwise = acc+      where hi    = S.unsafeIndex b off+            lo    = S.unsafeIndex b (off+1)++stepChecksum :: Word32 -> Word8 -> Word8 -> Word32+stepChecksum acc hi lo = acc + fromIntegral hi `shiftL` 8 + fromIntegral lo+{-# INLINE stepChecksum #-}
+ src/Hans/Config.hs view
@@ -0,0 +1,111 @@+module Hans.Config (+    Config(..),+    defaultConfig,+    HasConfig(..),+  ) where++import Hans.Lens++import Data.Time.Clock (NominalDiffTime)+import Data.Word (Word8)+++-- | General network stack configuration.+data Config = Config { cfgInputQueueSize :: !Int++                     , cfgArpTableSize :: !Int+                       -- ^ Best to pick a prime number.++                     , cfgArpTableLifetime :: !NominalDiffTime++                     , cfgArpRetry :: !Int+                       -- ^ Number of times to retry an arp request before+                       -- failing++                     , cfgArpRetryDelay :: !Int+                       -- ^ The amount of time to wait between arp request+                       -- retransmission.++                     , cfgIP4FragTimeout :: !NominalDiffTime+                       -- ^ Number of seconds to wait before expiring a+                       -- partially reassembled IP4 packet++                     , cfgIP4InitialTTL :: !Word8++                     , cfgIP4MaxFragTableEntries :: !Int+                       -- ^ Maximum packets being reassembled at any time.++                     , cfgUdpSocketTableSize :: !Int+                       -- ^ Number of buckets in the udp socket table++                     , cfgDnsResolveTimeout :: !Int+                       -- ^ In microseconds++                     , cfgTcpListenTableSize :: !Int+                       -- ^ Small-ish prime number++                     , cfgTcpActiveTableSize :: !Int+                       -- ^ Best to pick a prime number++                     , cfgTcpTimeoutTimeWait :: !NominalDiffTime+                       -- ^ Time to remain in TimeWait, in seconds.++                     , cfgTcpInitialMSS :: !Int+                       -- ^ Initial MSS for tcp connections++                     , cfgTcpMaxSynBacklog :: !Int+                       -- ^ Maximum number of connections waiting for an+                       -- acknowledgement.++                     , cfgTcpInitialWindow :: !Int+                       -- ^ Initial local window for tcp connections.++                     , cfgTcpMSL :: !Int+                       -- ^ Maximum segment lifetime++                     , cfgTcpTSClockFrequency :: !NominalDiffTime+                       -- ^ Frequency (in Hz) of timestamp clock updates.+                       -- Should be between 1Hz and 1000Hz, according to+                       -- RFC-1323.++                     , cfgTcpTimeWaitSocketLimit :: !Int+                       -- ^ The max number of threads allowed in the time+                       -- wait heap.++                     , cfgNatMaxEntries :: !Int+                       -- ^ The maximum number of entries allowed in the TCP or+                       -- UDP NAT tables+                     }++defaultConfig :: Config+defaultConfig  = Config { cfgInputQueueSize     = 128+                        , cfgArpTableSize       = 67+                        , cfgArpTableLifetime   = 60 -- 60 seconds+                        , cfgArpRetry           = 10+                        , cfgArpRetryDelay      = 2000 -- 2 seconds+                        , cfgIP4FragTimeout     = 30+                        , cfgIP4InitialTTL      = 128+                        , cfgIP4MaxFragTableEntries= 32+                        , cfgUdpSocketTableSize = 31+                        , cfgDnsResolveTimeout  = 5000000+                        , cfgTcpListenTableSize = 5+                        , cfgTcpActiveTableSize = 67+                        , cfgTcpTimeoutTimeWait = 60.0 -- one minute+                        , cfgTcpInitialMSS      = 1380+                          --  ^ 1500 - (max (20 for IPv4) (60 for IPv6)+                          --           + 60 for maximum TCP header)+                        , cfgTcpMaxSynBacklog   = 128+                        , cfgTcpInitialWindow   = 14600+                        , cfgTcpMSL             = 60 -- one minute+                        , cfgTcpTSClockFrequency = 1000 -- 1000hz, update every 1ms+                        , cfgTcpTimeWaitSocketLimit = 70+                        , cfgNatMaxEntries      = 200+                        }++class HasConfig cfg where+  config :: Getting r cfg Config++instance HasConfig Config where+  config = id+  {-# INLINE config #-}+
+ src/Hans/Device.hs view
@@ -0,0 +1,28 @@+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE CPP #-}++module Hans.Device (+    module Exports,+    closeDevice,+    startDevice+  ) where++#if   defined(HANS_TARGET_UNIX)+import           Hans.Device.Tap as Exports (listDevices,openDevice)+#elif defined(HANS_TARGET_XEN)+import           Hans.Device.Xen as Exports (listDevices,openDevice)+#endif++import           Hans.Device.Types as Exports+++-- | Stop packets flowing, and cleanup any resources associated with this+-- device.+closeDevice :: Device -> IO ()+closeDevice Device { .. } =+  do devStop+     devCleanup++-- | Start processing packets through this device.+startDevice :: Device -> IO ()+startDevice Device { .. } = devStart
− src/Hans/Device/Ivc.hs
@@ -1,19 +0,0 @@-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE FlexibleContexts #-}-module Hans.Device.Ivc (-    ivcSend-  , ivcReceiveLoop-  ) where--import Hans.Layer.Ethernet (EthernetHandle,queueEthernet)-import Communication.IVC as IVC-import Control.Monad (forever,when)-import qualified Data.ByteString as S--ivcSend :: IVC.WriteableChan c S.ByteString => c -> S.ByteString -> IO ()-ivcSend chan = IVC.put chan--ivcReceiveLoop :: IVC.ReadableChan c S.ByteString => c -> EthernetHandle -> IO ()-ivcReceiveLoop chan eth = forever $-  do bs <- IVC.get chan-     when (S.length bs > 14) (queueEthernet eth bs)
+ src/Hans/Device/Loopback.hs view
@@ -0,0 +1,35 @@+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE OverloadedStrings #-}++module Hans.Device.Loopback where++import Hans.Device.Types+import Hans.Ethernet.Types (Mac(..))+import Hans.Types+++import Control.Concurrent.BoundedChan (newBoundedChan)++++-- | A device that just posts outgoing packets back to the input queue.+newLoopbackDevice :: NetworkStack -> IO Device+newLoopbackDevice _ns =+  do let noChecksum = ChecksumOffload { coIP4   = True+                                      , coUdp   = True+                                      , coTcp   = True+                                      , coIcmp4 = True }++     let devConfig = defaultDeviceConfig { dcSendQueueLen = 1+                                         , dcTxOffload    = noChecksum+                                         , dcRxOffload    = noChecksum }+     let devName   = "lo"+     let devMac    = Mac 0 0 0 0 0 0++     devStats     <- newDeviceStats+     devSendQueue <- newBoundedChan (dcSendQueueLen devConfig)++     return $! Device { devStart   = return ()+                      , devStop    = return ()+                      , devCleanup = return ()+                      , .. }
− src/Hans/Device/Tap.hs
@@ -1,69 +0,0 @@-{-# LANGUAGE ForeignFunctionInterface #-}--module Hans.Device.Tap where--import Hans.Layer.Ethernet-import Hans.Utils (strict,DeviceName)--import Control.Concurrent (threadWaitRead)-import Control.Monad (forever)-import Data.Word (Word8)-import Foreign.C.String (CString,withCString)-import Foreign.C.Types (CLong(..),CSize(..),CInt(..))-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-import qualified Data.ByteString.Lazy     as L----- | 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.------ TODO: make more use of the lazy bytestring-tapSend :: Fd -> L.ByteString -> IO ()-tapSend fd packet = do-  let (fptr, 0, len) = S.toForeignPtr (strict 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 S.ByteString-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--c_read' :: Fd -> Ptr Word8 -> CSize -> IO CLong-c_read' fd buf size = do-  res <- c_read fd buf size---  Errno eno <- getErrno-  return res--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 safe "read"-  c_read :: Fd -> Ptr Word8 -> CSize -> IO CLong-
+ src/Hans/Device/Tap.hsc view
@@ -0,0 +1,171 @@+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ForeignFunctionInterface #-}++module Hans.Device.Tap (listDevices,openDevice) where++#include <sys/types.h>+#include <sys/uio.h>+#include <unistd.h>++import           Hans.Ethernet.Types (Mac(..))+import           Hans.Device.Types+import           Hans.Threads (forkNamed)+import           Hans.Types (NetworkStack(..),InputPacket(..))++import           Control.Concurrent+                     (threadWaitRead,killThread,newMVar,modifyMVar_)+import           Control.Concurrent.BoundedChan+                     (BoundedChan,newBoundedChan,readChan,tryWriteChan)+import qualified Control.Exception as X+import           Control.Monad (forever,when,foldM_)+import qualified Data.ByteString as S+import qualified Data.ByteString.Internal as S+import qualified Data.ByteString.Lazy as L+import qualified Data.ByteString.Unsafe as S+import           Data.Word (Word8)+import           Foreign.C.String (CString)+import           Foreign.C.Types (CSize(..),CLong(..),CInt(..),CChar(..))+import           Foreign.Marshal.Alloc (allocaBytes)+import           Foreign.Marshal.Array (allocaArray,peekArray)+import           Foreign.Ptr (Ptr,plusPtr)+import           Foreign.Storable (pokeByteOff)+import           System.Posix.Types (Fd(..))+++-- | Not sure how this should work yet... Should it only ever show tap device+-- names? Maybe this should return a singleton list of an ephemeral device?+listDevices :: IO [DeviceName]+listDevices  = return []++openDevice :: NetworkStack -> DeviceName -> DeviceConfig -> IO Device+openDevice ns devName devConfig =+  do (fd,devMac) <- initTapDevice devName++     -- The `starting` lock makes sure that only one set of threads will be+     -- started at once, while the `running` var holds the ids of the running+     -- threads.+     threadIds <- newMVar Nothing++     devStats <- newDeviceStats++     devSendQueue <- newBoundedChan (dcSendQueueLen devConfig)++     let dev = Device { .. }++         devStart = modifyMVar_ threadIds $ \ mbTids ->+           case mbTids of++             Nothing ->+               do recvThread <- forkNamed "tapRecvLoop"+                      (tapRecvLoop ns dev fd)++                  sendThread <- forkNamed "tapSendLoop"+                      (tapSendLoop devStats fd devSendQueue)++                  return (Just (recvThread,sendThread))++             Just {} ->+                  return mbTids++         devStop = modifyMVar_ threadIds $ \ mbTids ->+           case mbTids of+             Just (recvThread,sendThread) ->+               do killThread recvThread+                  killThread sendThread+                  return Nothing++             Nothing ->+                  return Nothing++         devCleanup =+           do tapClose fd++     return dev++initTapDevice :: DeviceName -> IO (Fd,Mac)+initTapDevice devName =+  do (fd,[a,b,c,d,e,f]) <-+         allocaArray 6 $ \ macPtr ->+             do fd <- S.unsafeUseAsCString devName $ \ devNamePtr ->+                          c_init_tap_device devNamePtr macPtr++                mac <- peekArray 6 macPtr+                return (fd,mac)++     when (fd < 0) (X.throwIO (FailedToOpen devName))++     return (fd, Mac a b c d e f)+++-- | Send a packet out over the tap device.+tapSendLoop :: DeviceStats -> Fd -> BoundedChan L.ByteString -> IO ()+tapSendLoop stats fd queue = forever $+  do bs <- readChan queue++     let chunks = L.toChunks bs+         len    = length chunks++     allocaBytes (fromIntegral ((#size struct iovec) * len)) $ \ iov ->+       do foldM_ writeChunk iov chunks+          bytesWritten <- c_writev fd iov (fromIntegral len)+          if fromIntegral bytesWritten == L.length bs+             then do updateBytes   statTX stats (fromIntegral bytesWritten)+                     updatePackets statTX stats++             else updateError statTX stats+  where++  -- write the chunk address and length into the iovec entry+  writeChunk iov chunk =+    do S.unsafeUseAsCStringLen chunk $ \ (ptr,clen) ->+              writeIOVec iov ptr (fromIntegral clen)++       return (iov `plusPtr` (#size struct iovec))+++-- | Receive a packet from the tap device.+tapRecvLoop :: NetworkStack -> Device -> Fd -> IO ()+tapRecvLoop ns dev @ Device { .. } fd = forever $+  do threadWaitRead fd++     bytes <- S.createUptoN 1514 $ \ ptr ->+       do actual <- c_read fd ptr 1514+          return (fromIntegral actual)++     -- tap devices don't appear to pad received packets out to the minimum size+     -- of 60 bytes, so just don't do that check here.++     success <- tryWriteChan (nsInput ns) $! FromDevice dev bytes+     if success+        then do updateBytes   statRX devStats (S.length bytes)+                updatePackets statRX devStats++        else updateError statRX devStats+++tapClose :: Fd -> IO ()+tapClose fd =+  do c_close fd+++-- Foreign Interface -----------------------------------------------------------++foreign import ccall unsafe "init_tap_device"+  c_init_tap_device :: CString -> Ptr Word8 -> IO Fd++type IOVec = ()++writeIOVec :: Ptr IOVec -> Ptr CChar -> CSize -> IO ()+writeIOVec iov ptr len =+  do (#poke struct iovec, iov_base) iov ptr+     (#poke struct iovec, iov_len)  iov len+++foreign import ccall unsafe "writev"+  c_writev :: Fd -> Ptr IOVec -> CSize -> IO CLong++foreign import ccall safe "read"+  c_read :: Fd -> Ptr Word8 -> CSize -> IO CLong++foreign import ccall safe "close"+  c_close :: Fd -> IO ()
+ src/Hans/Device/Types.hs view
@@ -0,0 +1,193 @@+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveFoldable #-}+{-# LANGUAGE DeriveTraversable #-}+{-# LANGUAGE RecordWildCards #-}++module Hans.Device.Types where++import           Hans.Ethernet.Types (Mac)+import           Hans.Lens++import           Control.Concurrent.BoundedChan (BoundedChan)+import qualified Control.Exception as X+import qualified Data.ByteString as S+import qualified Data.ByteString.Lazy as L+import           Data.IORef (IORef,newIORef,atomicModifyIORef',readIORef)+import           Data.Typeable (Typeable)+++type DeviceName = S.ByteString++data ChecksumOffload = ChecksumOffload { coIP4   :: !Bool+                                       , coUdp   :: !Bool+                                       , coTcp   :: !Bool+                                       , coIcmp4 :: !Bool+                                       } deriving (Show)++defaultChecksumOffload :: ChecksumOffload+defaultChecksumOffload  = ChecksumOffload { coIP4   = False+                                          , coUdp   = False+                                          , coTcp   = False+                                          , coIcmp4 = False }++-- | Static configuration data for creating a device.+data DeviceConfig = DeviceConfig { dcSendQueueLen :: {-# UNPACK #-} !Int+                                   -- ^ How large the send queue should be.++                                 , dcTxOffload :: !ChecksumOffload+                                 , dcRxOffload :: !ChecksumOffload++                                 , dcMtu :: !Int+                                 } deriving (Show)++class HasDeviceConfig cfg where+  deviceConfig :: Getting r cfg DeviceConfig++instance HasDeviceConfig DeviceConfig where+  deviceConfig = id+  {-# INLINE deviceConfig #-}++instance HasDeviceConfig Device where+  deviceConfig = to devConfig+  {-# INLINE deviceConfig #-}++-- | The TX checksum offload config.+txOffload :: HasDeviceConfig cfg => Getting r cfg ChecksumOffload+txOffload  = deviceConfig . to dcTxOffload++-- | The RX checksum offload config.+rxOffload :: HasDeviceConfig cfg => Getting r cfg ChecksumOffload+rxOffload  = deviceConfig . to dcRxOffload++defaultDeviceConfig :: DeviceConfig+defaultDeviceConfig  = DeviceConfig { dcSendQueueLen = 128+                                    , dcTxOffload    = defaultChecksumOffload+                                    , dcRxOffload    = defaultChecksumOffload+                                    , dcMtu          = 1500+                                    }++data Device = Device { devName :: !DeviceName+                       -- ^ The name of this device++                     , devMac :: !Mac+                       -- ^ The mac address associated with this device++                     , devConfig :: !DeviceConfig+                       -- ^ Static configuration information for this device++                     , devSendQueue :: !(BoundedChan L.ByteString)+                       -- ^ Outgoing message queue for this device++                     , devStart :: !(IO ())+                       -- ^ Start packet flow++                     , devStop :: !(IO ())+                       -- ^ Stop packet flow++                     , devCleanup :: !(IO ())+                       -- ^ Cleanup resources associated with a 'Device'++                     , devStats :: !DeviceStats+                       -- ^ Statistics about this device+                     }++-- Devices are compared by mac address+instance Eq Device where+  a == b = devMac a == devMac b+  a /= b = devMac a == devMac b++  {-# INLINE (==) #-}+  {-# INLINE (/=) #-}++instance Ord Device where+  compare a b = compare (devMac a) (devMac b)+  {-# INLINE compare #-}+++data DeviceException = FailedToOpen !DeviceName+                       deriving (Typeable,Show)++instance X.Exception DeviceException+++-- Statistics ------------------------------------------------------------------++type Stat = IORef Int++incrementStat :: Stat -> IO ()+incrementStat ref = atomicModifyIORef' ref (\ i -> (i + 1, ()))++addStat :: Stat -> Int -> IO ()+addStat ref n = atomicModifyIORef' ref (\ i -> (i + n, ()))++data StatGroup = StatGroup { _statBytes   :: !Stat+                           , _statPackets :: !Stat+                           , _statErrors  :: !Stat+                           , _statDropped :: !Stat+                           }++statBytes, statPackets, statErrors, statDropped :: Getting r StatGroup Stat+statBytes   = to _statBytes+statPackets = to _statPackets+statErrors  = to _statErrors+statDropped = to _statDropped++newStatGroup :: IO StatGroup+newStatGroup  =+  do _statBytes   <- newIORef 0+     _statPackets <- newIORef 0+     _statErrors  <- newIORef 0+     _statDropped <- newIORef 0+     return $! StatGroup { .. }++dumpStatGroup :: String -> StatGroup -> IO ()+dumpStatGroup pfx = \ StatGroup { .. } ->+  do putStrLn header+     mapM_ showStat [_statBytes,_statPackets,_statErrors,_statDropped]+     putStrLn ""+  where+  header = unwords (map pad [ pfx ++ " bytes", "packets", "errors", "dropped" ])+  pad xs = xs ++ replicate (19 - length xs) ' '++  showStat ref =+    do val <- readIORef ref+       putStr (pad (show val))+       putStr " "+{-# INLINE dumpStatGroup #-}++data DeviceStats = DeviceStats { _statTX :: !StatGroup+                               , _statRX :: !StatGroup+                               }++statTX, statRX :: Getting r DeviceStats StatGroup+statTX = to _statTX+statRX = to _statRX++newDeviceStats :: IO DeviceStats+newDeviceStats  =+  do _statTX <- newStatGroup+     _statRX <- newStatGroup+     return $! DeviceStats { .. }++dumpStats :: DeviceStats -> IO ()+dumpStats DeviceStats { .. } =+  do dumpStatGroup "RX:" _statRX+     dumpStatGroup "TX:" _statTX+++-- | Add one to the count of dropped packets for this device.+updateDropped :: Getting Stat DeviceStats StatGroup -> DeviceStats -> IO ()+updateDropped group stats = incrementStat (view (group . statDropped) stats)++-- | Add one to the error count for this device.+updateError :: Getting Stat DeviceStats StatGroup -> DeviceStats -> IO ()+updateError group stats = incrementStat (view (group . statErrors) stats)++-- | Update information about bytes received.+updateBytes :: Getting Stat DeviceStats StatGroup -> DeviceStats -> Int -> IO ()+updateBytes group stats n = addStat (view (group . statBytes) stats) n++-- | Update information about bytes received.+updatePackets :: Getting Stat DeviceStats StatGroup -> DeviceStats -> IO ()+updatePackets group stats = incrementStat (view (group . statPackets) stats)
src/Hans/Device/Xen.hs view
@@ -1,18 +1,95 @@+{-# LANGUAGE RecordWildCards #-}+ module Hans.Device.Xen where -import Hans.Layer.Ethernet+import Hans.Device.Types+import Hans.Ethernet.Types (readMac)+import Hans.Threads (forkNamed)+import Hans.Types -import XenDevice.NIC as Xen-import qualified Data.ByteString      as S+import           Control.Concurrent (newMVar,modifyMVar_,killThread)+import           Control.Concurrent.BoundedChan+                     (BoundedChan,newBoundedChan,tryWriteChan,readChan)+import           Control.Exception(try)+import           Control.Monad (forever)+import qualified Data.ByteString as S+import qualified Data.ByteString.Char8 as S8 import qualified Data.ByteString.Lazy as L+import           Hypervisor.ErrorCodes(ErrorCode)+import           Hypervisor.XenStore (XenStore)+import           XenDevice.NIC (NIC,listNICs,openNIC,setReceiveHandler,sendPacket) --- Xen NIC ---------------------------------------------------------------------+listDevices :: XenStore -> IO [DeviceName]+listDevices xs =+  do nics <- listNICs xs+     return (map S8.pack nics) -xenSend :: NIC -> L.ByteString -> IO ()-xenSend = Xen.sendPacket+openDevice :: XenStore -> NetworkStack -> DeviceName -> DeviceConfig -> IO Device+openDevice xs ns devName devConfig =+  do let macStr = S8.unpack devName+     nic <- openNIC xs macStr -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))+     devStats     <- newDeviceStats+     devSendQueue <- newBoundedChan (dcSendQueueLen devConfig)++     thread <- newMVar Nothing++     devMac <- case readMac macStr of+                 [(x,_)] -> return x+                 _       -> fail ("Unable to parse mac address: " ++ macStr)++     let dev = Device { .. }++         devStart = modifyMVar_ thread $ \ mbTid ->+           case mbTid of++             Nothing ->+               do sendThread <- forkNamed "xenSendLoop"+                      (xenSendLoop devStats nic devSendQueue)++                  setReceiveHandler nic (xenRecv ns dev)++                  return (Just sendThread)++             Just {} ->+                  return mbTid++         devStop = modifyMVar_ thread $ \ mbTid ->+           case mbTid of++             Just tid ->+               do killThread tid+                  return Nothing++             Nothing ->+                  return Nothing++         -- NOTE: there's no way to cleanup a NIC+         devCleanup =+             return ()++     return dev+++-- NOTE: No way to update stats here, as we can't tell if sendPacket failed.+xenSendLoop :: DeviceStats -> NIC -> BoundedChan L.ByteString -> IO ()+xenSendLoop stats nic chan = forever $+  do bs  <- readChan chan+     res <- try $ sendPacket nic bs++     case res :: Either ErrorCode () of+       Right () ->+         do updateBytes   statTX stats (fromIntegral (L.length bs))+            updatePackets statTX stats+       Left _ ->+            updateError   statTX stats++xenRecv :: NetworkStack -> Device -> L.ByteString -> IO ()+xenRecv ns dev @ Device { .. } = \ bytes ->+  do let bytes' = L.toStrict bytes+     success <- tryWriteChan (nsInput ns) $! FromDevice dev bytes'+     if success+        then do updateBytes   statRX devStats (S.length bytes')+                updatePackets statRX devStats++        else updateError statRX devStats
− src/Hans/DhcpClient.hs
@@ -1,267 +0,0 @@-module Hans.DhcpClient (-    dhcpDiscover-  ) where--import Hans.Address-import Hans.Address.IP4 (IP4(..),broadcastIP4,IP4Mask(..))-import Hans.Address.Mac (Mac(..),broadcastMac)-import Hans.Layer.Ethernet (sendEthernet,addEthernetHandler)-import Hans.Layer.IP4 (connectEthernet)-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.NetworkStack-import Hans.Timers (delay_)--import Control.Monad (guard,unless)-import Control.Concurrent.STM-import Data.Maybe (fromMaybe,mapMaybe)-import System.Random (randomIO)-import qualified Data.ByteString as S-import qualified Data.ByteString.Lazy as L----- 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--ethernetIp4 :: EtherType-ethernetIp4  = EtherType 0x0800--defaultRoute :: IP4Mask-defaultRoute  = IP4 0 0 0 0 `withMask` 0----- DHCP ---------------------------------------------------------------------------- | Discover a dhcp server, and request an address.-dhcpDiscover :: ( HasEthernet stack, HasArp stack, HasIP4 stack, HasUdp stack-                , HasDns stack )-             => stack -> Mac -> IO (Maybe IP4)-dhcpDiscover ns mac = do-  addEthernetHandler (ethernetHandle ns) ethernetIp4 (dhcpIP4Handler ns)--  offerTMV <- newEmptyTMVarIO-  addUdpHandler ns bootpc (handleOffer ns offerTMV)--  w32 <- randomIO-  let xid = Xid (fromIntegral (w32 :: Int))-      disc = discoverToMessage (mkDiscover xid mac)--  --  waitResult sends our DHCP discover and waits for an offer.-  mbOffer <- waitResult retries disc offerTMV-  case mbOffer of--    -- We exceeded our retries, give up.-    Nothing    -> return Nothing--    --  We got an offer-    --  - Install an DHCP Ack handler.-    --  - Send a DHCP Request via waitResult, and wait for an IP to appear in resp.-    Just offer -> do-      resp <- newEmptyTMVarIO-      addUdpHandler ns bootpc (handleAck ns offer (Just (atomically . putTMVar resp)))-      let req = requestToMessage (offerToRequest offer)-      waitResult retries req resp--  where-    -- RFC 2131 says to do exponential backoff starting at 4s and going to 64s.-    -- It also says to add random noise between -1s and +1s, but we're skipping that for now.-    initialTimeout :: Int-    initialTimeout = 4000000 -- 4 seconds in µs--    retries :: Int-    retries = 6--    -- Sends a message and waits for a response (indicated by a value appearing the TMVar)-    -- until timeout. Retries a given number of times doubling the backoff each time.-    waitResult :: Int -> Dhcp4Message -> TMVar a -> IO (Maybe a)-    waitResult n msg result = go n initialTimeout-      where-        go 0 _to = return Nothing-        go i  to =-          do sendMessage ns msg currentNetwork broadcastIP4 broadcastMac-             timeout <- registerDelay to-             -- If there is a result, return Just it.-             -- If not, and we aren't timed out, retry.-             -- If we are timed out, return Nothing.-             mb <- atomically $ orElse (fmap Just (takeTMVar result))-                                       (do isTimedOut <- readTVar timeout-                                           unless isTimedOut retry-                                           return Nothing)-             -- Exponential backoff on timeout, return result on success.-             case mb of-               Just _  -> return mb-               Nothing -> go (i-1) (to * 2)---- | Restore the connection between the Ethernet and IP4 layers.-restoreIp4 :: (HasEthernet stack, HasIP4 stack) => stack -> IO ()-restoreIp4 ns = connectEthernet (ip4Handle ns) (ethernetHandle ns)---- | Handle IP4 messages from the Ethernet layer, passing all relevant DHCP--- messages to the UDP layer.-dhcpIP4Handler :: (HasUdp stack)-               => stack -> S.ByteString -> IO ()-dhcpIP4Handler ns bytes =-  case parseIP4Packet bytes of-    Left err            -> putStrLn err >> return ()-    Right (hdr,ihl,len)-      | ip4Protocol hdr == udpProtocol -> queue-      | otherwise                      -> return ()-      where-      queue = queueUdp ns hdr-            $ S.take (len - ihl)-            $ S.drop ihl bytes---- | Handle a DHCP Offer message.------  * Remove the current UDP handler---  * Write offer to TMV for retrieval.-handleOffer :: ( HasEthernet stack, HasArp stack, HasIP4 stack, HasUdp stack-               , HasDns stack )-            => stack -> TMVar Offer -> IP4 -> UdpPort-            -> S.ByteString -> IO ()-handleOffer ns tmv _src _srcPort bytes =-  case getDhcp4Message bytes of-    Right msg -> case parseDhcpMessage msg of--      Just (Right (OfferMessage offer)) -> do-        removeUdpHandler ns bootpc-        atomically $ putTMVar tmv offer--      msg1 -> do-        putStrLn (show msg)-        putStrLn (show msg1)--    Left err -> putStrLn err---- | Handle a DHCP Ack message.------   The optional handler (mbh) is present only the first time handleAck is---   installed, so we can update the main thread on our IP assignment. After---   that (when dhcpRenew installs handleAck) it is Nothing, because we just---   have to update the network stack.------  * 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 :: ( HasEthernet stack, HasArp stack, HasIP4 stack, HasUdp stack-             , HasDns stack )-          => stack -> Offer -> Maybe (IP4 -> IO ()) -> IP4 -> UdpPort-          -> S.ByteString -> IO ()-handleAck ns offer mbh _src _srcPort bytes =-  case getDhcp4Message bytes of-    Right msg -> case parseDhcpMessage msg of--      Just (Right (AckMessage ack)) -> do-        removeUdpHandler ns bootpc-        restoreIp4 ns-        ackNsOptions ack ns-        let ms = fromIntegral (ackLeaseTime ack) * 500-        delay_ ms (dhcpRenew ns offer)--        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 request message, generated from the offer given.-dhcpRenew :: ( HasEthernet stack, HasArp stack, HasIP4 stack, HasUdp stack-             , HasDns stack )-          => stack -> Offer -> IO ()-dhcpRenew ns offer = do-  addEthernetHandler (ethernetHandle ns) ethernetIp4 (dhcpIP4Handler ns)--  let req = requestToMessage (offerToRequest offer)-  addUdpHandler 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 :: (HasIP4 stack, HasArp stack, HasDns stack)-             => Ack -> stack -> IO ()-ackNsOptions ack ns = do-  let mac     = ackClientHardwareAddr ack-      addr    = ackYourAddr ack-      opts    = ackOptions ack-      mask    = fromMaybe 24 (lookupSubnet opts)-      gateway = fromMaybe (ackRelayAddr ack) (lookupGateway opts)-  addIP4Addr ns (addr `withMask` mask) mac 1500-  routeVia ns defaultRoute gateway--  let nameServers = concat (mapMaybe getNameServers (ackOptions ack))-  mapM_ (addNameServer ns) nameServers--getNameServers :: Dhcp4Option -> Maybe [IP4]-getNameServers (OptNameServers addrs) = Just addrs-getNameServers _                      = Nothing----- Packet Helpers ----------------------------------------------------------------sendMessage :: HasEthernet stack-            => stack -> Dhcp4Message -> IP4 -> IP4 -> Mac -> IO ()-sendMessage ns resp src dst hwdst = do-  ipBytes <- mkIpBytes src dst bootpc bootps (putDhcp4Message resp)-  let mac   = dhcp4ClientHardwareAddr resp-  let frame = EthernetFrame-        { etherDest         = hwdst-        , etherSource       = mac-        , etherType         = ethernetIp4-        }-  sendEthernet (ethernetHandle ns) frame ipBytes--mkIpBytes :: IP4 -> IP4 -> UdpPort -> UdpPort -> L.ByteString -> IO L.ByteString-mkIpBytes srcAddr dstAddr srcPort dstPort payload = do-  udpBytes <- do-    let udpHdr = UdpHeader srcPort dstPort 0-        mk     = mkIP4PseudoHeader srcAddr dstAddr udpProtocol-    renderUdpPacket udpHdr payload mk--  ipBytes  <- do-    let ipHdr = emptyIP4Header-          { ip4SourceAddr = srcAddr-          , ip4DestAddr   = dstAddr-          , ip4Protocol   = udpProtocol-          }-    renderIP4Packet ipHdr udpBytes--  return ipBytes
+ src/Hans/Dns.hs view
@@ -0,0 +1,159 @@+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE OverloadedStrings #-}++module Hans.Dns where++import Hans.Config+import Hans.Dns.Packet+import Hans.IP4.Packet+import Hans.Lens+import Hans.Serialize (runPutPacket)+import Hans.Socket+import Hans.Types++import           Control.Exception+import           Control.Monad (when)+import qualified Data.Foldable as F+import qualified Data.ByteString.Char8 as S8+import qualified Data.ByteString.Lazy as L+import           Data.Serialize (runGetLazy)+import           Data.Typeable (Typeable)+import           Data.Word (Word16)+import           System.Timeout (timeout)+++type HostName = S8.ByteString++data HostEntry = HostEntry { hostName      :: HostName+                           , hostAliases   :: [HostName]+                           , hostAddresses :: [IP4]+                           } deriving (Show)++parseHostEntry :: Source -> [RR] -> HostEntry+parseHostEntry (FromHost host)  = parseAddr host+parseHostEntry (FromAddr4 addr) = parsePtr addr++-- | Parse the A and CNAME parts out of a response.+parseAddr :: HostName -> [RR] -> HostEntry+parseAddr host = F.foldl' processAnswer emptyHostEntry+  where++  emptyHostEntry = HostEntry { hostName      = host+                             , hostAliases   = []+                             , hostAddresses = [] }++  processAnswer he RR { .. } = case rrRData of+    RDA ip     -> he { hostAddresses = ip : hostAddresses he }+    RDCNAME ns -> he { hostName      = S8.intercalate "." ns+                     , hostAliases   = hostName he : hostAliases he }+    _          -> he++parsePtr :: IP4 -> [RR] -> HostEntry+parsePtr addr = F.foldl' processAnswer emptyHostEntry+  where+  emptyHostEntry = HostEntry { hostName      = ""+                             , hostAliases   = []+                             , hostAddresses = [addr] }++  processAnswer he RR { .. } = case rrRData of+    RDPTR name -> he { hostName = S8.intercalate "." name }+    _          -> he+++data DnsException = NoNameServers+                    deriving (Show,Typeable)++instance Exception DnsException+++getHostByName :: HasNetworkStack ns => ns -> HostName -> IO (Maybe HostEntry)+getHostByName ns host = sendRequest ns (FromHost host)++sendRequest :: HasNetworkStack ns => ns -> Source -> IO (Maybe HostEntry)+sendRequest ns src =+  do nameServers <- getNameServers4 ns++     when (null nameServers) (throwIO NoNameServers)++     bracket (newUdpSocket ns defaultSocketConfig Nothing WildcardIP4 Nothing)+             sClose $ \ sock ->+       do let req = runPutPacket 1450 1450 L.empty (putDNSPacket (mkPacket src 0))++          mbResp <- queryServers4 sock req nameServers+          case mbResp of+            Just DNSPacket { .. }+              | DNSHeader { .. } <- dnsHeader+              , dnsRC == RespNoError ->+                return (Just (parseHostEntry src dnsAnswers))+            _ ->+              return Nothing+++queryServers4 :: UdpSocket IP4 -> L.ByteString -> [IP4] -> IO (Maybe DNSPacket)+queryServers4 sock req = go+  where+  go (addr:addrs) =+    do sendto sock addr 53 req+       mbRes <- timeout (cfgDnsResolveTimeout (view config (view networkStack sock)))+                    (recvfrom sock)++       -- require that the server we sent a request to is the one that responded+       case mbRes of+         Just (_,srcIp,srcPort,bytes)+           | srcIp == addr, srcPort == 53 ->+             case runGetLazy getDNSPacket bytes of+               Right res -> return (Just res)+               Left _    -> return Nothing++         _ -> go addrs++  go [] = return Nothing+++data Source = FromHost HostName+            | FromAddr4 IP4+              deriving (Show)++sourceHost :: Source -> Name+sourceHost (FromHost host) = toLabels host+sourceHost (FromAddr4 ip4) = let (a,b,c,d)  = unpackIP4 ip4+                                 showByte x = S8.pack (show x)+                              in map showByte [d,c,b,a] ++ ["in-addr","arpa"]++toLabels :: HostName -> Name+toLabels str =+  case S8.break (== '.') str of+    (as,bs) | S8.null bs -> [as]+            | otherwise  -> as : toLabels (S8.tail bs)++sourceQType :: Source -> [QType]+sourceQType FromHost{}  = [QType A]+sourceQType FromAddr4{} = [QType PTR]++mkPacket :: Source -> Word16 -> DNSPacket+mkPacket src dnsId =+  DNSPacket { dnsHeader            = hdr+            , dnsQuestions         = [ mkQuery q | q <- sourceQType src ]+            , dnsAnswers           = []+            , dnsAuthorityRecords  = []+            , dnsAdditionalRecords = []+            }+  where+  n = sourceHost src++  hdr =+    DNSHeader { dnsQuery  = True+              , dnsOpCode = OpQuery+              , dnsAA     = False+              , dnsTC     = False+              , dnsRD     = True+              , dnsRA     = False+              , dnsRC     = RespNoError+              , .. }++  mkQuery qType =+    Query { qName = n+          , qClass = QClass IN+          , .. }
+ src/Hans/Dns/Packet.hs view
@@ -0,0 +1,594 @@++{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE MultiWayIf #-}++module Hans.Dns.Packet (+    DNSPacket(..)+  , DNSHeader(..)+  , OpCode(..)+  , RespCode(..)+  , Query(..)+  , QClass(..)+  , QType(..)+  , RR(..)+  , Type(..)+  , Class(..)+  , RData(..)+  , Name++  , getDNSPacket+  , putDNSPacket+  ) where++import Hans.IP4.Packet (IP4,getIP4,putIP4)++import           Control.Monad+import           Data.Bits+import qualified Data.ByteString as S+import qualified Data.Map.Strict as Map+import qualified Data.Serialize.Get as C+import qualified Data.Foldable as F+import           Data.Int+import           Data.Serialize ( Putter, runPut, putWord8, putWord16be, putWord32be+                                , putByteString )+import           Data.Word+import           MonadLib ( lift, StateT, runStateT, get, set )+import           Numeric ( showHex )++++-- DNS Packets -----------------------------------------------------------------++data DNSPacket = DNSPacket { dnsHeader            :: DNSHeader+                           , dnsQuestions         :: [Query]+                           , dnsAnswers           :: [RR]+                           , dnsAuthorityRecords  :: [RR]+                           , dnsAdditionalRecords :: [RR]+                           } deriving (Show)++data DNSHeader = DNSHeader { dnsId     :: !Word16+                           , dnsQuery  :: Bool+                           , dnsOpCode :: OpCode+                           , dnsAA     :: Bool+                           , dnsTC     :: Bool+                           , dnsRD     :: Bool+                           , dnsRA     :: Bool+                           , dnsRC     :: RespCode+                           } deriving (Show)++data OpCode = OpQuery+            | OpIQuery+            | OpStatus+            | OpReserved !Word16+              deriving (Show)++data RespCode = RespNoError+              | RespFormatError+              | RespServerFailure+              | RespNameError+              | RespNotImplemented+              | RespRefused+              | RespReserved !Word16+                deriving (Eq,Show)++type Name = [S.ByteString]++data Query = Query { qName  :: Name+                   , qType  :: QType+                   , qClass :: QClass+                   } deriving (Show)++data RR = RR { rrName  :: Name+             , rrClass :: Class+             , rrTTL   :: !Int32+             , rrRData :: RData+             } deriving (Show)++data QType = QType Type+           | AFXR+           | MAILB+           | MAILA+           | QTAny+             deriving (Show)++data Type = A+          | NS+          | MD+          | MF+          | CNAME+          | SOA+          | MB+          | MG+          | MR+          | NULL+          | PTR+          | HINFO+          | MINFO+          | MX+          | AAAA+            deriving (Show)++data QClass = QClass Class+            | QAnyClass+              deriving (Show)++data Class = IN | CS | CH | HS+             deriving (Show,Eq)++data RData = RDA IP4+           | RDNS Name+           | RDMD Name+           | RDMF Name+           | RDCNAME Name+           | RDSOA Name Name !Word32 !Int32 !Int32 !Int32 !Word32+           | RDMB Name+           | RDMG Name+           | RDMR Name+           | RDPTR Name+           | RDHINFO S.ByteString S.ByteString+           | RDMINFO Name Name+           | RDMX !Word16 Name+           | RDNULL S.ByteString+           | RDUnknown Type S.ByteString+             deriving (Show)+++-- Cereal With Label Compression -----------------------------------------------++data RW = RW { rwOffset :: !Int+             , rwLabels :: Map.Map Int Name+             } deriving (Show)++type Get = StateT RW C.Get++{-# INLINE unGet #-}+unGet :: Get a -> C.Get a+unGet m =+  do (a,_) <- runStateT RW { rwOffset = 0, rwLabels = Map.empty } m+     return a++getOffset :: Get Int+getOffset  = rwOffset `fmap` get++addOffset :: Int -> Get ()+addOffset off =+  do rw <- get+     set $! rw { rwOffset = rwOffset rw + off }++lookupPtr :: Int -> Get Name+lookupPtr off =+  do rw <- get+     when (off >= rwOffset rw) (fail "Invalid offset in pointer")+     case Map.lookup off (rwLabels rw) of+       Just ls -> return ls+       Nothing -> fail $ "Unknown label for offset: " ++ showHex off "\n"+                      ++ show (rwLabels rw)++data Label = Label Int S.ByteString+           | Ptr Int Name+             deriving (Show)++labelsToName :: [Label] -> Name+labelsToName  = F.foldMap toName+  where+  toName (Label _ l) = [l]+  toName (Ptr _ n)   = n++addLabels :: [Label] -> Get ()+addLabels labels =+  do rw <- get+     set $! rw { rwLabels = Map.fromList newLabels `Map.union` rwLabels rw }+  where+  newLabels = go labels (labelsToName labels)++  go (Label off _ : rest) name@(_ : ns) = (off,name) : go rest ns+  go (Ptr off _   : _)    name          = [(off,name)]+  go _                    _             = []+++{-# INLINE liftGet #-}+liftGet :: Int -> C.Get a -> Get a+liftGet n m = do addOffset n+                 lift m+++{-# INLINE getWord8 #-}+getWord8 :: Get Word8+getWord8  = liftGet 1 C.getWord8++{-# INLINE getWord16be #-}+getWord16be :: Get Word16+getWord16be  = liftGet 2 C.getWord16be++{-# INLINE getWord32be #-}+getWord32be :: Get Word32+getWord32be  = liftGet 4 C.getWord32be++{-# INLINE getInt32be #-}+getInt32be :: Get Int32+getInt32be  = fromIntegral `fmap` liftGet 4 C.getWord32be++{-# INLINE getBytes #-}+getBytes :: Int -> Get S.ByteString+getBytes n = liftGet n (C.getBytes n)++isolate :: Int -> Get a -> Get a+isolate n body =+  do off      <- get+     (a,off') <- lift (C.isolate n (runStateT off body))+     set off'+     return a++label :: String -> Get a -> Get a+label str m =+  do off      <- get+     (a,off') <- lift (C.label str (runStateT off m))+     set off'+     return a+++{-# INLINE putInt32be #-}+putInt32be :: Putter Int32+putInt32be i = putWord32be (fromIntegral i)++-- Parsing ---------------------------------------------------------------------++getDNSPacket :: C.Get DNSPacket+getDNSPacket  = unGet $ label "DNSPacket" $+  do dnsHeader <- getDNSHeader+     qdCount   <- getWord16be+     anCount   <- getWord16be+     nsCount   <- getWord16be+     arCount   <- getWord16be++     let blockOf c l m = label l (replicateM (fromIntegral c) m)+     dnsQuestions         <- blockOf qdCount "Questions"          getQuery+     dnsAnswers           <- blockOf anCount "Answers"            getRR+     dnsAuthorityRecords  <- blockOf nsCount "Authority Records"  getRR+     dnsAdditionalRecords <- blockOf arCount "Additional Records" getRR++     return DNSPacket { .. }++--                                  1  1  1  1  1  1+--    0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5+--  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--++--  |                      ID                       |+--  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--++--  |QR|   Opcode  |AA|TC|RD|RA|   Z    |   RCODE   |+--  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--++--  |                    QDCOUNT                    |+--  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--++--  |                    ANCOUNT                    |+--  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--++--  |                    NSCOUNT                    |+--  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--++--  |                    ARCOUNT                    |+--  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--++getDNSHeader :: Get DNSHeader+getDNSHeader  = label "DNS Header" $+  do dnsId <- getWord16be+     flags <- getWord16be+     let dnsQuery  = not (flags `testBit` 15)+         dnsOpCode = parseOpCode (flags `shiftR` 11)+         dnsAA     = flags `testBit` 10+         dnsTC     = flags `testBit`  9+         dnsRD     = flags `testBit`  8+         dnsRA     = flags `testBit`  7+         dnsZ      = (flags `shiftR` 4) .&. 0x7+         dnsRC     = parseRespCode (flags .&. 0xf)++     unless (dnsZ == 0) (fail ("Z not zero"))++     return DNSHeader { .. }+++parseOpCode :: Word16 -> OpCode+parseOpCode 0 = OpQuery+parseOpCode 1 = OpIQuery+parseOpCode 2 = OpStatus+parseOpCode c = OpReserved (c .&. 0xf)++parseRespCode :: Word16 -> RespCode+parseRespCode 0 = RespNoError+parseRespCode 1 = RespFormatError+parseRespCode 2 = RespServerFailure+parseRespCode 3 = RespNameError+parseRespCode 4 = RespNotImplemented+parseRespCode 5 = RespRefused+parseRespCode c = RespReserved (c .&. 0xf)++getQuery :: Get Query+getQuery  = label "Question" $+  do qName  <- getName+     qType  <- label "QTYPE"  getQType+     qClass <- label "QCLASS" getQClass+     return Query { .. }++--                                  1  1  1  1  1  1+--    0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5+--  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--++--  |                                               |+--  /                                               /+--  /                      NAME                     /+--  |                                               |+--  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--++--  |                      TYPE                     |+--  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--++--  |                     CLASS                     |+--  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--++--  |                      TTL                      |+--  |                                               |+--  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--++--  |                   RDLENGTH                    |+--  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|+--  /                     RDATA                     /+--  /                                               /+--  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--++getRR :: Get RR+getRR  = label "RR" $+  do rrName  <- getName+     ty      <- getType+     rrClass <- getClass+     rrTTL   <- getInt32be+     rrRData <- getRData ty+     return RR { .. }+++getType :: Get Type+getType  =+  do qt <- getQType+     case qt of+       QType ty -> return ty+       _        -> fail ("Invalid TYPE: " ++ show qt)++getQType :: Get QType+getQType  =+  do tag <- getWord16be+     case tag of+       1   -> return (QType A)+       2   -> return (QType NS)+       3   -> return (QType MD)+       4   -> return (QType MF)+       5   -> return (QType CNAME)+       6   -> return (QType SOA)+       7   -> return (QType MB)+       8   -> return (QType MG)+       9   -> return (QType MR)+       10  -> return (QType NULL)+       12  -> return (QType PTR)+       13  -> return (QType HINFO)+       14  -> return (QType MINFO)+       15  -> return (QType MX)+       28  -> return (QType AAAA)+       252 -> return AFXR+       253 -> return MAILB+       254 -> return MAILA+       255 -> return QTAny+       _   -> fail ("Invalid TYPE: " ++ show tag)+++getQClass :: Get QClass+getQClass  =+  do tag <- getWord16be+     case tag of+       1   -> return (QClass IN)+       2   -> return (QClass CS)+       3   -> return (QClass CH)+       4   -> return (QClass HS)+       255 -> return QAnyClass+       _   -> fail ("Invalid CLASS: " ++ show tag)++getName :: Get Name+getName  =+  do labels <- go+     addLabels labels+     return (labelsToName labels)+  where+  go = do off <- getOffset+          len <- getWord8+          if | len .&. 0xc0 == 0xc0 ->+               do l <- getWord8+                  let ptr = fromIntegral ((0x3f .&. len) `shiftL` 8)+                          + fromIntegral l+                  ns <- lookupPtr ptr+                  return [Ptr off ns]++             | len == 0 ->+                  return []++             | otherwise ->+               do l  <- getBytes (fromIntegral len)+                  ls <- go+                  return (Label off l:ls)++getClass :: Get Class+getClass  = label "CLASS" $+  do qc <- getQClass+     case qc of+       QClass c  -> return c+       QAnyClass -> fail "Invalid CLASS"++getRData :: Type -> Get RData+getRData ty = label (show ty) $+  do len <- getWord16be+     isolate (fromIntegral len) $ case ty of+       A     -> RDA  `fmap` liftGet 4 getIP4+       NS    -> RDNS `fmap` getName+       MD    -> RDMD `fmap` getName+       MF    -> RDMF `fmap` getName+       CNAME -> RDCNAME `fmap` getName+       SOA   -> do mname   <- getName+                   rname   <- getName+                   serial  <- getWord32be+                   refresh <- getInt32be+                   retry   <- getInt32be+                   expire  <- getInt32be+                   minTTL  <- getWord32be+                   return (RDSOA mname rname serial refresh retry expire minTTL)+       MB    -> RDMB `fmap` getName+       MG    -> RDMG `fmap` getName+       MR    -> RDMR `fmap` getName+       NULL  -> RDNULL `fmap` (getBytes =<< lift C.remaining)+       PTR   -> RDPTR `fmap` getName++       HINFO -> do cpuLen <- getWord8+                   cpu    <- getBytes (fromIntegral cpuLen)+                   osLen  <- getWord8+                   os     <- getBytes (fromIntegral osLen)+                   return (RDHINFO cpu os)++       MINFO -> do rmailBx <- getName+                   emailBx <- getName+                   return (RDMINFO rmailBx emailBx)++       MX    -> do pref <- getWord16be+                   ex   <- getName+                   return (RDMX pref ex)++       _     -> RDUnknown ty `fmap` (getBytes =<< lift C.remaining)+++-- Rendering -------------------------------------------------------------------++putDNSPacket :: Putter DNSPacket+putDNSPacket DNSPacket{ .. } =+  do putDNSHeader dnsHeader++     putWord16be (fromIntegral (length dnsQuestions))+     putWord16be (fromIntegral (length dnsAnswers))+     putWord16be (fromIntegral (length dnsAuthorityRecords))+     putWord16be (fromIntegral (length dnsAdditionalRecords))++     F.traverse_ putQuery dnsQuestions+     F.traverse_ putRR dnsAnswers+     F.traverse_ putRR dnsAuthorityRecords+     F.traverse_ putRR dnsAdditionalRecords++putDNSHeader :: Putter DNSHeader+putDNSHeader DNSHeader { .. } =+  do putWord16be dnsId+     let flag i b w | b         = setBit w i+                    | otherwise = clearBit w i+         flags = flag 15 (not dnsQuery)+               $ flag 10 dnsAA+               $ flag  9 dnsTC+               $ flag  8 dnsRD+               $ flag  7 dnsRA+               $ flag  4 False -- dnsZ+               $ (renderOpCode dnsOpCode `shiftL` 11) .|. renderRespCode dnsRC+     putWord16be flags++renderOpCode :: OpCode -> Word16+renderOpCode OpQuery        = 0+renderOpCode OpIQuery       = 1+renderOpCode OpStatus       = 2+renderOpCode (OpReserved c) = c .&. 0xf++renderRespCode :: RespCode -> Word16+renderRespCode RespNoError        = 0+renderRespCode RespFormatError    = 1+renderRespCode RespServerFailure  = 2+renderRespCode RespNameError      = 3+renderRespCode RespNotImplemented = 4+renderRespCode RespRefused        = 5+renderRespCode (RespReserved c)   = c .&. 0xf++putName :: Putter Name+putName  = go+  where+  go (l:ls)+    | S.null l        = putWord8 0+    | S.length l > 63 = error "Label too big"+    | otherwise       = do putWord8 (fromIntegral len)+                           putByteString l+                           go ls+    where+    len = S.length l++  go [] = putWord8 0++putQuery :: Putter Query+putQuery Query { .. } =+  do putName   qName+     putQType  qType+     putQClass qClass++putType :: Putter Type+putType A     = putWord16be 1+putType NS    = putWord16be 2+putType MD    = putWord16be 3+putType MF    = putWord16be 4+putType CNAME = putWord16be 5+putType SOA   = putWord16be 6+putType MB    = putWord16be 7+putType MG    = putWord16be 8+putType MR    = putWord16be 9+putType NULL  = putWord16be 10+putType PTR   = putWord16be 12+putType HINFO = putWord16be 13+putType MINFO = putWord16be 14+putType MX    = putWord16be 15+putType AAAA  = putWord16be 28++putQType :: Putter QType+putQType (QType ty) = putType ty+putQType AFXR       = putWord16be 252+putQType MAILB      = putWord16be 253+putQType MAILA      = putWord16be 254+putQType QTAny      = putWord16be 255++putQClass :: Putter QClass+putQClass (QClass c) = putClass c+putQClass QAnyClass  = putWord16be 255++putRR :: Putter RR+putRR RR { .. } =+  do putName rrName+     let (ty,rdata) = putRData rrRData+     putType ty+     putClass rrClass+     putWord32be (fromIntegral rrTTL)+     putWord16be (fromIntegral (S.length rdata))+     putByteString rdata++putClass :: Putter Class+putClass IN = putWord16be 1+putClass CS = putWord16be 2+putClass CH = putWord16be 3+putClass HS = putWord16be 4++putRData :: RData -> (Type,S.ByteString)+putRData rd = case rd of+  RDA addr           -> rdata A     (putIP4 addr)+  RDNS name          -> rdata NS    (putName name)+  RDMD name          -> rdata MD    (putName name)+  RDMF name          -> rdata MF    (putName name)+  RDCNAME name       -> rdata CNAME (putName name)+  RDSOA m r s f t ex ttl ->+                        rdata SOA $ do putName m+                                       putName r+                                       putWord32be s+                                       putInt32be f+                                       putInt32be t+                                       putInt32be ex+                                       putWord32be ttl++  RDMB name          -> rdata MB    (putName name)+  RDMG name          -> rdata MG    (putName name)+  RDMR name          -> rdata MR    (putName name)+  RDNULL bytes       -> rdata NULL  $ do putWord8 (fromIntegral (S.length bytes))+                                         putByteString bytes+  RDPTR name         -> rdata PTR   (putName name)+  RDHINFO cpu os     -> rdata HINFO $ do putWord8 (fromIntegral (S.length cpu))+                                         putByteString cpu+                                         putWord8 (fromIntegral (S.length os))+                                         putByteString os+  RDMINFO rm em      -> rdata MINFO $ do putName rm+                                         putName em+  RDMX pref ex       -> rdata MX    $ do putWord16be pref+                                         putName ex+  RDUnknown ty bytes -> (ty,bytes)+  where+  rdata tag m = (tag,runPut m)
+ src/Hans/Ethernet.hs view
@@ -0,0 +1,27 @@+{-# LANGUAGE RecordWildCards #-}++module Hans.Ethernet (+    module Exports,+    module Hans.Ethernet+  ) where++import Hans.Device.Types+import Hans.Ethernet.Types as Exports+import Hans.Serialize (runPutPacket)++import           Control.Concurrent.BoundedChan (tryWriteChan)+import           Control.Monad (unless)+import qualified Data.ByteString.Lazy as L+++-- | Send a message out via a device.+sendEthernet :: Device -> Mac -> EtherType -> L.ByteString -> IO ()+sendEthernet Device { .. } eDest eType payload =+  do let packet = runPutPacket 14 100 payload+                $ putEthernetHeader EthernetHeader { eSource = devMac, .. }++     -- if the packet is too big for the device, throw it away+     if (fromIntegral (L.length packet) > dcMtu devConfig + 14)+        then updateError statTX devStats+        else do queued <- tryWriteChan devSendQueue packet+                unless queued (updateDropped statTX devStats)
+ src/Hans/Ethernet/Types.hs view
@@ -0,0 +1,117 @@+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE PatternSynonyms #-}++module Hans.Ethernet.Types (+    -- * Ethernet Headers+    EthernetHeader(..), getEthernetHeader, putEthernetHeader,+    EtherType,++    -- ** MAC addresses+    Mac(..), getMac, putMac, showMac, readMac,+    pattern BroadcastMac,++    -- ** EtherType Patterns+    pattern ETYPE_IPV4,+    pattern ETYPE_ARP,+    pattern ETYPE_IPV6++  ) where++import           Data.Serialize+                     (Get,getWord8,getWord16be,Putter,putWord16be,putWord8+                     ,Serialize(..))+import           Data.Word (Word8,Word16)+import           Numeric (readHex,showHex)+++-- Mac Addresses ---------------------------------------------------------------++data Mac = Mac {-# UNPACK #-} !Word8+               {-# UNPACK #-} !Word8+               {-# UNPACK #-} !Word8+               {-# UNPACK #-} !Word8+               {-# UNPACK #-} !Word8+               {-# UNPACK #-} !Word8+               deriving (Eq,Ord,Show,Read)++instance Serialize Mac where+  get = getMac+  put = putMac+  {-# INLINE get #-}+  {-# INLINE put #-}+++getMac :: Get Mac+getMac  =+  do a <- getWord8+     b <- getWord8+     c <- getWord8+     d <- getWord8+     e <- getWord8+     f <- getWord8+     return $! Mac a b c d e f++putMac :: Putter Mac+putMac (Mac a b c d e f) =+  do putWord8 a+     putWord8 b+     putWord8 c+     putWord8 d+     putWord8 e+     putWord8 f++showMac :: Mac -> ShowS+showMac (Mac a b c d e f) = pad a . showChar ':'+                          . pad b . showChar ':'+                          . pad c . showChar ':'+                          . pad d . showChar ':'+                          . pad e . showChar ':'+                          . pad f+  where+  pad x | x < 0x10  = showChar '0' . showHex x+        | otherwise =                showHex x+++readMac :: ReadS Mac+readMac str =+  do (a,':':rest1) <- readHex str+     (b,':':rest2) <- readHex rest1+     (c,':':rest3) <- readHex rest2+     (d,':':rest4) <- readHex rest3+     (e,':':rest5) <- readHex rest4+     (f,rest6)     <- readHex rest5+     return (Mac a b c d e f, rest6)++-- | The broadcast MAC address.+pattern BroadcastMac = Mac 0xff 0xff 0xff 0xff 0xff 0xff+++-- Ethernet Headers ------------------------------------------------------------++type EtherType = Word16++data EthernetHeader = EthernetHeader+  { eDest    :: {-# UNPACK #-} !Mac+  , eSource  :: {-# UNPACK #-} !Mac+  , eType    :: {-# UNPACK #-} !EtherType+  } deriving (Eq,Show)++getEthernetHeader :: Get EthernetHeader+getEthernetHeader =+  do eDest   <- getMac+     eSource <- getMac+     eType   <- getWord16be+     return EthernetHeader { .. }++putEthernetHeader :: Putter EthernetHeader+putEthernetHeader EthernetHeader { .. } =+  do putMac      eDest+     putMac      eSource+     putWord16be eType+++-- Common Ether-Types ----------------------------------------------------------++pattern ETYPE_IPV4 = 0x0800+pattern ETYPE_ARP  = 0x0806+pattern ETYPE_IPV6 = 0x86DD
+ src/Hans/HashTable.hs view
@@ -0,0 +1,193 @@+{-# LANGUAGE RecordWildCards #-}++module Hans.HashTable (+    HashTable(),+    newHashTable,+    lookup,+    delete, deletes,+    mapHashTable, mapHashTableM_,+    filterHashTable,+    alter,+    keys,+    hasKey,+    size,+  ) where++import           Prelude hiding (lookup)++import           Control.Monad (replicateM,forM_)+import           Data.Array (Array,listArray,(!))+import           Data.Function (on)+import           Data.Hashable (Hashable,hash)+import           Data.IORef (IORef,newIORef,atomicModifyIORef',readIORef)+import qualified Data.List as List+import           Data.Ord (comparing)+++data HashTable k a =+  HashTable { htSize    :: {-# UNPACK #-} !Int+            , htBuckets :: {-# UNPACK #-} !(Array Int (Bucket k a))+            }++type Bucket k a = IORef [(k,a)]++cons' :: a -> [a] -> [a]+cons' h tl = h `seq` tl `seq` (h:tl)+{-# INLINE cons' #-}++nfList :: [a] -> ()+nfList []       = ()+nfList (a:rest) = a `seq` nfList rest+{-# INLINE nfList #-}++nfListId :: [a] -> [a]+nfListId xs = nfList xs `seq` xs+{-# INLINE nfListId #-}++-- | Create a new hash table with the given size.+newHashTable :: (Eq k, Hashable k) => Int -> IO (HashTable k a)+newHashTable htSize =+  do buckets <- replicateM htSize (newIORef [])+     return HashTable { htBuckets = listArray (0,htSize - 1) buckets+                      , .. }++mapBuckets :: (Eq k, Hashable k) => ([(k,a)] -> [(k,a)]) -> HashTable k a -> IO ()+mapBuckets f HashTable { .. } = go 0+  where+  update bucket = (f bucket, ())++  go ix | ix < htSize = do atomicModifyIORef' (htBuckets ! ix) update+                           go (succ ix)+        | otherwise   = return ()+{-# INLINE mapBuckets #-}++filterHashTable :: (Eq k, Hashable k)+                => (k -> a -> Bool) -> HashTable k a -> IO ()+filterHashTable p = mapBuckets go+  where+  go []                         = []+  go (x@(k,a):rest) | p k a     = cons' x (go rest)+                    | otherwise = go rest+{-# INLINE filterHashTable #-}++-- | Monadic mapping over the values of a hash table.+mapHashTableM_ :: (Eq k, Hashable k)+               => (k -> a -> IO ()) -> HashTable k a -> IO ()+mapHashTableM_ f HashTable { .. } = go 0+  where+  go ix | ix < htSize = do bs <- readIORef (htBuckets ! ix)+                           mapM_ (\(k,a) -> f k a) bs+                           go (ix + 1)++        | otherwise   = return ()+{-# INLINE mapHashTableM_ #-}++mapHashTable :: (Eq k, Hashable k) => (k -> a -> a) -> HashTable k a -> IO ()+mapHashTable f = mapBuckets go+  where+  go []           = []+  go ((k,a):rest) = cons' ((,) k $! f k a) (go rest)+{-# INLINE mapHashTable #-}++getBucket :: Hashable k => HashTable k a -> k -> Bucket k a+getBucket HashTable { .. } k = htBuckets ! key+  where+  key = hash k `mod` htSize+{-# INLINE getBucket #-}++modifyBucket :: Hashable k+             => HashTable k a -> k -> ([(k,a)] -> ([(k,a)],b)) -> IO b+modifyBucket ht k = atomicModifyIORef' (getBucket ht k)+{-# INLINE modifyBucket #-}++lookup :: (Eq k, Hashable k) => k -> HashTable k a -> IO (Maybe a)+lookup k ht =+  do bucket <- readIORef (getBucket ht k)+     return $! List.lookup k bucket+{-# INLINE lookup #-}++delete :: (Eq k, Hashable k) => k -> HashTable k a -> IO ()+delete k ht = modifyBucket ht k (\ bucket -> (nfListId (removeEntry bucket), ()))++  where++  removeEntry (entry @ (k',_):rest)+    | k' == k   = rest+    | otherwise = let rest' = removeEntry rest+                   in rest' `seq` (entry : rest')++  removeEntry []  = []+{-# INLINE delete #-}+++-- | Delete a collection of keys from the table. This is good for multiple+-- deletes, as deletes from the same bucket can be grouped together.+deletes :: (Eq k, Hashable k) => [k] -> HashTable k a -> IO ()+deletes ks ht =+  forM_ groups $ \ grp ->+    case grp of+      (_,ix):_ -> atomicModifyIORef' (htBuckets ht ! ix)+                  (\ bucket -> (bucket `without` grp, ()))+      _        -> return ()+  where++  -- keys grouped by bucket+  groups = List.groupBy ((==) `on` snd)+         $ List.sortBy  (comparing snd)+         $ [ (k, hash k `mod` htSize ht) | k <- ks ]++  -- remove entries from the bucket+  without = List.foldl' (\b (k,_) -> remove k b)++  remove k = go+    where+    go []                          = []+    go (e@(k',_):rest) | k == k'   = rest+                       | otherwise = e:go rest+++-- | Create, update, or delete an entry in the hash table, returning some+-- additional value derived from the operation.+-- NOTE: This operation is only useful when you need to perform any of these+-- operations at the same time -- the specialized versions of the individual+-- behaviors all perform slightly better.+alter :: (Eq k, Hashable k)+       => (Maybe a -> (Maybe a, b)) -> k -> HashTable k a -> IO b+alter f k ht = modifyBucket ht k (update nfListId)+  where++  update mkBucket (e@(k',a):rest)+    | k == k'   = finish mkBucket (Just a) rest+    | otherwise = update (\l -> mkBucket ( e : l )) rest++  update mkBucket [] = finish mkBucket Nothing []++  finish mkBucket mb rest =+    case f mb of+      (Just a, b) -> (mkBucket ((k,a):rest), b)+      (Nothing,b) -> (mkBucket        rest , b)+{-# INLINE alter #-}+++size :: HashTable k a -> IO Int+size HashTable { .. } = loop 0 0+  where+  loop acc ix | ix < htSize = do bucket <- readIORef (htBuckets ! ix)+                                 (loop $! acc + length bucket) (ix + 1)+              | otherwise   = return acc+++-- | Gives back an (unsorted) list of all the keys present in the hash table.+keys :: HashTable k a -> IO [k]+keys HashTable { .. } = go [] 0+  where+  go acc ix | ix < htSize = do bucket <- readIORef (htBuckets ! ix)+                               go (map fst bucket ++ acc) (ix + 1)++            | otherwise   = return acc++hasKey :: (Eq k, Hashable k) => k -> HashTable k a -> IO Bool+hasKey k ht =+  do b <- readIORef (getBucket ht k)+     return $! any (\ (k',_) -> k == k') b+{-# INLINE hasKey #-}
+ src/Hans/IP4.hs view
@@ -0,0 +1,13 @@+{-# LANGUAGE PatternSynonyms #-}+module Hans.IP4 (+    module Exports+  ) where+++import Hans.IP4.Packet as Exports+           (IP4,packIP4,unpackIP4,pattern BroadcastIP4,pattern WildcardIP4+           ,ip4PseudoHeader)++import Hans.IP4.Output as Exports++import Hans.IP4.State as Exports (SendSource(..))
+ src/Hans/IP4/ArpTable.hs view
@@ -0,0 +1,185 @@+{-# LANGUAGE RecordWildCards #-}++module Hans.IP4.ArpTable (+    -- * Arp Table+    ArpTable(), newArpTable,++    -- ** Update+    addEntry,+    markUnreachable,++    -- ** Query+    lookupEntry,+    resolveAddr, QueryResult(..),+    WaitStrategy(), blockingStrategy, writeChanStrategy++  ) where++import           Hans.Config (Config(..))+import           Hans.Device.Types (DeviceStats,updateError,statTX)+import           Hans.Ethernet (Mac)+import qualified Hans.HashTable as HT+import           Hans.IP4.Packet (IP4)+import           Hans.Threads (forkNamed)+import           Hans.Time (toUSeconds)++import           Control.Concurrent+                     (threadDelay,MVar,newEmptyMVar,ThreadId,tryPutMVar)+import qualified Control.Concurrent.BoundedChan as BC+import           Control.Monad (forever)+import           Data.Time.Clock+                     (UTCTime,NominalDiffTime,addUTCTime,getCurrentTime)+++-- | The Arp table consists of a map of IP4 to Mac, as well as a heap that+-- orders the IP4 addresses according to when their corresponding entries should+-- be expired.+--+-- NOTE: There's currently no way to limit the memory use of the arp table, but+-- that might not be a huge problem as the entries are added based on requests+-- from higher layers.+--+-- INVARIANT: there should never be entries in the map that aren't also in the+-- heap.+data ArpTable = ArpTable { atMacs        :: !(HT.HashTable IP4 Entry)+                         , atLifetime    :: !NominalDiffTime+                         , atPurgeThread :: !ThreadId+                         }++data Entry = Waiting [Maybe Mac -> IO ()]+           | Present !UTCTime !Mac+++newArpTable :: Config -> IO ArpTable+newArpTable Config { .. } =+  do atMacs        <- HT.newHashTable cfgArpTableSize+     atPurgeThread <- forkNamed "Arp Purge Thread"+                          (purgeArpTable cfgArpTableLifetime atMacs)+     return ArpTable { atLifetime = cfgArpTableLifetime, .. }+++-- | Loops forever, delaying until the next arp table entry needs to be purged.+-- If no entries exist, it waits for the maximum entry lifetime before checking+-- again.+purgeArpTable :: NominalDiffTime -> HT.HashTable IP4 Entry -> IO ()+purgeArpTable lifetime table = forever $+  do now <- getCurrentTime+     HT.filterHashTable (update now) table+     threadDelay delay++  where++  update _   _ Waiting{}          = False+  update now _ (Present expire _) = expire < now++  delay = toUSeconds lifetime+++-- | Lookup an entry in the Arp table.+lookupEntry :: ArpTable -> IP4 -> IO (Maybe Mac)+lookupEntry ArpTable { .. } spa =+  do mb <- HT.lookup spa atMacs+     case mb of+       Just (Present _ mac) -> return (Just mac)+       _                    -> return Nothing+++-- | Insert an entry into the Arp table, and unblock any waiting actions.+addEntry :: ArpTable -> IP4 -> Mac -> IO ()+addEntry ArpTable { .. } spa sha =+  do now <- getCurrentTime+     let end = addUTCTime atLifetime now++     waiters <- HT.alter (update end) spa atMacs++     -- NOTE: as we don't allow user-supplied IO actions to be registered as+     -- callbacks, we don't catch any exceptions here; the only way that an+     -- action can end up in this list is through the constructors for the+     -- WaitStrategy type defined below.+     --+     -- If it turns out that it's significantly impacting the performance of the+     -- fast path, we should consider forking a thread to run the waiters.+     mapM_ ($ Just sha) waiters++  where++  update expire (Just (Waiting ks)) = (Just (Present expire sha), ks)+  update expire (Just Present{})    = (Just (Present expire sha), [])+  update expire Nothing             = (Just (Present expire sha), [])+++-- | If nobody has responded to queries for this address, notify any waiters+-- that there is no mac associated.+--+-- NOTE: in the future, it might be nice to keep an entry in the table that+-- indicates that this host is unreachable.+markUnreachable :: ArpTable -> IP4 -> IO ()+markUnreachable ArpTable { .. } addr =+  do waiters <- HT.alter update addr atMacs++     -- See the note in addEntry about why we don't need to sandbox the+     -- callbacks.+     mapM_ ($ Nothing) waiters++  where++  update (Just (Waiting ks))  = (Nothing, ks)+  update ent@(Just Present{}) = (ent, [])+  update Nothing              = (Nothing,[])++++newtype WaitStrategy res = WaitStrategy { getWaiter :: IO (Maybe Mac -> IO (), res) }+++-- | Have the entry block on a +blockingStrategy :: WaitStrategy (MVar (Maybe Mac))+blockingStrategy  = WaitStrategy $+  do mvar <- newEmptyMVar++     let write mb = do _ <- tryPutMVar mvar mb+                       return ()++     return (write, mvar)+++-- | Write the discovered Mac to a bounded channel, passing it through a+-- filtering function first.+writeChanStrategy :: Maybe DeviceStats -> (Maybe Mac -> Maybe msg) -> BC.BoundedChan msg+                  -> WaitStrategy ()+writeChanStrategy mbStats f chan = WaitStrategy (return (handler,()))+  where+  handler mb =+    case f mb of+      Just msg -> do written <- BC.tryWriteChan chan msg+                     case mbStats of+                       Just stats | not written -> updateError statTX stats+                       _                        -> return ()++      -- XXX should this update a stat?+      Nothing  -> return ()+++data QueryResult res = Known !Mac+                     | Unknown !Bool res+++-- | Returns either the address, or an empty 'MVar' that will eventually contain+-- the 'Mac', or 'Nothing' if the Arp request fails.+resolveAddr :: ArpTable -> IP4 -> WaitStrategy res -> IO (QueryResult res)+resolveAddr arp addr strategy =+  do mb <- lookupEntry arp addr+     case mb of+       Just mac -> return (Known mac)+       Nothing  -> registerWaiter arp addr strategy+++-- | Register to wait on an entry in the table.+registerWaiter :: ArpTable -> IP4 -> WaitStrategy res -> IO (QueryResult res)+registerWaiter ArpTable { .. } addr strategy =+  do waiter <- getWaiter strategy+     HT.alter (update waiter) addr atMacs+  where+  update (w,r) (Just (Waiting ws))        = (Just (Waiting (w:ws)), Unknown False r)+  update _     ent@(Just (Present _ mac)) = (ent, Known mac)+  update (w,r) Nothing                    = (Just (Waiting [w]), Unknown True r)
+ src/Hans/IP4/Dhcp/Client.hs view
@@ -0,0 +1,239 @@+{-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE RecordWildCards #-}++module Hans.IP4.Dhcp.Client (+    DhcpConfig(..),+    defaultDhcpConfig,+    DhcpLease(..),+    dhcpClient,+  ) where++import Hans.Device.Types (Device(devMac))+import Hans.IP4.Dhcp.Codec (SubnetMask(..))+import Hans.IP4.Dhcp.Packet+import Hans.IP4.Dhcp.Options+import Hans.IP4.Packet (IP4,pattern WildcardIP4,pattern BroadcastIP4,IP4Mask(..))+import Hans.IP4.RoutingTable(Route(..),RouteType(..))+import Hans.Lens+import Hans.Socket+           (UdpSocket,newUdpSocket,sClose,sendto,recvfrom,SockPort+           ,defaultSocketConfig)+import Hans.Serialize (runPutPacket)+import Hans.Threads (forkNamed)+import Hans.Time (toUSeconds)+import Hans.Types (NetworkStack,networkStack,addRoute,addNameServer4)++import           Control.Concurrent (threadDelay,killThread)+import           Control.Monad (guard)+import qualified Data.ByteString.Lazy as L+import           Data.Maybe (fromMaybe,mapMaybe)+import           Data.Serialize.Get (runGetLazy)+import           Data.Time.Clock (NominalDiffTime)+import           System.Random (randomIO,randomRIO)+import           System.Timeout (timeout)+++-- | BOOTP server port.+bootps :: SockPort+bootps  = 67++-- | BOOTP client port.+bootpc :: SockPort+bootpc  = 68++mkXid :: IO Xid+mkXid  = do w <- randomIO+            return (Xid w)+++renderMessage :: Dhcp4Message -> L.ByteString+renderMessage msg = runPutPacket 236 256 L.empty (putDhcp4Message msg)++data DhcpConfig = DhcpConfig { dcInitialTimeout :: !NominalDiffTime+                               -- ^ Initial timeout++                             , dcRetries :: !Int+                               -- ^ Number of retries++                             , dcDefaultRoute :: Bool+                               -- ^ Whether or not routing information received+                               -- from the DHCP server should be used as the+                               -- default route for the network stack.++                             , dcAutoRenew :: Bool+                               -- ^ Whether or not to fork a renew thread once+                               -- configuration information has been received.+                             }++defaultDhcpConfig :: DhcpConfig+defaultDhcpConfig  = DhcpConfig { dcInitialTimeout = 4.0+                                , dcRetries        = 6+                                , dcDefaultRoute   = True+                                , dcAutoRenew      = True }+++-- | Wait for a result on a socket+waitResponse :: DhcpConfig -> IO () -> IO a -> IO (Maybe a)+waitResponse DhcpConfig { .. } send recv =+  go dcRetries (toUSeconds dcInitialTimeout)+  where+  go retries toVal =+    do send+       mb <- timeout toVal recv+       case mb of++         Just{} -> return mb++         -- adjust the timeout by two, and add some slack before trying again+         Nothing | retries > 0 ->+           do slack <- randomRIO (500,1000)+              go (retries - 1) (toVal * 2 + slack * 1000)++         _ -> return Nothing+++data DhcpLease = DhcpLease { dhcpRenew :: !(IO ())+                           , dhcpAddr  :: !IP4+                           }+++dhcpClient :: NetworkStack -> DhcpConfig -> Device -> IO (Maybe DhcpLease)+dhcpClient ns cfg dev =+  do sock <- newUdpSocket ns defaultSocketConfig (Just dev) WildcardIP4 (Just bootpc)+     dhcpDiscover cfg dev sock+++-- | Discover a dhcp server, and request an address.+dhcpDiscover :: DhcpConfig -> Device -> UdpSocket IP4 -> IO (Maybe DhcpLease)+dhcpDiscover cfg dev sock =+  do xid  <- mkXid+     let msg = renderMessage (discoverToMessage (mkDiscover xid (devMac dev)))++     mb <- waitResponse cfg (sendto sock BroadcastIP4 bootps msg) (awaitOffer sock)+     case mb of+       Just offer -> dhcpRequest cfg dev sock offer+       Nothing    -> do sClose sock+                        return Nothing+++-- | Only accept an offer.+awaitOffer :: UdpSocket IP4 -> IO Offer+awaitOffer sock = go+  where+  go =+    do (_,_,srcPort,bytes) <- recvfrom sock++       if srcPort /= bootps+          then go+          else case runGetLazy getDhcp4Message bytes of++                 Right msg+                   | Just (Right (OfferMessage o)) <- parseDhcpMessage msg ->+                     return o++                 _ -> go+++-- | Respond to an offer with a request, and configure the network stack if an+-- acknowledgement is received.+dhcpRequest :: DhcpConfig -> Device -> UdpSocket IP4 -> Offer -> IO (Maybe DhcpLease)+dhcpRequest cfg dev sock offer =+  do let req = renderMessage (requestToMessage (offerToRequest offer))+     mb <- waitResponse cfg (sendto sock BroadcastIP4 bootps req) (awaitAck sock)+     sClose sock+     case mb of++       Nothing  -> return Nothing++       Just ack ->+         do lease <- handleAck (view networkStack sock) cfg dev offer ack+            return (Just lease)+++awaitAck :: UdpSocket IP4 -> IO Ack+awaitAck sock = go+  where+  go =+    do (_,_,srcPort,bytes) <- recvfrom sock++       if srcPort /= bootps+          then go+          else case runGetLazy getDhcp4Message bytes of++                 Right msg+                   | Just (Right (AckMessage a)) <- parseDhcpMessage msg ->+                     return a++                 _ -> go++++-- | Perform a DHCP Renew.+renew :: NetworkStack -> DhcpConfig -> Device -> Offer -> IO ()+renew ns cfg dev offer =+  do sock <- newUdpSocket ns defaultSocketConfig (Just dev) WildcardIP4 (Just bootpc)+     _    <- dhcpRequest cfg dev sock offer++     return ()+++-- Ack Management --------------------------------------------------------------++-- | Apply the information in the Ack to the NetworkStack, and Device. Returns+-- information about the lease, as well as an IO action that can be used to+-- renew it.+handleAck :: NetworkStack -> DhcpConfig -> Device -> Offer -> Ack -> IO DhcpLease+handleAck ns cfg dev offer Ack { .. } =+  do let addr     = ackYourAddr+         mask     = fromMaybe 24 (lookupSubnet ackOptions)++     let nameServers = concat (mapMaybe getNameServers ackOptions)+     mapM_ (addNameServer4 ns) nameServers++     addRoute ns False Route+       { routeNetwork = IP4Mask addr mask+       , routeType    = Direct+       , routeDevice  = dev+       }++     case lookupGateway ackOptions of+       Just gw | dcDefaultRoute cfg ->+         addRoute ns True Route+             { routeNetwork = IP4Mask addr 0+             , routeType    = Indirect gw+             , routeDevice  = dev+             }++       _ -> return ()++     dhcpRenew <-+       if dcAutoRenew cfg+          then -- wait for half of the lease time, then automatically renew+               -- XXX: what happens on a 32-bit system here?+               do tid <- forkNamed "dhcpRenew" $+                         do threadDelay (fromIntegral ackLeaseTime * 500000)+                            renew ns cfg dev offer++                  return $ do killThread tid+                              renew ns cfg dev offer++          else return (renew ns cfg dev offer)++     return $! DhcpLease { dhcpAddr = addr, .. }+++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++getNameServers :: Dhcp4Option -> Maybe [IP4]+getNameServers (OptNameServers addrs) = Just addrs+getNameServers _                      = Nothing
+ src/Hans/IP4/Dhcp/Codec.hs view
@@ -0,0 +1,99 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++module Hans.IP4.Dhcp.Codec where++import Hans.Ethernet (Mac,getMac,putMac)+import Hans.IP4.Packet (IP4,IP4Mask(..),getIP4,putIP4)++import Data.List (find)+import Data.Serialize.Get+import Data.Serialize.Put+import Data.Word (Word8, Word16, Word32)+++class CodecAtom a where+  getAtom :: Get a+  putAtom :: a -> Put+  atomSize :: a -> Int++instance (CodecAtom a, CodecAtom b) => CodecAtom (a,b) where+  getAtom       = do a <- getAtom+                     b <- getAtom+                     return (a,b)+  putAtom (a,b) = do 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       = getIP4+  putAtom       = putIP4+  atomSize _    = 4++instance CodecAtom IP4Mask where+  getAtom =+    do addr            <- getAtom+       SubnetMask mask <- getAtom+       return $! IP4Mask addr mask++  putAtom (IP4Mask addr mask) =+    do putAtom addr+       putAtom (SubnetMask mask)++  atomSize _    = atomSize (undefined :: IP4)+                + atomSize (undefined :: SubnetMask)++instance CodecAtom Mac where+  getAtom       = getMac+  putAtom       = putMac+  atomSize _    = 6++-----------------------------------------------------------------------+-- Subnet parser/unparser operations ----------------------------------+-----------------------------------------------------------------------++newtype SubnetMask = SubnetMask { unmask :: Int+                                } deriving (Show, Eq)++word32ToSubnetMask :: Word32 -> Maybe SubnetMask+word32ToSubnetMask mask =+  do i <- find (\ i -> computeMask i == mask) [0..32]+     return (SubnetMask i)++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/IP4/Dhcp/Options.hs view
@@ -0,0 +1,884 @@+module Hans.IP4.Dhcp.Options where++import Hans.IP4.Dhcp.Codec+import Hans.IP4.Packet (IP4,IP4Mask)++import qualified Control.Applicative as A+import           Control.Monad (unless)+import           Data.Maybe (fromMaybe)+import           Data.Foldable (traverse_)+import qualified Data.Traversable as T+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)+++-----------------------------------------------------------------------+-- 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) `fmap` getOption+      case tag of+        OptTagPad                           -> A.pure (Left ControlPad)+        OptTagEnd                           -> A.pure (Left 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 = do 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 =+  do putAtom MagicCookie+     traverse_ putDhcp4Option opts+     putControlOption ControlEnd++scrubControls :: (A.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) =+  do traverse_ eatPad xs+     return []++scrubControls (Right o : xs) =+  do os <- scrubControls xs+     return (o:os)++-- | '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 :: A.Applicative f => Int -> f a -> f [a]+replicateA n f = T.sequenceA (replicate n f)++repeatedly :: Get a -> Get [a]+repeatedly m = go []+  where+  go acc =+    do done <- isEmpty+       if done then return (reverse acc)+               else do a <- m+                       go (a:acc)++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 `fmap` getWord8++putLen :: Int -> Put+putLen n = putWord8 (fromIntegral n)
+ src/Hans/IP4/Dhcp/Packet.hs view
@@ -0,0 +1,662 @@+{-# LANGUAGE PatternSynonyms #-}++{- | 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.IP4.Dhcp.Packet+  (+  -- ** 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.Ethernet (Mac)+import Hans.IP4.Dhcp.Codec+import Hans.IP4.Dhcp.Options+import Hans.IP4.Packet (IP4(..),pattern WildcardIP4)++import qualified Control.Applicative as A+import           Control.Monad (unless)+import           Data.Bits (testBit,bit)+import qualified Data.ByteString as BS+import qualified Data.ByteString.Char8 as BS8+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)+++-- 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++  , requestServerAddr :: !IP4++  , 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 A.<* (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 =+    do w <- getAtom+       return (Xid w)++  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 =+    do b <- getAtom+       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+       return Flags { broadcastFlag = testBit (b :: Word16) 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 `fmap` case messageType of++         Dhcp4Request -> RequestMessage `fmap`+           do params <- lookupParams (dhcp4Options msg)+              let params' = selectKnownTags params+              let addr = lookupRequestAddr (dhcp4Options msg)+              return $! Request+                { requestXid                          = dhcp4Xid msg+                , requestBroadcast                    = dhcp4Broadcast msg+                , requestServerAddr                   = dhcp4ServerAddr msg+                , requestClientHardwareAddress        = dhcp4ClientHardwareAddr msg+                , requestParameters                   = params'+                , requestAddress                      = addr+                }++         Dhcp4Discover -> DiscoverMessage `fmap`+           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 `fmap` case messageType of++         Dhcp4Ack -> AckMessage `fmap`+           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 -> return $! OfferMessage Offer+                { offerHops                           = dhcp4Hops msg+                , offerXid                            = dhcp4Xid msg+                , offerYourAddr                       = dhcp4YourAddr msg+                , offerServerAddr                     = getOfferServerAddr (dhcp4ServerAddr msg) (dhcp4Options msg)+                , offerRelayAddr                      = dhcp4RelayAddr msg+                , offerClientHardwareAddr             = dhcp4ClientHardwareAddr msg+                , offerOptions                        = dhcp4Options msg+                }++         _ -> Nothing+++getOfferServerAddr :: IP4 -> [Dhcp4Option] -> IP4+getOfferServerAddr  = foldr check+  where+  check (OptServerIdentifier ip) _   = ip+  check _                        def = def++--+-- Structured to unstructured 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             = WildcardIP4+  , dhcp4YourAddr               = WildcardIP4+  , dhcp4ServerAddr             = WildcardIP4+  , dhcp4RelayAddr              = WildcardIP4+  , 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             = WildcardIP4+  , 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             = WildcardIP4+  , 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             = WildcardIP4+  , dhcp4YourAddr               = WildcardIP4+  , dhcp4ServerAddr             = WildcardIP4+  , dhcp4RelayAddr              = WildcardIP4+  , dhcp4ClientHardwareAddr     = requestClientHardwareAddress request+  , dhcp4ServerHostname         = ""+  , dhcp4BootFilename           = ""+  , dhcp4Options                = [ OptMessageType Dhcp4Request+                                  , OptServerIdentifier (requestServerAddr request)+                                  , 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+  , requestServerAddr                   = offerServerAddr offer+  , requestClientHardwareAddress        = offerClientHardwareAddr offer+  , requestParameters                   = [ OptTagSubnetMask+                                          , OptTagBroadcastAddress+                                          , OptTagTimeOffset+                                          , OptTagRouters+                                          , OptTagDomainName+                                          , OptTagNameServers+                                          , OptTagHostName+                                          ]+  , requestAddress                      = Just (offerYourAddr offer)+  }
+ src/Hans/IP4/Fragments.hs view
@@ -0,0 +1,198 @@+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE PatternGuards #-}++module Hans.IP4.Fragments (+    FragTable(),+    newFragTable, cleanupFragTable,+    processFragment,+  ) where++import           Hans.Config+import qualified Hans.HashTable as HT+import           Hans.IP4.Packet+import           Hans.Lens (view)+import           Hans.Monad+import           Hans.Network.Types (NetworkProtocol)+import           Hans.Threads (forkNamed)+import           Hans.Time (toUSeconds)++import           Control.Concurrent (ThreadId,threadDelay,killThread)+import           Control.Monad (forever)+import qualified Data.ByteString as S+import           Data.Time.Clock+                     (UTCTime,getCurrentTime,NominalDiffTime,addUTCTime)+++-- | Keys are of the form @(src,dest,prot,ident)@.+type Key = (IP4,IP4,NetworkProtocol,IP4Ident)++type Table  = HT.HashTable Key Buffer++-- XXX: there isn't any way to limit the size of the fragment table right now.+data FragTable = FragTable { ftEntries     :: !Table+                           , ftDuration    :: !NominalDiffTime+                           , ftPurgeThread :: !ThreadId+                           }++newFragTable :: Config -> IO FragTable+newFragTable Config { .. } =+  do ftEntries     <- HT.newHashTable 31+     ftPurgeThread <- forkNamed "IP4 Fragment Purge Thread"+                          (purgeEntries cfgIP4FragTimeout ftEntries)+     return FragTable { ftDuration = cfgIP4FragTimeout, .. }+++cleanupFragTable :: FragTable -> IO ()+cleanupFragTable FragTable { .. } = killThread ftPurgeThread+++-- | Handle an incoming fragment. If the fragment is buffered, but doesn't+-- complete the packet, the escape continuation is invoked.+processFragment :: FragTable -> IP4Header -> S.ByteString+                -> Hans (IP4Header,S.ByteString)++processFragment FragTable { .. } hdr body++    -- no fragments+  | not (view ip4MoreFragments hdr) && view ip4FragmentOffset hdr == 0 =+    return (hdr,body)++    -- fragment+  | otherwise =+    do mb <- io $ do now <- getCurrentTime+                     let expire = addUTCTime ftDuration now+                         frag   = mkFragment hdr body+                         key    = mkKey hdr++                     HT.alter (updateBuffer expire hdr frag) key ftEntries+       case mb of+         -- abort packet processing here, as there's nothing left to do+         Nothing           -> escape++         -- return the reassembled packet+         Just (hdr',body') -> return (hdr',body')+{-# INLINE processFragment #-}+++-- Table Purging ---------------------------------------------------------------++-- | Every second, purge the fragment table of entries that have expired.+purgeEntries :: NominalDiffTime -> Table -> IO ()+purgeEntries lifetime entries = forever $+  do threadDelay halfLife++     now <- getCurrentTime+     HT.filterHashTable (\_ Buffer { .. } -> bufExpire < now) entries++  where+  halfLife = toUSeconds (lifetime / 2)+++-- Fragment Operations ---------------------------------------------------------++-- INVARIANT: When new fragments are inserted into bufFragments, they are merged+-- together when possible. This makes it easier to check the state of the whole+-- buffer.+data Buffer = Buffer { bufExpire    :: !UTCTime+                     , bufSize      :: !(Maybe Int)+                     , bufHeader    :: !(Maybe IP4Header)+                     , bufFragments :: ![Fragment]+                     }++data Fragment = Fragment { fragStart   :: {-# UNPACK #-} !Int+                         , fragEnd     :: {-# UNPACK #-} !Int+                         , fragPayload ::                 [S.ByteString]+                         } deriving (Show)++mkKey :: IP4Header -> Key+mkKey IP4Header { .. } = (ip4SourceAddr,ip4DestAddr,ip4Protocol,ip4Ident)+++mkFragment :: IP4Header -> S.ByteString -> Fragment+mkFragment hdr body = Fragment { .. }+  where+  fragStart   = fromIntegral (view ip4FragmentOffset hdr)+  fragEnd     = fragStart + S.length body+  fragPayload = [body]+++-- | Create a buffer, given an expiration time, initial fragment, and+-- 'IP4Header' of that initial fragment. The initial header is included for the+-- case where the initial fragment is also the first fragment in the sequence.+mkBuffer :: UTCTime -> IP4Header -> Fragment -> Buffer+mkBuffer bufExpire hdr frag =+  addFragment hdr frag+  Buffer { bufHeader    = Nothing+         , bufSize      = Nothing+         , bufFragments = []+         , .. }+++-- | For use with HT.alter. When the first element is 'Just', the second will+-- be 'Nothing', indicating that the entry in the table should be updated, and+-- there's no result yet. When the first element is 'Nothing', the second will+-- be 'Just', indicating that the entry should be removed from the table, and+-- that this is the final buffer.+updateBuffer :: UTCTime -> IP4Header -> Fragment -> Maybe Buffer+               -> (Maybe Buffer,Maybe (IP4Header,S.ByteString))++-- the entry already exists in the table, removing it if it's full+updateBuffer _ hdr frag (Just buf) =+  let buf' = addFragment hdr frag buf+   in case bufFull buf' of+        Just res -> (Nothing, Just res)+        Nothing  -> (Just buf', Nothing)++-- create a new entry in the table+updateBuffer expire hdr frag Nothing =+  let buf = mkBuffer expire hdr frag+   in buf `seq` (Just buf, Nothing)+++-- | When the buffer is full and all fragments are accounted for, reassemble it+-- into a new packet.+bufFull :: Buffer -> Maybe (IP4Header,S.ByteString)+bufFull Buffer { .. }++  | Just size <- bufSize+  , Just hdr  <- bufHeader+  , [Fragment { .. }] <- bufFragments+  , fragEnd == size =+    Just (hdr, S.concat fragPayload)++  | otherwise =+    Nothing+++-- | Insert the fragment into the buffer.+addFragment :: IP4Header -> Fragment -> Buffer -> Buffer+addFragment hdr frag buf =+  Buffer { bufExpire    = bufExpire buf+         , bufSize      = size'+         , bufHeader    = case bufHeader buf of+                            Nothing | view ip4FragmentOffset hdr == 0 -> Just hdr+                            _                                         -> bufHeader buf++         , bufFragments = insertFragment (bufFragments buf)+         }++  where++  size' | view ip4MoreFragments hdr = bufSize buf+        | otherwise                 = Just $! fragEnd frag++  insertFragment frags@(f:fs)+    | fragEnd   frag == fragStart f = mergeFragment frag f : fs+    | fragStart frag == fragEnd f   = mergeFragment f frag : fs+    | fragStart frag <  fragStart f = frag : frags+    | otherwise                     = f : insertFragment fs++  insertFragment [] = [frag]+++mergeFragment :: Fragment -> Fragment -> Fragment+mergeFragment a b =+  Fragment { fragStart   = fragStart a+           , fragEnd     = fragEnd b+           , fragPayload = fragPayload a ++ fragPayload b+           }
+ src/Hans/IP4/Icmp4.hs view
@@ -0,0 +1,465 @@+{-# LANGUAGE RecordWildCards #-}++module Hans.IP4.Icmp4 where++import Hans.Checksum (computeChecksum)+import Hans.Device.Types (ChecksumOffload(..))+import Hans.IP4.Packet (IP4,getIP4,putIP4)+import Hans.Serialize (runPutPacket)++import           Control.Monad (unless,when,replicateM,liftM2)+import qualified Data.ByteString as S+import qualified Data.ByteString.Lazy as L+import           Data.Int (Int32)+import           Data.Serialize (Serialize(..))+import           Data.Serialize.Get+                     (Get,getWord8,getWord16be,getWord32be,getInt32be,label+                     ,getByteString,skip,remaining,isEmpty)+import           Data.Serialize.Put+                     (Put,Putter,putWord8,putWord16be,putWord32be,putByteString)+import           Data.Word (Word8,Word16,Word32)+++-- General ICMP Packets --------------------------------------------------------++type Lifetime = Word16++getLifetime :: Get Lifetime+getLifetime  = getWord16be++putLifetime :: Putter Lifetime+putLifetime  = putWord16be+++data Icmp4Packet+  -- RFC 792 - Internet Control Message Protocol+  = EchoReply !Identifier !SequenceNumber !S.ByteString+  | DestinationUnreachable !DestinationUnreachableCode !S.ByteString+  | SourceQuench !S.ByteString+  | Redirect !RedirectCode !IP4 !S.ByteString+  | Echo !Identifier !SequenceNumber !S.ByteString+  | RouterAdvertisement !Lifetime [RouterAddress]+  | RouterSolicitation+  | TimeExceeded !TimeExceededCode !S.ByteString+  | ParameterProblem !Word8 !S.ByteString+  | 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 (Eq,Show)++noCode :: String -> Get ()+noCode str = do+  code <- getWord8+  unless (code == 0)+      (fail (str ++ " expects code 0"))++getIcmp4Packet :: Get Icmp4Packet+getIcmp4Packet  = label "ICMP" $+  do ty <- getWord8++     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 of+       0  -> firstGet "Echo Reply" $ \ NoCode -> do+                ident    <- getIdentifier+                seqNum   <- getSequenceNumber+                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  <- getIP4+                dat      <- getByteString =<< remaining+                return $! Redirect code gateway dat++       8  -> firstGet "Echo" $ \ NoCode -> do+                ident    <- getIdentifier+                seqNum   <- getSequenceNumber+                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 <- getLifetime+                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    <- getIdentifier+                seqNum   <- getSequenceNumber+                origTime <- getWord32be+                recvTime <- getWord32be+                tranTime <- getWord32be+                return $! Timestamp ident seqNum origTime recvTime tranTime++       14 -> firstGet "Timestamp Reply" $ \ NoCode -> do+                ident    <- getIdentifier+                seqNum   <- getSequenceNumber+                origTime <- getWord32be+                recvTime <- getWord32be+                tranTime <- getWord32be+                return $! TimestampReply ident seqNum origTime recvTime tranTime++       15 -> firstGet "Information" $ \ NoCode -> do+                ident    <- getIdentifier+                seqNum   <- getSequenceNumber+                return $! Information ident seqNum++       16 -> firstGet "Information Reply" $ \ NoCode -> do+                ident    <- getIdentifier+                seqNum   <- getSequenceNumber+                return $! InformationReply ident seqNum++       17 -> firstGet "Address Mask" $ \ NoCode -> do+                ident    <- getIdentifier+                seqNum   <- getSequenceNumber+                skip 4   -- address mask+                return $! AddressMask ident seqNum++       18 -> firstGet "Address Mask Reply" $ \ NoCode -> do+                ident    <- getIdentifier+                seqNum   <- getSequenceNumber+                mask     <- getWord32be+                return $! AddressMaskReply ident seqNum mask++       30 -> firstGet "Trace Route" $ \ code -> do+                ident    <- getIdentifier+                skip 2   -- unused+                outHop   <- getWord16be+                retHop   <- getWord16be+                speed    <- getWord32be+                mtu      <- getWord32be+                return $! TraceRoute code ident outHop retHop speed mtu++       _ -> fail ("Unknown type: " ++ show ty)+++-- NOTE: we may want to parameterize this on the MTU of the device that will+-- send the message, as it could fragment.+renderIcmp4Packet :: ChecksumOffload -> Icmp4Packet -> L.ByteString+renderIcmp4Packet ChecksumOffload { .. } pkt+  | coIcmp4   = bytes+  | otherwise = L.take 2 bytes `L.append`+                    runPutPacket 2 100 (L.drop 4 bytes) (putWord16be cs)++  where++  -- best guess mtu+  mtu   = 1500 - 20++  bytes = runPutPacket mtu mtu L.empty (putIcmp4Packet pkt)+  cs    = computeChecksum bytes+++putIcmp4Packet :: Putter Icmp4Packet+putIcmp4Packet  = put'+  where+  firstPut :: Serialize a => Word8 -> a -> Put+  firstPut ty code+    = do putWord8 ty+         put code+         putWord16be 0++  put' (EchoReply ident seqNum dat)+    = do firstPut 0 NoCode+         putIdentifier ident+         putSequenceNumber seqNum+         putByteString dat++  put' (DestinationUnreachable code dat)+    = do firstPut 3 code+         putWord32be 0 -- unused+         putByteString dat++  put' (SourceQuench dat)+    = do firstPut 4 NoCode+         putWord32be 0 -- unused+         putByteString dat++  put' (Redirect code gateway dat)+    = do firstPut 5 code+         putIP4 gateway+         putByteString dat++  put' (Echo ident seqNum dat)+    = do firstPut 8 NoCode+         putIdentifier ident+         putSequenceNumber seqNum+         putByteString dat++  put' (RouterAdvertisement lifetime addrs)+    = do let len      = length addrs+             addrSize = 2++         when (len > 255)+             (fail "Too many routers in Router Advertisement")++         firstPut 9 NoCode+         putWord8 (fromIntegral len)+         putWord8 addrSize+         putLifetime lifetime+         mapM_ put addrs++  put' RouterSolicitation+    = do firstPut 10 NoCode+         putWord32be 0 -- RESERVED++  put' (TimeExceeded code dat)+    = do firstPut 11 code+         putWord32be 0 -- unused+         putByteString dat++  put' (ParameterProblem ptr dat)+    = do firstPut 12 NoCode+         put ptr+         putWord8    0 -- unused+         putWord16be 0 -- unused+         putByteString dat++  put' (Timestamp ident seqNum origTime recvTime tranTime)+    = do firstPut 13 NoCode+         putIdentifier     ident+         putSequenceNumber seqNum+         putWord32be       origTime+         putWord32be       recvTime+         putWord32be       tranTime++  put' (TimestampReply ident seqNum origTime recvTime tranTime)+    = do firstPut 14 NoCode+         putIdentifier     ident+         putSequenceNumber seqNum+         putWord32be       origTime+         putWord32be       recvTime+         putWord32be       tranTime++  put' (Information ident seqNum)+    = do firstPut 15 NoCode+         putIdentifier ident+         putSequenceNumber seqNum++  put' (InformationReply ident seqNum)+    = do firstPut 16 NoCode+         putIdentifier ident+         putSequenceNumber seqNum++  put' (AddressMask ident seqNum)+    = do firstPut 17 NoCode+         putIdentifier ident+         putSequenceNumber seqNum+         putWord32be 0 -- address mask++  put' (AddressMaskReply ident seqNum mask)+    = do firstPut 18 NoCode+         putIdentifier ident+         putSequenceNumber seqNum+         putWord32be mask++  put' (TraceRoute code ident outHop retHop speed mtu)+    = do firstPut 30 code+         putIdentifier ident+         putWord16be 0 -- unused+         putWord16be outHop+         putWord16be retHop+         putWord32be speed+         putWord32be 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+  | DestinationNetworkUnknown+  | DestinationHostUnknown+  | SourceHostIsolatedError+  | AdministrativelyProhibited+  | HostAdministrativelyProhibited+  | NetworkUnreachableForTOS+  | HostUnreachableForTOS+  | CommunicationAdministrativelyProhibited+  | HostPrecedenceViolation+  | PrecedenceCutoffInEffect+    deriving (Eq,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+             6  -> return DestinationNetworkUnknown+             7  -> return DestinationHostUnknown+             8  -> return SourceHostIsolatedError+             9  -> return AdministrativelyProhibited+             10 -> return HostAdministrativelyProhibited+             11 -> return NetworkUnreachableForTOS+             12 -> return HostUnreachableForTOS+             13 -> return CommunicationAdministrativelyProhibited+             14 -> return HostPrecedenceViolation+             15 -> return PrecedenceCutoffInEffect+             _  -> 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+  put DestinationNetworkUnknown               = putWord8 6+  put DestinationHostUnknown                  = putWord8 7+  put SourceHostIsolatedError                 = putWord8 8+  put AdministrativelyProhibited              = putWord8 9+  put HostAdministrativelyProhibited          = putWord8 10+  put NetworkUnreachableForTOS                = putWord8 11+  put HostUnreachableForTOS                   = putWord8 12+  put CommunicationAdministrativelyProhibited = putWord8 13+  put HostPrecedenceViolation                 = putWord8 14+  put PrecedenceCutoffInEffect                = putWord8 15++data TimeExceededCode+  = TimeToLiveExceededInTransit+  | FragmentReassemblyTimeExceeded+    deriving (Eq,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 (Eq,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 (Eq,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 -------------------------------------------------------++type PreferenceLevel = Int32++data RouterAddress = RouterAddress { raAddr            :: IP4+                                   , raPreferenceLevel :: PreferenceLevel+                                   } deriving (Eq,Show)++instance Serialize RouterAddress where+  get =+    do raAddr            <- getIP4+       raPreferenceLevel <- getInt32be+       return RouterAddress { .. }++  put RouterAddress { .. } =+    do putIP4 raAddr+       putWord32be (fromIntegral raPreferenceLevel)++type Identifier = Word16++getIdentifier :: Get Identifier+getIdentifier  = getWord16be+{-# INLINE getIdentifier #-}++putIdentifier :: Putter Identifier+putIdentifier  = putWord16be+{-# INLINE putIdentifier #-}++type SequenceNumber = Word16++getSequenceNumber :: Get SequenceNumber+getSequenceNumber  = getWord16be+{-# INLINE getSequenceNumber #-}++putSequenceNumber :: Putter SequenceNumber+putSequenceNumber  = putWord16be+{-# INLINE putSequenceNumber #-}++getUntilDone :: Serialize a => Get [a]+getUntilDone  =+  do empty <- isEmpty+     if empty then return []+              else liftM2 (:) get getUntilDone+
+ src/Hans/IP4/Input.hs view
@@ -0,0 +1,174 @@+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE PatternSynonyms #-}++module Hans.IP4.Input (+    processArp,+    processIP4,+    handleIP4,+  ) where++import Hans.Checksum (computeChecksum)+import Hans.Device (Device(..),ChecksumOffload(..),rxOffload)+import Hans.Ethernet (Mac,pattern ETYPE_ARP,sendEthernet)+import Hans.IP4.ArpTable (addEntry,lookupEntry)+import Hans.IP4.Fragments (processFragment)+import Hans.IP4.Icmp4 (Icmp4Packet(..),getIcmp4Packet)+import Hans.IP4.Output (queueIcmp4,portUnreachable)+import Hans.IP4.Packet+import Hans.IP4.RoutingTable (Route(..))+import Hans.Lens (view)+import Hans.Monad (Hans,io,dropPacket,escape,decode,decode')+import Hans.Network.Types+import Hans.Serialize (runPutPacket)+import Hans.Types+import Hans.Udp.Input (processUdp)+import Hans.Tcp.Input (processTcp)++import           Control.Monad (when,unless)+import qualified Data.ByteString as S+import qualified Data.ByteString.Lazy as L+++-- Arp Processing --------------------------------------------------------------++-- | Handle incoming Arp packets.+processArp :: NetworkStack -> Device -> S.ByteString -> Hans ()+processArp ns dev payload =+  do ArpPacket { .. } <- decode (devStats dev) getArpPacket payload++     -- if the entry already exists in the arp table, update it+     merge <- io (updateEntry ns arpSHA arpSPA)++     -- are we the target of the request?+     mb   <- io (isLocalAddr ns arpTPA)+     dev' <- case mb of+               Just route -> return (routeDevice route)+               Nothing    -> escape+     let lha = devMac dev'++     -- add the entry if it didn't already exist+     unless merge (io (addEntry (ip4ArpTable (view ip4State ns)) arpSPA arpSHA))++     -- respond if the packet was a who-has request for our mac+     when (arpOper == ArpRequest)+       $ io+       $ sendEthernet dev' arpSHA ETYPE_ARP+       $ runPutPacket 28 100 L.empty+       $ putArpPacket ArpPacket { arpSHA  = lha,    arpSPA = arpTPA+                                , arpTHA  = arpSHA, arpTPA = arpSPA+                                , arpOper = ArpReply }+++-- | Update an entry in the arp table, if it exists already.+updateEntry :: NetworkStack -> Mac -> IP4 -> IO Bool+updateEntry ns sha spa =+  do mb <- lookupEntry (ip4ArpTable (view ip4State ns)) spa+     case mb of++       Just _  -> do addEntry (ip4ArpTable (view ip4State ns)) spa sha+                     return True++       Nothing -> return False+++-- IP4 Processing --------------------------------------------------------------++-- | Process a packet that has arrived from a device.+processIP4 :: NetworkStack -> Device -> S.ByteString -> Hans ()+processIP4 ns dev payload =+  do ((hdr,hdrLen,bodyLen),body) <- decode' (devStats dev) getIP4Packet payload++     -- only validate the checkum if the device hasn't done that already+     let packetValid = coIP4 (view rxOffload dev)+                    || 0 == computeChecksum (S.take hdrLen payload)+     unless packetValid (dropPacket (devStats dev))++     -- Drop packets that weren't destined for an address that this device+     -- holds.+     -- XXX: if we ever want to use HaNS as a router, this would be where IP+     -- routing would happen+     checkDestination ns dev (ip4DestAddr hdr)++     handleIP4 ns dev (Just (hdrLen,payload)) hdr (S.take bodyLen body)+++-- | The processing stage after the packet has been decoded and validated. It's+-- exposed here so that routing to an address that's managed by the network+-- stack can skip the device layer.+handleIP4 :: NetworkStack -> Device -> Maybe (Int,S.ByteString)+          -> IP4Header -> S.ByteString -> Hans ()+handleIP4 ns dev mbOrig hdr body =+  do (IP4Header { .. },body') <-+         processFragment (ip4Fragments (view ip4State ns)) hdr body++     case ip4Protocol of+       PROT_ICMP4 -> processICMP ns dev ip4SourceAddr ip4DestAddr body'++       PROT_UDP   ->+         do routed <- processUdp ns dev ip4SourceAddr ip4DestAddr body'+            case (routed,mbOrig) of+              -- when we failed to route the datagram, and have the original+              -- message handy, send a portUnreachable message.+              (False,Just(ihl,orig)) -> io+                $ portUnreachable ns dev (SourceIP4 ip4DestAddr) ip4SourceAddr+                $ S.take (ihl + 8) orig++              -- otherwise, the packet was routed from an internal device, or a+              -- destination actually existed.+              _ -> return ()++       PROT_TCP   -> processTcp  ns dev ip4SourceAddr ip4DestAddr body'+       _          -> dropPacket (devStats dev)+++-- | Validate the destination of this packet.+checkDestination :: NetworkStack -> Device -> IP4 -> Hans ()++-- always accept broadcast messages+checkDestination _ _ BroadcastIP4 = return ()++-- require that the input device has the destination address+checkDestination ns dev dest =+  do mb <- io (isLocalAddr ns dest)+     case mb of+       Just Route { .. }+         | routeDevice == dev -> return ()++           -- A route was found, and it didn't involve the device the packet+           -- arrived on+         | otherwise          -> escape++       -- No route was found. Check to see if there are any routes for this+       -- device, and forward the packet on if there are none (in order to+       -- support things like unicast DHCP).+       Nothing ->+         do routes <- io (routesForDev ns dev)+            unless (null routes) escape+++++-- ICMP Processing -------------------------------------------------------------++-- | Process incoming ICMP packets.+processICMP :: NetworkStack -> Device -> IP4 -> IP4 -> S.ByteString -> Hans ()++processICMP ns dev src dst body =+  do let packetValid = coIcmp4 (view rxOffload dev) || 0 == computeChecksum body++     unless packetValid (dropPacket (devStats dev))++     msg <- decode (devStats dev) getIcmp4Packet body++     case msg of++       -- XXX: use the stats and config for the device that the message arrived+       -- on. As it's probably going out the same device, this seems OK, but it+       -- would be nice to confirm that.+       Echo ident seqNum bytes ->+         do io (queueIcmp4 ns dev (SourceIP4 dst) src (EchoReply ident seqNum bytes))+            escape+++       -- Drop all other messages for now+       _ -> escape
+ src/Hans/IP4/Output.hs view
@@ -0,0 +1,253 @@+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE PatternSynonyms #-}++module Hans.IP4.Output (+    sendIP4, queueIP4,+    prepareIP4,+    primSendIP4,+    responder,++    -- * ICMP4 Messages+    queueIcmp4,+    portUnreachable,+  ) where++import Hans.Checksum (computeChecksum)+import Hans.Config (config,Config(..))+import Hans.Device+           (Device(..),DeviceConfig(..),DeviceStats(..),updateError,statTX+           ,ChecksumOffload(..),txOffload,deviceConfig)+import Hans.Ethernet+           ( Mac,sendEthernet,pattern ETYPE_IPV4, pattern ETYPE_ARP+           , pattern BroadcastMac)+import Hans.IP4.ArpTable+           (lookupEntry,resolveAddr,QueryResult(..),markUnreachable+           ,writeChanStrategy)+import Hans.IP4.Icmp4+           (Icmp4Packet(..),DestinationUnreachableCode(..),renderIcmp4Packet)+import Hans.IP4.Packet+import Hans.IP4.RoutingTable (Route(..),routeSource,routeNextHop)+import Hans.Lens+import Hans.Network.Types+import Hans.Serialize (runPutPacket)+import Hans.Threads (forkNamed)+import Hans.Types++import           Control.Concurrent (threadDelay)+import qualified Control.Concurrent.BoundedChan as BC+import           Control.Monad (when,forever,unless)+import qualified Data.ByteString as S+import qualified Data.ByteString.Lazy as L+import           Data.Serialize.Put (putWord16be)++++responder :: NetworkStack -> IO ()+responder ns = forever $+  do req <- BC.readChan (ip4ResponderQueue (view ip4State ns))+     case req of++       Send mbSrc dst df prot payload ->+         do _ <- sendIP4 ns mbSrc dst df prot payload+            return ()++       Finish dev mac frames ->+            sendIP4Frames dev mac frames+++-- | Queue a message on the responder queue instead of attempting to send it+-- directly.+queueIP4 :: NetworkStack -> DeviceStats+         -> SendSource -> IP4 -> Bool -> NetworkProtocol -> L.ByteString+         -> IO ()+queueIP4 ns stats mbSrc dst df prot payload =+  do written <- BC.tryWriteChan (ip4ResponderQueue (view ip4State ns))+                    (Send mbSrc dst df prot payload)+     unless written (updateError statTX stats)+++-- | Send an IP4 packet to the given destination. If it's not possible to find a+-- route to the destination, return False.+sendIP4 :: NetworkStack -> SendSource -> IP4+        -> Bool -> NetworkProtocol -> L.ByteString+        -> IO Bool++-- A special case for when the sender knows that this is the right device and+-- source address. The routing table is still queried to find the next hop, and+-- if the route found doesn't use the device provided, the packets aren't sent.+sendIP4 ns (SourceDev dev src) dst df prot payload =+  do mbRoute <- lookupRoute4 ns dst+     case mbRoute of+       Just (_,next,dev') | devName dev == devName dev' ->+         do primSendIP4 ns dev src dst next df prot payload+            return True++       _ ->+         do updateError statTX (devStats dev)+            return False++-- sending from a specific device+sendIP4 ns (SourceIP4 src) dst df prot payload =+  do mbRoute <- isLocalAddr ns src+     case mbRoute of+       Just route ->+         do primSendIP4 ns (routeDevice route) (routeSource route)+                dst (routeNextHop dst route) df prot payload+            return True++       Nothing ->+            return False++-- find the right path out+sendIP4 ns SourceAny dst df prot payload =+  do mbRoute <- lookupRoute4 ns dst+     case mbRoute of+       Just (src,next,dev) -> do primSendIP4 ns dev src dst next df prot payload+                                 return True+       Nothing             -> return False+++prepareHeader :: NetworkStack -> IP4 -> IP4 -> Bool -> NetworkProtocol -> IO IP4Header+prepareHeader ns src dst df prot =+  do ident <- nextIdent ns+     return $! set ip4DontFragment df+               emptyIP4Header { ip4Ident      = ident+                              , ip4SourceAddr = src+                              , ip4DestAddr   = dst+                              , ip4Protocol   = prot+                              , ip4TimeToLive = cfgIP4InitialTTL (view config ns)+                              }+++-- | Prepare IP4 fragments to be sent.+prepareIP4 :: NetworkStack -> Device -> IP4 -> IP4 -> Bool -> NetworkProtocol+           -> L.ByteString+           -> IO [L.ByteString]+prepareIP4 ns dev src dst df prot payload =+  do hdr <- prepareHeader ns src dst df prot++     let DeviceConfig { .. } = devConfig dev++     return $ [ renderIP4Packet (view txOffload dev) h p+              | (h,p) <- splitPacket (fromIntegral dcMtu) hdr payload ]+++-- | Send an IP4 packet to the given destination. This assumes that routing has+-- already taken place, and that the source and destination addresses are+-- correct.+primSendIP4 :: NetworkStack -> Device -> IP4 -> IP4 -> IP4 -> Bool -> NetworkProtocol+             -> L.ByteString -> IO ()+primSendIP4 ns dev src dst next df prot payload+    -- when the source and next hop are the same, re-queue in the network stack+    -- after fragment reassembly+  | src == next =+    do hdr <- prepareHeader ns src dst df prot+       _   <- BC.tryWriteChan (nsInput ns) $! FromIP4 dev hdr (L.toStrict payload)+       -- don't write any stats for packets that skip the device layer+       return ()++    -- the packet is leaving the network stack so encode it and send+  | otherwise =+    do packets <- prepareIP4 ns dev src dst df prot payload+       arpOutgoing ns dev src next packets+++-- | Retrieve the outgoing address for this IP4 packet, and send along all+-- fragments.+arpOutgoing :: NetworkStack -> Device -> IP4 -> IP4 -> [L.ByteString] -> IO ()+arpOutgoing _ dev _ BroadcastIP4 packets =+    sendIP4Frames dev BroadcastMac packets++arpOutgoing ns dev src next packets =+  do res <- resolveAddr (ip4ArpTable (view ip4State ns)) next queueSend+     case res of+       Known dstMac ->+         sendIP4Frames dev dstMac packets++       -- The mac wasn't present in the table. If this was the first request for+       -- this address, start a request thread.+       Unknown newRequest () ->+         when newRequest $ do _ <- forkNamed "arpRequestThread"+                                       (arpRequestThread ns dev src next)+                              return ()++  where++  queueSend =+    writeChanStrategy (Just (devStats dev)) mkFinish+        (ip4ResponderQueue (view ip4State ns))++  mkFinish mbMac =+    do dstMac <- mbMac+       return $! Finish dev dstMac packets+++sendIP4Frames :: Device -> Mac -> [L.ByteString] -> IO ()+sendIP4Frames dev dstMac packets =+  mapM_ (sendEthernet dev dstMac ETYPE_IPV4) packets+++-- | Make an Arp request for the given IP address, until the maximum retries+-- have been exhausted, or the entry made it into the table.+arpRequestThread :: NetworkStack -> Device -> IP4 -> IP4 -> IO ()+arpRequestThread ns dev src dst = loop 0+  where+  IP4State { ..} = view ip4State ns++  request = renderArpPacket ArpPacket { arpOper   = ArpRequest+                                      , arpSHA    = devMac dev+                                      , arpSPA    = src+                                      , arpTHA    = BroadcastMac+                                      , arpTPA    = dst+                                      }++  loop n =+    do sendEthernet dev BroadcastMac ETYPE_ARP request+       threadDelay ip4ArpRetryDelay++       mb <- lookupEntry ip4ArpTable dst+       case mb of+         Just{}                    -> return ()+         Nothing | n < ip4ArpRetry -> loop (n + 1)+                 | otherwise       -> markUnreachable ip4ArpTable dst+++-- | The final step to render an IP header and its payload out as a lazy+-- 'ByteString'. Compute the checksum over the packet with its checksum zeroed,+-- then reconstruct a new lazy 'ByteString' that contains chunks from the old+-- header, and the new checksum.+renderIP4Packet :: ChecksumOffload -> IP4Header -> L.ByteString -> L.ByteString+renderIP4Packet ChecksumOffload { .. } hdr pkt+  | coIP4     = bytes `L.append` pkt+  | otherwise = withChecksum+  where++  pktlen    = L.length pkt++  bytes     = runPutPacket 20 40 pkt (putIP4Header hdr (fromIntegral pktlen))+  cs        = computeChecksum (L.take (L.length bytes - pktlen) bytes)++  beforeCS  = L.take 10 bytes+  afterCS   = L.drop 12 bytes+  csBytes   = runPutPacket 2 100 afterCS (putWord16be cs)++  withChecksum = beforeCS `L.append` csBytes+++-- ICMP Messages ---------------------------------------------------------------++queueIcmp4 :: NetworkStack -> Device -> SendSource -> IP4 -> Icmp4Packet+           -> IO ()+queueIcmp4 ns dev src dst pkt =+  let msg = renderIcmp4Packet (view txOffload dev) pkt+      df  = fromIntegral (L.length msg) < dcMtu (view deviceConfig dev) - 20+   in queueIP4 ns (devStats dev) src dst df PROT_ICMP4 msg++-- | Emit a destination unreachable ICMP message. This will always be queued via+-- the responder queue, as it is most likely coming from the fast path. The+-- bytestring argument is assumed to be the original IP4 datagram, trimmed to+-- IP4 header + 8 bytes of data.+portUnreachable :: NetworkStack -> Device -> SendSource -> IP4 -> S.ByteString+                -> IO ()+portUnreachable ns dev src dst chunk =+  queueIcmp4 ns dev src dst (DestinationUnreachable PortUnreachable chunk)
+ src/Hans/IP4/Packet.hs view
@@ -0,0 +1,486 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveGeneric #-}++module Hans.IP4.Packet where++import Hans.Checksum+           (Checksum(..),PartialChecksum,Pair8(..),emptyPartialChecksum)+import Hans.Ethernet (Mac,getMac,putMac,pattern ETYPE_IPV4)+import Hans.Lens as L+import Hans.Network.Types (NetworkProtocol)+import Hans.Serialize (runPutPacket)++import           Control.Monad (unless,guard)+import           Data.Bits as B+                     ((.|.),(.&.),testBit,shiftL,shiftR,bit,setBit,bit+                     ,complement)+import qualified Data.ByteString.Short as Sh+import qualified Data.ByteString.Lazy as L+import           Data.Hashable (Hashable)+import           Data.Int (Int64)+import           Data.Serialize+                    (Get,getWord8,getWord16be,getWord32be,getShortByteString+                    ,label,isolate,Serialize(..)+                    ,Putter,Put,putWord8,putWord16be,putWord32be+                    ,putLazyByteString,putShortByteString)+import           Data.Typeable (Typeable)+import           Data.Word (Word8,Word16,Word32)+import           GHC.Generics (Generic)+import           Numeric (readDec)+++-- IP4 Addresses ---------------------------------------------------------------++newtype IP4 = IP4 Word32+              deriving (Eq,Ord,Show,Read,Hashable,Checksum,Typeable,Generic)++instance Serialize IP4 where+  get = getIP4+  put = putIP4+  {-# INLINE get #-}+  {-# INLINE put #-}++getIP4 :: Get IP4+getIP4  =+  do w <- getWord32be+     return (IP4 w)++putIP4 :: Putter IP4+putIP4 (IP4 w) = putWord32be w++packIP4 :: Word8 -> Word8 -> Word8 -> Word8 -> IP4+packIP4 a b c d = IP4 $! set (byte 3) a+                      $! set (byte 2) b+                      $! set (byte 1) c+                      $! set (byte 0) d 0+{-# INLINE packIP4 #-}+++unpackIP4 :: IP4 -> (Word8,Word8,Word8,Word8)+unpackIP4 (IP4 w) = ( view (byte 3) w+                    , view (byte 2) w+                    , view (byte 1) w+                    , view (byte 0) w+                    )+{-# INLINE unpackIP4 #-}++showIP4 :: IP4 -> ShowS+showIP4 ip4 =+  let (a,b,c,d) = unpackIP4 ip4+   in shows a . showChar '.' .+      shows b . showChar '.' .+      shows c . showChar '.' .+      shows d+{-# INLINE showIP4 #-}++readIP4 :: ReadS IP4+readIP4 str =+  do (a,'.':rest1) <- readDec str+     (b,'.':rest2) <- readDec rest1+     (c,'.':rest3) <- readDec rest2+     (d,rest4)     <- readDec rest3+     return (packIP4 a b c d, rest4)+{-# INLINE readIP4 #-}+++pattern BroadcastIP4 = IP4 0xffffffff++pattern CurrentNetworkIP4 = IP4 0x0++pattern WildcardIP4 = IP4 0x0+++-- IP4 Masks -------------------------------------------------------------------++data IP4Mask = IP4Mask {-# UNPACK #-} !IP4+                       {-# UNPACK #-} !Int -- ^ Between 0 and 32+               deriving (Show,Read)++instance Eq IP4Mask where+  m1 == m2 = maskBits m1 == maskBits m2+          && clearHostBits m1 == clearHostBits m2++hostmask :: Int -> Word32+hostmask bits = B.bit (32 - bits) - 1++netmask :: Int -> Word32+netmask bits = complement (hostmask bits)++maskRange :: IP4Mask -> (IP4,IP4)+maskRange mask = (clearHostBits mask, setHostBits mask)++maskBits :: IP4Mask -> Int+maskBits (IP4Mask _ bits) = bits++maskAddr :: IP4Mask -> IP4+maskAddr (IP4Mask addr _) = addr++clearHostBits :: IP4Mask -> IP4+clearHostBits (IP4Mask (IP4 addr) bits)= IP4 (addr .&. netmask bits)++setHostBits :: IP4Mask -> IP4+setHostBits (IP4Mask (IP4 addr) bits) = IP4 (addr .|. hostmask bits)++broadcastAddress :: IP4Mask -> IP4+broadcastAddress  = setHostBits++readIP4Mask :: ReadS IP4Mask+readIP4Mask str =+  do (addr,'/':rest1) <- readIP4 str+     (bits,rest2)     <- readDec rest1+     return (IP4Mask addr bits, rest2)++showIP4Mask :: IP4Mask -> ShowS+showIP4Mask (IP4Mask addr bits) = showIP4 addr . showChar '/' . shows bits+++-- IP4 Pseudo Header -----------------------------------------------------------++-- 0      7 8     15 16    23 24    31 +-- +--------+--------+--------+--------++-- |          source address           |+-- +--------+--------+--------+--------++-- |        destination address        |+-- +--------+--------+--------+--------++-- |  zero  |protocol|     length      |+-- +--------+--------+--------+--------++ip4PseudoHeader :: IP4 -> IP4 -> NetworkProtocol -> Int -> PartialChecksum+ip4PseudoHeader src dst prot len =+  extendChecksum (fromIntegral len :: Word16) $+  extendChecksum (Pair8 0 prot)               $+  extendChecksum dst                          $+  extendChecksum src emptyPartialChecksum+++-- IP4 Packets -----------------------------------------------------------------++type IP4Ident = Word16+++data IP4Header = IP4Header+  { ip4TypeOfService  :: {-# UNPACK #-} !Word8+  , ip4Ident          :: {-# UNPACK #-} !IP4Ident+  , ip4Fragment_      :: {-# UNPACK #-} !Word16+    -- ^ This includes the flags, and the fragment offset.+  , ip4TimeToLive     :: {-# UNPACK #-} !Word8+  , ip4Protocol       :: {-# UNPACK #-} !NetworkProtocol+  , ip4Checksum       :: {-# UNPACK #-} !Word16+  , ip4SourceAddr     :: {-# UNPACK #-} !IP4+  , ip4DestAddr       :: {-# UNPACK #-} !IP4+  , ip4Options        :: ![IP4Option]+  } deriving (Eq,Show)++emptyIP4Header :: IP4Header+emptyIP4Header  = IP4Header+  { ip4TypeOfService  = 0+  , ip4Ident          = 0+  , ip4Fragment_      = 0+  , ip4TimeToLive     = 127+  , ip4Protocol       = 0+  , ip4Checksum       = 0+  , ip4SourceAddr     = IP4 0+  , ip4DestAddr       = IP4 0+  , ip4Options        = []+  }++ip4DCSP :: Lens' IP4Header Word8+ip4DCSP f IP4Header { .. } =+  fmap (\ w -> IP4Header { ip4TypeOfService = ip4TypeOfService .|. (w `shiftL` 2), .. })+       (f (ip4TypeOfService `shiftR` 2))+{-# INLINE ip4DCSP #-}++ip4ECN :: Lens' IP4Header Word8+ip4ECN f IP4Header { .. } =+  fmap (\ w -> IP4Header { ip4TypeOfService = ip4TypeOfService .|. (w .&. 0x3), .. })+       (f (ip4TypeOfService .&. 0x3))+{-# INLINE ip4ECN #-}++ip4Fragment :: Lens' IP4Header Word16+ip4Fragment f IP4Header { .. } =+  fmap (\flags' -> IP4Header { ip4Fragment_ = flags', .. }) (f ip4Fragment_)+{-# INLINE ip4Fragment #-}++ip4DontFragment :: Lens' IP4Header Bool+ip4DontFragment  = ip4Fragment . L.bit 14+{-# INLINE ip4DontFragment #-}++ip4MoreFragments :: Lens' IP4Header Bool+ip4MoreFragments  = ip4Fragment . L.bit 13+{-# INLINE ip4MoreFragments #-}++-- | The fragment offset, in bytes.+ip4FragmentOffset :: Lens' IP4Header Word16+ip4FragmentOffset  = ip4Fragment . lens f g+  where+  f frag     = (frag .&. 0x1fff) `shiftL` 3+  g frag len = (frag .&. complement 0x1fff)+           .|. ((len `shiftR` 3) .&. 0x1fff)+{-# INLINE ip4FragmentOffset #-}+++noMoreFragments :: IP4Header -> IP4Header+noMoreFragments  = set ip4MoreFragments False++moreFragments :: IP4Header -> IP4Header+moreFragments  = set ip4MoreFragments True++addOffset :: Word16 -> IP4Header -> IP4Header+addOffset off = over ip4FragmentOffset (+ off)++setIdent :: IP4Ident -> IP4Header -> IP4Header+setIdent i hdr = hdr { ip4Ident = i }+++-- | Calculate the size of an IP4 packet+ip4PacketSize :: IP4Header -> L.ByteString -> Int+ip4PacketSize hdr bs =+  ip4HeaderSize hdr + fromIntegral (L.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 -> IP4Header -> L.ByteString -> [(IP4Header,L.ByteString)]+splitPacket mtu hdr bs+  | ip4PacketSize hdr bs <= mtu = [(hdr,bs)]+  | otherwise                   = fragmentPacket (fromIntegral mtu) hdr bs+++-- | Given a fragment size and a packet, fragment the packet into multiple+-- smaller ones.+fragmentPacket :: Int64 -> IP4Header -> L.ByteString+               -> [(IP4Header,L.ByteString)]+fragmentPacket mtu0 hdr0 = loop hdr0+  where+  mtu = mtu0 - fromIntegral (ip4HeaderSize hdr0)++  loop hdr bs+    | payloadLen <= mtu = [(noMoreFragments hdr, bs)]+    | otherwise         = frag : loop hdr' rest+    where+    payloadLen = L.length bs+    (as,rest)  = L.splitAt mtu bs+    alen       = fromIntegral (L.length as)+    hdr'       = addOffset alen hdr+    frag       = (moreFragments hdr, as)+++-- | Compute the value of the version/header length byte.+ip4VersionIHL :: Int -> Word8+ip4VersionIHL ihl = 4 `shiftL` 4 .|. fromIntegral (ihl `shiftR` 2)+++--  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                        |+-- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-++getIP4Packet :: Get (IP4Header, Int, Int)+getIP4Packet  = label "IP4 Header" $ do+  do b0 <- getWord8+     let ver = b0 `shiftR` 4+     unless (ver == 4) (fail "Invalid version")++     let ihl = fromIntegral ((b0 .&. 0xf) * 4)+     isolate (ihl - 1) $+       do ip4TypeOfService   <- getWord8+          payloadLen         <- getWord16be+          ip4Ident           <- getWord16be+          ip4Fragment_       <- getWord16be+          ip4TimeToLive      <- getWord8+          ip4Protocol        <- getWord8+          ip4Checksum        <- getWord16be+          ip4SourceAddr      <- getIP4+          ip4DestAddr        <- getIP4+          let optlen = ihl - 20+          ip4Options <-+              label "IP4 Options" $ isolate optlen+                                  $ getIP4Options optlen++          let hdr = IP4Header { .. }+          hdr `seq` return (hdr, ihl, fromIntegral payloadLen - ihl)++putIP4Header :: IP4Header -> Int -> Put+putIP4Header IP4Header { .. } pktlen = do+  let (optbs,optlen) = renderIP4Options ip4Options+  let ihl            = 20 + optlen+  putWord8    (ip4VersionIHL ihl)+  putWord8     ip4TypeOfService++  putWord16be (fromIntegral (pktlen + ihl))+  putWord16be ip4Ident+  putWord16be ip4Fragment_++  putWord8    ip4TimeToLive+  putWord8    ip4Protocol+  putWord16be 0 -- checksum++  putIP4 ip4SourceAddr+  putIP4 ip4DestAddr++  putLazyByteString optbs+++-- IP4 Options -----------------------------------------------------------------++renderIP4Options :: [IP4Option] -> (L.ByteString,Int)+renderIP4Options []   = (L.empty,0)+renderIP4Options opts =+  case optlen `mod` 4 of+    0 -> (optbs,optlen)++    -- pad with no-ops+    n -> (optbs `L.append` L.replicate (fromIntegral n) 0x1, optlen + n)+  where+  optbs  = runPutPacket 40 100 L.empty (mapM_ putIP4Option opts)+  optlen = fromIntegral (L.length optbs)+++getIP4Options :: Int -> Get [IP4Option]+getIP4Options len+  | len <= 0  = return []+  | otherwise = do o    <- getIP4Option+                   rest <- getIP4Options (len - ip4OptionSize o)+                   return $! (o : rest)+++data IP4Option = IP4Option+  { ip4OptionCopied :: !Bool+  , ip4OptionClass  :: {-# UNPACK #-} !Word8+  , ip4OptionNum    :: {-# UNPACK #-} !Word8+  , ip4OptionData   :: {-# UNPACK #-} !Sh.ShortByteString+  } deriving (Eq,Show)+++ip4OptionSize :: IP4Option -> Int+ip4OptionSize opt = case ip4OptionNum opt of+  0 -> 1+  1 -> 1+  _ -> 2 + fromIntegral (Sh.length (ip4OptionData opt))+++getIP4Option :: Get IP4Option+getIP4Option =+  do b <- getWord8+     let ip4OptionCopied = testBit b 7+     let ip4OptionClass  = (b `shiftR` 5) .&. 0x3+     let ip4OptionNum    = b .&. 0x1f++     ip4OptionData <-+       if ip4OptionNum < 2+          then return Sh.empty+          else do len <- getWord8+                  unless (len >= 2) (fail "Option length parameter is to small")+                  getShortByteString (fromIntegral (len - 2))++     return $! IP4Option { .. }+++ip4OptionType :: Bool -> Word8 -> Word8 -> Word8+ip4OptionType copied cls num =+  copiedFlag ((cls .&. 0x3 `shiftL` 5) .|. (num .&. 0x1f))+  where+  copiedFlag | copied    = (`setBit` 7)+             | otherwise = id++putIP4Option :: Putter IP4Option+putIP4Option IP4Option { .. } =+  do let copied | ip4OptionCopied = B.bit 7+                | otherwise       = 0++     putWord8 $ copied .|. ((ip4OptionClass .&. 0x3) `shiftL` 5)+                       .|.  ip4OptionNum    .&. 0x1f++     case ip4OptionNum of+       0 -> return ()+       1 -> return ()+       _ -> do putWord8 (fromIntegral (Sh.length ip4OptionData + 2))+               putShortByteString ip4OptionData+++-- Arp Packets -----------------------------------------------------------------++-- | Arp packets, specialized to IP4 and Mac addresses.+data ArpPacket = ArpPacket { arpOper   :: {-# UNPACK #-} !ArpOper+                           , arpSHA    :: !Mac+                           , arpSPA    :: !IP4+                           , arpTHA    :: !Mac+                           , arpTPA    :: !IP4+                           } deriving (Eq,Show)++-- | Parse an Arp packet, given a way to parse hardware and protocol addresses.+getArpPacket :: Get ArpPacket+getArpPacket  = label "ArpPacket" $+  do hwtype <- getWord16be+     ptype  <- getWord16be+     hwlen  <- getWord8+     plen   <- getWord8++     -- make sure that this packet is specialized to IP4/Ethernet+     guard $ hwtype == 0x1        && hwlen == 6+          && ptype  == ETYPE_IPV4 && plen  == 4++     arpOper   <- getArpOper++     arpSHA    <- getMac+     arpSPA    <- getIP4++     arpTHA    <- getMac+     arpTPA    <- getIP4++     return ArpPacket { .. }+++renderArpPacket :: ArpPacket -> L.ByteString+renderArpPacket pkt = runPutPacket 28 100 L.empty (putArpPacket pkt)++-- | Render an Arp packet, given a way to render hardware and protocol+-- addresses.+putArpPacket :: Putter ArpPacket+putArpPacket ArpPacket { .. } =+  do putWord16be   0x1+     putWord16be   ETYPE_IPV4+     putWord8      6+     putWord8      4++     putArpOper    arpOper++     putMac        arpSHA+     putIP4        arpSPA++     putMac        arpTHA+     putIP4        arpTPA+++-- Arp Opcodes -----------------------------------------------------------------++type ArpOper = Word16++pattern ArpRequest = 0x1+pattern ArpReply   = 0x2++-- | Parse an Arp operation.+getArpOper :: Get ArpOper+getArpOper  =+  do w <- getWord16be+     guard (w == ArpRequest || w == ArpReply)+     return w+{-# INLINE getArpOper #-}++-- | Render an Arp operation.+putArpOper :: Putter ArpOper+putArpOper  = putWord16be+{-# INLINE putArpOper #-}
+ src/Hans/IP4/RoutingTable.hs view
@@ -0,0 +1,139 @@+{-# LANGUAGE RecordWildCards #-}++module Hans.IP4.RoutingTable (+    Route(..), RouteType(..),+    routeSource, routeNextHop,+    RoutingTable,+    empty,+    addRule,+    deleteRule,+    lookupRoute,+    isLocal,+    getRoutes,+    routesForDev,+  ) where++import Hans.Device.Types (Device)+import Hans.IP4.Packet++import Control.Monad (guard)+import Data.Bits ((.&.))+import Data.List (insertBy)+import Data.Maybe (mapMaybe)+import Data.Word (Word32)+++data RouteType = Direct+               | Indirect !IP4+               | Loopback++data Route = Route { routeNetwork :: {-# UNPACK #-} !IP4Mask+                   , routeType    ::                !RouteType+                   , routeDevice  ::                !Device+                   }++routeSource :: Route -> IP4+routeSource Route { routeNetwork = IP4Mask addr _ } = addr++routeNextHop :: IP4 -> Route -> IP4+routeNextHop dest route =+  case routeType route of+    Direct           -> dest+    Indirect nextHop -> nextHop+    Loopback         -> routeSource route+++data Rule = Rule { ruleMask   :: {-# UNPACK #-} !Word32+                 , rulePrefix :: {-# UNPACK #-} !Word32+                 , ruleRoute  ::                !Route+                 }++ruleMaskLen :: Rule -> Int+ruleMaskLen rule = maskBits (routeNetwork (ruleRoute rule))++ruleSource :: Rule -> IP4+ruleSource rule = maskAddr (routeNetwork (ruleRoute rule))++ruleDevice :: Rule -> Device+ruleDevice rule = routeDevice (ruleRoute rule)++mkRule :: Route -> Rule+mkRule ruleRoute = Rule { .. }+  where+  IP4Mask (IP4 w) bits = routeNetwork ruleRoute++  ruleMask             = netmask bits+  rulePrefix           = ruleMask .&. w++routesTo :: Rule -> IP4 -> Bool+routesTo Rule { .. } (IP4 addr) = addr .&. ruleMask == rulePrefix++-- | Simple routing.+data RoutingTable = RoutingTable { rtRules :: [Rule]+                                   -- ^ Insertions must keep this list ordered+                                   -- by the network prefix length, descending.++                                 , rtDefault :: !(Maybe Route)+                                   -- ^ Optional default route.+                                 }++empty :: RoutingTable+empty  = RoutingTable { rtRules = [], rtDefault = Nothing }++getRoutes :: RoutingTable -> [Route]+getRoutes RoutingTable { .. } = map ruleRoute rtRules++addRule :: Bool -> Route -> RoutingTable -> RoutingTable+addRule isDefault route RoutingTable { .. } =+  rule `seq`+    RoutingTable { rtRules   = insertBy maskLenDesc rule rtRules+                 , rtDefault = if isDefault+                                  then Just route+                                  else rtDefault+                 }++  where++  -- compare b to a, to get descending order+  maskLenDesc a b = compare (ruleMaskLen b) (ruleMaskLen a)++  rule = mkRule route++deleteRule :: IP4Mask -> RoutingTable -> RoutingTable+deleteRule mask RoutingTable { .. } =+  rules' `seq` def' `seq` RoutingTable { rtRules = rules', rtDefault = def' }++  where++  rules' =+    do rule <- rtRules+       guard (routeNetwork (ruleRoute rule) /= mask)+       return rule++  def' =+    case rtDefault of+      Just Route { .. } | routeNetwork == mask -> Nothing+      _                                        -> rtDefault++lookupRoute :: IP4 -> RoutingTable -> Maybe Route+lookupRoute dest RoutingTable { .. } = foldr findRoute rtDefault rtRules+  where+  findRoute rule continue+    | rule `routesTo` dest = Just (ruleRoute rule)+    | otherwise            = continue++-- | If the address given is the source address for a rule in the table, return+-- the associated 'Device'.+isLocal :: IP4 -> RoutingTable -> Maybe Route+isLocal addr RoutingTable { .. } = foldr hasSource Nothing rtRules+  where+  hasSource rule continue+    | ruleSource rule == addr = Just (ruleRoute rule)+    | otherwise               = continue++-- | Give back routes that involve this device.+routesForDev :: Device -> RoutingTable -> [Route]+routesForDev dev RoutingTable { .. } = mapMaybe usesDev rtRules+  where+  usesDev rule | ruleDevice rule == dev = Just (ruleRoute rule)+               | otherwise              = Nothing
+ src/Hans/IP4/State.hs view
@@ -0,0 +1,131 @@+{-# LANGUAGE RecordWildCards #-}++module Hans.IP4.State (+    IP4State(..),+    SendSource(..),+    ResponderRequest(..),+    newIP4State,+    HasIP4State(..),+    addRoute,+    lookupRoute4,+    isLocalAddr,+    nextIdent,+    routesForDev,+  ) where++import           Hans.Config (Config(..))+import           Hans.Device.Types (Device(..))+import           Hans.Ethernet (Mac)+import           Hans.IP4.ArpTable (ArpTable,newArpTable)+import           Hans.IP4.Fragments (FragTable,newFragTable)+import           Hans.IP4.Packet (IP4,IP4Ident)+import qualified Hans.IP4.RoutingTable as RT+import           Hans.Lens+import           Hans.Network.Types (NetworkProtocol)+++import qualified Control.Concurrent.BoundedChan as BC+import qualified Data.ByteString.Lazy as L+import           Data.IORef (IORef,newIORef,atomicModifyIORef',readIORef)+import           System.Random (StdGen,newStdGen,Random(random))+++-- IP4 State -------------------------------------------------------------------++data SendSource = SourceAny+                  -- ^ Any interface that will route the message++                | SourceIP4 !IP4+                  -- ^ The interface with this address++                | SourceDev !Device !IP4+                  -- ^ This device with this source address+++data ResponderRequest = Finish !Device !Mac [L.ByteString]+                        -- ^ Finish sending these IP4 packets++                      | Send !SendSource !IP4 !Bool !NetworkProtocol L.ByteString+                       -- ^ Send this IP4 payload to this address+++data IP4State = IP4State { ip4Routes :: !(IORef RT.RoutingTable)+                           -- ^ Addresses currently assigned to devices.++                         , ip4ArpTable :: !ArpTable+                           -- ^ The ARP cache.++                         , ip4Fragments :: !FragTable+                           -- ^ IP4 packet fragments++                         , ip4ArpRetry :: {-# UNPACK #-} !Int+                           -- ^ Arp retry count++                         , ip4ArpRetryDelay :: {-# UNPACK #-} !Int++                         , ip4ResponderQueue :: !(BC.BoundedChan ResponderRequest)++                         , ip4RandomSeed :: !(IORef StdGen)+                         }++newIP4State :: Config -> IO IP4State+newIP4State cfg =+  do ip4Routes         <- newIORef RT.empty+     ip4ArpTable       <- newArpTable cfg+     ip4Fragments      <- newFragTable cfg+     ip4ResponderQueue <- BC.newBoundedChan 32+     ip4RandomSeed     <- newIORef =<< newStdGen+     return IP4State { ip4ArpRetry      = cfgArpRetry cfg+                     , ip4ArpRetryDelay = cfgArpRetryDelay cfg * 1000+                     , .. }++class HasIP4State state where+  ip4State :: Getting r state IP4State++instance HasIP4State IP4State where+  ip4State = id+  {-# INLINE ip4State #-}+++addRoute :: HasIP4State state => state -> Bool -> RT.Route -> IO ()+addRoute state = \ defRoute route ->+  atomicModifyIORef' ip4Routes (\ table -> (RT.addRule defRoute route table, ()))+  where+  IP4State { .. } = view ip4State state+++-- | Lookup the source address, as well as the next hop and device.+lookupRoute4 :: HasIP4State state => state -> IP4 -> IO (Maybe (IP4,IP4,Device))+lookupRoute4 state = \ dest ->+  do routes <- readIORef ip4Routes+     case RT.lookupRoute dest routes of+       Just route -> return (Just ( RT.routeSource route+                                  , RT.routeNextHop dest route+                                  , RT.routeDevice route))+       Nothing    -> return Nothing+  where+  IP4State { .. } = view ip4State state+++-- | Is this an address that's assigned to a device in the network stack?+isLocalAddr :: HasIP4State state => state -> IP4 -> IO (Maybe RT.Route)+isLocalAddr state = \ dst ->+  do rt <- readIORef ip4Routes+     return $! RT.isLocal dst rt+  where+  IP4State { .. } = view ip4State state+++-- | Give back the result of using the 'random' function on the internal state.+nextIdent :: HasIP4State state => state -> IO IP4Ident+nextIdent state =+  atomicModifyIORef' ip4RandomSeed (\g -> case random g of (a,g') -> (g',a) )+  where+  IP4State { .. } = view ip4State state+++-- | Give back the list of routing rules associated with this device.+routesForDev :: HasIP4State state => state -> Device -> IO [RT.Route]+routesForDev state dev =+  do routes <- readIORef (ip4Routes (view ip4State state))+     return $! RT.routesForDev dev routes
+ src/Hans/Input.hs view
@@ -0,0 +1,48 @@+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE PatternSynonyms #-}++module Hans.Input where++import Hans.Device (Device(..))+import Hans.Ethernet+import Hans.IP4.Input (processArp,processIP4,handleIP4)+import Hans.Monad (Hans,runHans,dropPacket,io,escape,decode')+import Hans.Types (NetworkStack(..),InputPacket(..))++import           Control.Concurrent.BoundedChan (readChan)+import           Control.Monad (unless)+import qualified Data.ByteString as S+++-- Incoming Packets ------------------------------------------------------------++-- | Handle incoming packets.+processPackets :: NetworkStack -> IO ()+processPackets ns = runHans $+  do input <- io (readChan (nsInput ns))+     case input of+       FromDevice dev pkt   -> processEthernet ns dev pkt+       FromIP4 dev hdr body -> handleIP4 ns dev Nothing hdr body+++processEthernet :: NetworkStack -> Device -> S.ByteString -> Hans ()+processEthernet ns dev bytes =+  do (hdr,payload) <- decode' (devStats dev) getEthernetHeader bytes++     -- XXX at some point, we should extend this to support multicast+     let validFrame = eDest hdr == BroadcastMac+                   || eDest hdr == devMac dev++     -- XXX should we increment a stat here?+     unless validFrame escape++     case eType hdr of++       ETYPE_IPV4 ->+         processIP4 ns dev payload++       ETYPE_ARP ->+         processArp ns dev payload++       _ ->+         dropPacket (devStats dev)
− src/Hans/Layer.hs
@@ -1,163 +0,0 @@-{-# 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),BaseM(inBase))-import qualified Control.Exception as X-import qualified Data.Map.Strict 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-  | Exit !(LayerState i) !Action-  | Result !(LayerState i) !a !Action---- | Early exit continuation-type Exit i r = LayerState i -> Action -> Result i r---- | 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-                       -> Exit i r-                       -> 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 Exit Error success-  where success a i o = Result i a o--loopLayer :: String -> i -> IO msg -> (msg -> Layer i ()) -> IO ()-loopLayer name i0 msg k =-  loop (LayerState 0 i0) `X.finally` putStrLn (name ++ " died")-  where-  loop i = do-    a   <- msg-    now <- getPOSIXTime-    let res = (runLayer $! i {lsNow = now }) (k a)-    _ <- X.evaluate res `X.catch` \ se -> do-           putStrLn (name ++ show (se :: X.SomeException))-           return res-    case res of-      Error        m -> runAction m >> loop i-      Exit i' m      -> runAction m >> loop i'-      Result i' () m -> runAction m >> loop i'--instance Functor (Layer i) where-  fmap g m = Layer $ \i0 o0 x f k ->-                     getLayer m i0 o0 x 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 x f k -> getLayer a i o x (\_ -> getLayer b i o x f k) k)--instance Monad (Layer i) where-  return a = Layer (\i o _ _ k -> k a i o)-  m >>= g = Layer $ \i0 o0 x f k -> getLayer m i0 o0 x f $ \a i o ->-                                    getLayer (g a) i o x 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)--instance BaseM (Layer i) (Layer i) where-  inBase = id----- Utilities ----------------------------------------------------------------------- | Finish early, successfully, with no further processing.-finish :: Layer i a-finish  = Layer (\i o x _ _ -> x i o)-{-# INLINE finish #-}--{-# INLINE dropPacket #-}-dropPacket :: Layer i a-dropPacket  = finish--{-# INLINE time #-}-time :: Layer i POSIXTime-time  = Layer (\i o _ _ k -> (k $! lsNow i) i o)--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
@@ -1,240 +0,0 @@-{-# LANGUAGE BangPatterns #-}-{-# LANGUAGE FlexibleContexts #-}--module Hans.Layer.Arp (-    ArpHandle-  , runArpLayer--    -- External Interface-  , arpWhoHas-  , arpIP4Packet-  , addLocalAddress-  ) where--import Hans.Address.IP4 (IP4,parseIP4,renderIP4)-import Hans.Address.Mac (Mac,parseMac,renderMac,broadcastMac)-import Hans.Channel-import Hans.Layer-import Hans.Layer.Arp.Table-import Hans.Layer.Ethernet-import Hans.Message.Arp-    (ArpPacket(..),parseArpPacket,renderArpPacket,ArpOper(..))-import Hans.Message.EthernetFrame-import Hans.Timers (delay_)-import Hans.Utils--import Control.Concurrent (forkIO,takeMVar,putMVar,newEmptyMVar)-import Control.Monad (forM_,mplus,guard,unless,when)-import MonadLib (BaseM(inBase),set,get)-import qualified Data.ByteString.Lazy as L-import qualified Data.Map.Strict      as Map-import qualified Data.ByteString      as S----- Arp ----------------------------------------------------------------------------- | A handle to a running arp layer.-type ArpHandle = Channel (Arp ())----- | Start an arp layer.-runArpLayer :: ArpHandle -> EthernetHandle -> IO ()-runArpLayer h eth = do-  addEthernetHandler eth (EtherType 0x0806) (send h . handleIncoming)-  let i = emptyArpState h eth-  void (forkIO (loopLayer "arp" i (receive h) id))----- External Interface -------------------------------------------------------------- | 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 -> L.ByteString -> IO ()-arpIP4Packet h !src !dst !pkt = send h (handleOutgoing src dst pkt)---- | Associate an address with a mac in the Arp layer.-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 :: {-# UNPACK #-} !EthernetHandle-  , arpSelf     :: {-# UNPACK #-} !ArpHandle-  }--emptyArpState :: ArpHandle -> EthernetHandle -> ArpState-emptyArpState h eth = ArpState-  { arpTable    = Map.empty-  , arpAddrs    = Map.empty-  , arpWaiting  = Map.empty-  , arpEthernet = eth-  , arpSelf     = h-  }--ethernetHandle :: Arp EthernetHandle-ethernetHandle  = arpEthernet `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-        }-      body = renderArpPacket renderMac renderIP4 msg-  output (sendEthernet eth frame body)--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    = broadcastMac-              , arpTPA    = ip-              , arpOper   = ArpRequest-              }-        now <- time-        let table' = addPending now ip (arpTable state)-        set state { arpTable = table' }-        addWaiter ip k-        mapM_ (sendArpPacket . msg) addrs-        output (delay_ 10000 (send (arpSelf state) advanceArpTable))---- | Process an incoming arp packet-handleIncoming :: S.ByteString -> Arp ()-handleIncoming bs = do-  msg <- liftRight (parseArpPacket parseMac parseIP4 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 -> L.ByteString -> Arp ()-handleOutgoing src dst body = do-  eth <- ethernetHandle-  lha <- localHwAddress src-  let frame dha = EthernetFrame-        { etherDest   = dha-        , etherSource = lha-        , etherType   = 0x0800-        }-  whoHas dst $ \ res -> case res of-    Nothing  -> return ()-    Just dha -> sendEthernet eth (frame dha) body
− src/Hans/Layer/Arp/Table.hs
@@ -1,59 +0,0 @@-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.Strict as Map----- Arp Table ---------------------------------------------------------------------arpEntryTimeout :: POSIXTime-arpEntryTimeout = 60--data ArpEntry-  = ArpEntry    { arpMac     :: {-# UNPACK #-} !Mac-                , arpTimeout :: !POSIXTime-                }-  | ArpPending  { arpTimeout :: !POSIXTime-                }- deriving Show--data ArpResult-  = KnownAddress {-# UNPACK #-} !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 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/Dns.hs
@@ -1,362 +0,0 @@-{-# LANGUAGE DeriveDataTypeable #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE PatternGuards #-}-{-# LANGUAGE OverloadedStrings #-}--module Hans.Layer.Dns (-    DnsHandle-  , runDnsLayer-  , DnsException--  , addNameServer-  , removeNameServer--  , HostName-  , HostEntry(..)-  , getHostByName-  , getHostByAddr-  ) where--import Hans.Address.IP4-import Hans.Channel-import Hans.Layer-import Hans.Layer.Udp as Udp-import Hans.Message.Dns-import Hans.Message.Udp-import Hans.Timers--import Control.Concurrent ( forkIO, MVar, newEmptyMVar, takeMVar, putMVar )-import Control.Monad ( mzero, guard, when )-import Data.Bits ( shiftR, (.&.), (.|.) )-import Data.Foldable ( foldl' )-import Data.List ( intercalate )-import Data.String ( fromString )-import Data.Typeable ( Typeable )-import Data.Word ( Word16 )-import MonadLib ( get, set )-import qualified Control.Exception as X-import qualified Data.ByteString as S-import qualified Data.ByteString.Char8 as C8-import qualified Data.ByteString.Lazy as L-import qualified Data.Map.Strict as Map----- External Interface ------------------------------------------------------------type DnsHandle = Channel (Dns ())--runDnsLayer :: DnsHandle -> UdpHandle -> IO ()-runDnsLayer h udp =-  do _ <- forkIO (loopLayer "dns" (emptyDnsState h udp) (receive h) id)-     return ()--data DnsException = NoNameServers-                    -- ^ No name servers have been configured-                  | OutOfServers-                    -- ^ Ran out of name servers to try-                  | DoesNotExist-                    -- ^ Unable to find any information about the host-                  | DnsRequestFailed-                    deriving (Show,Typeable)--instance X.Exception DnsException--addNameServer :: DnsHandle -> IP4 -> IO ()-addNameServer h addr =-  send h $ do state <- get-              set $! state { dnsNameServers = addr : dnsNameServers state }--removeNameServer :: DnsHandle -> IP4 -> IO ()-removeNameServer h addr =-  send h $ do state <- get-              set $! state-                { dnsNameServers = filter (/= addr) (dnsNameServers state) }--type HostName = String--data HostEntry = HostEntry { hostName      :: HostName-                           , hostAliases   :: [HostName]-                           , hostAddresses :: [IP4]-                           } deriving (Show)--getHostByName :: DnsHandle -> HostName -> IO HostEntry-getHostByName h host =-  do res <- newEmptyMVar-     send h (getHostEntry res (FromHost host))-     e <- takeMVar res-     case e of-       Right he -> return he-       Left err -> X.throwIO err---getHostByAddr :: DnsHandle -> IP4 -> IO HostEntry-getHostByAddr h addr =-  do res <- newEmptyMVar-     send h (getHostEntry res (FromIP4 addr))-     e <- takeMVar res-     case e of-       Right he -> return he-       Left err -> X.throwIO err----- Handlers ----------------------------------------------------------------------type Dns = Layer DnsState--data DnsState = DnsState { dnsSelf        :: {-# UNPACK #-} !DnsHandle-                         , dnsUdpHandle   :: {-# UNPACK #-} !UdpHandle-                         , dnsNameServers :: ![IP4]-                         , dnsReqId       :: {-# UNPACK #-} !Word16-                         , dnsQueries     :: !(Map.Map Word16 DnsQuery)-                         , dnsTimeout     :: {-# UNPACK #-} !Milliseconds-                         }--emptyDnsState :: DnsHandle -> UdpHandle -> DnsState-emptyDnsState h udp = DnsState { dnsSelf        = h-                               , dnsUdpHandle   = udp-                               , dnsNameServers = []-                               , dnsReqId       = 1-                               , dnsQueries     = Map.empty-                               , dnsTimeout     = 180000 -- 3 minutes-                               }---- LFSR: x^16 + x^14 + x^13 + x^11 + 1------ 0xB400 ~ bit 15 .|. bit 13 .|. bit 12 .|. bit 10-stepReqId :: Word16 -> Word16-stepReqId w = (w `shiftR` 1) .|. (negate (w .&. 0x1) .&. 0xB400)---- | Register a fresh request, along with a timer to reap the request after--- 'dnsTimeout'.-registerRequest :: (Word16 -> DnsQuery) -> Dns Word16-registerRequest mk =-  do state <- get-     let reqId = dnsReqId state-     set state { dnsReqId   = stepReqId reqId-               , dnsQueries = Map.insert reqId (mk reqId) (dnsQueries state)-               }-     return reqId--registerTimeout :: Word16 -> Timer -> Dns ()-registerTimeout reqId timer =-  do DnsState { .. } <- get-     case Map.lookup reqId dnsQueries of-       Just query -> updateRequest reqId query { qTimeout = Just timer }-       Nothing    -> output (cancel timer)--updateRequest :: Word16 -> DnsQuery -> Dns ()-updateRequest reqId query =-  do state <- get-     set state { dnsQueries = Map.insert reqId query (dnsQueries state) }--lookupRequest :: Word16 -> Dns DnsQuery-lookupRequest reqId =-  do DnsState { .. } <- get-     case Map.lookup reqId dnsQueries of-       Just query -> return query-       Nothing    -> mzero--removeRequest :: Word16 -> Dns ()-removeRequest reqId =-  do state <- get-     set state { dnsQueries = Map.delete reqId (dnsQueries state) }---data Source = FromHost HostName-            | FromIP4 IP4-              deriving (Show)--sourceQType :: Source -> [QType]-sourceQType FromHost{} = [QType A]-sourceQType FromIP4{}  = [QType PTR]--sourceHost :: Source -> Name-sourceHost (FromHost h)            = toLabels h-sourceHost (FromIP4 (IP4 a b c d)) = let byte w = fromString (show w)-                                      in map byte [d,c,b,a] ++ ["in-addr","arpa"]--toLabels :: String -> Name-toLabels str = case break (== '.') str of-  (as,_:bs) -> fromString as : toLabels bs-  (as,_)    -> [fromString as]--getHostEntry :: DnsResult -> Source -> Dns ()-getHostEntry res src =-  do DnsState { .. } <- get--     -- make sure that there are name servers to work with-     when (null dnsNameServers) $-       do output (putError res NoNameServers)-          mzero--     -- register a upd handler on a fresh port, and query the name servers in-     -- order-     output $-       do port <- addUdpHandlerAnyPort dnsUdpHandle (serverResponse dnsSelf src)-          send dnsSelf (createRequest res dnsNameServers src port)----- | Create the query packet, and register the request with the DNS layer.--- Then, send a request to the first name server.-createRequest :: DnsResult -> [IP4] -> Source -> UdpPort -> Dns ()-createRequest res nss src port =-  do DnsState { .. } <- get-     reqId <- registerRequest (mkDnsQuery res nss port src)-     sendRequest reqId----- | Send a request to the next name server in the queue.-sendRequest :: Word16 -> Dns ()-sendRequest reqId =-  do query <- lookupRequest reqId--     case qServers query of--       n:rest -> do updateRequest reqId query { qServers    = rest-                                              , qLastServer = Just n-                                              }-                    sendQuery n (qUdpPort query) reqId (qRequest query)--       -- out of servers to try-       [] -> do removeRequest reqId-                output (putError (qResult query) OutOfServers)--expireRequest :: Word16 -> Dns ()-expireRequest reqId =-  do DnsQuery { .. } <- lookupRequest reqId-     removeRequest reqId-     output (putError qResult OutOfServers)---- | Handle the response from the server.-handleResponse :: Source -> IP4 -> UdpPort -> S.ByteString -> Dns ()-handleResponse src srcIp srcPort bytes =-  do guard (srcPort == 53)--     DNSPacket { .. } <- liftRight (parseDNSPacket bytes)-     let DNSHeader { .. } = dnsHeader-     DnsQuery { .. } <- lookupRequest dnsId--     -- require that the last name server we sent to was the one that responded,-     -- and that it responded with a response, not a request.-     guard (Just srcIp == qLastServer && not dnsQuery)--     if dnsRC == RespNoError-        then output (putResult qResult (parseHostEntry src dnsAnswers))-        else output (putError  qResult DnsRequestFailed)--     removeRequest dnsId-     DnsState { .. } <- get-     output $ do removeUdpHandler dnsUdpHandle qUdpPort-                 case qTimeout of-                   Just timeout -> cancel timeout-                   Nothing      -> return ()--parseHostEntry :: Source -> [RR] -> HostEntry-parseHostEntry (FromHost host) = parseAddr host-parseHostEntry (FromIP4 addr)  = parsePtr addr---- | Parse the A and CNAME parts out of a response.-parseAddr :: HostName -> [RR] -> HostEntry-parseAddr host = foldl' processAnswer emptyHostEntry-  where--  emptyHostEntry = HostEntry { hostName      = host-                             , hostAliases   = []-                             , hostAddresses = [] }--  processAnswer he RR { .. } = case rrRData of-    RDA ip     -> he { hostAddresses = ip : hostAddresses he }-    RDCNAME ns -> he { hostName      = intercalate "." (map C8.unpack ns)-                     , hostAliases   = hostName he : hostAliases he }-    _          -> he--parsePtr :: IP4 -> [RR] -> HostEntry-parsePtr addr = foldl' processAnswer emptyHostEntry-  where-  emptyHostEntry = HostEntry { hostName      = ""-                             , hostAliases   = []-                             , hostAddresses = [addr] }--  processAnswer he RR { .. } = case rrRData of-    RDPTR name -> he { hostName = intercalate "." (map C8.unpack name) }-    _          -> he----- Query Management --------------------------------------------------------------type DnsResult = MVar (Either DnsException HostEntry)--putResult :: DnsResult -> HostEntry -> IO ()-putResult var he = putMVar var (Right he)--putError :: DnsResult -> DnsException -> IO ()-putError var err = putMVar var (Left err)--data DnsQuery = DnsQuery { qResult     :: DnsResult-                           -- ^ The handle back to the thread waiting fo the-                           -- HostEntry-                         , qUdpPort    :: !UdpPort-                           -- ^ The port this request is receiving on -                         , qRequest    :: L.ByteString-                           -- ^ The packet to send-                         , qServers    :: [IP4]-                           -- ^ Name servers left to try-                         , qLastServer :: Maybe IP4-                           -- ^ The last server queried-                         , qTimeout    :: Maybe Timer-                           -- ^ The timer for the current request-                         }--mkDnsQuery :: DnsResult -> [IP4] -> UdpPort -> Source -> Word16 -> DnsQuery-mkDnsQuery res nss port src reqId =-  DnsQuery { qResult     = res-           , qUdpPort    = port-           , qRequest    = renderDNSPacket (mkDNSPacket host qs reqId)-           , qServers    = nss-           , qLastServer = Nothing-           , qTimeout    = Nothing-           }-  where-  host = sourceHost src-  qs   = sourceQType src---mkDNSPacket :: Name -> [QType] -> Word16 -> DNSPacket-mkDNSPacket name qs reqId =-  DNSPacket { dnsHeader            = hdr-            , dnsQuestions         = [ mkQuery q | q <- qs ]-            , dnsAnswers           = []-            , dnsAuthorityRecords  = []-            , dnsAdditionalRecords = []-            }-  where-  hdr = DNSHeader { dnsId     = reqId-                  , dnsQuery  = True-                  , dnsOpCode = OpQuery-                  , dnsAA     = False-                  , dnsTC     = False-                  , dnsRD     = True-                  , dnsRA     = False-                  , dnsRC     = RespNoError-                  }--  mkQuery qty = Query { qName  = name-                      , qType  = qty-                      , qClass = QClass IN-                      }----- UDP Interaction ----------------------------------------------------------------- | Send a UDP query to the server given-sendQuery :: IP4 -> UdpPort -> Word16 -> L.ByteString -> Dns ()-sendQuery nameServer sp reqId bytes =-  do DnsState { .. } <- get-     output $ do sendUdp dnsUdpHandle nameServer (Just sp) 53 bytes-                 expire <- delay dnsTimeout (send dnsSelf (expireRequest reqId) `X.finally` putStrLn "KILLED")-                 send dnsSelf (registerTimeout reqId expire)---- | Queue the packet into the DNS layer for processing.-serverResponse :: DnsHandle -> Source -> UdpPort -> Udp.Handler-serverResponse dns src _ srcIp srcPort bytes =-  send dns (handleResponse src srcIp srcPort bytes)
− src/Hans/Layer/Ethernet.hs
@@ -1,187 +0,0 @@-{-# 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 (void,just)--import Control.Concurrent (forkIO,ThreadId,killThread)-import Control.Monad (mplus)-import MonadLib (get,set)-import qualified Data.ByteString.Lazy as L-import qualified Data.Map.Strict      as Map-import qualified Data.ByteString      as S----- Ethernet Layer ----------------------------------------------------------------type Handler = S.ByteString -> IO ()--type Tx = L.ByteString   -> IO ()-type Rx = EthernetHandle -> IO ()--type EthernetHandle = Channel (Eth ())---- | Run the ethernet layer.-runEthernetLayer :: EthernetHandle -> IO ()-runEthernetLayer h =-  void (forkIO (loopLayer "ethernet" (emptyEthernetState h) (receive h) id))----- External Interface ------------------------------------------------------------sendEthernet :: EthernetHandle -> EthernetFrame -> L.ByteString -> IO ()-sendEthernet h !frame body = send h (handleOutgoing frame body)--queueEthernet :: EthernetHandle -> S.ByteString -> 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   :: {-# UNPACK #-} !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 :: S.ByteString -> Eth ()-handleIncoming pkt = do-  (hdr,body) <- liftRight (parseEthernetFrame pkt)-  h          <- getHandler (etherType hdr)-  output (h body)----- | 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 -> L.ByteString -> Eth ()-handleOutgoing frame body = do-  dev <- getDevice (etherSource frame)-  output (devTx dev (renderEthernetFrame frame body))----- | 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
@@ -1,210 +0,0 @@-{-# LANGUAGE BangPatterns               #-}-{-# LANGUAGE MultiParamTypeClasses      #-}-{-# LANGUAGE TypeSynonymInstances       #-}-{-# LANGUAGE FlexibleInstances          #-}--module Hans.Layer.IP4 (-    IP4Handle-  , runIP4Layer-  , Rule(..)--    -- * External Interface-  , connectEthernet-  , withIP4Source-  , sendIP4Packet-  , addIP4RoutingRule-  , addIP4Handler, Handler-  , removeIP4Handler--  , Mtu-  ) 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, (<=<))-import MonadLib (get,set)-import qualified Data.ByteString.Lazy as L-import qualified Data.ByteString      as S---type Handler = IP4Header -> S.ByteString -> IO ()--type IP4Handle = Channel (IP ())--runIP4Layer :: IP4Handle -> ArpHandle -> EthernetHandle -> IO ()-runIP4Layer h arp eth = do-  void (forkIO (loopLayer "ip4" (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 -> IP4Header -> L.ByteString -> IO ()-sendIP4Packet h !hdr !pkt = send h (sendBytes hdr 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 :: {-# UNPACK #-} !Ident-  , ip4ArpHandle :: {-# UNPACK #-} !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 :: IP4Header -> L.ByteString -> IP ()-sendBytes hdr0 bs = do-  rule@(src,_,_) <- findRoute (ip4DestAddr hdr0)-  ident          <- nextIdent-  let hdr = hdr0-        { ip4SourceAddr = src-        , ip4Ident      = ident-        }-  sendPacket' hdr bs rule---- | Send a packet using a given routing rule-sendPacket' :: IP4Header -> L.ByteString -> (IP4,IP4,Mtu) -> IP ()-sendPacket' hdr body (src,dst,mtu) = do-  arp  <- arpHandle-  output $ do-    let frags = splitPacket mtu hdr body-    mapM_ (arpIP4Packet arp src dst <=< uncurry 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.--- XXX Hide this facility behind configuration-_forward :: IP4Header -> L.ByteString -> IP ()-_forward hdr body = do-  rule@(src,_,_) <- findRoute (ip4DestAddr hdr)-  guard (src /= ip4SourceAddr hdr)-  sendPacket' hdr body rule---- | 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 :: IP4Header -> S.ByteString -> IP ()-routeLocal hdr body = do-  let dest = ip4DestAddr hdr-  localAddress dest-  h  <- getHandler (ip4Protocol hdr)-  mb <- handleFragments hdr body-  case mb of-    Just bs -> output (h hdr (strict bs))-    Nothing -> return ()---handleFragments :: IP4Header -> S.ByteString -> IP (Maybe L.ByteString)-handleFragments hdr body = do-  state <- get-  now   <- time-  let (table',mb) = processIP4Packet now (ip4Fragments state) hdr body-  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 :: S.ByteString -> IP ()-handleIncoming bs = do-  (hdr,hlen,plen) <- liftRight (parseIP4Packet bs)-  let (header,rest) = S.splitAt hlen bs-  let checksum      = computeChecksum 0 header-  guard $ and-    [ S.length bs       >= 20-    , hlen              >= 20-    , checksum          == 0-    , ip4Version hdr    == 4-    ]--  -- forward?-  let payload = S.take (plen - hlen) rest-  routeLocal hdr payload-    -- XXX Hide this old router functionality behind a setting-    -- `mplus` forward hdr (chunk payload)---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
@@ -1,115 +0,0 @@--module Hans.Layer.IP4.Fragmentation where--import Hans.Address-import Hans.Address.IP4-import Hans.Message.Ip4-import Hans.Utils (chunk)--import Data.Ord (comparing)-import Data.Time.Clock.POSIX (POSIXTime)-import qualified Data.ByteString.Lazy as L-import qualified Data.Map.Strict      as Map-import qualified Data.ByteString      as S---type FragmentationTable addr = Map.Map (Ident,addr,addr) Fragments--emptyFragmentationTable :: FragmentationTable IP4-emptyFragmentationTable  = Map.empty---data Fragments = Fragments-  { startTime :: !POSIXTime-  , totalSize :: {-# UNPACK #-} !Int-  , fragments :: ![Fragment]-  } deriving Show--data Fragment = Fragment-  { fragmentOffset  :: {-# UNPACK #-} !Int-  , fragmentLength  :: {-# UNPACK #-} !Int-  , fragmentPayload :: !L.ByteString-  } 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 `L.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 -> S.ByteString-                -> (FragmentationTable addr, Maybe L.ByteString)-processFragment _   table False   0   _   _    _     bs =-  (table, Just (chunk 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 = S.length bs-  cur    = Fragment off curlen (chunk bs)-  newTotalLen | areMore   = -1-              | otherwise = off + curlen---processIP4Packet :: POSIXTime -> FragmentationTable IP4-                 -> IP4Header -> S.ByteString-                 -> (FragmentationTable IP4, Maybe L.ByteString)-processIP4Packet now table 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
@@ -1,87 +0,0 @@-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
@@ -1,110 +0,0 @@-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE TypeSynonymInstances  #-}-{-# LANGUAGE FlexibleInstances     #-}--module Hans.Layer.Icmp4 (-    Icmp4Handle-  , runIcmp4Layer-  , addIcmp4Handler-  , destUnreachable-  ) where--import Hans.Address.IP4 (IP4,broadcastIP4)-import Hans.Channel-import Hans.Layer-import Hans.Message.Icmp4-import Hans.Message.Ip4-import Hans.Utils-import qualified Hans.Layer.IP4 as IP4--import Control.Concurrent (forkIO)-import Control.Monad (unless)-import Data.Serialize (runPut,putByteString)-import MonadLib (get,set)-import qualified Data.ByteString as S--type Handler = Icmp4Packet -> IO ()--type Icmp4Handle = Channel (Icmp4 ())--icmpProtocol :: IP4Protocol-icmpProtocol  = IP4Protocol 0x1--runIcmp4Layer :: Icmp4Handle -> IP4.IP4Handle -> IO ()-runIcmp4Layer h ip4 = do-  let handles = Icmp4Handles ip4 []-  IP4.addIP4Handler ip4 icmpProtocol-    $ \ hdr bs -> send h (handleIncoming hdr bs)-  void (forkIO (loopLayer "icmp4" handles (receive h) id))--data Icmp4Handles = Icmp4Handles-  { icmpIp4      :: {-# UNPACK #-} !IP4.IP4Handle-  , icmpHandlers :: ![Handler]-  }--type Icmp4 = Layer Icmp4Handles--ip4Handle :: Icmp4 IP4.IP4Handle-ip4Handle  = icmpIp4 `fmap` get---- | Add a handler for Icmp4 messages that match the provided predicate.-addIcmp4Handler :: Icmp4Handle -> Handler -> IO ()-addIcmp4Handler h k = send h (handleAdd k)---- | Send a destination unreachable message to a host, with the given bytes as--- its body.  Don't send the message, if the message was broadcast.-destUnreachable :: Icmp4Handle -> DestinationUnreachableCode-                -> IP4Header -> Int -> S.ByteString -> IO ()-destUnreachable h code hdr len body-  | ip4DestAddr hdr == broadcastIP4 = return ()-  | otherwise                       = send h $ do-    let bytes = runPut $ do-          putIP4Header hdr len-          putByteString (S.take 8 body)-    sendPacket True (ip4SourceAddr hdr) (DestinationUnreachable code bytes)---- Message Handling ---------------------------------------------------------------- | Deliver an ICMP message via the IP4 layer.-sendPacket :: Bool -> IP4 -> Icmp4Packet -> Icmp4 ()-sendPacket df dst pkt = do-  ip4 <- ip4Handle-  let hdr = emptyIP4Header-        { ip4DestAddr     = dst-        , ip4Protocol     = icmpProtocol-        , ip4DontFragment = df-        }-  output $ IP4.sendIP4Packet ip4 hdr-         $ renderIcmp4Packet pkt---- | Handle incoming ICMP packets-handleIncoming :: IP4Header -> S.ByteString -> Icmp4 ()-handleIncoming hdr bs = do-  pkt <- liftRight (parseIcmp4Packet bs)-  matchHandlers pkt-  case pkt of-    -- XXX: Only echo-request is handled at the moment-    Echo ident seqNum dat -> handleEchoRequest hdr ident seqNum dat-    _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 :: IP4Header -> Identifier -> SequenceNumber -> S.ByteString-                  -> Icmp4 ()-handleEchoRequest hdr ident seqNum dat =-  sendPacket (ip4DontFragment hdr) (ip4SourceAddr hdr)-      (EchoReply ident seqNum dat)----- | Output the IO actions for each handler that's registered.-matchHandlers :: Icmp4Packet -> Icmp4 ()-matchHandlers pkt = do-  s <- get-  unless (null (icmpHandlers s)) (output (mapM_ ($ pkt) (icmpHandlers s)))
− src/Hans/Layer/Tcp.hs
@@ -1,59 +0,0 @@-{-# LANGUAGE BangPatterns #-}--module Hans.Layer.Tcp (-    TcpHandle-  , runTcpLayer-  , queueTcp-  ) where--import Hans.Channel-import Hans.Layer-import Hans.Layer.IP4-import Hans.Layer.Tcp.Handlers-import Hans.Layer.Tcp.Monad-import Hans.Layer.Tcp.Timers-import Hans.Layer.Tcp.Types-import Hans.Message.Ip4-import Hans.Message.Tcp-import Hans.Utils--import Control.Concurrent (forkIO)-import Control.Monad (when)-import Data.Time.Clock.POSIX (getPOSIXTime,POSIXTime)-import qualified Data.ByteString as S---runTcpLayer :: TcpHandle -> IP4Handle -> IO ()-runTcpLayer tcp ip4 = do-  start <- getPOSIXTime-  let s0 = emptyTcpState tcp ip4 start-  void (forkIO (loopLayer "tcp" s0 (receive tcp) stepTcp))-  addIP4Handler ip4 tcpProtocol (queueTcp tcp)--  -- initialize the timers-  initTimers tcp---- | Queue a tcp packet.-queueTcp :: TcpHandle -> IP4Header -> S.ByteString -> IO ()-queueTcp tcp !hdr !bs = send tcp (handleIncomingTcp hdr bs)---- | Rate of ISN increase, in Hz.-isnRate :: POSIXTime-isnRate  = 128000---- | Run the tcp action, after updating any internal state.-stepTcp :: Tcp () -> Tcp ()-stepTcp body = do-  now        <- time-  lastUpdate <- getLastUpdate-  let diff = now - lastUpdate-      -- increment the ISN at 128KHz-      inc  = round (isnRate * diff)--  when (inc > 0) $-    modifyHost_ $ \ host ->-      host { hostLastUpdate    = now-           , hostInitialSeqNum = hostInitialSeqNum host + inc-           }--  body
− src/Hans/Layer/Tcp/Handlers.hs
@@ -1,506 +0,0 @@-{-# LANGUAGE MultiWayIf #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE PatternGuards #-}--module Hans.Layer.Tcp.Handlers (-    handleIncomingTcp-  , outputSegments-  ) where--import Hans.Address.IP4-import Hans.Layer-import Hans.Layer.Tcp.Messages-import Hans.Layer.Tcp.Monad-import Hans.Layer.Tcp.Timers-import Hans.Layer.Tcp.Types-import Hans.Layer.Tcp.Window-import Hans.Message.Ip4-import Hans.Message.Tcp--import Control.Monad (guard,when,unless,join)-import Data.Bits (bit)-import Data.Int (Int64)-import Data.Maybe (fromMaybe,isJust)-import qualified Data.ByteString as S-import qualified Data.ByteString.Lazy as L-import qualified Data.Foldable as F----- Incoming Packets ---------------------------------------------------------------- | Process a single incoming tcp packet.-handleIncomingTcp :: IP4Header -> S.ByteString -> Tcp ()-handleIncomingTcp ip4 bytes = do--  let src = ip4SourceAddr ip4-      dst = ip4DestAddr ip4--  guard (validateTcpChecksumIP4 src dst bytes)-  (hdr,body) <- liftRight (parseTcpPacket bytes)--  withConnection' src hdr (segmentArrives src hdr body)-    $ timeWaitConnection src hdr-    $ noConnection src hdr body--noConnection :: IP4 -> TcpHeader -> S.ByteString -> Tcp ()-noConnection src hdr @ TcpHeader { .. } body =-  do if | tcpRst    -> return ()-        | tcpAck    -> sendSegment src (mkRst hdr)                    L.empty-        | otherwise -> sendSegment src (mkRstAck hdr (S.length body)) L.empty-     finish----- Arrival to a TIME_WAIT socket---------------------------------------------------- | Handle an incoming packet in the TimeWait state-timeWaitConnection :: IP4 -> TcpHeader -> Tcp () -> Tcp ()-timeWaitConnection src hdr noTimeWait =-  do mb <- getTimeWait src hdr-     case mb of-       Just (sid,tcp) -> handleTimeWait sid tcp hdr-       Nothing        -> noTimeWait---handleTimeWait :: SocketId -> TimeWaitSock -> TcpHeader -> Tcp ()-handleTimeWait sid TimeWaitSock { .. } TcpHeader { .. }-  | tcpRst || tcpSyn = removeTimeWait sid-  | tcpAck           = do resetTimeWait2MSL sid--                          let addTimestamp-                                | Just ts <- twTimestamp = setTcpOption (mkTimestamp ts)-                                | otherwise              = id--                              hdr = addTimestamp emptyTcpHeader-                                        { tcpDestPort   = tcpSourcePort-                                        , tcpSourcePort = tcpDestPort-                                        , tcpSeqNum     = twSeqNum-                                          -- advance RCV.NXT over the FIN-                                        , tcpAckNum     = tcpSeqNum + 1-                                        , tcpAck        = True-                                        }--                          -- ACK the retransmitted FIN,ACK-                          when tcpFin (sendSegment (sidRemoteHost sid) hdr L.empty)-  | otherwise        = return ()------ Segment Arrival ---------------------------------------------------------------{-# INLINE discardAndReturn #-}-discardAndReturn :: Sock a-discardAndReturn  = do outputSegments-                       escape--{-# INLINE done #-}-done :: Sock a-done  = do outputSegments-           escape--segmentArrives :: IP4 -> TcpHeader -> S.ByteString -> Sock ()-segmentArrives src hdr body =-  do whenState Closed (inTcp (noConnection src hdr body))--     whenState Listen $-       do when (tcpAck hdr) (rst hdr)--          when (tcpSyn hdr) $ do child <- createConnection src hdr-                                 _     <- withChild child synAck-                                 return ()--          -- RST will be dropped at this point, as will anything else that-          -- wasn't covered by the above two cases.-          done--     shouldDrop <- modifyTcpSocket (updateTimestamp hdr)-     when shouldDrop discardAndReturn--     whenState SynSent $-       do tcp <- getTcpSocket--          -- check the ACK-          when (tcpAck hdr) $-            do when (tcpAckNum hdr <= tcpIss tcp ||-                     tcpAckNum hdr >  tcpSndNxt tcp) $-                 do unless (tcpRst hdr) (rst hdr)-                    discardAndReturn--          -- one of these has to be set to continue processing in this state.-          unless (tcpSyn hdr || tcpRst hdr) discardAndReturn--          -- check RST-          let accAcceptable = tcpSndUna tcp <= tcpAckNum hdr &&-                              tcpAckNum hdr <= tcpSndNxt tcp--          when (tcpRst hdr) $-            do when accAcceptable $ do notify False-                                       closeSocket-               discardAndReturn--          -- this is where a security/compartment check would be done--          -- check SYN--          when (tcpSyn hdr) $-            do modifyTcpSocket_ $ \ sock -> sock-                 { tcpOutMSS      = fromMaybe (tcpInMSS sock) (getMSS hdr)--                   -- clear out, and configure the retransmit buffer-                 , tcpOut         = setSndWind (tcpWindow hdr)-                                  $ setSndWindScale (windowScale hdr)-                                  $ clearRetransmit-                                  $ tcpOut sock--                   -- this corresponds to setting IRS to SEQ.SEG, and advancing-                   -- RCV.NXT by one-                 , tcpIn          = emptyLocalWindow (tcpSeqNum hdr + 1) 14600 0-                 , tcpSack        = sackSupported hdr-                 , tcpWindowScale = isJust (findTcpOption OptTagWindowScaling hdr)-                 }--               when (tcpAck hdr) $-                 do -- advance SND.UNA-                    modifyTcpSocket_ $ \ sock -> sock-                      { tcpSndUna = tcpAckNum hdr }--                    TcpSocket { .. } <- getTcpSocket-                    if tcpSndUna > tcpIss-                       then do ack-                               notify True--                               setState Established--                               -- continue at step 6-                               when (tcpUrg hdr) (proceedFromStep6 hdr body)--                       else do setState SynReceived-                               synAck-                               -- XXX queue any additional data for processing-                               -- once Established has been reached--                    done--          when (not (tcpSyn hdr || tcpRst hdr)) discardAndReturn--     -- make sure that the sequence numbers are valid-     checkSequenceNumber hdr body-     checkResetBit hdr-     -- skip security/precidence check-     checkSynBit hdr-     checkAckBit hdr-     proceedFromStep6 hdr body--proceedFromStep6 :: TcpHeader -> S.ByteString -> Sock ()-proceedFromStep6 hdr body =-  do -- XXX skipping URG processing-     processSegmentText hdr body-     checkFinBit hdr----- | Make sure that there is space for the incoming segment-checkSequenceNumber :: TcpHeader -> S.ByteString -> Sock ()-checkSequenceNumber hdr body =-  do TcpSocket { .. } <- getTcpSocket--     -- RCV.NXT <= SEG.SEQ + off < RCV.NXT + RCV.WND-     let canReceive off =-           lwRcvNxt tcpIn <= segSeq &&-           segSeq         <  lwRcvNxt tcpIn + fromIntegral (lwRcvWind tcpIn)-           where-           segSeq = tcpSeqNum hdr + off--         len = fromIntegral (S.length body)--         shouldDiscard-           | len == 0  = if lwRcvWind tcpIn == 0-                                   -- SEQ.SEG = RCV.NXT-                            then tcpSeqNum hdr == lwRcvNxt tcpIn-                            else canReceive 0-           | otherwise = canReceive 0 || canReceive (len - 1)--     when (shouldDiscard && all not [tcpAck hdr, tcpUrg hdr, tcpRst hdr]) $-       do unless (tcpRst hdr) ack-          discardAndReturn---- | Process the presence of the RST bit-checkResetBit :: TcpHeader -> Sock ()-checkResetBit hdr-  | tcpRst hdr = do-      TcpSocket { .. } <- getTcpSocket-      whenStates [ SynReceived, Established, FinWait1, FinWait2-                 , CloseWait, Closing, LastAck] $-        do notify False-           closeSocket-           done--  | otherwise  = return ()--checkSynBit :: TcpHeader -> Sock ()-checkSynBit hdr-  | tcpSyn hdr = whenStates [SynReceived,Established,FinWait1,FinWait2-                            ,CloseWait,Closing,LastAck] $-    do tcp <- getTcpSocket--       when (tcpSeqNum hdr `inRcvWnd` tcp) $-         do closeSocket-            done--  | otherwise  = return ()--checkAckBit :: TcpHeader -> Sock ()-checkAckBit hdr-  | tcpAck hdr =-    do whenState SynReceived $-         do tcp <- getTcpSocket-            if tcpSndUna tcp <= tcpAckNum hdr && tcpAckNum hdr <= tcpSndNxt tcp-               then establishConnection-               else rst hdr--       whenStates [Established,FinWait1,FinWait2,CloseWait,Closing] $-         do let TcpHeader { .. } = hdr-            TcpSocket { .. } <- getTcpSocket--            -- if the ack was to something that is outstanding, update SND.UNA,-            -- and filter things out of the retransmit queue-            when (tcpSndUna < tcpAckNum && tcpAckNum <= tcpSndNxt) (handleAck hdr)--            -- if the ack is to something that hasn't been sent yet, drop the-            -- segment-            when (tcpSndNxt < tcpAckNum) discardAndReturn--            whenState FinWait1 $-              do tcp <- getTcpSocket-                 when (nothingOutstanding tcp) (setState FinWait2)--            -- XXX no way to acknowledge the user's close request from FinWait2-            -- whenState FinWait2 $ ...--            whenState Closing $-              do tcp <- getTcpSocket-                 when (nothingOutstanding tcp) enterTimeWait--       whenState LastAck $-         do handleAck hdr-            tcp <- getTcpSocket-            when (nothingOutstanding tcp) $ do closeSocket-                                               done--  | otherwise  = discardAndReturn--processSegmentText :: TcpHeader -> S.ByteString -> Sock ()-processSegmentText hdr body =-  whenStates [Established,FinWait1,FinWait2] $-    do if S.null body-          -- make sure that the delayed ack flag gets set-          then when (tcpSyn hdr || tcpFin hdr) $ modifyTcpTimers_-                                               $ \ tt -> tt { ttDelayedAck = True }--          -- push the segment through the local window, if it was the next thing-          -- expected, a wakeup action will be returned, notifying a waiting-          -- thread that there's something to read in the input buffer.-          else do mb <- modifyTcpSocket (handleData hdr body)--                  -- notify any waiting threads that there is stuff to read-                  case mb of-                    Just wakeup -> outputS (tryAgain wakeup)-                    Nothing     -> return ()---  -- otherwise, ignore the segment text--checkFinBit :: TcpHeader -> Sock ()-checkFinBit hdr-  | tcpFin hdr =-    do -- do not process the FIN-       whenStates [Closed,Listen,SynSent]-         discardAndReturn--       -- advance RCV.NXT over the FIN-       advanceRcvNxt 1--       flushQueues-       ack--       -- don't emit a FIN here, as that should be done by the user with an-       -- explicit call to `close`--       whenStates [SynReceived,Established] (setState CloseWait)--       -- XXX this needs to check that the FIN was ACKed, instead of just-       -- entering TimeWait-       whenState FinWait1 $ do TcpSocket { .. } <- getTcpSocket-                               if tcpSndNxt <= tcpSndUna-                                  then enterTimeWait-                                  else setState Closing--       whenState FinWait2 enterTimeWait--       -- don't do anything else for the remaining states-       done--  | otherwise  = return ()----- | Flush all pending outgoing state.------ TODO:--- * Push all data out of the incoming queue to the incoming buffer--- * Mark the socket as not accepting any more user data-flushQueues :: Sock ()-flushQueues  =-  do finalizers <- modifyTcpSocket flush-     outputS finalizers-  where-  flush tcp = (inFins >> outFins,tcp')-    where--    -- empty the outgoing buffer, notifying all waiting processes that the send-    -- won't happen-    (outFins,out') = flushWaiting (tcpOutBuffer tcp)--    -- empty the input buffer-    (inFins, in')  = flushWaiting (tcpInBuffer tcp)--    tcp' = tcp { tcpOutBuffer = out'-               , tcpInBuffer  = in'-               }----- | Update the currently held timestamp for both sides, and return a boolean--- that indicates whether or not the packet should be dropped.------ RFC 1323-updateTimestamp :: TcpHeader -> TcpSocket -> (Bool,TcpSocket)-updateTimestamp hdr tcp -  | shouldDrop = (True, tcp)-  | otherwise  = (False,tcp { tcpTimestamp = ts' })-  where-  -- when the timestamp check fails from an ack, that's not a syn,ack, mark this-  -- packet as one to be dropped.-  shouldDrop = not (tcpSyn hdr || tcpRst hdr)-            && isJust (tcpTimestamp tcp) /= isJust ts'-  ts' = do-    ts                     <- tcpTimestamp tcp-    OptTimestamp them echo <- findTcpOption OptTagTimestamp hdr-    -- when this is an ack, the echo value should not be greater than the-    -- current timestamp.  this doesn't currently account for overflow, so-    -- connections lasting >27 days will probably fail.-    let rel       = tsTimestamp ts - echo-        isGreater = 0 < rel && rel < bit 31-    when (tcpAck hdr) (guard (tsTimestamp ts == echo || isGreater))-    return ts { tsLastTimestamp = them }---- | Enqueue a new packet in the local window, attempting to place the bytes--- received in the user buffer as they complete a part of the stream.  Bytes are--- ack'd as they complete the stream, and bytes that would cause the local--- buffer to overflow are dropped.-handleData :: TcpHeader -> S.ByteString -> TcpSocket-           -> (Maybe Wakeup, TcpSocket)-handleData hdr body tcp0 = fromMaybe (Nothing,tcp) $ do-  (wakeup,buf') <- putBytes bytes (tcpInBuffer tcp)-  let tcp' = tcp-        { tcpInBuffer = buf'-        , tcpTimers   = (tcpTimers tcp)-          { ttDelayedAck = or [ not (L.null bytes)-                              , tcpSyn hdr-                              , tcpFin hdr ]-          }-        }-  return (wakeup, tcp')-  where-  (segs,win') = incomingPacket hdr body (tcpIn tcp0)-  tcp         = tcp0 { tcpIn = win' }-  bytes       = L.fromChunks (map inBody (F.toList segs))----- | Handle an ACK to a sent data segment.-handleAck :: TcpHeader -> Sock ()-handleAck hdr = do-  now <- inTcp time-  modifyTcpSocket_ (updateAck now)-  where-  -- using Karns algorithm, only calibrate the RTO if the packet that was ack'd-  -- has not yet been retransmitted.-  updateAck now tcp = case receiveAck hdr (tcpOut tcp) of-    Just (seg,out') ->-      let calibrate | outFresh seg = calibrateRTO now (outTime seg)-                    | otherwise    = id-       in tcp { tcpOut    = out'-              , tcpSndUna = tcpAckNum hdr-              , tcpTimers = calibrate (tcpTimers tcp)-              }-    Nothing -> tcp---- | Setup the 2MSL timer, and enter the TIME_WAIT state.  We only enter--- TimeWait if all of our sent data has been ACKed, so it's safe to clear out--- the retransmit queue here.-enterTimeWait :: Sock ()-enterTimeWait  = do-  modifyTcpSocket_ $ \ tcp -> tcp { tcpOut = clearRetransmit (tcpOut tcp) }-  set2MSL mslTimeout-  setState TimeWait--createConnection :: IP4 -> TcpHeader -> Sock TcpSocket-createConnection ip4 hdr =-  do let parent = listenSocketId (tcpDestPort hdr)-     isn <- inTcp initialSeqNum-     tcp <- getTcpSocket-     return (emptyTcpSocket (tcpWindow hdr) (windowScale hdr))-       { tcpParent      = Just parent-       , tcpSocketId    = incomingSocketId ip4 hdr-       , tcpState       = SynReceived-       , tcpIss         = isn-       , tcpSndNxt      = isn-       , tcpSndUna      = isn-       , tcpIn          = emptyLocalWindow (tcpSeqNum hdr) 14600 0-       , tcpOutMSS      = fromMaybe defaultMSS (getMSS hdr)-       , tcpTimestamp   = do-           -- require that the parent had a timestamp-           ts <- tcpTimestamp tcp-           -- require that they have sent us a timestamp, before using them-           OptTimestamp val _ <- findTcpOption OptTagTimestamp hdr-           return ts { tsLastTimestamp = val }-       , tcpSack        = sackSupported hdr-       , tcpWindowScale = isJust (findTcpOption OptTagWindowScaling hdr)-       }--establishConnection :: Sock ()-establishConnection  =-  do mb <- inParent popAcceptor-     case join mb of-       Just k  -> do sid <- tcpSocketId `fmap` getTcpSocket-                     outputS (k sid)-                     setState Established--       -- no one available to accept the connection, close it-       Nothing -> do finAck-                     setState FinWait1-                     done----- Buffer Delivery ----------------------------------------------------------------- | Fill up the remote window with segments.-outputSegments :: Sock ()-outputSegments  = do-  now <- inTcp time-  (ws,segs) <- modifyTcpSocket (genSegments now)-  F.mapM_ outputSegment segs-  unless (null ws) (outputS (F.traverse_ tryAgain ws))----- Utilities ----------------------------------------------------------------------- | Extract the maximum segment size from a header, if the option is present.-getMSS :: TcpHeader -> Maybe Int64-getMSS hdr = do-  OptMaxSegmentSize n <- findTcpOption OptTagMaxSegmentSize hdr-  return (fromIntegral n)--windowScale :: TcpHeader -> Int-windowScale hdr = fromMaybe 0 $ do-  OptWindowScaling n <- findTcpOption OptTagWindowScaling hdr-  return (fromIntegral n)--sackSupported :: TcpHeader -> Bool-sackSupported  = isJust . findTcpOption OptTagSackPermitted
− src/Hans/Layer/Tcp/Messages.hs
@@ -1,264 +0,0 @@-module Hans.Layer.Tcp.Messages where--import Hans.Layer ( time )-import Hans.Layer.Tcp.Monad-import Hans.Layer.Tcp.Types-import Hans.Layer.Tcp.Window-import Hans.Message.Tcp--import Data.Maybe ( fromMaybe )-import Data.Time.Clock.POSIX (POSIXTime)-import qualified Data.ByteString.Lazy as L-import qualified Data.Foldable as F-import qualified Data.Sequence as Seq----- Generic Packets ---------------------------------------------------------------mkSegment :: TcpSocket -> TcpHeader-mkSegment tcp = case tcpTimestamp tcp of-  Just ts -> setTcpOption (mkTimestamp ts) hdr-  Nothing -> hdr-  where-  hdr  = emptyTcpHeader-    { tcpDestPort   = sidRemotePort (tcpSocketId tcp)-    , tcpSourcePort = sidLocalPort (tcpSocketId tcp)-    , tcpSeqNum     = tcpSndNxt tcp-    , tcpAckNum     = tcpRcvNxt tcp-      -- XXX this doesn't really reflect the right number-    , tcpWindow     = lwRcvWind (tcpIn tcp)-    }--mkAck :: TcpSocket -> TcpHeader-mkAck tcp = addSackOption tcp-          $ (mkSegment tcp)-            { tcpAck = True-            }----- Options ------------------------------------------------------------------------- | Add the Sack option to a tcp packet.-addSackOption :: TcpSocket -> TcpHeader -> TcpHeader-addSackOption sock-  | tcpSack sock && not (null bs) = setTcpOption (OptSack bs)-  | otherwise                     = id-  where-  bs = F.toList (localWindowSackBlocks (tcpIn sock))---- | Add the sack permitted tcp option.-addSackPermitted :: TcpSocket -> TcpHeader -> TcpHeader-addSackPermitted sock-  | tcpSack sock = setTcpOption OptSackPermitted-  | otherwise    = id---- | Add the window scale option on the outgoing header.-addWindowScale :: TcpSocket -> TcpHeader -> TcpHeader-addWindowScale sock-  | tcpWindowScale sock = setTcpOption-                        $ OptWindowScaling-                        $ fromIntegral-                        $ lwRcvWindScale-                        $ tcpIn sock-  | otherwise           = id----- Connection Refusal -------------------------------------------------------------- | Given a tcp header, generate the next header in the sequence that--- corresponds to the RST ACK response.  As this should only be used in--- situations in which an ACK was not received, this adds one plus the body--- length to the ack number.-mkRstAck :: TcpHeader -> Int -> TcpHeader-mkRstAck hdr len = emptyTcpHeader-  { tcpSeqNum     = 0-  , tcpAckNum     = tcpSeqNum hdr + fromIntegral len + 1 -- to ack their message-  , tcpRst        = True-  , tcpAck        = True-  , tcpDestPort   = tcpSourcePort hdr-  , tcpSourcePort = tcpDestPort hdr-  }--mkRst :: TcpHeader -> TcpHeader-mkRst hdr = emptyTcpHeader-  { tcpSeqNum     = tcpAckNum hdr-  , tcpRst        = True-  , tcpDestPort   = tcpSourcePort hdr-  , tcpSourcePort = tcpDestPort hdr-  }----- Connection Establishment ------------------------------------------------------mkSyn :: TcpSocket -> TcpHeader-mkSyn tcp = addSackPermitted tcp-          $ addWindowScale tcp-          $ setTcpOption (mkMSS tcp)-          $ (mkSegment tcp)-            { tcpSyn    = True-            , tcpAckNum = 0-            }---- | Construct a SYN ACK packet, in response to a SYN.-mkSynAck :: TcpSocket -> TcpHeader-mkSynAck tcp = addSackPermitted tcp-             $ addWindowScale tcp-             $ setTcpOption (mkMSS tcp)-             $ (mkSegment tcp)-               { tcpSyn = True-               , tcpAck = True-               }----- Connection Closing -------------------------------------------------------------- | Construct a FIN packet.------ XXX should this include a sack option?-mkFinAck :: TcpSocket -> TcpHeader-mkFinAck tcp = (mkSegment tcp)-  { tcpFin = True-  , tcpAck = True-  }----- Data Packets ------------------------------------------------------------------mkData :: TcpSocket -> TcpHeader-mkData tcp = addSackOption tcp-           $ (mkSegment tcp)-             { tcpAck = True-             , tcpPsh = True-             }----- Socket Actions ----------------------------------------------------------------syn :: Sock ()-syn  = do-  tcp <- getTcpSocket-  tcpOutput (mkSyn tcp) L.empty-  advanceSndNxt 1---- | Respond to a SYN message with a SYN ACK message.-synAck :: Sock ()-synAck  = do-  advanceRcvNxt 1-  tcp <- getTcpSocket-  tcpOutput (mkSynAck tcp) L.empty-  advanceSndNxt 1---- | Send an ACK packet.-ack :: Sock ()-ack  = do-  clearDelayedAck-  tcp <- getTcpSocket-  tcpOutput (mkAck tcp) L.empty---- | Schedule a delayed ACK packet.-delayedAck :: Sock ()-delayedAck  = modifyTcpTimers_ (\tt -> tt { ttDelayedAck = True })---- | Unschedule a delayed ACK packet.-clearDelayedAck :: Sock ()-clearDelayedAck  = modifyTcpTimers_ (\tt -> tt { ttDelayedAck = False })---- | Queue an outgoing fin packet in the outgoing window, and send it.------ NOTE:  This uses genSegments, which will pull data out of the waiti-finAck :: Sock ()-finAck  = do-  now <- inTcp time--  seg <- modifyTcpSocket $ \ tcp ->--    let -- construct the outgoing FIN,ACK segment-        hdr          = mkFinAck tcp-        finAckOutSeg = mkOutSegment now (ttRTO (tcpTimers tcp)) hdr L.empty--        -- push the FIN,ACK into the outgoing window-        tcp' = tcp { tcpOut    = addSegment finAckOutSeg (tcpOut tcp)--                     -- advance SND.NXT over the FIN-                   , tcpSndNxt = tcpSndNxt tcp + 1-                   }--     in (finAckOutSeg, tcp')--  outputSegment seg-  clearDelayedAck--rstAck :: TcpHeader -> Int -> Sock ()-rstAck hdr len = tcpOutput (mkRstAck hdr len) L.empty--rst :: TcpHeader -> Sock ()-rst hdr = tcpOutput (mkRst hdr) L.empty---- | Send a segment.-outputSegment :: OutSegment -> Sock ()-outputSegment seg = do-  clearDelayedAck-  tcpOutput (outHeader seg) (outBody seg)----- Flag Tests --------------------------------------------------------------------type SetFlag   = TcpHeader -> Bool-type UnsetFlag = TcpHeader -> Bool--testFlags :: [SetFlag] -> [UnsetFlag] -> TcpHeader -> Bool-testFlags sfs ufs hdr = all test sfs && all (not . test) ufs-  where-  test prj = prj hdr--isSyn :: TcpHeader -> Bool-isSyn  = testFlags [ tcpSyn ]-                   [ tcpCwr, tcpEce, tcpUrg, tcpAck, tcpPsh, tcpRst, tcpFin ]--isSynAck :: TcpHeader -> Bool-isSynAck  = testFlags [ tcpSyn, tcpAck ]-                      [ tcpCwr, tcpEce, tcpUrg, tcpPsh, tcpRst, tcpFin ]--isRstAck :: TcpHeader -> Bool-isRstAck = testFlags [ tcpRst, tcpAck ]-                     [ tcpCwr, tcpEce, tcpUrg, tcpPsh, tcpSyn, tcpFin ]--isAck :: TcpHeader -> Bool-isAck = testFlags [ tcpAck ]-                  [ tcpCwr, tcpEce, tcpUrg, tcpPsh, tcpRst, tcpSyn, tcpFin ]--isFin :: TcpHeader -> Bool-isFin  = testFlags [ tcpFin ]-                   [ tcpCwr, tcpEce, tcpUrg, tcpAck, tcpPsh, tcpRst, tcpSyn ]--isFinAck :: TcpHeader -> Bool-isFinAck  = testFlags [ tcpFin, tcpAck ]-                      [ tcpCwr, tcpEce, tcpUrg, tcpPsh, tcpRst, tcpSyn ]----- Packet Output ------------------------------------------------------------------- | Take data from the output buffer, and turn it into segments.  When data was--- freed from the output buffer, the wakeup actions for any threads currently--- blocked on writing to the output buffer will be returned.-genSegments :: POSIXTime -> TcpSocket -> (([Wakeup],OutSegments),TcpSocket)-genSegments now tcp0 = loop [] Seq.empty tcp0-  where-  loop ws segs tcp-    | rwAvailable (tcpOut tcp) <= 0 = result-    | otherwise                     = fromMaybe result $ do-      let len = nextSegSize tcp-      (mbWakeup,body,bufOut) <- takeBytes len (tcpOutBuffer tcp)-      let seg  = mkOutSegment now (ttRTO (tcpTimers tcp)) (mkData tcp) body-          tcp' = tcp { tcpSndNxt    = outAckNum seg-                     , tcpOut       = addSegment seg (tcpOut tcp)-                     , tcpOutBuffer = bufOut-                     }--      return (loop (addWakeup mbWakeup) (segs Seq.|> seg) tcp')-    where--    addWakeup Nothing  =   ws-    addWakeup (Just w) = w:ws--    result = ((ws,segs),tcp)
− src/Hans/Layer/Tcp/Monad.hs
@@ -1,473 +0,0 @@-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE Rank2Types #-}-{-# LANGUAGE MultiWayIf #-}--module Hans.Layer.Tcp.Monad where--import Hans.Address.IP4-import Hans.Channel-import Hans.Layer-import Hans.Layer.IP4-import Hans.Layer.Tcp.Types-import Hans.Layer.Tcp.Window-import Hans.Message.Ip4-import Hans.Message.Tcp--import Control.Applicative(Applicative(..))-import Control.Monad (MonadPlus(..),guard,when)-import Data.Time.Clock.POSIX (POSIXTime)-import MonadLib (get,set)-import qualified Data.ByteString.Lazy as L-import qualified Data.Map.Strict as Map-import qualified Data.Sequence as Seq----- TCP Monad ---------------------------------------------------------------------type TcpHandle = Channel (Tcp ())--type Tcp = Layer TcpState--data TcpState = TcpState-  { tcpSelf  :: {-# UNPACK #-} !TcpHandle-  , tcpIP4   :: {-# UNPACK #-} !IP4Handle-  , tcpHost  :: !Host-  }--emptyTcpState :: TcpHandle -> IP4Handle -> POSIXTime -> TcpState-emptyTcpState tcp ip4 start = TcpState-  { tcpSelf  = tcp-  , tcpIP4   = ip4-  , tcpHost  = emptyHost start-  }---- | 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----- Host Operations ---------------------------------------------------------------getHost :: Tcp Host-getHost  = tcpHost `fmap` get--getLastUpdate :: Tcp POSIXTime-getLastUpdate  = hostLastUpdate `fmap` getHost--setHost :: Host -> Tcp ()-setHost host = do-  rw <- get-  host `seq` set rw { tcpHost = host }--modifyHost_ :: (Host -> Host) -> Tcp ()-modifyHost_ f = do-  host <- getHost-  setHost (f host)--modifyHost :: (Host -> (a,Host)) -> Tcp a-modifyHost f = do-  host <- getHost-  let (a,host') = f host-  setHost host'-  return a---- | Reset the 2MSL timer on the socket in TimeWait.-resetTimeWait2MSL :: SocketId -> Tcp ()-resetTimeWait2MSL sid = modifyHost_ $ \ host ->-  host { hostTimeWaits = Map.adjust twReset2MSL sid (hostTimeWaits host) }--getTimeWait :: IP4 -> TcpHeader -> Tcp (Maybe (SocketId,TimeWaitSock))-getTimeWait remote hdr =-  do host <- getHost-     let sid = incomingSocketId remote hdr-     return $ do tw <- Map.lookup sid (hostTimeWaits host)-                 return (sid,tw)--removeTimeWait :: SocketId -> Tcp ()-removeTimeWait sid =-  modifyHost_ $ \ host ->-    host { hostTimeWaits = Map.delete sid (hostTimeWaits host) }--getConnections :: Tcp Connections-getConnections  = hostConnections `fmap` getHost--takeConnections :: Tcp Connections-takeConnections  =-  modifyHost $ \ Host { .. } ->-    (hostConnections, Host { hostConnections = Map.empty, .. })--setConnections :: Connections -> Tcp ()-setConnections cons = modifyHost_ (\host -> host { hostConnections = cons })---- | Lookup a connection, returning @Nothing@ if the connection doesn't exist.-lookupConnection :: SocketId -> Tcp (Maybe TcpSocket)-lookupConnection sid = do-  cons <- getConnections-  return (Map.lookup sid cons)---- | Retrieve a connection from the host.  The computation fails if the--- connection doesn't exist.-getConnection :: SocketId -> Tcp TcpSocket-getConnection sid = do-  cs <- getConnections-  case Map.lookup sid cs of-    Just tcp -> return tcp-    Nothing  -> mzero---- | Assign a connection to a socket id.  If the TcpSocket is in TimeWait, this--- will do two things:------  1. Remove the corresponding key from the connections map---  2. Add the socket to the TimeWait map, using the current value of its 2MSL---     timer (which should be set when the TimeWait state is entered)------ The purpose of this is to clean up the memory associated with the connection--- as soon as possible, and once it's in TimeWait, no data will flow on the--- socket.-setConnection :: SocketId -> TcpSocket -> Tcp ()-setConnection ident con-  | tcpState con == TimeWait =-    modifyHost_ $ \ host ->-      host { hostTimeWaits   = addTimeWait con (hostTimeWaits host)-           , hostConnections = Map.delete ident (hostConnections host)-           }--  | otherwise =-    do cons <- getConnections-       setConnections (Map.insert ident con cons)---- | Add a new connection to the host.-addConnection :: SocketId -> TcpSocket -> Tcp ()-addConnection  = setConnection---- | Modify an existing connection in the host.-modifyConnection :: SocketId -> (TcpSocket -> TcpSocket) -> Tcp ()-modifyConnection sid k = do-  cons <- getConnections-  setConnections (Map.adjust k sid cons)---- | Remove a connection from the host.-remConnection :: SocketId -> Tcp ()-remConnection sid = do-  cons <- getConnections-  setConnections (Map.delete sid cons)---- | Send out a tcp segment via the IP layer.-sendSegment :: IP4 -> TcpHeader -> L.ByteString -> Tcp ()-sendSegment dst hdr body = do-  ip4 <- ip4Handle-  output $ withIP4Source ip4 dst $ \ src -> do-    let ip4Hdr = emptyIP4Header-          { ip4DestAddr     = dst-          , ip4Protocol     = tcpProtocol-          , ip4DontFragment = False-          }-        pkt    = renderWithTcpChecksumIP4 src dst hdr body-    sendIP4Packet ip4 ip4Hdr pkt---- | Get the initial sequence number.-initialSeqNum :: Tcp TcpSeqNum-initialSeqNum  = hostInitialSeqNum `fmap` getHost---- | Increment the initial sequence number by a value.-addInitialSeqNum :: TcpSeqNum -> Tcp ()-addInitialSeqNum sn =-  modifyHost_ (\host -> host { hostInitialSeqNum = hostInitialSeqNum host + sn })---- | Allocate a new port for use.-allocatePort :: Tcp TcpPort-allocatePort  = do-  host <- getHost-  case takePort host of-    Just (p,host') -> do-      setHost host'-      return p-    Nothing -> mzero---- | Release a used port.-closePort :: TcpPort -> Tcp ()-closePort port = modifyHost_ (releasePort port)----- Socket Monad -------------------------------------------------------------------- | Tcp operations in the context of a socket.------ This implementation is a bit ridiculous, and when the eventual rewrite comes--- this should be one of the first things to be reconsidered.  The basic problem--- is that if you rely on the `finished` implementation for the Layer monad, you--- exit from the socket context as well, losing any changes that have been made--- locally.  This gives the ability to simulate `finished`, with the benefit of--- only yielding from the Sock context, not the whole Tcp context.-newtype Sock a = Sock-  { unSock :: forall r. TcpSocket -> Escape r -> Next a r -> Tcp r-  }--type Escape r = TcpSocket      -> Tcp r-type Next a r = TcpSocket -> a -> Tcp r--instance Functor Sock where-  fmap f m = Sock $ \s  x k -> unSock m s x-                  $ \s' a   -> k s' (f a)-  {-# INLINE fmap #-}--instance Applicative Sock where-  {-# INLINE pure #-}-  pure x = Sock $ \ s _ k -> k s x--  {-# INLINE (<*>) #-}-  f <*> a = Sock $ \ s   x k -> unSock f s  x-                 $ \ s'  g   -> unSock a s' x-                 $ \ s'' b   -> k s'' (g b)--instance Monad Sock where-  {-# INLINE return #-}-  return = pure--  m >>= f = Sock $ \ s  x k -> unSock m s x-                 $ \ s' a   -> unSock (f a) s' x k-  {-# INLINE (>>=) #-}--  m >> n = Sock $ \ s  x k -> unSock m s  x-                $ \ s' _   -> unSock n s' x k-  {-# INLINE (>>) #-}--inTcp :: Tcp a -> Sock a-inTcp m = Sock $ \ s _ k -> do a <- m-                               k s a-{-# INLINE inTcp #-}----- | Finish early, with no result.-escape :: Sock a-escape  = Sock $ \ s x _ -> x s--runSock_ :: TcpSocket -> Sock a -> Tcp ()-runSock_ tcp sm =-  do _ <- runSock tcp sm-     return ()--runSock :: TcpSocket -> Sock a -> Tcp (Maybe a)-runSock tcp sm =-  do (tcp',mb) <- runSock' tcp sm-     setConnection (tcpSocketId tcp') $! tcp'-     return mb--seqMaybe :: Maybe a -> ()-seqMaybe (Just a) = a `seq` ()-seqMaybe Nothing  = ()---- | Run the socket action, and increment its internal timestamp value.  Do not--- add it back to the connections map.-runSock' :: TcpSocket -> Sock a -> Tcp (TcpSocket,Maybe a)-runSock' tcp sm = do-  now      <- time-  let steppedTcp = tcp { tcpTimestamp =-                           let ts' = stepTimestamp now `fmap` tcpTimestamp tcp-                            in seqMaybe ts' `seq` ts'-                       }-  res@(tcp',_) <- (unSock sm $! steppedTcp) escapeK nextK-  tcp' `seq` return res-  where-  escapeK s = return (s,Nothing)-  nextK s a = return (s,Just a)---- | Iterate for each connection, rolling back to its previous state if the--- computation fails.-eachConnection :: Sock () -> Tcp ()-eachConnection m =-  do cs       <- takeConnections-     (cs',ws) <- sandbox [] [] (Map.elems cs)--     modifyHost_ $ \ Host { .. } ->-       Host { hostConnections = cs'-            , hostTimeWaits   = Map.union ws hostTimeWaits-            , ..-            }--  where--  -- Prevent failure in the socket action from leaking out of this scope.  When-  -- failure is detected, just return the old TCB-  sandbox active timeWait (tcp:rest) =-    do tcp' <- fmap fst (runSock' tcp m) `mplus` return tcp--       if | tcpState tcp' == TimeWait -> sandbox       active (tcp':timeWait) rest-          | tcpState tcp' == Closed-            && tcpUserClosed tcp'     -> sandbox       active       timeWait  rest-          | otherwise                 -> sandbox (tcp':active)      timeWait  rest--  sandbox active timeWait [] =-    return ( Map.fromList [ (tcpSocketId tcp, tcp)            | tcp <- active ]-           , Map.fromList [ (tcpSocketId tcp, mkTimeWait tcp) | tcp <- timeWait ])--withConnection :: IP4 -> TcpHeader -> Sock a -> Tcp ()-withConnection remote hdr m = withConnection' remote hdr m mzero--withConnection' :: IP4 -> TcpHeader -> Sock a -> Tcp () -> Tcp ()-withConnection' remote hdr m noConn = do-  cs <- getConnections-  case Map.lookup estId cs `mplus` Map.lookup listenId cs of-    Just con -> runSock_ con m-    Nothing  -> noConn-  where-  estId    = incomingSocketId remote hdr-  listenId = listenSocketId (tcpDestPort hdr)--listeningConnection :: SocketId -> Sock a -> Tcp (Maybe a)-listeningConnection sid m = do-  tcp <- getConnection sid-  guard (tcpState tcp == Listen && isAccepting tcp)-  mb <- runSock tcp m-  return mb---- | Run a socket operation in the context of the socket identified by the--- socket id.------ XXX this should really be renamed, as it's not guarding on the state of the--- socket-establishedConnection :: SocketId -> Sock a -> Tcp ()-establishedConnection sid m = do-  tcp <- getConnection sid-  runSock_ tcp m---- | Get the parent id of the current socket, and fail if it doesn't exist.-getParent :: Sock (Maybe SocketId)-getParent  = tcpParent `fmap` getTcpSocket---- | Run an action in the context of the socket's parent.  Returns `Nothing` if--- the connection has no parent.-inParent :: Sock a -> Sock (Maybe a)-inParent m = do-  mbPid <- getParent-  case mbPid of-    Just pid -> inTcp $ do p  <- getConnection pid-                           mb <- runSock p m-                           return mb-    Nothing  -> return Nothing--withChild :: TcpSocket -> Sock a -> Sock (Maybe a)-withChild tcp m = inTcp $ do mb <- runSock tcp m-                             return mb--getTcpSocket :: Sock TcpSocket-getTcpSocket  = Sock (\s _ k -> k s $! s)-{-# INLINE getTcpSocket #-}--setTcpSocket :: TcpSocket -> Sock ()-setTcpSocket tcp = Sock (\ _ _ k -> (k $! tcp) ())-{-# INLINE setTcpSocket #-}--getTcpTimers :: Sock TcpTimers-getTcpTimers  = tcpTimers `fmap` getTcpSocket--modifyTcpSocket :: (TcpSocket -> (a,TcpSocket)) -> Sock a-modifyTcpSocket f = Sock $ \ s _ k -> let (a,s') = f s-                                       in (k $! s') a--modifyTcpSocket_ :: (TcpSocket -> TcpSocket) -> Sock ()-modifyTcpSocket_ k = modifyTcpSocket (\tcp -> ((), k tcp))--modifyTcpTimers :: (TcpTimers -> (a,TcpTimers)) -> Sock a-modifyTcpTimers k = modifyTcpSocket $ \ tcp ->-  let (a,t') = k (tcpTimers tcp)-   in (a,tcp { tcpTimers = t' })--modifyTcpTimers_ :: (TcpTimers -> TcpTimers) -> Sock ()-modifyTcpTimers_ k = modifyTcpTimers (\t -> ((), k t))---- | Set the state of the current connection.-setState :: ConnState -> Sock ()-setState state = modifyTcpSocket_ (\tcp -> tcp { tcpState = state })---- | Get the state of the current connection.-getState :: Sock ConnState-getState  = tcpState `fmap` getTcpSocket--whenState :: ConnState -> Sock () -> Sock ()-whenState state body = do-  curState <- getState-  when (state == curState) body--whenStates :: [ConnState] -> Sock () -> Sock ()-whenStates states body = do-  curState <- getState-  when (curState `elem` states) body--pushAcceptor :: Acceptor -> Sock ()-pushAcceptor k = modifyTcpSocket_ $ \ tcp -> tcp-  { tcpAcceptors = tcpAcceptors tcp Seq.|> k-  }---- | Pop off an acceptor.-popAcceptor :: Sock (Maybe Acceptor)-popAcceptor  = do-  tcp <- getTcpSocket-  case Seq.viewl (tcpAcceptors tcp) of-    a Seq.:< as -> do setTcpSocket $! tcp { tcpAcceptors = as }-                      return (Just a)-    Seq.EmptyL  -> return Nothing---- | Send a notification back to a waiting process that the socket has been--- established, or that it has failed.  It's assumed that this will only be--- called from the context of a user socket, so when the parameter is @False@,--- the user close field will be set to true.-notify :: Bool -> Sock ()-notify success = do-  mbNotify <- modifyTcpSocket $ \ tcp ->-    let tcp' = tcp { tcpNotify = Nothing }-     in if success-           then (tcpNotify tcp, tcp')-           else (tcpNotify tcp, tcp' { tcpUserClosed = True })--  case mbNotify of-    Just f  -> outputS (f success)-    Nothing -> return ()---- | Output some IO to the Tcp layer.-outputS :: IO () -> Sock ()-outputS  = inTcp . output--advanceRcvNxt :: TcpSeqNum -> Sock ()-advanceRcvNxt n =-  modifyTcpSocket_ (\tcp -> tcp { tcpIn = addRcvNxt n (tcpIn tcp) })--advanceSndNxt :: TcpSeqNum -> Sock ()-advanceSndNxt n =-  modifyTcpSocket_ (\tcp -> tcp { tcpSndNxt = tcpSndNxt tcp + n })--remoteHost :: Sock IP4-remoteHost  = (sidRemoteHost . tcpSocketId) `fmap` getTcpSocket---- | Send a TCP segment in the context of a socket.-tcpOutput :: TcpHeader -> L.ByteString -> Sock ()-tcpOutput hdr body = do-  dst <- remoteHost-  inTcp (sendSegment dst hdr body)----- | Unblock any waiting processes, in preparation to close.-shutdown :: Sock ()-shutdown  = do-  finalize <- modifyTcpSocket $ \ tcp -> -      let (wOut,bufOut) = flushWaiting (tcpOutBuffer tcp)-          (wIn,bufIn)   = flushWaiting (tcpInBuffer tcp)-       in (wOut >> wIn,tcp { tcpOut       = clearRetransmit (tcpOut tcp)-                           , tcpOutBuffer = bufOut-                           , tcpInBuffer  = bufIn-                           })-  outputS finalize---- | Set the socket state to closed, and unblock any waiting processes.-closeSocket :: Sock ()-closeSocket  = do-  shutdown-  setState Closed--userClose :: Sock ()-userClose  = modifyTcpSocket_ (\tcp -> tcp { tcpUserClosed = True })
− src/Hans/Layer/Tcp/Socket.hs
@@ -1,309 +0,0 @@-{-# LANGUAGE DeriveDataTypeable #-}--module Hans.Layer.Tcp.Socket (-    Socket()-  , sockRemoteHost-  , sockRemotePort-  , sockLocalPort-  , connect, ConnectError(..)-  , listen, ListenError(..)-  , accept, AcceptError(..)-  , close, CloseError(..)-  , sendBytes, canSend-  , recvBytes, canRecv-  ) where--import Hans.Address.IP4-import Hans.Channel-import Hans.Layer-import Hans.Layer.Tcp.Handlers-import Hans.Layer.Tcp.Messages-import Hans.Layer.Tcp.Monad-import Hans.Layer.Tcp.Types-import Hans.Layer.Tcp.Window-import Hans.Message.Tcp--import Control.Concurrent (MVar,newEmptyMVar,takeMVar,putMVar)-import Control.Exception (Exception,throwIO)-import Control.Monad (mplus)-import Data.Int (Int64)-import Data.Maybe (isJust)-import Data.Typeable (Typeable)-import qualified Data.ByteString.Lazy as L----- Socket Interface --------------------------------------------------------------data Socket = Socket-  { sockHandle :: !TcpHandle-  , sockId     :: !SocketId-  }---- | The remote host of a socket.-sockRemoteHost :: Socket -> IP4-sockRemoteHost  = sidRemoteHost . sockId---- | The remote port of a socket.-sockRemotePort :: Socket -> TcpPort-sockRemotePort  = sidRemotePort . sockId---- | The local port of a socket.-sockLocalPort :: Socket -> TcpPort-sockLocalPort  = sidLocalPort . sockId--data SocketGenericError = SocketGenericError-    deriving (Show,Typeable)--instance Exception SocketGenericError---- | Block on the result of a Tcp action, from a different context.------ XXX closing the socket should also unblock any other threads waiting on--- socket actions-blockResult :: TcpHandle -> (MVar (SocketResult a) -> Tcp ()) -> IO a-blockResult tcp action = do-  res     <- newEmptyMVar-  -- XXX put a more meaningful error here-  let unblock = output (putMVar res (socketError SocketGenericError))-  send tcp (action res `mplus` unblock)-  sockRes <- takeMVar res-  case sockRes of-    SocketResult a -> return a-    SocketError e  -> throwIO e----- Connect ------------------------------------------------------------------------- | A connect call failed.-data ConnectError = ConnectionRefused-    deriving (Show,Typeable)--instance Exception ConnectError---- | Connect to a remote host.-connect :: TcpHandle -> IP4 -> TcpPort -> Maybe TcpPort -> IO Socket-connect tcp remote remotePort mbLocal = blockResult tcp $ \ res -> do-  localPort <- maybe allocatePort return mbLocal-  isn       <- initialSeqNum-  now       <- time-  let sid  = SocketId-        { sidLocalPort  = localPort-        , sidRemoteHost = remote-        , sidRemotePort = remotePort-        }-      sock = (emptyTcpSocket 0 0)-        { tcpSocketId  = sid-        , tcpNotify    = Just $ \ success -> putMVar res $! if success-            then SocketResult Socket-              { sockHandle = tcp-              , sockId     = sid-              }-            else socketError ConnectionRefused-        , tcpState     = Listen-        , tcpSndNxt    = isn + 1-        , tcpSndUna    = isn-        , tcpIss       = isn-        , tcpTimestamp = Just (emptyTimestamp now)-        }-  -- XXX how should the retry/backoff be implemented-  runSock_ sock $ do-    syn-    setState SynSent----- Listen ------------------------------------------------------------------------data ListenError = ListenError-    deriving (Show,Typeable)--instance Exception ListenError---- | Open a new listening socket that can be used to accept new connections.-listen :: TcpHandle -> IP4 -> TcpPort -> IO Socket-listen tcp _src port = blockResult tcp $ \ res -> do-  let sid = listenSocketId port-  mb <- lookupConnection sid-  case mb of--    Nothing -> do-      now <- time-      let con = (emptyTcpSocket 0 0)-            { tcpSocketId  = sid-            , tcpState     = Listen-            , tcpTimestamp = Just (emptyTimestamp now)-            }-      addConnection sid con-      output $ putMVar res $ SocketResult Socket-        { sockHandle = tcp-        , sockId     = sid-        }--    Just _ -> output (putMVar res (socketError ListenError))----- Accept ------------------------------------------------------------------------data AcceptError = AcceptError-    deriving (Show,Typeable)--instance Exception AcceptError---- | Accept new incoming connections on a listening socket.-accept :: Socket -> IO Socket-accept sock = blockResult (sockHandle sock) $ \ res ->-  establishedConnection (sockId sock) $ do-    state <- getState-    case state of-      Listen -> pushAcceptor $ \ sid -> putMVar res $ SocketResult $ Socket-        { sockHandle = sockHandle sock-        , sockId     = sid-        }--      -- XXX need more descriptive errors-      _ -> outputS (putMVar res (socketError AcceptError))----- Close -------------------------------------------------------------------------data CloseError = CloseError-    deriving (Show,Typeable)--instance Exception CloseError---- | Close an open socket.-close :: Socket -> IO ()-close sock = blockResult (sockHandle sock) $ \ res -> do-  let unblock r  = output (putMVar res r)-      ok         = inTcp (unblock (SocketResult ()))-      closeError = inTcp (unblock (socketError CloseError))-      connected  = establishedConnection (sockId sock) $ do-        userClose-        state <- getState-        case state of--          Established ->-            do finAck-               setState FinWait1-               ok--          CloseWait ->-            do finAck-               setState LastAck-               ok--          SynSent ->-            do closeSocket-               ok--          SynReceived ->-            do finAck-               setState FinWait1-               ok--          -- XXX how should we close a listening socket?  what should happen to-          -- connections that are in-progress?-          Listen ->-            do setState Closed-               closeSocket-               ok--          FinWait1 -> ok-          FinWait2 -> ok--          _ -> closeError---  -- closing a connection that doesn't exist causes a CloseError-  connected `mplus` unblock (socketError CloseError)------ Writing -----------------------------------------------------------------------canSend :: Socket -> IO Bool-canSend sock =-  blockResult           (sockHandle sock) $ \ res ->-  establishedConnection (sockId sock)     $-    do tcp <- getTcpSocket-       let avail = tcpState tcp == Established && not (isFull (tcpOutBuffer tcp))-       outputS (putMVar res (SocketResult avail))---- | Send bytes over a socket.  The number of bytes delivered will be returned,--- with 0 representing the other side having closed the connection.-sendBytes :: Socket -> L.ByteString -> IO Int64-sendBytes sock bytes = blockResult (sockHandle sock) $ \ res ->-  let result len  = putMVar res (SocketResult len)-      performSend = establishedConnection (sockId sock) $ do-        let wakeup continue-              | continue  = send (sockHandle sock) performSend-              | otherwise = result 0-        mbWritten <- modifyTcpSocket (outputBytes bytes wakeup)-        case mbWritten of-          Just len -> outputS (result len)-          Nothing  -> return ()-        outputSegments-   in performSend--outputBytes :: L.ByteString -> Wakeup -> TcpSocket -> (Maybe Int64, TcpSocket)-outputBytes bytes wakeup tcp-  | tcpState tcp == Established              = ok-  | tcpState tcp == CloseWait                = ok-  | tcpState tcp `elem` [ Closing, LastAck-                        , FinWait1, FinWait2-                        , TimeWait ]         = bad-  | otherwise                                = bad-  where-  (mbWritten,bufOut) = writeBytes bytes wakeup (tcpOutBuffer tcp)--  -- normal response (add the data to the output buffer, or queue if there's no-  -- space available)-  ok = (mbWritten, tcp { tcpOutBuffer = bufOut })--  -- the connection is closed, so return that no data was written, and don't-  -- queue a wakeup action-  bad = (Just 0, tcp)----- Reading ------------------------------------------------------------------------- | True when there are bytes queued to receive.-canRecv :: Socket -> IO Bool-canRecv sock =-  blockResult           (sockHandle sock) $ \ res ->-  establishedConnection (sockId sock)     $-    do tcp <- getTcpSocket-       let avail = tcpState tcp == Established && not (isEmpty (tcpInBuffer tcp))-       outputS (putMVar res (SocketResult avail))---- | Receive bytes from a socket.  A null ByteString represents the other end--- closing the socket.-recvBytes :: Socket -> Int64 -> IO L.ByteString-recvBytes sock len = blockResult (sockHandle sock) $ \ res ->-  let result bytes = putMVar res (SocketResult bytes)-      performRecv  = establishedConnection (sockId sock) $ do-        let wakeup continue-              | continue  = send (sockHandle sock) performRecv-              | otherwise = result L.empty-        mbRead <- modifyTcpSocket (inputBytes len wakeup)-        case mbRead of-          Just bytes -> outputS (result bytes)-          Nothing    -> return ()-   in performRecv--inputBytes :: Int64 -> Wakeup -> TcpSocket -> (Maybe L.ByteString, TcpSocket)-inputBytes len wakeup tcp-  | tcpState tcp == Established                        = ok-  | tcpState tcp == CloseWait                          = if isJust mbRead-                                                            then ok-                                                            else bad-  | tcpState tcp `elem` [ Closing, LastAck, TimeWait ] = bad-  | otherwise                                          = ok--  where-  (mbRead,bufIn) = readBytes len wakeup (tcpInBuffer tcp)--  -- normal behavior (read buffered data, or queue)-  ok = (mbRead, tcp { tcpInBuffer = bufIn })--  -- return that there's no data to read, and don't queue a wakeup action-  bad = (Just L.empty, tcp)
− src/Hans/Layer/Tcp/Timers.hs
@@ -1,167 +0,0 @@-{-# LANGUAGE RecordWildCards #-}--module Hans.Layer.Tcp.Timers (-    initTimers--  , resetIdle-  , whenIdleFor--  , set2MSL--  , calibrateRTO-  ) where--import Hans.Channel-import Hans.Layer.Tcp.Messages-import Hans.Layer.Tcp.Monad-import Hans.Layer.Tcp.Types-import Hans.Layer.Tcp.Window--import Control.Concurrent (forkIO,threadDelay)-import Control.Monad (when,unless,forever,void)-import Data.Time.Clock.POSIX (POSIXTime)-import qualified Data.Foldable as F----- Timer Handlers ------------------------------------------------------------------ | Schedule the timers to run on the fast and slow intervals.-initTimers :: TcpHandle -> IO ()-initTimers tcp = do-  every 500 slowTimer-  every 200 fastTimer--  where--  -- XXX we should investigate timing the time that body takes to run, and-  -- making the decision about how long to delay based on that.-  every n body = void $ forkIO $ forever $-    do threadDelay timeout-       send tcp body-    where-    timeout = n * 1000---- | Fires every 500ms.-slowTimer :: Tcp ()-slowTimer  =-  do -- first, handle active connections-     eachConnection $ do-       -- the slow timer is valid for all states but TimeWait; the check is done-       -- here because the socket may have only just transitioned to TimeWait,-       -- and hasn't been moved to hostTimeWaits yet.-       TcpSocket { .. } <- getTcpSocket-       unless (tcpState == TimeWait) handleRTO-       handle2MSL-       updateTimers--     -- second, decrement the 2MSL timer for sockets in the TimeWait state-     modifyHost_ $ \ host ->-       host { hostTimeWaits = stepTimeWaitConnections (hostTimeWaits host) }--resetIdle :: Sock ()-resetIdle  = modifyTcpTimers_ (\tt -> tt { ttIdle = 0 })---- | Handle only the delayed ack timer, fires ever 200ms.-fastTimer :: Tcp ()-fastTimer  = eachConnection $ do-  tcp <- getTcpSocket-  when (tcpState tcp /= TimeWait && needsDelayedAck tcp) ack----- Timer Interaction --------------------------------------------------------------- | Update timers, decrementing to 0 ones that expire, and incrementing the--- others.-updateTimers :: Sock ()-updateTimers  =-  modifyTcpSocket_ $ \ tcp ->-    let tt = tcpTimers tcp-     in tcp { tcpTimers = tt-              { tt2MSL = decrement (tt2MSL tt)-              , ttIdle = increment (ttIdle tt)-              }-            }-  where-  decrement 0   = 0-  decrement val = pred val-  increment     = succ----- | Conditionally run a timer, when this slow-timer tick will decrement it to--- zero.-whenTimer :: (TcpTimers -> SlowTicks) -> Sock () -> Sock ()-whenTimer prj body = do-  tt <- getTcpTimers-  when (prj tt == 1) body---- | Conditionally run an action when the connection has been idle for at least--- timeout @SlowTick@s.-whenIdleFor :: Int -> Sock () -> Sock ()-whenIdleFor timeout body = do-  tt <- getTcpTimers-  when (ttIdle tt >= timeout) body----- 2MSL ---------------------------------------------------------------------------- | Set the value of the 2MSL timer.-set2MSL :: SlowTicks -> Sock ()-set2MSL val = modifyTcpTimers_ (\tt -> tt { tt2MSL = val })---- | 75 second delay.-tcpKeepIntVal :: SlowTicks-tcpKeepIntVal  = 150---- | The timer that handles the TIME_WAIT, as well as the idle timeout.-handle2MSL :: Sock ()-handle2MSL  =-  do whenTimer tt2MSL $-       do TcpSocket { .. } <- getTcpSocket-          let TcpTimers { .. } = tcpTimers-          if tcpState /= TimeWait && ttIdle <= ttMaxIdle-             then set2MSL tcpKeepIntVal-             else closeSocket---- RTO ----------------------------------------------------------------------------- | Update segments in the outgoing window, decrementing their RTO timers, and--- retransmitting them if it expires.-handleRTO :: Sock ()-handleRTO  = do-  s <- getState-  when (s /= Closed) (F.mapM_ outputSegment =<< modifyTcpSocket update)-  where-  update tcp = (segs,tcp { tcpOut = win' })-    where-    (segs,win') = genRetransmitSegments (tcpOut tcp)---- | Calibrate the RTO timer, as specified by RFC-6298.-calibrateRTO :: POSIXTime -> POSIXTime -> TcpTimers -> TcpTimers-calibrateRTO sent ackd tt-  | ttSRTT tt == 0 = initial-  | otherwise      = rolling-  where--  -- round trip measurement-  r = ackd - sent--  -- no data has been sent before, seed the RTO values.-  initial = updateRTO tt-    { ttSRTT   = r-    , ttRTTVar = r / 2-    }--  -- data has been sent, update based on previous values-  alpha   = 0.125-  beta    = 0.25-  rttvar  = (1 - beta)  * ttRTTVar tt + beta  * abs (ttSRTT tt * r)-  srtt    = (1 - alpha) * ttSRTT   tt + alpha * r-  rolling = updateRTO tt-    { ttRTTVar = rttvar-    , ttSRTT   = srtt-    }--  -- update the RTO timer length, bounding it at 64 seconds (128 ticks)-  updateRTO tt' = tt'-    { ttRTO = min 128 (ceiling (ttSRTT tt' + max 0.5 (2 * ttRTTVar tt')))-    }
− src/Hans/Layer/Tcp/Types.hs
@@ -1,304 +0,0 @@-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE RecordWildCards #-}--module Hans.Layer.Tcp.Types where--import Hans.Address.IP4-import Hans.Layer.Tcp.Window-import Hans.Message.Tcp-import Hans.Ports--import Control.Exception (Exception,SomeException,toException)-import Control.Monad ( guard )-import Data.Time.Clock.POSIX (POSIXTime)-import Data.Int (Int64)-import Data.Maybe (fromMaybe)-import Data.Word (Word16,Word32)-import qualified Data.Map.Strict as Map-import qualified Data.Sequence as Seq----- Hosts Information -------------------------------------------------------------data Host = Host-  { hostConnections   :: !Connections-  , hostTimeWaits     :: !TimeWaitConnections-  , hostInitialSeqNum :: {-# UNPACK #-} !TcpSeqNum-  , hostPorts         :: !(PortManager TcpPort)-  , hostLastUpdate    :: !POSIXTime-  }--emptyHost :: POSIXTime -> Host-emptyHost start = Host-  { hostConnections   = Map.empty-  , hostTimeWaits     = Map.empty-    -- XXX what should we seed this with?-  , hostInitialSeqNum = 0-  , hostPorts         = emptyPortManager [32768 .. 61000]-  , hostLastUpdate    = start-  }--takePort :: Host -> Maybe (TcpPort,Host)-takePort host = do-  (p,ps) <- nextPort (hostPorts host)-  return (p, host { hostPorts = ps })--releasePort :: TcpPort -> Host -> Host-releasePort p host = fromMaybe host $ do-  ps <- unreserve p (hostPorts host)-  return host { hostPorts = ps }----- Connections -------------------------------------------------------------------type Connections = Map.Map SocketId TcpSocket--removeClosed :: Connections -> Connections-removeClosed  =-  Map.filter (\tcp -> tcpState tcp /= Closed || not (tcpUserClosed tcp))--data SocketId = SocketId-  { sidLocalPort  :: {-# UNPACK #-} !TcpPort-  , sidRemotePort :: {-# UNPACK #-} !TcpPort-  , sidRemoteHost :: {-# UNPACK #-} !IP4-  } deriving (Eq,Show,Ord)--emptySocketId :: SocketId-emptySocketId  = SocketId-  { sidLocalPort  = TcpPort 0-  , sidRemotePort = TcpPort 0-  , sidRemoteHost = IP4 0 0 0 0-  }--listenSocketId :: TcpPort -> SocketId-listenSocketId port = emptySocketId { sidLocalPort = port }--incomingSocketId :: IP4 -> TcpHeader -> SocketId-incomingSocketId remote hdr = SocketId-  { sidLocalPort  = tcpDestPort hdr-  , sidRemotePort = tcpSourcePort hdr-  , sidRemoteHost = remote-  }--data SocketResult a-  = SocketResult a-  | SocketError SomeException-    deriving (Show)--socketError :: Exception e => e -> SocketResult a-socketError  = SocketError . toException----- Connections in TimeWait --------------------------------------------------------- | The socket that's in TimeWait, plus its current 2MSL value.-type TimeWaitConnections = Map.Map SocketId TimeWaitSock--data TimeWaitSock = TimeWaitSock { tw2MSL      :: {-# UNPACK #-} !SlowTicks-                                   -- ^ The current 2MSL value-                                 , twInit2MSL  :: {-# UNPACK #-} !SlowTicks-                                   -- ^ The initial 2MSL value-                                 , twSeqNum    :: {-# UNPACK #-} !TcpSeqNum-                                   -- ^ The sequence number to use when-                                   -- responding to messages-                                 , twTimestamp :: !(Maybe Timestamp)-                                   -- ^ The last timestamp used-                                 } deriving (Show)--twReset2MSL :: TimeWaitSock -> TimeWaitSock-twReset2MSL tw = tw { tw2MSL = twInit2MSL tw }--mkTimeWait :: TcpSocket -> TimeWaitSock-mkTimeWait TcpSocket { .. } =-  TimeWaitSock { tw2MSL      = timeout-               , twInit2MSL  = timeout-               , twSeqNum    = tcpSndNxt-               , twTimestamp = tcpTimestamp-               }-  where-  timeout | tt2MSL tcpTimers <= 0 = 2 * mslTimeout-          | otherwise             = tt2MSL tcpTimers---- | Add a socket to the TimeWait map.-addTimeWait :: TcpSocket -> TimeWaitConnections -> TimeWaitConnections-addTimeWait tcp socks = Map.insert (tcpSocketId tcp) (mkTimeWait tcp) socks---- | Take one step, collecting any connections whose 2MSL timer goes to 0.-stepTimeWaitConnections :: TimeWaitConnections -> TimeWaitConnections-stepTimeWaitConnections  = Map.mapMaybe $ \ tw ->-  do guard (tw2MSL tw > 0)-     return tw { tw2MSL = tw2MSL tw - 1 }----- Timers ------------------------------------------------------------------------type SlowTicks = Int---- | MSL is 60 seconds, which is slightly more aggressive than the 2 minutes--- from the original RFC.-mslTimeout :: SlowTicks-mslTimeout = 2 * 60--data TcpTimers = TcpTimers-  { ttDelayedAck :: !Bool--    -- 2MSL-  , tt2MSL       :: {-# UNPACK #-} !SlowTicks--    -- retransmit timer-  , ttRTO        :: {-# UNPACK #-} !SlowTicks-  , ttSRTT       :: !POSIXTime-  , ttRTTVar     :: !POSIXTime--    -- idle timer-  , ttMaxIdle    :: {-# UNPACK #-} !SlowTicks-  , ttIdle       :: {-# UNPACK #-} !SlowTicks-  }--emptyTcpTimers :: TcpTimers-emptyTcpTimers  = TcpTimers-  { ttDelayedAck = False-  , tt2MSL       = 0-  , ttRTO        = 2 -- one second-  , ttSRTT       = 0-  , ttRTTVar     = 0-  , ttMaxIdle    = 10 * 60 * 2-  , ttIdle       = 0-  }----- Timestamp Management ------------------------------------------------------------ | Manage the timestamp values that are in flight between two hosts.-data Timestamp = Timestamp-  { tsTimestamp     :: {-# UNPACK #-} !Word32-  , tsLastTimestamp :: {-# UNPACK #-} !Word32-  , tsGranularity   :: !POSIXTime -- ^ Hz-  , tsLastUpdate    :: !POSIXTime-  } deriving (Show)--emptyTimestamp :: POSIXTime -> Timestamp-emptyTimestamp start = Timestamp-  { tsTimestamp     = 0-  , tsLastTimestamp = 0-  , tsGranularity   = 200-  , tsLastUpdate    = start-  }---- | Update the timestamp value, advancing based on the timestamp granularity.--- If the number of ticks to advance is 0, don't advance the timestamp.-stepTimestamp :: POSIXTime -> Timestamp -> Timestamp-stepTimestamp now ts-  | diff == 0 = ts-  | otherwise = ts-    { tsLastUpdate = now-    , tsTimestamp  = tsTimestamp ts + diff-    }-  where-  diff = ceiling (tsGranularity ts * (now - tsLastUpdate ts))---- | Generate timestamp option for an outgoing packet.-mkTimestamp :: Timestamp -> TcpOption-mkTimestamp ts = OptTimestamp (tsTimestamp ts) (tsLastTimestamp ts)----- Internal Sockets --------------------------------------------------------------type Acceptor = SocketId -> IO ()--type Notify = Bool -> IO ()--type Close = IO ()--data TcpSocket = TcpSocket-  { tcpParent      :: !(Maybe SocketId)-  , tcpSocketId    :: {-# UNPACK #-} !SocketId-  , tcpState       :: !ConnState-  , tcpAcceptors   :: !(Seq.Seq Acceptor)-  , tcpNotify      :: !(Maybe Notify)-  , tcpIss         :: {-# UNPACK #-} !TcpSeqNum-  , tcpSndNxt      :: {-# UNPACK #-} !TcpSeqNum-  , tcpSndUna      :: {-# UNPACK #-} !TcpSeqNum--  , tcpUserClosed  :: !Bool-  , tcpOut         :: !RemoteWindow-  , tcpOutBuffer   :: !(Buffer Outgoing)-  , tcpOutMSS      :: {-# UNPACK #-} !Int64-  , tcpIn          :: !LocalWindow-  , tcpInBuffer    :: !(Buffer Incoming)-  , tcpInMSS       :: {-# UNPACK #-} !Int64--  , tcpTimers      :: {-# UNPACK #-} !TcpTimers-  , tcpTimestamp   :: !(Maybe Timestamp)--  , tcpSack        :: !Bool-  , tcpWindowScale :: !Bool-  }--emptyTcpSocket :: Word16 -> Int -> TcpSocket-emptyTcpSocket sendWindow sendScale = TcpSocket-  { tcpParent      = Nothing-  , tcpSocketId    = emptySocketId-  , tcpState       = Closed-  , tcpAcceptors   = Seq.empty-  , tcpNotify      = Nothing-  , tcpIss         = 0-  , tcpSndNxt      = 0-  , tcpSndUna      = 0--  , tcpUserClosed  = False-  , tcpOut         = emptyRemoteWindow sendWindow sendScale-  , tcpOutBuffer   = emptyBuffer 16384-  , tcpOutMSS      = defaultMSS-  , tcpIn          = emptyLocalWindow 0 14600 0-  , tcpInBuffer    = emptyBuffer 16384-  , tcpInMSS       = defaultMSS--  , tcpTimers      = emptyTcpTimers-  , tcpTimestamp   = Nothing--  , tcpSack        = True-  , tcpWindowScale = True-  }--defaultMSS :: Int64-defaultMSS  = 1460--nothingOutstanding :: TcpSocket -> Bool-nothingOutstanding TcpSocket { .. } = tcpSndUna == tcpSndNxt--tcpRcvNxt :: TcpSocket -> TcpSeqNum-tcpRcvNxt = lwRcvNxt . tcpIn--inRcvWnd :: TcpSeqNum -> TcpSocket -> Bool-inRcvWnd (TcpSeqNum sn) TcpSocket { .. } =-  rcvNxt <= sn && sn < rcvNxt + fromIntegral (lwRcvWind tcpIn)-  where-  TcpSeqNum rcvNxt = lwRcvNxt tcpIn--nextSegSize :: TcpSocket -> Int64-nextSegSize tcp =-  min (fromIntegral (rwAvailable (tcpOut tcp))) (tcpOutMSS tcp)--isAccepting :: TcpSocket -> Bool-isAccepting  = not . Seq.null . tcpAcceptors--needsDelayedAck :: TcpSocket -> Bool-needsDelayedAck  = ttDelayedAck . tcpTimers--mkMSS :: TcpSocket -> TcpOption-mkMSS tcp = OptMaxSegmentSize (fromIntegral (tcpInMSS tcp))--data ConnState-  = Closed-  | Listen-  | SynSent-  | SynReceived-  | Established-  | CloseWait-  | FinWait1-  | FinWait2-  | Closing-  | LastAck-  | TimeWait-    deriving (Show,Eq,Ord)
− src/Hans/Layer/Tcp/WaitBuffer.hs
@@ -1,159 +0,0 @@-{-# LANGUAGE EmptyDataDecls #-}--module Hans.Layer.Tcp.WaitBuffer (-    -- * Delayed work-    Wakeup-  , tryAgain-  , abort--    -- * Directions-  , Incoming, Outgoing--    -- * Directed Buffers-  , Buffer-  , emptyBuffer-  , isFull-  , isEmpty-  , flushWaiting--    -- ** Application Side-  , writeBytes-  , readBytes--    -- ** Kernel Side-  , takeBytes-  , putBytes-  ) where--import Control.Monad (guard)-import Data.Int (Int64)-import qualified Data.ByteString.Lazy as L-import qualified Data.Foldable as F-import qualified Data.Sequence as Seq----- Chunk Buffering ----------------------------------------------------------------- | The boolean parameter indicates whether or not the wakeup is happening in--- the event of a shutdown, or if the original request should be re-run,--- allowing more data to flow.-type Wakeup = Bool -> IO ()---- | Indicate that the action should be retried.-tryAgain :: Wakeup -> IO ()-tryAgain f = f True---- | Indicate that the original action will never succeed.-abort :: Wakeup -> IO ()-abort f = f False---- | Incoming data.-data Incoming---- | Outgoing data.-data Outgoing---- | Data Buffers, in a direction.-data Buffer d = Buffer-  { bufBytes     :: !L.ByteString-  , bufWaiting   :: !(Seq.Seq Wakeup)-  , bufSize      :: {-# UNPACK #-} !Int64-  , bufAvailable :: {-# UNPACK #-} !Int64-  }---- | An empty buffer, with a limit.-emptyBuffer :: Int64 -> Buffer d-emptyBuffer size = Buffer-  { bufBytes     = L.empty-  , bufWaiting   = Seq.empty-  , bufSize      = size-  , bufAvailable = size-  }---- | A queue is empty when all space is available.-isEmpty :: Buffer d -> Bool-isEmpty buf = bufAvailable buf == bufSize buf---- | A queue is full when there is no available space.-isFull :: Buffer d -> Bool-isFull buf = bufAvailable buf == 0---- | Flush the queue of blocked processes, returning an IO action that--- negatively acks the waiting processes, and a buffer with an empty wait queue.-flushWaiting :: Buffer d -> (IO (), Buffer d)-flushWaiting buf = (F.traverse_ abort (bufWaiting buf), buf { bufWaiting = Seq.empty })---- | Queue a wakeup action into a buffer.-queueWaiting :: Wakeup -> Buffer d -> Buffer d-queueWaiting wakeup buf = buf { bufWaiting = bufWaiting buf Seq.|> wakeup }---- | Queue bytes into a buffer that has some available space.  The first result--- is the number of bytes written (0, in the Nothing case), and the second is--- the new buffer.-queueBytes :: L.ByteString -> Buffer d -> (Maybe Int64, Buffer d)-queueBytes bytes buf-  | bufAvailable buf <= 0 = (Nothing,buf)-  | otherwise             = (Just qlen, buf')-  where-  queued = L.take (bufAvailable buf) bytes-  qlen   = L.length queued-  buf'   = buf-    { bufBytes     = bufBytes buf `L.append` queued-    , bufAvailable = bufAvailable buf - qlen-    }---- | Take bytes off of a buffer.  When the buffer is empty, this returns--- Nothing, otherwise it returns (<= len) bytes and the buffer with that number--- of bytes removed.-removeBytes :: Int64 -> Buffer d -> Maybe (L.ByteString, Buffer d)-removeBytes len buf = do-  guard (not (L.null (bufBytes buf)))-  let (bytes,rest) = L.splitAt len (bufBytes buf)-      buf' = buf-        { bufBytes     = rest-        , bufAvailable = bufAvailable buf + L.length bytes-        }-  return (bytes,buf')----- Sending Buffer ------------------------------------------------------------------ | Queue bytes in an outgoing buffer.  When there's no space available in the--- buffer, the wakeup action is queued.-writeBytes :: L.ByteString -> Wakeup -> Buffer Outgoing-           -> (Maybe Int64,Buffer Outgoing)-writeBytes bytes wakeup buf = case queueBytes bytes buf of-  (Nothing,buf') -> (Nothing,queueWaiting wakeup buf')-  res            -> res---- | Take bytes off of a sending queue, making room new data.-takeBytes :: Int64 -> Buffer Outgoing-          -> Maybe (Maybe Wakeup,L.ByteString,Buffer Outgoing)-takeBytes len buf = do-  (bytes,buf') <- removeBytes len buf-  case Seq.viewl (bufWaiting buf') of-    Seq.EmptyL  -> return (Nothing, bytes, buf')-    w Seq.:< ws -> return (Just w, bytes, buf' { bufWaiting = ws })----- Receiving Buffer ---------------------------------------------------------------- | Read bytes from an incoming buffer, queueing if there are no bytes to read.-readBytes :: Int64 -> Wakeup -> Buffer Incoming-          -> (Maybe L.ByteString, Buffer Incoming)-readBytes len wakeup buf =-  case removeBytes len buf of-    Just (bytes,buf') -> (Just bytes, buf')-    Nothing           -> (Nothing,    queueWaiting wakeup buf)---- | Place bytes on the incoming buffer, provided that there is enough space for--- all of the bytes.-putBytes :: L.ByteString -> Buffer Incoming-         -> Maybe (Maybe Wakeup,Buffer Incoming)-putBytes bytes buf = do-  let needed = L.length bytes + L.length (bufBytes buf)-  guard (needed < bufSize buf)-  let buf' = buf { bufBytes = bufBytes buf `L.append` bytes }-  case Seq.viewl (bufWaiting buf') of-    Seq.EmptyL  -> return (Nothing, buf')-    w Seq.:< ws -> return (Just w, buf' { bufWaiting = ws })
− src/Hans/Layer/Tcp/Window.hs
@@ -1,299 +0,0 @@-{-# LANGUAGE RecordWildCards #-}--module Hans.Layer.Tcp.Window (-    module Hans.Layer.Tcp.Window-  , module Hans.Layer.Tcp.WaitBuffer-  ) where--import Hans.Layer.Tcp.WaitBuffer-import Hans.Message.Tcp--import Control.Monad (mzero)-import Data.Bits (shiftL)-import Data.Time.Clock.POSIX (POSIXTime)-import Data.Word (Word16,Word32)-import qualified Data.ByteString as S-import qualified Data.ByteString.Lazy as L-import qualified Data.Foldable as F-import qualified Data.Sequence as Seq-import qualified Data.Traversable as T----- Remote Window ------------------------------------------------------------------- | Remote window management.-data RemoteWindow = RemoteWindow-  { rwSegments     :: !OutSegments-  , rwAvailable    :: {-# UNPACK #-} !Word32-  , rwSize         :: {-# UNPACK #-} !Word32-  , rwSndWind      :: {-# UNPACK #-} !Word16-  , rwSndWindScale :: {-# UNPACK #-} !Int-  } deriving (Show)---- | The empty window, seeded with an initial size.-emptyRemoteWindow :: Word16 -> Int -> RemoteWindow-emptyRemoteWindow size scale = refreshRemoteWindow $! RemoteWindow-  { rwSegments     = Seq.empty-  , rwAvailable    = 0-  , rwSize         = 0-  , rwSndWind      = fromIntegral size-  , rwSndWindScale = scale-  }---- | Recalculate internal constants of the remote window.-refreshRemoteWindow :: RemoteWindow -> RemoteWindow-refreshRemoteWindow rw = rw-  { rwSize      = size-  , rwAvailable = avail-  }-  where-  size                = fromIntegral (rwSndWind rw) `shiftL` rwSndWindScale rw-  used                = rwSize rw - rwAvailable rw-  avail | used > size = 0-        | otherwise   = size - used----- | Set the Snd.Wind.Scale variable for the remote window.-setSndWindScale :: Int -> RemoteWindow -> RemoteWindow-setSndWindScale scale rw = refreshRemoteWindow $! rw-  { rwSndWindScale = scale-  }---- | Set the Snd.Wind variable for the remote window.-setSndWind :: Word16 -> RemoteWindow -> RemoteWindow-setSndWind size rw = refreshRemoteWindow $! rw-  { rwSndWind = size-  }---- | Adjust the internal available counter.-releaseSpace :: Word32 -> RemoteWindow -> RemoteWindow-releaseSpace len rw = rw-  { rwAvailable = min (rwSize rw) (rwAvailable rw + len)-  }---- | Add a segment to the window.-addSegment :: OutSegment -> RemoteWindow -> RemoteWindow-addSegment seg win = win-  { rwSegments  = rwSegments win Seq.|> seg-  , rwAvailable = rwAvailable win - outSize seg-  }---- | Process an incoming ack, returning a finalizer, and a new window if there--- was a matching set of packets waiting for an ack.-receiveAck :: TcpHeader -> RemoteWindow -> Maybe (OutSegment,RemoteWindow)-receiveAck hdr win = do--  -- XXX this doesn't deal with partial acks-  let match seg   = tcpSeqNum (outHeader seg) < tcpAckNum hdr-                 && outAckNum seg <= tcpAckNum hdr-      (acks,rest) = Seq.spanl match (rwSegments win)--  -- require that there was something to ack.-  case Seq.viewr acks of-    _ Seq.:> seg -> do-      let len  = F.sum (outSize `fmap` acks)-          win' = setSndWind (tcpWindow hdr)-               $ releaseSpace len-               $ win { rwSegments = rest }-      return (seg, win')-    Seq.EmptyR -> mzero----- | Update the RTO timer on all segments waiting for an ack.  When the timer--- runs out, output the segment for retransmission.-genRetransmitSegments :: RemoteWindow -> (OutSegments,RemoteWindow)-genRetransmitSegments win = (outSegs, win { rwSegments = segs' })-  where-  (outSegs,segs') = T.mapAccumL step Seq.empty (rwSegments win)-  step rts seg = (rts',seg')-    where-    seg'                    = decrementRTO seg-    rts' | outRTO seg' <= 0 = rts Seq.|> seg'-         | otherwise        = rts--clearRetransmit :: RemoteWindow -> RemoteWindow-clearRetransmit rw = rw { rwSegments = Seq.empty }--retransmitEmpty :: RemoteWindow -> Bool-retransmitEmpty rw = Seq.null (rwSegments rw)---type OutSegments = Seq.Seq OutSegment---- | A delivered segment.-data OutSegment = OutSegment-  { outAckNum :: {-# UNPACK #-} !TcpSeqNum-  , outTime   :: !POSIXTime-  , outFresh  :: !Bool               -- ^ Whether or not this is a retransmission-  , outRTO    :: {-# UNPACK #-} !Int -- ^ Retransmit timer for this segment-  , outHeader :: !TcpHeader-  , outBody   :: !L.ByteString-  } deriving (Show)--mkOutSegment :: POSIXTime -> Int -> TcpHeader -> L.ByteString -> OutSegment-mkOutSegment created rto hdr body =-  OutSegment { outAckNum = tcpSeqNum hdr + outSize' hdr body-             , outTime   = created-             , outFresh  = True-             , outRTO    = rto-             , outHeader = hdr-             , outBody   = body-             }--ctlLength :: Num len => Bool -> len-ctlLength True  = 1-ctlLength False = 0--outSize :: Num a => OutSegment -> a-outSize OutSegment { .. } = outSize' outHeader outBody---- | The size of a segment body.-outSize' :: Num a => TcpHeader -> L.ByteString -> a-outSize' TcpHeader { .. } body = fromIntegral (L.length body)-                               + ctlLength tcpSyn-                               + ctlLength tcpFin----- | Decrement the RTO value on a segment by the timer granularity (500ms).--- Once the RTO value dips below 1, mark the segment as no longer fresh.-decrementRTO :: OutSegment -> OutSegment-decrementRTO seg-  | outRTO seg' <= 0 = seg' { outFresh = False }-  | otherwise        = seg'-  where-  seg' = seg { outRTO = outRTO seg - 1 }----- Local Window -------------------------------------------------------------------- | Local window, containing a buffer of incoming packets, indexed by their--- sequence number, relative to RCV.NXT.------ XXX make this respect the values of size and available-data LocalWindow = LocalWindow-  { lwBuffer       :: Seq.Seq InSegment-  , lwRcvNxt       :: !TcpSeqNum-  , lwAvailable    :: !Word32-  , lwSize         :: !Word32-  , lwRcvWind      :: !Word16-  , lwRcvWindScale :: !Int-  } deriving (Show)---- | Empty local buffer, with an initial sequence number as the next expected--- sequence number.-emptyLocalWindow :: TcpSeqNum -> Word16 -> Int -> LocalWindow-emptyLocalWindow sn size scale = refreshLocalWindow $! LocalWindow-  { lwBuffer       = Seq.empty-  , lwRcvNxt       = sn-  , lwAvailable    = 0-  , lwSize         = 0-  , lwRcvWind      = size-  , lwRcvWindScale = scale-  }---- | Produce a sequence of blocks for the sack option.-localWindowSackBlocks :: LocalWindow -> Seq.Seq SackBlock-localWindowSackBlocks lw = case Seq.viewl (fmap mkSackBlock (lwBuffer lw)) of-  b Seq.:< rest -> uncurry (Seq.|>) (F.foldl step (Seq.empty,b) rest)-  Seq.EmptyL    -> Seq.empty-  where-  step (bs,b) b'-    | sbRight b == sbLeft b' = (bs,b { sbRight = sbRight b' })-    | otherwise              = (bs Seq.|> b, b')---- | Recalculate internal constants.-refreshLocalWindow :: LocalWindow -> LocalWindow-refreshLocalWindow lw = lw-  { lwSize = fromIntegral (lwRcvWind lw) `shiftL` lwRcvWindScale lw-  }---- | Update the size of the remote window.-setRcvNxt :: TcpSeqNum -> LocalWindow -> LocalWindow-setRcvNxt sn lw = lw { lwRcvNxt = sn }---- | Add a sequence number to the value of Rcv.Nxt.-addRcvNxt :: TcpSeqNum -> LocalWindow -> LocalWindow-addRcvNxt sn win = win { lwRcvNxt = lwRcvNxt win + sn }---- | Set the Rcv.Wind variable for the local window.-setRcvWind :: Word16 -> LocalWindow -> LocalWindow-setRcvWind size lw = refreshLocalWindow $! lw-  { lwRcvWind = size-  }---- | Set the Rcv.Wind.Scale variable in the local window.-setRcvWindScale :: Int -> LocalWindow -> LocalWindow-setRcvWindScale scale lw = refreshLocalWindow $! lw-  { lwRcvWindScale = scale-  }---- | Process an incoming packet that needs to pass through the incoming queue.-incomingPacket :: TcpHeader -> S.ByteString -> LocalWindow-               -> (Seq.Seq InSegment, LocalWindow)-incomingPacket hdr body win = stepWindow (addInSegment hdr body win)---- | Queue an incoming packet in the incoming window.-addInSegment :: TcpHeader -> S.ByteString -> LocalWindow -> LocalWindow-addInSegment hdr body win = win { lwBuffer = insert (lwBuffer win) }-  where-  seg        = mkInSegment (lwRcvNxt win) hdr body-  insert buf = case Seq.viewl buf of-    a Seq.:< rest -> case compare (inRelSeqNum a) (inRelSeqNum seg) of-      LT -> a   Seq.<| insert rest-      EQ -> seg Seq.<| rest-      GT -> seg Seq.<| buf-    Seq.EmptyL    -> Seq.singleton seg---- | Advance the window, if there are packets available to be returned.-stepWindow :: LocalWindow -> (Seq.Seq InSegment, LocalWindow)-stepWindow  = loop 0 Seq.empty-  where-  loop expect segs win = case Seq.viewl (lwBuffer win) of-    a Seq.:< rest | inRelSeqNum a == expect -> keep a segs rest win-                  | otherwise               -> finalize segs win-    Seq.EmptyL                              -> finalize segs win--  keep seg segs rest win = loop (inRelSeqNum seg + len) (segs Seq.|> seg) win-    { lwBuffer = rest-    , lwRcvNxt = lwRcvNxt win + len-    }-    where-    len = fromIntegral (S.length (inBody seg))--  finalize segs win = (segs,win { lwBuffer = update `fmap` lwBuffer win })-    where-    len       = F.sum (inLength `fmap` segs)-    update is = is { inRelSeqNum = inRelSeqNum is - len }--data InSegment = InSegment-  { inRelSeqNum :: !TcpSeqNum-  , inHeader    :: !TcpHeader-  , inBody      :: !S.ByteString-  } deriving (Show)--inSeqNum :: InSegment -> TcpSeqNum-inSeqNum  = tcpSeqNum . inHeader--inLength :: Num a => InSegment -> a-inLength  = fromIntegral . S.length . inBody---- | Generate an incoming segment, relative to the value of RCV.NXT-mkInSegment :: TcpSeqNum -> TcpHeader -> S.ByteString -> InSegment-mkInSegment rcvNxt hdr body = InSegment-  { inRelSeqNum = rel-  , inHeader    = hdr-  , inBody      = body-  }-  where-  -- calculate the index, relative to RCV.NXT, taking sequence number wrap into-  -- account-  rel | tcpSeqNum hdr < rcvNxt = maxBound      - rcvNxt + tcpSeqNum hdr + 1-      | otherwise              = tcpSeqNum hdr - rcvNxt--mkSackBlock :: InSegment -> SackBlock-mkSackBlock is = SackBlock-  { sbLeft  = sn-  , sbRight = sn + fromIntegral (S.length (inBody is))-  }-  where-  sn = tcpSeqNum (inHeader is)
− src/Hans/Layer/Udp.hs
@@ -1,211 +0,0 @@-{-# LANGUAGE BangPatterns          #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE TypeSynonymInstances  #-}-{-# LANGUAGE FlexibleInstances     #-}-{-# LANGUAGE DeriveDataTypeable #-}--module Hans.Layer.Udp (-    UdpHandle-  , UdpException-  , runUdpLayer--  , queueUdp-  , sendUdp-  , Handler-  , addUdpHandler-  , addUdpHandlerAnyPort-  , removeUdpHandler-  ) where--import Hans.Address.IP4-import Hans.Channel-import Hans.Layer-import Hans.Message.Icmp4-import Hans.Message.Ip4-import Hans.Message.Udp-import Hans.Ports-import Hans.Utils-import qualified Hans.Layer.IP4 as IP4-import qualified Hans.Layer.Icmp4 as Icmp4--import Control.Concurrent (forkIO,newEmptyMVar,takeMVar,putMVar)-import Control.Monad (guard,mplus,when)-import Data.Maybe (isNothing)-import Data.Serialize.Get (runGet)-import Data.Typeable (Typeable)-import MonadLib (get,set)-import qualified Control.Exception    as X-import qualified Data.ByteString.Lazy as L-import qualified Data.ByteString      as S---type Handler = IP4 -> UdpPort -> S.ByteString -> IO ()--type UdpHandle = Channel (Udp ())--data UdpException = NoPortsAvailable-                  | PortInUse UdpPort-                    deriving (Show,Typeable)--instance X.Exception UdpException--runUdpLayer :: UdpHandle -> IP4.IP4Handle -> Icmp4.Icmp4Handle -> IO ()-runUdpLayer h ip4 icmp4 = do-  IP4.addIP4Handler ip4 udpProtocol (queueUdp h)-  void (forkIO (loopLayer "udp" (emptyUdp4State ip4 icmp4) (receive h) id))---- | Send a UDP datagram.  When the source port is given, this will send using--- that source port.  If the source port is not given, a fresh one is used, and--- immediately recycled.------ NOTE: this doesn't prevent you from sending messages on a port that another--- thread is already using.  This is a funky design, and we'd be better suited--- by introducing a UdpSocket type.-sendUdp :: UdpHandle -> IP4 -> Maybe UdpPort -> UdpPort -> L.ByteString -> IO ()-sendUdp h !dst (Just sp) !dp !bs =-  send h (handleOutgoing dst sp dp bs)-sendUdp h !dst Nothing !dp !bs = do-  res <- newEmptyMVar--  send h $ do e <- allocPort-              case e of-                Right sp ->-                  do handleOutgoing dst sp dp bs-                     freePort sp-                     output (putMVar res Nothing)--                Left err -> output (putMVar res (Just err))--  mbErr <- takeMVar res-  case mbErr of-    Nothing  -> return ()-    Just err -> X.throwIO err---- | Queue an incoming udp message from the IP4 layer.-queueUdp :: UdpHandle -> IP4Header -> S.ByteString -> IO ()-queueUdp h !ip4 !bs = send h (handleIncoming ip4 bs)---- | Add a handler for incoming udp datagrams on a specific port.-addUdpHandler :: UdpHandle -> UdpPort -> Handler -> IO ()-addUdpHandler h sp k = do-  res <- newEmptyMVar-  send h $ do mb <- reservePort sp-              when (isNothing mb) (addHandler sp k)-              output (putMVar res mb)-  mb <- takeMVar res-  case mb of-    Nothing  -> return ()-    Just err -> X.throwIO err---- | Add a handler for incoming udp datagrams on a freshly allocated port.-addUdpHandlerAnyPort :: UdpHandle -> (UdpPort -> Handler) -> IO UdpPort-addUdpHandlerAnyPort h k = do-  res <- newEmptyMVar-  send h $ do e <- allocPort-              case e of-                Right sp -> addHandler sp (k sp)-                Left _   -> return ()-              output (putMVar res e)--  e <- takeMVar res-  case e of-    Right sp -> return sp-    Left err -> X.throwIO err---- | Remove a handler present on the port given.-removeUdpHandler :: UdpHandle -> UdpPort -> IO ()-removeUdpHandler h !sp = send h $ do freePort sp-                                     removeHandler sp----- Udp State ---------------------------------------------------------------------type Udp = Layer UdpState--data UdpState = UdpState-  { udpPorts       :: !(PortManager UdpPort)-  , udpHandlers    :: !(Handlers UdpPort Handler)-  , udpIp4Handle   :: {-# UNPACK #-} !IP4.IP4Handle-  , udpIcmp4Handle :: {-# UNPACK #-} !Icmp4.Icmp4Handle-  }--emptyUdp4State :: IP4.IP4Handle -> Icmp4.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 ---------------------------------------------------------------------modifyPortManager :: (PortManager UdpPort -> (a,PortManager UdpPort)) -> Udp a-modifyPortManager f = do-  state <- get-  let (a,pm') = f (udpPorts state)-  pm' `seq` set state { udpPorts = pm' }-  return a--ip4Handle :: Udp IP4.IP4Handle-ip4Handle  = udpIp4Handle `fmap` get--icmp4Handle :: Udp Icmp4.Icmp4Handle-icmp4Handle  = udpIcmp4Handle `fmap` get--allocPort :: Udp (Either UdpException UdpPort)-allocPort = modifyPortManager $ \pm ->-              case nextPort pm of-                Just (p,pm') -> (Right p,pm')-                Nothing      -> (Left NoPortsAvailable,pm)--reservePort :: UdpPort -> Udp (Maybe UdpException)-reservePort sp = modifyPortManager $ \ pm ->-                   case reserve sp pm of-                     Just pm' -> (Nothing,pm')-                     Nothing  -> (Just (PortInUse sp), pm)--freePort :: UdpPort -> Udp ()-freePort sp = modifyPortManager $ \ pm ->-                case unreserve sp pm of-                  Just pm' -> ((), pm')-                  Nothing  -> ((), pm )----- Message Handling --------------------------------------------------------------handleIncoming :: IP4Header -> S.ByteString -> Udp ()-handleIncoming ip4 bs = do-  let src = ip4SourceAddr ip4-  guard (validateUdpChecksum src (ip4DestAddr ip4) bs)-  (hdr,bytes) <- liftRight (runGet parseUdpPacket bs)-  listening src hdr bytes `mplus` unreachable ip4 bs--listening :: IP4 -> UdpHeader -> S.ByteString -> Udp ()-listening src hdr bytes = do-  h <- getHandler (udpDestPort hdr)-  output $ do _ <- forkIO (h src (udpSourcePort hdr) bytes)-              return ()---- | Deliver a destination unreachable mesasge, via the icmp layer.-unreachable :: IP4Header -> S.ByteString -> Udp ()-unreachable hdr orig = do-  icmp4 <- icmp4Handle-  output (Icmp4.destUnreachable icmp4 PortUnreachable hdr (S.length orig) orig)--handleOutgoing :: IP4 -> UdpPort -> UdpPort -> L.ByteString -> Udp ()-handleOutgoing dst sp dp bs = do-  ip4 <- ip4Handle-  let hdr = UdpHeader sp dp 0-  output $ IP4.withIP4Source ip4 dst $ \ src -> do-    let ip4Hdr = emptyIP4Header-          { ip4DestAddr     = dst-          , ip4Protocol     = udpProtocol-          , ip4DontFragment = False-          }-    pkt <- renderUdpPacket hdr bs (mkIP4PseudoHeader src dst udpProtocol)-    IP4.sendIP4Packet ip4 ip4Hdr pkt
+ src/Hans/Lens.hs view
@@ -0,0 +1,126 @@+{-# LANGUAGE Rank2Types #-}+{-# LANGUAGE DeriveFunctor #-}++module Hans.Lens (++    -- * Lenses+    Lens, Lens',+    lens,++    -- ** Getters+    Getting,+    Getter,+    view,+    to,++    -- ** Setters+    ASetter, ASetter',+    set,+    over,+    modify,++    -- * Utility Lenses+    bit,+    byte,++  ) where++import qualified Control.Applicative as A+import qualified Data.Bits as B+import           Data.Word (Word8)+import           MonadLib (Id,runId)+++-- Lenses ----------------------------------------------------------------------++-- | General lenses that allow for the type of the inner field to change.+type Lens s t a b = forall f. Functor f => (a -> f b) -> (s -> f t)++-- | Lenses that don't change type.+type Lens' s a = Lens s s a a++lens :: (s -> a) -> (s -> b -> t) -> Lens s t a b+lens get upd = \ f s -> upd s `fmap` f (get s)+{-# INLINE lens #-}+++-- Getters ---------------------------------------------------------------------++type Getting r s a = (a -> Const r a) -> (s -> Const r s)++type Getter s a = forall r. Getting r s a++newtype Const r a = Const { runConst :: r } deriving Functor++-- | This is just a handy way of not exposing a Contrafunctor class, as we only+-- really need it for the definition of `to`.+castConst' :: (b -> a) -> Const r a -> Const r b+castConst' _ (Const r) = Const r+{-# INLINE castConst' #-}++-- NOTE: the @(s -> a)@ part could be generalized to @ReaderM m s@.+view :: Getting a s a -> s -> a+view l = \ s -> runConst (l Const s)+{-# INLINE view #-}++to :: (s -> a) -> Getting r s a+to f = \ l s -> castConst' f (l (f s))+{-# INLINE to #-}+++-- Setters ---------------------------------------------------------------------++type ASetter s t a b = (a -> Id b) -> (s -> Id t)++type ASetter' s a = ASetter s s a a++set :: Lens s t a b -> b -> s -> t+set l b = \ s -> runId (l (\ _ -> A.pure b) s)+{-# INLINE set #-}++over :: ASetter s t a b -> (a -> b) -> (s -> t)+over l f = \ s -> runId (l (A.pure . f) s)+{-# INLINE over #-}++newtype Modify r a = Modify { runModify :: (a,r) }++instance Functor (Modify r) where+  fmap f = \ (Modify (a,r)) -> Modify (f a, r)+  {-# INLINE fmap #-}++modify :: Lens s t a b -> (a -> (b,r)) -> (s -> (t,r))+modify l f = \ s -> runModify (l (\ a -> Modify (f a)) s)+{-# INLINE modify #-}+++-- Utility Lenses --------------------------------------------------------------++-- NOTE: successive uses of 'bit' with 'set' will cause terms like this to build+-- up:+--+-- > or# (or# .. (or# val (__word m1)) .. (__word mi)) (__word mj)+--+-- This can be fixed with a rewrite rule that associates all uses of or# to the+-- right, but this seems like it might block other simplifications that would+-- have fired if it was associated to the left.+--+-- The real problem here is that GHC isn't reorganizing the uses of or# to group+-- together constants, which would allow only one final use of or# after+-- simplification.+bit :: B.Bits a => Int -> Lens' a Bool+bit n = lens get upd+  where+  get a       = B.testBit a n++  upd a True  = B.setBit   a n+  upd a False = B.clearBit a n+++byte :: (Integral a, B.Bits a) => Int -> Lens' a Word8+byte n = lens get upd+  where+  sh      = n * 8++  get a   = fromIntegral (a `B.shiftR` sh)++  upd a b = a B..|. (fromIntegral b `B.shiftL` sh)
− src/Hans/Message/Arp.hs
@@ -1,79 +0,0 @@-module Hans.Message.Arp where--import Hans.Address (Address(addrSize))-import Hans.Utils (chunk)--import Control.Applicative (Applicative(..),(<$>))-import Data.Serialize.Get (Get,runGet,getWord8,getWord16be)-import Data.Serialize.Put (Putter,runPut,putWord16be,putWord8)-import Data.Word (Word16)-import qualified Data.ByteString as S-import qualified Data.ByteString.Lazy as L----- Arp Packets -------------------------------------------------------------------data ArpPacket hw p = ArpPacket-  { arpHwType :: !Word16-  , arpPType  :: !Word16-  , arpOper   :: ArpOper-  , arpSHA    :: hw-  , arpSPA    :: p-  , arpTHA    :: hw-  , arpTPA    :: p-  } deriving (Show)---- | Parse an Arp packet, given a way to parse hardware and protocol addresses.-parseArpPacket :: Get hw -> Get p-               -> S.ByteString -> Either String (ArpPacket hw p)-parseArpPacket getHw getP = runGet $-      ArpPacket-  <$> getWord16be  -- hardware type-  <*> getWord16be  -- protocol type-  <*  getWord8     -- hardware address length (ignored)-  <*  getWord8     -- protocol address length (ignored)-  <*> parseArpOper -- operation-  <*> getHw        -- sender hardware address-  <*> getP         -- sender protocol address-  <*> getHw        -- target hardware address-  <*> getP         -- target protocol address---- | Render an Arp packet, given a way to render hardware and protocol--- addresses.-renderArpPacket :: (Address hw, Address p)-                => Putter hw -> Putter p-                -> ArpPacket hw p -> L.ByteString-renderArpPacket putHw putP arp = chunk $ runPut $ do-  putWord16be   (arpHwType arp)-  putWord16be   (arpPType arp)-  putWord8      (addrSize (arpSHA arp))-  putWord8      (addrSize (arpSPA arp))-  renderArpOper (arpOper arp)-  putHw         (arpSHA arp)-  putP          (arpSPA arp)-  putHw         (arpTHA arp)-  putP          (arpTPA arp)----- Arp Opcodes --------------------------------------------------------------------- | Arp operations.-data ArpOper-  = ArpRequest -- ^ 0x1-  | ArpReply   -- ^ 0x2-  deriving (Show,Eq)---- | Parse an Arp operation.-parseArpOper :: Get ArpOper-parseArpOper  = do-  b <- getWord16be-  case b of-    0x1 -> return ArpRequest-    0x2 -> return ArpReply-    _   -> fail "invalid Arp opcode"---- | Render an Arp operation.-renderArpOper :: Putter ArpOper-renderArpOper op = case op of-  ArpRequest -> putWord16be 0x1-  ArpReply   -> putWord16be 0x2
− src/Hans/Message/Dhcp4.hs
@@ -1,575 +0,0 @@-{- | 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 Hans.Utils (chunk)--import Control.Applicative ((<*), (<$>))-import Control.Monad (unless)-import Data.Bits (testBit,bit)-import Data.Maybe (mapMaybe)-import Data.Serialize.Get (Get, runGet, getByteString, isolate, remaining, label-                          , skip)-import Data.Serialize.Put (Put, runPut, putByteString)-import Data.Word (Word8,Word16,Word32)-import Numeric (showHex)-import qualified Data.ByteString as BS-import qualified Data.ByteString.Char8 as BS8-import qualified Data.ByteString.Lazy as L---- 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-  , requestServerAddr :: IP4-  , 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 :: BS.ByteString -> Either String Dhcp4Message-getDhcp4Message = runGet $ 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 -> L.ByteString-putDhcp4Message dhcp = chunk $ runPut $ 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-          , requestServerAddr                   = dhcp4ServerAddr 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-                                  , OptServerIdentifier (requestServerAddr request)-                                  , 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-  , requestServerAddr                   = offerServerAddr offer-  , requestClientHardwareAddress        = offerClientHardwareAddr offer-  , requestParameters                   = [ OptTagSubnetMask-                                          , OptTagBroadcastAddress-                                          , OptTagTimeOffset-                                          , OptTagRouters-                                          , OptTagDomainName-                                          , OptTagNameServers-                                          , OptTagHostName-                                          ]-  , requestAddress                      = Just (offerYourAddr offer)-  }
− src/Hans/Message/Dhcp4Codec.hs
@@ -1,90 +0,0 @@-{-# 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
@@ -1,869 +0,0 @@-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/Dns.hs
@@ -1,601 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE TupleSections #-}-{-# LANGUAGE MultiWayIf #-}--module Hans.Message.Dns (-    DNSPacket(..)-  , DNSHeader(..)-  , OpCode(..)-  , RespCode(..)-  , Query(..)-  , QClass(..)-  , QType(..)-  , RR(..)-  , Type(..)-  , Class(..)-  , RData(..)-  , Name--  , parseDNSPacket,  getDNSPacket-  , renderDNSPacket, putDNSPacket-  ) where--import Hans.Address.IP4-import Hans.Utils (chunk)--import Control.Monad-import Data.Bits-import Data.Foldable ( traverse_, foldMap )-import Data.Int-import Data.Serialize ( Putter, runPut, putWord8, putWord16be, putWord32be-                      , putByteString )-import Data.Word-import MonadLib ( lift, StateT, runStateT, get, set )-import Numeric ( showHex )--import qualified Data.ByteString as S-import qualified Data.ByteString.Lazy as L-import qualified Data.Map.Strict as Map-import qualified Data.Serialize.Get as C----- DNS Packets -------------------------------------------------------------------data DNSPacket = DNSPacket { dnsHeader            :: DNSHeader-                           , dnsQuestions         :: [Query]-                           , dnsAnswers           :: [RR]-                           , dnsAuthorityRecords  :: [RR]-                           , dnsAdditionalRecords :: [RR]-                           } deriving (Show)--data DNSHeader = DNSHeader { dnsId     :: !Word16-                           , dnsQuery  :: Bool-                           , dnsOpCode :: OpCode-                           , dnsAA     :: Bool-                           , dnsTC     :: Bool-                           , dnsRD     :: Bool-                           , dnsRA     :: Bool-                           , dnsRC     :: RespCode-                           } deriving (Show)--data OpCode = OpQuery-            | OpIQuery-            | OpStatus-            | OpReserved !Word16-              deriving (Show)--data RespCode = RespNoError-              | RespFormatError-              | RespServerFailure-              | RespNameError-              | RespNotImplemented-              | RespRefused-              | RespReserved !Word16-                deriving (Eq,Show)--type Name = [S.ByteString]--data Query = Query { qName  :: Name-                   , qType  :: QType-                   , qClass :: QClass-                   } deriving (Show)--data RR = RR { rrName  :: Name-             , rrClass :: Class-             , rrTTL   :: !Int32-             , rrRData :: RData-             } deriving (Show)--data QType = QType Type-           | AFXR-           | MAILB-           | MAILA-           | QTAny-             deriving (Show)--data Type = A-          | NS-          | MD-          | MF-          | CNAME-          | SOA-          | MB-          | MG-          | MR-          | NULL-          | PTR-          | HINFO-          | MINFO-          | MX-          | AAAA-            deriving (Show)--data QClass = QClass Class-            | QAnyClass-              deriving (Show)--data Class = IN | CS | CH | HS-             deriving (Show,Eq)--data RData = RDA IP4-           | RDNS Name-           | RDMD Name-           | RDMF Name-           | RDCNAME Name-           | RDSOA Name Name !Word32 !Int32 !Int32 !Int32 !Word32-           | RDMB Name-           | RDMG Name-           | RDMR Name-           | RDPTR Name-           | RDHINFO S.ByteString S.ByteString-           | RDMINFO Name Name-           | RDMX !Word16 Name-           | RDNULL S.ByteString-           | RDUnknown Type S.ByteString-             deriving (Show)----- Cereal With Label Compression -------------------------------------------------data RW = RW { rwOffset :: !Int-             , rwLabels :: Map.Map Int Name-             } deriving (Show)--type Get = StateT RW C.Get--{-# INLINE unGet #-}-unGet :: Get a -> C.Get a-unGet m =-  do (a,_) <- runStateT RW { rwOffset = 0, rwLabels = Map.empty } m-     return a--getOffset :: Get Int-getOffset  = rwOffset `fmap` get--addOffset :: Int -> Get ()-addOffset off =-  do rw <- get-     set $! rw { rwOffset = rwOffset rw + off }--lookupPtr :: Int -> Get Name-lookupPtr off =-  do rw <- get-     when (off >= rwOffset rw) (fail "Invalid offset in pointer")-     case Map.lookup off (rwLabels rw) of-       Just ls -> return ls-       Nothing -> fail $ "Unknown label for offset: " ++ showHex off "\n"-                      ++ show (rwLabels rw)--data Label = Label Int S.ByteString-           | Ptr Int Name-             deriving (Show)--labelsToName :: [Label] -> Name-labelsToName  = foldMap toName-  where-  toName (Label _ l) = [l]-  toName (Ptr _ n)   = n--addLabels :: [Label] -> Get ()-addLabels labels =-  do rw <- get-     set $! rw { rwLabels = Map.fromList newLabels `Map.union` rwLabels rw }-  where-  newLabels = go labels (labelsToName labels)--  go (Label off _ : rest) name@(_ : ns) = (off,name) : go rest ns-  go (Ptr off _   : _)    name          = [(off,name)]-  go _                    _             = []---{-# INLINE liftGet #-}-liftGet :: Int -> C.Get a -> Get a-liftGet n m = do addOffset n-                 lift m---{-# INLINE getWord8 #-}-getWord8 :: Get Word8-getWord8  = liftGet 1 C.getWord8--{-# INLINE getWord16be #-}-getWord16be :: Get Word16-getWord16be  = liftGet 2 C.getWord16be--{-# INLINE getWord32be #-}-getWord32be :: Get Word32-getWord32be  = liftGet 4 C.getWord32be--{-# INLINE getInt32be #-}-getInt32be :: Get Int32-getInt32be  = fromIntegral `fmap` liftGet 4 C.getWord32be--{-# INLINE getBytes #-}-getBytes :: Int -> Get S.ByteString-getBytes n = liftGet n (C.getBytes n)--isolate :: Int -> Get a -> Get a-isolate n body =-  do off      <- get-     (a,off') <- lift (C.isolate n (runStateT off body))-     set off'-     return a--label :: String -> Get a -> Get a-label str m =-  do off      <- get-     (a,off') <- lift (C.label str (runStateT off m))-     set off'-     return a---{-# INLINE putInt32be #-}-putInt32be :: Putter Int32-putInt32be i = putWord32be (fromIntegral i)---- Parsing -----------------------------------------------------------------------parseDNSPacket :: S.ByteString -> Either String DNSPacket-parseDNSPacket  = C.runGet getDNSPacket--getDNSPacket :: C.Get DNSPacket-getDNSPacket  = unGet $ label "DNSPacket" $-  do dnsHeader <- getDNSHeader-     qdCount   <- getWord16be-     anCount   <- getWord16be-     nsCount   <- getWord16be-     arCount   <- getWord16be--     let blockOf c l m = label l (replicateM (fromIntegral c) m)-     dnsQuestions         <- blockOf qdCount "Questions"          getQuery-     dnsAnswers           <- blockOf anCount "Answers"            getRR-     dnsAuthorityRecords  <- blockOf nsCount "Authority Records"  getRR-     dnsAdditionalRecords <- blockOf arCount "Additional Records" getRR--     return DNSPacket { .. }----                                  1  1  1  1  1  1---    0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5---  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+---  |                      ID                       |---  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+---  |QR|   Opcode  |AA|TC|RD|RA|   Z    |   RCODE   |---  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+---  |                    QDCOUNT                    |---  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+---  |                    ANCOUNT                    |---  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+---  |                    NSCOUNT                    |---  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+---  |                    ARCOUNT                    |---  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+-getDNSHeader :: Get DNSHeader-getDNSHeader  = label "DNS Header" $-  do dnsId <- getWord16be-     flags <- getWord16be-     let dnsQuery  = not (flags `testBit` 15)-         dnsOpCode = parseOpCode (flags `shiftR` 11)-         dnsAA     = flags `testBit` 10-         dnsTC     = flags `testBit`  9-         dnsRD     = flags `testBit`  8-         dnsRA     = flags `testBit`  7-         dnsZ      = (flags `shiftR` 4) .&. 0x7-         dnsRC     = parseRespCode (flags .&. 0xf)--     unless (dnsZ == 0) (fail ("Z not zero"))--     return DNSHeader { .. }---parseOpCode :: Word16 -> OpCode-parseOpCode 0 = OpQuery-parseOpCode 1 = OpIQuery-parseOpCode 2 = OpStatus-parseOpCode c = OpReserved (c .&. 0xf)--parseRespCode :: Word16 -> RespCode-parseRespCode 0 = RespNoError-parseRespCode 1 = RespFormatError-parseRespCode 2 = RespServerFailure-parseRespCode 3 = RespNameError-parseRespCode 4 = RespNotImplemented-parseRespCode 5 = RespRefused-parseRespCode c = RespReserved (c .&. 0xf)--getQuery :: Get Query-getQuery  = label "Question" $-  do qName  <- getName-     qType  <- label "QTYPE"  getQType-     qClass <- label "QCLASS" getQClass-     return Query { .. }----                                  1  1  1  1  1  1---    0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5---  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+---  |                                               |---  /                                               /---  /                      NAME                     /---  |                                               |---  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+---  |                      TYPE                     |---  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+---  |                     CLASS                     |---  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+---  |                      TTL                      |---  |                                               |---  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+---  |                   RDLENGTH                    |---  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|---  /                     RDATA                     /---  /                                               /---  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+-getRR :: Get RR-getRR  = label "RR" $-  do rrName  <- getName-     ty      <- getType-     rrClass <- getClass-     rrTTL   <- getInt32be-     rrRData <- getRData ty-     return RR { .. }---getType :: Get Type-getType  =-  do qt <- getQType-     case qt of-       QType ty -> return ty-       _        -> fail ("Invalid TYPE: " ++ show qt)--getQType :: Get QType-getQType  =-  do tag <- getWord16be-     case tag of-       1   -> return (QType A)-       2   -> return (QType NS)-       3   -> return (QType MD)-       4   -> return (QType MF)-       5   -> return (QType CNAME)-       6   -> return (QType SOA)-       7   -> return (QType MB)-       8   -> return (QType MG)-       9   -> return (QType MR)-       10  -> return (QType NULL)-       12  -> return (QType PTR)-       13  -> return (QType HINFO)-       14  -> return (QType MINFO)-       15  -> return (QType MX)-       28  -> return (QType AAAA)-       252 -> return AFXR-       253 -> return MAILB-       254 -> return MAILA-       255 -> return QTAny-       _   -> fail ("Invalid TYPE: " ++ show tag)---getQClass :: Get QClass-getQClass  =-  do tag <- getWord16be-     case tag of-       1   -> return (QClass IN)-       2   -> return (QClass CS)-       3   -> return (QClass CH)-       4   -> return (QClass HS)-       255 -> return QAnyClass-       _   -> fail ("Invalid CLASS: " ++ show tag)--getName :: Get Name-getName  =-  do labels <- go-     addLabels labels-     return (labelsToName labels)-  where-  go = do off <- getOffset-          len <- getWord8-          if | len .&. 0xc0 == 0xc0 ->-               do l <- getWord8-                  let ptr = fromIntegral ((0x3f .&. len) `shiftL` 8)-                          + fromIntegral l-                  ns <- lookupPtr ptr-                  return [Ptr off ns]--             | len == 0 ->-                  return []--             | otherwise ->-               do l  <- getBytes (fromIntegral len)-                  ls <- go-                  return (Label off l:ls)--getClass :: Get Class-getClass  = label "CLASS" $-  do qc <- getQClass-     case qc of-       QClass c  -> return c-       QAnyClass -> fail "Invalid CLASS"--getRData :: Type -> Get RData-getRData ty = label (show ty) $-  do len <- getWord16be-     isolate (fromIntegral len) $ case ty of-       A     -> RDA  `fmap` liftGet 4 parseIP4-       NS    -> RDNS `fmap` getName-       MD    -> RDMD `fmap` getName-       MF    -> RDMF `fmap` getName-       CNAME -> RDCNAME `fmap` getName-       SOA   -> do mname   <- getName-                   rname   <- getName-                   serial  <- getWord32be-                   refresh <- getInt32be-                   retry   <- getInt32be-                   expire  <- getInt32be-                   minTTL  <- getWord32be-                   return (RDSOA mname rname serial refresh retry expire minTTL)-       MB    -> RDMB `fmap` getName-       MG    -> RDMG `fmap` getName-       MR    -> RDMR `fmap` getName-       NULL  -> RDNULL `fmap` (getBytes =<< lift C.remaining)-       PTR   -> RDPTR `fmap` getName--       HINFO -> do cpuLen <- getWord8-                   cpu    <- getBytes (fromIntegral cpuLen)-                   osLen  <- getWord8-                   os     <- getBytes (fromIntegral osLen)-                   return (RDHINFO cpu os)--       MINFO -> do rmailBx <- getName-                   emailBx <- getName-                   return (RDMINFO rmailBx emailBx)--       MX    -> do pref <- getWord16be-                   ex   <- getName-                   return (RDMX pref ex)--       _     -> RDUnknown ty `fmap` (getBytes =<< lift C.remaining)----- Rendering ---------------------------------------------------------------------renderDNSPacket :: DNSPacket -> L.ByteString-renderDNSPacket pkt = chunk (runPut (putDNSPacket pkt))--putDNSPacket :: Putter DNSPacket-putDNSPacket DNSPacket{ .. } =-  do putDNSHeader dnsHeader--     putWord16be (fromIntegral (length dnsQuestions))-     putWord16be (fromIntegral (length dnsAnswers))-     putWord16be (fromIntegral (length dnsAuthorityRecords))-     putWord16be (fromIntegral (length dnsAdditionalRecords))--     traverse_ putQuery dnsQuestions-     traverse_ putRR dnsAnswers-     traverse_ putRR dnsAuthorityRecords-     traverse_ putRR dnsAdditionalRecords--putDNSHeader :: Putter DNSHeader-putDNSHeader DNSHeader { .. } =-  do putWord16be dnsId-     let flag i b w | b         = setBit w i-                    | otherwise = clearBit w i-         flags = flag 15 (not dnsQuery)-               $ flag 10 dnsAA-               $ flag  9 dnsTC-               $ flag  8 dnsRD-               $ flag  7 dnsRA-               $ flag  4 False -- dnsZ-               $ (renderOpCode dnsOpCode `shiftL` 11) .|. renderRespCode dnsRC-     putWord16be flags--renderOpCode :: OpCode -> Word16-renderOpCode OpQuery        = 0-renderOpCode OpIQuery       = 1-renderOpCode OpStatus       = 2-renderOpCode (OpReserved c) = c .&. 0xf--renderRespCode :: RespCode -> Word16-renderRespCode RespNoError        = 0-renderRespCode RespFormatError    = 1-renderRespCode RespServerFailure  = 2-renderRespCode RespNameError      = 3-renderRespCode RespNotImplemented = 4-renderRespCode RespRefused        = 5-renderRespCode (RespReserved c)   = c .&. 0xf--putName :: Putter Name-putName  = go-  where-  go (l:ls)-    | S.null l        = putWord8 0-    | S.length l > 63 = error "Label too big"-    | otherwise       = do putWord8 (fromIntegral len)-                           putByteString l-                           go ls-    where-    len = S.length l--  go [] = putWord8 0--putQuery :: Putter Query-putQuery Query { .. } =-  do putName   qName-     putQType  qType-     putQClass qClass--putType :: Putter Type-putType A     = putWord16be 1-putType NS    = putWord16be 2-putType MD    = putWord16be 3-putType MF    = putWord16be 4-putType CNAME = putWord16be 5-putType SOA   = putWord16be 6-putType MB    = putWord16be 7-putType MG    = putWord16be 8-putType MR    = putWord16be 9-putType NULL  = putWord16be 10-putType PTR   = putWord16be 12-putType HINFO = putWord16be 13-putType MINFO = putWord16be 14-putType MX    = putWord16be 15-putType AAAA  = putWord16be 28--putQType :: Putter QType-putQType (QType ty) = putType ty-putQType AFXR       = putWord16be 252-putQType MAILB      = putWord16be 253-putQType MAILA      = putWord16be 254-putQType QTAny      = putWord16be 255--putQClass :: Putter QClass-putQClass (QClass c) = putClass c-putQClass QAnyClass  = putWord16be 255--putRR :: Putter RR-putRR RR { .. } =-  do putName rrName-     let (ty,rdata) = putRData rrRData-     putType ty-     putClass rrClass-     putWord32be (fromIntegral rrTTL)-     putWord16be (fromIntegral (S.length rdata))-     putByteString rdata--putClass :: Putter Class-putClass IN = putWord16be 1-putClass CS = putWord16be 2-putClass CH = putWord16be 3-putClass HS = putWord16be 4--putRData :: RData -> (Type,S.ByteString)-putRData rd = case rd of-  RDA addr           -> rdata A     (renderIP4 addr)-  RDNS name          -> rdata NS    (putName name)-  RDMD name          -> rdata MD    (putName name)-  RDMF name          -> rdata MF    (putName name)-  RDCNAME name       -> rdata CNAME (putName name)-  RDSOA m r s f t ex ttl ->-                        rdata SOA $ do putName m-                                       putName r-                                       putWord32be s-                                       putInt32be f-                                       putInt32be t-                                       putInt32be ex-                                       putWord32be ttl--  RDMB name          -> rdata MB    (putName name)-  RDMG name          -> rdata MG    (putName name)-  RDMR name          -> rdata MR    (putName name)-  RDNULL bytes       -> rdata NULL  $ do putWord8 (fromIntegral (S.length bytes))-                                         putByteString bytes-  RDPTR name         -> rdata PTR   (putName name)-  RDHINFO cpu os     -> rdata HINFO $ do putWord8 (fromIntegral (S.length cpu))-                                         putByteString cpu-                                         putWord8 (fromIntegral (S.length os))-                                         putByteString os-  RDMINFO rm em      -> rdata MINFO $ do putName rm-                                         putName em-  RDMX pref ex       -> rdata MX    $ do putWord16be pref-                                         putName ex-  RDUnknown ty bytes -> (ty,bytes)-  where-  rdata tag m = (tag,runPut m)
− src/Hans/Message/EthernetFrame.hs
@@ -1,60 +0,0 @@-{-# LANGUAGE GeneralizedNewtypeDeriving #-}--module Hans.Message.EthernetFrame (-    EtherType(..)-  , parseEtherType, renderEtherType--  , EthernetFrame(..)-  , parseEthernetFrame, renderEthernetFrame-  ) where--import Hans.Address.Mac (Mac,parseMac,renderMac)-import Hans.Utils (chunk)--import Control.Applicative ((<$>),(<*>))-import Data.Serialize.Get (Get,remaining,getWord16be,getBytes,runGet)-import Data.Serialize.Put (Putter,putWord16be,runPut)-import Data.Word (Word16)-import Numeric (showHex)-import qualified Data.ByteString.Lazy as L-import qualified Data.ByteString      as S----- Ether Type --------------------------------------------------------------------newtype EtherType = EtherType { getEtherType :: Word16 }-  deriving (Eq,Num,Ord)--instance Show EtherType where-  showsPrec _ (EtherType et) = showString "EtherType 0x" . showHex et--parseEtherType :: Get EtherType-parseEtherType  = EtherType <$> getWord16be--renderEtherType :: Putter EtherType-renderEtherType  = putWord16be . getEtherType----- Ethernet Frames ---------------------------------------------------------------data EthernetFrame = EthernetFrame-  { etherDest   :: !Mac-  , etherSource :: !Mac-  , etherType   :: !EtherType-  } deriving (Eq,Show)--parseEthernetFrame :: S.ByteString -> Either String (EthernetFrame,S.ByteString)-parseEthernetFrame = runGet ((,) <$> header <*> (getBytes =<< remaining))-  where-  header =  EthernetFrame-        <$> parseMac-        <*> parseMac-        <*> parseEtherType--renderEthernetFrame :: EthernetFrame -> L.ByteString -> L.ByteString-renderEthernetFrame frame body = chunk hdr `L.append` body-  where-  hdr = runPut $ do-    renderMac         (etherDest   frame)-    renderMac         (etherSource frame)-    renderEtherType   (etherType   frame)
− src/Hans/Message/Icmp4.hs
@@ -1,442 +0,0 @@-{-# LANGUAGE GeneralizedNewtypeDeriving #-}--module Hans.Message.Icmp4 where--import Hans.Address.IP4 (IP4)-import Hans.Message.Types (Lifetime,parseLifetime,renderLifetime)-import Hans.Utils (chunk)-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, runGet)-import Data.Serialize.Put (Putter, putWord8,putByteString, Put, runPut)-import Data.Int (Int32)-import Data.Word (Word8,Word16,Word32)-import System.IO.Unsafe (unsafePerformIO)-import qualified Data.ByteString as S-import qualified Data.ByteString.Lazy as L----- General ICMP Packets ----------------------------------------------------------data Icmp4Packet-  -- RFC 792 - Internet Control Message Protocol-  = EchoReply Identifier SequenceNumber S.ByteString-  | DestinationUnreachable DestinationUnreachableCode S.ByteString-  | SourceQuench S.ByteString-  | Redirect RedirectCode IP4 S.ByteString-  | Echo Identifier SequenceNumber S.ByteString-  | RouterAdvertisement Lifetime [RouterAddress]-  | RouterSolicitation-  | TimeExceeded TimeExceededCode S.ByteString-  | ParameterProblem Word8 S.ByteString-  | 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 (Eq,Show)--noCode :: String -> Get ()-noCode str = do-  code <- getWord8-  unless (code == 0)-    (fail (str ++ " expects code 0"))--{-# INLINE parseIcmp4Packet #-}-parseIcmp4Packet :: S.ByteString -> Either String Icmp4Packet-parseIcmp4Packet  = runGet $ do-  rest <- lookAhead (getBytes =<< remaining)-  unless (computeChecksum 0 rest == 0)-    (fail "Bad checksum")-  getIcmp4Packet--getIcmp4Packet :: Get Icmp4Packet-getIcmp4Packet  = label "ICMP" $ do--  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 <- parseLifetime-             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)---renderIcmp4Packet :: Icmp4Packet -> L.ByteString-renderIcmp4Packet icmp =-  -- Argument for safety: The bytestring being-  -- destructively modified here is only accessible-  -- through the composition and will never escape-  chunk (unsafePerformIO (setChecksum (runPut (putIcmp4Packet icmp))))-  where-  setChecksum pkt = pokeChecksum (computeChecksum 0 pkt) pkt 2--putIcmp4Packet :: Putter Icmp4Packet-putIcmp4Packet  = put'-  where-  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-         renderLifetime 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 18 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-  | DestinationNetworkUnknown-  | DestinationHostUnknown-  | SourceHostIsolatedError-  | AdministrativelyProhibited-  | HostAdministrativelyProhibited-  | NetworkUnreachableForTOS-  | HostUnreachableForTOS-  | CommunicationAdministrativelyProhibited-  | HostPrecedenceViolation-  | PrecedenceCutoffInEffect-  deriving (Eq,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-             6  -> return DestinationNetworkUnknown-             7  -> return DestinationHostUnknown-             8  -> return SourceHostIsolatedError-             9  -> return AdministrativelyProhibited-             10 -> return HostAdministrativelyProhibited-             11 -> return NetworkUnreachableForTOS-             12 -> return HostUnreachableForTOS-             13 -> return CommunicationAdministrativelyProhibited-             14 -> return HostPrecedenceViolation-             15 -> return PrecedenceCutoffInEffect-             _  -> fail "Invalid code for Destination Unreachable"--  put code = case code of-    NetUnreachable                          -> putWord8 0-    HostUnreachable                         -> putWord8 1-    ProtocolUnreachable                     -> putWord8 2-    PortUnreachable                         -> putWord8 3-    FragmentationUnreachable                -> putWord8 4-    SourceRouteFailed                       -> putWord8 5-    DestinationNetworkUnknown               -> putWord8 6-    DestinationHostUnknown                  -> putWord8 7-    SourceHostIsolatedError                 -> putWord8 8-    AdministrativelyProhibited              -> putWord8 9-    HostAdministrativelyProhibited          -> putWord8 10-    NetworkUnreachableForTOS                -> putWord8 11-    HostUnreachableForTOS                   -> putWord8 12-    CommunicationAdministrativelyProhibited -> putWord8 13-    HostPrecedenceViolation                 -> putWord8 14-    PrecedenceCutoffInEffect                -> putWord8 15--data TimeExceededCode-  = TimeToLiveExceededInTransit-  | FragmentReassemblyTimeExceeded-  deriving (Eq,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 (Eq,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 (Eq,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 (Eq,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
@@ -1,288 +0,0 @@-{-# 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.Int (Int64)-import Data.Serialize-    (Serialize(..),Get,getWord8,getWord16be,isolate,label,getByteString-    ,Put,runPut,putWord8,putWord16be,putByteString,runGet)-import Data.Bits (Bits((.&.),(.|.),testBit,setBit,shiftR,shiftL,bit))-import Data.Word (Word8,Word16)-import qualified Data.ByteString as S-import qualified Data.ByteString.Lazy as L----- 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 IP4Header = IP4Header-  { ip4Version        :: !Word8-  , ip4TypeOfService  :: !Word8-  , ip4Ident          :: !Ident-  , ip4DontFragment   :: Bool-  , ip4MoreFragments  :: Bool-  , ip4FragmentOffset :: !Word16-  , ip4TimeToLive     :: !Word8-  , ip4Protocol       :: !IP4Protocol-  , ip4Checksum       :: !Word16-  , ip4SourceAddr     :: !IP4-  , ip4DestAddr       :: !IP4-  , ip4Options        :: [IP4Option]-  } deriving (Eq,Show)--emptyIP4Header :: IP4Header-emptyIP4Header  = IP4Header-  { ip4Version        = 4-  , ip4TypeOfService  = 0-  , ip4Ident          = 0-  , ip4DontFragment   = False-  , ip4MoreFragments  = False-  , ip4FragmentOffset = 0-  , ip4TimeToLive     = 127-  , ip4Protocol       = IP4Protocol 0-  , ip4Checksum       = 0-  , ip4SourceAddr     = IP4 0 0 0 0-  , ip4DestAddr       = IP4 0 0 0 0-  , 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 :: IP4Header -> L.ByteString -> Int-ip4PacketSize hdr bs =-  ip4HeaderSize hdr + fromIntegral (L.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 -> IP4Header -> L.ByteString -> [(IP4Header,L.ByteString)]-splitPacket mtu hdr bs-  | ip4PacketSize hdr bs <= mtu = [(hdr,bs)]-  | otherwise                   = fragmentPacket (fromIntegral mtu) hdr bs----- | Given a fragment size and a packet, fragment the packet into multiple--- smaller ones.-fragmentPacket :: Int64 -> IP4Header -> L.ByteString-               -> [(IP4Header,L.ByteString)]-fragmentPacket mtu = loop-  where-  loop hdr bs-    | payloadLen <= mtu = [(noMoreFragments hdr, bs)]-    | otherwise         = frag : loop hdr' rest-    where-    payloadLen = L.length bs-    (as,rest)  = L.splitAt mtu bs-    alen       = fromIntegral (L.length as)-    hdr'       = addOffset alen hdr-    frag       = (moreFragments hdr, 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                        |--- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-getIP4Packet :: Get (IP4Header, Int, Int)-getIP4Packet  = 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-          , ip4DontFragment   = 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)--{-# INLINE parseIP4Packet #-}-parseIP4Packet :: S.ByteString -> Either String (IP4Header, Int, Int)-parseIP4Packet bytes = runGet getIP4Packet bytes--{-# INLINE renderIP4Header #-}-renderIP4Header :: IP4Header -> Int -> S.ByteString-renderIP4Header hdr pktlen = runPut (putIP4Header hdr pktlen)--putIP4Header :: IP4Header -> Int -> Put-putIP4Header hdr pktlen = do-  let (optbs,optlen) = renderOptions (ip4Options hdr)-  let ihl            = 20 + optlen-  putWord8    (ip4Version hdr `shiftL` 4 .|. (ihl `div` 4))-  putWord8    (ip4TypeOfService hdr)-  putWord16be (fromIntegral pktlen + fromIntegral ihl)-  put         (getIdent (ip4Ident hdr))-  let frag | ip4DontFragment 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 -- checksum--  put (ip4SourceAddr hdr)--  put (ip4DestAddr hdr)--  putByteString optbs----- | The final step to render an IP header and its payload out as a bytestring.-renderIP4Packet :: IP4Header -> L.ByteString -> IO L.ByteString-renderIP4Packet hdr pkt = do--  hdrBytes <--    let pktlen = fromIntegral (L.length pkt)-        bytes  = renderIP4Header hdr pktlen-        cs     = computeChecksum 0 bytes-     in pokeChecksum cs bytes 10--  return (chunk hdrBytes `L.append` pkt)----- 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 (Eq,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
@@ -1,498 +0,0 @@-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}--module Hans.Message.Tcp where--import Hans.Address.IP4 (IP4)-import Hans.Message.Ip4 (mkIP4PseudoHeader,IP4Protocol(..))-import Hans.Utils (chunk)-import Hans.Utils.Checksum--import Control.Monad (unless,ap,replicateM_,replicateM)-import Data.Bits ((.&.),setBit,testBit,shiftL,shiftR)-import Data.List (find)-import Data.Monoid (Monoid(..))-import Data.Serialize-    (Get,Put,Putter,getWord16be,putWord16be,getWord32be,putWord32be,getWord8-    ,putWord8,putByteString,getBytes,remaining,label,isolate,skip,runGet,runPut-    ,putLazyByteString)-import Data.Word (Word8,Word16,Word32)-import System.IO.Unsafe (unsafePerformIO)-import qualified Data.ByteString.Lazy as L-import qualified Data.ByteString      as S-import qualified Data.Foldable        as F----- Tcp Support Types -------------------------------------------------------------tcpProtocol :: IP4Protocol-tcpProtocol  = IP4Protocol 0x6--newtype TcpPort = TcpPort-  { getPort :: Word16-  } deriving (Eq,Ord,Read,Show,Num,Enum,Bounded)--putTcpPort :: Putter TcpPort-putTcpPort (TcpPort w16) = putWord16be w16--getTcpPort :: Get TcpPort-getTcpPort  = TcpPort `fmap` getWord16be---newtype TcpSeqNum = TcpSeqNum-  { getSeqNum :: Word32-  } deriving (Eq,Ord,Show,Num,Bounded,Enum,Real,Integral)--instance Monoid TcpSeqNum where-  mempty  = 0-  mappend = (+)--putTcpSeqNum :: Putter TcpSeqNum-putTcpSeqNum (TcpSeqNum w32) = putWord32be w32--getTcpSeqNum :: Get TcpSeqNum-getTcpSeqNum  = TcpSeqNum `fmap` getWord32be----- | An alias to TcpSeqNum, as these two are used in the same role.-type TcpAckNum = TcpSeqNum--putTcpAckNum :: Putter TcpAckNum-putTcpAckNum  = putTcpSeqNum--getTcpAckNum :: Get TcpAckNum-getTcpAckNum  = getTcpSeqNum----- 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 (Eq,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        = 0-  , 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---- | 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)-  let (optLen,padding) = tcpOptionsLength (tcpOptions hdr)-  putWord8 (fromIntegral ((tcpFixedHeaderLength + optLen) `shiftL` 4))-  putTcpControl hdr-  putWord16be (tcpWindow hdr)-  putWord16be 0-  putWord16be (tcpUrgentPointer hdr)-  mapM_ putTcpOption (tcpOptions hdr)-  replicateM_ padding (putTcpOptionTag OptTagEndOfOptions)---- | 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  = label "TcpHeader" $ 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   <- label "options" (isolate (optsLen `shiftL` 2) getTcpOptions)-  let hdr = setTcpControl cont emptyTcpHeader-        { tcpSourcePort    = src-        , tcpDestPort      = dst-        , tcpSeqNum        = seqNum-        , tcpAckNum        = ackNum-        , tcpWindow        = win-        , tcpChecksum      = cs-        , tcpUrgentPointer = urgent-        , tcpOptions       = filter (/= OptEndOfOptions) 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--setTcpOptions :: HasTcpOptions a => [TcpOption] -> a -> a-setTcpOptions opts a = foldr setTcpOption a opts--data TcpOptionTag-  = OptTagEndOfOptions-  | OptTagNoOption-  | OptTagMaxSegmentSize-  | OptTagWindowScaling-  | OptTagSackPermitted-  | OptTagSack-  | 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-    4 -> OptTagSackPermitted-    5 -> OptTagSack-    8 -> OptTagTimestamp-    _ -> OptTagUnknown ty--putTcpOptionTag :: Putter TcpOptionTag-putTcpOptionTag tag =-  putWord8 $ case tag of-    OptTagEndOfOptions   -> 0-    OptTagNoOption       -> 1-    OptTagMaxSegmentSize -> 2-    OptTagWindowScaling  -> 3-    OptTagSackPermitted  -> 4-    OptTagSack           -> 5-    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-  | OptSackPermitted-  | OptSack [SackBlock]-  | OptTimestamp !Word32 !Word32-  | OptUnknown !Word8 !Word8 !S.ByteString-    deriving (Show,Eq)--data SackBlock = SackBlock-  { sbLeft  :: !TcpSeqNum-  , sbRight :: !TcpSeqNum-  } deriving (Show,Eq)--tcpOptionTag :: TcpOption -> TcpOptionTag-tcpOptionTag opt = case opt of-  OptEndOfOptions{}   -> OptTagEndOfOptions-  OptNoOption{}       -> OptTagNoOption-  OptMaxSegmentSize{} -> OptTagMaxSegmentSize-  OptSackPermitted{}  -> OptTagSackPermitted-  OptSack{}           -> OptTagSack-  OptWindowScaling{}  -> OptTagWindowScaling-  OptTimestamp{}      -> OptTagTimestamp-  OptUnknown ty _ _   -> OptTagUnknown ty---- | Get the rendered length of a list of TcpOptions, in 4-byte words, and the--- number of padding bytes required.  This rounds up to the nearest 4-byte word.-tcpOptionsLength :: [TcpOption] -> (Int,Int)-tcpOptionsLength opts-  | left == 0 = (len,0)-  | otherwise = (len + 1,4 - left)-  where-  (len,left) = F.sum (fmap tcpOptionLength opts) `quotRem` 4--tcpOptionLength :: TcpOption -> Int-tcpOptionLength opt = case opt of-  OptEndOfOptions{}   -> 1-  OptNoOption{}       -> 1-  OptMaxSegmentSize{} -> 4-  OptWindowScaling{}  -> 3-  OptSackPermitted{}  -> 2-  OptSack bs          -> sackLength bs-  OptTimestamp{}      -> 10-  OptUnknown _ len _  -> fromIntegral len---putTcpOption :: Putter TcpOption-putTcpOption opt = do-  putTcpOptionTag (tcpOptionTag opt)-  case opt of-    OptEndOfOptions       -> return ()-    OptNoOption           -> return ()-    OptMaxSegmentSize mss -> putMaxSegmentSize mss-    OptWindowScaling w    -> putWindowScaling w-    OptSackPermitted      -> putSackPermitted-    OptSack bs            -> putSack bs-    OptTimestamp v r      -> putTimestamp v r-    OptUnknown _ len bs   -> putUnknown len bs---- | Parse in known tcp options.-getTcpOptions :: Get [TcpOption]-getTcpOptions  = label "Tcp Options" loop-  where-  loop = do-    left <- remaining-    if left > 0 then body else return []--  body = do-    opt <- getTcpOption-    case opt of--      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-    OptTagSackPermitted  -> getSackPermitted-    OptTagSack           -> getSack-    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--getSackPermitted :: Get TcpOption-getSackPermitted  = label "Sack Permitted" $ isolate 1 $ do-  len <- getWord8-  unless (len == 2) (fail ("Unexpected length: " ++ show len))-  return OptSackPermitted--putSackPermitted :: Put-putSackPermitted  = do-  putWord8 2--getSack :: Get TcpOption-getSack  = label "Sack" $ do-  len <- getWord8-  let edgeLen = fromIntegral len - 2-  OptSack `fmap` isolate edgeLen (replicateM (edgeLen `shiftR` 3) getSackBlock)--putSack :: Putter [SackBlock]-putSack bs = do-  putWord8 (fromIntegral (sackLength bs))-  mapM_ putSackBlock bs--getSackBlock :: Get SackBlock-getSackBlock  = do-  l <- getTcpSeqNum-  r <- getTcpSeqNum-  return $! SackBlock-    { sbLeft  = l-    , sbRight = r-    }--putSackBlock :: Putter SackBlock-putSackBlock sb = do-  putTcpSeqNum (sbLeft sb)-  putTcpSeqNum (sbRight sb)--sackLength :: [SackBlock] -> Int-sackLength bs = length bs * 8 + 2--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 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 -> S.ByteString -> Put-putUnknown len body = do-  putWord8 len-  putByteString body----- Tcp Packet --------------------------------------------------------------------{-# INLINE parseTcpPacket #-}-parseTcpPacket :: S.ByteString -> Either String (TcpHeader,S.ByteString)-parseTcpPacket bytes = runGet getTcpPacket bytes---- | Parse a TcpPacket.-getTcpPacket :: Get (TcpHeader,S.ByteString)-getTcpPacket  = do-  pktLen       <- remaining-  (hdr,hdrLen) <- getTcpHeader-  body         <- getBytes (pktLen - hdrLen)-  return (hdr,body)---- | Render out a TcpPacket, without calculating its checksum.-putTcpPacket :: TcpHeader -> L.ByteString -> Put-putTcpPacket hdr body = do-  putTcpHeader hdr-  putLazyByteString body---- | Calculate the checksum of a TcpHeader, and its body.-renderWithTcpChecksumIP4 :: IP4 -> IP4 -> TcpHeader -> L.ByteString-                         -> L.ByteString-renderWithTcpChecksumIP4 src dst hdr body = chunk hdrbs `L.append` body-  where-  (hdrbs,_) = computeTcpChecksumIP4 src dst hdr body---- | Calculate the checksum of a tcp packet, and return its rendered header.-computeTcpChecksumIP4 :: IP4 -> IP4 -> TcpHeader -> L.ByteString-                      -> (S.ByteString,Word16)-computeTcpChecksumIP4 src dst 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-  phcs  = computePartialChecksum emptyPartialChecksum-        $ mkIP4PseudoHeader src dst tcpProtocol-        $ S.length hdrbs + fromIntegral (L.length body)-  hdrbs = runPut (putTcpHeader hdr { tcpChecksum = 0 })-  hdrcs = computePartialChecksum phcs hdrbs-  cs    = finalizeChecksum (computePartialChecksumLazy hdrcs body)---- | Re-create the checksum, minimizing duplication of the original, rendered--- TCP packet.-validateTcpChecksumIP4 :: IP4 -> IP4 -> S.ByteString -> Bool-validateTcpChecksumIP4 src dst bytes =-  finalizeChecksum (computePartialChecksum phcs bytes) == 0-  where-  phcs = computePartialChecksum emptyPartialChecksum-       $ mkIP4PseudoHeader src dst tcpProtocol-       $ S.length bytes-
− src/Hans/Message/Types.hs
@@ -1,17 +0,0 @@-{-# LANGUAGE GeneralizedNewtypeDeriving #-}--module Hans.Message.Types where--import Control.Applicative ((<$>))-import Data.Serialize.Get (Get,getWord16be)-import Data.Serialize.Put (Putter,putWord16be)-import Data.Word (Word16)--newtype Lifetime = Lifetime { getLifetime :: Word16 }-  deriving (Show,Eq,Ord,Num)--parseLifetime :: Get Lifetime-parseLifetime  = Lifetime <$> getWord16be--renderLifetime :: Putter Lifetime-renderLifetime  = putWord16be . getLifetime
− src/Hans/Message/Udp.hs
@@ -1,105 +0,0 @@-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE DeriveDataTypeable #-}--module Hans.Message.Udp where--import Hans.Address.IP4-import Hans.Message.Ip4-import Hans.Utils-import Hans.Utils.Checksum--import Control.Applicative ((<$>))-import Data.Serialize.Get (Get,getWord16be,isolate,label,getBytes,remaining)-import Data.Serialize.Put (Put,Putter,runPut,putWord16be)-import Data.Typeable (Typeable)-import Data.Word (Word16)-import qualified Data.ByteString.Lazy as L-import qualified Data.ByteString      as S----- Udp Protocol Number -----------------------------------------------------------udpProtocol :: IP4Protocol-udpProtocol  = IP4Protocol 0x11----- Udp Ports ---------------------------------------------------------------------newtype UdpPort = UdpPort { getUdpPort :: Word16 }-  deriving (Eq,Ord,Num,Read,Show,Enum,Bounded,Typeable)--parseUdpPort :: Get UdpPort-parseUdpPort  = UdpPort <$> getWord16be--renderUdpPort :: Putter UdpPort-renderUdpPort  = putWord16be . getUdpPort----- Udp Header --------------------------------------------------------------------data UdpHeader = UdpHeader-  { udpSourcePort :: !UdpPort-  , udpDestPort   :: !UdpPort-  , udpChecksum   :: !Word16-  } deriving (Eq,Show)--udpHeaderSize :: Int-udpHeaderSize  = 8---- | Parse out a @UdpHeader@, and the size of the payload.-parseUdpHeader :: Get (UdpHeader,Int)-parseUdpHeader  = do-  src <- parseUdpPort-  dst <- parseUdpPort-  len <- getWord16be-  cs  <- getWord16be-  let hdr = UdpHeader src dst cs-  return (hdr,fromIntegral len - udpHeaderSize)---- | Render a @UdpHeader@.-renderUdpHeader :: UdpHeader -> Int -> Put-renderUdpHeader hdr bodyLen = do-  renderUdpPort (udpSourcePort hdr)-  renderUdpPort (udpDestPort hdr)-  putWord16be   (fromIntegral (bodyLen + udpHeaderSize))-  putWord16be   (udpChecksum hdr)----- Udp Packets -------------------------------------------------------------------parseUdpPacket :: Get (UdpHeader,S.ByteString)-parseUdpPacket  = do-  (hdr,len) <- parseUdpHeader-  label "UDPPacket" $ isolate len $ do-    bs <- getBytes =<< remaining-    return (hdr,bs)---- | Given a way to make the pseudo header, render the UDP packet.-renderUdpPacket :: UdpHeader -> L.ByteString -> MkPseudoHeader-                -> IO L.ByteString-renderUdpPacket hdr body mk = do-  -- the checksum is 6 bytes into the rendered packet-  hdrBytes' <- pokeChecksum cs hdrBytes 6-  return (L.fromChunks [hdrBytes'] `L.append` body)-  where-  -- pseudo header-  bodyLen  = fromIntegral (L.length body)-  ph       = mk (bodyLen + udpHeaderSize)-  pcs      = computePartialChecksum emptyPartialChecksum ph--  -- real header-  hdrBytes = runPut (renderUdpHeader (hdr { udpChecksum = 0 }) bodyLen)--  -- body, and final checksum-  hcs = computePartialChecksum pcs hdrBytes-  cs  = finalizeChecksum (computePartialChecksumLazy hcs body)---- | Recreate the UDP checksum, given a rendered packet, and the source and--- destination.-validateUdpChecksum :: IP4 -> IP4 -> S.ByteString -> Bool-validateUdpChecksum src dst bytes =-  finalizeChecksum (computePartialChecksum phcs bytes) == 0-  where-  phcs = computePartialChecksum emptyPartialChecksum-       $ mkIP4PseudoHeader src dst udpProtocol-       $ S.length bytes
+ src/Hans/Monad.hs view
@@ -0,0 +1,109 @@+{-# LANGUAGE MultiParamTypeClasses #-}++module Hans.Monad (+    Hans()+  , runHans, runHansOnce+  , setEscape, escape, dropPacket+  , callCC+  , io+  , decode, decode'+  ) where++import Hans.Device.Types (DeviceStats(),updateDropped,statRX)++import qualified Control.Applicative as A+import qualified Data.ByteString as S+import           Data.IORef (newIORef,writeIORef,readIORef)+import           Data.Serialize.Get (runGet,runGetState,Get)+import           MonadLib (BaseM(..))+++newtype Hans a = Hans { unHans :: IO () -> (a -> IO ()) -> IO () }++instance Functor Hans where+  fmap f m = Hans (\ e k -> unHans m e (k . f))+  {-# INLINE fmap #-}++instance A.Applicative Hans where+  pure x  = Hans (\ _ k -> k x)++  f <*> x = Hans $ \ e k -> unHans f e+                 $ \ g   -> unHans x e+                 $ \ y   -> k (g y)++  {-# INLINE pure  #-}+  {-# INLINE (<*>) #-}++instance Monad Hans where+  return  = A.pure++  m >>= f = Hans $ \ e k -> unHans m e+                 $ \ a   -> unHans (f a) e k++  {-# INLINE return #-}+  {-# INLINE (>>=)  #-}++instance BaseM Hans IO where+  inBase = io+  {-# INLINE inBase #-}+++-- | Run only one iteration of the Hans monad.+runHansOnce :: Hans a -> IO (Maybe a)+runHansOnce (Hans f) =+  do res <- newIORef Nothing+     f (writeIORef res Nothing) (\x -> writeIORef res (Just x))+     readIORef res+++-- | Loop forever, running the underlying operation.+runHans :: Hans () -> IO ()+runHans (Hans m) = loop ()+  where+  loop () = m (loop ()) loop+{-# INLINE runHans #-}++-- | Set the escape continuation to the current position, within the operation+-- given, so that control flow always restores back through this point.+setEscape :: Hans () -> Hans ()+setEscape m = Hans (\ _ k -> unHans m (k ()) k)+{-# INLINE setEscape #-}++-- | Invoke the escape continuation.+escape :: Hans a+escape  = Hans (\ e _ -> e)+{-# INLINE escape #-}++-- | Call-cc. NOTE: the continuation will inherit the escape point of the+-- calling context.+callCC :: ((b -> Hans a) -> Hans b) -> Hans b+callCC f = Hans (\e k -> unHans (f (\b -> Hans (\_ _ -> k b))) e k)+{-# INLINE callCC #-}++-- | Synonym for 'escape' that also updates device statistics.+dropPacket :: DeviceStats -> Hans a+dropPacket stats =+  do io (updateDropped statRX stats)+     escape+{-# INLINE dropPacket #-}++-- | Lift an 'IO' operation into 'Hans'.+io :: IO a -> Hans a+io m = Hans (\ _ k -> m >>= k )+{-# INLINE io #-}++-- | Run a Get action in the context of the Hans monad, +decode :: DeviceStats -> Get a -> S.ByteString -> Hans a+decode dev m bytes =+  case runGet m bytes of+    Right a -> return a+    Left _  -> dropPacket dev+{-# INLINE decode #-}++-- | Run a Get action in the context of the Hans monad, returning any unconsumed+-- input.+decode' :: DeviceStats -> Get a -> S.ByteString -> Hans (a,S.ByteString)+decode' dev m bytes =+  case runGetState m bytes 0 of+    Right a -> return a+    Left _  -> dropPacket dev
+ src/Hans/Nat.hs view
@@ -0,0 +1,55 @@+module Hans.Nat where++import           Hans.Addr (toAddr)+import qualified Hans.Nat.State as Nat+import           Hans.Network (Network)+import           Hans.Tcp.Packet (TcpPort)+import           Hans.Types+import           Hans.Udp.Packet (UdpPort)+++-- | Add a TCP port-forwarding rule.+forwardTcpPort :: Network addr+               => NetworkStack+               -> addr    -- ^ Local address (can be wildcard)+               -> TcpPort -- ^ Local port+               -> addr    -- ^ Remote address+               -> TcpPort -- ^ Remote port+               -> IO ()+forwardTcpPort ns src srcPort dest destPort =+  Nat.addTcpPortForward ns+    Nat.PortForward { pfSourceAddr = toAddr src+                    , pfSourcePort = srcPort+                    , pfDestAddr   = toAddr dest+                    , pfDestPort   = destPort }++-- | Remove a TCP port-forwarding rule.+removeTcpPortForward :: Network addr+                     => NetworkStack+                     -> addr    -- ^ Local address (can be wildcard)+                     -> TcpPort -- ^ Local port+                     -> IO ()+removeTcpPortForward ns src port = Nat.removeTcpPortForward ns (toAddr src) port++-- | Add a UDP port-forwarding rule.+forwardUdpPort :: Network addr+               => NetworkStack+               -> addr    -- ^ Local address (can be wildcard)+               -> UdpPort -- ^ Local port+               -> addr    -- ^ Remote address+               -> UdpPort -- ^ Remote port+               -> IO ()+forwardUdpPort ns src srcPort dest destPort =+  Nat.addUdpPortForward ns+    Nat.PortForward { pfSourceAddr = toAddr src+                    , pfSourcePort = srcPort+                    , pfDestAddr   = toAddr dest+                    , pfDestPort   = destPort }++-- | Remove a UDP port-forwarding rule.+removeUdpPortForward :: Network addr+                     => NetworkStack+                     -> addr    -- ^ Local address (can be wildcard)+                     -> UdpPort -- ^ Local port+                     -> IO ()+removeUdpPortForward ns src port = Nat.removeUdpPortForward ns (toAddr src) port
+ src/Hans/Nat/Forward.hs view
@@ -0,0 +1,120 @@+{-# LANGUAGE RecordWildCards #-}++module Hans.Nat.Forward ( tryForwardUdp, tryForwardTcp ) where++import Hans.Addr.Types (Addr)+import Hans.Lens (view)+import Hans.Network (lookupRoute,RouteInfo(..))+import Hans.Tcp.Packet (TcpHeader(..),tcpSyn)+import Hans.Types+import Hans.Udp.Packet (UdpHeader(..))+++-- TCP -------------------------------------------------------------------------++-- | Try to produce a new TCP packet that should be forwarded. Returns 'Nothing'+-- if the packet was destined for the local machine.+tryForwardTcp :: NetworkStack+              -> Addr -- ^ Local addr+              -> Addr -- ^ Remote addr+              -> TcpHeader+              -> IO (Maybe (RouteInfo Addr,Addr,TcpHeader))+tryForwardTcp ns local remote hdr =+  do let key = Flow local (tcpDestPort hdr) remote (tcpSourcePort hdr)+     mbEntry <- tcpForwardingActive ns key+     case mbEntry of++       -- forwarding is already established, rewrite the packet+       Just entry -> return $! rewrite key entry++       -- No forwarding entry exists. If it's a syn packet and there's a rule, start a+       -- new session.+       Nothing+         | view tcpSyn hdr ->+           do mbRule <- shouldForwardTcp ns key+              case mbRule of+                Nothing -> return Nothing++                -- add an entry to the table, and rewrite the packet+                Just rule ->+                  do mbSess <- newSession ns key rule+                     case mbSess of+                       Just entry -> do addTcpSession ns entry+                                        return $! rewrite key entry++                       Nothing -> return Nothing++         | otherwise ->+           return Nothing++  where++  -- rewrite the source and destination in the header+  rewrite key entry =+    let other = otherSide key entry+        hdr'  = hdr { tcpSourcePort = flowLocalPort  other+                    , tcpDestPort   = flowRemotePort other+                    , tcpChecksum   = 0 }++     in hdr' `seq` Just (flowLocal other, flowRemote other, hdr')+++-- UDP -------------------------------------------------------------------------++-- | Try to produce a new TCP packet that should be forwarded. Returns 'Nothing'+-- if the packet was destined for the local machine.+tryForwardUdp :: NetworkStack+              -> Addr -- ^ Local addr+              -> Addr -- ^ Remote addr+              -> UdpHeader+              -> IO (Maybe (RouteInfo Addr,Addr,UdpHeader))+tryForwardUdp ns local remote hdr =+  do let key = Flow local (udpDestPort hdr) remote (udpSourcePort hdr)+     mbEntry <- udpForwardingActive ns key+     case mbEntry of++       -- forwarding is already established, rewrite the packet+       Just entry -> return $! rewrite key entry++       -- No forwarding entry exists. If a rule exists, add it to the table.+       Nothing ->+         do mbRule <- shouldForwardUdp ns key+            case mbRule of++              Nothing -> return Nothing++              -- add an entry to the table, and rewrite the packet+              Just rule ->+                do mbSess <- newSession ns key rule+                   case mbSess of+                     Just entry -> do addUdpSession ns entry+                                      return $! rewrite key entry+                     Nothing    -> return Nothing++  where++  rewrite key entry =+    let other = otherSide key entry+        hdr' = hdr { udpSourcePort = flowLocalPort  other+                   , udpDestPort   = flowRemotePort other+                   , udpChecksum   = 0 }++     in hdr' `seq` Just (flowLocal other, flowRemote other, hdr')+++newSession :: NetworkStack -> Flow Addr -> PortForward -> IO (Maybe Session)+newSession ns flow rule =+  do l <- lookupRoute ns (flowRemote flow)+     r <- lookupRoute ns (pfDestAddr rule)+     p <- nextTcpPort ns (flowLocal flow) (pfDestAddr rule) (pfDestPort rule)++     case (l,r,p) of+       (Just riLeft, Just riRight, Just rightPort) ->+         return $ Just+                $ Session { sessLeft  = flow { flowLocal = riLeft }+                          , sessRight = Flow { flowLocal      = riRight+                                             , flowLocalPort  = rightPort+                                             , flowRemote     = pfDestAddr rule+                                             , flowRemotePort = pfDestPort rule } }++       _ -> return Nothing
+ src/Hans/Nat/State.hs view
@@ -0,0 +1,282 @@+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DeriveFunctor #-}++module Hans.Nat.State (+    NatState(), HasNatState(..),+    newNatState,+    Flow(..),+    Session(..),++    otherSide,++    PortForward(..),++    -- ** Rules+    addUdpPortForward, removeUdpPortForward,+    addTcpPortForward, removeTcpPortForward,++    -- ** Queries+    udpForwardingActive, addUdpSession, shouldForwardUdp,+    tcpForwardingActive, addTcpSession, shouldForwardTcp,+  ) where++import           Hans.Addr (Addr,isWildcardAddr)+import           Hans.Config (Config(..))+import           Hans.Lens (Getting,view)+import           Hans.Network.Types (RouteInfo(..))+import           Hans.Tcp.Packet (TcpPort)+import           Hans.Threads (forkNamed)+import           Hans.Udp.Packet (UdpPort)++import           Control.Concurrent (ThreadId,threadDelay)+import           Control.Monad (forever)+import           Data.HashPSQ as Q+import           Data.Hashable (Hashable)+import           Data.IORef (IORef,newIORef,readIORef,atomicModifyIORef')+import           Data.List (find)+import           Data.Time.Clock+                     (UTCTime,getCurrentTime,NominalDiffTime,addUTCTime)+import           Data.Word (Word16)+import           GHC.Generics (Generic)+++-- State -----------------------------------------------------------------------++-- | NOTE: as TcpPort and UdpPort are both type aliases to Word16, Flow isn't+-- parameterized on the port type.+data Flow local = Flow { flowLocal      :: !local+                       , flowLocalPort  :: !Word16+                       , flowRemote     :: !Addr+                       , flowRemotePort :: !Word16+                       } deriving (Functor,Eq,Ord,Generic,Show)++instance Hashable remote => Hashable (Flow remote)+++data NatState =+  NatState { natTcpTable_ :: !NatTable+             -- ^ Active TCP flows++           , natTcpRules_ :: !(IORef [PortForward])+             -- ^ Ports that have been forwarded in the TCP layer++           , natUdpTable_ :: !NatTable+             -- ^ Active UDP flows++           , natUdpRules_ :: !(IORef [PortForward])+             -- ^ Ports that have been forwarded in the UDP layer++           , natReaper_   :: !ThreadId+             -- ^ When flows are active, this is the id of the reaping thread+           }++class HasNatState state where+  natState :: Getting r state NatState++instance HasNatState NatState where+  natState = id++data PortForward = PortForward { pfSourceAddr :: !Addr+                                 -- ^ Local address to listen on++                               , pfSourcePort :: !Word16+                                 -- ^ The port on this network stack to+                                 -- forward++                               , pfDestAddr :: !Addr+                                 -- ^ Destination machine to forward to++                               , pfDestPort :: !Word16+                                 -- ^ Destination port to forward to+                               }+++newNatState :: Config -> IO NatState+newNatState cfg =+  do natTcpTable_ <- newNatTable cfg+     natTcpRules_ <- newIORef []+     natUdpTable_ <- newNatTable cfg+     natUdpRules_ <- newIORef []+     natReaper_   <- forkNamed "Nat.reaper" (reaper natTcpTable_ natUdpTable_)+     return NatState { .. }+++-- Nat Tables ------------------------------------------------------------------++data Session = Session { sessLeft, sessRight :: !(Flow (RouteInfo Addr)) }++-- | Gives back the other end of the session.+otherSide :: Flow Addr -> Session -> Flow (RouteInfo Addr)+otherSide flow Session { .. } =+  if flowRemote flow == flowRemote sessLeft+     && flowRemotePort flow == flowRemotePort sessLeft+     then sessRight else sessLeft++sessionFlows :: Session -> (Flow Addr, Flow Addr)+sessionFlows Session { .. } = (fmap riSource sessLeft, fmap riSource sessRight)+++type Sessions = Q.HashPSQ (Flow Addr) UTCTime Session++addSession :: UTCTime -> Session -> Sessions -> Sessions+addSession age a q =+  let (l,r) = sessionFlows a+   in Q.insert l age a (Q.insert r age a q)++removeOldest :: Sessions -> Sessions+removeOldest q =+  case Q.minView q of+    Just (k,_,a,q') -> Q.delete (fmap riSource (otherSide k a)) q'+    Nothing         -> q++removeSession :: Flow Addr -> Sessions -> Maybe (Session,Sessions)+removeSession flow q =+  case Q.deleteView flow q of+    Just (_,a,q') -> Just (a,Q.delete (fmap riSource (otherSide flow a)) q')+    Nothing       -> Nothing+++data NatTable = NatTable { natConfig :: Config+                         , natTable  :: !(IORef Sessions)+                         }++newNatTable :: Config -> IO NatTable+newNatTable natConfig =+  do natTable <- newIORef Q.empty+     return NatTable { .. }++-- | Insert an entry into the NAT table.+insertNatTable :: Session -> NatTable -> IO ()+insertNatTable sess NatTable { .. } =+  do now <- getCurrentTime+     atomicModifyIORef' natTable $ \ q ->+       let q' = addSession now sess q+        in if Q.size q' > cfgNatMaxEntries natConfig+              then (removeOldest q', ())+              else (q', ())++-- | Remove entries from the NAT table, decrementing the size by the number of+-- entries that were removed.+expireEntries :: UTCTime -> NatTable -> IO ()+expireEntries now NatTable { .. } =+  atomicModifyIORef' natTable go+  where+  now' = addUTCTime (negate fourMinutes) now++  -- remove entries that are older than four minutes+  go q =+    case Q.minView q of+      Just (k,p,a,q')+        | p < now'  -> go (Q.delete (fmap riSource (otherSide k a)) q')+        | otherwise -> (q, ())++      Nothing -> (Q.empty, ())++-- | Lookup and touch an entry in the NAT table.+lookupNatTable :: Flow Addr -> NatTable -> IO (Maybe Session)+lookupNatTable key NatTable { .. } =+  do now <- getCurrentTime+     atomicModifyIORef' natTable $ \ q ->+       case removeSession key q of+         Just (a,q') -> (addSession now a q', Just a)+         Nothing     -> (q, Nothing)+++-- Table Reaping ---------------------------------------------------------------++-- | Every two minutes, reap old entries from the TCP and UDP NAT tables.+reaper :: NatTable -> NatTable -> IO ()+reaper tcp udp = forever $+  do threadDelay (2 * 60 * 1000000) -- delay for two minutes++     now <- getCurrentTime+     expireEntries now tcp+     expireEntries now udp++fourMinutes :: NominalDiffTime+fourMinutes  = 4 * 60.0+++-- Rules -----------------------------------------------------------------------++addTcpPortForward :: HasNatState state => state -> PortForward -> IO ()+addTcpPortForward state rule =+  do let NatState { .. } = view natState state+     atomicModifyIORef' natTcpRules_ (\rs -> (rule : rs, ()))++-- | Remove port forwarding for UDP based on source address and port number.+removeTcpPortForward :: HasNatState state => state -> Addr -> TcpPort -> IO ()+removeTcpPortForward state addr port =+  do let NatState { .. } = view natState state+     atomicModifyIORef' natTcpRules_ (\rs -> (filter keepRule rs, ()))+  where+  keepRule PortForward { .. } = pfSourceAddr /= addr || pfSourcePort /= port++addUdpPortForward :: HasNatState state => state -> PortForward -> IO ()+addUdpPortForward state rule =+  do let NatState { .. } = view natState state+     atomicModifyIORef' natUdpRules_ (\rs -> (rule : rs, ()))++-- | Remove port forwarding for UDP based on source address and port number.+removeUdpPortForward :: HasNatState state => state -> Addr -> UdpPort -> IO ()+removeUdpPortForward state addr port =+  do let NatState { .. } = view natState state+     atomicModifyIORef' natUdpRules_ (\rs -> (filter keepRule rs, ()))+  where+  keepRule PortForward { .. } = pfSourceAddr /= addr || pfSourcePort /= port+++-- Queries ---------------------------------------------------------------------++-- | Lookup information about an active forwarding session.+tcpForwardingActive :: HasNatState state+                    => state -> Flow Addr -> IO (Maybe Session)+tcpForwardingActive state key =+  do let NatState { .. } = view natState state+     lookupNatTable key natTcpTable_+++-- | Lookup information about an active forwarding session.+udpForwardingActive :: HasNatState state+                    => state -> Flow Addr -> IO (Maybe Session)+udpForwardingActive state key =+  do let NatState { .. } = view natState state+     lookupNatTable key natUdpTable_+++-- | Insert a TCP forwarding entry into the NAT state.+addTcpSession :: HasNatState state => state -> Session -> IO ()+addTcpSession state sess =+  do let NatState { .. } = view natState state+     insertNatTable sess natTcpTable_+++-- | Insert a UDP forwarding entry into the NAT state.+addUdpSession :: HasNatState state => state -> Session -> IO ()+addUdpSession state sess =+  do let NatState { .. } = view natState state+     insertNatTable sess natUdpTable_++ruleApplies :: Flow Addr -> PortForward -> Bool+ruleApplies Flow { .. } = \ PortForward { .. } ->+  flowLocalPort == pfSourcePort &&+  (flowLocal == pfSourceAddr || isWildcardAddr pfSourceAddr)+++-- | Returns the forwarding rule to use, if this connection should be forwarded.+shouldForwardTcp :: HasNatState state+                 => state -> Flow Addr -> IO (Maybe PortForward)+shouldForwardTcp state flow =+  do let NatState { .. } = view natState state+     rules <- readIORef natTcpRules_+     return $! find (ruleApplies flow) rules+++-- | Returns the forwarding rule to use, if this session should be forwarded+shouldForwardUdp :: HasNatState state+                 => state -> Flow Addr -> IO (Maybe PortForward)+shouldForwardUdp state flow =+  do let NatState { .. } = view natState state+     rules <- readIORef natUdpRules_+     return $! find (ruleApplies flow) rules
+ src/Hans/Network.hs view
@@ -0,0 +1,118 @@+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE PatternSynonyms #-}++module Hans.Network (+    module Hans.Network,+    module Hans.Network.Types+  ) where++import           Hans.Addr (NetworkAddr)+import           Hans.Addr.Types (Addr(..))+import           Hans.Checksum (PartialChecksum)+import           Hans.Device.Types (Device)+import qualified Hans.IP4        as IP4+import qualified Hans.IP4.State  as IP4+import           Hans.Lens+import           Hans.Network.Types+import           Hans.Types++import qualified Data.ByteString.Lazy as L+++-- | Interaction with routing and message delivery for a network layer.+class NetworkAddr addr => Network addr where+  -- | Calculate the pseudo-header for checksumming a packet at this layer of+  -- the network.+  pseudoHeader :: addr -> addr -> NetworkProtocol -> Int -> PartialChecksum++  -- | Lookup a route to reach this destination address.+  lookupRoute :: HasNetworkStack ns => ns -> addr -> IO (Maybe (RouteInfo addr))++  -- | Send a single datagram to a destination.+  sendDatagram' :: HasNetworkStack ns+                => ns+                -> Device+                -> addr -- ^ Source+                -> addr -- ^ Destination+                -> addr -- ^ Next-hop+                -> Bool -- ^ Don't fragment+                -> NetworkProtocol+                -> L.ByteString+                -> IO ()+++sendDatagram :: (HasNetworkStack ns, Network addr)+             => ns -> RouteInfo addr -> addr+             -> Bool -> NetworkProtocol -> L.ByteString+             -> IO ()+sendDatagram ns RouteInfo { .. } = \ dst ->+  sendDatagram' ns riDev riSource dst riNext+{-# INLINE sendDatagram #-}++++-- | Send a datagram and lookup routing information at the same time. Returns+-- 'False' if no route to the destination was known.+routeDatagram :: (HasNetworkStack ns, Network addr)+              => ns -> addr -> Bool -> NetworkProtocol -> L.ByteString -> IO Bool+routeDatagram ns dst df prot bytes =+  do mbRoute <- lookupRoute ns dst+     case mbRoute of+       Just route -> do sendDatagram ns route dst df prot bytes+                        return True++       Nothing    -> return False+++findNextHop :: (HasNetworkStack ns, Network addr)+            => ns+            -> Maybe Device -- ^ Desired output device+            -> Maybe addr   -- ^ Desired source address+            -> addr         -- ^ Destination+            -> IO (Maybe (RouteInfo addr))+findNextHop ns mbDev mbSrc dst =++  -- XXX it might be nice to have lookupRoute return a list of possible routes.+  -- is it unreasonable to think that there may be multiple valid routes for a+  -- datagram?+  do mbRoute <- lookupRoute ns dst+     case mbRoute of+       Just ri | maybe True (== riDev    ri) mbDev+              && maybe True (== riSource ri) mbSrc -> return (Just ri)++       _ -> return Nothing+++-- Generic ---------------------------------------------------------------------++instance Network Addr where+  pseudoHeader (Addr4 src) (Addr4 dst) = \ prot len -> pseudoHeader src dst prot len+  {-# INLINE pseudoHeader #-}++  lookupRoute ns (Addr4 dst) =+    do ri <- lookupRoute ns dst+       return (fmap (fmap Addr4) ri)+  {-# INLINE lookupRoute #-}++  sendDatagram' ns dev (Addr4 src) (Addr4 dst) (Addr4 next) =+    sendDatagram' ns dev src dst next+  {-# INLINE sendDatagram' #-}++++-- IP4 -------------------------------------------------------------------------++instance Network IP4.IP4 where+  pseudoHeader = IP4.ip4PseudoHeader+  {-# INLINE pseudoHeader #-}++  lookupRoute ns ip4 =+    do mb <- IP4.lookupRoute4 (view networkStack ns) ip4+       case mb of+         Just (riSource,riNext,riDev) -> return $! Just RouteInfo { .. }+         Nothing                      -> return Nothing+  {-# INLINE lookupRoute #-}++  sendDatagram' ns dev src dst df next =+    IP4.primSendIP4 (view networkStack ns) dev src dst df next+  {-# INLINE sendDatagram' #-}
+ src/Hans/Network/Types.hs view
@@ -0,0 +1,37 @@+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE PatternSynonyms #-}++module Hans.Network.Types where++import Hans.Device.Types (Device,HasDeviceConfig(..))+import Hans.Lens++import Data.Serialize (Get,Put,getWord8,putWord8)+import Data.Word (Word8)+++type NetworkProtocol = Word8++pattern PROT_ICMP4 = 0x1+pattern PROT_TCP   = 0x6+pattern PROT_UDP   = 0x11++getNetworkProtocol :: Get NetworkProtocol+getNetworkProtocol  = getWord8++putNetworkProtocol :: NetworkProtocol -> Put+putNetworkProtocol  = putWord8+++-- | Information about how to reach a specific destination address (source and+-- next-hop addresses, and device to use).+data RouteInfo addr = RouteInfo { riSource :: !addr+                                  -- ^ The source address to use when sending+                                , riNext :: !addr+                                  -- ^ The next-hop in the route+                                , riDev :: !Device+                                  -- ^ The device used for delivery+                                } deriving (Eq,Functor)++instance HasDeviceConfig (RouteInfo addr) where+  deviceConfig = to riDev . deviceConfig
− src/Hans/NetworkStack.hs
@@ -1,251 +0,0 @@-{-# LANGUAGE TypeSynonymInstances #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE RecordWildCards #-}--module Hans.NetworkStack (-    module Hans.NetworkStack--    -- * Re-exported Types-  , UdpPort-  , TcpPort--    -- * DNS-  , Dns.HostName-  , Dns.HostEntry(..)--    -- * Sockets-  , Tcp.Socket()--    -- ** Socket Functions-  , Tcp.sockRemoteHost-  , Tcp.sockRemotePort-  , Tcp.sockLocalPort-  , Tcp.accept-  , Tcp.close-  , Tcp.sendBytes-  , Tcp.recvBytes--    -- ** Socket Exceptions-  , Tcp.AcceptError(..)-  , Tcp.CloseError(..)-  , Tcp.ConnectError(..)-  , Tcp.ListenError(..)-  ) where--import Hans.Address (getMaskComponents)-import Hans.Address.IP4 (IP4Mask,IP4)-import Hans.Address.Mac (Mac)-import Hans.Channel (newChannel)-import Hans.Message.Ip4 (IP4Protocol,IP4Header)-import Hans.Message.Tcp (TcpPort)-import Hans.Message.Udp (UdpPort)-import qualified Hans.Layer.Arp as Arp-import qualified Hans.Layer.Ethernet as Eth-import qualified Hans.Layer.Dns as Dns-import qualified Hans.Layer.Icmp4 as Icmp4-import qualified Hans.Layer.IP4 as IP4-import qualified Hans.Layer.Tcp as Tcp-import qualified Hans.Layer.Tcp.Socket as Tcp-import qualified Hans.Layer.Udp as Udp--import qualified Data.ByteString as S-import qualified Data.ByteString.Lazy as L----- Generic Network Stack ----------------------------------------------------------- | An example implementation of the whole network stack.-data NetworkStack = NetworkStack-  { nsArp       :: Arp.ArpHandle-  , nsEthernet  :: Eth.EthernetHandle-  , nsIp4       :: IP4.IP4Handle-  , nsIcmp4     :: Icmp4.Icmp4Handle-  , nsUdp       :: Udp.UdpHandle-  , nsTcp       :: Tcp.TcpHandle-  , nsDns       :: Dns.DnsHandle-  }--instance HasArp      NetworkStack where arpHandle      = nsArp-instance HasEthernet NetworkStack where ethernetHandle = nsEthernet-instance HasIP4      NetworkStack where ip4Handle      = nsIp4-instance HasIcmp4    NetworkStack where icmp4Handle    = nsIcmp4-instance HasTcp      NetworkStack where tcpHandle      = nsTcp-instance HasUdp      NetworkStack where udpHandle      = nsUdp-instance HasDns      NetworkStack where dnsHandle      = nsDns--newNetworkStack :: IO NetworkStack-newNetworkStack  = do-  nsEthernet <- newChannel-  nsArp      <- newChannel-  nsIp4      <- newChannel-  nsIcmp4    <- newChannel-  nsUdp      <- newChannel-  nsTcp      <- newChannel-  nsDns      <- newChannel--  let ns = NetworkStack { .. }--  startEthernetLayer ns-  startArpLayer      ns-  startIcmp4Layer    ns-  startIP4Layer      ns-  startUdpLayer      ns-  startTcpLayer      ns-  startDnsLayer      ns--  return ns------ Ethernet Layer Interface ------------------------------------------------------class HasEthernet stack where-  ethernetHandle :: stack -> Eth.EthernetHandle---- | Start the ethernet layer in a network stack.-startEthernetLayer :: HasEthernet stack => stack -> IO ()-startEthernetLayer stack =-  Eth.runEthernetLayer (ethernetHandle stack)---- | Add an ethernet device to the ethernet layer.-addDevice :: HasEthernet stack => stack -> Mac -> Eth.Tx -> Eth.Rx -> IO ()-addDevice stack = Eth.addEthernetDevice (ethernetHandle stack)---- | Remove a device from the ethernet layer.-removeDevice :: HasEthernet stack => stack -> Mac -> IO ()-removeDevice stack = Eth.removeEthernetDevice (ethernetHandle stack)---- | Bring an ethernet device in the ethernet layer up.-deviceUp :: HasEthernet stack => stack -> Mac -> IO ()-deviceUp stack = Eth.startEthernetDevice (ethernetHandle stack)---- | Bring an ethernet device in the ethernet layer down.-deviceDown :: HasEthernet stack => stack -> Mac -> IO ()-deviceDown stack = Eth.stopEthernetDevice (ethernetHandle stack)----- Arp Layer Interface -----------------------------------------------------------class HasArp stack where-  arpHandle :: stack -> Arp.ArpHandle---- | Start the arp layer in a network stack.-startArpLayer :: (HasEthernet stack, HasArp stack) => stack -> IO ()-startArpLayer stack =-  Arp.runArpLayer (arpHandle stack) (ethernetHandle stack)----- Icmp4 Layer Interface ---------------------------------------------------------class HasIcmp4 stack where-  icmp4Handle :: stack -> Icmp4.Icmp4Handle---- | Start the icmp4 layer in a network stack..-startIcmp4Layer :: (HasIcmp4 stack, HasIP4 stack) => stack -> IO ()-startIcmp4Layer stack =-  Icmp4.runIcmp4Layer (icmp4Handle stack) (ip4Handle stack)----- IP4 Layer Interface -----------------------------------------------------------class HasIP4 stack where-  ip4Handle :: stack -> IP4.IP4Handle---- | Start the IP4 layer in a network stack.-startIP4Layer :: (HasArp stack, HasEthernet stack, HasIP4 stack)-              => stack -> IO ()-startIP4Layer stack =-  IP4.runIP4Layer (ip4Handle stack) (arpHandle stack) (ethernetHandle stack)--type Mtu = Int---- | Add an IP4 address to a network stack.-addIP4Addr :: (HasArp stack, HasIP4 stack)-           => stack -> IP4Mask -> Mac -> Mtu -> IO ()-addIP4Addr stack mask mac mtu = do-  let (addr,_) = getMaskComponents mask-  Arp.addLocalAddress (arpHandle stack) addr mac-  IP4.addIP4RoutingRule (ip4Handle stack) (IP4.Direct mask addr mtu)---- | Add a route for a network, via an address.-routeVia :: HasIP4 stack => stack -> IP4Mask -> IP4 -> IO ()-routeVia stack mask addr =-  IP4.addIP4RoutingRule (ip4Handle stack) (IP4.Indirect mask addr)---- | Register a handler for an IP4 protocol-listenIP4Protocol :: HasIP4 stack-                  => stack -> IP4Protocol -> IP4.Handler -> IO ()-listenIP4Protocol stack = IP4.addIP4Handler (ip4Handle stack)---- | Register a handler for an IP4 protocol-ignoreIP4Protocol :: HasIP4 stack => stack -> IP4Protocol -> IO ()-ignoreIP4Protocol stack = IP4.removeIP4Handler (ip4Handle stack)----- Udp Layer Interface -----------------------------------------------------------class HasUdp stack where-  udpHandle :: stack -> Udp.UdpHandle---- | Start the UDP layer of a network stack.-startUdpLayer :: (HasIP4 stack, HasIcmp4 stack, HasUdp stack) => stack -> IO ()-startUdpLayer stack =-  Udp.runUdpLayer (udpHandle stack) (ip4Handle stack) (icmp4Handle stack)---- | Add a handler for a UDP port.-addUdpHandler :: HasUdp stack => stack -> UdpPort -> Udp.Handler -> IO ()-addUdpHandler stack = Udp.addUdpHandler (udpHandle stack)---- | Remove a handler for a UDP port.-removeUdpHandler :: HasUdp stack => stack -> UdpPort -> IO ()-removeUdpHandler stack = Udp.removeUdpHandler (udpHandle stack)---- | Inject a packet into the UDP layer.-queueUdp :: HasUdp stack => stack -> IP4Header -> S.ByteString -> IO ()-queueUdp stack = Udp.queueUdp (udpHandle stack)---- | Send a UDP packet.-sendUdp :: HasUdp stack-        => stack -> IP4 -> Maybe UdpPort -> UdpPort -> L.ByteString -> IO ()-sendUdp stack = Udp.sendUdp (udpHandle stack)----- Tcp Layer Interface -----------------------------------------------------------class HasIP4 stack => HasTcp stack where-  tcpHandle :: stack -> Tcp.TcpHandle---- | Start the TCP layer of a network stack.-startTcpLayer :: HasTcp stack => stack -> IO ()-startTcpLayer stack =-  Tcp.runTcpLayer (tcpHandle stack) (ip4Handle stack)---- | Listen for incoming connections.-listen :: HasTcp stack => stack -> IP4 -> TcpPort -> IO Tcp.Socket-listen stack = Tcp.listen (tcpHandle stack)---- | Make a remote connection.-connect :: HasTcp stack => stack -> IP4 -> TcpPort -> Maybe TcpPort -> IO Tcp.Socket-connect stack = Tcp.connect (tcpHandle stack)----- Dns Layer Interface -----------------------------------------------------------class HasUdp stack => HasDns stack where-  dnsHandle :: stack -> Dns.DnsHandle--startDnsLayer :: HasDns stack => stack -> IO ()-startDnsLayer stack =-  Dns.runDnsLayer (dnsHandle stack) (udpHandle stack)--addNameServer :: HasDns stack => stack -> IP4 -> IO ()-addNameServer stack = Dns.addNameServer (dnsHandle stack)--removeNameServer :: HasDns stack => stack -> IP4 -> IO ()-removeNameServer stack = Dns.removeNameServer (dnsHandle stack)--getHostByName :: HasDns stack => stack -> Dns.HostName -> IO Dns.HostEntry-getHostByName stack = Dns.getHostByName (dnsHandle stack)--getHostByAddr :: HasDns stack => stack -> IP4 -> IO Dns.HostEntry-getHostByAddr stack = Dns.getHostByAddr (dnsHandle stack)
− src/Hans/Ports.hs
@@ -1,60 +0,0 @@-{-# LANGUAGE RecordWildCards #-}--module Hans.Ports (-    -- * Port Management-    PortManager-  , emptyPortManager-  , isUsed-  , reserve-  , unreserve-  , nextPort-  ) where--import Control.Monad (guard)-import qualified Data.Set as Set----- Port Management ---------------------------------------------------------------data PortManager i = PortManager-  { portNext     :: [i]-  , portReserved :: Set.Set i-  , portActive   :: Set.Set i-  }--instance Show i => Show (PortManager i) where-  show pm = "<Active ports: " ++ show (portActive pm) ++ ">"--emptyPortManager :: [i] -> PortManager i-emptyPortManager range = PortManager-  { portNext     = range-  , portReserved = Set.empty-  , portActive   = Set.empty-  }--isUsed :: (Eq i, Ord i) => i -> PortManager i -> Bool-isUsed i PortManager { .. } = i `Set.member` portActive-                           || i `Set.member` portReserved--reserve :: (Eq i, Ord i, Show i) => i -> PortManager i -> Maybe (PortManager i)-reserve i pm = do-  guard (not (isUsed i pm))-  return $! pm-    { portReserved = Set.insert i (portReserved pm)-    }--unreserve :: (Eq i, Ord i, Show i) => i -> PortManager i -> Maybe (PortManager i)-unreserve i pm @ PortManager { .. }-  | Set.member i portReserved = Just pm  { portReserved = Set.delete i portReserved }-  | Set.member i portActive   = Just pm' { portActive   = Set.delete i portActive }-  | otherwise                 = Nothing-  where-  pm' = pm { portNext = i : portNext }--nextPort :: (Eq i, Ord i, Show i) => PortManager i -> Maybe (i, PortManager i)-nextPort pm = case span (`isUsed` pm) (portNext pm) of--  (_,i:rest) -> return (i, pm { portNext = rest-                              , portActive = Set.insert i (portActive pm) })--  _ -> Nothing
+ src/Hans/Serialize.hs view
@@ -0,0 +1,15 @@+module Hans.Serialize where++import qualified Data.ByteString.Builder.Extra as B+import qualified Data.ByteString.Lazy as L+import           Data.Serialize (Put,execPut)+++-- | A specialized version of 'runPut' that allows the initial and successive+-- buffer sizes for the 'Builder' to be defined. For example, if you're+-- rendering an IP4 packet, you might give the first parameter as 20 (the size+-- of the fixed header), and the second as 40 (the maximum additional length for+-- options).+runPutPacket :: Int -> Int -> L.ByteString -> Put -> L.ByteString+runPutPacket isz ssz tl m =+  B.toLazyByteStringWith (B.untrimmedStrategy isz ssz) tl (execPut m)
− src/Hans/Simple.hs
@@ -1,64 +0,0 @@-module Hans.Simple (-    -- * UDP Messages-    renderUdp-  , renderIp4--    -- * Ident-  , Ident-  , newIdent-  , nextIdent-  ) where--import Hans.Address.IP4 (IP4)-import Hans.Message.Ip4-    (IP4Header(..),IP4Protocol(..) ,mkIP4PseudoHeader,splitPacket-    ,renderIP4Packet,emptyIP4Header)-import Hans.Message.Udp (UdpHeader(..),UdpPort,renderUdpPacket)-import qualified Hans.Message.Ip4 as IP4--import Control.Concurrent (MVar,newMVar,modifyMVar)-import Data.Word (Word16)-import qualified Data.ByteString.Lazy as L--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-          -> L.ByteString-          -> IO [L.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 hdr payload mk-  renderIp4 i mb prot source dest udp----- | Render an IP4 packet.-renderIp4 :: Ident -> Maybe MTU -> IP4Protocol -> IP4 -> IP4 -> L.ByteString-          -> IO [L.ByteString]-renderIp4 ident mb prot source dest payload = do-  i <- nextIdent ident-  let hdr = emptyIP4Header-        { ip4Protocol   = prot-        , ip4SourceAddr = source-        , ip4DestAddr   = dest-        , ip4Ident      = i-        }-  mapM (uncurry renderIP4Packet) (splitPacket (fromMTU mb) hdr payload)
+ src/Hans/Socket.hs view
@@ -0,0 +1,39 @@+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE FlexibleContexts #-}++module Hans.Socket (+    -- * Abstract Sockets+    Socket(..),+    ListenSocket(..),+    DataSocket(..),+    SocketConfig(..), defaultSocketConfig,+    SockPort,++    -- ** UDP Sockets+    UdpSocket(),+    newUdpSocket,+    sendto,+    recvfrom,+    recvfrom',++    -- ** TCP Sockets+    TcpSocket(),+    TcpListenSocket(),++    tcpRemoteAddr,+    tcpRemotePort,+    tcpLocalAddr,+    tcpLocalPort,++    -- ** Exceptions+    ConnectionException,+    ListenException,+    RoutingException++  ) where++import Hans.Socket.Udp+import Hans.Socket.Tcp+import Hans.Socket.Types
+ src/Hans/Socket/Handle.hs view
@@ -0,0 +1,105 @@+{-# LANGUAGE FlexibleInstances    #-}+{-# LANGUAGE MultiWayIf           #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}+module Hans.Socket.Handle(makeHansHandle) where++import           Control.Concurrent(threadDelay)+import           Control.Exception(throwIO)+import qualified Data.ByteString as BSS+import qualified Data.ByteString.Lazy as BS+import           Data.Typeable(Typeable)+import           Foreign.Ptr(Ptr, castPtr, plusPtr)+import           GHC.IO.Buffer(newByteBuffer)+import           GHC.IO.BufferedIO(BufferedIO(..),+                                   readBuf, readBufNonBlocking,+                                   writeBuf, writeBufNonBlocking)+import           GHC.IO.Device(IODevice(..), RawIO(..), IODeviceType(..))+import           GHC.IO.Handle(mkFileHandle, noNewlineTranslation)+import           Hans.Network(Network(..))+import           Hans.Socket(Socket(..), DataSocket(..))+import           Prelude hiding (read)+import           System.IO(Handle, IOMode)+++instance (Socket sock, DataSocket sock, Network addr) =>+            IODevice (sock addr) where+  ready dev forWrite msecs =+    do let tester = if forWrite then sCanWrite else sCanRead+       canDo <- tester dev+       if | canDo      -> return True+          | msecs <= 0 -> return False+          | otherwise  -> do let delay = min msecs 100+                             threadDelay (delay * 1000)+                             ready dev forWrite (msecs - delay)+  close bs     = sClose bs+  isTerminal _ = return False+  isSeekable _ = return False+  seek _ _ _   = throwIO (userError "Seek on HaNS socket.")+  tell _       = throwIO (userError "Tell on HaNS socket.")+  getSize _    = throwIO (userError "getSize on HaNS socket.")+  setSize _ _  = throwIO (userError "setSize on HaNS socket.")+  setEcho _ _  = throwIO (userError "setEcho on HaNS socket.")+  getEcho _    = throwIO (userError "getEcho on HaNS socket.")+  setRaw _ _   = return ()+  devType _    = return Stream+  dup _        = throwIO (userError "dup on HaNS socket.")+  dup2 _ _     = throwIO (userError "dup2 on HaNS socket.")++instance (Socket sock, DataSocket sock, Network addr) =>+            RawIO (sock addr) where+  read sock dptr sz =+    do bstr <- sRead sock (fromIntegral sz)+       copyToPtr dptr sz bstr+  readNonBlocking sock dptr sz =+    do mbstr <- sTryRead sock (fromIntegral sz)+       case mbstr of+         Nothing   -> return Nothing+         Just bstr -> Just `fmap` copyToPtr dptr sz bstr+  write sock ptr sz =+    do bstr <- BSS.packCStringLen (castPtr ptr, sz)+       sendAll (BS.fromStrict bstr)+   where+    sendAll bstr+      | BS.null bstr = return ()+      | otherwise    = do num <- sWrite sock bstr+                          sendAll (BS.drop (fromIntegral num) bstr)+  writeNonBlocking sock ptr sz =+    do bstr <- BSS.packCStringLen (castPtr ptr, sz)+       num  <- sWrite sock (BS.fromStrict bstr)+       return (fromIntegral num)++instance (Socket sock, DataSocket sock, Network addr) =>+            BufferedIO (sock addr) where+  newBuffer         _ = newByteBuffer (64 * 1024)+  fillReadBuffer      = readBuf+  fillReadBuffer0     = readBufNonBlocking+  flushWriteBuffer    = writeBuf+  flushWriteBuffer0   = writeBufNonBlocking++-- |Make a GHC Handle from a Hans handle.+makeHansHandle :: (Socket sock, DataSocket sock, Network addr, Typeable sock) =>+                  sock addr -> IOMode -> IO Handle+makeHansHandle socket mode =+  mkFileHandle socket "<socket>" mode Nothing noNewlineTranslation++copyToPtr :: Num a => Ptr b -> Int -> BS.ByteString -> IO a+copyToPtr ptr sz bstr+  | BS.length bstr == 0              = return 0+  | BS.length bstr > fromIntegral sz = fail "Too big a chunk for copy!"+  | otherwise                        =+      do copyBS (BS.toChunks bstr) ptr sz+         return (fromIntegral (BS.length bstr))++copyBS :: [BSS.ByteString] -> Ptr a -> Int -> IO ()+copyBS [] _ _ = return ()+copyBS (f:rest) sptr szLeft+  | BSS.null f   = copyBS rest sptr szLeft+  | szLeft <= 0  = return ()+  | otherwise    =+      do let (chunk1, chunk2) = BSS.splitAt szLeft f+             amt              = fromIntegral (BSS.length chunk1)+         BSS.useAsCString chunk1 $ \ dptr -> memcpy dptr sptr amt+         copyBS (chunk2 : rest) (sptr `plusPtr` amt) (szLeft - amt)++foreign import ccall unsafe "string.h memcpy"+  memcpy :: Ptr a -> Ptr b -> Int -> IO ()
+ src/Hans/Socket/Tcp.hs view
@@ -0,0 +1,241 @@+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE RecordWildCards    #-}+{-# LANGUAGE TypeFamilies       #-}++module Hans.Socket.Tcp where++import           Hans.Addr+import qualified Hans.Buffer.Stream as Stream+import qualified Hans.HashTable as HT+import           Hans.Lens (Getting,view,to)+import           Hans.Network+import           Hans.Socket.Types+import           Hans.Tcp.Tcb+import           Hans.Tcp.Message+import           Hans.Tcp.Output+import qualified Hans.Tcp.SendWindow as Send+import           Hans.Types++import           Control.Concurrent (newEmptyMVar,tryPutMVar,takeMVar,yield)+import           Control.Exception (throwIO, handle)+import           Control.Monad (unless,when)+import qualified Data.ByteString.Lazy as L+import           Data.IORef (readIORef)+import           Data.Time.Clock (getCurrentTime)+import           Data.Typeable(Typeable)+import           System.CPUTime (getCPUTime)+++-- TCP Sockets -----------------------------------------------------------------++data TcpSocket addr = TcpSocket { tcpNS  :: !NetworkStack+                                , tcpTcb :: !Tcb+                                }+ deriving (Typeable)++instance HasNetworkStack (TcpSocket addr) where+  networkStack = to tcpNS++-- | Routing information for this socket.+tcpRoute :: NetworkAddr addr => Getting r (TcpSocket addr) (RouteInfo addr)+tcpRoute  = to (\ TcpSocket { tcpTcb = Tcb { .. } } -> cast tcbRouteInfo)+  where+  cast RouteInfo { .. } =+    case (fromAddr riSource, fromAddr riNext) of+      (Just a,Just b) -> RouteInfo { riSource = a, riNext = b, .. }+      _               -> error "tcpRoute: invalid address combination"++-- | The source address of this socket.+tcpLocalAddr :: NetworkAddr addr => Getting r (TcpSocket addr) addr+tcpLocalAddr  = tcpRoute . to riSource++-- | The local port for this socket.+tcpLocalPort :: Getting r (TcpSocket addr) SockPort+tcpLocalPort  = to (\ TcpSocket { tcpTcb = Tcb { .. } } -> tcbLocalPort )++-- | The remote address of this socket.+tcpRemoteAddr :: NetworkAddr addr => Getting r (TcpSocket addr) addr+tcpRemoteAddr  = to (\ TcpSocket { tcpTcb = Tcb { .. } } -> cast tcbRemote)+  where+  cast addr =+    case fromAddr addr of+      Just a  -> a+      Nothing -> error "tcpRemoteHost: invalid remote address"++-- | The remote port of this socket.+tcpRemotePort :: Getting r (TcpSocket addr) SockPort+tcpRemotePort  = to (\ TcpSocket { tcpTcb = Tcb { .. } } -> tcbRemotePort)+++-- | Add a new active connection to the TCP state. The connection will initially+-- be in the 'SynSent' state, as a Syn will be sent when the 'Tcb' is created.+activeOpen :: Network addr+           => NetworkStack -> RouteInfo addr -> SockPort -> addr -> SockPort+           -> IO Tcb+activeOpen ns ri srcPort dst dstPort =+  do let ri'  = toAddr `fmap` ri+         dst' = toAddr dst++     done <- newEmptyMVar++     now   <- getCurrentTime+     tsval <- getCPUTime+     let tsc = Send.initialTSClock (fromInteger tsval) now++     iss <- nextIss (view tcpState ns) (riSource ri') srcPort dst' dstPort+     tcb <- newTcb ns Nothing iss ri' srcPort dst' dstPort Closed tsc+                (\_ _ -> tryPutMVar done True  >> return ())+                (\_ _ -> tryPutMVar done False >> return ())++     let update Nothing = (Just tcb, True)+         update Just{}  = (Nothing, False)+     success <- HT.alter update (view tcbKey tcb) (view tcpActive ns)+     if success+        then+          do syn <- mkSyn tcb+             _   <- sendWithTcb ns tcb syn L.empty+             setState tcb SynSent++             established <- takeMVar done+             if established+                then return tcb+                else throwIO ConnectionRefused++        else throwIO AlreadyConnected+++instance Socket TcpSocket where++  sClose TcpSocket { .. } =+    do state <- readIORef (tcbState tcpTcb)+       case state of++         -- the remote side closed the connection, so we just need to cleanup.+         CloseWait ->+           do sendFin tcpNS tcpTcb+              setState tcpTcb LastAck++         Established ->+           do sendFin tcpNS tcpTcb+              setState tcpTcb FinWait1++         -- SynSent, Listen, Closed, Closing, LastAck+         _ -> return ()+++-- | Guard an action that will use the send window.+guardSend :: Tcb -> IO r -> IO r+guardSend tcb send =+  do st <- getState tcb+     case st of+       Closed -> throwIO NoConnection++       Listen -> error "guardSend: Listen state for active tcb"++       -- the send window should queue these+       SynReceived -> send+       SynSent     -> send++       Established -> send+       CloseWait   -> send++       -- FinWait1, FinWait2, Closing, LastAck, TimeWait+       _ -> throwIO ConnectionClosing+++-- | Guard an action that will use the receive buffer.+guardRecv :: Tcb -> IO r -> IO r+guardRecv tcb recv =+  do st <- getState tcb+     case st of+       Closed      -> throwIO NoConnection++       -- these three cases will block until data is available+       Listen      -> recv+       SynSent     -> recv+       SynReceived -> recv++       -- the common case+       Established -> recv+       FinWait1    -> recv+       FinWait2    -> recv++       -- XXX: is this enough?+       CloseWait   -> do avail <- Stream.bytesAvailable (tcbRecvBuffer tcb)+                         if avail+                            then recv+                            else throwIO ConnectionClosing++       Closing     -> throwIO ConnectionClosing+       LastAck     -> throwIO ConnectionClosing+       TimeWait    -> throwIO ConnectionClosing++++instance DataSocket TcpSocket where++  sConnect ns SocketConfig { .. } mbDev src mbSrcPort dst dstPort =+    do let tcpNS = view networkStack ns++       ri <- route tcpNS mbDev src dst++       srcPort <- case mbSrcPort of+                    Just port -> return port+                    Nothing   ->+                      do mb <- nextTcpPort tcpNS (toAddr (riSource ri)) (toAddr dst) dstPort+                         case mb of+                           Just port -> return port+                           Nothing   -> throwIO NoPortAvailable++       -- activeOpen will start the connection for us, sending a SYN to the+       -- remote end of the connection.+       tcpTcb <- activeOpen tcpNS ri srcPort dst dstPort+       return TcpSocket { .. }++  sCanWrite TcpSocket { .. } =+    handle ((\ _ -> return False) :: (ConnectionException -> IO Bool)) $+      guardSend tcpTcb (canSend tcpTcb)++  -- segmentize the bytes, and return to the user the number of bytes that have+  -- been moved into the send window+  sWrite TcpSocket { .. } bytes =+    guardSend tcpTcb $ do len <- sendData tcpNS tcpTcb bytes++                          when (len < L.length bytes) yield++                          return $! fromIntegral len++  sCanRead TcpSocket { .. }     = guardRecv tcpTcb (haveBytesAvail      tcpTcb)+  sRead    TcpSocket { .. } len = guardRecv tcpTcb (receiveBytes    len tcpTcb)+  sTryRead TcpSocket { .. } len = guardRecv tcpTcb (tryReceiveBytes len tcpTcb)+++data TcpListenSocket addr = TcpListenSocket { tlNS  :: !NetworkStack+                                            , tlTcb :: !ListenTcb+                                            }+++instance Socket TcpListenSocket where++  -- NOTE: as listen sockets are always in the Listen state, we don't need to+  -- consider any of the other cases on page 60 of RFC 793.+  sClose TcpListenSocket { .. } = deleteListening tlNS tlTcb+  {-# INLINE sClose #-}+++instance ListenSocket TcpListenSocket where++  type Client TcpListenSocket = TcpSocket++  sListen ns SocketConfig { .. } src srcPort backlog =+    do let tlNS = view networkStack ns+       tlTcb <- newListenTcb (toAddr src) srcPort backlog++       created <- registerListening tlNS tlTcb+       unless created (throwIO AlreadyListening)++       return $! TcpListenSocket { .. }++  sAccept TcpListenSocket { .. } =+    do tcpTcb <- acceptTcb tlTcb+       return $! TcpSocket { tcpNS = tlNS, .. }
+ src/Hans/Socket/Types.hs view
@@ -0,0 +1,145 @@+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE DeriveDataTypeable #-}++module Hans.Socket.Types where++import Hans.Addr (isWildcardAddr)+import Hans.Device.Types (Device)+import Hans.Network (Network(..),RouteInfo(..))+import Hans.Types (HasNetworkStack,NetworkStack)++import           Control.Exception (Exception,throwIO)+import qualified Data.ByteString.Lazy as L+import           Data.Typeable (Typeable)+import           Data.Word (Word16)+++-- Socket Addresses ------------------------------------------------------------++type SockPort = Word16+++-- Generic Socket Operations ---------------------------------------------------++data SocketConfig = SocketConfig { scRecvBufferSize :: !Int+                                   -- ^ Bytes to buffer+                                 } deriving (Show)++defaultSocketConfig :: SocketConfig+defaultSocketConfig  = SocketConfig { scRecvBufferSize = 4096 }++class Socket sock where++  -- | Close an open socket.+  sClose :: Network addr => sock addr -> IO ()+++class (DataSocket (Client sock), Socket sock) => ListenSocket sock where++  type Client sock :: * -> *++  -- | Create a listening socket, with a backlog of n.+  sListen :: (HasNetworkStack ns, Network addr)+          => ns -> SocketConfig -> addr -> SockPort -> Int -> IO (sock addr)++  sAccept :: Network addr => sock addr -> IO (Client sock addr)+++class Socket sock => DataSocket sock where++  -- | Connect this socket to one on a remote machine.+  sConnect :: (HasNetworkStack ns, Network addr)+           => ns+           -> SocketConfig+           -> Maybe Device+           -> addr           -- ^ Local address+           -> Maybe SockPort -- ^ Local port+           -> addr           -- ^ Remote host+           -> SockPort       -- ^ Remote port+           -> IO (sock addr)++  -- | Returns True iff there is currently space in the buffer to accept a+  -- write. Note, this is probably a bad thing to count on in a concurrent+  -- system ...+  sCanWrite :: Network addr => sock addr -> IO Bool++  -- | Send a chunk of data on a socket.+  sWrite :: Network addr => sock addr -> L.ByteString -> IO Int++  -- | Returns True iff there is data in the buffer that can be read.+  -- Note, this is probably a bad thing to count on in a concurrent+  -- system ...+  sCanRead :: Network addr => sock addr -> IO Bool++  -- | Read a chunk of data from a socket. Reading an empty result indicates+  -- that the socket has closed.+  sRead :: Network addr => sock addr -> Int -> IO L.ByteString++  -- | Non-blocking read from a socket. Reading an empty result means that the+  -- socket has closed, while reading a 'Nothing' result indicates that there+  -- was no data available.+  sTryRead :: Network addr => sock addr -> Int -> IO (Maybe L.ByteString)+++-- Exceptions ------------------------------------------------------------------++data ConnectionException = AlreadyConnected+                           -- ^ This connection already exists.++                         | NoConnection+                           -- ^ No information about the other end of the+                           -- socket was present.++                         | NoPortAvailable+                           -- ^ All ports are in use.++                         | ConnectionRefused++                         | ConnectionClosing++                         | DoesNotExist+                           -- ^ The connection is already closed.+                           deriving (Show,Typeable)++data ListenException = AlreadyListening+                       -- ^ Something is already listening on this+                       -- host/port combination.+                       deriving (Show,Typeable)++data RoutingException = NoRouteToHost+                        -- ^ It's not possible to reach this host from this+                        -- source address.+                        deriving (Show,Typeable)++instance Exception ConnectionException+instance Exception ListenException+instance Exception RoutingException+++-- Utilities -------------------------------------------------------------------++-- | Raise an exception when no route can be found to the destination.+route :: Network addr+      => NetworkStack -> Maybe Device -> addr -> addr -> IO (RouteInfo addr)++route ns mbDev src dst =+  do mbRoute <- route' ns mbDev src dst+     case mbRoute of+       Just ri -> return ri+       Nothing -> throwIO NoRouteToHost+++-- | Return source routing information, when a route exists to the destination.+route' :: Network addr+       => NetworkStack -> Maybe Device -> addr -> addr+       -> IO (Maybe (RouteInfo addr))++route' ns mbDev src dst =+  do mbRoute <- lookupRoute ns dst+     case mbRoute of+       Just ri | maybe True (riDev ri ==) mbDev+                 && (src == riSource ri || isWildcardAddr src) ->+                 return (Just ri)++       _ -> return Nothing
+ src/Hans/Socket/Udp.hs view
@@ -0,0 +1,194 @@+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TypeFamilies #-}++module Hans.Socket.Udp where++import           Hans.Addr (toAddr,fromAddr,isBroadcastAddr)+import qualified Hans.Buffer.Datagram as DGram+import           Hans.Device.Types (Device)+import           Hans.Lens (view,to)+import           Hans.Network (Network,RouteInfo(..))+import           Hans.Socket.Types+import           Hans.Types+import           Hans.Udp.Output (primSendUdp)++import           Control.Exception (throwIO)+import qualified Data.ByteString as S+import qualified Data.ByteString.Lazy as L+import           Data.IORef (IORef,newIORef,readIORef)+++-- UDP Sockets -----------------------------------------------------------------++data SockState addr = KnownRoute !(RouteInfo addr) !addr !SockPort !SockPort+                      -- ^ Cached route, and port information++                    | KnownSource !(Maybe Device) !addr !SockPort+                      -- ^ Known source only.+++data UdpSocket addr = UdpSocket { udpNS        :: !NetworkStack+                                , udpBuffer    :: !UdpBuffer+                                , udpSockState :: !(IORef (SockState addr))+                                , udpClose     :: !(IO ())+                                }++instance HasNetworkStack (UdpSocket addr) where+  networkStack = to udpNS+  {-# INLINE networkStack #-}++instance Socket UdpSocket where++  sClose UdpSocket { .. } = udpClose+  {-# INLINE sClose #-}+++newUdpSocket :: (HasNetworkStack ns, Network addr)+             => ns+             -> SocketConfig+             -> Maybe Device+             -> addr+             -> Maybe SockPort+             -> IO (UdpSocket addr)++newUdpSocket ns SocketConfig { .. } mbDev src mbSrcPort =+  do let udpNS = view networkStack ns++     srcPort <- case mbSrcPort of+                 Nothing -> do mb <- nextUdpPort udpNS (toAddr src)+                               case mb of+                                 Just port -> return port+                                 Nothing   -> throwIO NoPortAvailable++                 Just p  -> return p++     udpSockState <- newIORef (KnownSource mbDev src srcPort)++     udpBuffer <- DGram.newBuffer scRecvBufferSize++     -- XXX: Need some SocketError exceptions: this only happens if there's+     -- already something listening+     mbClose  <- registerRecv udpNS (toAddr src) srcPort udpBuffer+     udpClose <- case mbClose of+                   Just unreg -> return unreg+                   Nothing    -> throwIO AlreadyListening++     return $! UdpSocket { .. }+++instance DataSocket UdpSocket where++  -- Always lookup the route before modifying the state of the socket, so that+  -- the state can be manipulated atomically.+  sConnect ns SocketConfig { .. } mbDev src mbPort dst dstPort =+    do let udpNS = view networkStack ns++       ri <- route udpNS mbDev src dst++       srcPort <- case mbPort of+                    Just p  -> return p+                    Nothing -> do mb <- nextUdpPort udpNS (toAddr (riSource ri))+                                  case mb of+                                    Just port -> return port+                                    Nothing   -> throwIO NoPortAvailable++       udpSockState <- newIORef (KnownRoute ri dst srcPort dstPort)++       udpBuffer <- DGram.newBuffer scRecvBufferSize++       mbClose  <- registerRecv udpNS (toAddr src) srcPort udpBuffer+       udpClose <- case mbClose of+                     Just unreg -> return unreg+                     Nothing    -> throwIO AlreadyConnected++       return UdpSocket { .. }++  sCanWrite UdpSocket { .. } =+    do path <- readIORef udpSockState+       case path of+         KnownRoute _ _ _ _ -> return True+         KnownSource{}      -> return False++  sWrite UdpSocket { .. } bytes =+    do path <- readIORef udpSockState+       case path of++         KnownRoute ri dst srcPort dstPort ->+           do sent <- primSendUdp udpNS ri dst srcPort dstPort bytes+              if sent+                 then return (fromIntegral (L.length bytes))+                 else return (-1)++         KnownSource{} ->+              throwIO NoConnection++  sCanRead UdpSocket { .. } =+    not `fmap` DGram.isEmptyBuffer udpBuffer++  sRead UdpSocket { .. } len =+    do (_,buf) <- DGram.readChunk udpBuffer+       return (L.fromStrict (S.take len buf))++  sTryRead UdpSocket { .. } len =+    do mb <- DGram.tryReadChunk udpBuffer+       case mb of+         Just (_,buf) -> return $! Just $! L.fromStrict $! S.take len buf+         Nothing      -> return Nothing++-- | Receive, with information about who sent this datagram.+recvfrom :: Network addr+         => UdpSocket addr -> IO (Device,addr,SockPort,L.ByteString)+recvfrom sock = do+  (dev,srcAddr,srcPort,_,msg) <- recvfrom' sock+  return (dev,srcAddr,srcPort,msg)+{-# INLINE recvfrom #-}++recvfrom' :: Network addr+         => UdpSocket addr -> IO (Device,addr,SockPort,addr,L.ByteString)+recvfrom' UdpSocket { .. } = loop+  where++  -- NOTE: this loop shouldn't run more than one time, as it's very unlikely+  -- that we receive a packet destined for a different protocol address+  loop =+    do ((dev,srcAddr,srcPort,dstAddr,_), chunk) <- DGram.readChunk udpBuffer+       case (fromAddr srcAddr, fromAddr dstAddr) of+         (Just srcAddr', Just dstAddr') ->+           return (dev,srcAddr',srcPort,dstAddr',L.fromStrict chunk)+         _ -> loop+{-# INLINE recvfrom' #-}+++-- | Send to a specific end host.+sendto :: Network addr+       => UdpSocket addr -> addr -> SockPort -> L.ByteString -> IO ()+sendto UdpSocket { .. } = \ dst dstPort bytes ->+  do state <- readIORef udpSockState+     case state of++       KnownSource mbDev src srcPort ->+         do mbRoute <- route' udpNS mbDev src dst+            case mbRoute of++              Just ri ->+                  do _ <- primSendUdp udpNS ri dst srcPort dstPort bytes+                     return ()++              -- no route found, but we're broadcasting using a known device+              Nothing+                | Just dev <- mbDev, isBroadcastAddr dst ->+                  do let ri = RouteInfo { riSource = src+                                        , riNext   = dst+                                        , riDev    = dev }+                     _ <- primSendUdp udpNS ri dst srcPort dstPort bytes+                     return ()++              _ ->+                  throwIO NoRouteToHost++       -- we can't use sendto if sConnect has been used already+       KnownRoute{} ->+         throwIO AlreadyConnected+{-# INLINE sendto #-}++
+ src/Hans/Tcp/Input.hs view
@@ -0,0 +1,550 @@+{-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE RecordWildCards #-}++module Hans.Tcp.Input (+    processTcp+  ) where++import Hans.Addr (Addr,NetworkAddr(..))+import Hans.Checksum (finalizeChecksum,extendChecksum)+import Hans.Config (config)+import Hans.Device.Types (Device(..),ChecksumOffload(..),rxOffload)+import Hans.Lens (view,set)+import Hans.Monad (Hans,escape,decode',dropPacket,io)+import Hans.Nat.Forward (tryForwardTcp)+import Hans.Network+import Hans.Tcp.Message+import Hans.Tcp.Output (routeTcp,queueTcp,queueAck,queueWithTcb,queueTcp)+import Hans.Tcp.Packet+import Hans.Tcp.RecvWindow+           (sequenceNumberValid,recvSegment)+import Hans.Tcp.SendWindow (ackSegment,nullWindow)+import Hans.Tcp.Tcb+import Hans.Types++import           Control.Monad (unless,when)+import qualified Data.ByteString as S+import qualified Data.ByteString.Lazy as L+import           Data.IORef (atomicModifyIORef',atomicWriteIORef,readIORef)+import           Data.Maybe (isJust)+import           Data.Time.Clock (UTCTime,NominalDiffTime,getCurrentTime)+++-- | Process incoming tcp segments.+processTcp :: Network addr+           => NetworkStack -> Device -> addr -> addr -> S.ByteString -> Hans ()+processTcp ns dev src dst bytes =+  do -- make sure that the checksum is valid+     let checksum = finalizeChecksum $ extendChecksum bytes+                                     $ pseudoHeader src dst PROT_TCP+                                     $ S.length bytes+     unless (coTcp (view rxOffload dev) || checksum == 0)+            (dropPacket (devStats dev))++     (hdr,payload) <- decode' (devStats dev) getTcpHeader bytes++     let remote = toAddr src+     let local  = toAddr dst++     tryActive ns dev remote local hdr payload++-- | A single case for an incoming segment.+type InputCase = NetworkStack -> Device -> Addr -> Addr -> TcpHeader+              -> S.ByteString -> Hans ()++-- | Process incoming segments that are destined for an active connection.+tryActive :: InputCase+tryActive ns dev src dst hdr payload =+  do mbActive <- io (lookupActive ns src (tcpSourcePort hdr) dst (tcpDestPort hdr))+     case mbActive of+       Just tcb -> handleActive ns dev hdr payload tcb+       Nothing  -> tryListening ns dev src dst hdr payload+{-# INLINE tryActive #-}+++-- | Process incoming segments that are destined for a listening connection.+tryListening :: InputCase+tryListening ns dev src dst hdr payload =+  do mbListening <- io (lookupListening ns dst (tcpDestPort hdr))+     case mbListening of+       Just tcb -> handleListening ns dev src dst hdr payload tcb+       Nothing  -> tryTimeWait ns dev src dst hdr payload+{-# INLINE tryListening #-}+++-- | Process incoming segments that are destined for a connection in TimeWait.+tryTimeWait :: InputCase+tryTimeWait ns dev remote local hdr payload =+  do mbTimeWait <- io (lookupTimeWait ns remote (tcpSourcePort hdr) local (tcpDestPort hdr))+     case mbTimeWait of+       Just tcb -> handleTimeWait ns hdr payload tcb+       Nothing  -> tryForward ns dev remote local hdr payload+{-# INLINE tryTimeWait #-}+++-- | Process incoming segments to a forwarded port.+tryForward :: InputCase+tryForward ns dev remote local hdr payload =+  do mbHdr <- io (tryForwardTcp ns local remote hdr)+     case mbHdr of++       Just (ri,dst,hdr') -> io $+         do _ <- queueTcp ns ri dst hdr' (L.fromStrict payload)+            return ()++       Nothing            -> handleClosed ns dev remote local hdr payload+{-# INLINE tryForward #-}++++-- Active Connections ----------------------------------------------------------++handleActive :: NetworkStack+             -> Device -> TcpHeader -> S.ByteString -> Tcb -> Hans ()++handleActive ns dev hdr payload tcb =+     -- XXX it would be nice to add header prediction here+  do updateTimestamp tcb hdr payload++     whenState tcb SynSent (handleSynSent ns dev hdr payload tcb)++     -- page 69+     -- check sequence numbers+     mbSegs <- io (atomicModifyIORef' (tcbRecvWindow tcb) (recvSegment hdr payload))+     case mbSegs of++       Nothing ->+         do unless (view tcpRst hdr) $ io $+              do _ <- queueWithTcb ns tcb (set tcpAck True emptyTcpHeader) L.empty+                 return ()++       Just segs ->+         do now <- io getCurrentTime+            handleActiveSegs ns tcb now segs++     escape+++-- | Update the internal timestamp. This follows the algorithm described on+-- pages 15 and 16 of RFC-1323.+updateTimestamp :: Tcb -> TcpHeader -> S.ByteString -> Hans ()+updateTimestamp Tcb { .. } hdr payload =+  do TcbConfig { .. } <- io (readIORef tcbConfig)++     when tcUseTimestamp $+       case findTcpOption OptTagTimestamp hdr of++         -- update TS.recent when Last.ACK.sent falls within the segment, and+         -- there isn't an outstanding delayed ack registered.+         Just (OptTimestamp val _) ->+           do lastAckSent <- io (readIORef tcbLastAckSent)+              delayed     <- io (readIORef tcbNeedsDelayedAck)+              let end = tcpSegNextAckNum hdr (S.length payload)+              when (not delayed && withinWindow (tcpSeqNum hdr) end lastAckSent)+                   (io (atomicWriteIORef tcbTSRecent val))++         -- when the timestamp is missing, but we're using timestamps, drop the+         -- segment, unless it was an RST+         _ | view tcpRst hdr -> return ()+           | otherwise       -> escape+++-- | At this point, the list of segments is contiguous, and starts at the old+-- value of RCV.NXT. RCV.NXT has been advanced to point at the end of the+-- segment list.+handleActiveSegs :: NetworkStack -> Tcb -> UTCTime -> [(TcpHeader,S.ByteString)]+                 -> Hans ()+handleActiveSegs ns tcb now = go+  where+  go []                   = return ()+  go ((hdr,payload):segs) =+    do -- page 70 and page 71+       -- check RST/check SYN+       when (view tcpRst hdr || view tcpSyn hdr) $+         do io $ do when (view tcpSyn hdr) $+                      do let rst = set tcpRst True emptyTcpHeader+                         _ <- queueWithTcb ns tcb rst L.empty+                         return ()++                    -- when the tcb was passively opened, this will free its+                    -- accept queue slot.+                    setState tcb Closed++                    closeActive ns tcb++            escape++       -- page 71+       -- skipping security/precedence++       -- page 72+       -- check ACK+       -- NOTE: processing ends here if ack is not present, which is why the+       -- remainder of packet processing is underneath this `when`.+       when (view tcpAck hdr) $ +         do -- Reset idle timeout+            io $ atomicModifyIORef' (tcbTimers tcb) resetIdleTimer++            -- update the send window+            mbAck <- io $+              do mbAck <- atomicModifyIORef' (tcbSendWindow tcb)+                              (ackSegment (view config ns) now (tcpAckNum hdr))+                 handleRTTMeasurement tcb mbAck++            state <- io (getState tcb)+            case state of++              SynReceived ->+                case mbAck of+                  Just True  -> io (setState tcb Established)+                  Just False -> return ()+                  Nothing    -> do let rst = set tcpRst True emptyTcpHeader+                                   _ <- io (queueWithTcb ns tcb rst L.empty)+                                   return ()++              FinWait1 ->+                case mbAck of+                  Just True ->+                    do io (setState tcb FinWait2)+                       io (processFinWait2 ns tcb)++                  _ -> return ()++              FinWait2 ->+                case mbAck of+                  Just True -> io (processFinWait2 ns tcb)+                  _         -> return ()++              Closing ->+                case mbAck of+                  Just True -> enterTimeWait ns tcb+                  _         -> return ()++              LastAck ->+                case mbAck of+                  Just True ->+                    do io (setState tcb Closed)+                       io (closeActive ns tcb)+                       escape++                  _ -> return ()++              -- TimeWait processing is done in handleTimeWait++              -- CloseWait | Established+              _ -> return ()++            -- page 73+            -- check URG++            -- page 74+            -- process the segment text+            -- XXX: we're ignoring PSH for now, just making the data immediately+            -- available+            unless (S.null payload) $ io $+              do signalDelayedAck tcb+                 queueBytes payload tcb++            -- page 75+            -- check FIN+            when (view tcpFin hdr) $+              do -- send an ACK to the FIN+                 _ <- io (queueAck ns tcb)++                 state' <- io (getState tcb)+                 case state' of++                   SynReceived -> io (setState tcb CloseWait)+                   Established -> io (setState tcb CloseWait)++                   FinWait1 ->+                     case mbAck of+                       Just True -> enterTimeWait ns tcb+                       _         -> io (setState tcb Closing)++                   FinWait2 -> enterTimeWait ns tcb++                   _ -> return ()++       go segs+++-- | Processing for the FinWait2 state, when the retransmit queue is known to be+-- empty. (Page 73 of RFC-793)+processFinWait2 :: NetworkStack -> Tcb -> IO ()+processFinWait2 _ns Tcb { .. } =+  do win <- readIORef tcbSendWindow++     when (nullWindow win) $+       do -- XXX acknowledge the user's close request+          return ()+++-- | Invalidate, and remove the active 'Tcb', replacing it with a 'TimeWaitTcb'+-- that was derived from it.+enterTimeWait :: NetworkStack -> Tcb -> Hans ()+enterTimeWait ns tcb =+  do tw <- io (mkTimeWaitTcb tcb)+     io (closeActive ns tcb)+     io (registerTimeWait ns tw)+     escape+++-- | Apply an RTT measurement to the timers in a tcb, and return the relevant+-- information about the state of the send queue.+handleRTTMeasurement :: Tcb -> Maybe (Bool, Maybe NominalDiffTime)+                     -> IO (Maybe Bool)+handleRTTMeasurement Tcb { .. } mb =+  case mb of+    Just (b, Just rtt) ->+      do atomicModifyIORef' tcbTimers (calibrateRTO rtt)+         return (Just b)++    Just (b,_) ->+         return (Just b)++    Nothing ->+         return Nothing+++-- Half-open Connections -------------------------------------------------------++-- | Handle incoming packets destined for a tcb that's in the SYN-SENT state.+handleSynSent :: NetworkStack -> Device -> TcpHeader -> S.ByteString -> Tcb+              -> Hans ()+handleSynSent ns _dev hdr _payload tcb =+  do -- page 66+     iss <- io (readIORef (tcbIss tcb))+     when (view tcpAck hdr) $+       do sndNxt <- getSndNxt tcb++          when (tcpAckNum hdr <= iss || tcpAckNum hdr > sndNxt) $+            do when (view tcpRst hdr) escape++               rst <- io (mkRst hdr)+               _   <- io (queueTcp ns (tcbRouteInfo tcb) (tcbRemote tcb) rst L.empty)+               escape++     -- page 66/67+     when (view tcpRst hdr) $+       do when (view tcpAck hdr) $ io $+            -- NOTE: the ACK must have been acceptable at this point, as we+            -- would have not made it this far otherwise.+            do setState tcb Closed+               deleteActive ns tcb++          escape++     -- no security/precedence currently++     -- page 67/68+     when (view tcpSyn hdr) $+       do let rcvNxt = tcpSeqNum hdr + 1++          res <- io (setRcvNxt rcvNxt tcb)+          -- should never happen, but if a segment was queued in the receive+          -- window, mucking about with RCV.NXT will cause processing to abort+          --+          -- XXX: maybe there's a better way to encode advancing RCV.NXT over+          -- the SYN?+          unless res escape++          io (atomicWriteIORef (tcbIrs tcb) (tcpSeqNum hdr))++          sndUna <- io $+            if view tcpAck hdr+               then do now <- getCurrentTime+                       mb  <- atomicModifyIORef' (tcbSendWindow tcb)+                                  (ackSegment (view config ns) now (tcpAckNum hdr))+                       _   <- handleRTTMeasurement tcb mb+                       return (tcpAckNum hdr)++               else getSndUna tcb++          when (sndUna > iss) $+            do -- XXX: include any queued data/controls+               _ <- io (queueAck ns tcb)+               io (setState tcb Established)++               -- XXX: not processing additional data/controls from the ack+               escape++          io (setState tcb SynReceived)+          let synAck = set tcpSyn True+                     $ set tcpAck True emptyTcpHeader+          _ <- io (queueWithTcb ns tcb synAck L.empty)++          -- XXX: not queueing any additional data+          escape++     -- page 68+     -- at this point, neither syn or rst were set, so drop the segment+     escape+++-- Listening Connections -------------------------------------------------------++-- | Respond to a segment directed at a socket in the Listen state. This+-- implements the LISTEN case from pages 64-67 of RFC793.+handleListening :: NetworkStack+                -> Device -> Addr -> Addr -> TcpHeader -> S.ByteString+                -> ListenTcb -> Hans ()++handleListening ns dev remote local hdr _payload tcb =+     -- ignore all incoming RST packets (page 64)+  do when (view tcpRst hdr)+          escape++     -- send a RST for incoming ACK packets (page 64)+     when (view tcpAck hdr) $+       do hdr' <- io (mkRst hdr)+          _    <- io (routeTcp ns dev local remote hdr' L.empty)+          escape++     -- we don't enforce security in HaNS (page 65)+     -- XXX should we be managing precedence?+     -- XXX we're not queueing any other control/data that arrived+     when (view tcpSyn hdr) $+       do canAccept <- io (decrSynBacklog ns)+          unless canAccept (rejectSyn ns dev remote local hdr)++          createChildTcb ns dev remote local hdr tcb+          escape++     -- drop anything else that made it this far (page 65)+     escape+++-- | Create a child TCB from a listening one.+createChildTcb :: NetworkStack -> Device -> Addr -> Addr -> TcpHeader -> ListenTcb+               -> Hans ()+createChildTcb ns dev remote local hdr parent =++     -- cache routing information for the child, and fail if there's no valid+     -- route+  do mbRoute <- io (findNextHop ns (Just dev) (Just local) remote)+     ri      <- case mbRoute of+                  Just ri -> return ri+                  Nothing -> rejectSyn ns dev remote local hdr++     -- reserve a spot in the accept queue+     canAccept <- io (reserveSlot parent)+     unless canAccept (rejectSyn ns dev remote local hdr)++     -- construct a new tcb, and initialize it as specified on (page 65)+     (added,child) <- io $+       do iss   <- nextIss ns local (tcpDestPort hdr) remote (tcpSourcePort hdr)+          child <- createChild ns iss parent ri remote hdr+                   (\_ _ -> incrSynBacklog ns)+                   (\_ s -> when (s == SynReceived) (incrSynBacklog ns))+          added <- registerActive ns child+          return (added,child)++     -- if we couldn't add the entry to the active connections, reject it+     unless added $+       do io (releaseSlot parent)+          rejectSyn ns dev remote local hdr++     -- queueing a SYN/ACK in the send window will advance SND.NXT+     -- automatically+     io (processSynOptions child hdr)+     let synAck = set tcpSyn True+                $ set tcpAck True emptyTcpHeader+     _ <- io (queueWithTcb ns child synAck L.empty)++     return ()+++-- | Determine which options should be sent in the SYN,ACK response.+processSynOptions :: Tcb -> TcpHeader -> IO ()+processSynOptions Tcb { .. } hdr =+  do case findTcpOption OptTagTimestamp hdr of++       -- the timestamp option is requested by the remote side+       Just (OptTimestamp val 0) ->+         do atomicModifyIORef' tcbConfig $ \ TcbConfig { .. } ->+                (TcbConfig { tcUseTimestamp = True, .. }, ())+            atomicWriteIORef tcbTSRecent val++       -- no timestamp option, disable timestamps+       _ -> atomicModifyIORef' tcbConfig $ \ TcbConfig { .. } ->+                (TcbConfig { tcUseTimestamp = False, .. }, ())++     -- XXX: in the future, enable SACK here+     -- XXX: in the future, enable Window Scale here++     return ()+++-- | Reject the SYN by sending an RST, drop the segment and return.+rejectSyn :: NetworkStack -> Device -> Addr -> Addr -> TcpHeader -> Hans a+rejectSyn ns dev remote local hdr =+  do hdr' <- io (mkRst hdr)+     _    <- io (routeTcp ns dev local remote hdr' L.empty)+     escape+++-- TimeWait Connections --------------------------------------------------------++handleTimeWait :: NetworkStack -> TcpHeader -> S.ByteString -> TimeWaitTcb -> Hans ()+handleTimeWait ns hdr payload tcb =+     -- page 69+  do (rcvNxt,rcvRight) <- getRecvWindow tcb+     unless (isJust (sequenceNumberValid rcvNxt rcvRight hdr payload)) $+       do unless (view tcpRst hdr) $ io $+            do ack <- mkAck (twSndNxt tcb) rcvNxt (tcpDestPort hdr) (tcpSourcePort hdr)+               _   <- queueTcp ns (twRouteInfo tcb) (twRemote tcb) ack L.empty+               return ()+          escape++     -- page 70+     when (view tcpRst hdr) $+       do io (deleteTimeWait ns tcb)+          escape++     -- page 71+     when (view tcpSyn hdr) $+       do rst <- io (mkRst hdr)+          _   <- io (queueTcp ns (twRouteInfo tcb) (twRemote tcb) rst L.empty)+          io (deleteTimeWait ns tcb)+          escape++     -- page 73+     when (not (view tcpAck hdr)) escape++     -- page 74+     -- ignore the URG bit++     -- page 75+     -- ignore the segment text++     -- page 75/76+     -- process the FIN bit+     when (view tcpFin hdr) $+       do rcvNxt' <- io $+              atomicModifyIORef' (twRcvNxt tcb) $ \ i ->+                  let i' = i + 1 + fromIntegral (S.length payload)+                   in (i', i')++          ack <- io (mkAck (twSndNxt tcb) rcvNxt' (tcpDestPort hdr) (tcpSourcePort hdr))+          _   <- io (queueTcp ns (twRouteInfo tcb) (twRemote tcb) ack L.empty)++          io (resetTimeWait ns tcb)+          escape++     escape+++-- Closed Connections ----------------------------------------------------------++-- | Respond to a segment that was directed at a closed port. This implements+-- the CLOSED case on page 64 of RFC793+handleClosed :: NetworkStack+             -> Device -> Addr -> Addr -> TcpHeader -> S.ByteString -> Hans ()+handleClosed ns dev remote local hdr payload =+  do when (view tcpRst hdr) escape++     rst <- io $ if view tcpAck hdr then mkRst    hdr+                                    else mkRstAck hdr payload+     _   <- io (routeTcp ns dev local remote rst L.empty)+     escape
+ src/Hans/Tcp/Message.hs view
@@ -0,0 +1,77 @@+{-# LANGUAGE RecordWildCards #-}++module Hans.Tcp.Message where++import Hans.Lens+import Hans.Tcp.Packet+import Hans.Tcp.Tcb++import qualified Data.ByteString as S+import           Data.IORef (readIORef)+++-- | @<SEQ=SEG.ACK><CTL=RST>@+mkRst :: TcpHeader -> IO TcpHeader+mkRst TcpHeader { .. } =+     return $ set tcpRst True+            $ emptyTcpHeader { tcpSourcePort = tcpDestPort+                             , tcpDestPort   = tcpSourcePort+                             , tcpSeqNum     = tcpAckNum+                             }+{-# INLINE mkRst #-}+++-- | @<SEQ=0><ACK=SEG.SEQ+SEG.LEN><CTL=RST,ACK>@+mkRstAck :: TcpHeader -> S.ByteString -> IO TcpHeader+mkRstAck hdr @ TcpHeader { .. } payload =+     return $ set tcpRst True+            $ set tcpAck True+            $ emptyTcpHeader { tcpSourcePort = tcpDestPort+                             , tcpDestPort   = tcpSourcePort+                             , tcpSeqNum     = 0+                             , tcpAckNum     = tcpSegNextAckNum hdr (S.length payload)+                             }+{-# INLINE mkRstAck #-}+++mkSyn :: Tcb -> IO TcpHeader+mkSyn Tcb { .. } =+  do iss <- readIORef tcbIss+     return $ set tcpSyn True+            $ emptyTcpHeader { tcpSourcePort = tcbRemotePort+                             , tcpDestPort   = tcbLocalPort+                             , tcpSeqNum     = iss+                             , tcpAckNum     = 0+                             }+{-# INLINE mkSyn #-}+++-- | @<SEQ=ISS><ACK=RCV.NXT><CTL=SYN,ACK>@+mkSynAck :: Tcb -> TcpHeader -> IO TcpHeader+mkSynAck Tcb { .. } TcpHeader { .. } =+  do iss <- readIORef tcbIss+     ack <- getRcvNxt tcbRecvWindow++     return $ set tcpSyn True+            $ set tcpAck True+            $ emptyTcpHeader { tcpSourcePort = tcpDestPort+                             , tcpDestPort   = tcpSourcePort+                             , tcpSeqNum     = iss+                             , tcpAckNum     = ack+                             }+{-# INLINE mkSynAck #-}+++mkAck :: TcpSeqNum    -- ^ SEG.SEQ+      -> TcpSeqNum    -- ^ SEG.ACK+      -> TcpPort      -- ^ Local port+      -> TcpPort      -- ^ Remote port+      -> IO TcpHeader+mkAck segSeq segAck local remote =+     return $ set tcpAck True+            $ emptyTcpHeader { tcpSourcePort = local+                             , tcpDestPort   = remote+                             , tcpSeqNum     = segSeq+                             , tcpAckNum     = segAck+                             }+{-# INLINE mkAck #-}
+ src/Hans/Tcp/Output.hs view
@@ -0,0 +1,265 @@+{-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE RecordWildCards #-}++module Hans.Tcp.Output (+    -- * Output+    routeTcp,+    sendTcp,++    -- ** With a TCB+    sendWithTcb,+    sendAck,+    sendFin,+    sendData,+    canSend,++    -- ** From the fast-path+    queueTcp,+    queueWithTcb,+    queueAck,+    responder,++    -- $notes+  ) where++import           Hans.Addr.Types (Addr)+import           Hans.Config (config)+import           Hans.Checksum (finalizeChecksum,extendChecksum)+import           Hans.Device.Types (Device(..),ChecksumOffload(..),txOffload)+import           Hans.Lens (view,set)+import           Hans.Network+import           Hans.Serialize (runPutPacket)+import           Hans.Tcp.Packet+                     (TcpHeader(..),putTcpHeader,emptyTcpHeader,tcpAck,tcpFin+                     ,tcpPsh,TcpOption(..),setTcpOption)+import qualified Hans.Tcp.RecvWindow as Recv+import qualified Hans.Tcp.SendWindow as Send+import           Hans.Tcp.Tcb+import           Hans.Types++import qualified Control.Concurrent.BoundedChan as BC+import           Control.Monad (when,forever)+import qualified Data.ByteString.Lazy as L+import           Data.IORef (readIORef,atomicModifyIORef',atomicWriteIORef)+import           Data.Int (Int64)+import           Data.Serialize.Put (putWord16be)+import           Data.Time.Clock (getCurrentTime)+import           Data.Word (Word32)+++-- Sending with a TCB ----------------------------------------------------------++-- | Send a single ACK immediately.+sendAck :: NetworkStack -> Tcb -> IO ()+sendAck ns tcb =+  do _ <- sendWithTcb ns tcb (set tcpAck True emptyTcpHeader) L.empty+     return ()++-- | Send a single FIN packet.+sendFin :: NetworkStack -> Tcb -> IO ()+sendFin ns tcb =+  do let hdr = set tcpFin True+             $ set tcpAck True emptyTcpHeader+     _ <- sendWithTcb ns tcb hdr L.empty+     return ()++-- | Send a data segment, potentially sending multiple packets if the send+-- window allows, and the payload is larger than MSS. When the remote window is+-- full, this returns 0.+sendData :: NetworkStack -> Tcb -> L.ByteString -> IO Int64+sendData ns tcb = go 0+  where+  -- technically the MSS could change between sends, so this ensures that we+  -- would pick up that change.+  go acc bytes+    | L.null bytes =+      return acc++    | otherwise =+      do mss <- fromIntegral `fmap` readIORef (tcbMss tcb)+         mb  <- sendWithTcb ns tcb hdr (L.take mss bytes)+         case mb of++           -- when the amount sent was less than mss, we've filled the send+           -- window, and won't be able to send any more.+           Just len | len < mss -> return $! acc + len+                    | otherwise -> let acc' = acc + len+                                    in acc' `seq` go acc' (L.drop len bytes)++           -- the send window is full, return the accumulator+           Nothing -> return acc++  hdr = set tcpAck True+      $ set tcpPsh True+        emptyTcpHeader+++-- | Determine if there is any room in the remote window for us to send+-- data.+canSend :: Tcb -> IO Bool+canSend Tcb { .. } =+  (not . Send.fullWindow) `fmap` readIORef tcbSendWindow++-- | Send a segment and queue it in the remote window. The number of bytes that+-- were sent is returned.+sendWithTcb :: NetworkStack -> Tcb -> TcpHeader -> L.ByteString -> IO (Maybe Int64)+sendWithTcb ns Tcb { .. } hdr body =+  do TcbConfig { .. } <- readIORef tcbConfig++     recvWindow <- readIORef tcbRecvWindow++     mbTSecr <- if tcUseTimestamp+                   then Just `fmap` readIORef tcbTSRecent+                   else return Nothing++     let mkHdr tsVal seqNum =+           addTimestamp tsVal mbTSecr+             hdr { tcpSeqNum     = seqNum+                 , tcpAckNum     = if view tcpAck hdr+                                      then view Recv.rcvNxt recvWindow+                                      else 0+                 , tcpDestPort   = tcbRemotePort+                 , tcpSourcePort = tcbLocalPort+                 , tcpWindow     = view Recv.rcvWnd recvWindow+                 }++     -- only enter the retransmit queue if the segment contains data+     now   <- getCurrentTime+     mbRes <- atomicModifyIORef' tcbSendWindow+                  (Send.queueSegment (view config ns) now mkHdr body)+     case mbRes of++       Just (startRT,hdr',body') ->+         do -- clear the delayed ack flag and update Last.ACK.sent, when an ack+            -- is present+            when (view tcpAck hdr') $+              do atomicWriteIORef tcbNeedsDelayedAck False+                 atomicWriteIORef tcbLastAckSent (tcpAckNum hdr')++            -- reset the retransmit timer, if the retransmit queue is now+            -- non-empty+            when startRT (atomicModifyIORef' tcbTimers resetRetransmit)++            -- send the frame+            _ <- sendTcp ns tcbRouteInfo tcbRemote hdr' body'++            -- return how much of the segment was actually delivered+            return (Just (L.length body'))++       Nothing ->+            return Nothing+++-- | The presence of a tracked TSecr value controls whether or not we send the+-- timestamp option.+addTimestamp :: Word32 -> Maybe Word32 -> TcpHeader -> TcpHeader+addTimestamp tsVal (Just tsEcr) hdr = setTcpOption (OptTimestamp tsVal tsEcr) hdr+addTimestamp _     _            hdr = hdr+++-- Fast-path Sending -----------------------------------------------------------++-- | Responder thread for messages generated in the fast-path.+responder :: NetworkStack -> IO ()+responder ns = forever $+  do msg <- BC.readChan chan+     case msg of+       SendSegment ri dst hdr body ->+         do _ <- sendTcp ns ri dst hdr body+            return ()++       SendWithTcb tcb hdr body ->+         do _ <- sendWithTcb ns tcb hdr body+            return ()++  where+  chan = view tcpQueue ns+++-- | Queue an outgoing TCP segment from the fast-path.+--+-- See note "No Retransmit Queue" ("Hans.Tcp.Output#no-retransmit-queue").+queueTcp :: NetworkStack+         -> RouteInfo Addr -> Addr -> TcpHeader -> L.ByteString -> IO Bool+queueTcp ns ri dst hdr body =+  BC.tryWriteChan (view tcpQueue ns) $! SendSegment ri dst hdr body+++-- | Queue an outgoing TCP segment from the fast-path.+queueWithTcb :: NetworkStack -> Tcb -> TcpHeader -> L.ByteString -> IO Bool+queueWithTcb ns tcb hdr body =+  BC.tryWriteChan (view tcpQueue ns) $! SendWithTcb tcb hdr body+++-- | Queue an ACK from the fast-path.+queueAck :: NetworkStack -> Tcb -> IO Bool+queueAck ns tcb = queueWithTcb ns tcb (set tcpAck True emptyTcpHeader) L.empty+++-- Primitive Send --------------------------------------------------------------++-- | Send outgoing tcp segments, with a route calculation.+--+-- See note "No Retransmit Queue" ("Hans.Tcp.Output#no-retransmit-queue").+routeTcp :: Network addr+         => NetworkStack -> Device+         -> addr -> addr -> TcpHeader -> L.ByteString -> IO Bool+routeTcp ns dev src dst hdr payload+  | L.length payload > fromIntegral (maxBound :: Word32) =+    return False++  | otherwise =+    do mbRoute <- findNextHop ns (Just dev) (Just src) dst+       case mbRoute of++         Just ri ->+           do let bytes = renderTcpPacket (view txOffload dev) src dst hdr payload+              sendDatagram ns ri dst False PROT_TCP bytes+              return True++         Nothing ->+              return False+++-- | Lowest-level output function for TCP.+--+-- See note "No Retransmit Queue" ("Hans.Tcp.Output#no-retransmit-queue").+sendTcp :: Network addr+        => NetworkStack+        -> RouteInfo addr -> addr -> TcpHeader -> L.ByteString -> IO Bool+sendTcp ns ri dst hdr payload+  | L.length payload >= fromIntegral (maxBound :: Word32) =+    return False++  | otherwise =+    do let bytes = renderTcpPacket (view txOffload ri) (riSource ri) dst hdr payload+       sendDatagram ns ri dst False PROT_TCP bytes++       return True+++-- | Render out a tcp packet, calculating the checksum when the device requires+-- it.+renderTcpPacket :: Network addr+                => ChecksumOffload -> addr -> addr -> TcpHeader -> L.ByteString+                -> L.ByteString+renderTcpPacket ChecksumOffload { .. } src dst hdr body+  | coTcp     = bytes+  | otherwise = beforeCS `L.append` csBytes+  where+  bytes  = runPutPacket 20 40 body (putTcpHeader hdr)++  cs     = finalizeChecksum+         $ extendChecksum bytes+         $ pseudoHeader src dst PROT_TCP (fromIntegral (L.length bytes))++  beforeCS = L.take 16 bytes+  csBytes  = runPutPacket 2 0 (L.drop 18 bytes) (putWord16be cs)+++-- $notes+-- #no-retransmit-queue#+--+-- = No Retransmit Queue+-- This function will not record entries in the retransmit queue, and is+-- responsible only for output to a lower layer.
+ src/Hans/Tcp/Packet.hs view
@@ -0,0 +1,501 @@+{-# LANGUAGE Rank2Types #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++module Hans.Tcp.Packet (++    -- * Header+    TcpHeader(..),+    TcpPort, putTcpPort,+    emptyTcpHeader,+    tcpHeaderSize,++    -- ** Sequence Numbers+    TcpSeqNum, TcpAckNum,+    withinWindow,+    fromTcpSeqNum,++    -- ** Header Flags+    tcpNs, tcpCwr, tcpEce, tcpUrg, tcpAck, tcpPsh, tcpRst, tcpSyn,+    tcpFin,++    -- ** Serialization+    getTcpHeader, putTcpHeader,++    -- ** Options+    HasTcpOptions(..),+    findTcpOption,+    setTcpOption,+    setTcpOptions,+    TcpOption(..),+    TcpOptionTag(..), tcpOptionTag,+    SackBlock(..),+    tcpOptionsSize,+    tcpOptionSize,++    -- * Segment Operations+    tcpSegLen,+    tcpSegLastSeqNum,+    tcpSegNextAckNum,+  ) where++import Hans.Lens++import           Control.Monad (replicateM,replicateM_,unless)+import           Data.Bits ((.|.),(.&.),shiftL,shiftR)+import qualified Data.ByteString as S+import qualified Data.Foldable as F+import           Data.Int (Int32)+import           Data.List (find)+import           Data.Serialize.Get+                     (Get,getWord8,getWord16be,getWord32be,label,isolate+                     ,getBytes,remaining,skip)+import           Data.Serialize.Put+                     (Putter,Put,putWord8,putWord16be,putWord32be,putByteString)+import           Data.Word (Word8,Word16,Word32)+++-- Tcp Support Types -----------------------------------------------------------++type TcpPort = Word16++putTcpPort :: Putter TcpPort+putTcpPort  = putWord16be++getTcpPort :: Get TcpPort+getTcpPort  = getWord16be+++newtype TcpSeqNum = TcpSeqNum Word32+                    deriving (Num,Eq,Show)++fromTcpSeqNum :: Num a => TcpSeqNum -> a+fromTcpSeqNum (TcpSeqNum a) = fromIntegral a++instance Ord TcpSeqNum where+  compare (TcpSeqNum a) (TcpSeqNum b) =+    compare (fromIntegral (a - b) :: Int32) 0++  TcpSeqNum a <  TcpSeqNum b = (fromIntegral (a - b) :: Int32) <  0+  TcpSeqNum a <= TcpSeqNum b = (fromIntegral (a - b) :: Int32) <= 0+  TcpSeqNum a >  TcpSeqNum b = (fromIntegral (a - b) :: Int32) >  0+  TcpSeqNum a >= TcpSeqNum b = (fromIntegral (a - b) :: Int32) >= 0++  {-# INLINE compare #-}+  {-# INLINE (<)     #-}+  {-# INLINE (<=)    #-}+  {-# INLINE (>)     #-}+  {-# INLINE (>=)    #-}++withinWindow :: TcpSeqNum -> TcpSeqNum -> TcpSeqNum -> Bool+withinWindow l r = \x -> l <= x && x < r+{-# INLINE withinWindow #-}++putTcpSeqNum :: Putter TcpSeqNum+putTcpSeqNum (TcpSeqNum w) = putWord32be w++getTcpSeqNum :: Get TcpSeqNum+getTcpSeqNum  =+  do w <- getWord32be+     return (TcpSeqNum w)+++-- | An alias to TcpSeqNum, as these two are used in the same role.+type TcpAckNum = TcpSeqNum++putTcpAckNum :: Putter TcpAckNum+putTcpAckNum  = putTcpSeqNum++getTcpAckNum :: Get TcpAckNum+getTcpAckNum  = getTcpSeqNum+++-- 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 |     |N|C|E|U|A|P|R|S|F|                               |+--   | Offset| Res.|S|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+                           , tcpFlags_        :: !Word16+                           , tcpWindow        :: !Word16+                           , tcpChecksum      :: !Word16+                           , tcpUrgentPointer :: !Word16+                           , tcpOptions_      :: [TcpOption]+                           } deriving (Eq,Show)++emptyTcpHeader :: TcpHeader+emptyTcpHeader  = TcpHeader { tcpSourcePort    = 0+                            , tcpDestPort      = 0+                            , tcpSeqNum        = 0+                            , tcpAckNum        = 0+                            , tcpFlags_        = 0+                            , tcpWindow        = 0+                            , tcpChecksum      = 0+                            , tcpUrgentPointer = 0+                            , tcpOptions_      = []+                            }++tcpFlags :: Lens' TcpHeader Word16+tcpFlags f hdr =+  fmap (\flags' -> hdr { tcpFlags_ = flags' }) (f (tcpFlags_ hdr))+{-# INLINE tcpFlags #-}++tcpNs, tcpCwr, tcpEce, tcpUrg, tcpAck, tcpPsh, tcpRst, tcpSyn,+  tcpFin :: Lens' TcpHeader Bool+tcpNs  = tcpFlags . bit 8+tcpCwr = tcpFlags . bit 7+tcpEce = tcpFlags . bit 6+tcpUrg = tcpFlags . bit 5+tcpAck = tcpFlags . bit 4+tcpPsh = tcpFlags . bit 3+tcpRst = tcpFlags . bit 2+tcpSyn = tcpFlags . bit 1+tcpFin = tcpFlags . bit 0+{-# INLINE tcpNs  #-}+{-# INLINE tcpCwr #-}+{-# INLINE tcpEce #-}+{-# INLINE tcpUrg #-}+{-# INLINE tcpAck #-}+{-# INLINE tcpPsh #-}+{-# INLINE tcpRst #-}+{-# INLINE tcpSyn #-}+{-# INLINE tcpFin #-}+++-- | The length of the fixed part of the TcpHeader, in 4-byte octets.+tcpFixedHeaderSize :: Int+tcpFixedHeaderSize  = 5++-- | The encoded size of a header.+tcpHeaderSize :: TcpHeader -> Int+tcpHeaderSize TcpHeader { .. } =+  let (size,padding) = tcpOptionsSize tcpOptions_+   in size + padding + tcpFixedHeaderSize++-- | Render a TcpHeader.  The checksum value is never rendered, as it is+-- expected to be calculated and poked in afterwords.+putTcpHeader :: Putter TcpHeader+putTcpHeader TcpHeader { .. } =+  do putTcpPort tcpSourcePort+     putTcpPort tcpDestPort+     putTcpSeqNum tcpSeqNum+     putTcpAckNum tcpAckNum+     let (optLen,padding) = tcpOptionsSize tcpOptions_+     putTcpControl (tcpFixedHeaderSize + optLen) tcpFlags_+     putWord16be tcpWindow+     putWord16be 0+     putWord16be tcpUrgentPointer+     mapM_ putTcpOption tcpOptions_+     replicateM_ padding (putTcpOptionTag OptTagEndOfOptions)++-- | Parse a TcpHeader.+getTcpHeader :: Get TcpHeader+getTcpHeader  = label "TcpHeader" $+  do tcpSourcePort <- getTcpPort+     tcpDestPort   <- getTcpPort+     tcpSeqNum     <- getTcpSeqNum+     tcpAckNum     <- getTcpAckNum++     -- data offset and flags+     tcpFlags_ <- getWord16be+     let dataOff = fromIntegral ((tcpFlags_ `shiftR` 12) .&. 0xf)++     tcpWindow        <- getWord16be+     tcpChecksum      <- getWord16be+     tcpUrgentPointer <- getWord16be++     -- options, not including the end-of-list option+     let optsLen = dataOff - tcpFixedHeaderSize+     opts <- label "options" (isolate (optsLen `shiftL` 2) getTcpOptions)+     let tcpOptions_ = filter (/= OptEndOfOptions) opts++     return $! TcpHeader { .. }++-- | Render out the @Word8@ that contains the Control field of the TcpHeader.+putTcpControl :: Int -> Word16 -> Put+putTcpControl hdrLenWords flags =+  putWord16be (fromIntegral (hdrLenWords) `shiftL` 12 .|. (flags .&. 0x1ff))+++-- Tcp Options -----------------------------------------------------------------++class HasTcpOptions a where+  tcpOptions :: Lens' a [TcpOption]++instance HasTcpOptions TcpHeader where+  tcpOptions f TcpHeader { .. } =+    fmap (\opts -> TcpHeader { tcpOptions_ = opts, .. }) (f tcpOptions_)++instance HasTcpOptions [TcpOption] where+  tcpOptions = id++findTcpOption :: HasTcpOptions opts => TcpOptionTag -> opts -> Maybe TcpOption+findTcpOption tag opts = find p (view tcpOptions opts)+  where+  p opt = tag == tcpOptionTag opt++setTcpOption :: HasTcpOptions opts => TcpOption -> opts -> opts+setTcpOption opt = over tcpOptions loop+  where+  tag = tcpOptionTag opt++  loop [] = [opt]+  loop (o:opts)+    | tcpOptionTag o == tag = opt : opts+    | otherwise             = o : loop opts++setTcpOptions :: HasTcpOptions opts => [TcpOption] -> opts -> opts+setTcpOptions opts a = foldr setTcpOption a opts++data TcpOptionTag = OptTagEndOfOptions+                  | OptTagNoOption+                  | OptTagMaxSegmentSize+                  | OptTagWindowScaling+                  | OptTagSackPermitted+                  | OptTagSack+                  | 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+       4 -> OptTagSackPermitted+       5 -> OptTagSack+       8 -> OptTagTimestamp+       _ -> OptTagUnknown ty++putTcpOptionTag :: Putter TcpOptionTag+putTcpOptionTag tag =+  putWord8 $! case tag of+    OptTagEndOfOptions   -> 0+    OptTagNoOption       -> 1+    OptTagMaxSegmentSize -> 2+    OptTagWindowScaling  -> 3+    OptTagSackPermitted  -> 4+    OptTagSack           -> 5+    OptTagTimestamp      -> 8+    OptTagUnknown ty     -> ty++++data TcpOption = OptEndOfOptions+               | OptNoOption+               | OptMaxSegmentSize !Word16+               | OptWindowScaling !Word8+               | OptSackPermitted+               | OptSack [SackBlock]+               | OptTimestamp !Word32 !Word32+               | OptUnknown !Word8 !Word8 !S.ByteString+                 deriving (Show,Eq)++data SackBlock = SackBlock { sbLeft  :: !TcpSeqNum+                           , sbRight :: !TcpSeqNum+                           } deriving (Show,Eq)++tcpOptionTag :: TcpOption -> TcpOptionTag+tcpOptionTag OptEndOfOptions{}   = OptTagEndOfOptions+tcpOptionTag OptNoOption{}       = OptTagNoOption+tcpOptionTag OptMaxSegmentSize{} = OptTagMaxSegmentSize+tcpOptionTag OptSackPermitted{}  = OptTagSackPermitted+tcpOptionTag OptSack{}           = OptTagSack+tcpOptionTag OptWindowScaling{}  = OptTagWindowScaling+tcpOptionTag OptTimestamp{}      = OptTagTimestamp+tcpOptionTag (OptUnknown ty _ _) = OptTagUnknown ty++-- | Get the rendered length of a list of TcpOptions, in 4-byte words, and the+-- number of padding bytes required.  This rounds up to the nearest 4-byte word.+tcpOptionsSize :: [TcpOption] -> (Int,Int)+tcpOptionsSize opts+  | left == 0 = (len,0)+  | otherwise = (len + 1,4 - left)+  where+  (len,left) = F.foldl' (\acc o -> acc + tcpOptionSize o) 0 opts `quotRem` 4+++tcpOptionSize :: TcpOption -> Int+tcpOptionSize OptEndOfOptions{}    = 1+tcpOptionSize OptNoOption{}        = 1+tcpOptionSize OptMaxSegmentSize{}  = 4+tcpOptionSize OptWindowScaling{}   = 3+tcpOptionSize OptSackPermitted{}   = 2+tcpOptionSize (OptSack bs)         = sackLength bs+tcpOptionSize OptTimestamp{}       = 10+tcpOptionSize (OptUnknown _ len _) = fromIntegral len+++putTcpOption :: Putter TcpOption+putTcpOption opt =+  do putTcpOptionTag (tcpOptionTag opt)+     case opt of+       OptEndOfOptions       -> return ()+       OptNoOption           -> return ()+       OptMaxSegmentSize mss -> putMaxSegmentSize mss+       OptWindowScaling w    -> putWindowScaling w+       OptSackPermitted      -> putSackPermitted+       OptSack bs            -> putSack bs+       OptTimestamp v r      -> putTimestamp v r+       OptUnknown _ len bs   -> putUnknown len bs++-- | Parse in known tcp options.+getTcpOptions :: Get [TcpOption]+getTcpOptions  = label "Tcp Options" loop+  where+  loop =+    do left <- remaining+       if left > 0+          then body+          else return []++  body =+    do opt <- getTcpOption+       case opt of++         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+       OptTagSackPermitted  -> getSackPermitted+       OptTagSack           -> getSack+       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++getSackPermitted :: Get TcpOption+getSackPermitted  = label "Sack Permitted" $ isolate 1 $+  do len <- getWord8+     unless (len == 2) (fail ("Unexpected length: " ++ show len))+     return OptSackPermitted++putSackPermitted :: Put+putSackPermitted  = putWord8 2++getSack :: Get TcpOption+getSack  = label "Sack" $+  do len <- getWord8+     let edgeLen = fromIntegral len - 2+     bs <- isolate edgeLen (replicateM (edgeLen `shiftR` 3) getSackBlock)+     return $! OptSack bs++putSack :: Putter [SackBlock]+putSack bs =+  do putWord8 (fromIntegral (sackLength bs))+     mapM_ putSackBlock bs++getSackBlock :: Get SackBlock+getSackBlock  =+  do l <- getTcpSeqNum+     r <- getTcpSeqNum+     return $! SackBlock { sbLeft  = l+                         , sbRight = r+                         }++putSackBlock :: Putter SackBlock+putSackBlock sb =+  do putTcpSeqNum (sbLeft sb)+     putTcpSeqNum (sbRight sb)++sackLength :: [SackBlock] -> Int+sackLength bs = length bs * 8 + 2++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))+     a <- getWord32be+     b <- getWord32be+     return $! OptTimestamp a b++putTimestamp :: Word32 -> Word32 -> Put+putTimestamp v r =+  do 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 -> S.ByteString -> Put+putUnknown len body =+  do putWord8 len+     putByteString body+++-- Tcp Packet ------------------------------------------------------------------++-- | The length of the data segment, including Syn and Fin.+tcpSegLen :: TcpHeader -> Int -> Int+tcpSegLen hdr len = len + flagVal tcpSyn + flagVal tcpFin+  where+  flagVal l | view l hdr = 1+            | otherwise  = 0++-- | The last sequence number used in a segment.+tcpSegLastSeqNum :: TcpHeader -> Int -> TcpSeqNum+tcpSegLastSeqNum hdr len = tcpSeqNum hdr + fromIntegral (tcpSegLen hdr len) - 1++-- | The ack number for this segment.+tcpSegNextAckNum :: TcpHeader -> Int -> TcpAckNum+tcpSegNextAckNum hdr len = tcpSeqNum hdr + fromIntegral (tcpSegLen hdr len)
+ src/Hans/Tcp/RecvWindow.hs view
@@ -0,0 +1,297 @@+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE PatternGuards #-}+{-# LANGUAGE MultiWayIf #-}++module Hans.Tcp.RecvWindow (+    -- * Receive Window+    Window(),+    emptyWindow,+    recvSegment,+    rcvWnd,+    rcvNxt, setRcvNxt,+    rcvRight,+    moveRcvRight,+++    -- ** Sequence Numbers+    sequenceNumberValid,+  ) where++import           Hans.Lens+import           Hans.Tcp.Packet++import qualified Data.ByteString as S+import           Data.Word (Word16)+++-- Segments --------------------------------------------------------------------++data Segment = Segment { segStart :: !TcpSeqNum+                         -- ^ The first byte occupied by this segment++                       , segEnd :: !TcpSeqNum+                         -- ^ The last byte occupied by this segment++                       , segHdr  :: !TcpHeader+                       , segBody :: !S.ByteString+                       } deriving (Show)++mkSegment :: TcpHeader -> S.ByteString -> Segment+mkSegment segHdr segBody =+  Segment { segStart = tcpSeqNum segHdr+          , segEnd   = tcpSegLastSeqNum segHdr (S.length segBody)+          , .. }++-- | The next segment number, directly after this one.+segNext :: Segment -> TcpSeqNum+segNext Segment { .. } = segEnd + 1++-- | Drop data off of the front of this segment.+trimSeg :: Int -> Segment -> Maybe Segment+trimSeg len seg@Segment { .. }+  | len' <= 0 =+    Just seg++  | len' >= S.length segBody =+    Nothing++  | otherwise =+    Just $! Segment { segStart = segStart + fromIntegral len'+                    , segHdr   = segHdr { tcpSeqNum = tcpSeqNum segHdr+                                                    + fromIntegral len }+                    , segBody  = S.drop len segBody+                    , .. }++  where++  flag l | view l segHdr = 1+         | otherwise     = 0++  -- adjust the length to account for syn/fin+  len' = len - flag tcpSyn - flag tcpFin++-- | Resolve overlap between two segments. It's assumed that the two segments do+-- actually overlap. Overlap is resolved by trimming the front of the later+-- packet.+resolveOverlap :: Segment -> Segment -> [Segment]+resolveOverlap a b =+  case trimSeg (fromTcpSeqNum (segEnd x - segStart y)) y of+    Just y' -> [x,y']+    Nothing -> error "resolveOverlap: invariant violated"+  where+  (x,y) | segStart a < segStart b = (a,b) -- a overlaps b+        | otherwise               = (b,a) -- b overlaps a+++-- Receive Window --------------------------------------------------------------++-- | The receive window.+--+-- INVARIANTS:+--+--  1. All segments in rwSegments are within the window defined by rwRcvNxt and+--     rwRcvWnd+--  2. The segments in rwSegments should not overlap+--+data Window = Window { wSegments :: ![Segment]+                       -- ^ Out of order segments.++                     , wRcvNxt :: !TcpSeqNum+                       -- ^ Left-edge of the receive window++                     , wRcvRight :: !TcpSeqNum+                       -- ^ Right-edge of the receive window++                     , wMax :: !TcpSeqNum+                       -- ^ Maximum size of the receive window+                     } deriving (Show)++emptyWindow :: TcpSeqNum -> Int -> Window+emptyWindow wRcvNxt maxWin =+  Window { wSegments = []+         , wRcvRight = wRcvNxt + wMax+         , .. }+  where+  wMax = fromIntegral maxWin++-- | The value of RCV.WND.+rcvWnd :: Lens' Window Word16+rcvWnd f Window { .. } =+  fmap (\ wnd -> Window { wRcvRight = wRcvNxt + fromIntegral wnd, .. })+       (f (fromTcpSeqNum (wRcvRight - wRcvNxt)))++-- | The left edge of the receive window.+rcvNxt :: Getting r Window TcpSeqNum+rcvNxt  = to wRcvNxt++-- | The right edge of the receive window, RCV.NXT + RCV.WND.+rcvRight :: Getting r Window TcpSeqNum+rcvRight  = to wRcvRight++-- | Only sets RCV.NXT when the segment queue is empty. Returns 'True' when the+-- value has been successfully changed.+setRcvNxt :: TcpSeqNum -> Window -> (Window,Bool)+setRcvNxt nxt win+  | null (wSegments win) = (win { wRcvNxt = nxt, wRcvRight = nxt + wMax win }, True)+  | otherwise            = (win, False)+++-- | Check an incoming segment, and queue it in the receive window. When the+-- segment received was valid 'Just' is returned, including segments in the+-- receive window were unblocked by it.+--+-- NOTE: when Just[] is returned, this should be a signal to issue a duplicate+-- ACK, as we've receive something out of sequence (fast retransmit).+recvSegment :: TcpHeader -> S.ByteString -> Window+            -> (Window, Maybe [(TcpHeader,S.ByteString)])+recvSegment hdr body win++    -- add the trimmed frame to the receive queue+  | Just seg <- sequenceNumberValid (wRcvNxt win) (wRcvRight win) hdr body =+    let (win', segs) = addSegment seg win+     in (win', Just [ (segHdr,segBody) | Segment { .. } <- segs ])++    -- drop the invalid frame+  | otherwise =+    (win, Nothing)+++-- | Increase the right edge of the window by n, clamping at the maximum window+-- size.+moveRcvRight :: Int -> Window -> (Window, ())+moveRcvRight n = \ win ->+  let rcvRight' = view rcvRight win + min (max 0 (fromIntegral n)) (wMax win)+   in (win { wRcvRight = rcvRight' }, ())+{-# INLINE moveRcvRight #-}+++-- | Add a validated segment to the receive window, and return +addSegment :: Segment -> Window -> (Window, [Segment])+addSegment seg win++    -- The new segment falls right at the beginning of the receive window. Move+    -- RCV.NXT and put the segment into the receive buffer.  Don't modify+    -- RCV.WND until the segment has been removed from the receive buffer.+  | segStart seg == wRcvNxt win =+    advanceLeft seg win++    -- As addSegment should only be called with the results of+    -- sequenceNumberValid, the only remaining case to consider is that the+    -- segment falls somewhere else within the window.+  | otherwise =+    (insertOutOfOrder seg win, [])+++-- | Use this segment to advance the window, which may unblock zero or more out+-- of order segments. The list returned is always non-empty, as it includes the+-- segment that's given.+advanceLeft :: Segment -> Window -> (Window, [Segment])+advanceLeft seg win++    -- there were no other segments that might be unblocked by this one+  | null (wSegments win) =+    ( win { wRcvNxt = segNext seg }, [seg])++    -- see if this segment unblocks any others+  | otherwise =+    let win'             = insertOutOfOrder seg win -- to resolve overlap+        (nxt,valid,rest) = splitContiguous (wSegments win')+     in (win' { wSegments = rest, wRcvNxt = nxt }, valid)+++-- | Insert a new segment into the receive window. NOTE: we don't need to worry+-- about trimming the segment to fit the window, as that's already been done by+-- sequenceNumberValid.+insertOutOfOrder :: Segment -> Window -> Window+insertOutOfOrder seg Window { .. } = Window { wSegments = segs', .. }+  where+  segs' = loop seg wSegments++  loop new segs@(x:xs)++      -- new segment ends before x starts+    | segEnd new < segStart x = new : segs++      -- new segment starts after x+    | segStart new > segEnd x =+      x : loop new segs++      -- segments overlap+    | otherwise = resolveOverlap new x ++ xs++  loop new [] = [new]+++-- | Split out contiguous segments, and out of order segments. NOTE: this+-- assumes that the segment list given does not contain any overlapping+-- segments, and is ordered.+--+-- NOTE: this should never be called with an empty list.+splitContiguous :: [Segment] -> (TcpSeqNum,[Segment],[Segment])++splitContiguous (seg:segs) = loop [seg] (segNext seg) segs+  where+  loop acc from (x:xs) | segStart x == from = loop (x:acc) (segNext seg) xs+  loop acc from    xs                       = (from, reverse acc, xs)++splitContiguous [] = error "splitContiguous: empty list"+++-- Window Checks ---------------------------------------------------------------++-- | This is the check described on page 68 of RFC793, which checks that data+-- falls within the expected receive window. When the check is successful, the+-- segment returned is one that has been trimmed to fit in the window (if+-- necessary).+--+-- When this produces a segment, the segment has these properties:+--+--  1. The sequence number is within the window+--  2. The segment body falls within the window+--  3. The segment has been copied from the original bytestring+--+-- The reason for point 3 is that when frames are allocated by devices, they are+-- likely allocated to the full MTU, and not resized. Copying here frees up some+-- memory.+sequenceNumberValid :: TcpSeqNum  -- ^ RCV.NXT+                    -> TcpSeqNum  -- ^ RCV.NXT + RCV.WND+                    -> TcpHeader+                    -> S.ByteString+                    -> Maybe Segment++sequenceNumberValid nxt wnd hdr@TcpHeader { .. } payload++  | payloadLen == 0 =+    if nullWindow+       -- test 1+       then if tcpSeqNum == nxt then Just (mkSegment hdr S.empty) else Nothing++       -- test 2+       else if seqNumInWindow   then Just (mkSegment hdr S.empty) else Nothing++  | otherwise =+    if nullWindow+       -- test 3+       then Nothing++       -- test 4+       else if | seqNumInWindow  -> Just (mkSegment hdr  seg')+               | dataEndInWindow -> Just (mkSegment hdr' seg')+               | otherwise       -> Nothing++  where++  nullWindow = nxt == wnd+  payloadLen = tcpSegLen hdr (fromIntegral (S.length payload))+  segEnd     = tcpSeqNum + fromIntegral (payloadLen - 1)++  -- adjusted header for when the payload spans RCV.NXT+  hdr' = hdr { tcpSeqNum = nxt }++  -- XXX: this doesn't account for syn/fin at the moment+  -- trim the payload to fit in the window+  seg' = S.copy $ S.drop (fromTcpSeqNum (nxt    - tcpSeqNum))+                $ S.take (fromTcpSeqNum (segEnd - wnd)) payload++  seqNumInWindow  = nxt <= tcpSeqNum && tcpSeqNum < wnd+  dataEndInWindow = nxt <= segEnd    && segEnd    < wnd
+ src/Hans/Tcp/SendWindow.hs view
@@ -0,0 +1,353 @@+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE MultiWayIf #-}++module Hans.Tcp.SendWindow (+    -- * Remote Window+    Window(),+    emptyWindow,+    sndNxt, setSndNxt,+    sndUna,+    sndWnd,+    nullWindow,+    fullWindow,+    flushWindow,++    -- ** Timestamp Clock+    TSClock(),+    initialTSClock,+    updateTSClock,+    tsVal,++    -- ** Packet Processing+    queueSegment,+    retransmitTimeout,+    ackSegment,++    -- ** Selective Ack+    handleSack,+  ) where++import Hans.Config+import Hans.Lens+import Hans.Tcp.Packet++import           Control.Monad (guard)+import qualified Data.ByteString.Lazy as L+import           Data.List (sortBy)+import           Data.Maybe (isJust)+import           Data.Ord (comparing)+import           Data.Time.Clock (UTCTime,NominalDiffTime,diffUTCTime)+import           Data.Word (Word32)+++-- Segments --------------------------------------------------------------------++data Segment = Segment { segHeader    :: !TcpHeader+                       , segRightEdge :: !TcpSeqNum -- ^ Cached right edge+                       , segBody      :: !L.ByteString+                       , segSentAt    :: !(Maybe UTCTime)+                       , segSACK      :: !Bool+                       }++segHeaderL :: Lens' Segment TcpHeader+segHeaderL f Segment { .. } =+  fmap (\h -> Segment { segHeader = h, .. }) (f segHeader)++instance HasTcpOptions Segment where+  tcpOptions = segHeaderL . tcpOptions++mkSegment :: TcpHeader -> L.ByteString -> UTCTime -> Segment+mkSegment segHeader segBody now  =+  Segment { segRightEdge = tcpSegNextAckNum segHeader (fromIntegral (L.length segBody))+          , segSACK      = False+          , segSentAt    = Just now+          , .. }++-- | The sequence number of the frame.+--+-- INVARIANT: the sequence number must always be greater than or equal to the+-- current sequence number, and less than the value of the right edge of the+-- segment.+leftEdge :: Lens' Segment TcpSeqNum+leftEdge f seg@Segment { segHeader = hdr@TcpHeader { .. }, .. } =+  fmap update (f tcpSeqNum)+  where++  update sn+      -- only update if the sequence number can actually trim the packet+    | sn <= tcpSeqNum = seg+    | otherwise       =+      -- account syn counting for one+      let len = fromTcpSeqNum (sn - tcpSeqNum)+          (hdr',len') | view tcpSyn hdr = (set tcpSyn False hdr,len - 1)+                      | otherwise       = (hdr,len)++      -- NOTE: the header gets the unmodified sequence number, but the data gets+      -- the adjusted version, as the adjusted +      in Segment { segHeader = hdr' { tcpSeqNum = sn } -- NOTE: use old sn here+                 , segBody   = L.drop len' segBody+                 , .. }+{-# INLINE leftEdge #-}+++-- | The sequence number of the first byte AFTER this segment.+--+-- INVARIANT: the sequence number must always be less than or equal to the+-- current right edge sequence number, and greater than or equal to the left+-- edge.+rightEdge :: Getting r Segment TcpSeqNum+rightEdge  = to segRightEdge+{-# INLINE rightEdge #-}+++-- | The SACK flag for this segment. This flag is turned TRUE when the segment+-- has been wholly acknowledged through the SACK option on an incoming ACK.+sack :: Lens' Segment Bool+sack f seg@Segment { .. } =+  fmap update (f segSACK)+  where+  update b | b == segSACK = seg+           | otherwise    = Segment { segSACK = b, .. }+++-- Timestamp Clock -------------------------------------------------------------++data TSClock = TSClock { tscVal :: !Word32, tscLastUpdate :: !UTCTime }++-- | Create a 'TSClock'.+initialTSClock :: Word32 -> UTCTime -> TSClock+initialTSClock tscVal tscLastUpdate = TSClock { .. }++-- | Update the timestamp clock, and return the new value of TSval.+updateTSClock :: Config -> UTCTime -> TSClock -> TSClock+updateTSClock Config { .. } now TSClock { .. } =+  let diff = truncate (diffUTCTime now tscLastUpdate * cfgTcpTSClockFrequency)+   in TSClock { tscVal = tscVal + diff, tscLastUpdate = now }++-- | The current value of the TS clock.+tsVal :: Getting r TSClock Word32+tsVal  = to tscVal++-- | Given an echo'd timestamp, generate an RTT measurement.+measureRTT :: Config -> Word32 -> TSClock -> NominalDiffTime+measureRTT Config { .. } ecr clk =+  fromIntegral (view tsVal clk - ecr) / cfgTcpTSClockFrequency+++-- Send Window -----------------------------------------------------------------++type Segments = [Segment]++-- | This structure holds bookkeeping variables for the remote end's receive+-- window, as well as the retransmit queue.+data Window = Window { wRetransmitQueue :: !Segments+                       -- ^ The retransmit queue contains segments that fall+                       -- between SND.UNA and SND.NXT++                     , wSndAvail        :: !Int+                       -- ^ The effective window++                     , wSndNxt          :: !TcpSeqNum+                     , wSndWnd          :: !TcpSeqNum++                     , wTSClock :: !TSClock+                     }+++emptyWindow :: TcpSeqNum -- ^ SND.NXT+            -> TcpSeqNum -- ^ SND.WND+            -> TSClock+            -> Window+emptyWindow wSndNxt wSndWnd wTSClock =+  Window { wRetransmitQueue = []+         , wSndAvail        = fromTcpSeqNum wSndWnd+         , .. }+++-- | Remove everything from the remote window.+flushWindow :: Window -> (Window, ())+flushWindow Window { .. } = (Window { wRetransmitQueue = [], .. }, ())+++-- | True when the window is empty.+nullWindow :: Window -> Bool+nullWindow Window { .. } = null wRetransmitQueue++-- | True when the window is full.+fullWindow :: Window -> Bool+fullWindow Window { .. } = wSndAvail == 0++-- | The value of SND.NXT.+--+-- NOTE: SND.UNA <= SND.NXT < SND.UNA + SND.WND+sndNxt :: Getting r Window TcpSeqNum+sndNxt  = to wSndNxt+++-- | Only sets the value of SND.NXT when the retransmit queue is empty.+setSndNxt :: TcpSeqNum -> Window -> (Window, Bool)+setSndNxt nxt win+  | null (wRetransmitQueue win) = (win { wSndNxt = nxt }, True)+  | otherwise                   = (win, False)+++-- | The value of SND.WND.+sndWnd :: Lens' Window TcpSeqNum+sndWnd f Window { .. } =+  fmap (\ wnd -> Window { wSndWnd   = wnd+                        , wSndAvail = wSndAvail + fromTcpSeqNum (wnd - wSndWnd)+                        , .. })+       (f wSndWnd)+++-- | The value of SND.UNA -- the left-edge of the send window.+sndUna :: Getting r Window TcpSeqNum+sndUna  = to $ \ Window { .. } ->+  case wRetransmitQueue of+    seg : _ -> view leftEdge seg+    []      -> wSndNxt+++-- | Returns the new send window, as well as boolean indicating whether or not+-- the retransmit timer needs to be started.+queueSegment :: Config -> UTCTime -> (Word32 -> TcpSeqNum -> TcpHeader) -> L.ByteString+             -> Window -> (Window,Maybe (Bool,TcpHeader,L.ByteString))+queueSegment cfg now mkHdr body win+  | size == 0          = (win, Just (False,hdr,L.empty))+  | wSndAvail win == 0 = (win,Nothing)+  | otherwise          = (win',Just (startRTO,hdr,trimmedBody))+  where++  clock'      = updateTSClock cfg now (wTSClock win)+  hdr         = mkHdr (view tsVal clock') (wSndNxt win)++  trimmedBody = L.take (fromIntegral (wSndAvail win)) body+  seg         = mkSegment hdr trimmedBody now++  size        = tcpSegLen hdr (fromIntegral (L.length trimmedBody))++  win' = win { wRetransmitQueue = wRetransmitQueue win ++ [seg]+             , wSndAvail        = wSndAvail win - size+             , wSndNxt          = wSndNxt win + fromIntegral size+             , wTSClock         = clock'+             }++  -- start the retransmit timer when the queue was empty+  startRTO = null (wRetransmitQueue win)+++-- | A retransmit timer has gone off: reset the sack bit on all segments in the+-- queue; if the left-edge exists, mark it as having been retransmitted, and+-- return it back to be sent.+--+-- XXX: does this need to update the TS clock?+retransmitTimeout :: Window -> (Window,Maybe (TcpHeader,L.ByteString))+retransmitTimeout win = (win { wRetransmitQueue = queue' }, mbSeg)+  where++  (mbSeg,queue') =+    case wRetransmitQueue win of+      Segment { .. } : rest ->+        ( Just (segHeader,segBody)+        , map (set sack False) (Segment { segSentAt = Nothing, .. } : rest ) )++      [] -> (Nothing,[])+++-- | Remove all segments of the send window that occur before this sequence+-- number, and increase the size of the available window. When the segment+-- doesn't acknowledge anything in the window, 'Nothing' as the second+-- parameter. Otherwise, return a boolean that is 'True' when there are no+-- outstanding segments, and a measurement of the RTT when the segment has not+-- been retransmitted.+ackSegment :: Config -> UTCTime -> TcpSeqNum -> Window+           -> (Window, Maybe (Bool,Maybe NominalDiffTime))+ackSegment cfg now ack win+  | view sndUna win <= ack && ack <= view sndNxt win =+    ( win', Just (null (wRetransmitQueue win'), mbMeasurement) )++  | otherwise =+    ( win, Nothing )+  where+  win' = win { wRetransmitQueue = queue'+             , wSndAvail        = wSndAvail win + fromTcpSeqNum (ack - view sndUna win)+             , wTSClock         = updateTSClock cfg now (wTSClock win)+             }++  -- zip down the send queue, partitioning it into (reversed) acknowledged+  -- packets, and outstanding packets.+  go acks segs@(seg : rest)+    | view rightEdge seg <= ack = go (seg:acks) rest+    | view leftEdge  seg <= ack = (seg:acks, set leftEdge ack seg : rest)+    | otherwise                 = (acks,segs)++  go acks [] = (acks,[])++  -- partition packets that have been acknowledged+  (ackd,queue') = go [] (wRetransmitQueue win)++  -- generate a measurement. If the timestamp option is available, that will be+  -- used to generate the measurement, otherwise the sending time for the+  -- segment will be diffed if it's valid.+  mbMeasurement =+    case ackd of+      seg : _++        | Just (OptTimestamp val _) <- findTcpOption OptTagTimestamp seg ->+          return (measureRTT cfg val (wTSClock win'))++        | otherwise ->+          do let samples = filter (isJust . segSentAt) ackd+             guard (not (null samples))+             let Segment { .. } = last samples+             sent <- segSentAt+             return $! diffUTCTime sent now++      [] -> Nothing+++-- Selective ACK ---------------------------------------------------------------++-- | Process a sack option, and return the updated window, and the segments that+-- are missing from the remote window.+handleSack :: [SackBlock] -> Window -> (Window,[(TcpHeader,L.ByteString)])+handleSack blocks win =+  let win' = processSackBlocks blocks win+   in (win', sackRetransmit win')++-- | All segments that have not been selectively acknowledged. This can be used+-- when replying to a duplicate ack that contains a SACK option, after the+-- option has been processed. NOTE: this still doesn't remove the packets from+-- the queue, it just means that we know what parts to retransmit.+sackRetransmit :: Window -> [(TcpHeader,L.ByteString)]+sackRetransmit Window { .. } =+  [ (segHeader,segBody) | Segment { .. } <- wRetransmitQueue, not segSACK ]+++-- | Mark segments that have been acknowledged through the SACK option.+processSackBlocks :: [SackBlock] -> Window -> Window+processSackBlocks blocks Window { .. } =+  Window { wRetransmitQueue = go wRetransmitQueue (sortBy (comparing sbLeft) blocks)+         , .. }+  where++  go queue@(seg:segs) bs@(SackBlock { .. } :rest)++      -- segment falls within the block+    | segWithin seg sbLeft sbRight = set sack True seg : go segs bs++      -- segment begins after the block+    | view leftEdge seg >= sbRight = go queue rest++      -- segment ends before the block+    | otherwise = seg : go segs bs++  go segs _ = segs+++-- | True when the segment falls wholly within the range given.+segWithin :: Segment -> TcpSeqNum -> TcpSeqNum -> Bool+segWithin seg l r = view leftEdge seg >= l && view rightEdge seg < r+  -- Remember that since SACK blocks define the right edge as being the first+  -- sequence number of the /next/ block, we use strict less-than for the+  -- comparison of the right edge.
+ src/Hans/Tcp/State.hs view
@@ -0,0 +1,405 @@+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE BangPatterns #-}++module Hans.Tcp.State (+    -- * Tcp State+    HasTcpState(..), TcpState(),+    newTcpState,++    -- ** Responder Interaction+    tcpQueue,+    TcpResponderRequest(..),++    -- ** Listen Sockets+    incrSynBacklog,+    decrSynBacklog,+    registerListening,+    lookupListening,+    deleteListening,++    -- ** Active Sockets+    Key(), tcbKey,+    tcpActive,+    lookupActive,+    registerActive,+    closeActive,+    deleteActive,++    -- ** TimeWait Sockets+    registerTimeWait,+    lookupTimeWait,+    resetTimeWait,+    deleteTimeWait,++    -- ** Port Management+    nextTcpPort,++    -- ** Sequence Numbers+    nextIss,+  ) where++import           Hans.Addr (Addr,wildcardAddr,putAddr)+import           Hans.Config (HasConfig(..),Config(..))+import qualified Hans.HashTable as HT+import           Hans.Lens+import           Hans.Network.Types (RouteInfo(..))+import           Hans.Tcp.Packet+import           Hans.Tcp.Tcb+import           Hans.Threads (forkNamed)+import           Hans.Time++import           Control.Concurrent (threadDelay,MVar,newMVar,modifyMVar)+import qualified Control.Concurrent.BoundedChan as BC+import           Control.Monad (guard)+import qualified Data.ByteString as S+import qualified Data.ByteString.Lazy as L+import           Data.Digest.Pure.SHA(sha1,integerDigest)+import qualified Data.Foldable as F+import           Data.Hashable (Hashable)+import qualified Data.Heap as H+import           Data.IORef (IORef,newIORef,atomicModifyIORef',readIORef)+import           Data.Serialize (runPutLazy,putByteString)+import           Data.Time.Clock (UTCTime,getCurrentTime,addUTCTime,diffUTCTime)+import           Data.Word (Word32)+import           GHC.Generics (Generic)+import           System.Random (newStdGen,random,randoms)+++-- General State ---------------------------------------------------------------++data ListenKey = ListenKey !Addr !TcpPort+                 deriving (Show,Eq,Ord,Generic)++listenKey :: Getting r ListenTcb ListenKey+listenKey  = to (\ ListenTcb { .. } -> ListenKey lSrc lPort)+{-# INLINE listenKey #-}+++data Key = Key !Addr    -- Remote address+               !TcpPort -- Remote port+               !Addr    -- Local address+               !TcpPort -- Local port+           deriving (Show,Eq,Ord,Generic)++tcbKey :: Getting r Tcb Key+tcbKey  = to $ \Tcb { tcbRouteInfo = RouteInfo { .. }, .. } ->+                Key tcbRemote tcbRemotePort riSource tcbLocalPort+{-# INLINE tcbKey #-}++++instance Hashable ListenKey+instance Hashable Key++type TimeWaitHeap = ExpireHeap TimeWaitTcb++data TcpState =+  TcpState { tcpListen_     :: {-# UNPACK #-} !(HT.HashTable ListenKey ListenTcb)+           , tcpActive_     :: {-# UNPACK #-} !(HT.HashTable Key Tcb)+           , tcpTimeWait_   :: {-# UNPACK #-} !(IORef TimeWaitHeap)+           , tcpSynBacklog_ :: {-# UNPACK #-} !(IORef Int)+             -- ^ Decrements when a connection enters SynReceived or SynSent,+             -- and increments back up once it's closed, or enters Established.++           , tcpPorts       :: {-# UNPACK #-} !(MVar TcpPort)+           , tcpISSTimer    :: {-# UNPACK #-} !(IORef Tcp4USTimer)++           , tcpQueue_      :: {-# UNPACK #-} !(BC.BoundedChan TcpResponderRequest)+           }++-- | Requests that can be made to the responder thread.+data TcpResponderRequest = SendSegment !(RouteInfo Addr) !Addr !TcpHeader !L.ByteString+                         | SendWithTcb !Tcb !TcpHeader !L.ByteString++tcpQueue :: HasTcpState state => Getting r state (BC.BoundedChan TcpResponderRequest)+tcpQueue  = tcpState . to tcpQueue_+{-# INLINE tcpQueue #-}++tcpListen :: HasTcpState state => Getting r state (HT.HashTable ListenKey ListenTcb)+tcpListen  = tcpState . to tcpListen_+{-# INLINE tcpListen #-}++tcpActive :: HasTcpState state => Getting r state (HT.HashTable Key Tcb)+tcpActive  = tcpState . to tcpActive_+{-# INLINE tcpActive #-}++tcpTimeWait :: HasTcpState state => Getting r state (IORef TimeWaitHeap)+tcpTimeWait  = tcpState . to tcpTimeWait_+{-# INLINE tcpTimeWait #-}++tcpSynBacklog :: HasTcpState state => Getting r state (IORef Int)+tcpSynBacklog  = tcpState . to tcpSynBacklog_+{-# INLINE tcpSynBacklog #-}+++class HasTcpState state where+  tcpState :: Getting r state TcpState++instance HasTcpState TcpState where+  tcpState = id+  {-# INLINE tcpState #-}++++data Tcp4USTimer = Tcp4USTimer { tcpTimer      :: {-# UNPACK #-} !Word32+                               , tcpSecret     :: {-# UNPACK #-} !S.ByteString+                               , tcpLastUpdate :: !UTCTime+                               }++newTcp4USTimer :: IO Tcp4USTimer+newTcp4USTimer  =+  do tcpLastUpdate <- getCurrentTime+     gen           <- newStdGen+     let (tcpTimer,gen') = random gen+         tcpSecret       = S.pack (take 256 (randoms gen'))+     return Tcp4USTimer { .. }++++newTcpState :: Config -> IO TcpState+newTcpState Config { .. } =+  do tcpListen_     <- HT.newHashTable cfgTcpListenTableSize+     tcpActive_     <- HT.newHashTable cfgTcpActiveTableSize+     tcpTimeWait_   <- newIORef emptyHeap+     tcpSynBacklog_ <- newIORef cfgTcpMaxSynBacklog+     tcpPorts       <- newMVar 32767+     tcpISSTimer    <- newIORef =<< newTcp4USTimer+     tcpQueue_      <- BC.newBoundedChan 128+     return TcpState { .. }+++-- | Returns 'True' when there is space in the Syn backlog, and False if the+-- connection should be rejected.+decrSynBacklog :: HasTcpState state => state -> IO Bool+decrSynBacklog state =+  atomicModifyIORef' (view tcpSynBacklog state) $ \ backlog ->+    if backlog > 0+       then (backlog - 1, True)+       else (backlog, False)++-- | Yield back an entry in the Syn backlog.+incrSynBacklog :: HasTcpState state => state -> IO ()+incrSynBacklog state =+  atomicModifyIORef' (view tcpSynBacklog state)+                     (\ backlog -> (backlog + 1, ()))+++-- Listening Sockets -----------------------------------------------------------++-- | Register a new listening socket.+registerListening :: HasTcpState state+                  => state -> ListenTcb -> IO Bool+registerListening state tcb =+  HT.alter update (view listenKey tcb) (view tcpListen state)+  where+  update Nothing   = (Just tcb, True)+  update mb@Just{} = (mb, False)+{-# INLINE registerListening #-}+++-- | Remove a listening socket.+deleteListening :: HasTcpState state+                => state -> ListenTcb -> IO ()+deleteListening state tcb =+  HT.delete (view listenKey tcb) (view tcpListen state)+{-# INLINE deleteListening #-}+++-- | Lookup a socket in the Listen state.+lookupListening :: HasTcpState state+                => state -> Addr -> TcpPort -> IO (Maybe ListenTcb)+lookupListening state src port =+  do mb <- HT.lookup (ListenKey src port) (view tcpListen state)+     case mb of+       Just {} -> return mb+       Nothing ->+         HT.lookup (ListenKey (wildcardAddr src) port) (view tcpListen state)+{-# INLINE lookupListening #-}+++-- TimeWait Sockets ------------------------------------------------------------++-- | Register a socket in the TimeWait state. If the heap was empty, fork off a+-- thread to reap its contents after the timeWaitTimeout.+--+-- NOTE: this doesn't remove the original socket from the Active set.+registerTimeWait :: (HasConfig state, HasTcpState state)+                 => state -> TimeWaitTcb -> IO ()+registerTimeWait state tcb =+  let Config { .. } = view config state+   in updateTimeWait state $ \ now heap ->+          let heap' = if H.size heap >= cfgTcpTimeWaitSocketLimit+                         then H.deleteMin heap+                         else heap+           in fst (expireAt (addUTCTime cfgTcpTimeoutTimeWait now) tcb heap')++-- | Reset the timer associated with a TimeWaitTcb.+resetTimeWait :: (HasConfig state, HasTcpState state)+              => state -> TimeWaitTcb -> IO ()+resetTimeWait state tcb =+  let Config { .. } = view config state+   in updateTimeWait state $ \ now heap ->+          fst $ expireAt (addUTCTime cfgTcpTimeoutTimeWait now) tcb+              $ filterHeap (/= tcb) heap++-- | Modify the TimeWait heap, and spawn a reaping thread when necessary.+updateTimeWait :: (HasConfig state, HasTcpState state)+               => state -> (UTCTime -> TimeWaitHeap -> TimeWaitHeap) -> IO ()+updateTimeWait state update =+  do now    <- getCurrentTime+     mbReap <-+       atomicModifyIORef' (view tcpTimeWait state) $ \ heap ->+           let heap'  = update now heap++               -- Return a reaping action if:+               --+               -- 1. The original heap was empty, signifying that there was no+               --    existing reaper running+               --+               -- 2. The user action added something to the heap+               reaper = do guard (nullHeap heap)+                           future <- nextEvent heap'+                           return $ do delayDiff now future+                                       reapLoop++            in (heap', reaper)++     case mbReap of+       Just reaper -> do _ <- forkNamed "TimeWait Reaper" reaper+                         return ()++       Nothing     -> return ()++  where++  -- delay by at least half a second, until some point in the future.+  delayDiff now future =+    threadDelay (max 500000 (toUSeconds (diffUTCTime future now)))++  -- The reap thread will reap TimeWait sockets according to their expiration+  -- time, and then exit.+  reapLoop =+    do now      <- getCurrentTime+       mbExpire <-+         atomicModifyIORef' (view tcpTimeWait state) $ \ heap ->+             let heap' = dropExpired now heap+              in (heap', nextEvent heap')++       case mbExpire of+         Just future -> do delayDiff now future+                           reapLoop++         Nothing     -> return ()+++-- | Lookup a socket in the TimeWait state.+lookupTimeWait :: HasTcpState state+             => state -> Addr -> TcpPort -> Addr -> TcpPort+             -> IO (Maybe TimeWaitTcb)+lookupTimeWait state dst dstPort src srcPort =+  do heap <- readIORef (view tcpTimeWait state)+     return (payload `fmap` F.find isConn heap)+  where+  isConn Entry { payload = TimeWaitTcb { .. } } =+    and [ twRemote             == dst+        , twRemotePort         == dstPort+        , riSource twRouteInfo == src+        , twLocalPort          == srcPort ]+{-# INLINE lookupTimeWait #-}+++-- | Delete an entry from the TimeWait heap.+deleteTimeWait :: HasTcpState state => state -> TimeWaitTcb -> IO ()+deleteTimeWait state tw =+  atomicModifyIORef' (view tcpTimeWait state) $ \ heap ->+      (filterHeap (/= tw) heap, ())+{-# INLINE deleteTimeWait #-}+++-- Active Sockets --------------------------------------------------------------++-- | Register a new active socket.+registerActive :: HasTcpState state => state -> Tcb -> IO Bool+registerActive state tcb =+  HT.alter update (view tcbKey tcb) (view tcpActive state)+  where+  update Nothing = (Just tcb, True)+  update mb      = (mb, False)+{-# INLINE registerActive #-}+++-- | Lookup an active socket.+lookupActive :: HasTcpState state+             => state -> Addr -> TcpPort -> Addr -> TcpPort -> IO (Maybe Tcb)+lookupActive state dst dstPort src srcPort =+  HT.lookup (Key dst dstPort src srcPort) (view tcpActive state)+{-# INLINE lookupActive #-}+++-- | Delete the 'Tcb', and notify any waiting processes.+closeActive :: HasTcpState state => state -> Tcb -> IO ()+closeActive state tcb =+  do finalizeTcb tcb+     deleteActive state tcb+{-# INLINE closeActive #-}+++-- | Delete an active connection from the tcp state.+deleteActive :: HasTcpState state => state -> Tcb -> IO ()+deleteActive state tcb =+  HT.delete (view tcbKey tcb) (view tcpActive state)+{-# INLINE deleteActive #-}+++-- Port Management -------------------------------------------------------------++-- | Pick a fresh port for a connection.+nextTcpPort :: HasTcpState state+            => state -> Addr -> Addr -> TcpPort -> IO (Maybe TcpPort)+nextTcpPort state src dst dstPort =+  modifyMVar tcpPorts (pickFreshPort tcpActive_ (Key dst dstPort src))+  where+  TcpState { .. } = view tcpState state++pickFreshPort :: HT.HashTable Key Tcb -> (TcpPort -> Key) -> TcpPort+              -> IO (TcpPort, Maybe TcpPort)+pickFreshPort ht mkKey p0 = go 0 p0+  where++  go :: Int -> TcpPort -> IO (TcpPort,Maybe TcpPort)+  go i _ | i > 65535 = return (p0, Nothing)+  go i 0             = go (i+1) 1025+  go i port          =+    do used <- HT.hasKey (mkKey port) ht+       if not used+          then return (port, Just port)+          else go (i + 1) (port + 1)+++-- Sequence Numbers ------------------------------------------------------------++nextIss :: HasTcpState state+        => state -> Addr -> TcpPort -> Addr -> TcpPort -> IO TcpSeqNum+nextIss state src srcPort dst dstPort =+  do let TcpState { .. } = view tcpState state+     now <- getCurrentTime+     (m,f_digest) <- atomicModifyIORef' tcpISSTimer $ \ Tcp4USTimer { .. } ->+       let diff    = diffUTCTime now tcpLastUpdate+           ticks   = tcpTimer + truncate (diff * 250000) -- 4us chunks+           timers' = Tcp4USTimer { tcpTimer      = ticks+                                 , tcpLastUpdate = now+                                 , .. }++           digest  = integerDigest $ sha1 $ runPutLazy $+             do putAddr src+                putTcpPort srcPort+                putAddr dst+                putTcpPort dstPort+                putByteString tcpSecret++        in (timers', (ticks, digest))++     return (fromIntegral (m + fromIntegral f_digest))
+ src/Hans/Tcp/Tcb.hs view
@@ -0,0 +1,631 @@+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE FlexibleInstances #-}++module Hans.Tcp.Tcb (+    -- * Timers+    SlowTicks,+    TcpTimers(..),+    emptyTcpTimers,+    resetRetransmit,+    retryRetransmit,+    stopRetransmit,+    reset2MSL,+    updateTimers,+    calibrateRTO,++    -- * TCB States+    State(..),+    GetState(..),+    whenState,+    setState,++    -- * Sending+    CanSend(..),+    getSndNxt,+    getSndWnd,++    -- * Receiving+    CanReceive(..),+    getRcvNxt,+    getRcvWnd,+    getRcvRight,++    -- * Listening TCBs+    ListenTcb(..),+    newListenTcb,+    createChild,+    reserveSlot, releaseSlot,+    acceptTcb,++    -- * Active TCBs+    Tcb(..),+    newTcb,+    signalDelayedAck,+    setRcvNxt,+    finalizeTcb,+    getSndUna,+    resetIdleTimer,++    -- ** Active Config+    TcbConfig(..),+    usingTimestamps,+    disableTimestamp,++    -- ** Windowing+    queueBytes,+    haveBytesAvail,+    receiveBytes, tryReceiveBytes,++    -- * TimeWait TCBs+    TimeWaitTcb(..),+    mkTimeWaitTcb,+  ) where++import           Hans.Addr (Addr)+import           Hans.Buffer.Signal+import qualified Hans.Buffer.Stream as Stream+import           Hans.Config (HasConfig(..),Config(..))+import           Hans.Lens+import           Hans.Network.Types (RouteInfo)+import           Hans.Tcp.Packet+import qualified Hans.Tcp.RecvWindow as Recv+import qualified Hans.Tcp.SendWindow as Send++import           Control.Monad (when)+import qualified Data.ByteString as S+import qualified Data.ByteString.Lazy as L+import           Data.IORef+                     (IORef,newIORef,atomicModifyIORef',readIORef+                     ,atomicWriteIORef)+import           Data.Int (Int64)+import qualified Data.Sequence as Seq+import           Data.Time.Clock (NominalDiffTime,getCurrentTime)+import           Data.Word (Word16,Word32)+import           MonadLib (BaseM(..))+import           System.CPUTime (getCPUTime)+++-- Timers ----------------------------------------------------------------------++type SlowTicks = Int++data TcpTimers = TcpTimers { ttDelayedAck :: !Bool++                             -- MSL+                           , tt2MSL :: !SlowTicks++                             -- retransmit timer+                           , ttRetransmitValid :: !Bool+                           , ttRetransmit      :: !SlowTicks+                           , ttRetries         :: !Int++                             -- retransmit timer config+                           , ttRTO    :: !SlowTicks+                           , ttSRTT   :: !NominalDiffTime+                           , ttRTTVar :: !NominalDiffTime++                             -- idle timer+                           , ttMaxIdle :: !SlowTicks+                           , ttIdle    :: !SlowTicks+                           }++emptyTcpTimers :: TcpTimers+emptyTcpTimers  = TcpTimers { ttDelayedAck = False+                            , tt2MSL       = 0++                            , ttRetransmitValid = False+                            , ttRetransmit      = 0+                            , ttRetries         = 0++                            , ttRTO        = 2 -- two ticks -- one second+                            , ttSRTT       = 0+                            , ttRTTVar     = 0+                            , ttMaxIdle    = 10 * 60 * 2 -- 10 minutes+                            , ttIdle       = 0+                            }+++-- | Reset retransmit info.+resetRetransmit :: TcpTimers -> (TcpTimers, ())+resetRetransmit TcpTimers { .. } =+  (TcpTimers { ttRetransmitValid = True+             , ttRetransmit      = ttRTO+             , ttRetries         = 0+             , .. }, ())+++-- | Increment the retry count, and double the last retransmit timer.+retryRetransmit :: TcpTimers -> (TcpTimers, ())+retryRetransmit TcpTimers { .. } =+  (TcpTimers { ttRetransmitValid = True+             , ttRetransmit      = ttRTO * 2 ^ retries+             , ttRetries         = retries+             , .. }, ())+  where+  retries = ttRetries + 1+++-- | Invalidate the retransmit timer.+stopRetransmit :: TcpTimers -> (TcpTimers, ())+stopRetransmit TcpTimers { .. } =+  (TcpTimers { ttRetransmitValid = False+             , ttRetries         = 0+             , .. }, ())+++reset2MSL :: Config -> TcpTimers -> (TcpTimers, ())+reset2MSL Config { .. } tt = (tt { tt2MSL = 4 * cfgTcpMSL }, ())+  -- NOTE: cfgTcpMSL is multiplied by four here, as it's given in seconds, but+  -- counted in slow ticks (half seconds). Multiplying by four gives the value+  -- of 2*MSL in slow ticks.+++-- | Update all slow-tick timers. Return the old timers, for use with+-- 'atomicModifyIORef\''.+updateTimers :: TcpTimers -> (TcpTimers, TcpTimers)+updateTimers tt = (tt',tt)+  where+  tt' = tt { ttRetransmit = if ttRetransmitValid tt then ttRetransmit tt - 1 else 0+           , tt2MSL       = max 0 (tt2MSL tt - 1)+           , ttIdle       = ttIdle tt + 1+           }++-- | Reset idle timer in the presence of packets, for use with+-- 'atomicModifyIORef\''.+resetIdleTimer :: TcpTimers -> (TcpTimers, ())+resetIdleTimer t = (idleReset, ())+  where+   idleReset = t { ttIdle = 0 }++-- | Calibrate the RTO timer, given a round-trip measurement, as specified by+-- RFC-6298.+calibrateRTO :: NominalDiffTime -> TcpTimers -> (TcpTimers, ())+calibrateRTO r tt+  | ttSRTT tt > 0 = (rolling, ())+  | otherwise     = (initial, ())+  where++  -- no data has been sent before, seed the RTO values.+  initial = updateRTO tt+    { ttSRTT   = r+    , ttRTTVar = r / 2+    }++  -- data has been sent, update based on previous values+  alpha   = 0.125+  beta    = 0.25+  rttvar  = (1 - beta)  * ttRTTVar tt + beta  * abs (ttSRTT tt * r)+  srtt    = (1 - alpha) * ttSRTT   tt + alpha * r+  rolling = updateRTO tt+    { ttRTTVar = rttvar+    , ttSRTT   = srtt+    }++  -- update the RTO timer length, bounding it at 64 seconds (128 ticks)+  updateRTO tt' = tt'+    { ttRTO = min 128 (ceiling (ttSRTT tt' + max 0.5 (2 * ttRTTVar tt')))+    }+++-- Socket State ----------------------------------------------------------------++whenState :: (BaseM m IO, GetState tcb) => tcb -> State -> m () -> m ()+whenState tcb state m =+  do state' <- inBase (getState tcb)+     when (state == state') m++-- | The Tcb type is the only one that supports changing state.+setState :: Tcb -> State -> IO ()+setState tcb state =+  do old <- atomicModifyIORef' (tcbState tcb) (\old -> (state,old))+     case state of+       Established -> tcbEstablished tcb tcb old+       Closed      -> tcbClosed      tcb tcb old+       CloseWait   -> Stream.closeBuffer (tcbRecvBuffer tcb)+       _           -> return ()++class GetState tcb where+  getState :: tcb -> IO State++instance GetState ListenTcb where+  getState _ = return Listen++instance GetState Tcb where+  getState Tcb { .. } = readIORef tcbState++instance GetState TimeWaitTcb where+  getState _ = return TimeWait++data State = Listen+           | SynSent+           | SynReceived+           | Established+           | FinWait1+           | FinWait2+           | CloseWait+           | Closing+           | LastAck+           | TimeWait+           | Closed+             deriving (Eq,Show)+++-- Listening Sockets -----------------------------------------------------------++data ListenTcb = ListenTcb { lSrc    :: !Addr+                           , lPort   :: !TcpPort++                             -- accept+                           , lAccept       :: !(IORef AcceptQueue)+                           , lAcceptSignal :: !Signal++                           , lTSClock :: !(IORef Send.TSClock)+                           }++-- | Create a new listening socket.+newListenTcb :: Addr -> TcpPort -> Int -> IO ListenTcb+newListenTcb lSrc lPort aqFree =+  do lAccept       <- newIORef (AcceptQueue { aqTcbs = Seq.empty, .. })+     lAcceptSignal <- newSignal++     now      <- getCurrentTime+     tsval    <- getCPUTime+     lTSClock <- newIORef (Send.initialTSClock (fromInteger tsval) now)++     return ListenTcb { .. }+++-- | Create a child from a Syn request.+createChild :: HasConfig cfg+            => cfg -> TcpSeqNum -> ListenTcb -> RouteInfo Addr -> Addr -> TcpHeader+            -> (Tcb -> State -> IO ()) -- ^ On Established+            -> (Tcb -> State -> IO ()) -- ^ On Closed+            -> IO Tcb+createChild cxt iss parent ri remote hdr onEstablished onClosed =+  do let cfg = view config cxt++     now <- getCurrentTime+     tsc <- atomicModifyIORef' (lTSClock parent) $ \ tsc ->+                let tsc' = Send.updateTSClock cfg now tsc+                 in (tsc',tsc')++     child <- newTcb cfg (Just parent) iss ri (tcpDestPort hdr) remote+                  (tcpSourcePort hdr) SynReceived tsc+                  (\c state -> do queueTcb parent c state+                                  onEstablished c state)+                  (\c state -> do when (state == SynReceived) (releaseSlot parent)+                                  onClosed c state)+++     atomicWriteIORef (tcbIrs child) (tcpSeqNum hdr)+     atomicWriteIORef (tcbIss child)  iss++     -- advance RCV.NXT over the SYN+     _ <- setRcvNxt (tcpSeqNum hdr + 1) child+     _ <- setSndNxt iss child++     return child+++data AcceptQueue = AcceptQueue { aqFree :: !Int+                               , aqTcbs :: Seq.Seq Tcb+                               }+++-- | Reserve a slot in the accept queue, returns True when the space has been+-- reserved.+reserveSlot :: ListenTcb -> IO Bool+reserveSlot ListenTcb { .. } =+  atomicModifyIORef' lAccept $ \ aq ->+    if aqFree aq > 0+       then (aq { aqFree = aqFree aq - 1 }, True)+       else (aq, False)+{-# INLINE reserveSlot #-}+++-- | Release a slot back to the accept queue.+releaseSlot :: ListenTcb -> IO ()+releaseSlot ListenTcb { .. } =+  atomicModifyIORef' lAccept (\ aq -> (aq { aqFree = aqFree aq + 1 }, ()))+{-# INLINE releaseSlot #-}+++-- | Add a Tcb to the accept queue for this listening connection.+queueTcb :: ListenTcb -> Tcb -> State -> IO ()+queueTcb ListenTcb { .. } tcb _ =+  do atomicModifyIORef' lAccept $ \ aq ->+         (aq { aqTcbs = aqTcbs aq Seq.|> tcb }, ())+     signal lAcceptSignal+++-- | Wait until a Tcb is available in the accept queue.+acceptTcb :: ListenTcb -> IO Tcb+acceptTcb ListenTcb { .. } =+  do waitSignal lAcceptSignal+     atomicModifyIORef' lAccept $ \ AcceptQueue { .. } ->+         case Seq.viewl aqTcbs of++           tcb Seq.:< tcbs ->+             (AcceptQueue { aqTcbs = tcbs, aqFree = aqFree + 1 }, tcb)++           Seq.EmptyL ->+             error "Accept queue signaled with an empty queue"+++-- Active Sockets --------------------------------------------------------------++type SeqNumVar = IORef TcpSeqNum++data TcbConfig = TcbConfig { tcUseTimestamp :: !Bool+                           }++defaultTcbConfig :: TcbConfig+defaultTcbConfig  =+  TcbConfig { tcUseTimestamp = True+            }++-- | True when the timestamp option should be included.+usingTimestamps :: Tcb -> IO Bool+usingTimestamps Tcb { .. } =+  do TcbConfig { .. } <- readIORef tcbConfig+     return tcUseTimestamp++-- | Disable the use of the timestamp option.+disableTimestamp :: Tcb -> IO ()+disableTimestamp Tcb { .. } =+  atomicModifyIORef' tcbConfig $ \ TcbConfig { .. } ->+    (TcbConfig { tcUseTimestamp = False, .. }, ())++data Tcb = Tcb { tcbParent :: Maybe ListenTcb+                 -- ^ Parent to notify if this tcb was generated from a socket+                 -- in the LISTEN state++               , tcbConfig :: !(IORef TcbConfig)++               , tcbState       :: !(IORef State)+               , tcbEstablished :: Tcb -> State -> IO ()+               , tcbClosed      :: Tcb -> State -> IO ()++                 -- Sender variables+               , tcbSndUp  :: !SeqNumVar -- ^ SND.UP+               , tcbSndWl1 :: !SeqNumVar -- ^ SND.WL1+               , tcbSndWl2 :: !SeqNumVar -- ^ SND.WL2+               , tcbIss    :: !SeqNumVar -- ^ ISS+               , tcbSendWindow :: !(IORef Send.Window)++                 -- Receive variables+               , tcbRcvUp  :: !SeqNumVar -- ^ RCV.UP+               , tcbIrs    :: !SeqNumVar -- ^ IRS++               , tcbNeedsDelayedAck :: !(IORef Bool)+               , tcbRecvWindow :: !(IORef Recv.Window)+               , tcbRecvBuffer :: !Stream.Buffer++                 -- Port information+               , tcbLocalPort  :: !TcpPort -- ^ Local port+               , tcbRemotePort :: !TcpPort -- ^ Remote port++                 -- Routing information+               , tcbRouteInfo :: !(RouteInfo Addr) -- ^ Cached routing+               , tcbRemote    :: !Addr             -- ^ Remote host++                 -- Fragmentation information+               , tcbMss :: !(IORef Int64) -- ^ Maximum segment size++                 -- Timers+               , tcbTimers :: !(IORef TcpTimers)++                 -- Timer option state+               , tcbTSRecent    :: !(IORef Word32)+               , tcbLastAckSent :: !(IORef TcpSeqNum)+               }++newTcb :: HasConfig state+       => state+       -> Maybe ListenTcb+       -> TcpSeqNum -- ^ ISS+       -> RouteInfo Addr -> TcpPort -> Addr -> TcpPort+       -> State+       -> Send.TSClock+       -> (Tcb -> State -> IO ())+       -> (Tcb -> State -> IO ())+       -> IO Tcb+newTcb cxt tcbParent iss tcbRouteInfo tcbLocalPort tcbRemote tcbRemotePort+  state tsc tcbEstablished tcbClosed =+  do let Config { .. } = view config cxt++     tcbConfig <- newIORef defaultTcbConfig++     tcbState  <- newIORef state+     tcbSndUp  <- newIORef 0+     tcbSndWl1 <- newIORef 0+     tcbSndWl2 <- newIORef 0++     tcbSendWindow <-+         newIORef (Send.emptyWindow iss (fromIntegral cfgTcpInitialWindow) tsc)++     tcbIss    <- newIORef iss++     -- NOTE: the size of the receive buffer is gated by the local window+     tcbRecvWindow <- newIORef (Recv.emptyWindow 0 (fromIntegral cfgTcpInitialWindow))+     tcbRecvBuffer <- Stream.newBuffer++     tcbRcvUp  <- newIORef 0+     tcbNeedsDelayedAck <- newIORef False+     tcbIrs    <- newIORef 0+     tcbMss    <- newIORef (fromIntegral cfgTcpInitialMSS)+     tcbTimers <- newIORef emptyTcpTimers++     tcbTSRecent     <- newIORef 0+     tcbLastAckSent  <- newIORef 0++     return Tcb { .. }++-- | Record that a delayed ack should be sent.+signalDelayedAck :: Tcb -> IO ()+signalDelayedAck Tcb { .. } = atomicWriteIORef tcbNeedsDelayedAck True++-- | Set the value of RCV.NXT. Returns 'True' when the value has been set+-- successfully, and 'False' if the receive queue was not empty.+setRcvNxt :: TcpSeqNum -> Tcb -> IO Bool+setRcvNxt rcvNxt Tcb { .. } =+  atomicModifyIORef' tcbRecvWindow (Recv.setRcvNxt rcvNxt)+++-- | Set the value of SND.NXT. Returns 'True' when the value has been set+-- successfully, and 'False' if the send queue was not empty.+setSndNxt :: TcpSeqNum -> Tcb -> IO Bool+setSndNxt sndNxt Tcb { .. } =+  atomicModifyIORef' tcbSendWindow (Send.setSndNxt sndNxt)+++-- | Cleanup the Tcb.+finalizeTcb :: Tcb -> IO ()+finalizeTcb Tcb { .. } =+  do Stream.closeBuffer tcbRecvBuffer+     atomicModifyIORef' tcbTimers stopRetransmit+     atomicModifyIORef' tcbSendWindow Send.flushWindow++-- | Queue bytes in the receive buffer.+queueBytes :: S.ByteString -> Tcb -> IO ()+queueBytes bytes Tcb { .. } = Stream.putBytes bytes tcbRecvBuffer++-- | Determine if there are bytes in the receive buffer that can be read.+haveBytesAvail :: Tcb -> IO Bool+haveBytesAvail Tcb { .. } =+  Stream.bytesAvailable tcbRecvBuffer++-- | Remove data from the receive buffer, and move the right-side of the receive+-- window. Reading 0 bytes indicates that the remote side has closed the+-- connection.+receiveBytes :: Int -> Tcb -> IO L.ByteString+receiveBytes len Tcb { .. } =+  do bytes <- Stream.takeBytes len tcbRecvBuffer+     atomicModifyIORef' tcbRecvWindow+         (Recv.moveRcvRight (fromIntegral (L.length bytes)))+     return bytes++-- | Non-blocking version of 'receiveBytes'. Reading 0 bytes indicates that the+-- remote side has closed the connection.+tryReceiveBytes :: Int -> Tcb -> IO (Maybe L.ByteString)+tryReceiveBytes len Tcb { .. } =+  do mbBytes <- Stream.tryTakeBytes len tcbRecvBuffer+     case mbBytes of++       Just bytes ->+         do atomicModifyIORef' tcbRecvWindow+                (Recv.moveRcvRight (fromIntegral (L.length bytes)))+            return (Just bytes)++       Nothing ->+            return Nothing+++-- TimeWait Sockets ------------------------------------------------------------++data TimeWaitTcb = TimeWaitTcb { twSndNxt     :: !TcpSeqNum     -- ^ SND.NXT++                               , twRcvNxt     :: !SeqNumVar     -- ^ RCV.NXT+                               , twRcvWnd     :: !Word16        -- ^ RCV.WND++                                 -- Port information+                               , twLocalPort  :: !TcpPort+                               , twRemotePort :: !TcpPort++                                 -- Routing information+                               , twRouteInfo  :: !(RouteInfo Addr)+                               , twRemote     :: !Addr+                               } deriving (Eq)++mkTimeWaitTcb :: Tcb -> IO TimeWaitTcb+mkTimeWaitTcb Tcb { .. } =+  do send     <- readIORef tcbSendWindow+     recv     <- readIORef tcbRecvWindow+     twRcvNxt <- newIORef (view Recv.rcvNxt recv)++     return $! TimeWaitTcb { twSndNxt     = view Send.sndNxt send+                           , twRcvWnd     = view Recv.rcvWnd recv+                           , twLocalPort  = tcbLocalPort+                           , twRemotePort = tcbRemotePort+                           , twRouteInfo  = tcbRouteInfo+                           , twRemote     = tcbRemote+                           , .. }++++-- Sockets that send -----------------------------------------------------------++getSndNxt :: (BaseM io IO, CanSend sock) => sock -> io TcpSeqNum+getSndNxt sock =+  do (nxt,_) <- getSendWindow sock+     return nxt+{-# INLINE getSndNxt #-}++getSndWnd :: (BaseM io IO, CanSend sock) => sock -> io TcpSeqNum+getSndWnd sock =+  do (_,wnd) <- getSendWindow sock+     return wnd+{-# INLINE getSndWnd #-}++class CanSend sock where+  getSendWindow :: BaseM io IO => sock -> io (TcpSeqNum,TcpSeqNum)++instance CanSend (IORef Send.Window) where+  getSendWindow ref =+    do sw <- inBase (readIORef ref)+       return (view Send.sndNxt sw, view Send.sndWnd sw)+  {-# INLINE getSendWindow #-}++instance CanSend Tcb where+  getSendWindow Tcb { .. } = getSendWindow tcbSendWindow+  {-# INLINE getSendWindow #-}+++-- Sockets that receive --------------------------------------------------------++getSndUna :: BaseM io IO => Tcb -> io TcpSeqNum+getSndUna Tcb { .. } =+  do sw <- inBase (readIORef tcbSendWindow)+     return $! view Send.sndUna sw++getRcvNxt :: (BaseM io IO, CanReceive sock) => sock -> io TcpSeqNum+getRcvNxt sock =+  do (nxt,_) <- getRecvWindow sock+     return nxt+{-# INLINE getRcvNxt #-}++getRcvWnd :: (BaseM io IO, CanReceive sock) => sock -> io Word16+getRcvWnd sock =+  do (nxt,right) <- getRecvWindow sock+     return (fromTcpSeqNum (right - nxt))+{-# INLINE getRcvWnd #-}++getRcvRight :: (BaseM io IO, CanReceive sock) => sock -> io TcpSeqNum+getRcvRight sock =+  do (_,right) <- getRecvWindow sock+     return right+{-# INLINE getRcvRight #-}++class CanReceive sock where+  -- | Retrieve the left and right edges of the receive window:+  --+  -- (RCV.NXT, RCV.NXT + RCV.WND)+  getRecvWindow :: BaseM io IO => sock -> io (TcpSeqNum,TcpSeqNum)++instance CanReceive (IORef Recv.Window) where+  getRecvWindow ref = inBase $+    do rw <- readIORef ref+       return (view Recv.rcvNxt rw, view Recv.rcvRight rw)+  {-# INLINE getRecvWindow #-}++instance CanReceive Tcb where+  getRecvWindow Tcb { .. } = getRecvWindow tcbRecvWindow+  {-# INLINE getRecvWindow #-}++instance CanReceive TimeWaitTcb where+  getRecvWindow TimeWaitTcb { .. } = inBase $+    do rcvNxt <- readIORef twRcvNxt+       return (rcvNxt,rcvNxt + fromIntegral twRcvWnd)+  {-# INLINE getRecvWindow #-}
+ src/Hans/Tcp/Timers.hs view
@@ -0,0 +1,83 @@+{-# LANGUAGE RecordWildCards #-}+module Hans.Tcp.Timers where++import           Hans.Config+import qualified Hans.HashTable as HT+import           Hans.Lens+import           Hans.Tcp.Tcb+import           Hans.Tcp.Output+import           Hans.Tcp.SendWindow+import           Hans.Time (toUSeconds)+import           Hans.Types++import Control.Concurrent (threadDelay)+import Control.Monad (when)+import Data.IORef (atomicModifyIORef')+import Data.Time.Clock (getCurrentTime,diffUTCTime)+++-- | Process the slow and fast tcp timers. The fast timer runs four times a+-- second, while the slow timer runs two times a second.+tcpTimers :: NetworkStack -> IO ()+tcpTimers ns = loop True+  where+  loop runSlow =+    do start <- getCurrentTime++       HT.mapHashTableM_ (\_ -> updateActive ns runSlow) (view tcpActive ns)++       -- delay to the next 250ms boundary+       end <- getCurrentTime+       let delay = 0.250 - diffUTCTime end start+       when (delay > 0) (threadDelay (toUSeconds delay))++       loop $! not runSlow+++-- | The body of the fast and slow tick handlers. The boolean indicates whether+-- or not the slow tick should also be run.+updateActive :: NetworkStack -> Bool -> Tcb -> IO ()+updateActive ns runSlow tcb@Tcb { .. } =+  do -- the slow timer+     when runSlow $+       do ts <- atomicModifyIORef' tcbTimers updateTimers+          handleRTO  ns tcb ts+          handle2MSL ns tcb ts++     -- the fast timer+     shouldAck <- atomicModifyIORef' tcbNeedsDelayedAck (\ b -> (False,b))+     when shouldAck (sendAck ns tcb)+++-- | Handle the retransmit timer. When the timer expires, if there is anything+-- in the send window, retransmit the left-most segment.+handleRTO :: NetworkStack -> Tcb -> TcpTimers -> IO ()+handleRTO ns Tcb { .. } TcpTimers { .. }+  | ttRetransmitValid && ttRetransmit <= 0 =+    do mbSeg <- atomicModifyIORef' tcbSendWindow retransmitTimeout+       case mbSeg of+         Just (hdr,body) ->+           do atomicModifyIORef' tcbTimers retryRetransmit+              _ <- sendTcp ns tcbRouteInfo tcbRemote hdr body+              return ()++         Nothing ->+              return ()++  | otherwise =+    return ()+++-- | Make sure that the connection is still active. The Idle timer is checked+-- when the 2MSL timer expires.+handle2MSL :: NetworkStack -> Tcb -> TcpTimers -> IO ()+handle2MSL ns tcb@Tcb { .. } TcpTimers { .. }++  | tt2MSL <= 0 =+       -- why do we only check the idle timer when 2MSL goes off?+       if ttIdle >= ttMaxIdle+          then closeActive ns tcb+          else atomicModifyIORef' tcbTimers (reset2MSL (view config ns))++  | otherwise =+    return ()
+ src/Hans/Threads.hs view
@@ -0,0 +1,17 @@+module Hans.Threads where++import Control.Concurrent (forkFinally,ThreadId)+import Control.Exception (fromException,AsyncException(..))+++forkNamed :: String -> IO () -> IO ThreadId+forkNamed str body = forkFinally body showExn+  where++  showExn Right{} =+    return ()++  showExn (Left e) =+    case fromException e of+      Just ThreadKilled -> return ()+      _                 -> putStrLn (str ++ ": Exception escaped: " ++ show e)
+ src/Hans/Time.hs view
@@ -0,0 +1,84 @@+{-# LANGUAGE RecordWildCards #-}++module Hans.Time (+    module Hans.Time,+    H.Entry(..),+    H.toUnsortedList,+  ) where++import qualified Data.Heap as H+import           Data.Time.Clock (UTCTime,NominalDiffTime,diffUTCTime)+import           Data.Tuple (swap)+++type Expires = H.Entry UTCTime++expiresBefore :: UTCTime -> Expires a -> Bool+expiresBefore time entry = time >= H.priority entry+++type ExpireHeap a = H.Heap (Expires a)++emptyHeap :: ExpireHeap a+emptyHeap  = H.empty+{-# INLINE emptyHeap #-}++fromListHeap :: [Expires a] -> ExpireHeap a+fromListHeap  = H.fromList+{-# INLINE fromListHeap #-}++filterHeap :: (a -> Bool) -> ExpireHeap a -> ExpireHeap a+filterHeap p = H.filter p'+  where+  p' H.Entry { .. } = p payload+{-# INLINE filterHeap #-}++partitionHeap :: (a -> Bool) -> ExpireHeap a -> (ExpireHeap a,ExpireHeap a)+partitionHeap p = H.partition p'+  where+  p' H.Entry { .. } = p payload+{-# INLINE partitionHeap #-}++-- | The next time that something in the heap will expire, if the heap is+-- non-empty.+nextEvent :: ExpireHeap a -> Maybe UTCTime+nextEvent heap =+  do (entry,_) <- H.viewMin heap+     return (H.priority entry)++-- | Remove all expired entries from the heap.+dropExpired :: UTCTime -> ExpireHeap a -> ExpireHeap a+dropExpired now heap = H.dropWhile (expiresBefore now) heap+{-# INLINE dropExpired #-}++-- | Given the current time, partition the heap into valid entries, and entries+-- that have expired.+partitionExpired :: UTCTime -> ExpireHeap a -> (ExpireHeap a, ExpireHeap a)+partitionExpired now heap = swap (H.break (expiresBefore now) heap)+{-# INLINE partitionExpired #-}++-- | Add an entry to the 'ExpireHeap', and return the time of the next+-- expiration event.+expireAt :: UTCTime -> a -> ExpireHeap a -> (ExpireHeap a,UTCTime)+expireAt time a heap =+  let heap' = H.insert H.Entry { H.priority = time, H.payload = a } heap+   in (heap',H.priority (H.minimum heap'))+   -- NOTE: it's safe to use the partial function minimum, as we just inserted+   -- into the heap we're asking for the minimum element of.+{-# INLINE expireAt #-}++nullHeap :: ExpireHeap a -> Bool+nullHeap  = H.null+{-# INLINE nullHeap #-}++-- | The amount of time until the top of the heap expires, relative to the time+-- given.+expirationDelay :: UTCTime -> ExpireHeap a -> Maybe NominalDiffTime+expirationDelay now heap =+  do (H.Entry { .. }, _) <- H.viewMin heap+     return $! diffUTCTime priority now+++-- | Convert a 'NominalDiffTime' into microseconds for use with 'threadDelay'.+toUSeconds :: NominalDiffTime -> Int+toUSeconds diff = max 0 (truncate (diff * 1000000))
− src/Hans/Timers.hs
@@ -1,55 +0,0 @@-module Hans.Timers (-    Milliseconds-  , Timer()-  , delay-  , delay_-  , cancel-  , expired-  ) where--import Control.Concurrent (forkIO,ThreadId,threadDelay,killThread-                          ,mkWeakThreadId)-import GHC.Conc (threadStatus,ThreadStatus(..))-import System.Mem.Weak (Weak,deRefWeak)---type Milliseconds = Int---- | A handle to a scheduled timer.------ NOTE: This keeps a weak reference to the thread containing the timer, to--- allow it to still receive exceptions (see mkWeakThreadId).-newtype Timer = Timer (Weak ThreadId)---- | Delay an action, giving back a handle to allow the timer to be cancelled.-delay :: Milliseconds -> IO () -> IO Timer-delay n body =-  do tid <- forkIO (threadDelay (n * 1000) >> body)-     wid <- mkWeakThreadId tid-     return (Timer wid)---- | Delay an action.-delay_ :: Milliseconds -> IO () -> IO ()-delay_ n body =-  do _ <- forkIO (threadDelay (n * 1000) >> body)-     return ()---- | Cancel a delayed action.-cancel :: Timer -> IO ()-cancel (Timer wid) =-  do mb <- deRefWeak wid-     case mb of-       Just tid -> killThread tid-       Nothing  -> return ()--expired :: Timer -> IO Bool-expired (Timer wid) =-  do mb <- deRefWeak wid-     case mb of-       Just tid -> do status <- threadStatus tid-                      case status of-                        ThreadRunning   -> return False-                        ThreadBlocked _ -> return False-                        _               -> return True--       Nothing  -> return True
+ src/Hans/Types.hs view
@@ -0,0 +1,105 @@+{-# LANGUAGE RecordWildCards #-}++module Hans.Types (+    module Hans.Types,+    module Exports+  ) where++import Hans.Config (Config,HasConfig(..))+import Hans.Device.Types (Device)+import Hans.IP4.ArpTable as Exports (ArpTable)+import Hans.IP4.Packet+import Hans.IP4.State as Exports+import Hans.IP4.RoutingTable as Exports (RoutingTable)+import Hans.Lens+import Hans.Nat.State as Exports+import Hans.Tcp.State as Exports+import Hans.Udp.State as Exports++import           Control.Concurrent (ThreadId)+import           Control.Concurrent.BoundedChan (BoundedChan)+import qualified Data.ByteString as S+import           Data.IORef (IORef,atomicModifyIORef',readIORef)+++data InputPacket = FromDevice !Device !S.ByteString+                 | FromIP4 !Device !IP4Header !S.ByteString+++data NetworkStack = NetworkStack { nsConfig :: !Config+                                   -- ^ The configuration for this instance of+                                   -- the network stack.++                                 , nsNat :: !NatState+                                   -- ^ Nat table/config++                                 , nsInput :: !(BoundedChan InputPacket)+                                   -- ^ The input packet queue++                                 , nsDevices :: {-# UNPACK #-} !(IORef [Device])+                                   -- ^ All registered devices++                                 , nsIP4State :: !IP4State+                                   -- ^ State for IP4 processing++                                 , nsIP4Responder :: !ThreadId+                                   -- ^ Internal IP4 responder for messages+                                   -- produced in teh fast path.++                                 , nsUdpState :: !UdpState+                                   -- ^ State for UDP processing++                                 , nsUdpResponder :: !ThreadId+                                   -- ^ Responder thread for UDP messages+                                   -- produced in the fast path.++                                 , nsTcpState :: !TcpState+                                   -- ^ State for TCP processing++                                 , nsTcpResponder :: !ThreadId+                                   -- ^ Responder thread for TCP messages+                                   -- produced in the fast path.++                                 , nsTcpTimers :: !ThreadId+                                   -- ^ The TCP timer thread++                                 , nsNameServers4 :: !(IORef [IP4])+                                 }++instance HasConfig NetworkStack where+  config = to nsConfig+  {-# INLINE config #-}++instance HasIP4State NetworkStack where+  ip4State = to nsIP4State+  {-# INLINE ip4State #-}++instance HasUdpState NetworkStack where+  udpState = to nsUdpState+  {-# INLINE udpState #-}++instance HasTcpState NetworkStack where+  tcpState = to nsTcpState+  {-# INLINE tcpState #-}++instance HasNatState NetworkStack where+  natState = to nsNat+  {-# INLINE natState #-}++++class HasNetworkStack ns where+  networkStack :: Getting r ns NetworkStack++instance HasNetworkStack NetworkStack where+  networkStack = id+  {-# INLINE networkStack #-}+++addNameServer4 :: HasNetworkStack ns => ns -> IP4 -> IO ()+addNameServer4 ns addr =+  atomicModifyIORef' (nsNameServers4 (view networkStack ns))+      (\addrs -> (addr:addrs,()))++getNameServers4 :: HasNetworkStack ns => ns -> IO [IP4]+getNameServers4 ns = readIORef (nsNameServers4 (view networkStack ns))
+ src/Hans/Udp/Input.hs view
@@ -0,0 +1,64 @@+{-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE RecordWildCards #-}++module Hans.Udp.Input (+    processUdp+  ) where++import           Hans.Addr (Addr,toAddr)+import qualified Hans.Buffer.Datagram as DG+import           Hans.Checksum (finalizeChecksum,extendChecksum)+import           Hans.Device (Device(..),ChecksumOffload(..),rxOffload)+import           Hans.IP4.Packet (IP4)+import           Hans.Lens (view)+import           Hans.Monad (Hans,decode',dropPacket,io)+import           Hans.Nat.Forward (tryForwardUdp)+import           Hans.Network+import           Hans.Udp.Output (queueUdp)+import           Hans.Udp.Packet+import           Hans.Types++import           Control.Monad (unless)+import qualified Data.ByteString as S+import qualified Data.ByteString.Lazy as L+++{-# SPECIALIZE processUdp :: NetworkStack -> Device -> IP4 -> IP4+                          -> S.ByteString -> Hans Bool #-}++-- | Process a message destined for the UDP layer. When the message cannot be+-- routed, 'False' is returned.+processUdp :: Network addr+           => NetworkStack -> Device -> addr -> addr -> S.ByteString -> Hans Bool+processUdp ns dev src dst bytes =+  do let checksum = finalizeChecksum $ extendChecksum bytes+                                     $ pseudoHeader src dst PROT_UDP+                                     $ S.length bytes++     unless (coUdp (view rxOffload dev) || checksum == 0)+         (dropPacket (devStats dev))++     ((hdr,payloadLen),payload) <- decode' (devStats dev) getUdpHeader bytes++     let local  = toAddr dst+         remote = toAddr src++     -- attempt to find a destination for this packet+     io (routeMsg ns dev local remote hdr (S.take payloadLen payload))++routeMsg :: NetworkStack -> Device -> Addr -> Addr -> UdpHeader -> S.ByteString -> IO Bool+routeMsg ns dev local remote hdr payload =+  do mb <- lookupRecv ns remote (udpDestPort hdr)+     case mb of++       -- XXX: which stat should increment when writeChunk fails?+       Just buf ->+         do _ <- DG.writeChunk buf (dev,remote,udpSourcePort hdr,local,udpDestPort hdr) payload+            return True++       -- Check to see if there's a forwarding rule to use+       Nothing ->+         do mbFwd <- tryForwardUdp ns local remote hdr+            case mbFwd of+              Just (ri,dst',hdr') -> queueUdp ns ri dst' hdr' (L.fromStrict payload)+              Nothing             -> return False
+ src/Hans/Udp/Output.hs view
@@ -0,0 +1,101 @@+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE PatternSynonyms #-}++module Hans.Udp.Output (+    primSendUdp,++    -- ** Fast-path Output+    responder,+    queueUdp,+  ) where++import Hans.Addr.Types (Addr)+import Hans.Checksum (finalizeChecksum,extendChecksum)+import Hans.Device.Types (ChecksumOffload(..),txOffload)+import Hans.Lens (view)+import Hans.Network+import Hans.Serialize (runPutPacket)+import Hans.Udp.Packet+import Hans.Types++import qualified Control.Concurrent.BoundedChan as BC+import           Control.Monad (forever)+import qualified Data.ByteString.Lazy as L+import           Data.Serialize (putWord16be)+++-- Fast-path Output ------------------------------------------------------------++responder :: NetworkStack -> IO ()+responder ns = forever $+  do msg <- BC.readChan chan+     case msg of+       SendDatagram ri dst hdr body ->+         do _ <- sendUdp ns ri dst hdr body+            return ()++  where+  chan = view udpQueue ns++queueUdp :: NetworkStack -> RouteInfo Addr -> Addr -> UdpHeader -> L.ByteString+         -> IO Bool+queueUdp ns ri dst hdr body+    -- XXX should this record an error?+  | L.length body > 65527 = return False+  | otherwise             = BC.tryWriteChan (view udpQueue ns)+                         $! SendDatagram ri dst hdr body+++-- Output ----------------------------------------------------------------------++-- | Send Udp over IP4 with a pre-computed route.+primSendUdp :: Network addr+            => NetworkStack+            -> RouteInfo addr+            -> addr         -- ^ Destination addr+            -> UdpPort      -- ^ Source port+            -> UdpPort      -- ^ Destination port+            -> L.ByteString -- ^ Payload+            -> IO Bool+primSendUdp ns ri dst udpSourcePort udpDestPort payload+    -- XXX should this record an error?+  | L.length payload > 65527 = return False+  | otherwise                = sendUdp ns ri dst UdpHeader { udpChecksum = 0, .. } payload+++sendUdp :: Network addr+        => NetworkStack+        -> RouteInfo addr -> addr+        -> UdpHeader -> L.ByteString+        -> IO Bool+sendUdp ns ri dst hdr payload =+  do let bytes = renderUdpPacket (view txOffload ri)+                     (riSource ri) dst hdr payload++     sendDatagram ns ri dst False PROT_UDP bytes++     return True+++-- | Given a way to make the pseudo header, render the UDP packet.+renderUdpPacket :: Network addr+                => ChecksumOffload -> addr -> addr -> UdpHeader -> L.ByteString+                -> L.ByteString+renderUdpPacket ChecksumOffload { .. } src dst hdr body+  | coUdp     = bytes+  | otherwise = beforeCS `L.append` withCS++  where++  pktlen = fromIntegral (L.length body)+  bytes  = runPutPacket udpHeaderSize 0 body (putUdpHeader hdr pktlen)++  udplen = udpHeaderSize + pktlen+  cs     = finalizeChecksum+         $ extendChecksum bytes+         $ pseudoHeader src dst PROT_UDP udplen++  beforeCS = L.take (fromIntegral udpHeaderSize - 2) bytes+  afterCS  = L.drop (fromIntegral udpHeaderSize    ) bytes++  withCS   = runPutPacket 2 0 afterCS (putWord16be cs)
+ src/Hans/Udp/Packet.hs view
@@ -0,0 +1,53 @@+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE PatternSynonyms #-}++module Hans.Udp.Packet where++import Data.Serialize (Get,getWord16be,label,Putter,Put,putWord16be)+import Data.Word (Word16)+++-- Udp Ports -------------------------------------------------------------------++type UdpPort = Word16++getUdpPort :: Get UdpPort+getUdpPort  = getWord16be+{-# INLINE getUdpPort #-}++putUdpPort :: Putter UdpPort+putUdpPort  = putWord16be+{-# INLINE putUdpPort #-}+++-- Udp Header ------------------------------------------------------------------++data UdpHeader = UdpHeader { udpSourcePort :: {-# UNPACK #-} !UdpPort+                           , udpDestPort   :: {-# UNPACK #-} !UdpPort+                           , udpChecksum   :: {-# UNPACK #-} !Word16+                           } deriving (Eq,Show)++emptyUdpHeader :: UdpHeader+emptyUdpHeader  = UdpHeader { udpSourcePort = 0+                            , udpDestPort   = 0+                            , udpChecksum   = 0 }++udpHeaderSize :: Int+udpHeaderSize  = 8++-- | Parse out a @UdpHeader@, and the size of the payload.+getUdpHeader :: Get (UdpHeader,Int)+getUdpHeader  = label "UDP Header" $+  do udpSourcePort <- getUdpPort+     udpDestPort   <- getUdpPort+     len           <- getWord16be+     udpChecksum   <- getWord16be+     return (UdpHeader { .. },fromIntegral len - udpHeaderSize)++-- | Render a @UdpHeader@.+putUdpHeader :: UdpHeader -> Int -> Put+putUdpHeader UdpHeader { .. } bodyLen =+  do putUdpPort  udpSourcePort+     putUdpPort  udpDestPort+     putWord16be (fromIntegral (bodyLen + udpHeaderSize))+     putWord16be 0
+ src/Hans/Udp/State.hs view
@@ -0,0 +1,133 @@+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE DeriveGeneric #-}++module Hans.Udp.State (+    UdpState(..), newUdpState,+    HasUdpState(..),+    UdpBuffer,+    lookupRecv,+    registerRecv,+    nextUdpPort,++    -- ** Fast-path Resonder+    UdpResponderRequest(..),+    udpQueue,+  ) where++import           Hans.Addr (NetworkAddr(..),Addr)+import qualified Hans.Buffer.Datagram as DG+import           Hans.Config+import           Hans.Device.Types (Device)+import qualified Hans.HashTable as HT+import           Hans.Lens+import           Hans.Network.Types (RouteInfo)+import           Hans.Udp.Packet (UdpPort,UdpHeader)++import           Control.Concurrent (MVar,newMVar,modifyMVar)+import qualified Control.Concurrent.BoundedChan as BC+import qualified Data.ByteString.Lazy as L+import           Data.Hashable (Hashable)+import           GHC.Generics (Generic)+++data Key = Key !Addr !UdpPort+           deriving (Eq,Show,Generic)++instance Hashable Key+++type UdpBuffer = DG.Buffer (Device,Addr,UdpPort,Addr,UdpPort)++data UdpState = UdpState { udpRecv  :: !(HT.HashTable Key UdpBuffer)+                         , udpPorts :: !(MVar UdpPort)+                         , udpQueue_:: !(BC.BoundedChan UdpResponderRequest)+                         }++data UdpResponderRequest = SendDatagram !(RouteInfo Addr) !Addr !UdpHeader !L.ByteString+++newUdpState :: Config -> IO UdpState+newUdpState Config { .. } =+  do udpRecv  <- HT.newHashTable cfgUdpSocketTableSize+     udpPorts <- newMVar 32767+     udpQueue_<- BC.newBoundedChan 128+     return $! UdpState { .. }+++class HasUdpState udp where+  udpState :: Getting r udp UdpState++instance HasUdpState UdpState where+  udpState = id+  {-# INLINE udpState #-}++udpQueue :: HasUdpState state => Getting r state (BC.BoundedChan UdpResponderRequest)+udpQueue  = udpState . to udpQueue_++lookupRecv :: HasUdpState state+           => state -> Addr -> UdpPort -> IO (Maybe UdpBuffer)+lookupRecv state addr dstPort =+  do mb <- HT.lookup (Key addr dstPort) (udpRecv (view udpState state))+     case mb of++       -- there was a receiver waiting on this address and port+       Just _  -> return mb++       -- try the generic receiver for that port+       Nothing -> do+         mb' <- HT.lookup (Key (wildcardAddr addr) dstPort)+                          (udpRecv (view udpState state))+         return mb'+++-- | Register a listener for messages to this address and port, returning 'Just'+-- an action to unregister the listener on success.+registerRecv :: HasUdpState state+             => state -> Addr -> UdpPort -> UdpBuffer -> IO (Maybe (IO ()))+registerRecv state addr srcPort buf =+  do registered <- HT.alter update key table+     if registered+        then return (Just (HT.delete key table))+        else return Nothing+  where+  table = udpRecv (view udpState state)++  key = Key addr srcPort++  update mb@Just{} = (mb,False)+  update Nothing   = (Just buf,True)+++-- Port Management -------------------------------------------------------------++nextUdpPort :: HasUdpState state => state -> Addr -> IO (Maybe UdpPort)+nextUdpPort state addr =+  modifyMVar udpPorts (pickFreshPort udpRecv addr)+  where+  UdpState { .. } = view udpState state++pickFreshPort :: HT.HashTable Key UdpBuffer -> Addr -> UdpPort+              -> IO (UdpPort, Maybe UdpPort)+pickFreshPort ht addr p0 = go 0 p0+  where++  mkKey1 = Key addr+  mkKey2 = Key (wildcardAddr addr)++  check+    | isWildcardAddr addr = \port -> HT.hasKey (mkKey1 port) ht+    | otherwise           = \port ->+      do used <- HT.hasKey (mkKey1 port) ht+         if not used+            then HT.hasKey (mkKey2 port) ht+            else return True++  go :: Int -> UdpPort -> IO (UdpPort,Maybe UdpPort)+  go i _ | i > 65535 = return (p0, Nothing)+  go i 0             = go (i+1) 1025+  go i port          =+    do used <- check port+       if not used+          then return (port, Just port)+          else go (i + 1) (port + 1)
− src/Hans/Utils.hs
@@ -1,39 +0,0 @@-module Hans.Utils where--import Control.Monad (MonadPlus(mzero))-import Numeric (showHex)-import qualified Data.ByteString.Lazy as L-import qualified Data.ByteString      as S--type DeviceName = String---- | Pseudo headers are constructed strictly.-type MkPseudoHeader = Int -> S.ByteString--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, Show 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]---- | Make a lazy bytestring from a strict one.-chunk :: S.ByteString -> L.ByteString-chunk bs = L.fromChunks [bs]--strict :: L.ByteString -> S.ByteString-strict  = S.concat . L.toChunks
− src/Hans/Utils/Checksum.hs
@@ -1,109 +0,0 @@-{-# 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-       , finalizeChecksum-       , computePartialChecksum-       , computePartialChecksumLazy-       , clearChecksum-       , pokeChecksum-       , emptyPartialChecksum-       )- where--import Control.Exception (assert)-import Data.Bits (Bits(shiftL,shiftR,complement,clearBit,(.&.),rotate))-import Data.List (foldl')-import Data.Word (Word8,Word16,Word32)-import Foreign.Storable (pokeByteOff)-import qualified Data.ByteString.Lazy   as L-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--data PartialChecksum = PartialChecksum-  { pcAccum :: !Word32-  , pcCarry :: Maybe Word8-  }--emptyPartialChecksum :: PartialChecksum-emptyPartialChecksum  = PartialChecksum-  { pcAccum = 0-  , pcCarry = Nothing-  }--finalizeChecksum :: PartialChecksum -> Word16-finalizeChecksum pc = complement (fromIntegral (fold32 (fold32 result)))-  where-  result = case pcCarry pc of-    Nothing   -> pcAccum pc-    Just prev -> step (pcAccum pc) prev 0---- | Compute the final checksum, using the given initial value.-computeChecksum :: Word32 -> S.ByteString -> Word16-computeChecksum c0 = finalizeChecksum . computePartialChecksum PartialChecksum-  { pcAccum = c0-  , pcCarry = Nothing-  }---- | Compute the checksum of a lazy bytestring.-computePartialChecksumLazy :: PartialChecksum -> L.ByteString -> PartialChecksum-computePartialChecksumLazy c0 = foldl' computePartialChecksum c0 . L.toChunks---- | Compute a partial checksum, yielding a value suitable to be passed to--- computeChecksum.-computePartialChecksum :: PartialChecksum -> S.ByteString -> PartialChecksum-computePartialChecksum pc b-  | S.null b  = pc-  | otherwise = case pcCarry pc of-      Nothing   -> result-      Just prev -> computePartialChecksum (pc-        { pcCarry = Nothing-        , pcAccum = step (pcAccum pc) prev (S.unsafeIndex b 0)-        }) (S.tail b)-  where--  !n' = S.length b--  result = PartialChecksum-    { pcAccum = loop (fromIntegral (pcAccum pc)) 0-    , pcCarry = carry-    }--  carry-    | odd n'    = Just (S.unsafeIndex b (n' - 1))-    | otherwise = Nothing--  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)
− tcp-test-client/Main.hs
@@ -1,36 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Main where--import           Control.Concurrent (forkIO,killThread,threadDelay)-import qualified Control.Exception as X-import           Control.Monad (replicateM,when)-import qualified Data.ByteString.Char8 as S-import qualified Data.Foldable as F-import           Network (withSocketsDo,connectTo,PortID(..))-import           System.Environment (getArgs)-import           System.IO (hPutStrLn,hGetLine,hClose)--main = withSocketsDo $-  do [addr]  <- getArgs--     X.bracket (replicateM 100 (forkClient addr))-               (mapM_ killThread) $ \ _ ->--       do _ <- getLine-          return ()--forkClient addr =-  do tid <- forkIO (createClient addr)-     threadDelay (100 * 1000)-     return tid--createClient addr =-  X.bracket (connectTo addr (PortNumber 9001)) hClose $ \ conn ->-    F.forM_ (cycle ["foo", "bar", "baz", "ASDF"]) $ \ str ->-      do S.hPutStrLn conn str-         str' <- S.hGetLine conn-         when (str /= str') $-           do putStrLn "Invalid echo server response:"-              putStr   "  expected: " >> S.putStrLn str-              putStr   "  received: " >> S.putStrLn str'
− tcp-test/Main.hs
@@ -1,111 +0,0 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE OverloadedStrings #-}-module Main where--import Hans.Address-import Hans.DhcpClient-import Hans.Address.Mac-import Hans.Address.IP4-import Hans.NetworkStack--#ifdef HaLVM_HOST_OS-import Hans.Device.Xen-import Hypervisor.Console-import Hypervisor.XenStore-import XenDevice.NIC-#else-import Hans.Device.Tap-#endif--import Control.Concurrent (threadDelay,forkIO)--import Control.Monad (forever)-import System.Environment (getArgs)-import qualified Data.ByteString.Lazy as L-import qualified Control.Exception as X--localAddr :: IP4-localAddr  = IP4 192 168 90 2--main :: IO ()-main  = do--  ns  <- newNetworkStack-  mac <- initEthernetDevice ns-  deviceUp ns mac-  putStrLn "Network stack running..."--  args <- getArgs-  if args == ["dhcp"]-     then do putStrLn "Discovering address"-             mbIP <- dhcpDiscover ns mac-             case mbIP of-               Nothing -> putStrLn "Couldn't get an IP address."-               Just ip -> do-                 putStrLn ("Bound to address: " ++ show ip)--                 -- putStrLn "Looking up galois.com..."-                 -- HostEntry { .. } <- getHostByName ns "galois.com"-                 -- print hostAddresses---     else do setAddress mac ns--  server ns--server :: NetworkStack -> IO ()-server ns = do-  sock <- listen ns localAddr 9001--  forever $ do-    putStrLn "Waiting..."-    conn <- accept sock-    _    <- forkIO (handleClient conn)-    return ()--handleClient :: Socket -> IO ()-handleClient conn =-  do putStrLn ("Got one: " ++ show (sockRemoteHost conn))-     loop `X.catch` \se -> do print (se :: X.SomeException)-                              close conn-  where-  loop =-    do buf <- recvBytes conn 512-       if L.null buf-          then    putStrLn "Client closed connection"-          else do _ <- sendBytes conn buf-                  loop--message :: L.ByteString-message  = "Hello, world\n"--sleep :: Int -> IO ()-sleep s = threadDelay (s * 1000 * 1000)--setAddress :: Mac -> NetworkStack -> IO ()-setAddress mac ns = do-  addIP4Addr ns (localAddr `withMask` 24) mac 1500-  routeVia ns (IP4 0 0 0 0 `withMask` 0) (IP4 192 168 90 1)--#ifdef HaLVM_HOST_OS-initEthernetDevice :: NetworkStack -> IO Mac-initEthernetDevice ns =-  do xs <- initXenStore-     _ <- initXenConsole -- should set up putStrLn-     nics <- listNICs xs-     case nics of-       [] -> fail "No NICs found to use!"-       (macstr:_) ->-         do let mac = read macstr-            nic <- openNIC xs macstr-            addDevice ns mac (xenSend nic) (xenReceiveLoop nic)-            return mac-#else-initEthernetDevice :: NetworkStack -> IO Mac-initEthernetDevice ns = do-  let mac = Mac 0x52 0x54 0x00 0x12 0x34 0x56-  Just dev <- openTapDevice "tap6"-  addDevice ns mac (tapSend dev) (tapReceiveLoop dev)-  return mac-#endif
− tests/IP4.hs
@@ -1,11 +0,0 @@-module IP4 where--import IP4.Packet--import Test.Framework (Test,testGroup)---ip4Tests :: Test-ip4Tests  = testGroup "ip4"-  [ ip4PacketTests-  ]
− tests/IP4/Addr.hs
@@ -1,15 +0,0 @@-module IP4.Addr where--import Hans.Address.IP4--import Control.Applicative ((<$>),(<*>))-import System.Random ()-import Test.QuickCheck (Gen,arbitraryBoundedRandom)---arbitraryIP4 :: Gen IP4-arbitraryIP4  = IP4-            <$> arbitraryBoundedRandom-            <*> arbitraryBoundedRandom-            <*> arbitraryBoundedRandom-            <*> arbitraryBoundedRandom
− tests/IP4/Packet.hs
@@ -1,66 +0,0 @@-module IP4.Packet where--import IP4.Addr-import Utils--import Hans.Message.Ip4--import Control.Applicative ((<$>))-import Data.Word (Word16)-import System.Random ()-import Test.Framework (Test,testGroup)-import Test.Framework.Providers.QuickCheck2 (testProperty)-import Test.QuickCheck (Gen,arbitrary,arbitraryBoundedRandom,choose,forAll)----- Packet Generation -------------------------------------------------------------arbitraryIP4Header :: Gen IP4Header-arbitraryIP4Header  = do-  tos   <- arbitraryBoundedRandom-  ident <- arbitraryIdent-  df    <- arbitrary-  mf    <- arbitrary-  off   <- arbitraryFragmentOffset-  ttl   <- arbitraryBoundedRandom-  prot  <- arbitraryIP4Protocol-  src   <- arbitraryIP4-  dst   <- arbitraryIP4-  return emptyIP4Header-    { ip4TypeOfService  = tos-    , ip4DontFragment   = df-    , ip4MoreFragments  = mf-    , ip4FragmentOffset = off-    , ip4TimeToLive     = ttl-    , ip4Protocol       = prot-    , ip4SourceAddr     = src-    , ip4DestAddr       = dst-    }--arbitraryIdent :: Gen Ident-arbitraryIdent  = Ident <$> arbitraryBoundedRandom--arbitraryFragmentOffset :: Gen Word16-arbitraryFragmentOffset  = (8*) <$> choose (0,0x1fff)--arbitraryIP4Protocol :: Gen IP4Protocol-arbitraryIP4Protocol  = IP4Protocol <$> arbitraryBoundedRandom--arbitraryIP4PacketLen :: Gen Int-arbitraryIP4PacketLen  = choose (0,65535 - 20)----- Packet Tests ------------------------------------------------------------------ip4PacketTests :: Test-ip4PacketTests  = testGroup "packet parsing"-  [ testProperty "prop_headerRoundTrip" prop_headerRoundTrip-  ]--prop_headerRoundTrip = forAll arbitraryIP4PacketLen $ \ pktLen ->-  let parse = do-        (hdr,_,_) <- getIP4Packet-        return hdr-      render hdr = putIP4Header hdr pktLen-   in roundTrip arbitraryIP4Header parse render-
− tests/Icmp4.hs
@@ -1,11 +0,0 @@-module Icmp4 where--import Icmp4.Packet (icmp4PacketTests)--import Test.Framework (Test,testGroup)---icmp4Tests :: Test-icmp4Tests  = testGroup "icmp4"-  [ icmp4PacketTests-  ]
− tests/Icmp4/Packet.hs
@@ -1,139 +0,0 @@-module Icmp4.Packet where--import IP4.Addr-import Utils--import Hans.Message.Icmp4-import Hans.Message.Types (Lifetime(..))--import Control.Applicative ((<$>),(<*>),pure)-import System.Random ()-import Test.Framework (Test,testGroup)-import Test.Framework.Providers.QuickCheck2 (testProperty)-import Test.QuickCheck-    (Gen,oneof,arbitraryBoundedRandom,elements,listOf,listOf1,vectorOf)-import qualified Data.ByteString as S----- Packet Generation -------------------------------------------------------------arbitraryIcmp4Packet :: Gen Icmp4Packet-arbitraryIcmp4Packet  = oneof-  [ EchoReply <$> arbitraryIdentifier-              <*> arbitrarySequenceNumber-              <*> arbitraryEchoPayload-  , DestinationUnreachable <$> arbitraryDestinationUnreachableCode-                           <*> arbitraryPayload-  , SourceQuench <$> arbitraryPayload-  , Redirect <$> arbitraryRedirectCode-             <*> arbitraryIP4-             <*> arbitraryPayload-  , Echo <$> arbitraryIdentifier-         <*> arbitrarySequenceNumber-         <*> arbitraryEchoPayload-  , RouterAdvertisement <$> arbitraryLifetime-                        <*> listOf1 arbitraryRouterAddress-  , pure RouterSolicitation-  , TimeExceeded <$> arbitraryTimeExceededCode-                 <*> arbitraryPayload-  , ParameterProblem <$> arbitraryBoundedRandom-                     <*> arbitraryPayload-  , Timestamp <$> arbitraryIdentifier-              <*> arbitrarySequenceNumber-              <*> arbitraryBoundedRandom-              <*> arbitraryBoundedRandom-              <*> arbitraryBoundedRandom-  , TimestampReply <$> arbitraryIdentifier-                   <*> arbitrarySequenceNumber-                   <*> arbitraryBoundedRandom-                   <*> arbitraryBoundedRandom-                   <*> arbitraryBoundedRandom-  , Information <$> arbitraryIdentifier-                <*> arbitrarySequenceNumber-  , InformationReply <$> arbitraryIdentifier-                     <*> arbitrarySequenceNumber-  , TraceRoute <$> arbitraryTraceRouteCode-               <*> arbitraryIdentifier-               <*> arbitraryBoundedRandom-               <*> arbitraryBoundedRandom-               <*> arbitraryBoundedRandom-               <*> arbitraryBoundedRandom-  , AddressMask <$> arbitraryIdentifier-                <*> arbitrarySequenceNumber-  , AddressMaskReply <$> arbitraryIdentifier-                     <*> arbitrarySequenceNumber-                     <*> arbitraryBoundedRandom-  ]--arbitraryIdentifier :: Gen Identifier-arbitraryIdentifier  = Identifier <$> arbitraryBoundedRandom--arbitrarySequenceNumber :: Gen SequenceNumber-arbitrarySequenceNumber  = SequenceNumber <$> arbitraryBoundedRandom--arbitraryEchoPayload :: Gen S.ByteString-arbitraryEchoPayload  = S.pack <$> listOf arbitraryBoundedRandom--arbitraryPayload :: Gen S.ByteString-arbitraryPayload  = S.pack <$> vectorOf 28 arbitraryBoundedRandom--arbitraryDestinationUnreachableCode :: Gen DestinationUnreachableCode-arbitraryDestinationUnreachableCode  = elements-  [ NetUnreachable-  , HostUnreachable-  , ProtocolUnreachable-  , PortUnreachable-  , FragmentationUnreachable-  , SourceRouteFailed-  , DestinationNetworkUnknown-  , DestinationHostUnknown-  , SourceHostIsolatedError-  , AdministrativelyProhibited-  , HostAdministrativelyProhibited-  , NetworkUnreachableForTOS-  , HostUnreachableForTOS-  , CommunicationAdministrativelyProhibited-  , HostPrecedenceViolation-  , PrecedenceCutoffInEffect-  ]--arbitraryRedirectCode :: Gen RedirectCode-arbitraryRedirectCode  = elements-  [ RedirectForNetwork-  , RedirectForHost-  , RedirectForTypeOfServiceAndNetwork-  , RedirectForTypeOfServiceAndHost-  ]--arbitraryLifetime :: Gen Lifetime-arbitraryLifetime  = Lifetime <$> arbitraryBoundedRandom--arbitraryRouterAddress :: Gen RouterAddress-arbitraryRouterAddress  = RouterAddress-                      <$> arbitraryIP4-                      <*> arbitraryPreferenceLevel--arbitraryPreferenceLevel :: Gen PreferenceLevel-arbitraryPreferenceLevel  = PreferenceLevel <$> arbitraryBoundedRandom--arbitraryTimeExceededCode :: Gen TimeExceededCode-arbitraryTimeExceededCode  = elements-  [ TimeToLiveExceededInTransit-  , FragmentReassemblyTimeExceeded-  ]--arbitraryTraceRouteCode :: Gen TraceRouteCode-arbitraryTraceRouteCode  = elements-  [ TraceRouteForwarded-  , TraceRouteDiscarded-  ]---- Packet Tests ------------------------------------------------------------------icmp4PacketTests :: Test-icmp4PacketTests  = testGroup "packet parsing"-  [ testProperty "roundTrip" prop_roundTrip-  ]--prop_roundTrip =-  roundTrip arbitraryIcmp4Packet getIcmp4Packet putIcmp4Packet
tests/Main.hs view
@@ -1,16 +1,18 @@ module Main where -import Icmp4 (icmp4Tests)-import IP4 (ip4Tests)-import Tcp (tcpTests)-import Udp (udpTests)+import Tests.Checksum+import Tests.Ethernet+import Tests.IP4 -import Test.Framework (defaultMain)+import Test.Tasty+import Test.Tasty.Runners (consoleTestReporter)+import Test.Tasty.Runners.AntXML (antXMLRunner)  -main = defaultMain-  [ tcpTests-  , udpTests-  , icmp4Tests-  , ip4Tests-  ]+main :: IO ()+main  = defaultMainWithIngredients [antXMLRunner,consoleTestReporter] $+  testGroup "Properties"+    [ checksumTests+    , ethernetTests+    , ip4Tests+    ]
− tests/Tcp.hs
@@ -1,14 +0,0 @@-module Tcp (-    tcpTests-  ) where--import Tcp.Packet-import Tcp.Window--import Test.Framework (Test,testGroup)--tcpTests :: Test-tcpTests  = testGroup "tcp"-  [ tcpPacketTests-  , tcpWindowTests-  ]
− tests/Tcp/Packet.hs
@@ -1,140 +0,0 @@-{-# OPTIONS_GHC -fno-warn-orphans #-}--module Tcp.Packet where--import Utils--import Hans.Message.Tcp-    (TcpHeader(..),emptyTcpHeader,TcpPort(..),TcpOption(..),SackBlock(..)-    ,getTcpHeader,putTcpHeader,getTcpOption,putTcpOption,tcpOptionsLength)--import Control.Applicative (pure,(<*>),(<$>))-import Control.Monad (replicateM)-import Data.Serialize (runGet,runPut)-import Data.Word (Word8)-import System.Random ()-import Test.Framework (Test,testGroup)-import Test.Framework.Providers.QuickCheck2 (testProperty)-import Test.QuickCheck-    (Gen,choose,arbitrarySizedIntegral,sized,arbitrary,listOf,oneof,forAll-    ,suchThat)-import Test.QuickCheck.Property (Result(..),failed,succeeded)-import qualified Data.ByteString as S-import qualified Data.Sequence as Seq----- Utilities ----------------------------------------------------------------------- | Generate a strict bytestring of nonzero length, that falls within the MSS--- off the HaNS tcp layer.-arbitraryPayload :: Gen S.ByteString-arbitraryPayload  = do-  len   <- choose (1,10)-  S.pack `fmap` replicateM len arbitrarySizedIntegral---- | Generate the starting point of a data packet stream.-arbitraryDataPacket :: Gen (TcpHeader,S.ByteString)-arbitraryDataPacket  = do-  body <- arbitraryPayload-  sn   <- arbitrarySizedIntegral-  an   <- arbitrarySizedIntegral-  let hdr = emptyTcpHeader-        { tcpSeqNum = sn-        , tcpAckNum = an-        , tcpAck    = True-        }-  return (hdr,body)---- | Increment the sequence number, and generate some new data.-nextDataPacket :: (TcpHeader,S.ByteString) -> Gen (TcpHeader,S.ByteString)-nextDataPacket (hdr,body) = do-  body' <- arbitraryPayload-  let hdr' = hdr { tcpSeqNum = tcpSeqNum hdr + fromIntegral (S.length body) }-  return (hdr',body')---- | Generate a sequence of headers and packet payloads.  This will yield a--- non-empty stream whose length depends on the size parameter.-packetStream :: Gen (Seq.Seq (TcpHeader,S.ByteString))-packetStream  = do-  pkt@(hdr,body) <- arbitraryDataPacket-  sized (loop Seq.empty pkt)-  where-  loop segs pkt len-    | len == 0  = return (segs Seq.|> pkt)-    | otherwise = do-      pkt' <- nextDataPacket pkt-      loop (segs Seq.|> pkt) pkt' (len - 1)---arbitraryTcpPort :: Gen TcpPort-arbitraryTcpPort  = TcpPort `fmap` arbitrarySizedIntegral---- | Generate a completely arbitrary header.  This may be a semantically--- incorrect header, and is mainly useful for testing the parser.-arbitraryTcpHeader :: Gen TcpHeader-arbitraryTcpHeader  = TcpHeader-                  <$> arbitraryTcpPort-                  <*> arbitraryTcpPort-                  <*> arbitrarySizedIntegral-                  <*> arbitrarySizedIntegral-                  <*> arbitrary-                  <*> arbitrary-                  <*> arbitrary-                  <*> arbitrary-                  <*> arbitrary-                  <*> arbitrary-                  <*> arbitrary-                  <*> arbitrary-                  <*> arbitrarySizedIntegral-                  <*> pure 0 -- checksum-                  <*> pure 0 -- urgent pointer-                  <*> arbitraryTcpOptions--arbitraryTcpOptions :: Gen [TcpOption]-arbitraryTcpOptions  = listOf arbitraryTcpOption `suchThat` (not . tooBig)-  where-  tooBig opts = fst (tcpOptionsLength opts) > 10--arbitraryTcpOption :: Gen TcpOption-arbitraryTcpOption  = oneof-  [ pure OptNoOption-  , OptMaxSegmentSize <$> arbitrarySizedIntegral-  , OptWindowScaling  <$> arbitrarySizedIntegral-  , pure OptSackPermitted-  , do len <- choose (0,10)-       OptSack <$> replicateM len arbitrarySackBlock-  , OptTimestamp      <$> arbitrarySizedIntegral-                      <*> arbitrarySizedIntegral-  , do code  <- unusedTcpOptionNumber-       len   <- choose (0, 20)-       bytes <- replicateM (fromIntegral len) arbitrarySizedIntegral-       return (OptUnknown code (len + 2) (S.pack bytes))-  ]---- | Choose an unused tcp option number.-unusedTcpOptionNumber :: Gen Word8-unusedTcpOptionNumber  =-  arbitrarySizedIntegral `suchThat` (not . (`elem` avoid))-  where-  -- it would be nice if this could could be generated from TcpOptionTag-  avoid = [0, 1, 2, 3, 4, 5, 8]--arbitrarySackBlock :: Gen SackBlock-arbitrarySackBlock  = SackBlock-                  <$> arbitrarySizedIntegral-                  <*> arbitrarySizedIntegral----- Properties --------------------------------------------------------------------tcpPacketTests :: Test-tcpPacketTests  = testGroup "packet parsing"-  [ testProperty "prop_headerRoundTrip" prop_headerRoundTrip-  , testProperty "prop_optionRoundTrip" prop_optionRoundTrip-  ]--prop_headerRoundTrip =-  roundTrip arbitraryTcpHeader (fst <$> getTcpHeader) putTcpHeader--prop_optionRoundTrip =-  roundTrip arbitraryTcpOption getTcpOption putTcpOption
− tests/Tcp/Window.hs
@@ -1,61 +0,0 @@-module Tcp.Window where--import Tcp.Packet--import Hans.Layer.Tcp.Window-    (emptyLocalWindow,addInSegment,stepWindow,InSegment(..)-    ,localWindowSackBlocks)-import Hans.Message.Tcp (TcpHeader(..))--import Control.Arrow ((&&&))-import Test.Framework (Test,testGroup)-import Test.QuickCheck (forAll)-import Test.Framework.Providers.QuickCheck2 (testProperty)-import qualified Data.ByteString as S-import qualified Data.Foldable as F-import qualified Data.Sequence as Seq---tcpWindowTests :: Test-tcpWindowTests  = testGroup "tcp window"-  [ testProperty "prop_localOrdered" prop_localOrdered-  , testProperty "prop_localRandom"  prop_localRandom-  , testProperty "prop_sackOrdered"  prop_sackOrdered-  ]----- Local Window ------------------------------------------------------------------fromInSegment :: InSegment -> (TcpHeader,S.ByteString)-fromInSegment  = inHeader &&& inBody---- | Check that if a stream of packets goes into the @LocalWindow@ in order,--- that they will come out in the same order.-prop_localOrdered = forAll packetStream $ \ segs ->-  let (hdr,_)         = Seq.index segs 0-      win             = emptyLocalWindow (tcpSeqNum hdr) 14600 0-      step w (h,body) = addInSegment h body w-      win'            = F.foldl step win segs-      (segs',_)       = stepWindow win'-   in segs == fmap fromInSegment segs'---- | Check that if a stream of initially ordered packets goes into the--- @LocalWindow@ in a random order, that they come out ordered again.  In this--- case, we just reverse the stream, as that should be the degenrate case for--- incoming packets.-prop_localRandom = forAll packetStream $ \ segs ->-  let (hdr,_)         = Seq.index segs 0-      win             = emptyLocalWindow (tcpSeqNum hdr) 14600 0-      step (h,body) w = addInSegment h body w-      win'            = F.foldr step win segs-      (segs',_)       = stepWindow win'-   in segs == fmap fromInSegment segs'---- | Generating a sequence of sack blocks from a window constructed of in-order--- packets should generate a single sack block.-prop_sackOrdered = forAll packetStream $ \ segs ->-  let (hdr,_)         = Seq.index segs 0-      win             = emptyLocalWindow (tcpSeqNum hdr) 14600 0-      step w (h,body) = addInSegment h body w-      win'            = F.foldl step win segs-   in Seq.length (localWindowSackBlocks win') == 1
+ tests/Tests/Checksum.hs view
@@ -0,0 +1,20 @@+module Tests.Checksum where++import Hans.Checksum (computeChecksum)++import Data.Serialize (runPut,putWord32be,putWord16be)+import Test.Tasty+import Test.Tasty.QuickCheck (testProperty)+import Test.QuickCheck+++checksumTests :: TestTree+checksumTests  = testGroup "Checksum"+  [ testProperty "Word16"         $+    forAll arbitraryBoundedRandom $ \ w16 ->+      computeChecksum (runPut (putWord16be w16)) === computeChecksum w16++  , testProperty "Word32"         $+    forAll arbitraryBoundedRandom $ \ w32 ->+      computeChecksum (runPut (putWord32be w32)) === computeChecksum w32+  ]
+ tests/Tests/Ethernet.hs view
@@ -0,0 +1,44 @@+{-# LANGUAGE RecordWildCards #-}++module Tests.Ethernet where++import Tests.Utils (encodeDecodeIdentity,showReadIdentity)++import Hans.Ethernet++import Test.QuickCheck+import Test.Tasty+import Test.Tasty.QuickCheck (testProperty)+++arbitraryMac :: Gen Mac+arbitraryMac  =+  do a <- arbitraryBoundedIntegral+     b <- arbitraryBoundedIntegral+     c <- arbitraryBoundedIntegral+     d <- arbitraryBoundedIntegral+     e <- arbitraryBoundedIntegral+     f <- arbitraryBoundedIntegral+     return (Mac a b c d e f)++arbitrayEthernetHeader :: Gen EthernetHeader+arbitrayEthernetHeader  =+  do eSource <- arbitraryMac+     eDest   <- arbitraryMac+     eType   <- arbitraryBoundedRandom+     return EthernetHeader { .. }+++-- Ethernet Properties ---------------------------------------------------------++ethernetTests :: TestTree+ethernetTests  = testGroup "Ethernet"+  [ testProperty "Mac Address encode/decode" $+    encodeDecodeIdentity putMac getMac arbitraryMac++  , testProperty "Header encode/decode" $+    encodeDecodeIdentity putEthernetHeader getEthernetHeader arbitrayEthernetHeader++  , testProperty "Mac Address read/show" $+    showReadIdentity showMac readMac arbitraryMac+  ]
+ tests/Tests/IP4.hs view
@@ -0,0 +1,15 @@+module Tests.IP4 (ip4Tests) where++import Tests.IP4.Fragmentation (fragTests)+import Tests.IP4.Icmp4 (icmp4Tests)+import Tests.IP4.Packet (packetTests)++import Test.Tasty+++ip4Tests :: TestTree+ip4Tests  = testGroup "IP4"+  [ packetTests+  , fragTests+  , icmp4Tests+  ]
+ tests/Tests/IP4/Fragmentation.hs view
@@ -0,0 +1,58 @@+module Tests.IP4.Fragmentation where++import Tests.IP4.Packet+import Tests.Network++import Hans.Config+import Hans.IP4.Fragments+import Hans.IP4.Packet+import Hans.Monad (runHansOnce)++import qualified Data.ByteString.Lazy as L+import           Data.Maybe (catMaybes)+import           Test.Tasty+import           Test.Tasty.QuickCheck (testProperty)+import           Test.QuickCheck+import           Test.QuickCheck.Monadic+++fragTests :: TestTree+fragTests  = testGroup "Fragmentation"+  [ testProperty "Reassembly" propReassemble+  ]+++propReassemble :: Property+propReassemble  = monadicIO $+  do src   <- pick arbitraryIP4+     dst   <- pick arbitraryIP4+     prot  <- pick arbitraryProtocol+     ident <- pick arbitraryIdent++     len   <- pick (choose (100,1500))++     -- path mtu must be at least 68 (28 for header, and 40 for options)+     mtu   <- pick (choose (68,len))+     bytes <- pick (arbitraryPayload len)++     let hdr = emptyIP4Header { ip4DestAddr   = dst+                              , ip4SourceAddr = src+                              , ip4Protocol   = prot+                              , ip4Ident      = ident+                              }++         chunks = [(h,L.toStrict body) | (h,body) <- splitPacket mtu hdr bytes]++     incoming <- pick (shuffle chunks)+     table    <- run (newFragTable defaultConfig)++     results <- run $ sequence [ runHansOnce (processFragment table fhdr body)+                               | (fhdr,body) <- incoming ]++     run (cleanupFragTable table)++     -- we should only get a single successful result here, otherwise something+     -- is wrong with the way that fragments are being collected+     case catMaybes results of+       [(_,result)] -> return (L.fromStrict result == bytes)+       _            -> return False
+ tests/Tests/IP4/Icmp4.hs view
@@ -0,0 +1,167 @@+module Tests.IP4.Icmp4 where++import Tests.IP4.Packet (arbitraryIP4)+import Tests.Utils++import Hans.IP4.Icmp4++import qualified Data.ByteString as S+import           Test.Tasty (TestTree,testGroup)+import           Test.Tasty.QuickCheck (testProperty)+import           Test.QuickCheck+                     (Gen,oneof,arbitraryBoundedRandom,elements,listOf,listOf1+                     ,vectorOf)+++-- Packet Generation -----------------------------------------------------------++arbitraryIcmp4Packet :: Gen Icmp4Packet+arbitraryIcmp4Packet  = oneof+  [ do a <- arbitraryIdentifier+       b <- arbitrarySequenceNumber+       c <- arbitraryEchoPayload+       return $! EchoReply a b c++  , do a <- arbitraryDestinationUnreachableCode+       b <- arbitraryPayload+       return $! DestinationUnreachable a b++  , do a <- arbitraryPayload+       return $! SourceQuench a++  , do a <- arbitraryRedirectCode+       b <- arbitraryIP4+       c <- arbitraryPayload+       return $! Redirect a b c++  , do a <- arbitraryIdentifier+       b <- arbitrarySequenceNumber+       c <- arbitraryEchoPayload+       return $! Echo a b c++  , do a <- arbitraryBoundedRandom+       b <- listOf1 arbitraryRouterAddress+       return $! RouterAdvertisement a b++  , return RouterSolicitation++  , do a <- arbitraryTimeExceededCode+       b <- arbitraryPayload+       return $! TimeExceeded a b++  , do a <- arbitraryBoundedRandom+       b <- arbitraryPayload+       return $! ParameterProblem a b++  , do a <- arbitraryIdentifier+       b <- arbitrarySequenceNumber+       c <- arbitraryBoundedRandom+       d <- arbitraryBoundedRandom+       e <- arbitraryBoundedRandom+       return $! Timestamp a b c d e++  , do a <- arbitraryIdentifier+       b <- arbitrarySequenceNumber+       c <- arbitraryBoundedRandom+       d <- arbitraryBoundedRandom+       e <- arbitraryBoundedRandom+       return $! TimestampReply a b c d e++  , do a <- arbitraryIdentifier+       b <- arbitrarySequenceNumber+       return $! Information a b++  , do a <- arbitraryIdentifier+       b <- arbitrarySequenceNumber+       return $! InformationReply a b++  , do a <- arbitraryTraceRouteCode+       b <- arbitraryIdentifier+       c <- arbitraryBoundedRandom+       d <- arbitraryBoundedRandom+       e <- arbitraryBoundedRandom+       f <- arbitraryBoundedRandom+       return $! TraceRoute a b c d e f++  , do a <- arbitraryIdentifier+       b <- arbitrarySequenceNumber+       return $! AddressMask a b++  , do a <- arbitraryIdentifier+       b <- arbitrarySequenceNumber+       c <- arbitraryBoundedRandom+       return $! AddressMaskReply a b c+  ]++arbitraryIdentifier :: Gen Identifier+arbitraryIdentifier  = arbitraryBoundedRandom++arbitrarySequenceNumber :: Gen SequenceNumber+arbitrarySequenceNumber  = arbitraryBoundedRandom++arbitraryEchoPayload :: Gen S.ByteString+arbitraryEchoPayload  =+  do bytes <- listOf arbitraryBoundedRandom+     return $! S.pack bytes++arbitraryPayload :: Gen S.ByteString+arbitraryPayload  =+  do bytes <- vectorOf 28 arbitraryBoundedRandom+     return $! S.pack bytes++arbitraryDestinationUnreachableCode :: Gen DestinationUnreachableCode+arbitraryDestinationUnreachableCode  = elements+  [ NetUnreachable+  , HostUnreachable+  , ProtocolUnreachable+  , PortUnreachable+  , FragmentationUnreachable+  , SourceRouteFailed+  , DestinationNetworkUnknown+  , DestinationHostUnknown+  , SourceHostIsolatedError+  , AdministrativelyProhibited+  , HostAdministrativelyProhibited+  , NetworkUnreachableForTOS+  , HostUnreachableForTOS+  , CommunicationAdministrativelyProhibited+  , HostPrecedenceViolation+  , PrecedenceCutoffInEffect+  ]++arbitraryRedirectCode :: Gen RedirectCode+arbitraryRedirectCode  = elements+  [ RedirectForNetwork+  , RedirectForHost+  , RedirectForTypeOfServiceAndNetwork+  , RedirectForTypeOfServiceAndHost+  ]++arbitraryRouterAddress :: Gen RouterAddress+arbitraryRouterAddress  =+  do a <- arbitraryIP4+     b <- arbitraryPreferenceLevel+     return $! RouterAddress a b++arbitraryPreferenceLevel :: Gen PreferenceLevel+arbitraryPreferenceLevel  = arbitraryBoundedRandom++arbitraryTimeExceededCode :: Gen TimeExceededCode+arbitraryTimeExceededCode  = elements+  [ TimeToLiveExceededInTransit+  , FragmentReassemblyTimeExceeded+  ]++arbitraryTraceRouteCode :: Gen TraceRouteCode+arbitraryTraceRouteCode  = elements+  [ TraceRouteForwarded+  , TraceRouteDiscarded+  ]++-- Packet Tests ----------------------------------------------------------------++icmp4Tests :: TestTree+icmp4Tests  = testGroup "Icmp4"+  [ testProperty "Packet encode/decode" $+    encodeDecodeIdentity putIcmp4Packet getIcmp4Packet arbitraryIcmp4Packet+  ]
+ tests/Tests/IP4/Packet.hs view
@@ -0,0 +1,133 @@+{-# LANGUAGE RecordWildCards #-}++module Tests.IP4.Packet where++import Tests.Ethernet (arbitraryMac)+import Tests.Network (arbitraryProtocol)+import Tests.Utils (encodeDecodeIdentity,showReadIdentity)++import Hans.IP4.Packet+import Hans.Lens++import qualified Data.ByteString.Lazy as L+import qualified Data.ByteString.Short as Sh+import           Data.Word (Word8)+import           Test.QuickCheck+import           Test.Tasty (testGroup,TestTree)+import           Test.Tasty.QuickCheck (testProperty)+++-- Packet Generator Support ----------------------------------------------------++arbitraryIP4 :: Gen IP4+arbitraryIP4  =+  do a <- arbitraryBoundedRandom+     b <- arbitraryBoundedRandom+     c <- arbitraryBoundedRandom+     d <- arbitraryBoundedRandom+     return $! packIP4 a b c d+++arbitraryIP4Mask :: Gen IP4Mask+arbitraryIP4Mask  =+  do addr <- arbitraryIP4+     bits <- choose (0,32)+     return (IP4Mask addr bits)+++arbitraryIdent :: Gen IP4Ident+arbitraryIdent  = arbitraryBoundedRandom+++arbitraryPayload :: Int -> Gen L.ByteString+arbitraryPayload len =+  do bytes <- vectorOf len arbitraryBoundedRandom+     return (L.pack bytes)++arbitraryOptionPayload :: Word8 -> Gen Sh.ShortByteString+arbitraryOptionPayload len =+  do bytes <- vectorOf (fromIntegral len) arbitraryBoundedRandom+     return (Sh.pack bytes)+++arbitraryIP4Header :: Gen IP4Header+arbitraryIP4Header  =+  do ip4TypeOfService <- arbitraryBoundedRandom+     ip4Ident         <- arbitraryIdent+     ip4TimeToLive    <- arbitraryBoundedRandom+     ip4Protocol      <- arbitraryProtocol+     ip4SourceAddr    <- arbitraryIP4+     ip4DestAddr      <- arbitraryIP4++     -- checksum processing is validated by a different property+     let ip4Checksum = 0++     -- XXX need to generate options that fit within the additional 40 bytes+     -- available+     let ip4Options = []++     -- set the members of the ip4Fragment_ field on the final header+     df  <- arbitraryBoundedRandom+     mf  <- arbitraryBoundedRandom+     off <- choose (0,0x1fff)+     let hdr = IP4Header { ip4Fragment_ = 0, .. }++     return $! set ip4DontFragment df+            $! set ip4MoreFragments mf+            $! set ip4FragmentOffset off hdr+++arbitraryIP4Option :: Gen IP4Option+arbitraryIP4Option  =+  do ip4OptionCopied  <- arbitraryBoundedRandom+     ip4OptionClass   <- choose (0,0x3)+     ip4OptionNum     <- choose (0,0x1f)++     ip4OptionData <-+       if ip4OptionNum < 2+          then return Sh.empty+          else do len <- choose (0, 0xff - 2)+                  arbitraryOptionPayload len++     return IP4Option { .. }+++arbitraryArpPacket :: Gen ArpPacket+arbitraryArpPacket  =+  do arpOper <- elements [ArpRequest,ArpReply]+     arpSHA  <- arbitraryMac+     arpSPA  <- arbitraryIP4+     arpTHA  <- arbitraryMac+     arpTPA  <- arbitraryIP4+     return ArpPacket { .. }+++-- Packet Properties -----------------------------------------------------------++packetTests :: TestTree+packetTests  = testGroup "Packet"+  [ testProperty "IP4 Address encode/decode" $+    encodeDecodeIdentity putIP4 getIP4 arbitraryIP4++  , let get =+          do (hdr,20,0) <- getIP4Packet+             return hdr++        put hdr =+             putIP4Header hdr 0++     in testProperty "Header encode/decode" $+        encodeDecodeIdentity put get arbitraryIP4Header++  , testProperty "Option encode/decode" $+    encodeDecodeIdentity putIP4Option getIP4Option arbitraryIP4Option++  , testProperty "Arp Message encode/decode" $+    encodeDecodeIdentity putArpPacket getArpPacket arbitraryArpPacket++  , testProperty "IP4 Addr read/show" $+    showReadIdentity showIP4 readIP4 arbitraryIP4++  , testProperty "IP4 Mask read/show" $+    showReadIdentity showIP4Mask readIP4Mask arbitraryIP4Mask+  ]
+ tests/Tests/Network.hs view
@@ -0,0 +1,9 @@+module Tests.Network where++import Hans.Network.Types++import Test.QuickCheck (Gen,arbitraryBoundedRandom)+++arbitraryProtocol :: Gen NetworkProtocol+arbitraryProtocol  = arbitraryBoundedRandom
+ tests/Tests/Utils.hs view
@@ -0,0 +1,20 @@+module Tests.Utils where++import Test.QuickCheck++import Data.Serialize (Putter,Get,runGet,runPut)+++encodeDecodeIdentity :: (Eq a, Show a) => Putter a -> Get a -> Gen a -> Property+encodeDecodeIdentity put get gen =+  forAll gen $ \ a ->+    case runGet get (runPut (put a)) of+      Right a'  -> a === a'+      Left str  -> property False++showReadIdentity :: (Eq a, Show a) => (a -> ShowS) -> ReadS a -> Gen a -> Property+showReadIdentity sw rd gen =+  forAll gen $ \ a ->+    case rd (sw a "") of+      [(a',_)] -> a === a'+      _        -> property False
− tests/Udp.hs
@@ -1,11 +0,0 @@-module Udp where--import Udp.Packet--import Test.Framework (Test,testGroup)---udpTests :: Test-udpTests  = testGroup "udp"-  [ udpPacketTests-  ]
− tests/Udp/Packet.hs
@@ -1,43 +0,0 @@-module Udp.Packet where--import Hans.Message.Udp-    (UdpHeader(..),UdpPort(..),parseUdpHeader,renderUdpHeader)--import Control.Applicative ((<$>),(<*>))-import Data.Serialize (runGet,runPut)-import System.Random (Random)-import Test.Framework (Test,testGroup)-import Test.Framework.Providers.QuickCheck2 (testProperty)-import Test.QuickCheck (Gen,forAll,arbitraryBoundedRandom,arbitrary,choose)-import Test.QuickCheck.Property (Result(..),failed,succeeded)----- Utilities ---------------------------------------------------------------------arbitraryUdpPacketSize :: (Random a, Num a) => Gen a-arbitraryUdpPacketSize  = choose (0,65535 - 8)--arbitraryUdpPort :: Gen UdpPort-arbitraryUdpPort  = UdpPort <$> arbitraryBoundedRandom--arbitraryUdpHeader :: Gen UdpHeader-arbitraryUdpHeader  = UdpHeader-                  <$> arbitraryUdpPort-                  <*> arbitraryUdpPort-                  <*> arbitraryBoundedRandom -- not necessarily right----- Properties --------------------------------------------------------------------udpPacketTests :: Test-udpPacketTests  = testGroup "packet parsing"-  [ testProperty "prop_headerRoundTrip" prop_headerRoundTrip-  ]--prop_headerRoundTrip = forAll packet $ \ (len,hdr) ->-  case runGet parseUdpHeader (runPut (renderUdpHeader hdr len)) of-    Right (hdr',len') | hdr == hdr' && len == len' -> succeeded-                      | otherwise                  -> failed-    Left err                                       -> failed { reason = err }-  where-  packet = (,) <$> arbitraryUdpPacketSize <*> arbitraryUdpHeader
− tests/Utils.hs
@@ -1,16 +0,0 @@-module Utils where--import Data.Serialize (runGet,runPut,Putter,Get)-import Test.Framework.Providers.QuickCheck2 (testProperty)-import Test.QuickCheck (Gen,forAll)-import Test.QuickCheck.Property (Property,Result(..),succeeded,failed)----- | Round trip something through cereal, making sure that rendered version--- parses to the same initial value.-roundTrip :: (Show a, Eq a) => Gen a -> Get a -> Putter a -> Property-roundTrip gen decode encode = forAll gen $ \ a ->-  case runGet decode (runPut (encode a)) of-    Right a' | a == a'   -> succeeded-             | otherwise -> failed-    Left err             -> failed { reason = err }
− web-server/Main.hs
@@ -1,191 +0,0 @@-{-# LANGUAGE MultiWayIf #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE CPP #-}--- Copyright 2014 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.-import Control.Concurrent-import Control.Exception-import Control.Monad-import qualified Data.ByteString.Lazy as BS-import qualified Data.ByteString.Lazy.Char8 as BSC-import Data.Char-import Data.Time-import Data.Version-import Data.Word-import Hans.Address.Mac-import Hans.Address.IP4-import Hans.Device.Tap-import Hans.DhcpClient-import Hans.Layer.Dns(DnsException)-import Hans.NetworkStack hiding (close)-import qualified Hans.NetworkStack as Hans-import Network.HTTP.Base-import Network.HTTP.Headers-import Network.HTTP.Stream-import System.Exit-import System.Info-import Text.Blaze.Html5 as H hiding (map, main)-import Text.Blaze.Html5.Attributes(href)-import Text.Blaze.Html.Renderer.String--#if !MIN_VERSION_base(4,8,0)-import           System.Locale-#endif--instance Stream Socket where-  readLine s = loop ""-   where loop acc =-           do bstr <- recvBytes s 1-              if | BS.null bstr       -> return (Left ErrorClosed)-                 | BS.head bstr == 10 -> return (Right (acc ++ "\n"))-                 | otherwise          -> loop (acc ++ BSC.unpack bstr)--  readBlock s y = loop (fromIntegral y) BS.empty-    where loop 0 acc = return (Right (BSC.unpack acc))-          loop x acc =-            do bstr <- recvBytes s x-               if | BS.length bstr == x -> loop 0 (acc `BS.append` bstr)-                  | BS.length bstr == 0 -> return (Left ErrorClosed)-                  | otherwise           -> loop (x - BS.length bstr)-                                                (acc `BS.append` bstr)--  writeBlock s str = loop (BSC.pack str)-    where loop x | BS.null x = return (Right ())-                 | otherwise =-                    do amt <- sendBytes s x-                       loop (BS.drop amt x)--  close s = Hans.close s--  closeOnEnd _ _ = return ()--data ServerState = ServerState {-    startTime     :: String-  , responseCount :: MVar Word64-  , lastHosts     :: MVar [(IP4, Maybe String)]-  }--main :: IO ()-main =-  do startTime     <- formatTime defaultTimeLocale "%c" `fmap` getZonedTime-     responseCount <- newMVar 0-     lastHosts     <- newMVar []-     startServer ServerState { .. }--initEthernetDevice :: NetworkStack -> IO Mac-initEthernetDevice ns =-  do let mac = Mac 0x52 0x54 0x00 0x12 0x34 0x89-     Just dev <- openTapDevice "tap6"-     addDevice ns mac (tapSend dev) (tapReceiveLoop dev)-     return mac--startServer :: ServerState -> IO ()-startServer state =-  do ns  <- newNetworkStack-     mac <- initEthernetDevice ns-     deviceUp ns mac-     putStrLn ("Starting server on device " ++ show mac)--     mbIP <- dhcpDiscover ns mac-     case mbIP of-       Nothing -> putStrLn "Couldn't get an IP address."-       Just ipaddr -> do-         putStrLn ("Device " ++ show mac ++ " has IP " ++ show ipaddr)--         lsock <- listen ns undefined 80 `catch` handler-         forever $ do-           sock <- accept lsock-           putStrLn "Accepted socket."-           _ <- forkIO (handleClient sock state)-           _ <- forkIO (addHost ns (sockRemoteHost sock) (lastHosts state))-           return ()-- where-  handler ListenError{} =-    do putStrLn ("Unable to listen on port 80\n")-       threadDelay (5 * 1000000)-       exitFailure--handleClient :: Socket -> ServerState -> IO ()-handleClient sock state =-  do mreq <- receiveHTTP sock-     case mreq of-       Left err  -> putStrLn ("ReqERROR: " ++ show err)-       Right req ->-         do bod <- buildBody req state-            putStrLn "Built response"-            let lenstr = show (length bod)-                keepAlive = [ mkHeader HdrConnection "keep-alive"-                            | hdr <- retrieveHeaders HdrConnection req-                            , map toLower (hdrValue hdr) == "keep-alive" ]-                conn | null keepAlive = [ mkHeader HdrConnection "Close" ]-                     | otherwise      = keepAlive-                resp = Response {-                             rspCode = (2,0,0)-                           , rspReason = "OK"-                           , rspHeaders = mkHeader HdrContentLength lenstr-                                        : mkHeader HdrContentType   "text/html"-                                        : conn-                           , rspBody = bod-                           }-            respondHTTP sock resp-            if null keepAlive-               then Hans.close sock-               else handleClient sock state--buildBody :: Request String -> ServerState -> IO String-buildBody _req state =-  do numReqs <- modifyMVar (responseCount state) (\ x -> return (x + 1, x))-     prevHosts <- readMVar (lastHosts state)-     putStrLn ("prevHosts: " ++ show prevHosts ++ "\n")-     return $ renderHtml $-       docTypeHtml $ do-         H.head (title "HaLVM Test Server")-         body $ do-           h1 "Hi!"-           p $ do "I'm a HaLVM. Technically I'm " >> string compilerName-                  " version " >> string (showVersion compilerVersion)-                  ", ported to run on Xen. I started running on "-                  string (startTime state) >> ". You didn't know there was a "-                  "HaLVM standard time, did you? Well, there is. Really."-           p $ do "I have responded to " >> (string (show numReqs)) >> " "-                  "requests since I started, not including yours."-           p $ do "I am using: "-           ul $ do-             li $    hans >> " to talk to you over TCP."-             li $ do http >> " (modified to use the " >> network_hans-                     " shim) to understand your request and respond to it."-             li $ do blaze >> " (which built out of the box) to generate this "-                     "pretty HTML."-             li $ do "... and a host of other libraries to do other, more "-                     "standard, things."-           p $ do "Here are the last 5 hosts that I did something for:"-           ol $ forM_ prevHosts $ \ x -> li (renderHost x)- where-  hans, http, blaze, network_hans :: Html-  hans  = a ! href "http://hackage.haskell.org/package/hans" $ "HaNS"-  http  = a ! href "http://hackage.haskell.org/package/HTTP" $ "HTTP"-  blaze = a ! href "http://hackage.haskell.org/package/blaze-html"$"blaze-html"-  network_hans = a ! href "http://github.com/GaloisInc/network-hans" $-                   "network-hans"--renderHost :: (IP4, Maybe String) -> Html-renderHost (addr, Nothing) = string (show addr)-renderHost (addr, Just n) = string (n ++ " (" ++ show addr ++ ")")--addHost :: NetworkStack -> IP4 -> MVar [(IP4, Maybe String)] -> IO ()-addHost ns addr hostsMV =-  do putStrLn ("addHost " ++ show addr)-     entry <- catch (name `fmap` getHostByAddr ns addr) handleDnsError-     list  <- takeMVar hostsMV-     putStrLn ("list: " ++ show list)-     case lookup addr list of-       Just _  -> putMVar hostsMV list >> putStrLn "put old list"-       Nothing -> putMVar hostsMV (take 5 ((addr, entry) : list )) >> putStrLn "put new list"- where-  name = Just . hostName-  handleDnsError :: DnsException -> IO (Maybe String)-  handleDnsError e = print e >> return Nothing