- FTP, TCP

This commit is contained in:
Wiesner András 2024-04-24 22:23:08 +02:00
parent 499b5f030c
commit 96bc37f271
10 changed files with 388 additions and 34 deletions

View File

@ -1,6 +1,7 @@
#include "ftp_server.h" #include "ftp_server.h"
#include <stdbool.h> #include <stdbool.h>
#include <stdlib.h>
#include <etherlib/prefab/conn_blocks/tcp_connblock.h> #include <etherlib/prefab/conn_blocks/tcp_connblock.h>
@ -8,10 +9,21 @@
/* FTP response messages */ /* FTP response messages */
static char ftpGreeting[] = "220 Service ready for new user.\r\n"; static const char ftpOpeningDataConn[] = "150 Opening data connection.\r\n";
static char ftpNotImplemented[] = "502 Command not implemented.\r\n"; static const char ftpCmdOk[] = "200 Command OK.\r\n";
static char ftpLoginOK[] = "230 User logged in, proceed.\r\n"; static const char ftpGreeting[] = "220 Service ready for new user.\r\n";
static char ftpRootDirName[] = "257 \"/\" is the current directory\r\n"; static const char ftpClosingDataConn[] = "226 Closing data connection.\r\n";
static const char ftpEnteringPassivePrefix[] = "227 Entering passive mode ";
static const char ftpLoginOK[] = "230 User logged in, proceed.\r\n";
static const char ftpFileActionCplt[] = "250 Requested file action complete.\r\n";
static const char ftpRootDirName[] = "257 \"/\" is the current directory\r\n";
static const char ftpSyntaxError[] = "501 Syntax error in command or parameters\r\n";
static const char ftpNotImplemented[] = "502 Command not implemented.\r\n";
static const char ftpCmdNotImplForParam[] = "504 Command not implemented for that parameter\r\n";
#define FTPS_DYNAMIC_RESPONSE_MAXLEN (63)
static char ftpsDynResp[FTPS_DYNAMIC_RESPONSE_MAXLEN + 1] = {0};
/* Implemented commands */ /* Implemented commands */
@ -29,6 +41,9 @@ static char *ftpImplementedCmdsStr[] = {
// ------ additional commands // ------ additional commands
"PWD", "PWD",
"CWD",
"PASV",
"LIST",
"\0" // SENTRY! "\0" // SENTRY!
}; };
@ -45,7 +60,10 @@ typedef enum { // ordering must be the same as in the string version above
NOOP, NOOP,
// ------ additional commands // ------ additional commands
PWD PWD,
CWD,
PASV,
LIST
} Ftp_ImplementedCmds; } Ftp_ImplementedCmds;
// ----------- // -----------
@ -61,7 +79,7 @@ int ftps_ctrl_recv(cbd d) {
return 0; return 0;
} }
int ftps_accept_cb(cbd d) { int ftps_ctrl_accept_cb(cbd d) {
s.connCtrl = d; s.connCtrl = d;
uint32_t event = FTPEVT_NEW_CONNECTION; uint32_t event = FTPEVT_NEW_CONNECTION;
ETHLIB_OS_QUEUE_PUSH(s.eventQ, &event); ETHLIB_OS_QUEUE_PUSH(s.eventQ, &event);
@ -73,12 +91,36 @@ void ftps_thread(void *param);
void ftps_init() { void ftps_init() {
osThreadAttr_t attr; osThreadAttr_t attr;
memset(&attr, 0, sizeof(osThreadAttr_t)); memset(&attr, 0, sizeof(osThreadAttr_t));
attr.stack_size = 2048; attr.stack_size = 3072;
attr.priority = osPriorityNormal; attr.priority = osPriorityNormal;
attr.name = "ftp"; attr.name = "ftp";
osThreadNew(ftps_thread, NULL, &attr); osThreadNew(ftps_thread, NULL, &attr);
} }
char *ftps_get_word(char *word, char *str, uint32_t maxLen) {
char *iter = str;
uint32_t len = 0;
// skip leading whitespaces
while ((*iter <= ' ') && (*iter != '\0')) {
iter++;
}
// look for end of word, and copy until copy size is reached
while (*iter > ' ') {
if (len < maxLen) {
word[len] = *iter;
}
len++;
iter++;
}
word[len] = '\0'; // insert null termination
// return the position of the next whitespace
return iter;
}
bool ftps_is_command_implemented(char *cmd, uint32_t len, uint32_t *cmdCode) { bool ftps_is_command_implemented(char *cmd, uint32_t len, uint32_t *cmdCode) {
char *iter = ftpImplementedCmdsStr[0]; char *iter = ftpImplementedCmdsStr[0];
bool ret = false; bool ret = false;
@ -109,14 +151,29 @@ bool ftps_is_command_implemented(char *cmd, uint32_t len, uint32_t *cmdCode) {
return ret; return ret;
} }
void ftps_rtrim(char * str) { void ftps_rtrim(char *str) {
char * iter = str + strlen(str) - 1; char *iter = str + strlen(str) - 1;
while ((*iter <= ' ') && (iter >= str)) { while ((*iter <= ' ') && (iter >= str)) {
*iter = '\0'; *iter = '\0';
iter--; iter--;
} }
} }
// data reception on data port
int ftps_data_recv(cbd d) {
return 0;
}
// accept new data connection
int ftps_data_accept_cb(cbd d) {
s.connData = d; // set data connection ID
ETHLIB_OS_SEM_POST(s.dataConnHasOpened); // release data connection semaphore
return 0;
}
#define FTPS_TYPE_ASCII 'A'
#define FTPS_TYPE_IMAGE 'I'
void ftps_process_event(uint32_t event) { void ftps_process_event(uint32_t event) {
const char *resp = NULL; // response towards the FTP client const char *resp = NULL; // response towards the FTP client
char *cmdArg = NULL; // command argument char *cmdArg = NULL; // command argument
@ -169,12 +226,71 @@ void ftps_process_event(uint32_t event) {
s.connState = FTPS_ESTABLISHED; s.connState = FTPS_ESTABLISHED;
} }
} else if (s.connState == FTPS_ESTABLISHED) { } else if (s.connState == FTPS_ESTABLISHED) {
switch (cmdCode) switch (cmdCode) {
{
case PWD: // print working directory name case PWD: // print working directory name
resp = ftpRootDirName; resp = ftpRootDirName;
break; break;
case TYPE: // case CWD:
resp = ftpFileActionCplt;
break;
case TYPE: { // set transfer format
char typeChar[2]; // get type character
ftps_get_word(typeChar, cmdArg, 1); // (first parameter)
// if no parameter was defined
if (typeChar[0] == '\0') {
goto syntax_error;
}
// if parameter is defined, then act accordingly
switch (typeChar[0]) {
case FTPS_TYPE_ASCII: // ASCII
case FTPS_TYPE_IMAGE: // Image (binary)
// I don't know if there's any difference between ASCII and Image transfers, since bytes are 8-bit long
goto cmd_ok;
break;
default: // other formats are not implemented
goto cmd_param_not_implemented;
break;
}
} break;
case PASV: { // enter passive mode
if (!s.passiveMode) {
// start data connection listener socket
uint16_t port;
do { // randomize a port number not in use currently
port = (rand() % (0xFFFF - 1024)) + 1024; // it's a nice practice if we pick a port number greater than 1024
s.connDataS = tcp_new_connblock(s.intf, IPv4_IF_ADDR, port, ftps_data_recv);
} while (s.connDataS == 0); // retry if connection block could not be opened
// convert to server socket
tcp_set_accept_callback(s.connDataS, ftps_data_accept_cb);
tcp_listen(s.connDataS);
MSG("port: %u\n", port);
// turn passive mode on and save passive mode port
s.passiveMode = true;
s.passiveModePort = port;
}
// compile response message
SNPRINTF(ftpsDynResp, FTPS_DYNAMIC_RESPONSE_MAXLEN, "%s (%u,%u,%u,%u,%u,%u)\r\n", // no space in the whole address-port compound
ftpEnteringPassivePrefix, EXPLODE_IPv4(s.intf->ip), (s.passiveModePort >> 8) & 0xFF, s.passiveModePort & 0xFF);
// set response
resp = ftpsDynResp;
} break;
case LIST: { // list files
ETHLIB_OS_SEM_WAIT(s.dataConnHasOpened); // wait for data connection to establish
tcp_send(s.connCtrl, (const uint8_t *)ftpOpeningDataConn, strlen(ftpOpeningDataConn)); // opening data connection
tcp_send(s.connData, (const uint8_t *)"\r\n", 2); // sending listing data
tcp_send(s.connCtrl, (const uint8_t *)ftpClosingDataConn, strlen(ftpClosingDataConn)); // closing data connection
close_connection(s.connData); // actually close connection
s.connData = 0; // clear data connection CBD
break;
}
default: default:
break; break;
} }
@ -187,19 +303,41 @@ resp_and_return:
if (resp != NULL) { if (resp != NULL) {
tcp_send(s.connCtrl, (const uint8_t *)resp, strlen(resp)); tcp_send(s.connCtrl, (const uint8_t *)resp, strlen(resp));
} }
return;
// -----
syntax_error:
resp = ftpSyntaxError;
goto resp_and_return;
cmd_param_not_implemented:
resp = ftpCmdNotImplForParam;
goto resp_and_return;
cmd_ok:
resp = ftpCmdOk;
goto resp_and_return;
} }
void ftps_thread(void *param) { void ftps_thread(void *param) {
// set interface
s.intf = get_default_interface();
// initialize event queue // initialize event queue
s.eventQ = ETHLIB_OS_QUEUE_CREATE(FTPS_EVENT_QUEUE_LENGTH, uint32_t); s.eventQ = ETHLIB_OS_QUEUE_CREATE(FTPS_EVENT_QUEUE_LENGTH, uint32_t);
// initialize control connection // initialize control connection
s.connCtrlS = tcp_new_connblock(get_default_interface(), IPv4_IF_ADDR, FTPS_CTRL_PORT, ftps_ctrl_recv); s.connCtrlS = tcp_new_connblock(s.intf, IPv4_IF_ADDR, FTPS_CTRL_PORT, ftps_ctrl_recv);
tcp_listen(s.connCtrlS); // make it listen for incoming connections tcp_set_accept_callback(s.connCtrlS, ftps_ctrl_accept_cb); // set callback for accepting an incoming connection
tcp_set_accept_callback(s.connCtrlS, ftps_accept_cb); // set callback for accepting an incoming connection tcp_listen(s.connCtrlS); // make it listen for incoming connections
// initialize connection state // initialize connection state
s.connState = FTPS_IDLE; s.connState = FTPS_IDLE;
s.passiveMode = false; // at the beginning we disable passive mode
s.passiveModePort = 0;
// initialize semaphore which synchronizes data connection state
s.dataConnHasOpened = ETHLIB_OS_SEM_CREATE(1);
// process loop // process loop
for (;;) { for (;;) {

View File

@ -10,8 +10,6 @@
#define FTPS_LINEBUF_LEN (63) #define FTPS_LINEBUF_LEN (63)
#define FTPS_ROOT_DIR_NAME "ftp_root"
/** /**
* FTP connection state machine states. * FTP connection state machine states.
*/ */
@ -30,13 +28,20 @@ typedef enum {
FTPEVT_CTRL_RECVD, // a control message has been received FTPEVT_CTRL_RECVD, // a control message has been received
} Ftps_ConnEvents; } Ftps_ConnEvents;
/**
* FTP server state.
*/
typedef struct { typedef struct {
EthInterface * intf; // pointer to associated Ethernet interface
bool passiveMode; // indicates whether the server is operating in passive mode
uint16_t passiveModePort; // passive mode port once the passive mode is activated
cbd connCtrlS; // control connection server cbd connCtrlS; // control connection server
cbd connCtrl; // established control connection cbd connCtrl; // established control connection
cbd connDataS; // data connection server cbd connDataS; // data connection server
cbd connData; // established data connection cbd connData; // established data connection
Ftps_ConnState connState; // connection state Ftps_ConnState connState; // connection state
ETHLIB_OS_QUEUE_TYPE eventQ; // event queue ETHLIB_OS_QUEUE_TYPE eventQ; // event queue
ETHLIB_OS_SEM_TYPE dataConnHasOpened; // semaphore synchronizing data connection opening
char lineBuffer[FTPS_LINEBUF_LEN + 1]; // line buffer char lineBuffer[FTPS_LINEBUF_LEN + 1]; // line buffer
} Ftps_State; } Ftps_State;

View File

@ -11,12 +11,12 @@ typedef int (*ConnBlockTransmitFn)(struct EthInterface_ * intf, const uint8_t *
* Connection block. * Connection block.
*/ */
typedef struct ConnBlock_ { typedef struct ConnBlock_ {
PcktSieve * sieve; ///< Ethernet interface PcktSieve * sieve; ///< Pointer to the packet sieve
PcktSieveLayer * sieveLayer; ///< Sieve layer PcktSieveLayer * sieveLayer; ///< Sieve layer
} ConnBlock; } ConnBlock;
typedef struct ConstConnBlock_ { typedef struct ConstConnBlock_ {
const PcktSieve * sieve; ///< Ethernet interface const PcktSieve * sieve; ///< Pointer to the packet sieve
const PcktSieveLayer * sieveLayer; ///< Sieve layer const PcktSieveLayer * sieveLayer; ///< Sieve layer
} ConstConnBlock; } ConstConnBlock;

View File

@ -91,6 +91,13 @@ void close_connection(cbd d) {
cbdt_release(E.cbdt, d); // release from CBD table cbdt_release(E.cbdt, d); // release from CBD table
// connb_remove() calls pcktsieve_remove_layer() which does not guarantee
// that the layer is instantaneously removed, since some protocols (like TCP)
// must gracefully shutdown the connection and the protocol implementation
// will then remove the layer. As long as the protocol implementation does
// not attempt to use the CBD released above, the cleanup actions will
// execute silently in the background without raising errors.
connb_remove(&connBlock); // remove connection block connb_remove(&connBlock); // remove connection block
} }

View File

@ -257,6 +257,7 @@ PcktSieveLayer* packsieve_new_layer(PcktSieveLayer *parent, const PcktSieveFilte
layer->tag = tag; layer->tag = tag;
layer->connBReportFn = NULL; layer->connBReportFn = NULL;
layer->txTsCb = NULL; layer->txTsCb = NULL;
layer->mgmtCb = NULL;
return layer; return layer;
} }
} }
@ -267,6 +268,13 @@ bool packsieve_remove_layer(const PcktSieveLayer *layer) {
return true; return true;
} }
// dispath management message if callback is defined
if (layer->mgmtCb != NULL) {
if (layer->mgmtCb(layer, PSLEVT_REMOVE) == PSMGMT_RET_ABORT) {
return false;
}
}
// remove parent elements if their only subnode is the one we're deleting // remove parent elements if their only subnode is the one we're deleting
PcktSieveLayer *parent; PcktSieveLayer *parent;
while (layer != NULL && layer->nodes == NULL) { while (layer != NULL && layer->nodes == NULL) {

View File

@ -78,8 +78,28 @@ typedef int (*SieveCallBackFn)(const Pckt *pckt, PcktSieveLayerTag tag);
struct ConnBlock_; struct ConnBlock_;
typedef void (*ConnBlockReportFn)(const struct ConnBlock_ *connBlock); typedef void (*ConnBlockReportFn)(const struct ConnBlock_ *connBlock);
/**
* Timestamp callback function.
*/
typedef void(*TxTimeStampCBFn)(uint32_t ts_s, uint32_t ts_ns, uint32_t tag); typedef void(*TxTimeStampCBFn)(uint32_t ts_s, uint32_t ts_ns, uint32_t tag);
/**
* Management event types.
*/
typedef enum {
PSLEVT_NONE, ///< No event.
PSLEVT_REMOVE ///< The sievele layer is about to get deleted.
} PcktSieveLayerMgmtEvent;
// Management callback return values
#define PSMGMT_RET_PROCEED (0) // proceed with processing
#define PSMGMT_RET_ABORT (1) // abort processing
/**
* Packet sieve layer management function.
*/
typedef int(*MgmtCBFn)(const struct PcktSieveLayer_ * layer, PcktSieveLayerMgmtEvent event);
#define PCKT_SIEVE_INFOTAG_LEN (24) #define PCKT_SIEVE_INFOTAG_LEN (24)
/** /**
@ -97,6 +117,7 @@ typedef struct PcktSieveLayer_ {
struct PcktSieveLayer_ *next, *prev; ///< Next and previous sieve layer on the same level struct PcktSieveLayer_ *next, *prev; ///< Next and previous sieve layer on the same level
struct PcktSieveLayer_ *nodes; ///< Subnodes in the sieve tree struct PcktSieveLayer_ *nodes; ///< Subnodes in the sieve tree
ConnBlockReportFn connBReportFn; ///< Connection block report function pointer ConnBlockReportFn connBReportFn; ///< Connection block report function pointer
MgmtCBFn mgmtCb; ///< Management callback function
} PcktSieveLayer; } PcktSieveLayer;
/** /**

View File

@ -27,7 +27,7 @@ void tcpw_set_seqnum_offset(TcpWindow * tcpw, uint32_t offset) {
tcpw->firstUnACK = offset; tcpw->firstUnACK = offset;
} }
static inline uint32_t tcpw_get_occupied_size(TcpWindow * tcpw) { uint32_t tcpw_get_occupied_size(TcpWindow * tcpw) {
return tcpw->nextWritten - tcpw->firstUnACK; return tcpw->nextWritten - tcpw->firstUnACK;
} }

View File

@ -44,6 +44,15 @@ void tcpw_set_seqnum_offset(TcpWindow * tcpw, uint32_t offset);
*/ */
uint32_t tcpw_get_max_window_size(TcpWindow * tcpw); uint32_t tcpw_get_max_window_size(TcpWindow * tcpw);
/**
* TODO: rename function
* Get used area size of this window.
* @param tcpw pointer to TCP Window object
* @return used area size in bytes
*
*/
uint32_t tcpw_get_occupied_size(TcpWindow * tcpw);
/** TODO: rename function /** TODO: rename function
* Store TCP segment * Store TCP segment
* @param tcpw pointer to TCP Window object * @param tcpw pointer to TCP Window object

View File

@ -45,7 +45,7 @@ static bool filtTcp(const PcktSieveFilterCondition *filtCond, const PcktProps *c
(remote_addr == ipProps->SourceIPAddr)) || // the packet has come from the known origin (remote_addr == ipProps->SourceIPAddr)) || // the packet has come from the known origin
(remote_addr == 0)); // the latter conditions only matter if the control block is connected to a remote station (remote_addr == 0)); // the latter conditions only matter if the control block is connected to a remote station
return ipProps->Protocol == ETH_TCP_PACKET_CLASS && references_us; return (ipProps->Protocol == ETH_TCP_PACKET_CLASS) && references_us;
} }
#define MAX_TCP_WINDOW_SIZE (1200) #define MAX_TCP_WINDOW_SIZE (1200)
@ -53,19 +53,20 @@ static bool filtTcp(const PcktSieveFilterCondition *filtCond, const PcktProps *c
static inline TcpState *tcps_create() { static inline TcpState *tcps_create() {
TcpState *tcps = dynmem_alloc(sizeof(TcpState)); TcpState *tcps = dynmem_alloc(sizeof(TcpState));
uint32_t winSize = MAX(ETHLIB_DEF_TCP_WINDOW_SIZE, MAX_TCP_WINDOW_SIZE); uint32_t winSize = MAX(ETHLIB_DEF_TCP_WINDOW_SIZE, MAX_TCP_WINDOW_SIZE);
tcps->txWin = tcpw_create(winSize); // create transmit window tcps->txWin = tcpw_create(winSize); // create transmit window
tcps->txBufNotFull = ETHLIB_OS_SEM_CREATE(1); // create transmit buffer not full semaphore tcps->txBufNotFull = ETHLIB_OS_SEM_CREATE(1); // create transmit buffer not full semaphore
ETHLIB_OS_SEM_POST(tcps->txBufNotFull); // post the tx buffer semaphore ETHLIB_OS_SEM_POST(tcps->txBufNotFull); // post the tx buffer semaphore
tcps->txInProgress = ETHLIB_OS_SEM_CREATE(1); // create transmission in progress semaphore tcps->txInProgress = ETHLIB_OS_SEM_CREATE(1); // create transmission in progress semaphore
ETHLIB_OS_SEM_POST(tcps->txInProgress); ETHLIB_OS_SEM_POST(tcps->txInProgress); // post the TX in progress, since no transmission is in progress
tcps->eventFlags = osEventFlagsNew(NULL); // create event flags for internal event synchronization
osEventFlagsSet(tcps->eventFlags, EVTFLAG_ALL_ACKED); // everything is ACKed in the beginning
tcps->processMtx = ETHLIB_OS_RECMTX_CREATE(); // create mutex protecting the internal state
return tcps; return tcps;
} }
static inline void tcps_release_client_related_objects(TcpState *tcps) { static inline void tcps_release_client_related_objects(TcpState *tcps) {
tcpw_destroy(tcps->txWin); tcpw_destroy(tcps->txWin);
ETHLIB_OS_SEM_DESTROY(tcps->txBufNotFull); ETHLIB_OS_SEM_DESTROY(tcps->txBufNotFull);
ETHLIB_OS_SEM_DESTROY(tcps->txInProgress);
eth_bfifo_destroy(tcps->rxFifo); eth_bfifo_destroy(tcps->rxFifo);
} }
@ -78,6 +79,10 @@ static inline void tcps_remove_and_cleanup(TcpState *tcps) {
tcps_release_client_related_objects(tcps); tcps_release_client_related_objects(tcps);
} }
ETHLIB_OS_SEM_DESTROY(tcps->txInProgress); // TODO: ezt a szerver bezárásakor nem kell vizsgálni
osEventFlagsDelete(tcps->eventFlags);
ETHLIB_OS_MTX_DESTROY(tcps->processMtx);
dynmem_free(tcps); dynmem_free(tcps);
} }
@ -147,10 +152,76 @@ static inline cbd tcps_create_based_on_listening(const TcpState *source, uint16_
return d; return d;
} }
// ----------------------------
static bool tcp_close(TcpState *tcps) {
TcpConnectionState cs = tcps->connState;
// a closed connection cannot be more closed...
if (cs == TCP_STATE_CLOSED) {
return true;
}
// act according to the current state
switch (cs) {
case TCP_STATE_ESTAB: // close connecion from established state
// take send semaphore
ETHLIB_OS_SEM_WAIT(tcps->txInProgress);
MSG("TX SEM\n");
// wait for the TX FIFO contents to get transmitted
osEventFlagsWait(tcps->eventFlags, EVTFLAG_ALL_ACKED, osFlagsWaitAll | osFlagsNoClear, osWaitForever); // FIXME: ez így nem lesz jó, mert, ha valamiért nem kapjuk meg az ACK-t, akkor itt ragadunk
MSG("FLAG\n");
// lock the mutex -----
ETHLIB_OS_MTX_LOCK(tcps->processMtx);
// --------------------
// send a FIN-ACK segment
tcp_send_segment(&(tcps->connBlock), TCP_FLAG_FIN | TCP_FLAG_ACK, NULL, NULL, 0);
tcps->txSeqNum++; // increase own sequence number
// step into FIN WAIT-1 state
tcps->connState = TCP_STATE_FIN_WAIT_1;
// unlock the mutex -----
ETHLIB_OS_MTX_UNLOCK(tcps->processMtx);
// --------------------
break;
default:
break;
}
return false;
}
int tcp_mgmt_cb(const PcktSieveLayer *layer, PcktSieveLayerMgmtEvent event) {
TcpState *tcps = TCP_FETCH_STATE_FROM_SIEVE_LAYER(layer);
switch (event) {
case PSLEVT_REMOVE: { // connection removal is in progress
return tcp_close(tcps) ? PSMGMT_RET_PROCEED : PSMGMT_RET_ABORT; // abort further removal processing if further steps are required to close the connection
} break;
default:
break;
}
return PSMGMT_RET_PROCEED;
}
// ----------------------------
int tcp_receive_segment_cb(const Pckt *pckt, PcktSieveLayerTag tag) { int tcp_receive_segment_cb(const Pckt *pckt, PcktSieveLayerTag tag) {
TcpState *tcps = (TcpState *)tag.p; // pointer to state is stored in sieve layer 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 TcpProps *tcpProps = HEADER_FETCH_PROPS(TcpProps, pckt->header); // fetch header
// lock the mutex -----
ETHLIB_OS_MTX_LOCK(tcps->processMtx);
// --------------------
TcpConnectionState beginState = tcps->connState; TcpConnectionState beginState = tcps->connState;
IPv4Props *ipProps = HEADER_FETCH_PROPS(IPv4Props, pckt->header->prev); IPv4Props *ipProps = HEADER_FETCH_PROPS(IPv4Props, pckt->header->prev);
@ -175,6 +246,8 @@ int tcp_receive_segment_cb(const Pckt *pckt, PcktSieveLayerTag tag) {
// store rxAckNum // store rxAckNum
tcps->rxAckNum = tcpProps->SequenceNumber + 1; tcps->rxAckNum = tcpProps->SequenceNumber + 1;
// MSG("SYN ACK\n");
// get MSS // get MSS
TcpOption *mss = tcp_get_option_by_kind(tcpProps->options, TCP_OPT_KIND_MSS); TcpOption *mss = tcp_get_option_by_kind(tcpProps->options, TCP_OPT_KIND_MSS);
if (mss != NULL) { // if MSS is included in the segment if (mss != NULL) { // if MSS is included in the segment
@ -205,6 +278,9 @@ int tcp_receive_segment_cb(const Pckt *pckt, PcktSieveLayerTag tag) {
// step into next state // step into next state
tcps->connState = TCP_STATE_ESTAB; tcps->connState = TCP_STATE_ESTAB;
// set event flags
osEventFlagsSet(tcps->eventFlags, EVTFLAG_ESTAB);
} }
} }
@ -247,8 +323,7 @@ int tcp_receive_segment_cb(const Pckt *pckt, PcktSieveLayerTag tag) {
} }
} }
break; } break;
}
case TCP_STATE_ESTAB: /* Connection established */ case TCP_STATE_ESTAB: /* Connection established */
// if the other end tries to close down the connection // if the other end tries to close down the connection
@ -282,6 +357,13 @@ int tcp_receive_segment_cb(const Pckt *pckt, PcktSieveLayerTag tag) {
// release segment from retransmit window // release segment from retransmit window
tcpw_acknowledge(tcps->txWin, tcpProps->AcknowledgementNumber); tcpw_acknowledge(tcps->txWin, tcpProps->AcknowledgementNumber);
// set or clear all ACKed flag according to TX window state
if (tcpw_get_occupied_size(tcps->txWin) == 0) {
osEventFlagsSet(tcps->eventFlags, EVTFLAG_ALL_ACKED);
} else {
osEventFlagsClear(tcps->eventFlags, EVTFLAG_ALL_ACKED);
}
// release the transmit semaphore // release the transmit semaphore
ETHLIB_OS_SEM_POST(tcps->txBufNotFull); ETHLIB_OS_SEM_POST(tcps->txBufNotFull);
@ -296,12 +378,51 @@ int tcp_receive_segment_cb(const Pckt *pckt, PcktSieveLayerTag tag) {
case TCP_STATE_LAST_ACK: /* last ACK received */ case TCP_STATE_LAST_ACK: /* last ACK received */
if (tcpProps->Flags & TCP_FLAG_ACK) { if (tcpProps->Flags & TCP_FLAG_ACK) {
tcps->connState = TCP_STATE_CLOSED; tcps->connState = TCP_STATE_CLOSED;
//ETHLIB_OS_SEM_POST(tcps->txBufNotFull); FIXME! // ETHLIB_OS_SEM_POST(tcps->txBufNotFull); FIXME!
//ETHLIB_OS_SEM_WAIT(tcps->txInProgress); // ETHLIB_OS_SEM_WAIT(tcps->txInProgress);
ret = SIEVE_LAYER_REMOVE_THIS; // if peer closed the connection, remove sieve layer ret = SIEVE_LAYER_REMOVE_THIS; // if peer closed the connection, remove sieve layer
} }
break; break;
case TCP_STATE_FIN_WAIT_1: { /* our FIN has been sent, waiting for the peer's one */
uint16_t flags = tcpProps->Flags;
bool rxACKok = tcpProps->AcknowledgementNumber == tcps->txSeqNum; // check if received ACK was correct
bool sendACK = false; // set if we want to emit an ACK
if ((flags & TCP_FLAG_ACK) && (!(flags & TCP_FLAG_FIN))) { // only ACK received
tcps->connState = TCP_STATE_FIN_WAIT_2; // this case don't send ACK
} else if ((!(flags & TCP_FLAG_ACK)) && (flags & TCP_FLAG_FIN)) { // only FIN received
tcps->connState = TCP_STATE_CLOSING;
sendACK = true;
} else if ((flags & TCP_FLAG_ACK) && (flags & TCP_FLAG_FIN)) { // received ACK and FIN
tcps->connState = TCP_STATE_CLOSED;
ret = SIEVE_LAYER_REMOVE_THIS;
sendACK = true;
}
// send ACK if required and received ACK was correct
if (sendACK && rxACKok) {
tcps->rxAckNum++; // acknowledge the FIN-ACK packet
tcp_send_segment(&(tcps->connBlock), TCP_FLAG_ACK, NULL, NULL, 0);
}
} break;
case TCP_STATE_FIN_WAIT_2: { /* waiting on peer's FIN */
if (tcpProps->Flags & TCP_FLAG_FIN) {
tcps->rxAckNum++; // acknowledge the FIN packet
tcp_send_segment(&(tcps->connBlock), TCP_FLAG_ACK, NULL, NULL, 0);
tcps->connState = TCP_STATE_CLOSED;
ret = SIEVE_LAYER_REMOVE_THIS;
}
} break;
case TCP_STATE_CLOSING: /* waiting on arrival of ACK sent by the peer against our FIN */ /* connection is closing waiting on peers ACK */
if ((tcpProps->Flags & TCP_FLAG_ACK) && (tcpProps->AcknowledgementNumber == tcps->txSeqNum)) { // ACK received
tcps->connState = TCP_STATE_CLOSED;
ret = SIEVE_LAYER_REMOVE_THIS;
}
break;
case TCP_STATE_CLOSED: /* connection is CLOSED */ case TCP_STATE_CLOSED: /* connection is CLOSED */
default: default:
break; break;
@ -323,8 +444,13 @@ int tcp_receive_segment_cb(const Pckt *pckt, PcktSieveLayerTag tag) {
tcpopt_chain_free(tcpProps->options); tcpopt_chain_free(tcpProps->options);
} }
// unlock the mutex -----
ETHLIB_OS_MTX_UNLOCK(tcps->processMtx);
// --------------------
// release descriptor if not meaningful anymore // release descriptor if not meaningful anymore
if (ret == SIEVE_LAYER_REMOVE_THIS) { if (ret == SIEVE_LAYER_REMOVE_THIS) {
tcps->connBlock.sieveLayer->mgmtCb = NULL;
cbdt_release(E.cbdt, tcps->d); // release descriptor cbdt_release(E.cbdt, tcps->d); // release descriptor
tcps_remove_and_cleanup(tcps); // release TCP state tcps_remove_and_cleanup(tcps); // release TCP state
} }
@ -332,6 +458,7 @@ int tcp_receive_segment_cb(const Pckt *pckt, PcktSieveLayerTag tag) {
return ret; return ret;
} }
// TODO: mindent egy függvénybe kellene tenni, mert így nagyon sok versenyhelyet alakulhat ki
void tcp_bind(cbd d, ip4_addr remoteAddr, uint16_t remotePort) { void tcp_bind(cbd d, ip4_addr remoteAddr, uint16_t remotePort) {
ConnBlock connBlock; ConnBlock connBlock;
if (!cbdt_get_connection_block(E.cbdt, d, &connBlock)) { if (!cbdt_get_connection_block(E.cbdt, d, &connBlock)) {
@ -339,8 +466,14 @@ void tcp_bind(cbd d, ip4_addr remoteAddr, uint16_t remotePort) {
return; return;
} }
// store remote station data // fetch TCP state
TcpState *tcps = TCP_FETCH_STATE_FROM_CONNBLOCK(&connBlock); TcpState *tcps = TCP_FETCH_STATE_FROM_CONNBLOCK(&connBlock);
// lock the mutex -----
ETHLIB_OS_MTX_LOCK(tcps->processMtx);
// --------------------
// set peer address and port
tcps->remoteAddr = remoteAddr; tcps->remoteAddr = remoteAddr;
tcps->remotePort = remotePort; tcps->remotePort = remotePort;
@ -359,6 +492,13 @@ void tcp_bind(cbd d, ip4_addr remoteAddr, uint16_t remotePort) {
// step into SYN SENT state // step into SYN SENT state
tcps->connState = TCP_STATE_SYN_SENT; tcps->connState = TCP_STATE_SYN_SENT;
// unlock the mutex -----
ETHLIB_OS_MTX_UNLOCK(tcps->processMtx);
// --------------------
// wait for getting into Established state
osEventFlagsWait(tcps->eventFlags, EVTFLAG_ESTAB, osFlagsWaitAll | osFlagsNoClear, osWaitForever);
} }
void tcp_listen(cbd d) { void tcp_listen(cbd d) {
@ -368,12 +508,22 @@ void tcp_listen(cbd d) {
return; return;
} }
// fetch state
TcpState *tcps = TCP_FETCH_STATE_FROM_CONNBLOCK(&connBlock); TcpState *tcps = TCP_FETCH_STATE_FROM_CONNBLOCK(&connBlock);
// lock the mutex -----
ETHLIB_OS_MTX_LOCK(tcps->processMtx);
// --------------------
if ((tcps->streamCb != NULL) && (tcps->connState == TCP_STATE_CLOSED)) { // step into listen state if ((tcps->streamCb != NULL) && (tcps->connState == TCP_STATE_CLOSED)) { // step into listen state
tcps->connState = TCP_STATE_LISTEN; tcps->connState = TCP_STATE_LISTEN;
tcps->isServer = true; tcps->isServer = true;
tcps_release_client_related_objects(tcps); tcps_release_client_related_objects(tcps); // release client-only resources
} }
// unlock the mutex -----
ETHLIB_OS_MTX_UNLOCK(tcps->processMtx);
// --------------------
} }
void tcp_set_accept_callback(cbd d, TcpAcceptCbFn acb) { void tcp_set_accept_callback(cbd d, TcpAcceptCbFn acb) {
@ -415,6 +565,7 @@ static inline cbd tcp_new_connblock_filtcond(EthInterface *intf, ip4_addr ipAddr
tcpConnB.sieveLayer = packsieve_new_layer(ipConnB.sieveLayer, filtCond, false, filtTcp, tcp_receive_segment_cb, tag, ETH_TCP_PACKET_CLASS); tcpConnB.sieveLayer = packsieve_new_layer(ipConnB.sieveLayer, filtCond, false, filtTcp, tcp_receive_segment_cb, tag, ETH_TCP_PACKET_CLASS);
ASSERT_NULL(tcpConnB.sieveLayer); ASSERT_NULL(tcpConnB.sieveLayer);
tcpConnB.sieveLayer->connBReportFn = tcp_print_report; tcpConnB.sieveLayer->connBReportFn = tcp_print_report;
tcpConnB.sieveLayer->mgmtCb = tcp_mgmt_cb;
tcpConnB.sieve = &intf->sieve; tcpConnB.sieve = &intf->sieve;
tcpState->connBlock = tcpConnB; // also store connection block parameters tcpState->connBlock = tcpConnB; // also store connection block parameters
@ -565,6 +716,9 @@ uint32_t tcp_send(cbd d, const uint8_t *data, uint32_t size) {
// store segment for possible retransmission // store segment for possible retransmission
uint32_t txSize = tcpw_store(tcps->txWin, data, sizeLeft, tcps->txSeqNum); uint32_t txSize = tcpw_store(tcps->txWin, data, sizeLeft, tcps->txSeqNum);
// clear all ACK-ed flag
osEventFlagsClear(tcps->eventFlags, EVTFLAG_ALL_ACKED);
// send segment with data (no options) // send segment with data (no options)
tcp_send_segment(&connBlock, TCP_FLAG_ACK, NULL, data, txSize); tcp_send_segment(&connBlock, TCP_FLAG_ACK, NULL, data, txSize);

View File

@ -52,6 +52,9 @@ typedef enum {
typedef int (*StreamCallBackFn)(cbd d); typedef int (*StreamCallBackFn)(cbd d);
typedef int (*TcpAcceptCbFn)(cbd d); typedef int (*TcpAcceptCbFn)(cbd d);
#define EVTFLAG_ESTAB (0x00000001)
#define EVTFLAG_ALL_ACKED (0x00000002)
/** /**
* TCP state. * TCP state.
*/ */
@ -103,11 +106,20 @@ typedef struct {
// --------------- // ---------------
ETHLIB_OS_MTX_TYPE processMtx; ///< a mutex protecting TCP state changes (this is how we can manipulate the internal state without raising race conditions)
// ---------------
osEventFlagsId_t eventFlags; ///< Semaphore for synchronizing on getting into established state
// ---------------
EthBlockingFifo * rxFifo; ///< Receive FIFO EthBlockingFifo * rxFifo; ///< Receive FIFO
} TcpState; } TcpState;
#define TCP_FETCH_STATE_FROM_CONNBLOCK(connBlock) ((TcpState *) (connBlock)->sieveLayer->tag.p) #define TCP_FETCH_STATE_FROM_CONNBLOCK(connBlock) ((TcpState *) (connBlock)->sieveLayer->tag.p)
#define TCP_FETCH_STATE_FROM_SIEVE_LAYER(layer) ((TcpState *) (layer)->tag.p)
/** /**
* Create new TCP connection block * Create new TCP connection block