Fixed bug #47485 (tcp_close() should not fail on memory error) by retrying to send FIN from tcp_fasttmr

This commit is contained in:
goldsimon 2017-02-10 09:42:38 +01:00
parent 82711e069c
commit bc07fd9db5
3 changed files with 41 additions and 14 deletions

View File

@ -6,6 +6,9 @@ HISTORY
++ New features: ++ New features:
2017-02-10: Simon Goldschmidt
* tcp_close does not fail on memory error (instead, FIN is sent from tcp_tmr)
2017-02-04: David van Moolenbroek 2017-02-04: David van Moolenbroek
- IPv6 scopes support - IPv6 scopes support

View File

@ -132,6 +132,8 @@ static u8_t tcp_timer;
static u8_t tcp_timer_ctr; static u8_t tcp_timer_ctr;
static u16_t tcp_new_port(void); static u16_t tcp_new_port(void);
static err_t tcp_close_shutdown_fin(struct tcp_pcb *pcb);
/** /**
* Initialize this module. * Initialize this module.
*/ */
@ -258,8 +260,6 @@ tcp_backlog_accepted(struct tcp_pcb* pcb)
static err_t static err_t
tcp_close_shutdown(struct tcp_pcb *pcb, u8_t rst_on_unacked_data) tcp_close_shutdown(struct tcp_pcb *pcb, u8_t rst_on_unacked_data)
{ {
err_t err;
if (rst_on_unacked_data && ((pcb->state == ESTABLISHED) || (pcb->state == CLOSE_WAIT))) { if (rst_on_unacked_data && ((pcb->state == ESTABLISHED) || (pcb->state == CLOSE_WAIT))) {
if ((pcb->refused_data != NULL) || (pcb->rcv_wnd != TCP_WND_MAX(pcb))) { if ((pcb->refused_data != NULL) || (pcb->rcv_wnd != TCP_WND_MAX(pcb))) {
/* Not all data received by application, send RST to tell the remote /* Not all data received by application, send RST to tell the remote
@ -290,6 +290,8 @@ tcp_close_shutdown(struct tcp_pcb *pcb, u8_t rst_on_unacked_data)
} }
} }
/* - states which free the pcb are handled here,
- states which send FIN and change state are handled in tcp_close_shutdown_impl() */
switch (pcb->state) { switch (pcb->state) {
case CLOSED: case CLOSED:
/* Closing a pcb in the CLOSED state might seem erroneous, /* Closing a pcb in the CLOSED state might seem erroneous,
@ -299,27 +301,34 @@ tcp_close_shutdown(struct tcp_pcb *pcb, u8_t rst_on_unacked_data)
* or for a pcb that has been used and then entered the CLOSED state * or for a pcb that has been used and then entered the CLOSED state
* is erroneous, but this should never happen as the pcb has in those cases * is erroneous, but this should never happen as the pcb has in those cases
* been freed, and so any remaining handles are bogus. */ * been freed, and so any remaining handles are bogus. */
err = ERR_OK;
if (pcb->local_port != 0) { if (pcb->local_port != 0) {
TCP_RMV(&tcp_bound_pcbs, pcb); TCP_RMV(&tcp_bound_pcbs, pcb);
} }
memp_free(MEMP_TCP_PCB, pcb); memp_free(MEMP_TCP_PCB, pcb);
pcb = NULL;
break; break;
case LISTEN: case LISTEN:
err = ERR_OK;
tcp_listen_closed(pcb); tcp_listen_closed(pcb);
tcp_pcb_remove(&tcp_listen_pcbs.pcbs, pcb); tcp_pcb_remove(&tcp_listen_pcbs.pcbs, pcb);
memp_free(MEMP_TCP_PCB_LISTEN, pcb); memp_free(MEMP_TCP_PCB_LISTEN, pcb);
pcb = NULL;
break; break;
case SYN_SENT: case SYN_SENT:
err = ERR_OK;
TCP_PCB_REMOVE_ACTIVE(pcb); TCP_PCB_REMOVE_ACTIVE(pcb);
memp_free(MEMP_TCP_PCB, pcb); memp_free(MEMP_TCP_PCB, pcb);
pcb = NULL;
MIB2_STATS_INC(mib2.tcpattemptfails); MIB2_STATS_INC(mib2.tcpattemptfails);
break; break;
default:
return tcp_close_shutdown_fin(pcb);
}
return ERR_OK;
}
static err_t
tcp_close_shutdown_fin(struct tcp_pcb *pcb)
{
err_t err;
LWIP_ASSERT("pcb != NULL", pcb != NULL);
switch (pcb->state) {
case SYN_RCVD: case SYN_RCVD:
err = tcp_send_fin(pcb); err = tcp_send_fin(pcb);
if (err == ERR_OK) { if (err == ERR_OK) {
@ -344,18 +353,20 @@ tcp_close_shutdown(struct tcp_pcb *pcb, u8_t rst_on_unacked_data)
break; break;
default: default:
/* Has already been closed, do nothing. */ /* Has already been closed, do nothing. */
err = ERR_OK; return ERR_OK;
pcb = NULL;
break; break;
} }
if (pcb != NULL && err == ERR_OK) { if (err == ERR_OK) {
/* To ensure all data has been sent when tcp_close returns, we have /* To ensure all data has been sent when tcp_close returns, we have
to make sure tcp_output doesn't fail. to make sure tcp_output doesn't fail.
Since we don't really have to ensure all data has been sent when tcp_close Since we don't really have to ensure all data has been sent when tcp_close
returns (unsent data is sent from tcp timer functions, also), we don't care returns (unsent data is sent from tcp timer functions, also), we don't care
for the return value of tcp_output for now. */ for the return value of tcp_output for now. */
tcp_output(pcb); tcp_output(pcb);
} else if (err == ERR_MEM) {
/* Mark this pcb for closing. Closing is retried from tcp_tmr. */
pcb->flags |= TF_CLOSEPEND;
} }
return err; return err;
} }
@ -1276,6 +1287,12 @@ tcp_fasttmr_start:
tcp_output(pcb); tcp_output(pcb);
pcb->flags &= ~(TF_ACK_DELAY | TF_ACK_NOW); pcb->flags &= ~(TF_ACK_DELAY | TF_ACK_NOW);
} }
/* send pending FIN */
if (pcb->flags & TF_CLOSEPEND) {
LWIP_DEBUGF(TCP_DEBUG, ("tcp_fasttmr: pending FIN\n"));
pcb->flags &= ~(TF_CLOSEPEND);
tcp_close_shutdown_fin(pcb);
}
next = pcb->next; next = pcb->next;

View File

@ -145,7 +145,7 @@ typedef u32_t tcpwnd_size_t;
typedef u16_t tcpwnd_size_t; typedef u16_t tcpwnd_size_t;
#endif #endif
#if LWIP_WND_SCALE || TCP_LISTEN_BACKLOG #if LWIP_WND_SCALE || TCP_LISTEN_BACKLOG || LWIP_TCP_TIMESTAMPS
typedef u16_t tcpflags_t; typedef u16_t tcpflags_t;
#else #else
typedef u8_t tcpflags_t; typedef u8_t tcpflags_t;
@ -210,7 +210,7 @@ struct tcp_pcb {
#define TF_ACK_DELAY 0x01U /* Delayed ACK. */ #define TF_ACK_DELAY 0x01U /* Delayed ACK. */
#define TF_ACK_NOW 0x02U /* Immediate ACK. */ #define TF_ACK_NOW 0x02U /* Immediate ACK. */
#define TF_INFR 0x04U /* In fast recovery. */ #define TF_INFR 0x04U /* In fast recovery. */
#define TF_TIMESTAMP 0x08U /* Timestamp option enabled */ #define TF_CLOSEPEND 0x08U /* If this is set, tcp_close failed to enqueue the FIN (retried in tcp_tmr) */
#define TF_RXCLOSED 0x10U /* rx closed by tcp_shutdown */ #define TF_RXCLOSED 0x10U /* rx closed by tcp_shutdown */
#define TF_FIN 0x20U /* Connection was closed locally (FIN segment enqueued). */ #define TF_FIN 0x20U /* Connection was closed locally (FIN segment enqueued). */
#define TF_NODELAY 0x40U /* Disable Nagle algorithm */ #define TF_NODELAY 0x40U /* Disable Nagle algorithm */
@ -220,6 +220,9 @@ struct tcp_pcb {
#endif #endif
#if TCP_LISTEN_BACKLOG #if TCP_LISTEN_BACKLOG
#define TF_BACKLOGPEND 0x0200U /* If this is set, a connection pcb has increased the backlog on its listener */ #define TF_BACKLOGPEND 0x0200U /* If this is set, a connection pcb has increased the backlog on its listener */
#endif
#if LWIP_TCP_TIMESTAMPS
#define TF_TIMESTAMP 0x0400U /* Timestamp option enabled */
#endif #endif
/* the rest of the fields are in host byte order /* the rest of the fields are in host byte order
@ -358,7 +361,11 @@ void tcp_accept (struct tcp_pcb *pcb, tcp_accept_fn accept);
#endif /* LWIP_CALLBACK_API */ #endif /* LWIP_CALLBACK_API */
void tcp_poll (struct tcp_pcb *pcb, tcp_poll_fn poll, u8_t interval); void tcp_poll (struct tcp_pcb *pcb, tcp_poll_fn poll, u8_t interval);
#if LWIP_TCP_TIMESTAMPS
#define tcp_mss(pcb) (((pcb)->flags & TF_TIMESTAMP) ? ((pcb)->mss - 12) : (pcb)->mss) #define tcp_mss(pcb) (((pcb)->flags & TF_TIMESTAMP) ? ((pcb)->mss - 12) : (pcb)->mss)
#else /* LWIP_TCP_TIMESTAMPS */
#define tcp_mss(pcb) ((pcb)->mss)
#endif /* LWIP_TCP_TIMESTAMPS */
#define tcp_sndbuf(pcb) (TCPWND16((pcb)->snd_buf)) #define tcp_sndbuf(pcb) (TCPWND16((pcb)->snd_buf))
#define tcp_sndqueuelen(pcb) ((pcb)->snd_queuelen) #define tcp_sndqueuelen(pcb) ((pcb)->snd_queuelen)
/** @ingroup tcp_raw */ /** @ingroup tcp_raw */