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)
processTcp :: Network addr
=> NetworkStack -> Device -> addr -> addr -> S.ByteString -> Hans ()
processTcp ns dev src dst bytes =
do
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
type InputCase = NetworkStack -> Device -> Addr -> Addr -> TcpHeader
-> S.ByteString -> Hans ()
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
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
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
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
handleActive :: NetworkStack
-> Device -> TcpHeader -> S.ByteString -> Tcb -> Hans ()
handleActive ns dev hdr payload tcb =
do updateTimestamp tcb hdr payload
whenState tcb SynSent (handleSynSent ns dev hdr payload tcb)
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
updateTimestamp :: Tcb -> TcpHeader -> S.ByteString -> Hans ()
updateTimestamp Tcb { .. } hdr payload =
do TcbConfig { .. } <- io (readIORef tcbConfig)
when tcUseTimestamp $
case findTcpOption OptTagTimestamp hdr of
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))
_ | view tcpRst hdr -> return ()
| otherwise -> escape
handleActiveSegs :: NetworkStack -> Tcb -> UTCTime -> [(TcpHeader,S.ByteString)]
-> Hans ()
handleActiveSegs ns tcb now = go
where
go [] = return ()
go ((hdr,payload):segs) =
do
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 ()
setState tcb Closed
closeActive ns tcb
escape
when (view tcpAck hdr) $
do
io $ atomicModifyIORef' (tcbTimers tcb) resetIdleTimer
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 ()
_ -> return ()
unless (S.null payload) $ io $
do signalDelayedAck tcb
queueBytes payload tcb
when (view tcpFin hdr) $
do
_ <- 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
processFinWait2 :: NetworkStack -> Tcb -> IO ()
processFinWait2 _ns Tcb { .. } =
do win <- readIORef tcbSendWindow
when (nullWindow win) $
do
return ()
enterTimeWait :: NetworkStack -> Tcb -> Hans ()
enterTimeWait ns tcb =
do tw <- io (mkTimeWaitTcb tcb)
io (closeActive ns tcb)
io (registerTimeWait ns tw)
escape
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
handleSynSent :: NetworkStack -> Device -> TcpHeader -> S.ByteString -> Tcb
-> Hans ()
handleSynSent ns _dev hdr _payload tcb =
do
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
when (view tcpRst hdr) $
do when (view tcpAck hdr) $ io $
do setState tcb Closed
deleteActive ns tcb
escape
when (view tcpSyn hdr) $
do let rcvNxt = tcpSeqNum hdr + 1
res <- io (setRcvNxt rcvNxt tcb)
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
_ <- io (queueAck ns tcb)
io (setState tcb Established)
escape
io (setState tcb SynReceived)
let synAck = set tcpSyn True
$ set tcpAck True emptyTcpHeader
_ <- io (queueWithTcb ns tcb synAck L.empty)
escape
escape
handleListening :: NetworkStack
-> Device -> Addr -> Addr -> TcpHeader -> S.ByteString
-> ListenTcb -> Hans ()
handleListening ns dev remote local hdr _payload tcb =
do when (view tcpRst hdr)
escape
when (view tcpAck hdr) $
do hdr' <- io (mkRst hdr)
_ <- io (routeTcp ns dev local remote hdr' L.empty)
escape
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
escape
createChildTcb :: NetworkStack -> Device -> Addr -> Addr -> TcpHeader -> ListenTcb
-> Hans ()
createChildTcb ns dev remote local hdr parent =
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
canAccept <- io (reserveSlot parent)
unless canAccept (rejectSyn ns dev remote local hdr)
(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)
unless added $
do io (releaseSlot parent)
rejectSyn ns dev remote local hdr
io (processSynOptions child hdr)
let synAck = set tcpSyn True
$ set tcpAck True emptyTcpHeader
_ <- io (queueWithTcb ns child synAck L.empty)
return ()
processSynOptions :: Tcb -> TcpHeader -> IO ()
processSynOptions Tcb { .. } hdr =
do case findTcpOption OptTagTimestamp hdr of
Just (OptTimestamp val 0) ->
do atomicModifyIORef' tcbConfig $ \ TcbConfig { .. } ->
(TcbConfig { tcUseTimestamp = True, .. }, ())
atomicWriteIORef tcbTSRecent val
_ -> atomicModifyIORef' tcbConfig $ \ TcbConfig { .. } ->
(TcbConfig { tcUseTimestamp = False, .. }, ())
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
handleTimeWait :: NetworkStack -> TcpHeader -> S.ByteString -> TimeWaitTcb -> Hans ()
handleTimeWait ns hdr payload tcb =
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
when (view tcpRst hdr) $
do io (deleteTimeWait ns tcb)
escape
when (view tcpSyn hdr) $
do rst <- io (mkRst hdr)
_ <- io (queueTcp ns (twRouteInfo tcb) (twRemote tcb) rst L.empty)
io (deleteTimeWait ns tcb)
escape
when (not (view tcpAck hdr)) escape
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
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