module Hans.Tcp.Output (
routeTcp,
sendTcp,
sendWithTcb,
sendAck,
sendFin,
sendData,
canSend,
queueTcp,
queueWithTcb,
queueAck,
responder,
) 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)
sendAck :: NetworkStack -> Tcb -> IO ()
sendAck ns tcb =
do _ <- sendWithTcb ns tcb (set tcpAck True emptyTcpHeader) L.empty
return ()
sendFin :: NetworkStack -> Tcb -> IO ()
sendFin ns tcb =
do let hdr = set tcpFin True
$ set tcpAck True emptyTcpHeader
_ <- sendWithTcb ns tcb hdr L.empty
return ()
sendData :: NetworkStack -> Tcb -> L.ByteString -> IO Int64
sendData ns tcb = go 0
where
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
Just len | len < mss -> return $! acc + len
| otherwise -> let acc' = acc + len
in acc' `seq` go acc' (L.drop len bytes)
Nothing -> return acc
hdr = set tcpAck True
$ set tcpPsh True
emptyTcpHeader
canSend :: Tcb -> IO Bool
canSend Tcb { .. } =
(not . Send.fullWindow) `fmap` readIORef tcbSendWindow
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
}
now <- getCurrentTime
mbRes <- atomicModifyIORef' tcbSendWindow
(Send.queueSegment (view config ns) now mkHdr body)
case mbRes of
Just (startRT,hdr',body') ->
do
when (view tcpAck hdr') $
do atomicWriteIORef tcbNeedsDelayedAck False
atomicWriteIORef tcbLastAckSent (tcpAckNum hdr')
when startRT (atomicModifyIORef' tcbTimers resetRetransmit)
_ <- sendTcp ns tcbRouteInfo tcbRemote hdr' body'
return (Just (L.length body'))
Nothing ->
return Nothing
addTimestamp :: Word32 -> Maybe Word32 -> TcpHeader -> TcpHeader
addTimestamp tsVal (Just tsEcr) hdr = setTcpOption (OptTimestamp tsVal tsEcr) hdr
addTimestamp _ _ hdr = hdr
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
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
queueWithTcb :: NetworkStack -> Tcb -> TcpHeader -> L.ByteString -> IO Bool
queueWithTcb ns tcb hdr body =
BC.tryWriteChan (view tcpQueue ns) $! SendWithTcb tcb hdr body
queueAck :: NetworkStack -> Tcb -> IO Bool
queueAck ns tcb = queueWithTcb ns tcb (set tcpAck True emptyTcpHeader) L.empty
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
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
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)