Skip to content

Commit

Permalink
This commit implements a proper version of SO_LINGER. Not sufficientl…
Browse files Browse the repository at this point in the history
…y tested on initial commit.

Squashed commit of the following:

    net/: Fix some naming inconsistencies, Fix final compilation issies.

    net/inet/inet_close():  Now that we have logic to drain the buffered TX data, we can implement a proper lingering close.

    net/inet,tcp,udp:  Add functions to wait for write buffers to drain.

    net/udp:  Add support for notification when the UDP write buffer becomes empty.

    net/tcp:  Add support for notification when the TCP write buffer becomes empty.
  • Loading branch information
gregory-nutt committed Jul 1, 2019
1 parent ded1900 commit de5a616
Show file tree
Hide file tree
Showing 19 changed files with 952 additions and 195 deletions.
25 changes: 1 addition & 24 deletions TODO
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ nuttx/:
(9) Kernel/Protected Build
(3) C++ Support
(5) Binary loaders (binfmt/)
(18) Network (net/, drivers/net)
(17) Network (net/, drivers/net)
(4) USB (drivers/usbdev, drivers/usbhost)
(2) Other drivers (drivers/)
(9) Libraries (libs/libc/, libs/libm/)
Expand Down Expand Up @@ -1663,29 +1663,6 @@ o Network (net/, drivers/net)
anything but a well-known point-to-point configuration
impossible.

Title: SO_LINGER IMPLEMENTATION IS INCORRECT
Description: Support for the SO_LINGER socket option is implemented but
not correctly. Currently, it simply adds a timeout to the
"normal" delay for the FIN to be sent with an additional
timeout. That is not even close to the required behavior.
Per OpenGroup.org:

SO_LINGER
Lingers on a close() if data is present. This option
controls the action taken when unsent messages queue
on a socket and close() is performed. If SO_LINGER
is set, the system shall block the calling thread
during close() until it can transmit the data or
until the time expires. If SO_LINGER is not specified,
and close() is issued, the system handles the call
in a way that allows the calling thread to continue
as quickly as possible. This option takes a linger
structure, as defined in the <sys/socket.h> header,
to specify the state of the option and linger interval.

Status: Open
Priority: Medium Low.

o USB (drivers/usbdev, drivers/usbhost)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Expand Down
12 changes: 7 additions & 5 deletions include/nuttx/wqueue.h
Original file line number Diff line number Diff line change
Expand Up @@ -284,11 +284,13 @@ struct work_s

enum work_evtype_e
{
WORK_IOB_AVAIL = 1, /* Notify availability of an IOB */
WORK_NET_DOWN, /* Notify that the network is down */
WORK_TCP_READAHEAD, /* Notify that TCP read-ahead data is available */
WORK_TCP_DISCONNECT, /* Notify loss of TCP connection */
WORK_UDP_READAHEAD /* Notify that TCP read-ahead data is available */
WORK_IOB_AVAIL = 1, /* Notify availability of an IOB */
WORK_NET_DOWN, /* Notify that the network is down */
WORK_TCP_READAHEAD, /* Notify that TCP read-ahead data is available */
WORK_TCP_WRITEBUFFER, /* Notify that TCP write buffer is empty */
WORK_TCP_DISCONNECT, /* Notify loss of TCP connection */
WORK_UDP_READAHEAD, /* Notify that UDP read-ahead data is available */
WORK_UDP_WRITEBUFFER /* Notify that UDP write buffer is empty */
};

/* This structure describes one notification and is provided as input to
Expand Down
4 changes: 4 additions & 0 deletions net/inet/Make.defs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ ifeq ($(CONFIG_NET_IPv6),y)
SOCK_CSRCS += ipv6_setsockopt.c ipv6_getsockname.c ipv6_getpeername.c
endif

ifeq ($(CONFIG_NET_SOLINGER),y)
SOCK_CSRCS += inet_txdrain.c
endif

# Include inet build support

DEPPATH += --dep-path inet
Expand Down
23 changes: 23 additions & 0 deletions net/inet/inet.h
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,29 @@ ssize_t inet_recvfrom(FAR struct socket *psock, FAR void *buf, size_t len,

int inet_close(FAR struct socket *psock);

/****************************************************************************
* Name: inet_txdrain
*
* Description:
* Wait for any buffered Tx data to be sent from the socket. This is part
* of the implementation of SO_LINGER
*
* Parameters:
* psock - Pointer to the socket structure instance
* abstime - The absolute time when the timeout will occur
*
* Returned Value:
* Zero (OK) is returned on success; a negated error value is returned on
* any failure.
*
****************************************************************************/

#ifdef CONFIG_NET_SOLINGER
struct timespec;
int inet_txdrain(FAR struct socket *psock,
FAR const struct timespec *abstime);
#endif

#undef EXTERN
#if defined(__cplusplus)
}
Expand Down
157 changes: 41 additions & 116 deletions net/inet/inet_close.c
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,6 @@
#include <nuttx/net/tcp.h>
#include <nuttx/net/udp.h>

#ifdef CONFIG_NET_SOLINGER
# include <nuttx/clock.h>
#endif

#include "netdev/netdev.h"
#include "devif/devif.h"
#include "tcp/tcp.h"
Expand All @@ -81,61 +77,13 @@ struct tcp_close_s
FAR struct socket *cl_psock; /* Reference to the TCP socket */
sem_t cl_sem; /* Signals disconnect completion */
int cl_result; /* The result of the close */
#ifdef CONFIG_NET_SOLINGER
clock_t cl_start; /* Time close started (in ticks) */
#endif
};
#endif

/****************************************************************************
* Private Functions
****************************************************************************/

