A stack which works with or without an Mbed os library. Provides IPv4 or IPv6 with a full 1500 byte buffer.

Dependents:   oldheating gps motorhome heating

tcp/tcprecv.c

Committer:
andrewboyson
Date:
2019-05-17
Revision:
147:a6093b52e654
Parent:
146:0fc66d610fd6
Child:
156:be12b8fd5b21

File content as of revision 147:a6093b52e654:

#include <stdint.h>
#include <stdbool.h>
#include <stdarg.h>

#include     "log.h"
#include     "net.h"
#include  "action.h"
#include     "tcp.h"
#include  "tcphdr.h"
#include "tcpsend.h"
#include     "tcb.h"
#include     "ip4.h"
#include    "dhcp.h"
#include "httpshim.h"
#include     "led.h"
#include "mstimer.h"
#include "restart.h"

static void log(void (*traceback)(void), char* fmt, ...)
{
    if (TcpTrace)
    {
        if (NetTraceNewLine) Log("\r\n");
        LogTimeF("TCP port %hu - ", TcpHdrSrcPort);
        va_list argptr;
        va_start(argptr, fmt);
        LogV(fmt, argptr);
        va_end(argptr);
        Log("\r\n");
        if (NetTraceStack) traceback();
    }
}

static void handleSyn(void *pPacket, int ipType, int remArIndex, int locMss, struct tcb* pTcb)
{
    //Get the MSS to use for sends - it is the lower of the MSS advertised by the remote host and our local MSS
    int remMss = TcpHdrMssGet();
    pTcb->remMss = remMss ? remMss : 536; //default MSS for IPv4 [576 - 20(TCP) - 20(IP)];
    if (pTcb->remMss > locMss) pTcb->remMss = locMss;
    
    pTcb->timeSendsBeingAcked = MsTimerCount;
    pTcb->countSendsNotAcked  = 0;
    pTcb->rcvdFin             = false;
    pTcb->sentFin             = false;
    pTcb->appStarted          = false;
    for (char* p = pTcb->appData; p < pTcb->appData + TCB_APP_STATE_BYTES; p++) *p = 0;
    for (char* p = pTcb->tlsData; p < pTcb->tlsData + TCB_TLS_STATE_BYTES; p++) *p = 0;
    pTcb->remIsn              = TcpHdrSeqNum;
    pTcb->locIsn              = TcbGetIsn();
    pTcb->bytesRcvdFromRem    = 0;
    pTcb->bytesAckdByRem      = 0;
    pTcb->bytesAckdToRem      = 0;
    pTcb->bytesSentToRem      = 0;
}
static void handleReceivedData(void* pPacket, int dataLength, uint32_t position, struct tcb* pTcb)
{
    pTcb->window = TcpHdrWindow;
    char* pData = (char*)pPacket + TcpHdrSizeGet();
    switch (pTcb->locPort)
    {
        case  80: HttpShimRequest(dataLength, pData, position, pTcb->appData, pTcb->tlsData, false); break;
        case 443: HttpShimRequest(dataLength, pData, position, pTcb->appData, pTcb->tlsData, true ); break;
        default:                                                                                     break;
    }
}
static int sendResetFromPacket(int* pSizeTx, void* pPacketTx, int ipType, int remArIndex, int locIpScope, int seqLengthRcvd)
{
    /*RFC793 p36 If the connection does not exist (CLOSED) then a reset is sent
    in response to any incoming segment except another reset.
    If the incoming segment has an ACK field, the reset takes its sequence number from the ACK field of the segment,
    otherwise                                 the reset has sequence number zero
    and
    the ACK field is set to the sum of the sequence number and segment length of the incoming segment.
    The connection remains in the CLOSED state.
    In TcpSendReset TcpHdrAckNum = pTcb->bytesAckdToRem + pTcb->remIsn;  //Set up the acknowledgement field ready to send
                    TcpHdrSeqNum = pTcb->bytesSentToRem + pTcb->locIsn;  //Set up the start of the message before adding the bytes sent
    */
    
    struct tcb tcb;
    struct tcb* pTcb = &tcb;
    pTcb->timeLastRcvd     = MsTimerCount;
    pTcb->remArIndex       = remArIndex;
    pTcb->ipType           = ipType;
    pTcb->locIpScope       = locIpScope;
    pTcb->remPort          = TcpHdrSrcPort;
    pTcb->locPort          = TcpHdrDstPort;
    pTcb->window           = TcpHdrWindow;
    pTcb->state            = TCB_EMPTY;

    pTcb->timeSendsBeingAcked = MsTimerCount;
    pTcb->countSendsNotAcked  = 0;
    pTcb->rcvdFin             = false;
    pTcb->sentFin             = false;
    pTcb->appStarted          = false;
    for (char* p = pTcb->appData; p < pTcb->appData + TCB_APP_STATE_BYTES; p++) *p = 0;
    for (char* p = pTcb->tlsData; p < pTcb->tlsData + TCB_TLS_STATE_BYTES; p++) *p = 0;
    pTcb->remIsn              = TcpHdrSeqNum + seqLengthRcvd; //Ack number
    pTcb->locIsn              = TcpHdrACK ? TcpHdrAckNum : 0; //Seq number
    pTcb->bytesRcvdFromRem    = 0;
    pTcb->bytesAckdByRem      = 0;
    pTcb->bytesAckdToRem      = 0;
    pTcb->bytesSentToRem      = 0;

    return TcpSendReset(pSizeTx, pPacketTx, pTcb);
}

