error_t socketRegisterEvents(Socket *socket, OsEvent *event, uint_t eventMask) { //Make sure the socket handle is valid if(!socket) return ERROR_INVALID_PARAMETER; //Enter critical section osAcquireMutex(&socketMutex); //An user event may have been previously registered... if(socket->userEvent != NULL) socket->eventMask |= eventMask; else socket->eventMask = eventMask; //Suscribe to get notified of events socket->userEvent = event; #if (TCP_SUPPORT == ENABLED) //Handle TCP specific events if(socket->type == SOCKET_TYPE_STREAM) { tcpUpdateEvents(socket); } #endif #if (UDP_SUPPORT == ENABLED) //Handle UDP specific events if(socket->type == SOCKET_TYPE_DGRAM) { udpUpdateEvents(socket); } #endif #if (RAW_SOCKET_SUPPORT == ENABLED) //Handle events that are specific to raw sockets if(socket->type == SOCKET_TYPE_RAW_IP || socket->type == SOCKET_TYPE_RAW_ETH) { rawSocketUpdateEvents(socket); } #endif //Leave critical section osReleaseMutex(&socketMutex); //Successful processing return NO_ERROR; }
void tcpStateListen(Socket *socket, NetInterface *interface, IpPseudoHeader *pseudoHeader, TcpHeader *segment, size_t length) { uint_t i; TcpOption *option; TcpSynQueueItem *queueItem; //Debug message TRACE_DEBUG("TCP FSM: LISTEN state\r\n"); //An incoming RST should be ignored if(segment->flags & TCP_FLAG_RST) return; //Any acknowledgment is bad if it arrives on a connection //still in the LISTEN state if(segment->flags & TCP_FLAG_ACK) { //A reset segment should be formed for any arriving ACK-bearing segment tcpSendResetSegment(interface, pseudoHeader, segment, length); //Return immediately return; } //Check the SYN bit if(segment->flags & TCP_FLAG_SYN) { //The SYN queue is empty? if(!socket->synQueue) { //Allocate memory to save incoming data queueItem = memPoolAlloc(sizeof(TcpSynQueueItem)); //Add the newly created item to the queue socket->synQueue = queueItem; } else { //Point to the very first item queueItem = socket->synQueue; //Reach the last item in the receive queue for(i = 1; queueItem->next; i++) queueItem = queueItem->next; //Make sure the receive queue is not full if(i >= socket->synQueueSize) return; //Allocate memory to save incoming data queueItem->next = memPoolAlloc(sizeof(TcpSynQueueItem)); //Point to the newly created item queueItem = queueItem->next; } //Failed to allocate memory? if(!queueItem) return; #if (IPV4_SUPPORT == ENABLED) //IPv4 is currently used? if(pseudoHeader->length == sizeof(Ipv4PseudoHeader)) { //Save the source IPv4 address queueItem->srcAddr.length = sizeof(Ipv4Addr); queueItem->srcAddr.ipv4Addr = pseudoHeader->ipv4Data.srcAddr; //Save the destination IPv4 address queueItem->destAddr.length = sizeof(Ipv4Addr); queueItem->destAddr.ipv4Addr = pseudoHeader->ipv4Data.destAddr; } else #endif #if (IPV6_SUPPORT == ENABLED) //IPv6 is currently used? if(pseudoHeader->length == sizeof(Ipv6PseudoHeader)) { //Save the source IPv6 address queueItem->srcAddr.length = sizeof(Ipv6Addr); queueItem->srcAddr.ipv6Addr = pseudoHeader->ipv6Data.srcAddr; //Save the destination IPv6 address queueItem->destAddr.length = sizeof(Ipv6Addr); queueItem->destAddr.ipv6Addr = pseudoHeader->ipv6Data.destAddr; } else #endif //Invalid pseudo header? { //This should never occur... return; } //Initialize next field queueItem->next = NULL; //Underlying network interface queueItem->interface = interface; //Save the port number of the client queueItem->srcPort = segment->srcPort; //Save the initial sequence number queueItem->isn = segment->seqNum; //Default MSS value queueItem->mss = MIN(TCP_DEFAULT_MSS, TCP_MAX_MSS); //Get the maximum segment size option = tcpGetOption(segment, TCP_OPTION_MAX_SEGMENT_SIZE); //Specified option found? if(option != NULL && option->length == 4) { //Retrieve MSS value memcpy(&queueItem->mss, option->value, 2); //Convert from network byte order to host byte order queueItem->mss = ntohs(queueItem->mss); //Debug message TRACE_DEBUG("Remote host MSS = %" PRIu16 "\r\n", queueItem->mss); //Make sure that the MSS advertised by the peer is acceptable queueItem->mss = MIN(queueItem->mss, TCP_MAX_MSS); queueItem->mss = MAX(queueItem->mss, TCP_MIN_MSS); } //Notify user that a connection request is pending tcpUpdateEvents(socket); //The rest of the processing described in RFC 793 will be done //asynchronously when socketAccept() function is called } }
error_t tcpReceive(Socket *socket, uint8_t *data, size_t size, size_t *received, uint_t flags) { uint_t i; uint_t n; uint_t event; uint32_t seqNum; systime_t timeout; //Retrieve the break character code char_t c = LSB(flags); //No data has been read yet *received = 0; //Check whether the socket is in the listening state if(socket->state == TCP_STATE_LISTEN) return ERROR_NOT_CONNECTED; //Read as much data as possible while(*received < size) { //The SOCKET_FLAG_DONT_WAIT enables non-blocking operation timeout = (flags & SOCKET_FLAG_DONT_WAIT) ? 0 : socket->timeout; //Wait for data to be available for reading event = tcpWaitForEvents(socket, SOCKET_EVENT_RX_READY, timeout); //A timeout exception occurred? if(event != SOCKET_EVENT_RX_READY) return ERROR_TIMEOUT; //Check current TCP state switch(socket->state) { //ESTABLISHED, FIN-WAIT-1 or FIN-WAIT-2 state? case TCP_STATE_ESTABLISHED: case TCP_STATE_FIN_WAIT_1: case TCP_STATE_FIN_WAIT_2: //Sequence number of the first byte to read seqNum = socket->rcvNxt - socket->rcvUser; //Data is available in the receive buffer break; //CLOSE-WAIT, LAST-ACK, CLOSING or TIME-WAIT state? case TCP_STATE_CLOSE_WAIT: case TCP_STATE_LAST_ACK: case TCP_STATE_CLOSING: case TCP_STATE_TIME_WAIT: //The user must be satisfied with data already on hand if(!socket->rcvUser) { if(*received > 0) return NO_ERROR; else return ERROR_END_OF_STREAM; } //Sequence number of the first byte to read seqNum = (socket->rcvNxt - 1) - socket->rcvUser; //Data is available in the receive buffer break; //CLOSED state? default: //The connection was reset by remote side? if(socket->resetFlag) return ERROR_CONNECTION_RESET; //The connection has not yet been established? if(!socket->closedFlag) return ERROR_NOT_CONNECTED; //The user must be satisfied with data already on hand if(!socket->rcvUser) { if(*received > 0) return NO_ERROR; else return ERROR_END_OF_STREAM; } //Sequence number of the first byte to read seqNum = (socket->rcvNxt - 1) - socket->rcvUser; //Data is available in the receive buffer break; } //Sanity check if(!socket->rcvUser) return ERROR_FAILURE; //Calculate the number of bytes to read at a time n = MIN(socket->rcvUser, size - *received); //Copy data from circular buffer tcpReadRxBuffer(socket, seqNum, data, n); //Read data until a break character is encountered? if(flags & SOCKET_FLAG_BREAK_CHAR) { //Search for the specified break character for(i = 0; i < n && data[i] != c; i++); //Adjust the number of data to read n = MIN(n, i + 1); } //Total number of data that have been read *received += n; //Remaining data still available in the receive buffer socket->rcvUser -= n; //Update the receive window tcpUpdateReceiveWindow(socket); //Update RX event state tcpUpdateEvents(socket); //The SOCKET_FLAG_BREAK_CHAR flag causes the function to stop reading //data as soon as the specified break character is encountered if(flags & SOCKET_FLAG_BREAK_CHAR) { //Check whether a break character has been found if(data[n - 1] == c) break; } //The SOCKET_FLAG_WAIT_ALL flag causes the function to return //only when the requested number of bytes have been read else if(!(flags & SOCKET_FLAG_WAIT_ALL)) { break; } //Advance data pointer data += n; } //Successful read operation return NO_ERROR; }
error_t tcpSend(Socket *socket, const uint8_t *data, size_t length, size_t *written, uint_t flags) { uint_t n; uint_t totalLength; uint_t event; //Check whether the socket is in the listening state if(socket->state == TCP_STATE_LISTEN) return ERROR_NOT_CONNECTED; //Actual number of bytes written totalLength = 0; //Send as much data as possible do { //Wait until there is more room in the send buffer event = tcpWaitForEvents(socket, SOCKET_EVENT_TX_READY, socket->timeout); //A timeout exception occurred? if(event != SOCKET_EVENT_TX_READY) return ERROR_TIMEOUT; //Check current TCP state switch(socket->state) { //ESTABLISHED or CLOSE-WAIT state? case TCP_STATE_ESTABLISHED: case TCP_STATE_CLOSE_WAIT: //The send buffer is now available for writing break; //LAST-ACK, FIN-WAIT-1, FIN-WAIT-2, CLOSING or TIME-WAIT state? case TCP_STATE_LAST_ACK: case TCP_STATE_FIN_WAIT_1: case TCP_STATE_FIN_WAIT_2: case TCP_STATE_CLOSING: case TCP_STATE_TIME_WAIT: //The connection is being closed return ERROR_CONNECTION_CLOSING; //CLOSED state? default: //The connection was reset by remote side? return (socket->resetFlag) ? ERROR_CONNECTION_RESET : ERROR_NOT_CONNECTED; } //Determine the actual number of bytes in the send buffer n = socket->sndUser + socket->sndNxt - socket->sndUna; //Exit immediately if the transmission buffer is full (sanity check) if(n >= socket->txBufferSize) return ERROR_FAILURE; //Number of bytes available for writing n = socket->txBufferSize - n; //Calculate the number of bytes to copy at a time n = MIN(n, length - totalLength); //Any data to copy? if(n > 0) { //Copy user data to send buffer tcpWriteTxBuffer(socket, socket->sndNxt + socket->sndUser, data, n); //Update the number of data buffered but not yet sent socket->sndUser += n; //Advance data pointer data += n; //Update byte counter totalLength += n; //Total number of data that have been written if(written != NULL) *written = totalLength; //Update TX events tcpUpdateEvents(socket); //To avoid a deadlock, it is necessary to have a timeout to force //transmission of data, overriding the SWS avoidance algorithm. In //practice, this timeout should seldom occur (see RFC 1122 4.2.3.4) if(socket->sndUser == n) tcpTimerStart(&socket->overrideTimer, TCP_OVERRIDE_TIMEOUT); } //The Nagle algorithm should be implemented to coalesce //short segments (refer to RFC 1122 4.2.3.4) tcpNagleAlgo(socket, flags); //Send as much data as possible } while(totalLength < length); //The SOCKET_FLAG_WAIT_ACK flag causes the function to //wait for acknowledgement from the remote side if(flags & SOCKET_FLAG_WAIT_ACK) { //Wait for the data to be acknowledged event = tcpWaitForEvents(socket, SOCKET_EVENT_TX_ACKED, socket->timeout); //A timeout exception occurred? if(event != SOCKET_EVENT_TX_ACKED) return ERROR_TIMEOUT; //The connection was closed before an acknowledgement was received? if(socket->state != TCP_STATE_ESTABLISHED && socket->state != TCP_STATE_CLOSE_WAIT) return ERROR_NOT_CONNECTED; } //Successful write operation return NO_ERROR; }
Socket *tcpAccept(Socket *socket, IpAddr *clientIpAddr, uint16_t *clientPort) { error_t error; Socket *newSocket; TcpSynQueueItem *queueItem; //Ensure the socket was previously placed in the listening state if(tcpGetState(socket) != TCP_STATE_LISTEN) return NULL; //Enter critical section osAcquireMutex(&socketMutex); //Wait for an connection attempt while(1) { //The SYN queue is empty? if(!socket->synQueue) { //Set the events the application is interested in socket->eventMask = SOCKET_EVENT_RX_READY; //Reset the event object osResetEvent(&socket->event); //Leave critical section osReleaseMutex(&socketMutex); //Wait until a SYN message is received from a client osWaitForEvent(&socket->event, socket->timeout); //Enter critical section osAcquireMutex(&socketMutex); } //Check whether the queue is still empty if(!socket->synQueue) { //Timeout error newSocket = NULL; //Exit immediately break; } //Point to the first item in the receive queue queueItem = socket->synQueue; //Return the client IP address and port number if(clientIpAddr) *clientIpAddr = queueItem->srcAddr; if(clientPort) *clientPort = queueItem->srcPort; //Leave critical section osReleaseMutex(&socketMutex); //Create a new socket to handle the incoming connection request newSocket = socketOpen(SOCKET_TYPE_STREAM, SOCKET_IP_PROTO_TCP); //Enter critical section osAcquireMutex(&socketMutex); //Socket successfully created? if(newSocket != NULL) { //The user owns the socket newSocket->ownedFlag = TRUE; //Inherit settings from the listening socket newSocket->txBufferSize = socket->txBufferSize; newSocket->rxBufferSize = socket->rxBufferSize; //Number of chunks that comprise the TX and the RX buffers newSocket->txBuffer.maxChunkCount = arraysize(newSocket->txBuffer.chunk); newSocket->rxBuffer.maxChunkCount = arraysize(newSocket->rxBuffer.chunk); //Allocate transmit buffer error = netBufferSetLength((NetBuffer *) &newSocket->txBuffer, newSocket->txBufferSize); //Check status code if(!error) { //Allocate receive buffer error = netBufferSetLength((NetBuffer *) &newSocket->rxBuffer, newSocket->rxBufferSize); } //Transmit and receive buffers successfully allocated? if(!error) { //Bind the newly created socket to the appropriate interface newSocket->interface = queueItem->interface; //Bind the socket to the specified address newSocket->localIpAddr = queueItem->destAddr; newSocket->localPort = socket->localPort; //Save the port number and the IP address of the remote host newSocket->remoteIpAddr = queueItem->srcAddr; newSocket->remotePort = queueItem->srcPort; //Save the maximum segment size newSocket->mss = queueItem->mss; //Initialize TCP control block newSocket->iss = netGetRand(); newSocket->irs = queueItem->isn; newSocket->sndUna = newSocket->iss; newSocket->sndNxt = newSocket->iss + 1; newSocket->rcvNxt = newSocket->irs + 1; newSocket->rcvUser = 0; newSocket->rcvWnd = newSocket->rxBufferSize; //Default retransmission timeout newSocket->rto = TCP_INITIAL_RTO; #if (TCP_CONGESTION_CONTROL_SUPPORT == ENABLED) //Initial congestion window newSocket->cwnd = MIN(TCP_INITIAL_WINDOW * newSocket->mss, newSocket->txBufferSize); //Slow start threshold should be set arbitrarily high newSocket->ssthresh = UINT16_MAX; #endif //Send a SYN ACK control segment error = tcpSendSegment(newSocket, TCP_FLAG_SYN | TCP_FLAG_ACK, newSocket->iss, newSocket->rcvNxt, 0, TRUE); //TCP segment successfully sent? if(!error) { //Remove the item from the SYN queue socket->synQueue = queueItem->next; //Deallocate memory buffer memPoolFree(queueItem); //Update the state of events tcpUpdateEvents(socket); //The connection state should be changed to SYN-RECEIVED tcpChangeState(newSocket, TCP_STATE_SYN_RECEIVED); //We are done... break; } } //Dispose the socket tcpAbort(newSocket); } //Debug message TRACE_WARNING("Cannot accept TCP connection!\r\n"); //Remove the item from the SYN queue socket->synQueue = queueItem->next; //Deallocate memory buffer memPoolFree(queueItem); //Wait for the next connection attempt } //Leave critical section osReleaseMutex(&socketMutex); //Return a handle to the newly created socket return newSocket; }
void tcpTick(void) { error_t error; uint_t i; uint_t n; uint_t u; //Enter critical section osAcquireMutex(&socketMutex); //Loop through opened sockets for(i = 0; i < SOCKET_MAX_COUNT; i++) { //Shortcut to the current socket Socket *socket = socketTable + i; //Check socket type if(socket->type != SOCKET_TYPE_STREAM) continue; //Check the current state of the TCP state machine if(socket->state == TCP_STATE_CLOSED) continue; //Is there any packet in the retransmission queue? if(socket->retransmitQueue != NULL) { //Retransmission timeout? if(tcpTimerElapsed(&socket->retransmitTimer)) { //When a TCP sender detects segment loss using the retransmission //timer and the given segment has not yet been resent by way of //the retransmission timer, the value of ssthresh must be updated if(!socket->retransmitCount) { //Amount of data that has been sent but not yet acknowledged uint_t flightSize = socket->sndNxt - socket->sndUna; //Adjust ssthresh value socket->ssthresh = MAX(flightSize / 2, 2 * socket->mss); } //Furthermore, upon a timeout cwnd must be set to no more than //the loss window, LW, which equals 1 full-sized segment socket->cwnd = MIN(TCP_LOSS_WINDOW * socket->mss, socket->txBufferSize); //Make sure the maximum number of retransmissions has not been reached if(socket->retransmitCount < TCP_MAX_RETRIES) { //Debug message TRACE_INFO("%s: TCP segment retransmission #%u (%u data bytes)...\r\n", formatSystemTime(osGetSystemTime(), NULL), socket->retransmitCount + 1, socket->retransmitQueue->length); //Retransmit the earliest segment that has not been //acknowledged by the TCP receiver tcpRetransmitSegment(socket); //Use exponential back-off algorithm to calculate the new RTO socket->rto = MIN(socket->rto * 2, TCP_MAX_RTO); //Restart retransmission timer tcpTimerStart(&socket->retransmitTimer, socket->rto); //Increment retransmission counter socket->retransmitCount++; } else { //The maximum number of retransmissions has been exceeded tcpChangeState(socket, TCP_STATE_CLOSED); //Turn off the retransmission timer tcpTimerStop(&socket->retransmitTimer); } //TCP must use Karn's algorithm for taking RTT samples. That is, RTT //samples must not be made using segments that were retransmitted socket->rttBusy = FALSE; } } //Check the current state of the TCP state machine if(socket->state == TCP_STATE_CLOSED) continue; //The persist timer is used when the remote host advertises //a window size of zero if(!socket->sndWnd && socket->wndProbeInterval) { //Time to send a new probe? if(tcpTimerElapsed(&socket->persistTimer)) { //Make sure the maximum number of retransmissions has not been reached if(socket->wndProbeCount < TCP_MAX_RETRIES) { //Debug message TRACE_INFO("%s: TCP zero window probe #%u...\r\n", formatSystemTime(osGetSystemTime(), NULL), socket->wndProbeCount + 1); //Zero window probes usually have the sequence number one less than expected tcpSendSegment(socket, TCP_FLAG_ACK, socket->sndNxt - 1, socket->rcvNxt, 0, FALSE); //The interval between successive probes should be increased exponentially socket->wndProbeInterval = MIN(socket->wndProbeInterval * 2, TCP_MAX_PROBE_INTERVAL); //Restart the persist timer tcpTimerStart(&socket->persistTimer, socket->wndProbeInterval); //Increment window probe counter socket->wndProbeCount++; } else { //Enter CLOSED state tcpChangeState(socket, TCP_STATE_CLOSED); } } } //To avoid a deadlock, it is necessary to have a timeout to force //transmission of data, overriding the SWS avoidance algorithm. In //practice, this timeout should seldom occur (see RFC 1122 4.2.3.4) if(socket->state == TCP_STATE_ESTABLISHED || socket->state == TCP_STATE_CLOSE_WAIT) { //The override timeout occurred? if(socket->sndUser && tcpTimerElapsed(&socket->overrideTimer)) { //The amount of data that can be sent at any given time is //limited by the receiver window and the congestion window n = MIN(socket->sndWnd, socket->cwnd); n = MIN(n, socket->txBufferSize); //Retrieve the size of the usable window u = n - (socket->sndNxt - socket->sndUna); //Send as much data as possible while(socket->sndUser > 0) { //The usable window size may become zero or negative, //preventing packet transmission if((int_t) u <= 0) break; //Calculate the number of bytes to send at a time n = MIN(u, socket->sndUser); n = MIN(n, socket->mss); //Send TCP segment error = tcpSendSegment(socket, TCP_FLAG_PSH | TCP_FLAG_ACK, socket->sndNxt, socket->rcvNxt, n, TRUE); //Failed to send TCP segment? if(error) break; //Advance SND.NXT pointer socket->sndNxt += n; //Adjust the number of bytes buffered but not yet sent socket->sndUser -= n; } //Check whether the transmitter can accept more data tcpUpdateEvents(socket); //Restart override timer if necessary if(socket->sndUser > 0) tcpTimerStart(&socket->overrideTimer, TCP_OVERRIDE_TIMEOUT); } } //The FIN-WAIT-2 timer prevents the connection //from staying in the FIN-WAIT-2 state forever if(socket->state == TCP_STATE_FIN_WAIT_2) { //Maximum FIN-WAIT-2 time has elapsed? if(tcpTimerElapsed(&socket->finWait2Timer)) { //Debug message TRACE_WARNING("TCP FIN-WAIT-2 timer elapsed...\r\n"); //Enter CLOSED state tcpChangeState(socket, TCP_STATE_CLOSED); } } //TIME-WAIT timer if(socket->state == TCP_STATE_TIME_WAIT) { //2MSL time has elapsed? if(tcpTimerElapsed(&socket->timeWaitTimer)) { //Debug message TRACE_WARNING("TCP 2MSL timer elapsed (socket %u)...\r\n", i); //Enter CLOSED state tcpChangeState(socket, TCP_STATE_CLOSED); //Dispose the socket if the user does not have the ownership anymore if(!socket->ownedFlag) { //Delete the TCB tcpDeleteControlBlock(socket); //Mark the socket as closed socket->type = SOCKET_TYPE_UNUSED; } } } } //Leave critical section osReleaseMutex(&socketMutex); }