/****************************************************************************
* Name: tcp_close_timeout
*
* Description:
* Check for a timeout on a lingering close.
*
* Input Parameters:
* pstate - close state structure
*
* Returned Value:
* TRUE:timeout FALSE:no timeout
*
* Assumptions:
* The network is locked
*
****************************************************************************/

#if defined(NET_TCP_HAVE_STACK) && defined(CONFIG_NET_SOLINGER)
static inline int tcp_close_timeout(FAR struct tcp_close_s *pstate)
{
FAR struct socket *psock = 0;

/* Make sure that we are performing a lingering close */

if (pstate != NULL)
{
/* Yes Check for a timeout configured via setsockopts(SO_LINGER).
* If none... we well let the send wait forever.
*/

psock = pstate->cl_psock;
if (psock && psock->s_linger != 0)
{
/* Check if the configured timeout has elapsed */

return net_timeo(pstate->cl_start, psock->s_linger);
}
}

/* No timeout */

return FALSE;
}
#endif /* NET_TCP_HAVE_STACK && CONFIG_NET_SOLINGER */

/****************************************************************************
* Name: tcp_close_eventhandler
*
Expand Down Expand Up @@ -202,19 +150,6 @@ static uint16_t tcp_close_eventhandler(FAR struct net_driver_s *dev,
}
}

#ifdef CONFIG_NET_SOLINGER
/* Check for a timeout. */

else if (pstate && tcp_close_timeout(pstate))
{
/* Yes.. Wake up the waiting thread and report the timeout */

nerr("ERROR: CLOSE timeout\n");
pstate->cl_result = -ETIMEDOUT;
goto end_wait;
}
#endif /* CONFIG_NET_SOLINGER */

#ifdef CONFIG_NET_TCP_WRITE_BUFFERS
/* Check if all outstanding bytes have been ACKed */

Expand Down Expand Up @@ -337,6 +272,46 @@ static inline int tcp_close_disconnect(FAR struct socket *psock)
conn = (FAR struct tcp_conn_s *)psock->s_conn;
DEBUGASSERT(conn != NULL);

#ifdef CONFIG_NET_SOLINGER
/* SO_LINGER
* Lingers on a close() if data is present. This option controls the
* action taken when unsent messages queue on a socket and close() is
* performed. If SO_LINGER is set, the system shall block the calling
* thread during close() until it can transmit the data or until the
* time expires. If SO_LINGER is not specified, and close() is issued,
* the system handles the call in a way that allows the calling thread
* to continue as quickly as possible. This option takes a linger
* structure, as defined in the <sys/socket.h> header, to specify the
* state of the option and linger interval.
*/

linger = _SO_GETOPT(psock->s_options, SO_LINGER);
if (linger)
{
/* Get the current time */

DEBUGVERIFY(clock_gettime(CLOCK_REALTIME, &abstime));

/* NOTE: s_linger's unit is deciseconds so we don't need to update
* abstime.tv_nsec here.
*/

abstime.tv_sec += psock->s_linger / DSEC_PER_SEC;

/* Wait until abstime for the buffered TX data to be sent. */

ret = inet_txdrain(psock, &abstime);
if (ret < 0)
{
/* inet_txdrain may fail, but that won't stop us from closing the
* socket.
*/

nerr("ERROR: inet_txdrain() failed: %d\n", ret);
}
}
#endif

#ifdef CONFIG_NET_TCP_WRITE_BUFFERS
/* If we have a semi-permanent write buffer callback in place, then
* is needs to be be nullified.
Expand Down Expand Up @@ -387,63 +362,13 @@ static inline int tcp_close_disconnect(FAR struct socket *psock)
nxsem_init(&state.cl_sem, 0, 0);
nxsem_setprotocol(&state.cl_sem, SEM_PRIO_NONE);

#ifdef CONFIG_NET_SOLINGER
/* Record the time that we started the wait (in ticks) */

state.cl_start = clock_systimer();
#endif

/* Notify the device driver of the availability of TX data */

tcp_close_txnotify(psock, conn);

/* Wait for the disconnect event */

#ifdef CONFIG_NET_SOLINGER
/* A non-NULL value of the priv field means that lingering is
* enabled.
*
* REVISIT: SO_LINGER is not really implemented. Per OpenGroup.org:
*
* SO_LINGER
* Lingers on a close() if data is present. This option
* controls the action taken when unsent messages queue
* on a socket and close() is performed. If SO_LINGER
* is set, the system shall block the calling thread
* during close() until it can transmit the data or
* until the time expires. If SO_LINGER is not specified,
* and close() is issued, the system handles the call
* in a way that allows the calling thread to continue
* as quickly as possible. This option takes a linger
* structure, as defined in the <sys/socket.h> header,
* to specify the state of the option and linger interval.
*
* Here it merely adds a pointless timeout on top of the normal
* close operation. It should first wait for all data in the
* protocol-specific write buffers to drain.
*/

linger = _SO_GETOPT(psock->s_options, SO_LINGER);
if (linger)
{
DEBUGVERIFY(clock_gettime(CLOCK_REALTIME, &abstime));

/* NOTE: s_linger's unit is deciseconds,
* so we don't need to update abstime.tv_nsec here.
*/

abstime.tv_sec += psock->s_linger / DSEC_PER_SEC;

if (-ETIMEDOUT == net_timedwait(&state.cl_sem, &abstime))
{
state.cl_result = -ETIMEDOUT;
}
}
else
#endif
{
(void)net_lockedwait(&state.cl_sem);
}
(void)net_lockedwait(&state.cl_sem);

/* We are now disconnected */

Expand Down
Loading

0 comments on commit de5a616

Please sign in to comment.