diff --git a/apps/ftp_server.c b/apps/ftp_server.c index 66bb0d8..b630b6d 100644 --- a/apps/ftp_server.c +++ b/apps/ftp_server.c @@ -1,6 +1,7 @@ #include "ftp_server.h" #include +#include #include @@ -8,10 +9,21 @@ /* FTP response messages */ -static char ftpGreeting[] = "220 Service ready for new user.\r\n"; -static char ftpNotImplemented[] = "502 Command not implemented.\r\n"; -static char ftpLoginOK[] = "230 User logged in, proceed.\r\n"; -static char ftpRootDirName[] = "257 \"/\" is the current directory\r\n"; +static const char ftpOpeningDataConn[] = "150 Opening data connection.\r\n"; +static const char ftpCmdOk[] = "200 Command OK.\r\n"; +static const char ftpGreeting[] = "220 Service ready for new user.\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 */ @@ -29,6 +41,9 @@ static char *ftpImplementedCmdsStr[] = { // ------ additional commands "PWD", + "CWD", + "PASV", + "LIST", "\0" // SENTRY! }; @@ -45,7 +60,10 @@ typedef enum { // ordering must be the same as in the string version above NOOP, // ------ additional commands - PWD + PWD, + CWD, + PASV, + LIST } Ftp_ImplementedCmds; // ----------- @@ -61,7 +79,7 @@ int ftps_ctrl_recv(cbd d) { return 0; } -int ftps_accept_cb(cbd d) { +int ftps_ctrl_accept_cb(cbd d) { s.connCtrl = d; uint32_t event = FTPEVT_NEW_CONNECTION; ETHLIB_OS_QUEUE_PUSH(s.eventQ, &event); @@ -73,12 +91,36 @@ void ftps_thread(void *param); void ftps_init() { osThreadAttr_t attr; memset(&attr, 0, sizeof(osThreadAttr_t)); - attr.stack_size = 2048; + attr.stack_size = 3072; attr.priority = osPriorityNormal; attr.name = "ftp"; 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) { char *iter = ftpImplementedCmdsStr[0]; bool ret = false; @@ -109,14 +151,29 @@ bool ftps_is_command_implemented(char *cmd, uint32_t len, uint32_t *cmdCode) { return ret; } -void ftps_rtrim(char * str) { - char * iter = str + strlen(str) - 1; +void ftps_rtrim(char *str) { + char *iter = str + strlen(str) - 1; while ((*iter <= ' ') && (iter >= str)) { *iter = '\0'; 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) { const char *resp = NULL; // response towards the FTP client char *cmdArg = NULL; // command argument @@ -169,12 +226,71 @@ void ftps_process_event(uint32_t event) { s.connState = FTPS_ESTABLISHED; } } else if (s.connState == FTPS_ESTABLISHED) { - switch (cmdCode) - { + switch (cmdCode) { case PWD: // print working directory name resp = ftpRootDirName; 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: break; } @@ -187,19 +303,41 @@ resp_and_return: if (resp != NULL) { 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) { + // set interface + s.intf = get_default_interface(); + // initialize event queue s.eventQ = ETHLIB_OS_QUEUE_CREATE(FTPS_EVENT_QUEUE_LENGTH, uint32_t); // initialize control connection - s.connCtrlS = tcp_new_connblock(get_default_interface(), 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_accept_cb); // set callback for accepting an incoming connection + s.connCtrlS = tcp_new_connblock(s.intf, IPv4_IF_ADDR, FTPS_CTRL_PORT, ftps_ctrl_recv); + tcp_set_accept_callback(s.connCtrlS, ftps_ctrl_accept_cb); // set callback for accepting an incoming connection + tcp_listen(s.connCtrlS); // make it listen for incoming connections // initialize connection state 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 for (;;) { diff --git a/apps/ftp_server.h b/apps/ftp_server.h index 987135a..219aa79 100644 --- a/apps/ftp_server.h +++ b/apps/ftp_server.h @@ -10,8 +10,6 @@ #define FTPS_LINEBUF_LEN (63) -#define FTPS_ROOT_DIR_NAME "ftp_root" - /** * FTP connection state machine states. */ @@ -30,13 +28,20 @@ typedef enum { FTPEVT_CTRL_RECVD, // a control message has been received } Ftps_ConnEvents; +/** + * FTP server state. +*/ 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 connCtrl; // established control connection cbd connDataS; // data connection server cbd connData; // established data connection Ftps_ConnState connState; // connection state 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 } Ftps_State; diff --git a/connection_block.h b/connection_block.h index 2b9bc4c..45cc2f8 100644 --- a/connection_block.h +++ b/connection_block.h @@ -11,12 +11,12 @@ typedef int (*ConnBlockTransmitFn)(struct EthInterface_ * intf, const uint8_t * * Connection block. */ typedef struct ConnBlock_ { - PcktSieve * sieve; ///< Ethernet interface + PcktSieve * sieve; ///< Pointer to the packet sieve PcktSieveLayer * sieveLayer; ///< Sieve layer } ConnBlock; typedef struct ConstConnBlock_ { - const PcktSieve * sieve; ///< Ethernet interface + const PcktSieve * sieve; ///< Pointer to the packet sieve const PcktSieveLayer * sieveLayer; ///< Sieve layer } ConstConnBlock; diff --git a/global_state.c b/global_state.c index 982c2f2..46991a2 100644 --- a/global_state.c +++ b/global_state.c @@ -91,6 +91,13 @@ void close_connection(cbd d) { 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 } diff --git a/packet_sieve.c b/packet_sieve.c index 2ec171a..7ec77bb 100644 --- a/packet_sieve.c +++ b/packet_sieve.c @@ -257,6 +257,7 @@ PcktSieveLayer* packsieve_new_layer(PcktSieveLayer *parent, const PcktSieveFilte layer->tag = tag; layer->connBReportFn = NULL; layer->txTsCb = NULL; + layer->mgmtCb = NULL; return layer; } } @@ -267,6 +268,13 @@ bool packsieve_remove_layer(const PcktSieveLayer *layer) { 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 PcktSieveLayer *parent; while (layer != NULL && layer->nodes == NULL) { diff --git a/packet_sieve.h b/packet_sieve.h index ee108a1..4ffe773 100644 --- a/packet_sieve.h +++ b/packet_sieve.h @@ -78,8 +78,28 @@ typedef int (*SieveCallBackFn)(const Pckt *pckt, PcktSieveLayerTag tag); struct 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); +/** + * 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) /** @@ -97,6 +117,7 @@ typedef struct PcktSieveLayer_ { struct PcktSieveLayer_ *next, *prev; ///< Next and previous sieve layer on the same level struct PcktSieveLayer_ *nodes; ///< Subnodes in the sieve tree ConnBlockReportFn connBReportFn; ///< Connection block report function pointer + MgmtCBFn mgmtCb; ///< Management callback function } PcktSieveLayer; /** diff --git a/prefab/conn_blocks/tcp/tcp_window.c b/prefab/conn_blocks/tcp/tcp_window.c index d95f73f..433136e 100644 --- a/prefab/conn_blocks/tcp/tcp_window.c +++ b/prefab/conn_blocks/tcp/tcp_window.c @@ -27,7 +27,7 @@ void tcpw_set_seqnum_offset(TcpWindow * tcpw, uint32_t 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; } diff --git a/prefab/conn_blocks/tcp/tcp_window.h b/prefab/conn_blocks/tcp/tcp_window.h index 5f3cda2..4d8a361 100644 --- a/prefab/conn_blocks/tcp/tcp_window.h +++ b/prefab/conn_blocks/tcp/tcp_window.h @@ -44,6 +44,15 @@ void tcpw_set_seqnum_offset(TcpWindow * tcpw, uint32_t offset); */ 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 * Store TCP segment * @param tcpw pointer to TCP Window object diff --git a/prefab/conn_blocks/tcp_connblock.c b/prefab/conn_blocks/tcp_connblock.c index 934a8c1..29f7ea3 100644 --- a/prefab/conn_blocks/tcp_connblock.c +++ b/prefab/conn_blocks/tcp_connblock.c @@ -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 == 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) @@ -53,19 +53,20 @@ static bool filtTcp(const PcktSieveFilterCondition *filtCond, const PcktProps *c static inline TcpState *tcps_create() { TcpState *tcps = dynmem_alloc(sizeof(TcpState)); uint32_t winSize = MAX(ETHLIB_DEF_TCP_WINDOW_SIZE, MAX_TCP_WINDOW_SIZE); - tcps->txWin = tcpw_create(winSize); // create transmit window - tcps->txBufNotFull = ETHLIB_OS_SEM_CREATE(1); // create transmit buffer not full semaphore - ETHLIB_OS_SEM_POST(tcps->txBufNotFull); // post the tx buffer semaphore - tcps->txInProgress = ETHLIB_OS_SEM_CREATE(1); // create transmission in progress semaphore - ETHLIB_OS_SEM_POST(tcps->txInProgress); - + tcps->txWin = tcpw_create(winSize); // create transmit window + tcps->txBufNotFull = ETHLIB_OS_SEM_CREATE(1); // create transmit buffer not full semaphore + ETHLIB_OS_SEM_POST(tcps->txBufNotFull); // post the tx buffer semaphore + tcps->txInProgress = ETHLIB_OS_SEM_CREATE(1); // create transmission in progress semaphore + 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; } static inline void tcps_release_client_related_objects(TcpState *tcps) { tcpw_destroy(tcps->txWin); ETHLIB_OS_SEM_DESTROY(tcps->txBufNotFull); - ETHLIB_OS_SEM_DESTROY(tcps->txInProgress); eth_bfifo_destroy(tcps->rxFifo); } @@ -78,6 +79,10 @@ static inline void tcps_remove_and_cleanup(TcpState *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); } @@ -147,10 +152,76 @@ static inline cbd tcps_create_based_on_listening(const TcpState *source, uint16_ 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) { TcpState *tcps = (TcpState *)tag.p; // pointer to state is stored in sieve layer tag TcpProps *tcpProps = HEADER_FETCH_PROPS(TcpProps, pckt->header); // fetch header + // lock the mutex ----- + ETHLIB_OS_MTX_LOCK(tcps->processMtx); + // -------------------- + TcpConnectionState beginState = tcps->connState; 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 tcps->rxAckNum = tcpProps->SequenceNumber + 1; + // MSG("SYN ACK\n"); + // get MSS TcpOption *mss = tcp_get_option_by_kind(tcpProps->options, TCP_OPT_KIND_MSS); 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 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 */ // 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 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 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 */ if (tcpProps->Flags & TCP_FLAG_ACK) { tcps->connState = TCP_STATE_CLOSED; - //ETHLIB_OS_SEM_POST(tcps->txBufNotFull); FIXME! - //ETHLIB_OS_SEM_WAIT(tcps->txInProgress); + // ETHLIB_OS_SEM_POST(tcps->txBufNotFull); FIXME! + // ETHLIB_OS_SEM_WAIT(tcps->txInProgress); ret = SIEVE_LAYER_REMOVE_THIS; // if peer closed the connection, remove sieve layer } 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 */ default: break; @@ -323,8 +444,13 @@ int tcp_receive_segment_cb(const Pckt *pckt, PcktSieveLayerTag tag) { tcpopt_chain_free(tcpProps->options); } + // unlock the mutex ----- + ETHLIB_OS_MTX_UNLOCK(tcps->processMtx); + // -------------------- + // release descriptor if not meaningful anymore if (ret == SIEVE_LAYER_REMOVE_THIS) { + tcps->connBlock.sieveLayer->mgmtCb = NULL; cbdt_release(E.cbdt, tcps->d); // release descriptor tcps_remove_and_cleanup(tcps); // release TCP state } @@ -332,6 +458,7 @@ int tcp_receive_segment_cb(const Pckt *pckt, PcktSieveLayerTag tag) { 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) { ConnBlock 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; } - // store remote station data + // fetch TCP state 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->remotePort = remotePort; @@ -359,6 +492,13 @@ void tcp_bind(cbd d, ip4_addr remoteAddr, uint16_t remotePort) { // step into SYN SENT state 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) { @@ -368,12 +508,22 @@ void tcp_listen(cbd d) { return; } + // fetch state 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 tcps->connState = TCP_STATE_LISTEN; 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) { @@ -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); ASSERT_NULL(tcpConnB.sieveLayer); tcpConnB.sieveLayer->connBReportFn = tcp_print_report; + tcpConnB.sieveLayer->mgmtCb = tcp_mgmt_cb; tcpConnB.sieve = &intf->sieve; 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 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) tcp_send_segment(&connBlock, TCP_FLAG_ACK, NULL, data, txSize); diff --git a/prefab/conn_blocks/tcp_connblock.h b/prefab/conn_blocks/tcp_connblock.h index cf419a4..d094ee5 100644 --- a/prefab/conn_blocks/tcp_connblock.h +++ b/prefab/conn_blocks/tcp_connblock.h @@ -52,6 +52,9 @@ typedef enum { typedef int (*StreamCallBackFn)(cbd d); typedef int (*TcpAcceptCbFn)(cbd d); +#define EVTFLAG_ESTAB (0x00000001) +#define EVTFLAG_ALL_ACKED (0x00000002) + /** * 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 } TcpState; #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