int TcpHandleReceivedPacket(void (*traceback)(void), int sizeRx, void* pPacketRx, int* pSizeTx, void* pPacketTx, int ipType, int remArIndex, int locIpScope)
{
    int lastRestartPoint = RestartPoint;
    RestartPoint = FAULT_POINT_TcpHandleReceivedPacket;
    
    int action = DO_NOTHING;
    bool traceRequested = false;
    
    TcpHdrReadFromPacket(pPacketRx);
    
    if (remArIndex < 0)
    {
        log(traceback, "invalid remote AR index %d -> ignored packet", remArIndex);
        RestartPoint = lastRestartPoint;
        return DO_NOTHING;
    }
    RestartPoint += 100;
    
    int dataLength =   sizeRx - TcpHdrSizeGet();
    int locMss     = *pSizeTx - TcpHdrSizeGet();
    
    //Calculate the sequence length of the received packet
    int seqLengthRcvd = 0;
    if (TcpHdrSYN) seqLengthRcvd += 1;          //Add one to acknowledge the SYN
                   seqLengthRcvd += dataLength; //Add the number of bytes received
    if (TcpHdrFIN) seqLengthRcvd += 1;          //Add one to acknowledge the FIN
    
    //Filter out unwanted links
    RestartPoint++;
    switch (TcpHdrDstPort)
    {
        case 80:
            if (HttpShimGetTrace())
            {
                if (NetTraceNewLine) Log("\r\n");
                LogTime("HTTP server request\r\n");
                traceRequested = true;
            }
            break;
            
        case 443:
            if (HttpShimGetTrace())
            {
                if (NetTraceNewLine) Log("\r\n");
                LogTime("HTTPS server request\r\n");
                traceRequested = true;
            }
            break;
            
        default: //Send reset if unknown port
            log(traceback, "unhandled local port %hu -> sent reset", TcpHdrDstPort);
            action = sendResetFromPacket(pSizeTx, pPacketTx, ipType, remArIndex, locIpScope, seqLengthRcvd);
            RestartPoint = lastRestartPoint;
            return action;
    }
    
    //Get the Transmission Control Block
    RestartPoint++;
    struct tcb* pTcb = TcbGetExisting(ipType, remArIndex, locIpScope, TcpHdrSrcPort, TcpHdrDstPort);
    if (!pTcb) pTcb = TcbGetEmpty();
    if (!pTcb) //send reset if no more tcbs are available
    {
        log(traceback, "no more tcbs available -> sent reset");
        action = sendResetFromPacket(pSizeTx, pPacketTx, ipType, remArIndex, locIpScope, seqLengthRcvd);
        RestartPoint = lastRestartPoint;
        return action;
    }
    pTcb->timeLastRcvd     = MsTimerCount;
    pTcb->remArIndex       = remArIndex;
    pTcb->ipType           = ipType;
    pTcb->locIpScope       = locIpScope;
    pTcb->remPort          = TcpHdrSrcPort;
    pTcb->locPort          = TcpHdrDstPort;
    pTcb->window           = TcpHdrWindow;
    
    //Handle request to reset
    RestartPoint++;
    if (TcpHdrRST)
    {
        if (pTcb->state)
        {
            log(traceback, "received reset -> reaped TCB");
            pTcb->state = TCB_EMPTY;
        }
        RestartPoint = lastRestartPoint;
        return DO_NOTHING;        //Don't reply
    }
    
    //Handle request to synchronise
    RestartPoint++;
    if (TcpHdrSYN)
    {
        if (pTcb->state)
        {
            log(traceback, "received a SYN on an open connection -> sent reset");
            pTcb->state = TCB_EMPTY;
            action = TcpSendReset(pSizeTx, pPacketTx, pTcb);
            RestartPoint = lastRestartPoint;
            return action;
        }
        else
        {
            handleSyn(pPacketRx, ipType, remArIndex, locMss, pTcb);
        }
    }
    
    //Handle non SYN packet on an empty connection
    RestartPoint++;
    if (!TcpHdrSYN && !pTcb->state)
    {

        log(traceback, "non SYN packet received on a closed connection -> sent reset");
        pTcb->state = TCB_EMPTY;
        action = sendResetFromPacket(pSizeTx, pPacketTx, ipType, remArIndex, locIpScope, seqLengthRcvd);
        RestartPoint = lastRestartPoint;
        return action;
    }
    
    //Check if the acks of bytes sent has progressed and reset the timer
    RestartPoint++;
    uint32_t ackRcvdFromRem = TcpHdrACK ? TcpHdrAckNum - pTcb->locIsn : 0;
    if (ackRcvdFromRem > pTcb->bytesAckdByRem)
    {
        pTcb->timeSendsBeingAcked = MsTimerCount;
        pTcb->countSendsNotAcked  = 0;
    }

    //Record the number of bytes acked by the remote host
    RestartPoint++;
    pTcb->bytesAckdByRem = ackRcvdFromRem;

    /* If the connection is in a synchronized state
    any unacceptable segment (out of window sequence number or
    unacceptible acknowledgment number) must elicit only an empty
    acknowledgment segment containing the current send-sequence number
    and an acknowledgment indicating the next sequence number expected
    to be received, and the connection remains in the same state.*/
    RestartPoint++;
    uint32_t seqRcvdFromRem = TcpHdrSeqNum - pTcb->remIsn;
    if (seqRcvdFromRem != pTcb->bytesAckdToRem)
    {
        //Only warn non keep-alives
        if (seqRcvdFromRem != 0 || pTcb->bytesAckdToRem != 1)
        {
            log(traceback, "seq rcvd is %d and last seq ackd was %d -> resent last ACK", seqRcvdFromRem, pTcb->bytesAckdToRem);
        }
        action = TcpResendLastAck(pSizeTx, pPacketTx, pTcb);
        RestartPoint = lastRestartPoint;
        return action;
    }
    //Ignore data before established
    RestartPoint++;
    if (pTcb->state != TCB_ESTABLISHED && dataLength)
    {
        log(traceback, "data received before connection established -> sent reset");
        pTcb->state = TCB_EMPTY;
        action = TcpSendReset(pSizeTx, pPacketTx, pTcb);
        RestartPoint = lastRestartPoint;
        return action;
    }
    
    //Handle FIN
    RestartPoint++;
    if (TcpHdrFIN) pTcb->rcvdFin = true; //When reply is all sent only a passive close is needed
    
    //From now on there are no errors so display traceback if requested
    RestartPoint++;
    if (traceRequested && NetTraceStack) traceback();
    
    //Record the number of bytes received from the remote host
    RestartPoint++;
    pTcb->bytesRcvdFromRem += seqLengthRcvd;

    switch (pTcb->state) //This is the state of the connection BEFORE this packet arrived
    {
        case TCB_EMPTY:
            pTcb->state = TCB_SYN_RECEIVED;
            break;
            
        case TCB_SYN_RECEIVED:
            pTcb->state = TCB_ESTABLISHED;
            break;
            
        case TCB_ESTABLISHED:
            if (dataLength)
            {
                handleReceivedData(pPacketRx, dataLength, seqRcvdFromRem - 1, pTcb);
                pTcb->appStarted = true;
            }
            if (pTcb->sentFin)
            {
                pTcb->state = pTcb->rcvdFin ? TCB_EMPTY : TCB_CLOSE_FIN_WAIT;
            }
            break;
            
        case TCB_CLOSE_FIN_WAIT: //End of active close
            if (TcpHdrFIN)
            {
                pTcb->state = TCB_EMPTY;//Ignore ACK to our FIN. Wait for FIN then close.
            }
            break;
            
    }
    
    RestartPoint++;
    action = TcpSend(pSizeTx, pPacketTx, pTcb);

    RestartPoint = lastRestartPoint;
    return action;
}