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

This commit is contained in:
sg 2015-02-10 22:15:54 +01:00
parent ee833ea594
commit 6c3f6cfd89
7 changed files with 258 additions and 53 deletions

View File

@ -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

View File

@ -51,6 +51,7 @@
#include "lwip/raw.h"
#include "lwip/udp.h"
#include "lwip/tcp.h"
#include "lwip/tcp_impl.h"
#include <string.h>
@ -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);

View File

@ -55,6 +55,9 @@
#include <string.h>
/* 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;
}

View File

@ -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);

View File

@ -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

View File

@ -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 */

View File

@ -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.
*/