From 6c3f6cfd89b694163adace5aa971524e1afd0319 Mon Sep 17 00:00:00 2001 From: sg Date: Tue, 10 Feb 2015 22:15:54 +0100 Subject: [PATCH] netconn/socket api: fixed bug #44225 "closing TCP socket should time out eventually", implemented task #6930 "Implement SO_LINGER": closing TCP sockets times out after 20 seconds or after the configured SND_TIMEOUT or depending on the linger settings; fixed that netconn_close/netconn_delete still used message passing for LWIP_TCPIP_CORE_LOCKING==1 --- CHANGELOG | 10 ++ src/api/api_lib.c | 25 +++-- src/api/api_msg.c | 206 +++++++++++++++++++++++++++++-------- src/api/sockets.c | 47 ++++++++- src/include/lwip/api.h | 4 + src/include/lwip/api_msg.h | 5 + src/include/lwip/opt.h | 14 +++ 7 files changed, 258 insertions(+), 53 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 237b05b4..c633782f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -156,6 +156,16 @@ HISTORY ++ Bugfixes: + 2014-02-10: Simon Goldschmidt + * netconn API: fixed that netconn_close/netconn_delete still used message passing + for LWIP_TCPIP_CORE_LOCKING==1 + + 2014-02-10: Simon Goldschmidt + * netconn/socket api: fixed bug #44225 "closing TCP socket should time out + eventually", implemented task #6930 "Implement SO_LINGER": closing TCP sockets + times out after 20 seconds or after the configured SND_TIMEOUT or depending + on the linger settings. + 2014-01-27: Simon Goldschmidt * api_msg.c: fixed that SHUT_RD followed by SHUT_WR was different to SHUT_RDWR, fixed return value of lwip_netconn_do_close on unconnected netconns diff --git a/src/api/api_lib.c b/src/api/api_lib.c index b1e8d37b..75a295da 100644 --- a/src/api/api_lib.c +++ b/src/api/api_lib.c @@ -51,6 +51,7 @@ #include "lwip/raw.h" #include "lwip/udp.h" #include "lwip/tcp.h" +#include "lwip/tcp_impl.h" #include @@ -122,9 +123,16 @@ netconn_delete(struct netconn *conn) } API_MSG_VAR_ALLOC(msg); - API_MSG_VAR_REF(msg).function = lwip_netconn_do_delconn; API_MSG_VAR_REF(msg).msg.conn = conn; - err = tcpip_apimsg(&API_MSG_VAR_REF(msg)); +#if LWIP_SO_SNDTIMEO || LWIP_SO_LINGER + /* get the time we started, which is later compared to + sys_now() + conn->send_timeout */ + API_MSG_VAR_REF(msg).msg.msg.sd.time_started = sys_now(); +#else /* LWIP_SO_SNDTIMEO || LWIP_SO_LINGER */ + API_MSG_VAR_REF(msg).msg.msg.sd.polls_left = + ((LWIP_TCP_CLOSE_TIMEOUT_MS_DEFAULT + TCP_SLOW_INTERVAL - 1) / TCP_SLOW_INTERVAL) + 1; +#endif /* LWIP_SO_SNDTIMEO || LWIP_SO_LINGER */ + TCPIP_APIMSG(&API_MSG_VAR_REF(msg), lwip_netconn_do_delconn, err); API_MSG_VAR_FREE(msg); if (err != ERR_OK) { @@ -728,13 +736,18 @@ netconn_close_shutdown(struct netconn *conn, u8_t how) LWIP_ERROR("netconn_close: invalid conn", (conn != NULL), return ERR_ARG;); API_MSG_VAR_ALLOC(msg); - API_MSG_VAR_REF(msg).function = lwip_netconn_do_close; API_MSG_VAR_REF(msg).msg.conn = conn; /* shutting down both ends is the same as closing */ API_MSG_VAR_REF(msg).msg.msg.sd.shut = how; - /* because of the LWIP_TCPIP_CORE_LOCKING implementation of lwip_netconn_do_close, - don't use TCPIP_APIMSG here */ - err = tcpip_apimsg(&API_MSG_VAR_REF(msg)); +#if LWIP_SO_SNDTIMEO || LWIP_SO_LINGER + /* get the time we started, which is later compared to + sys_now() + conn->send_timeout */ + API_MSG_VAR_REF(msg).msg.msg.sd.time_started = sys_now(); +#else /* LWIP_SO_SNDTIMEO || LWIP_SO_LINGER */ + API_MSG_VAR_REF(msg).msg.msg.sd.polls_left = + ((LWIP_TCP_CLOSE_TIMEOUT_MS_DEFAULT + TCP_SLOW_INTERVAL - 1) / TCP_SLOW_INTERVAL) + 1; +#endif /* LWIP_SO_SNDTIMEO || LWIP_SO_LINGER */ + TCPIP_APIMSG(&API_MSG_VAR_REF(msg), lwip_netconn_do_close, err); API_MSG_VAR_FREE(msg); NETCONN_SET_SAFE_ERR(conn, err); diff --git a/src/api/api_msg.c b/src/api/api_msg.c index 0f2fbefa..24dcec9d 100644 --- a/src/api/api_msg.c +++ b/src/api/api_msg.c @@ -55,6 +55,9 @@ #include +/* netconns are polled once per second (e.g. continue write on memory error) */ +#define NETCONN_TCP_POLL_INTERVAL 2 + #define SET_NONBLOCKING_CONNECT(conn, val) do { if(val) { \ (conn)->flags |= NETCONN_FLAG_IN_NONBLOCKING_CONNECT; \ } else { \ @@ -64,7 +67,7 @@ /* forward declarations */ #if LWIP_TCP static err_t lwip_netconn_do_writemore(struct netconn *conn); -static void lwip_netconn_do_close_internal(struct netconn *conn); +static err_t lwip_netconn_do_close_internal(struct netconn *conn); #endif #if LWIP_RAW @@ -285,6 +288,11 @@ poll_tcp(void *arg, struct tcp_pcb *pcb) if (conn->state == NETCONN_WRITE) { lwip_netconn_do_writemore(conn); } else if (conn->state == NETCONN_CLOSE) { +#if !LWIP_SO_SNDTIMEO && !LWIP_SO_LINGER + if (conn->current_msg && conn->current_msg->msg.sd.polls_left) { + conn->current_msg->msg.sd.polls_left--; + } +#endif /* !LWIP_SO_SNDTIMEO && !LWIP_SO_LINGER */ lwip_netconn_do_close_internal(conn); } /* @todo: implement connect timeout here? */ @@ -423,7 +431,7 @@ setup_tcp(struct netconn *conn) tcp_arg(pcb, conn); tcp_recv(pcb, recv_tcp); tcp_sent(pcb, sent_tcp); - tcp_poll(pcb, poll_tcp, 4); + tcp_poll(pcb, poll_tcp, NETCONN_TCP_POLL_INTERVAL); tcp_err(pcb, err_tcp); } @@ -466,7 +474,7 @@ accept_function(void *arg, struct tcp_pcb *newpcb, err_t err) tcp_arg(pcb, NULL); tcp_recv(pcb, NULL); tcp_sent(pcb, NULL); - tcp_poll(pcb, NULL, 4); + tcp_poll(pcb, NULL, 0); tcp_err(pcb, NULL); /* remove reference from to the pcb from this netconn */ newconn->pcb.tcp = NULL; @@ -647,6 +655,9 @@ netconn_alloc(enum netconn_type t, netconn_callback callback) conn->recv_bufsize = RECV_BUFSIZE_DEFAULT; conn->recv_avail = 0; #endif /* LWIP_SO_RCVBUF */ +#if LWIP_SO_LINGER + conn->linger = -1; +#endif /* LWIP_SO_LINGER */ conn->flags = 0; return conn; free_and_return: @@ -752,12 +763,16 @@ netconn_drain(struct netconn *conn) * * @param conn the TCP netconn to close */ -static void +static err_t lwip_netconn_do_close_internal(struct netconn *conn) { err_t err; u8_t shut, shut_rx, shut_tx, close; - struct tcp_pcb* tpcb = conn->pcb.tcp; + u8_t close_finished = 0; + struct tcp_pcb* tpcb; +#if LWIP_SO_LINGER + u8_t linger_wait_required = 0; +#endif /* LWIP_SO_LINGER */ LWIP_ASSERT("invalid conn", (conn != NULL)); LWIP_ASSERT("this is for tcp netconns only", (NETCONNTYPE_GROUP(conn->type) == NETCONN_TCP)); @@ -765,6 +780,7 @@ lwip_netconn_do_close_internal(struct netconn *conn) LWIP_ASSERT("pcb already closed", (conn->pcb.tcp != NULL)); LWIP_ASSERT("conn->current_msg != NULL", conn->current_msg != NULL); + tpcb = conn->pcb.tcp; shut = conn->current_msg->msg.sd.shut; shut_rx = shut & NETCONN_SHUT_RD; shut_tx = shut & NETCONN_SHUT_WR; @@ -799,49 +815,136 @@ lwip_netconn_do_close_internal(struct netconn *conn) tcp_sent(tpcb, NULL); } if (close) { - tcp_poll(tpcb, NULL, 4); + tcp_poll(tpcb, NULL, 0); tcp_err(tpcb, NULL); } } /* Try to close the connection */ if (close) { - err = tcp_close(tpcb); +#if LWIP_SO_LINGER + /* check linger possibilites before calling tcp_close */ + err = ERR_OK; + /* linger enabled/required at all? (i.e. is there untransmitted data left?) */ + if ((conn->linger >= 0) && (conn->pcb.tcp->unsent || conn->pcb.tcp->unacked)) { + if ((conn->linger == 0)) { + /* data left but linger prevents waiting */ + tcp_abort(tpcb); + tpcb = NULL; + } else if (conn->linger > 0) { + /* data left and linger says we should wait */ + if (netconn_is_nonblocking(conn)) { + /* data left on a nonblocking netconn -> cannot linger */ + err = ERR_WOULDBLOCK; + } else if ((s32_t)(sys_now() - conn->current_msg->msg.sd.time_started) >= + (conn->linger * 1000)) { + /* data left but linger timeout has expired (this happens on further + calls to this function through poll_tcp */ + tcp_abort(tpcb); + tpcb = NULL; + } else { + /* data left -> need to wait for ACK after successful close */ + linger_wait_required = 1; + } + } + } + if ((err == ERR_OK) && (tpcb != NULL)) +#endif /* LWIP_SO_LINGER */ + { + err = tcp_close(tpcb); + } } else { err = tcp_shutdown(tpcb, shut_rx, shut_tx); } if (err == ERR_OK) { - /* Closing succeeded */ + close_finished = 1; +#if LWIP_SO_LINGER + if (linger_wait_required) { + /* wait for ACK of all unsent/unacked data by just getting called again */ + close_finished = 0; + } +#endif /* LWIP_SO_LINGER */ + } else { + if(err == ERR_MEM) { + /* Closing failed because of memory shortage */ + if (netconn_is_nonblocking(conn)) { + /* Nonblocking close failed */ + close_finished = 1; + err = ERR_WOULDBLOCK; + } else { + /* Blocking close, check the timeout */ +#if LWIP_SO_SNDTIMEO || LWIP_SO_LINGER + s32_t close_timeout = LWIP_TCP_CLOSE_TIMEOUT_MS_DEFAULT; + /* this is kind of an lwip addition to the standard sockets: we wait + for some time when failing to allocate a segment for the FIN */ +#if LWIP_SO_SNDTIMEO + if (conn->send_timeout >= 0) { + close_timeout = conn->send_timeout; + } +#endif /* LWIP_SO_SNDTIMEO */ +#if LWIP_SO_LINGER + if (conn->linger >= 0) { + /* use linger timeout (seconds) */ + close_timeout = conn->linger * 1000U; + } +#endif + if ((s32_t)(sys_now() - conn->current_msg->msg.sd.time_started) >= close_timeout) { +#else /* LWIP_SO_SNDTIMEO || LWIP_SO_LINGER */ + if (conn->current_msg->msg.sd.polls_left == 0) { +#endif /* LWIP_SO_SNDTIMEO || LWIP_SO_LINGER */ + close_finished = 1; + if (close) { + /* in this case, we want to RST the connection */ + tcp_abort(tpcb); + err = ERR_OK; + } + } + } + } else { + /* Closing failed for a non-memory error: give up */ + close_finished = 1; + } + } + if (close_finished) { + /* Closing done (succeeded, non-memory error, nonblocking error or timeout) */ sys_sem_t* op_completed_sem = LWIP_API_MSG_SEM(conn->current_msg); - conn->current_msg->err = ERR_OK; + conn->current_msg->err = err; conn->current_msg = NULL; conn->state = NETCONN_NONE; - if (close) { - /* Set back some callback pointers as conn is going away */ - conn->pcb.tcp = NULL; - /* Trigger select() in socket layer. Make sure everybody notices activity - on the connection, error first! */ - API_EVENT(conn, NETCONN_EVT_ERROR, 0); - } - if (shut_rx) { - API_EVENT(conn, NETCONN_EVT_RCVPLUS, 0); - } - if (shut_tx) { - API_EVENT(conn, NETCONN_EVT_SENDPLUS, 0); + if (err == ERR_OK) { + if (close) { + /* Set back some callback pointers as conn is going away */ + conn->pcb.tcp = NULL; + /* Trigger select() in socket layer. Make sure everybody notices activity + on the connection, error first! */ + API_EVENT(conn, NETCONN_EVT_ERROR, 0); + } + if (shut_rx) { + API_EVENT(conn, NETCONN_EVT_RCVPLUS, 0); + } + if (shut_tx) { + API_EVENT(conn, NETCONN_EVT_SENDPLUS, 0); + } } /* wake up the application task */ sys_sem_signal(op_completed_sem); - } else { - /* Closing failed, restore some of the callbacks */ + return ERR_OK; + } + if (!close_finished) { + /* Closing failed and we want to wait: restore some of the callbacks */ /* Closing of listen pcb will never fail! */ - LWIP_ASSERT("Closing a listen pcb may not fail!", (conn->pcb.tcp->state != LISTEN)); - tcp_sent(conn->pcb.tcp, sent_tcp); - tcp_poll(conn->pcb.tcp, poll_tcp, 4); - tcp_err(conn->pcb.tcp, err_tcp); - tcp_arg(conn->pcb.tcp, conn); + LWIP_ASSERT("Closing a listen pcb may not fail!", (tpcb->state != LISTEN)); + if (shut_tx) { + tcp_sent(tpcb, sent_tcp); + } + /* when waiting for close, set up poll interval to 500ms */ + tcp_poll(tpcb, poll_tcp, 1); + tcp_err(tpcb, err_tcp); + tcp_arg(tpcb, conn); /* don't restore recv callback: we don't want to receive any more data */ } /* If closing didn't succeed, we get called again either from poll_tcp or from sent_tcp */ + return err; } #endif /* LWIP_TCP */ @@ -854,13 +957,12 @@ lwip_netconn_do_close_internal(struct netconn *conn) void lwip_netconn_do_delconn(struct api_msg_msg *msg) { - /* @todo TCP: abort running write/connect? */ - if ((msg->conn->state != NETCONN_NONE) && + enum netconn_state state = msg->conn->state; + LWIP_ASSERT("netconn state error", /* this only happens for TCP netconns */ + (state == NETCONN_NONE) || (NETCONNTYPE_GROUP(msg->conn->type) == NETCONN_TCP)); + if ((msg->conn->state != NETCONN_NONE) && (msg->conn->state != NETCONN_LISTEN) && (msg->conn->state != NETCONN_CONNECT)) { - /* this only happens for TCP netconns */ - LWIP_ASSERT("NETCONNTYPE_GROUP(msg->conn->type) == NETCONN_TCP", - NETCONNTYPE_GROUP(msg->conn->type) == NETCONN_TCP); msg->err = ERR_INPROGRESS; } else { LWIP_ASSERT("blocking connect in progress", @@ -890,7 +992,17 @@ lwip_netconn_do_delconn(struct api_msg_msg *msg) msg->conn->state = NETCONN_CLOSE; msg->msg.sd.shut = NETCONN_SHUT_RDWR; msg->conn->current_msg = msg; +#if LWIP_TCPIP_CORE_LOCKING + if (lwip_netconn_do_close_internal(msg->conn) != ERR_OK) { + LWIP_ASSERT("state!", msg->conn->state == NETCONN_CLOSE); + UNLOCK_TCPIP_CORE(); + sys_arch_sem_wait(LWIP_API_MSG_SEM(msg), 0); + LOCK_TCPIP_CORE(); + LWIP_ASSERT("state!", msg->conn->state == NETCONN_NONE); + } +#else /* LWIP_TCPIP_CORE_LOCKING */ lwip_netconn_do_close_internal(msg->conn); +#endif /* LWIP_TCPIP_CORE_LOCKING */ /* API_EVENT is called inside lwip_netconn_do_close_internal, before releasing the application thread, so we can return at this point! */ return; @@ -1524,16 +1636,14 @@ void lwip_netconn_do_close(struct api_msg_msg *msg) { #if LWIP_TCP - /* @todo: abort running write/connect? */ - if ((msg->conn->state != NETCONN_NONE) && (msg->conn->state != NETCONN_LISTEN)) { - /* this only happens for TCP netconns */ - LWIP_ASSERT("NETCONNTYPE_GROUP(msg->conn->type) == NETCONN_TCP", - NETCONNTYPE_GROUP(msg->conn->type) == NETCONN_TCP); - msg->err = ERR_INPROGRESS; - } else if ((msg->conn->pcb.tcp != NULL) && (NETCONNTYPE_GROUP(msg->conn->type) == NETCONN_TCP)) { - if ((msg->msg.sd.shut != NETCONN_SHUT_RDWR) && (msg->conn->state == NETCONN_LISTEN)) { - /* LISTEN doesn't support half shutdown */ - msg->err = ERR_CONN; + enum netconn_state state = msg->conn->state; + /* First check if this is a TCP netconn and if it is in a correct state + (LISTEN doesn't support half shutdown) */ + if ((msg->conn->pcb.tcp != NULL) && + (NETCONNTYPE_GROUP(msg->conn->type) == NETCONN_TCP) && + ((msg->msg.sd.shut == NETCONN_SHUT_RDWR) || (state != NETCONN_LISTEN))) { + if ((state == NETCONN_CONNECT) || (state == NETCONN_WRITE)) { + msg->err = ERR_INPROGRESS; } else { if (msg->msg.sd.shut & NETCONN_SHUT_RD) { /* Drain and delete mboxes */ @@ -1543,7 +1653,17 @@ lwip_netconn_do_close(struct api_msg_msg *msg) msg->conn->write_offset == 0); msg->conn->state = NETCONN_CLOSE; msg->conn->current_msg = msg; +#if LWIP_TCPIP_CORE_LOCKING + if (lwip_netconn_do_close_internal(msg->conn) != ERR_OK) { + LWIP_ASSERT("state!", msg->conn->state == NETCONN_CLOSE); + UNLOCK_TCPIP_CORE(); + sys_arch_sem_wait(LWIP_API_MSG_SEM(msg), 0); + LOCK_TCPIP_CORE(); + LWIP_ASSERT("state!", msg->conn->state == NETCONN_NONE); + } +#else /* LWIP_TCPIP_CORE_LOCKING */ lwip_netconn_do_close_internal(msg->conn); +#endif /* LWIP_TCPIP_CORE_LOCKING */ /* for tcp netconns, lwip_netconn_do_close_internal ACKs the message */ return; } diff --git a/src/api/sockets.c b/src/api/sockets.c index 4109cee4..0421c280 100644 --- a/src/api/sockets.c +++ b/src/api/sockets.c @@ -558,6 +558,7 @@ lwip_close(int s) { struct lwip_sock *sock; int is_tcp = 0; + err_t err; LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_close(%d)\n", s)); @@ -572,7 +573,11 @@ lwip_close(int s) LWIP_ASSERT("sock->lastdata == NULL", sock->lastdata == NULL); } - netconn_delete(sock->conn); + err = netconn_delete(sock->conn); + if (err != ERR_OK) { + sock_set_errno(sock, err_to_errno(err)); + return -1; + } free_socket(sock, is_tcp); set_errno(0); @@ -1196,9 +1201,6 @@ lwip_selscan(int maxfdp1, fd_set *readset_in, fd_set *writeset_in, fd_set *excep return nready; } -/** - * Processing exceptset is not yet implemented. - */ int lwip_select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, struct timeval *timeout) @@ -1781,6 +1783,23 @@ lwip_getsockopt_impl(int s, int level, int optname, void *optval, socklen_t *opt *(int *)optval = netconn_get_recvbufsize(sock->conn); break; #endif /* LWIP_SO_RCVBUF */ +#if LWIP_SO_LINGER + case SO_LINGER: + { + s16_t conn_linger; + struct linger* linger = (struct linger*)optval; + LWIP_SOCKOPT_CHECK_OPTLEN_CONN(sock, *optlen, struct linger); + conn_linger = sock->conn->linger; + if (conn_linger >= 0) { + linger->l_onoff = 1; + linger->l_linger = (int)conn_linger; + } else { + linger->l_onoff = 0; + linger->l_linger = 0; + } + } + break; +#endif /* LWIP_SO_LINGER */ #if LWIP_UDP case SO_NO_CHECK: LWIP_SOCKOPT_CHECK_OPTLEN_CONN_PCB_TYPE(sock, *optlen, int, NETCONN_UDP); @@ -2117,6 +2136,26 @@ lwip_setsockopt_impl(int s, int level, int optname, const void *optval, socklen_ netconn_set_recvbufsize(sock->conn, *(int*)optval); break; #endif /* LWIP_SO_RCVBUF */ +#if LWIP_SO_LINGER + case SO_LINGER: + { + struct linger* linger = (struct linger*)optval; + LWIP_SOCKOPT_CHECK_OPTLEN_CONN(sock, optlen, struct linger); + if (linger->l_onoff) { + int lingersec = linger->l_linger; + if (lingersec < 0) { + return EINVAL; + } + if (lingersec > 0xFFFF) { + lingersec = 0xFFFF; + } + sock->conn->linger = (s16_t)lingersec; + } else { + sock->conn->linger = -1; + } + } + break; +#endif /* LWIP_SO_LINGER */ #if LWIP_UDP case SO_NO_CHECK: LWIP_SOCKOPT_CHECK_OPTLEN_CONN_PCB_TYPE(sock, optlen, int, NETCONN_UDP); diff --git a/src/include/lwip/api.h b/src/include/lwip/api.h index 7f0d014e..c0145a66 100644 --- a/src/include/lwip/api.h +++ b/src/include/lwip/api.h @@ -214,6 +214,10 @@ struct netconn { for UDP and RAW, used for FIONREAD */ int recv_avail; #endif /* LWIP_SO_RCVBUF */ +#if LWIP_SO_LINGER + /** values <0 mean linger is disabled, values > 0 are seconds to linger */ + s16_t linger; +#endif /* LWIP_SO_LINGER */ /** flags holding more netconn-internal state, see NETCONN_FLAG_* defines */ u8_t flags; #if LWIP_TCP diff --git a/src/include/lwip/api_msg.h b/src/include/lwip/api_msg.h index 3505bf36..e602a1af 100644 --- a/src/include/lwip/api_msg.h +++ b/src/include/lwip/api_msg.h @@ -113,6 +113,11 @@ struct api_msg_msg { /** used for lwip_netconn_do_close (/shutdown) */ struct { u8_t shut; +#if LWIP_SO_SNDTIMEO || LWIP_SO_LINGER + u32_t time_started; +#else /* LWIP_SO_SNDTIMEO || LWIP_SO_LINGER */ + u8_t polls_left; +#endif /* LWIP_SO_SNDTIMEO || LWIP_SO_LINGER */ } sd; #if LWIP_IGMP || (LWIP_IPV6 && LWIP_IPV6_MLD) /** used for lwip_netconn_do_join_leave_group */ diff --git a/src/include/lwip/opt.h b/src/include/lwip/opt.h index 9efcc50c..c0d600ca 100644 --- a/src/include/lwip/opt.h +++ b/src/include/lwip/opt.h @@ -1573,6 +1573,13 @@ #define LWIP_SO_RCVBUF 0 #endif +/** + * LWIP_SO_LINGER==1: Enable SO_LINGER processing. + */ +#ifndef LWIP_SO_LINGER +#define LWIP_SO_LINGER 0 +#endif + /** * If LWIP_SO_RCVBUF is used, this is the default value for recv_bufsize. */ @@ -1580,6 +1587,13 @@ #define RECV_BUFSIZE_DEFAULT INT_MAX #endif +/** + * By default, TCP socket/netconn close waits 20 seconds max to send the FIN + */ +#ifndef LWIP_TCP_CLOSE_TIMEOUT_MS_DEFAULT +#define LWIP_TCP_CLOSE_TIMEOUT_MS_DEFAULT 20000 +#endif + /** * SO_REUSE==1: Enable SO_REUSEADDR option. */