/* * Copyright (C) 2001-2007 by egnite Software GmbH * Copyright (C) 2009 by egnite GmbH * Copyright (c) 1993 by Digital Equipment Corporation * Copyright (c) 1983, 1993 by The Regents of the University of California * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the copyright holders nor the names of * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * For additional information see http://www.ethernut.de/ */ /*! * \file net/tcpsm.c * \brief TCP state machine. * * \verbatim * $Id: tcpsm.c 5691 2014-05-02 15:23:32Z haraldkipp $ * \endverbatim */ #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef NUTDEBUG #include #endif #ifndef NUT_THREAD_TCPSMSTACK #if defined(__AVR__) #if defined(__GNUC__) /* avr-gcc size optimized code used 148 bytes. */ #define NUT_THREAD_TCPSMSTACK 256 #else /* icc-avr v7.19 used 312 bytes. */ #define NUT_THREAD_TCPSMSTACK 512 #endif #elif defined(__AVR32__) #define NUT_THREAD_TCPSMSTACK 1024 #else /* arm-elf-gcc used 260 bytes with size optimized, 644 bytes with debug code. */ #define NUT_THREAD_TCPSMSTACK 384 #endif #endif #ifndef TCP_RETRIES_MAX #define TCP_RETRIES_MAX 7 #endif extern TCPSOCKET *tcpSocketList; /*! * \addtogroup xgTCP */ /*@{*/ HANDLE tcp_in_rdy; NETBUF *volatile tcp_in_nbq; static uint16_t tcp_in_cnt; static HANDLE tcpThread = 0; #ifndef TCP_COLLECT_INADV #define TCP_COLLECT_INADV 8 #endif #ifndef TCP_COLLECT_SLIMIT #define TCP_COLLECT_SLIMIT 256 #endif #ifndef TCP_BACKLOG_MAX #define TCP_BACKLOG_MAX 8 #endif #if TCP_BACKLOG_MAX #ifndef TCP_BACKLOG_TIME #define TCP_BACKLOG_TIME 5 #endif static NETBUF *tcp_backlog[TCP_BACKLOG_MAX]; static uint_fast8_t tcp_backlog_time[TCP_BACKLOG_MAX]; #endif static size_t tcp_adv_cnt; static size_t tcp_adv_max = TCP_WINSIZE; static int tcp_run_gc = 0; static void NutTcpStateProcess(TCPSOCKET * sock, NETBUF * nb); /* ================================================================ * Helper functions * ================================================================ */ #if TCP_BACKLOG_MAX static int NutTcpBacklogAdd(NETBUF *nb) { uint_fast8_t i; uint_fast8_t n = TCP_BACKLOG_MAX; IPHDR *ih = (IPHDR *) nb->nb_nw.vp; TCPHDR *th = (TCPHDR *) nb->nb_tp.vp; /* Process SYN segments only. */ if ((th->th_flags & (TH_SYN | TH_ACK | TH_RST)) == TH_SYN) { for (i = 0; i < TCP_BACKLOG_MAX; i++) { if (tcp_backlog[i] == NULL) { /* Remember the first free entry. */ if (n == TCP_BACKLOG_MAX) { n = i; } } else if (((IPHDR *) tcp_backlog[i]->nb_nw.vp)->ip_src == ih->ip_src && ((TCPHDR *) tcp_backlog[i]->nb_tp.vp)->th_sport == th->th_sport) { /* Already received a SYN. Either the remote is too impatient or we are too busy. Kill this entry and reject the SYN. */ NutNetBufFree(tcp_backlog[i]); tcp_backlog[i] = NULL; return -1; } } /* First SYN from the remote for this port. If there is a free entry left, then use it. */ if (n != TCP_BACKLOG_MAX) { tcp_backlog[n] = nb; tcp_backlog_time[n] = 0; return 0; } } return -1; } static NETBUF *NutTcpBacklogCheck(uint16_t port) { NETBUF *nb; uint_fast8_t i; uint_fast8_t n = TCP_BACKLOG_MAX; for (i = 0; i < TCP_BACKLOG_MAX; i++) { if (tcp_backlog[i]) { if (((TCPHDR *) tcp_backlog[i]->nb_tp.vp)->th_dport == port) { if (n == TCP_BACKLOG_MAX || tcp_backlog_time[i] > tcp_backlog_time[n]) { n = i; } } } } if (n == TCP_BACKLOG_MAX) { nb = NULL; } else { nb = tcp_backlog[n]; tcp_backlog[n] = NULL; } return nb; } static NETBUF *NutTcpBacklogTimer(void) { NETBUF *nb; uint_fast8_t i; uint_fast8_t n = TCP_BACKLOG_MAX; for (i = 0; i < TCP_BACKLOG_MAX; i++) { if (tcp_backlog[i]) { if (tcp_backlog_time[i] < TCP_BACKLOG_TIME) { tcp_backlog_time[i]++; } else { n = i; } } } if (n == TCP_BACKLOG_MAX) { nb = NULL; } else { nb = tcp_backlog[n]; tcp_backlog[n] = NULL; } return nb; } #endif /* TCP_BACKLOG_MAX */ /*! * \brief Reads TCP option fields if any, and writes the data to * the socket descriptor if important for us. * * \param sock Socket descriptor. This pointer must have been * retrieved by calling NutTcpCreateSocket(). * \param nb Network buffer structure containing a TCP segment. */ static void NutTcpInputOptions(TCPSOCKET * sock, NETBUF * nb) { uint8_t *cp; uint16_t s; /* any options there? */ if (nb->nb_tp.sz <= sizeof(TCPHDR)) { return; } /* loop through available options */ for (cp = ((uint8_t *) nb->nb_tp.vp) + sizeof(TCPHDR); (*cp != TCPOPT_EOL) && (cp - (uint8_t *) nb->nb_tp.vp < (int) nb->nb_tp.sz);) { switch (*cp) { /* On NOP just proceed to next option */ case TCPOPT_NOP: cp++; continue; /* Read MAXSEG option */ case TCPOPT_MAXSEG: /* Network uses big endian. */ s = cp[2]; s <<= 8; s |= cp[3]; if (s < sock->so_mss) { sock->so_mss = s; } cp += TCPOLEN_MAXSEG; break; /* Ignore any other options */ default: cp += *(uint8_t *) (cp + 1); break; } } } /*! * \brief Move application data in sync from the network buffer * structure to the socket's receive buffer. * * \param sock Socket descriptor. This pointer must have been * retrieved by calling NutTcpCreateSocket(). * \param nb Network buffer structure containing a TCP segment. */ static void NutTcpProcessAppData(TCPSOCKET * sock, NETBUF * nb) { /* * Add the NETBUF to the socket's input buffer. */ if (sock->so_rx_buf) { NETBUF *nbp = sock->so_rx_buf; while (nbp->nb_next) { nbp = nbp->nb_next; } nbp->nb_next = nb; } else { sock->so_rx_buf = nb; } /* * Update the number of bytes available in the socket's input buffer * and the sequence number we expect next. */ sock->so_rx_cnt += nb->nb_ap.sz; sock->so_rx_nxt += nb->nb_ap.sz; /* * Reduce our TCP window size. */ if (nb->nb_ap.sz >= sock->so_rx_win) { sock->so_rx_win = 0; } else { sock->so_rx_win -= nb->nb_ap.sz; } /* * Set the socket's ACK flag. This will enable ACK transmission in * the next outgoing segment. If no more NETBUFs are queued, we * force immediate transmission of the ACK. */ sock->so_tx_flags |= SO_ACK; if (nb->nb_next) { nb->nb_next = NULL; } else { sock->so_tx_flags |= SO_FORCE; } if (++sock->so_rx_apc > TCP_COLLECT_INADV) { NETBUF *nbq; int_fast8_t apc = sock->so_rx_apc; int cnt = sock->so_rx_cnt; for (nbq = sock->so_rx_buf; nbq; nbq = nbq->nb_next) { if (nbq->nb_ap.sz < TCP_COLLECT_SLIMIT) { sock->so_rx_apc -= NutNetBufCollect(nbq, cnt); break; } if (--apc < 8) { break; } cnt -= nbq->nb_ap.sz; } } NutTcpOutput(sock, NULL, 0); } /* * \param sock Socket descriptor. */ static void NutTcpProcessSyn(TCPSOCKET * sock, IPHDR * ih, TCPHDR * th) { uint16_t mss; NUTDEVICE *dev; IFNET *nif; sock->so_local_addr = ih->ip_dst; sock->so_remote_port = th->th_sport; sock->so_remote_addr = ih->ip_src; sock->so_rx_nxt = sock->so_tx_wl1 = sock->so_rx_isn = ntohl(th->th_seq); sock->so_rx_nxt++; sock->so_tx_win = ntohs(th->th_win); /* * To avoid unnecessary fragmentation, limit the * maximum segment size to the maximum transfer * unit of our interface. */ dev = NutIpRouteQuery(ih->ip_src, NULL); if (dev) { nif = dev->dev_icb; mss = nif->if_mtu - sizeof(IPHDR) - sizeof(TCPHDR); if (sock->so_mss == 0 || sock->so_mss > mss) { sock->so_mss = mss; } /* Limit output buffer size to mms */ if (sock->so_devobsz > sock->so_mss) { sock->so_devobsz = sock->so_mss; } } } /*! * \brief ACK processing. * * \param sock Socket descriptor. This pointer must have been * retrieved by calling NutTcpCreateSocket(). * */ static int NutTcpProcessAck(TCPSOCKET * sock, TCPHDR * th, uint16_t length) { NETBUF *nb; uint32_t h_seq; uint32_t h_ack; /* * If remote acked something not yet send, reply immediately. */ h_ack = ntohl(th->th_ack); if (SeqIsAfter(h_ack, sock->so_tx_nxt)) { sock->so_tx_flags |= SO_ACK | SO_FORCE; return 0; } /* * If the new sequence number or acknowledged sequence number * is above our last update, we adjust our transmit window. * Avoid dupe ACK processing on window updates. */ if (h_ack == sock->so_tx_una) { h_seq = ntohl(th->th_seq); if (SeqIsAfter(h_seq, sock->so_tx_wl1) || (h_seq == sock->so_tx_wl1 && !SeqIsAfter(sock->so_tx_wl2, h_ack))) { sock->so_tx_win = ntohs(th->th_win); sock->so_tx_wl1 = h_seq; sock->so_tx_wl2 = h_ack; } } /* * Ignore old ACKs but wake up sleeping transmitter threads, because * the window size may have changed. */ if (SeqIsAfter(sock->so_tx_una, h_ack)) { return 0; } /* * Process duplicate ACKs. */ if (h_ack == sock->so_tx_una) { /* * Don't count, if nothing is waiting for ACK, * segment contains data or on SYN/FIN segments. */ if (sock->so_tx_nbq && length == 0 && (th->th_flags & (TH_SYN | TH_FIN)) == 0) { /* * If dupe counter reaches it's limit, resend * the oldest unacknowledged netbuf. */ if (++sock->so_tx_dup >= 3) { sock->so_tx_dup = 0; #ifdef NUTDEBUG if (__tcp_trf & NET_DBG_SOCKSTATE) { NutDumpTcpHeader(__tcp_trs, "RET", sock, sock->so_tx_nbq); } #endif /* * Retransmit first unacked packet from queue. * Actually we got much more trouble if this fails. */ if (NutTcpStateRetranTimeout(sock)) { return -1; } } } return 0; } /* * We're here, so the ACK must have actually acked something */ sock->so_tx_dup = 0; sock->so_tx_una = h_ack; /* * Bugfix contributed by Liu Limin: If the remote is slow and this * line is missing, then Ethernut will send a full data packet even * if the remote closed the window. */ sock->so_tx_win = ntohs(th->th_win); /* * Do round trip time calculation. */ if (sock->so_rtt_seq && SeqIsAfter(h_ack, sock->so_rtt_seq)) { NutTcpCalcRtt(sock); } sock->so_rtt_seq = 0; /* * Remove all acknowledged netbufs. */ while ((nb = sock->so_tx_nbq) != NULL) { /* Calculate the sequence beyond this netbuf. */ h_seq = ntohl(((TCPHDR *) (nb->nb_tp.vp))->th_seq) + nb->nb_ap.sz; if (((TCPHDR *) (nb->nb_tp.vp))->th_flags & (TH_SYN | TH_FIN)) { h_seq++; } if (SeqIsAfter(h_seq, h_ack)) { break; } sock->so_tx_nbq = nb->nb_next; NutNetBufFree(nb); } /* * Reset retransmit timer and wake up waiting transmissions. */ if (sock->so_tx_nbq) { sock->so_retran_time = (uint16_t) NutGetMillis() | 1; } else { sock->so_retran_time = 0; } sock->so_retransmits = 0; return 0; } /* ================================================================ * State changes. * ================================================================ */ /*! * State change, possibly inform application. * * \param sock Socket descriptor. * \param state New state to switch to. * * \return 0 on success, -1 on illegal state change attempt. */ static int NutTcpStateChange(TCPSOCKET * sock, uint8_t state) { int rc = 0; ureg_t txf = 0; switch (sock->so_state) { /* Handle the most common case first. */ case TCPS_ESTABLISHED: switch (state) { case TCPS_FIN_WAIT_1: /* * Closed by application. */ sock->so_tx_flags |= SO_FIN | SO_ACK; txf = 1; #ifdef RTLCONNECTHACK /* * Hack alert! * On the RTL8019AS we got a problem. Because of not handling * the CHRDY line, the controller drops outgoing packets when * a browser opens multiple connections concurrently, producing * several short incoming packets. Empirical test showed, that * a slight delay during connects and disconnects helped to * remarkably reduce this problem. */ NutDelay(5); #endif break; case TCPS_CLOSE_WAIT: /* * FIN received. */ sock->so_tx_flags |= SO_ACK | SO_FORCE; txf = 1; break; default: rc = -1; break; } break; case TCPS_LISTEN: /* * SYN received. */ if (state == TCPS_SYN_RECEIVED) { sock->so_tx_flags |= SO_SYN | SO_ACK; txf = 1; #ifdef RTLCONNECTHACK /* * Hack alert! * On the RTL8019AS we got a problem. Because of not handling * the CHRDY line, the controller drops outgoing packets when * a browser opens multiple connections concurrently, producing * several short incoming packets. Empirical test showed, that * a slight delay during connects and disconnects helped to * remarkably reduce this problem. */ NutDelay(5); #endif } else if (state == TCPS_DESTROY) { /* Change to DESTROY. Socket will be destroyed ASAP */ break; } else rc = -1; break; case TCPS_SYN_SENT: switch (state) { case TCPS_LISTEN: /* * RST received on passive socket. */ break; case TCPS_SYN_RECEIVED: /* * SYN received. */ sock->so_tx_flags |= SO_SYN | SO_ACK; txf = 1; break; case TCPS_ESTABLISHED: /* * SYNACK received. */ sock->so_tx_flags |= SO_ACK | SO_FORCE; txf = 1; break; case TCPS_DESTROY: /* Change to DESTROY. Socket will be destroyed ASAP */ break; default: rc = -1; break; } break; case TCPS_SYN_RECEIVED: switch (state) { case TCPS_LISTEN: /* * RST received on passive socket. */ break; case TCPS_ESTABLISHED: /* * ACK of SYN received. */ break; case TCPS_FIN_WAIT_1: /* * Closed by application. */ sock->so_tx_flags |= SO_FIN; txf = 1; break; case TCPS_CLOSE_WAIT: /* * FIN received. */ sock->so_tx_flags |= SO_FIN | SO_ACK; txf = 1; break; default: rc = -1; break; } break; case TCPS_FIN_WAIT_1: switch (state) { case TCPS_FIN_WAIT_1: case TCPS_FIN_WAIT_2: /* * ACK of FIN received. */ break; case TCPS_CLOSING: /* * FIN received. */ sock->so_tx_flags |= SO_ACK | SO_FORCE; txf = 1; break; case TCPS_TIME_WAIT: /* * FIN and ACK of FIN received. */ break; case TCPS_DESTROY: /* Change to DESTROY. Socket will be destroyed ASAP */ break; default: rc = -1; break; } break; case TCPS_FIN_WAIT_2: if (state == TCPS_DESTROY) { /* Change to DESTROY. Socket will be destroyed ASAP */ break; } else /* * FIN received. */ if (state != TCPS_TIME_WAIT) { rc = -1; } sock->so_tx_flags |= SO_ACK | SO_FORCE; txf = 1; break; case TCPS_CLOSE_WAIT: /* * Closed by application. */ if (state == TCPS_LAST_ACK) { sock->so_tx_flags |= SO_FIN | SO_ACK; txf = 1; } else { rc = -1; } break; case TCPS_CLOSING: if (state == TCPS_DESTROY) { /* Change to DESTROY. Socket will be destroyed ASAP */ break; } else /* * ACK of FIN received. */ if (state != TCPS_TIME_WAIT) { rc = -1; } break; case TCPS_LAST_ACK: if (state == TCPS_DESTROY) { /* Change to DESTROY. Socket will be destroyed ASAP */ break; } else rc = -1; break; case TCPS_TIME_WAIT: if (state == TCPS_DESTROY) { /* Change to DESTROY. Socket will be destroyed ASAP */ break; } else rc = -1; break; case TCPS_CLOSED: switch (state) { case TCPS_LISTEN: /* * Passive open by application. */ break; case TCPS_SYN_SENT: /* * Active open by application. */ sock->so_tx_flags |= SO_SYN; txf = 1; break; case TCPS_DESTROY: /* Change to DESTROY. Socket will be destroyed ASAP */ break; default: rc = -1; break; } break; case TCPS_DESTROY: /* Do nothing, ignore the state change. Socket will be destroyed ASAP */ break; } #ifdef NUTDEBUG if (__tcp_trf & NET_DBG_SOCKSTATE) { fprintf(__tcp_trs, " %04x-", (unsigned int) sock); if (rc) { NutDumpSockState(__tcp_trs, sock->so_state, "**ERR ", "**>"); } NutDumpSockState(__tcp_trs, state, "[>", "]"); } #endif if (rc == 0) { if (state == TCPS_DESTROY) { tcp_run_gc = 1; } sock->so_state = state; if (txf && NutTcpOutput(sock, NULL, 0)) { if (state == TCPS_SYN_SENT) { rc = -1; sock->so_last_error = EHOSTDOWN; NutEventPostAsync(&sock->so_ac_tq); } } if (state == TCPS_CLOSE_WAIT) { /* * Inform application. */ NutEventBroadcast(&sock->so_rx_tq); NutEventBroadcast(&sock->so_pc_tq); NutEventBroadcast(&sock->so_ac_tq); /* Wake up all running selects (read and write queue) on this socket */ NutSelectWakeup(sock->so_rx_wq_list, WQ_FLAG_READ); NutSelectWakeup(sock->so_tx_wq_list, WQ_FLAG_WRITE); } } return rc; } /* ================================================================ * Application events. * ================================================================ */ /*! * \brief Initiated by the application. * * \param sock Socket descriptor. This pointer must have been * retrieved by calling NutTcpCreateSocket(). * * \return 0 if granted, -1 otherwise. The specific error code * can be retrieved by calling NutTcpError(). */ int NutTcpStatePassiveOpenEvent(TCPSOCKET * sock) { if (sock->so_state != TCPS_CLOSED) { sock->so_last_error = EISCONN; return -1; } NutTcpStateChange(sock, TCPS_LISTEN); #if TCP_BACKLOG_MAX { /* If a SYN segment is already waiting in the backlog, then process it and return to the caller. */ NETBUF *nb = NutTcpBacklogCheck(sock->so_local_port); if (nb) { NutTcpInputOptions(sock, nb); NutTcpStateProcess(sock, nb); return 0; } } if (NutEventWait(&sock->so_pc_tq, sock->so_read_to)) { sock->so_state = TCPS_CLOSED; sock->so_last_error = ETIMEDOUT; return -1; } #else /* For backward compatibility we simply block the application. If we do not have a backlog, then timing out would not make much sense anyway, because incoming connection attempts will be immediately rejected. */ NutEventWait(&sock->so_pc_tq, 0); #endif /* TCP_BACKLOG_MAX */ return 0; } /*! * \brief Initiated by the application. * * The caller must make sure, that the socket is in closed state. * * \param sock Socket descriptor. This pointer must have been * retrieved by calling NutTcpCreateSocket(). * * \return 0 if granted, -1 otherwise. */ int NutTcpStateActiveOpenEvent(TCPSOCKET * sock) { /* * Switch state to SYN_SENT. This will * transmit a SYN packet. */ NutTcpStateChange(sock, TCPS_SYN_SENT); /* * Block application. */ if (sock->so_state == TCPS_SYN_SENT) { if (NutEventWait(&sock->so_ac_tq, sock->so_write_to)) { NutTcpAbortSocket(sock, ETIMEDOUT); return -1; } } if (sock->so_state != TCPS_ESTABLISHED && sock->so_state != TCPS_CLOSE_WAIT) { return -1; } return 0; } /*! * \brief Socket close by application. * * If socket is in state SYN_RECEIVED or ESTABLISHED, * it is changed to FINWAIT1. * * No further data sending is accepted. * Receiving is still allowed. * * \param sock Socket descriptor. This pointer must have been * retrieved by calling NutTcpCreateSocket(). */ int NutTcpStateCloseEvent(TCPSOCKET * sock) { if (sock == NULL) { return -1; } NutThreadYield(); switch (sock->so_state) { case TCPS_LISTEN: case TCPS_SYN_SENT: case TCPS_CLOSED: /* * No connection yet, immediately destroy the socket. */ NutTcpStateChange(sock, TCPS_DESTROY); break; case TCPS_SYN_RECEIVED: case TCPS_ESTABLISHED: /* * Send a FIN and wait for ACK or FIN. */ NutTcpStateChange(sock, TCPS_FIN_WAIT_1); break; case TCPS_CLOSE_WAIT: /* * RFC 793 is wrong. */ NutTcpStateChange(sock, TCPS_LAST_ACK); break; case TCPS_FIN_WAIT_1: case TCPS_FIN_WAIT_2: case TCPS_CLOSING: case TCPS_LAST_ACK: case TCPS_TIME_WAIT: sock->so_last_error = EALREADY; return -1; case TCPS_DESTROY: default: sock->so_last_error = ENOTCONN; return -1; } return 0; } /*! * \brief Initiated by the application. * * \param sock Socket descriptor. This pointer must have been * retrieved by calling NutTcpCreateSocket(). */ int NutTcpStateWindowEvent(TCPSOCKET * sock) { if (sock == NULL) { return -1; } sock->so_tx_flags |= SO_ACK | SO_FORCE; NutTcpOutput(sock, NULL, 0); return 0; } /* ================================================================ * Timeout events. * ================================================================ */ /*! * \brief Retransmit a segment after ACK timeout. * * This function is called by the TCP timer. * * \param sock Socket descriptor. This pointer must have been * retrieved by calling NutTcpCreateSocket(). * \returns Nonzero if socket was aborted due to reach of retransmit * limit or network error. * */ int NutTcpStateRetranTimeout(TCPSOCKET * sock) { NETBUF *so_tx_next; if (sock->so_retransmits++ > TCP_RETRIES_MAX) { /* Abort the socket */ NutTcpAbortSocket(sock, ETIMEDOUT); return -1; } else { #ifdef NUTDEBUG if (__tcp_trf & NET_DBG_SOCKSTATE) { NutDumpTcpHeader(__tcp_trs, "RET", sock, sock->so_tx_nbq); } #endif /* We must save sock->so_tx_nbq->nb_next before calling NutIpOutput, * because in case of error the NETBUF is release by NutIpOutput and * not longer available. */ so_tx_next = sock->so_tx_nbq->nb_next; if (NutIpOutput(IPPROTO_TCP, sock->so_remote_addr, sock->so_tx_nbq)) { /* Adjust packet queue */ sock->so_tx_nbq = so_tx_next; /* Abort the socket */ NutTcpAbortSocket(sock, ENETDOWN); return -1; } else { /* Restart the retransmission timer. */ sock->so_retran_time = (uint16_t) NutGetMillis() | 1; /* Double retransmission timeout up to maximum. */ if (sock->so_rtto < TCP_RTTO_MAX / 2) { sock->so_rtto <<= 1; } else { sock->so_rtto = TCP_RTTO_MAX; } return 0; } } } /* ================================================================ * Segment arrival events. * ================================================================ */ /*! * \brief * Process incoming segments in listening state. * * Wait for a connection request from a remote socket. * * \param sock Socket descriptor. * \param nb Network buffer structure containing a TCP segment. */ static void NutTcpStateListen(TCPSOCKET * sock, uint8_t flags, TCPHDR * th, NETBUF * nb) { /* * Got a SYN segment. Store relevant data in our socket * structure and switch to TCPS_SYN_RECEIVED. */ if ((flags & (TH_SYN | TH_ACK | TH_RST)) == TH_SYN) { NutTcpProcessSyn(sock, nb->nb_nw.vp, th); NutTcpStateChange(sock, TCPS_SYN_RECEIVED); NutNetBufFree(nb); } else NutTcpReject(nb); } /*! * \brief Process incoming segments in SYN-SENT state. * * Wait for a matching connection request after having sent ours. * * \param sock Socket descriptor. * \param nb Network buffer structure containing a TCP segment. */ static void NutTcpStateSynSent(TCPSOCKET * sock, uint8_t flags, TCPHDR * th, NETBUF * nb) { /* * Validate ACK, if set. */ if (flags & TH_ACK) { if (!SeqIsBetween(ntohl(th->th_ack), sock->so_tx_isn + 1, sock->so_tx_nxt)) { NutTcpReject(nb); return; } } /* * Handle RST flag. If we were in the LISTEN state, * then we return to the LISTEN state, otherwise we * abort the connection and go to the CLOSED state. */ if (flags & TH_RST) { if (flags & TH_ACK) { NutTcpAbortSocket(sock, ECONNREFUSED); } NutNetBufFree(nb); return; } /* * Handle SYN flag. If we got a valid ACK too, then * our connection is established. Otherwise enter * SYNRCVD state. */ if (flags & TH_SYN) { NutTcpProcessSyn(sock, nb->nb_nw.vp, th); if (flags & TH_ACK) { NutTcpProcessAck(sock, th, nb->nb_ap.sz); NutTcpStateChange(sock, TCPS_ESTABLISHED); /* Wake up the actively connecting thread. */ NutEventPost(&sock->so_ac_tq); } else { NutTcpStateChange(sock, TCPS_SYN_RECEIVED); } } NutNetBufFree(nb); } /* * \brief * Process incoming segments in SYN-RECEIVED state. * * Waiting for a confirming connection request * acknowledgment after having both received * and sent a connection request. * * \param sock Socket descriptor. * \param nb Network buffer structure containing a TCP segment. */ static void NutTcpStateSynReceived(TCPSOCKET * sock, uint8_t flags, TCPHDR * th, NETBUF * nb) { /* * If our previous ack receives a reset response, * then we fall back to the listening state. */ if (flags & TH_RST) { if (sock->so_pc_tq) { NutTcpStateChange(sock, TCPS_LISTEN); } else { NutTcpAbortSocket(sock, ECONNREFUSED); } NutNetBufFree(nb); sock->so_retran_time = 0; NutTcpDiscardBuffers(sock); return; } /* * Silently discard duplicate SYN. */ if (flags & TH_SYN) { NutNetBufFree(nb); return; } /* * Silently discard segments without ACK. */ if ((flags & TH_ACK) == 0) { NutNetBufFree(nb); return; } /* * Reject out of window sequence. */ if (!SeqIsBetween(ntohl(th->th_ack), sock->so_tx_una + 1, sock->so_tx_nxt)) { NutTcpReject(nb); return; } /* Acknowledge processing. */ NutTcpProcessAck(sock, th, nb->nb_ap.sz); /* * Even SYN segments may contain application data, which will be stored * in the socket's input buffer. However, there is no need to post an * event to any thread waiting for data, because our connection is not * yet established. */ if (nb->nb_ap.sz) { NutTcpProcessAppData(sock, nb); } else { NutNetBufFree(nb); } /* * Process state change. */ if (flags & TH_FIN) { sock->so_rx_nxt++; NutTcpStateChange(sock, TCPS_CLOSE_WAIT); } else { NutTcpStateChange(sock, TCPS_ESTABLISHED); NutEventPost(&sock->so_pc_tq); NutEventPost(&sock->so_ac_tq); } } /* * \brief Process incoming segments from established connections. * * Received application data will be delivered to the application * until we receive a FIN segment. * * \param sock Socket descriptor. * \param flags TCP flags. * \param th Pointer to the TCP header within the NETBUF. * \param nb Network buffer structure containing a TCP segment. * * \todo We may remove the unused counter of dropped segments, which * were out of sequence. */ static void NutTcpStateEstablished(TCPSOCKET * sock, uint8_t flags, TCPHDR * th, NETBUF * nb) { if (flags & TH_RST) { NutNetBufFree(nb); NutTcpAbortSocket(sock, ECONNRESET); return; } /* * Reject SYNs. Silently discard late SYNs * (Thanks to Mike Cornelius). */ if (flags & TH_SYN) { if (ntohl(th->th_seq) != sock->so_rx_isn) { NutTcpReject(nb); } else { NutNetBufFree(nb); } return; } /* * Silently discard segments without ACK. */ if ((flags & TH_ACK) == 0) { NutNetBufFree(nb); return; } NutTcpProcessAck(sock, th, nb->nb_ap.sz); /* * If the sequence number of the incoming segment is larger than * expected, we probably missed one or more previous segments. Let's * add this one to a linked list of segments received in advance and * hope that the missing data will arrive later. */ if (SeqIsAfter(ntohl(th->th_seq), sock->so_rx_nxt)) { NETBUF *nbq; NETBUF **nbqp; TCPHDR *thq; uint32_t th_seq; uint32_t thq_seq; if (nb->nb_ap.sz) { /* Keep track of the number of bytes used by packets received in advance. Honor a global limit. */ tcp_adv_cnt += nb->nb_dl.sz + sizeof(IPHDR) + sizeof(TCPHDR) + nb->nb_ap.sz; if (tcp_adv_cnt > tcp_adv_max) { /* Limit reached, discard the packet. */ NutNetBufFree(nb); tcp_adv_cnt -= nb->nb_dl.sz + sizeof(IPHDR) + sizeof(TCPHDR) + nb->nb_ap.sz; } else { nbq = sock->so_rx_nbq; nbqp = &sock->so_rx_nbq; while (nbq) { thq = (TCPHDR *) (nbq->nb_tp.vp); th_seq = ntohl(th->th_seq); thq_seq = ntohl(thq->th_seq); if (SeqIsAfter(thq_seq, th_seq)) { *nbqp = nb; nb->nb_next = nbq; break; } if (th_seq == thq_seq) { NutNetBufFree(nb); sock->so_tx_flags |= SO_ACK | SO_FORCE; NutTcpOutput(sock, NULL, 0); return; } nbqp = &nbq->nb_next; nbq = nbq->nb_next; } if (nbq == NULL) { *nbqp = nb; nb->nb_next = NULL; } } } else NutNetBufFree(nb); sock->so_tx_flags |= SO_ACK | SO_FORCE; NutTcpOutput(sock, NULL, 0); return; } /* * Acknowledge any sequence numbers not expected, * even if they do not contain any data. Keep alive * packets contain a sequence number one less * than the next data expected and they do not * contain any data. */ if (ntohl(th->th_seq) != sock->so_rx_nxt) { sock->so_tx_flags |= SO_ACK | SO_FORCE; /* This seems to be unused. */ sock->so_oos_drop++; NutNetBufFree(nb); NutTcpOutput(sock, NULL, 0); } /* * The sequence number is exactly what we expected. */ else if (nb->nb_ap.sz) { NutTcpProcessAppData(sock, nb); /* * Process segments we may have received in advance. */ while ((nb = sock->so_rx_nbq) != NULL) { th = (TCPHDR *) (nb->nb_tp.vp); if (SeqIsAfter(ntohl(th->th_seq), sock->so_rx_nxt)) { break; } sock->so_rx_nbq = nb->nb_next; /* Update the heap space used by packets received in advance. */ tcp_adv_cnt -= nb->nb_dl.sz + sizeof(IPHDR) + sizeof(TCPHDR) + nb->nb_ap.sz; if (ntohl(th->th_seq) == sock->so_rx_nxt) { NutTcpProcessAppData(sock, nb); flags |= th->th_flags; } else NutNetBufFree(nb); } /* Wake up a thread waiting for data. */ NutEventPost(&sock->so_rx_tq); /* Wake up all running selects (read queue) on this socket */ NutSelectWakeup(sock->so_rx_wq_list, WQ_FLAG_READ); } else { NutNetBufFree(nb); } if (flags & TH_FIN) { sock->so_rx_nxt++; NutTcpStateChange(sock, TCPS_CLOSE_WAIT); } } /* * \brief Process incoming segments in FIN-WAIT1 state. * * Waiting for a connection termination request * from the remote, or an acknowledgment of the * connection termination request previously sent. * * The application already closed the socket. * * \param sock Socket descriptor. * \param nb Network buffer structure containing a TCP segment. * * \todo The out of sync case seems to be ignored. Anyway, do we * really need to process application data in this state? */ static void NutTcpStateFinWait1(TCPSOCKET * sock, uint8_t flags, TCPHDR * th, NETBUF * nb) { if (flags & TH_RST) { NutNetBufFree(nb); NutTcpStateChange(sock, TCPS_DESTROY); return; } /* * Reject SYNs. */ if (flags & TH_SYN) { NutTcpReject(nb); return; } /* * Silently discard segments without ACK. */ if ((flags & TH_ACK) == 0) { NutNetBufFree(nb); return; } NutTcpProcessAck(sock, th, nb->nb_ap.sz); /* * All segments had been acknowledged, including our FIN. */ if (sock->so_tx_nxt == sock->so_tx_una) { NutTcpStateChange(sock, TCPS_FIN_WAIT_2); } /* * Process application data and release the network buffer. * Is this really required? */ if (nb->nb_ap.sz) { NutTcpProcessAppData(sock, nb); /* Wake up a thread waiting for data. */ NutEventPost(&sock->so_rx_tq); /* Wake up all running selects (read queue) on this socket */ NutSelectWakeup(sock->so_rx_wq_list, WQ_FLAG_READ); } else { NutNetBufFree(nb); } if (flags & TH_FIN) { sock->so_rx_nxt++; /* * Our FIN has been acked. */ sock->so_time_wait = 0; if (sock->so_state == TCPS_FIN_WAIT_2) { NutTcpStateChange(sock, TCPS_TIME_WAIT); } else { NutTcpStateChange(sock, TCPS_CLOSING); } } } /* * \brief Process incoming segments in FIN-WAIT2 state. * * Waiting for a connection termination request * from the remote. * * The application already closed the socket. * * \param sock Socket descriptor. * \param nb Network buffer structure containing a TCP segment. * * \todo There's probably no need to process application data. */ static void NutTcpStateFinWait2(TCPSOCKET * sock, uint8_t flags, TCPHDR * th, NETBUF * nb) { if (flags & TH_RST) { NutNetBufFree(nb); NutTcpStateChange(sock, TCPS_DESTROY); return; } /* * Reject SYNs. */ if (flags & TH_SYN) { NutTcpReject(nb); return; } /* * Silently discard segments without ACK. */ if ((flags & TH_ACK) == 0) { NutNetBufFree(nb); return; } /* * Process acknowledge and application data and release the * network buffer. */ NutTcpProcessAck(sock, th, nb->nb_ap.sz); /* Do we really need this? */ if (nb->nb_ap.sz) { NutTcpProcessAppData(sock, nb); /* Wake up a thread waiting for data. */ NutEventPost(&sock->so_rx_tq); /* Wake up all running selects (read queue) on this socket */ NutSelectWakeup(sock->so_rx_wq_list, WQ_FLAG_READ); } else NutNetBufFree(nb); if (flags & TH_FIN) { sock->so_rx_nxt++; sock->so_time_wait = 0; NutTcpStateChange(sock, TCPS_TIME_WAIT); } } /* * \brief * Process incoming segments in CLOSE-WAIT state. * * Waiting for a connection termination request * from the local application. * * \param sock Socket descriptor. * \param nb Network buffer structure containing a TCP segment. */ static void NutTcpStateCloseWait(TCPSOCKET * sock, uint8_t flags, TCPHDR * th, NETBUF * nb) { if (flags & TH_RST) { NutNetBufFree(nb); NutTcpAbortSocket(sock, ECONNRESET); return; } /* * Reject SYNs. */ if (flags & TH_SYN) { NutTcpReject(nb); return; } /* * Silently discard segments without ACK. */ if (flags & TH_ACK) { NutTcpProcessAck(sock, th, nb->nb_ap.sz); } NutNetBufFree(nb); } /* * \brief * Process incoming segments in CLOSING state. * * Waiting for a connection termination request * acknowledgment from the remote. * * The application already closed the socket. * * \param sock Socket descriptor. * \param nb Network buffer structure containing a TCP segment. */ static void NutTcpStateClosing(TCPSOCKET * sock, uint8_t flags, TCPHDR * th, NETBUF * nb) { if (flags & TH_RST) { NutNetBufFree(nb); NutTcpStateChange(sock, TCPS_DESTROY); return; } /* * Reject SYNs. */ if (flags & TH_SYN) { NutTcpReject(nb); return; } /* * Silently discard segments without ACK. */ if ((flags & TH_ACK) == 0) { NutNetBufFree(nb); return; } NutTcpProcessAck(sock, th, nb->nb_ap.sz); /* * All segments had been acknowledged, including our FIN. */ if (sock->so_tx_nxt == sock->so_tx_una) { sock->so_time_wait = 0; NutTcpStateChange(sock, TCPS_TIME_WAIT); } NutNetBufFree(nb); } /*! * \brief * Process incoming segment in LAST_ACK state. * * Waiting for an acknowledgment of the connection termination * request previously sent. * * The application already closed the socket. * * \param sock Socket descriptor. * \param flags TCP flags of incoming segment. * \param th TCP header of incoming segment. * \param nb Network buffer structure containing a TCP segment. */ static void NutTcpStateLastAck(TCPSOCKET * sock, uint8_t flags, TCPHDR * th, NETBUF * nb) { if (flags & TH_RST) { NutNetBufFree(nb); NutTcpStateChange(sock, TCPS_DESTROY); return; } /* * Reject SYNs. */ if (flags & TH_SYN) { NutTcpReject(nb); return; } /* * Silently discard segments without ACK. */ if ((flags & TH_ACK) == 0) { NutNetBufFree(nb); return; } NutTcpProcessAck(sock, th, nb->nb_ap.sz); NutNetBufFree(nb); if (sock->so_tx_nxt == sock->so_tx_una) { NutTcpStateChange(sock, TCPS_DESTROY); } } /*! * \brief Process incoming TCP segments. * * Processing is based on the current state of the socket connection. * * \param sock Socket descriptor. This pointer must have been * retrieved by calling NutTcpCreateSocket(). * \param nb Network buffer structure containing a TCP segment. * Will be released within this routine. */ static void NutTcpStateProcess(TCPSOCKET * sock, NETBUF * nb) { uint32_t tx_win; uint32_t tx_una; TCPHDR *th = (TCPHDR *) nb->nb_tp.vp; uint8_t flags = th->th_flags; #ifdef NUTDEBUG if (__tcp_trf & NET_DBG_SOCKSTATE) { fprintf(__tcp_trs, " %04x-", (unsigned int) sock); NutDumpSockState(__tcp_trs, sock->so_state, "[", ">]"); } #endif switch (sock->so_state) { /* Handle the most common case first. */ case TCPS_ESTABLISHED: tx_win = sock->so_tx_win; tx_una = sock->so_tx_una; NutTcpStateEstablished(sock, flags, th, nb); /* Wake up all threads waiting for transmit, if something interesting happened. */ if (sock->so_state != TCPS_ESTABLISHED || /* Status changed. */ sock->so_tx_win > tx_win || /* Windows changed. */ sock->so_tx_una != tx_una) { /* Unacknowledged data changed. */ NutEventBroadcast(&sock->so_tx_tq); /* Wake up all running selects (write queue) on this socket */ NutSelectWakeup(sock->so_tx_wq_list, WQ_FLAG_WRITE); } break; case TCPS_LISTEN: NutTcpStateListen(sock, flags, th, nb); break; case TCPS_SYN_SENT: NutTcpStateSynSent(sock, flags, th, nb); break; case TCPS_SYN_RECEIVED: NutTcpStateSynReceived(sock, flags, th, nb); break; case TCPS_FIN_WAIT_1: NutTcpStateFinWait1(sock, flags, th, nb); break; case TCPS_FIN_WAIT_2: NutTcpStateFinWait2(sock, flags, th, nb); break; case TCPS_CLOSE_WAIT: NutTcpStateCloseWait(sock, flags, th, nb); break; case TCPS_CLOSING: NutTcpStateClosing(sock, flags, th, nb); break; case TCPS_LAST_ACK: NutTcpStateLastAck(sock, flags, th, nb); break; case TCPS_TIME_WAIT: /* * Ignore everything while in TIME_WAIT state. */ NutNetBufFree(nb); break; case TCPS_CLOSED: case TCPS_DESTROY: /* * Reject everything while in CLOSED or DESTROY state. */ NutTcpReject(nb); break; default: NutNetBufFree(nb); break; } } /*! * \brief Destroy previously allocated socket marked for destroy. * * Remove socket from the socket list and release occupied memory. * * This function is called, if the TCP statemachine is idle */ void NutTcpGarbadgeCollect(void) { TCPSOCKET *sp; TCPSOCKET *volatile *spp; TCPSOCKET *sock; /* * Remove socket from the list and free its allocated memory */ sp = tcpSocketList; spp = &tcpSocketList; while (sp) { if (sp->so_state == TCPS_DESTROY) { sock = sp; *spp = sp->so_next; sp = sp->so_next; NutTcpDestroySocket(sock); continue; } spp = &sp->so_next; sp = sp->so_next; } } /*! \fn NutTcpSm(void *arg) * \brief TCP state machine thread. * * The TCP state machine serves two purposes: It processes incoming TCP * segments and handles TCP timers. */ THREAD(NutTcpSm, arg) { NETBUF *nb; NETBUF *nbx; TCPHDR *th; IPHDR *ih; TCPSOCKET *sock; uint8_t tac = 0; (void)arg; /* * It won't help giving us a higher priority than the application * code. We depend on the speed of the reading application. */ NutThreadSetPriority(32); for (;;) { if (tcp_run_gc) { tcp_run_gc = 0; NutTcpGarbadgeCollect(); } if (++tac > 3 || NutEventWait(&tcp_in_rdy, 200)) { tac = 0; #if TCP_BACKLOG_MAX /* Process backlog timer. * * Note, that the tac counter will spoil any exact timing. * On the other hand, if we are very busy, it may not be that * bad to kill early SYN segments soon. */ nb = NutTcpBacklogTimer(); if (nb) { NutTcpReject(nb); } #endif /* TCP_BACKLOG_MAX */ for (sock = tcpSocketList; sock; sock = sock->so_next) { /* * Send late acks. */ if (sock->so_tx_flags & SO_ACK) { sock->so_tx_flags |= SO_FORCE; NutTcpOutput(sock, NULL, 0); } /* * Process retransmit timer. */ if (sock->so_tx_nbq && sock->so_retran_time) { if ((uint16_t) ((uint16_t) NutGetMillis() - (sock->so_retran_time & ~1)) >= sock->so_rtto) { NutTcpStateRetranTimeout(sock); } } /* * Destroy sockets after timeout in TIMEWAIT state. */ if (sock->so_state == TCPS_TIME_WAIT || sock->so_state == TCPS_FIN_WAIT_2) { if (sock->so_time_wait++ >= 9) { NutTcpStateChange(sock, TCPS_DESTROY); break; } } /* * Recover from SYN flood attacks. */ else if (sock->so_state == TCPS_SYN_RECEIVED) { if (sock->so_time_wait++ >= 45) { sock->so_state = TCPS_LISTEN; sock->so_time_wait = 0; } } } } else { nb = tcp_in_nbq; tcp_in_nbq = NULL; tcp_in_cnt = 0; while (nb) { ih = (IPHDR *) nb->nb_nw.vp; th = (TCPHDR *) nb->nb_tp.vp; sock = NutTcpFindSocket(th->th_dport, th->th_sport, ih->ip_src); #ifdef NUTDEBUG if (__tcp_trf & NET_DBG_SOCKSTATE) { NutDumpTcpHeader(__tcp_trs, " IN", sock, nb); } #endif nbx = nb->nb_next; /* If a matching socket exists, process the NETBUF. */ if (sock) { NutTcpInputOptions(sock, nb); NutTcpStateProcess(sock, nb); } #if TCP_BACKLOG_MAX /* No matching socket, try to add it to the backlog. */ else if (NutTcpBacklogAdd(nb) == 0) { } #endif /* No matching socket and no backlog. Reject it. */ else { NutTcpReject(nb); } nb = nbx; } } } } /*! * \brief Process incoming TCP segments. * * All incoming TCP packets are passed to this routine. They will * be added to a global queue and processed by the TCP state * machine, which is running on a separate thread. * * \note This routine is called by the IP layer on incoming * TCP segments. Applications typically do not call * this function. */ void NutTcpStateMachine(NETBUF * nb) { NETBUF *nbp; uint16_t size; nb->nb_next = NULL; /* * Incoming TCP segments are rejected and released if no TCP * sockets have been opened. Not doing so would add them * to the queue and never release the NETBUF. Thanks to * Ralph Mason for this fix. */ if (tcpThread == NULL) { NutTcpReject(nb); return; } nbp = tcp_in_nbq; if (nbp == NULL) { tcp_in_nbq = nb; NutEventPost(&tcp_in_rdy); } else { size = nb->nb_nw.sz + nb->nb_tp.sz + nb->nb_ap.sz; if (tcp_in_cnt + size + 2048 < NutHeapAvailable()) { tcp_in_cnt += size; while (nbp->nb_next) nbp = nbp->nb_next; nbp->nb_next = nb; NutEventPost(&tcp_in_rdy); } else NutNetBufFree(nb); } } /*! * \brief Start TCP state machine. * * The socket interface will automatically call this routine as * soon as the first socket is created. * * \return 0 on success, -1 otherwise. */ int NutTcpInitStateMachine(void) { if (tcpThread == NULL) { tcpThread = NutThreadCreate("tcpsm", NutTcpSm, NULL, NUT_THREAD_TCPSMSTACK * NUT_THREAD_STACK_MULT + NUT_THREAD_STACK_ADD); if (tcpThread == NULL) { return -1; } } return 0; } /*! * \brief Closes socket with error. * * Aborts any socket activity and sets last error. * * \param sock Socket descriptor. * \param last_error Error number to report * * \note This routine is called internally by Nut/Net. * Applications typically do not call this function. * * \return 0 on success, -1 otherwise. */ int NutTcpAbortSocket(TCPSOCKET * sock, uint16_t last_error) { uint8_t current_state = sock->so_state; sock->so_last_error = last_error; sock->so_retran_time = 0; sock->so_time_wait = 0; /* * If NutTcpCloseSocket was already called, we have to change * to TCPS_TIME_WAIT state, otherwise the socket will not be destroyed. * For the other cases just go to TCPS_CLOSED. */ if (sock->so_state >= TCPS_FIN_WAIT_1) { sock->so_state = TCPS_TIME_WAIT; } else { sock->so_state = TCPS_CLOSED; } NutTcpDiscardBuffers(sock); NutEventBroadcast(&sock->so_rx_tq); NutEventBroadcast(&sock->so_tx_tq); NutEventBroadcast(&sock->so_pc_tq); NutEventBroadcast(&sock->so_ac_tq); /* Check if we had a timeout on NutTcpConnect(). In this case there is no need for a notification of the select wait queues. */ if (!((current_state == TCPS_SYN_SENT) && (last_error == ETIMEDOUT))) { /* Wake up all running selects on this socket */ NutSelectWakeup(sock->so_rx_wq_list, WQ_FLAG_READ); NutSelectWakeup(sock->so_tx_wq_list, WQ_FLAG_WRITE); } return 0; } /*@}*/