module Hans.IP4.Output (
sendIP4, queueIP4,
prepareIP4,
primSendIP4,
responder,
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
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)
sendIP4 :: NetworkStack -> SendSource -> IP4
-> Bool -> NetworkProtocol -> L.ByteString
-> IO Bool
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
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
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)
}
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 ]
primSendIP4 :: NetworkStack -> Device -> IP4 -> IP4 -> IP4 -> Bool -> NetworkProtocol
-> L.ByteString -> IO ()
primSendIP4 ns dev src dst next df prot payload
| src == next =
do hdr <- prepareHeader ns src dst df prot
_ <- BC.tryWriteChan (nsInput ns) $! FromIP4 dev hdr (L.toStrict payload)
return ()
| otherwise =
do packets <- prepareIP4 ns dev src dst df prot payload
arpOutgoing ns dev src next packets
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
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
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
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
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
portUnreachable :: NetworkStack -> Device -> SendSource -> IP4 -> S.ByteString
-> IO ()
portUnreachable ns dev src dst chunk =
queueIcmp4 ns dev src dst (DestinationUnreachable PortUnreachable chunk)