EtherLib/prefab/conn_blocks/tcp_connblock.c
Wiesner András 8676a392e5 - TCP basic implementation added
- TCPWindow added
- Checksum bug fixed (again)
- CBD introduced
- ConnBlock modified
- PackSieve report funtionality modified to decrease memory consumption
2023-01-30 11:04:46 +01:00

348 lines
12 KiB
C

#include "tcp_connblock.h"
#include <stddef.h>
#include <stdlib.h>
#include "../packet_parsers/packet_parsers.h"
#include "../../utils.h"
#include "../../dynmem.h"
#include "etherlib/pckt_assembler.h"
#include "etherlib/eth_interface.h"
#include "ipv4_connblock.h"
#include "etherlib/prefab/packet_parsers/tcp_segment.h"
#include "etherlib/gen_queue.h"
#include "etherlib_options.h"
#include "etherlib/prefab/conn_blocks/tcp/tcp_window.h"
#include "etherlib/global_state.h"
static char *TCP_STATE_NAMES[] = {
"CLOSED", "LISTEN", "SYN_RCVD", "SYN_SENT", "ESTAB", "FIN_WAIT_1",
"FIN_WAIT_2", "CLOSE_WAIT", "CLOSING", "LAST_ACK", "TIME_WAIT"
};
int tcp_send_segment(const struct ConnBlock_ *connBlock, TcpFlag flags, TcpOption *opts, const uint8_t *data, uint32_t size);
static bool filtTcp(const PcktSieveFilterCondition *filtCond, const PcktProps *contProps, const PcktProps *ownProps, EthInterface *intf) {
IPv4Props *ipProps = (IPv4Props *) contProps;
TcpProps *tcpProps = (TcpProps *) ownProps;
return ipProps->Protocol == ETH_TCP_PACKET_CLASS && (TCP_PORT_FROM_FILTCOND(filtCond) == tcpProps->DestinationPort);
}
/**
* TCP state.
*/
typedef struct {
uint16_t localPort;
uint16_t remotePort;
ip4_addr remoteAddr;
TcpConnectionState connState;
uint16_t localMSS;
uint16_t remoteMSS;
uint32_t sequenceNumber; // seq. num of next transmitted octet
uint32_t ackNumber; // ack. number sent to the remote side (seq. num of next expected octet)
uint16_t window;
TcpWindow * txWin;
ConnBlock connBlock;
bool debug;
SieveCallBackFn userCb;
} TcpState;
#define TCP_DEFAULT_MSS (1200)
void tcps_init(TcpState *tcps, uint16_t localPort) {
tcps->sequenceNumber = (uint32_t) rand();
tcps->localPort = localPort;
tcps->localMSS = TCP_DEFAULT_MSS;
tcps->remotePort = 0;
tcps->remoteAddr = 0;
tcps->ackNumber = 0;
tcps->window = ETHLIB_DEF_TCP_WINDOW_SIZE;
tcps->txWin = tcpw_create(ETHLIB_DEF_TCP_WINDOW_SIZE);
//tcps->rxWin = tcpw_create(ETHLIB_DEF_TCP_WINDOW_SIZE);
tcps->debug = false;
tcps->userCb = NULL;
}
#define TCP_FETCH_STATE_FROM_CONNBLOCK(connBlock) ((TcpState *) (connBlock)->sieveLayer->tag.p)
void tcp_init_connection(ConnBlock *connBlock);
static void retry_to_connect_cb(Timer * timer, AlarmUserData user) {
ConnBlock * connBlock = (ConnBlock *) user.ptr;
tcp_init_connection(connBlock);
}
int tcp_receive_segment_cb(const Pckt *pckt, PcktSieveLayerTag tag) {
TcpState *tcps = (TcpState *) tag.p; // pointer to state is stored in sieve layer tag
TcpProps *tcpProps = HEADER_FETCH_PROPS(TcpProps, pckt->header); // fetch header
TcpConnectionState beginState = tcps->connState;
IPv4Props * ipProps = HEADER_FETCH_PROPS(IPv4Props, pckt->header->prev);
uint16_t dataSize = ipProps->TotalLength - tcpProps->DataOffset - ETH_IPv4_HEADER_SIZE; // extract data size
// process incoming packet
switch (tcps->connState) {
case TCP_STATE_SYN_SENT: {
if (!(tcpProps->Flags & TCP_FLAG_ACK)) {
// TODO: if not acknowledged
}
if (tcpProps->Flags & TCP_FLAG_SYN) {
tcps->ackNumber = tcpProps->SequenceNumber + 1;
}
// get MSS
TcpOption *mss = tcp_get_option_by_kind(tcpProps->options, TCP_OPT_KIND_MSS);
if (mss == NULL) {
tcps->connState = TCP_STATE_CLOSED; // TODO: not this way...
AlarmUserData alarmData;
alarmData.ptr = &tcps->connBlock;
timer_sched_rel(E.tmr, 1000000, retry_to_connect_cb, alarmData);
break;
}
FETCH_WORD_H2N(&tcps->remoteMSS, mss->value);
// send ACK
int flags = TCP_FLAG_ACK;
tcps->sequenceNumber++; // advance sequence number // TODO...
tcp_send_segment(&tcps->connBlock, flags, NULL, NULL, 0);
// step into next state
tcps->connState = TCP_STATE_ESTAB;
break;
}
case TCP_STATE_ESTAB:
// if the other end tries to close down the connection
if (tcpProps->Flags & TCP_FLAG_FIN) {
// send FIN, ACK
int flags = TCP_FLAG_FIN | TCP_FLAG_ACK;
tcps->ackNumber++; // advance acknowledgement number // TODO...
tcp_send_segment(&tcps->connBlock, flags, NULL, NULL, 0);
// step into next state
tcps->connState = TCP_STATE_LAST_ACK;
} else if ((dataSize > 0) && (tcps->ackNumber == tcpProps->SequenceNumber)) { // incoming data segment with integrity in ack/seq
// send acknowledge
tcps->ackNumber += dataSize;
tcp_send_segment(&tcps->connBlock, TCP_FLAG_ACK, NULL, NULL, 0);
// process data
if (tcps->userCb != NULL) {
PcktSieveLayerTag userTag = { 0 }; // TODO...
tcps->userCb(pckt, userTag);
}
} else if ((dataSize == 0) && (tcps->sequenceNumber == tcpProps->AcknowledgementNumber)) { // outgoing segment was acknowledged by peer
// release segment from retransmit window
tcpw_acknowledge(tcps->txWin, tcpProps->AcknowledgementNumber);
}
break;
case TCP_STATE_LAST_ACK:
if (tcpProps->Flags & TCP_FLAG_ACK) {
tcps->connState = TCP_STATE_CLOSED;
}
break;
case TCP_STATE_CLOSED:
default:
break;
}
// print state
if (tcps->debug && (beginState != tcps->connState)) {
MSG("%s -> %s\n", TCP_STATE_NAMES[beginState], TCP_STATE_NAMES[tcps->connState]);
}
return 0; // TODO
}
void tcp_init_connection(ConnBlock *connBlock) {
/* send SYN with maximum segment size option */
// TODO: check that our state is CLOSED
TcpState *tcps = TCP_FETCH_STATE_FROM_CONNBLOCK(connBlock);
// prepare MSS option
TcpOption *mss = tcpopt_new(TCP_OPT_KIND_MSS, 2);
FILL_WORD_H2N(mss->value, tcps->localMSS);
// prepare flags
int flags = TCP_FLAG_SYN;
// send segment
tcp_send_segment(connBlock, flags, mss, NULL, 0);
// release option
dynmem_free(mss);
// step into SYN SENT state
tcps->connState = TCP_STATE_SYN_SENT;
}
void tcp_bind(cbd d, ip4_addr remoteAddr, uint16_t remotePort) {
ConnBlock connBlock;
if (!cbdt_get_connection_block(E.cbdt, d, &connBlock)) {
ERROR("Invalid CBD descriptor: '%d'!\n", d);
return;
}
// store remote endpoint data
TcpState *tcps = TCP_FETCH_STATE_FROM_CONNBLOCK(&connBlock);
tcps->remoteAddr = remoteAddr;
tcps->remotePort = remotePort;
// initialize connection
tcp_init_connection(&connBlock);
}
void tcp_debug(cbd d, bool debug) {
ConnBlock connBlock;
if (!cbdt_get_connection_block(E.cbdt, d, &connBlock)) {
ERROR("Invalid CBD descriptor: '%d'!\n", d);
return;
}
TcpState *tcps = TCP_FETCH_STATE_FROM_CONNBLOCK(&connBlock);
tcps->debug = debug;
}
// ---------------------
cbd tcp_new_connblock(EthInterface *intf, ip4_addr ipAddr, uint16_t port, SieveCallBackFn cbFn) {
ConnBlock tcpConnB;
ConnBlock ipConnB = ipv4_new_connblock(intf, ipAddr, NULL); // create new IPv4 connection block
PcktSieveFilterCondition filtCond;
packfiltcond_zero(&filtCond);
TCP_PORT_TO_FILTCOND(&filtCond, port);
PcktSieveLayerTag tag; // store TCP state into sieve layer's tag
TcpState *tcpState = dynmem_alloc(sizeof(TcpState));
tcps_init(tcpState, port);
tcpState->userCb = cbFn;
tag.p = tcpState;
tcpConnB.sieveLayer = packsieve_new_layer(ipConnB.sieveLayer, &filtCond, false, filtTcp, tcp_receive_segment_cb, tag, ETH_TCP_PACKET_CLASS);
ASSERT_NULL(tcpConnB.sieveLayer);
tcpConnB.sieveLayer->connBReportFn = tcp_print_report;
tcpConnB.sieve = &intf->sieve;
tcpState->connBlock = tcpConnB; // also store connection block parameters
// store connection block to CBDT
cbd d = cbdt_alloc_new(E.cbdt, &tcpConnB);
if (d == CBDT_ERR) { // on error free everything we have allocated before
packsieve_remove_layer(tcpConnB.sieveLayer);
}
return d;
}
int tcp_send_segment(const struct ConnBlock_ *connBlock, TcpFlag flags, TcpOption *opts, const uint8_t *data, uint32_t size) {
// allocate headers
PcktHeaderElement *tcpHeader = ALLOC_HEADER_ELEMENT(TcpProps);
PcktHeaderElement *ipHeader = ALLOC_HEADER_ELEMENT(IPv4Props);
PcktHeaderElement *ethHeader = ALLOC_HEADER_ELEMENT(EthernetProps);
tcpHeader->next = NULL;
tcpHeader->prev = ipHeader;
ipHeader->next = tcpHeader;
ipHeader->prev = ethHeader;
ethHeader->next = ipHeader;
ethHeader->prev = NULL;
// prepare headers
TcpProps *tcpProps = HEADER_FETCH_PROPS(TcpProps, tcpHeader);
IPv4Props *ipProps = HEADER_FETCH_PROPS(IPv4Props, ipHeader);
EthernetProps *ethProps = HEADER_FETCH_PROPS(EthernetProps, ethHeader);
// get TCP state
PcktSieveLayer *layer = connBlock->sieveLayer; // TCP layer
TcpState *tcpState = (TcpState *) layer->tag.p;
// fetch sieve layers and fill transmit headers
tcpProps->SourcePort = tcpState->localPort;
tcpProps->DestinationPort = tcpState->remotePort;
tcpProps->SequenceNumber = tcpState->sequenceNumber;
tcpProps->AcknowledgementNumber = tcpState->ackNumber;
tcpProps->Flags = flags;
tcpProps->Window = tcpState->window;
tcpProps->Checksum = 0;
tcpProps->UrgentPtr = 0;
tcpProps->options = opts;
// get options size
uint32_t optSize = tcp_get_options_size(tcpProps->options);
// common fields for packet assembly
tcpProps->hdrInsFn = insert_tcp_header;
tcpProps->headerSize = ETH_TCP_HEADER_SIZE + optSize;
// IP
ipv4_fill_props(ipProps, ETH_TCP_HEADER_SIZE + optSize + size, ETH_TCP_PACKET_CLASS,
connBlock->sieve->intf->ip, tcpState->remoteAddr);
// Ethernet
layer = layer->parent;
if (tcpState->remoteAddr != 0xFFFFFFFF) {
ArpCache *arpc = connBlock->sieve->intf->arpc;
const ArpEntry *entry = arpc_get_ask(arpc, tcpState->remoteAddr);
memcpy(ethProps->destAddr, entry, ETH_HW_ADDR_LEN);
}
memcpy(ethProps->sourceAddr, connBlock->sieve->intf->mac, ETH_HW_ADDR_LEN);
ethProps->length_type = ETH_IPv4_PACKET_CLASS;
// common fields for packet assembly
ethProps->hdrInsFn = insert_ethernet_header;
ethProps->headerSize = ETH_ETHERNET_HEADER_SIZE;
// -----------------------------
Pckt cooked;
cooked.payload = data;
cooked.payloadSize = size;
cooked.header = ethHeader;
// NOT FILLED FIELDS
cooked.headerSize = 0;
cooked.time_s = 0;
cooked.time_ns = 0;
RawPckt raw;
pckt_assemble(&raw, &cooked);
ethinf_transmit(connBlock->sieve->intf, &raw);
// free headers
dynmem_free(tcpHeader);
dynmem_free(ipHeader);
dynmem_free(ethHeader);
// advance TCP sequence number
tcpState->sequenceNumber += size;
return 0;
}
uint32_t tcp_send(cbd d, const uint8_t * data, uint32_t size) {
ConnBlock connBlock;
if (!cbdt_get_connection_block(E.cbdt, d, &connBlock)) {
ERROR("Invalid CBD descriptor: '%d'!\n", d);
return 0;
}
TcpState *tcps = TCP_FETCH_STATE_FROM_CONNBLOCK(&connBlock);
if (tcps->connState == TCP_STATE_ESTAB) { // can send only data if connection is ESTABLISHED
uint32_t maxWinSize = tcpw_get_max_window_size(tcps->txWin);
uint32_t txSize = MIN(maxWinSize, size); // limit size to the allocable size of the retransmit buffer
TcpWindowSegment * winSeg = tcpw_store_segment(tcps->txWin, data, txSize, tcps->sequenceNumber); // store segment for possible retransmission
tcp_send_segment(&connBlock, TCP_FLAG_ACK, NULL, winSeg->data, winSeg->size);
return txSize;
} else {
return 0;
}
}
void tcp_print_report(const ConnBlock* connBlock) {
const PcktSieveLayer * sl = connBlock->sieveLayer;
INFO("TCP port: %d", TCP_PORT_FROM_FILTCOND(&sl->filtCond));
}