lwip: fix the assertion in tcp_pcb_purge()

Fix the assertion in tcp_pcb_purge().
This commit is contained in:
Liu Zhi Fu
2018-06-29 13:40:20 +08:00
parent 66dad0c506
commit 31ca811240
6 changed files with 149 additions and 57 deletions

View File

@@ -149,6 +149,22 @@ tcp_tmr(void)
}
}
#if LWIP_CALLBACK_API || TCP_LISTEN_BACKLOG
/** Called when a listen pcb is closed. Iterates one pcb list and removes the
* closed listener pcb from pcb->listener if matching.
*/
static void
tcp_remove_listener(struct tcp_pcb *list, struct tcp_pcb_listen *lpcb)
{
struct tcp_pcb *pcb;
for (pcb = list; pcb != NULL; pcb = pcb->next) {
if (pcb->listener == lpcb) {
pcb->listener = NULL;
}
}
}
#endif
void
tcp_set_fin_wait_1(struct tcp_pcb *pcb)
{
@@ -158,6 +174,70 @@ tcp_set_fin_wait_1(struct tcp_pcb *pcb)
#endif
}
/** Called when a listen pcb is closed. Iterates all pcb lists and removes the
* closed listener pcb from pcb->listener if matching.
*/
static void
tcp_listen_closed(struct tcp_pcb *pcb)
{
#if LWIP_CALLBACK_API || TCP_LISTEN_BACKLOG
size_t i;
LWIP_ASSERT("pcb != NULL", pcb != NULL);
LWIP_ASSERT("pcb->state == LISTEN", pcb->state == LISTEN);
for (i = 1; i < LWIP_ARRAYSIZE(tcp_pcb_lists); i++) {
tcp_remove_listener(*tcp_pcb_lists[i], (struct tcp_pcb_listen *)pcb);
}
#endif
LWIP_UNUSED_ARG(pcb);
}
#if TCP_LISTEN_BACKLOG
/** @ingroup tcp_raw
* Delay accepting a connection in respect to the listen backlog:
* the number of outstanding connections is increased until
* tcp_backlog_accepted() is called.
*
* ATTENTION: the caller is responsible for calling tcp_backlog_accepted()
* or else the backlog feature will get out of sync!
*
* @param pcb the connection pcb which is not fully accepted yet
*/
void
tcp_backlog_delayed(struct tcp_pcb *pcb)
{
LWIP_ASSERT("pcb != NULL", pcb != NULL);
if ((pcb->flags & TF_BACKLOGPEND) == 0) {
if (pcb->listener != NULL) {
pcb->listener->accepts_pending++;
LWIP_ASSERT("accepts_pending != 0", pcb->listener->accepts_pending != 0);
pcb->flags |= TF_BACKLOGPEND;
}
}
}
/** @ingroup tcp_raw
* A delayed-accept a connection is accepted (or closed/aborted): decreases
* the number of outstanding connections after calling tcp_backlog_delayed().
*
* ATTENTION: the caller is responsible for calling tcp_backlog_accepted()
* or else the backlog feature will get out of sync!
*
* @param pcb the connection pcb which is now fully accepted (or closed/aborted)
*/
void
tcp_backlog_accepted(struct tcp_pcb *pcb)
{
LWIP_ASSERT("pcb != NULL", pcb != NULL);
if ((pcb->flags & TF_BACKLOGPEND) != 0) {
if (pcb->listener != NULL) {
LWIP_ASSERT("accepts_pending != 0", pcb->listener->accepts_pending != 0);
pcb->listener->accepts_pending--;
pcb->flags &= ~TF_BACKLOGPEND;
}
}
}
#endif /* TCP_LISTEN_BACKLOG */
/**
* Closes the TX side of a connection held by the PCB.
* For tcp_close(), a RST is sent if the application didn't receive all data
@@ -227,6 +307,7 @@ tcp_close_shutdown(struct tcp_pcb *pcb, u8_t rst_on_unacked_data)
break;
case LISTEN:
err = ERR_OK;
tcp_listen_closed(pcb);
tcp_pcb_remove(&tcp_listen_pcbs.pcbs, pcb);
memp_free(MEMP_TCP_PCB_LISTEN, pcb);
pcb = NULL;
@@ -241,6 +322,7 @@ tcp_close_shutdown(struct tcp_pcb *pcb, u8_t rst_on_unacked_data)
case SYN_RCVD:
err = tcp_send_fin(pcb);
if (err == ERR_OK) {
tcp_backlog_accepted(pcb);
MIB2_STATS_INC(mib2.tcpattemptfails);
tcp_set_fin_wait_1(pcb);
}
@@ -408,6 +490,7 @@ tcp_abandon(struct tcp_pcb *pcb, int reset)
pcb->ooseq = NULL;
}
#endif /* TCP_QUEUE_OOSEQ */
tcp_backlog_accepted(pcb);
if (send_rst) {
LWIP_DEBUGF(TCP_RST_DEBUG, ("tcp_abandon: sending RST\n"));
tcp_rst(seqno, ackno, &pcb->local_ip, &pcb->remote_ip, local_port, pcb->remote_port);
@@ -1747,39 +1830,7 @@ tcp_pcb_purge(struct tcp_pcb *pcb)
LWIP_DEBUGF(TCP_DEBUG, ("tcp_pcb_purge\n"));
#if TCP_LISTEN_BACKLOG
if (pcb->state == SYN_RCVD) {
/* Need to find the corresponding listen_pcb and decrease its accepts_pending */
struct tcp_pcb_listen *lpcb;
/*
* The official LWIP will assert the system if tcp_listen_pcbs.listen_pcbs is NULL, it's a bug.
*
* Considering following scenario:
* 1. On TCP server side, one of TCP pcb is in SYNC_RECV state and is waiting for TCP ACK from TCP client.
* 2. The TCP server is closed by application, which causes the tcp_listen_pcbs.listen_pcbs to become NULL.
* 3. When SYNC_RECV state timeout (20s by default), tcp_pcb_purge() is called in tcp_slowtmr(), it asserts
* the system.
* This is a normal scenario, should NOT assert the system. So just remove it.
*
*/
if (tcp_listen_pcbs.listen_pcbs) {
for (lpcb = tcp_listen_pcbs.listen_pcbs; lpcb != NULL; lpcb = lpcb->next) {
if ((lpcb->local_port == pcb->local_port) &&
(IP_IS_V6_VAL(pcb->local_ip) == IP_IS_V6_VAL(lpcb->local_ip)) &&
(ip_addr_isany(&lpcb->local_ip) ||
ip_addr_cmp(&pcb->local_ip, &lpcb->local_ip))) {
/* port and address of the listen pcb match the timed-out pcb */
LWIP_ASSERT("tcp_pcb_purge: listen pcb does not have accepts pending",
lpcb->accepts_pending > 0);
lpcb->accepts_pending--;
break;
}
}
}
}
#endif /* TCP_LISTEN_BACKLOG */
tcp_backlog_accepted(pcb);
if (pcb->refused_data != NULL) {
LWIP_DEBUGF(TCP_DEBUG, ("tcp_pcb_purge: data left on ->refused_data\n"));