/** * Abandons a connection and optionally sends a RST to the remote * host. Deletes the local protocol control block. This is done when * a connection is killed because of shortage of memory. * * @param pcb the tcp_pcb to abort * @param reset boolean to indicate whether a reset should be sent */ void tcp_abandon(TCP_PCB *pcb, uint16 reset) { uint32 seqno, ackno; uint16 remote_port, local_port; IP_ADDR remote_ip, local_ip; /* if there is an outstanding delayed ACKs, send it */ if (pcb->state != TIME_WAIT && pcb->flags & TF_ACK_DELAY) { pcb->flags |= TF_ACK_NOW; tcp_output(pcb); } /* Figure out on which TCP PCB list we are, and remove us. If we are in an active state, call the receive function associated with the PCB with a NULL argument, and send an RST to the remote end. */ if (pcb->state == TIME_WAIT) { pcb->state = CLOSED; tcp_pcb_purge(pcb); tcp_close(pcb); } else { seqno = pcb->snd_nxt; ackno = pcb->rcv_nxt; ip_addr_set(&local_ip, &(pcb->local_ip)); ip_addr_set(&remote_ip, &(pcb->remote_ip)); local_port = pcb->local_port; remote_port = pcb->remote_port; pcb->state = CLOSED; if (pcb->unacked != NULL) { tcp_segs_free(pcb->unacked); pcb->unacked = NULL; } if (pcb->unsent != NULL) { tcp_segs_free(pcb->unsent); pcb->unsent = NULL; } if (pcb->ooseq != NULL) { tcp_segs_free(pcb->ooseq); pcb->ooseq = NULL; } if (reset) { printf("tcp rst tcp_abandon!\n"); tcp_rst(seqno, ackno, &local_ip, &remote_ip, local_port, remote_port); } } }
/****************************************************************************** * FunctionName : espconn_tcp_disconnect * Description : disconnect with host * Parameters : arg -- Additional argument to pass to the callback function * Returns : none *******************************************************************************/ static void ICACHE_FLASH_ATTR espconn_tcp_disconnect_successful(void *arg) { espconn_msg *pdiscon_cb = arg; sint8 dis_err = 0; espconn_buf *pdis_buf = NULL; espconn_buf *pdis_back = NULL; espconn_kill_oldest_pcb(); if (pdiscon_cb != NULL) { struct espconn *espconn = pdiscon_cb->preverse; dis_err = pdiscon_cb->pcommon.err; if (pdiscon_cb->pespconn != NULL){ struct tcp_pcb *pcb = NULL; if (espconn != NULL){/*Process the server's message block*/ if (pdiscon_cb->pespconn->proto.tcp != NULL && espconn->proto.tcp){ espconn_copy_partial(espconn, pdiscon_cb->pespconn); espconn_printf("server: %d.%d.%d.%d : %d disconnect\n", espconn->proto.tcp->remote_ip[0], espconn->proto.tcp->remote_ip[1],espconn->proto.tcp->remote_ip[2], espconn->proto.tcp->remote_ip[3],espconn->proto.tcp->remote_port); free(pdiscon_cb->pespconn->proto.tcp); pdiscon_cb->pespconn->proto.tcp = NULL; } free(pdiscon_cb->pespconn); pdiscon_cb->pespconn = NULL; } else {/*Process the client's message block*/ espconn = pdiscon_cb->pespconn; espconn_printf("client: %d.%d.%d.%d : %d disconnect\n", espconn->proto.tcp->local_ip[0], espconn->proto.tcp->local_ip[1],espconn->proto.tcp->local_ip[2], espconn->proto.tcp->local_ip[3],espconn->proto.tcp->local_port); } /*process the current TCP block*/ pcb = espconn_find_current_pcb(pdiscon_cb); if (pcb != NULL){ if (espconn_reuse_disabled(pdiscon_cb)) { struct tcp_pcb *cpcb = NULL; struct tcp_pcb *prev = NULL; uint8_t pcb_remove; espconn_printf("espconn_tcp_disconnect_successful %d, %d\n", pcb->state, pcb->local_port); cpcb = tcp_tw_pcbs; while (cpcb != NULL) { pcb_remove = 0; if (cpcb->local_port == pcb->local_port) { ++pcb_remove; } /* If the PCB should be removed, do it. */ if (pcb_remove) { struct tcp_pcb *backup_pcb = NULL; tcp_pcb_purge(cpcb); /* Remove PCB from tcp_tw_pcbs list. */ if (prev != NULL) { LWIP_ASSERT("espconn_tcp_delete: middle cpcb != tcp_tw_pcbs",cpcb != tcp_tw_pcbs); prev->next = cpcb->next; } else { /* This PCB was the first. */ LWIP_ASSERT("espconn_tcp_delete: first cpcb == tcp_tw_pcbs",tcp_tw_pcbs == cpcb); tcp_tw_pcbs = cpcb->next; } backup_pcb = cpcb; cpcb = cpcb->next; memp_free(MEMP_TCP_PCB, backup_pcb); } else { prev = cpcb; cpcb = cpcb->next; } } } else { tcp_arg(pcb, NULL); tcp_err(pcb, NULL); } } } /*to prevent memory leaks, ensure that each allocated is deleted*/ pdis_buf = pdiscon_cb->pcommon.pbuf; while (pdis_buf != NULL) { pdis_back = pdis_buf; pdis_buf = pdis_back->pnext; espconn_pbuf_delete(&pdiscon_cb->pcommon.pbuf, pdis_back); free(pdis_back); pdis_back = NULL; } bzero(&pktinfo[0], sizeof(struct espconn_packet)); memcpy(&pktinfo[0], (void*)&pdiscon_cb->pcommon.packet_info, sizeof(struct espconn_packet)); free(pdiscon_cb); pdiscon_cb = NULL; if (espconn->proto.tcp && espconn->proto.tcp->disconnect_callback != NULL) { espconn->proto.tcp->disconnect_callback(espconn); } } else { espconn_printf("espconn_tcp_disconnect err\n"); } }
/** * 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 * (tcp_recved() not called for all data passed to recv callback). * * Listening pcbs are freed and may not be referenced any more. * Connection pcbs are freed if not yet connected and may not be referenced * any more. If a connection is established (at least SYN received or in * a closing state), the connection is closed, and put in a closing state. * The pcb is then automatically freed in tcp_slowtmr(). It is therefore * unsafe to reference it. * * @param pcb the tcp_pcb to close * @return ERR_OK if connection has been closed * another err_t if closing failed and pcb is not freed */ static err_t tcp_close_shutdown(struct tcp_pcb *pcb, u8_t rst_on_unacked_data) { err_t err; if (rst_on_unacked_data && (pcb->state != LISTEN)) { if ((pcb->refused_data != NULL) || (pcb->rcv_wnd != TCP_WND)) { /* Not all data received by application, send RST to tell the remote side about this. */ LWIP_ASSERT("pcb->flags & TF_RXCLOSED", pcb->flags & TF_RXCLOSED); /* don't call tcp_abort here: we must not deallocate the pcb since that might not be expected when calling tcp_close */ tcp_rst(pcb->snd_nxt, pcb->rcv_nxt, &pcb->local_ip, &pcb->remote_ip, pcb->local_port, pcb->remote_port); tcp_pcb_purge(pcb); /* TODO: to which state do we move now? */ /* move to TIME_WAIT since we close actively */ TCP_RMV(&tcp_active_pcbs, pcb); pcb->state = TIME_WAIT; TCP_REG(&tcp_tw_pcbs, pcb); return ERR_OK; } } switch (pcb->state) { case CLOSED: /* Closing a pcb in the CLOSED state might seem erroneous, * however, it is in this state once allocated and as yet unused * and the user needs some way to free it should the need arise. * Calling tcp_close() with a pcb that has already been closed, (i.e. twice) * 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 * been freed, and so any remaining handles are bogus. */ err = ERR_OK; if (pcb->local_port != 0) { TCP_RMV(&tcp_bound_pcbs, pcb); } memp_free(MEMP_TCP_PCB, pcb); pcb = NULL; break; case LISTEN: err = ERR_OK; tcp_pcb_remove(&tcp_listen_pcbs.pcbs, pcb); memp_free(MEMP_TCP_PCB_LISTEN, pcb); pcb = NULL; break; case SYN_SENT: err = ERR_OK; tcp_pcb_remove(&tcp_active_pcbs, pcb); memp_free(MEMP_TCP_PCB, pcb); pcb = NULL; snmp_inc_tcpattemptfails(); break; case SYN_RCVD: err = tcp_send_fin(pcb); if (err == ERR_OK) { snmp_inc_tcpattemptfails(); pcb->state = FIN_WAIT_1; } break; case ESTABLISHED: err = tcp_send_fin(pcb); if (err == ERR_OK) { snmp_inc_tcpestabresets(); pcb->state = FIN_WAIT_1; } break; case CLOSE_WAIT: err = tcp_send_fin(pcb); if (err == ERR_OK) { snmp_inc_tcpestabresets(); pcb->state = LAST_ACK; } break; default: /* Has already been closed, do nothing. */ err = ERR_OK; pcb = NULL; break; } if (pcb != NULL && err == ERR_OK) { /* To ensure all data has been sent when tcp_close returns, we have to make sure tcp_output doesn't fail. 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 for the return value of tcp_output for now. */ /* @todo: When implementing SO_LINGER, this must be changed somehow: If SOF_LINGER is set, the data should be sent and acked before close returns. This can only be valid for sequential APIs, not for the raw API. */ tcp_output(pcb); } return err; }
/** * Called every 500 ms and implements the retransmission timer and the timer that * removes PCBs that have been in TIME-WAIT for enough time. It also increments * various timers such as the inactivity timer in each PCB. * * Automatically called from tcp_tmr(). */ void tcp_slowtmr(void) { TCP_PCB *pcb, *pcb2, *prev; uint16 eff_wnd; uint8 err= ERR_OK; ++tcp_ticks; /* Steps through all of the active PCBs. */ prev = NULL; pcb = &stTcpPcb; if (pcb->state == SYN_SENT && pcb->nrtx == TCP_SYNMAXRTX) { printf("SYN SENT CLOSED\n"); pcb->state = CLOSED; tcp_pcb_purge(pcb); tcp_close(pcb); return; } else if (pcb->nrtx == TCP_MAXRTX) { printf("TCP MAXRTX CLOSED\n"); pcb->state = CLOSED; tcp_pcb_purge(pcb); tcp_close(pcb); return; } else { if (pcb->persist_backoff > 0) { /* If snd_wnd is zero, use persist timer to send 1 byte probes * instead of using the standard retransmission mechanism. */ pcb->persist_cnt++; if (pcb->persist_cnt >= tcp_persist_backoff[pcb->persist_backoff-1]) { pcb->persist_cnt = 0; if (pcb->persist_backoff < sizeof(tcp_persist_backoff)) { pcb->persist_backoff++; } tcp_zero_window_probe(pcb); } } else { /* Increase the retransmission timer if it is running */ if (pcb->rtime >= 0) ++pcb->rtime; if (pcb->unacked != NULL && pcb->rtime >= pcb->rto) { /* Double retransmission time-out unless we are trying to * connect to somebody (i.e., we are in SYN_SENT). */ if (pcb->state != SYN_SENT) { pcb->rto = ((pcb->sa >> 3) + pcb->sv) << tcp_backoff[pcb->nrtx]; } /* Reset the retransmission timer. */ pcb->rtime = 0; /* Reduce congestion window and ssthresh. */ eff_wnd = LWIP_MIN(pcb->cwnd, pcb->snd_wnd); pcb->ssthresh = eff_wnd >> 1; if (pcb->ssthresh < pcb->mss) { pcb->ssthresh = pcb->mss * 2; } pcb->cwnd = pcb->mss; /* The following needs to be called AFTER cwnd is set to one mss - STJ */ tcp_rexmit_rto(pcb); } } }
/** * Implements the TCP state machine. Called by tcp_input. In some * states tcp_receive() is called to receive data. The tcp_seg * argument will be freed by the caller (tcp_input()) unless the * recv_data pointer in the pcb is set. * * @param pcb the tcp_pcb for which a segment arrived * * @note the segment which arrived is saved in global variables, therefore only the pcb * involved is passed as a parameter to this function */ static err_t tcp_process(struct tcp_pcb *pcb) { struct tcp_seg *rseg; u8_t acceptable = 0; err_t err; u8_t accepted_inseq; err = ERR_OK; /* Process incoming RST segments. */ if (flags & TCP_RST) { /* First, determine if the reset is acceptable. */ if (pcb->state == SYN_SENT) { if (ackno == pcb->snd_nxt) { acceptable = 1; } } else { if (TCP_SEQ_BETWEEN(seqno, pcb->rcv_nxt, pcb->rcv_nxt+pcb->rcv_ann_wnd)) { acceptable = 1; } } if (acceptable) { LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_process: Connection RESET\n")); LWIP_ASSERT("tcp_input: pcb->state != CLOSED", pcb->state != CLOSED); recv_flags = TF_RESET; pcb->flags &= ~TF_ACK_DELAY; return ERR_RST; } else { LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_process: unacceptable reset seqno %"U32_F" rcv_nxt %"U32_F"\n", seqno, pcb->rcv_nxt)); LWIP_DEBUGF(TCP_DEBUG, ("tcp_process: unacceptable reset seqno %"U32_F" rcv_nxt %"U32_F"\n", seqno, pcb->rcv_nxt)); return ERR_OK; } } /* Update the PCB (in)activity timer. */ pcb->tmr = tcp_ticks; pcb->keep_cnt_sent = 0; /* Do different things depending on the TCP state. */ switch (pcb->state) { case SYN_SENT: LWIP_DEBUGF(TCP_INPUT_DEBUG, ("SYN-SENT: ackno %"U32_F" pcb->snd_nxt %"U32_F" unacked %"U32_F"\n", ackno, pcb->snd_nxt, ntohl(pcb->unacked->tcphdr->seqno))); /* received SYN ACK with expected sequence number? */ if ((flags & TCP_ACK) && (flags & TCP_SYN) && ackno == ntohl(pcb->unacked->tcphdr->seqno) + 1) { pcb->snd_buf++; pcb->rcv_nxt = seqno + 1; pcb->lastack = ackno; pcb->snd_wnd = tcphdr->wnd; pcb->snd_wl1 = seqno - 1; /* initialise to seqno - 1 to force window update */ pcb->state = ESTABLISHED; /* Parse any options in the SYNACK before using pcb->mss since that * can be changed by the received options! */ tcp_parseopt(pcb); #if TCP_CALCULATE_EFF_SEND_MSS pcb->mss = tcp_eff_send_mss(pcb->mss, &(pcb->remote_ip)); #endif /* TCP_CALCULATE_EFF_SEND_MSS */ /* Set ssthresh again after changing pcb->mss (already set in tcp_connect * but for the default value of pcb->mss) */ pcb->ssthresh = pcb->mss * 10; pcb->cwnd = ((pcb->cwnd == 1) ? (pcb->mss * 2) : pcb->mss); LWIP_ASSERT("pcb->snd_queuelen > 0", (pcb->snd_queuelen > 0)); --pcb->snd_queuelen; LWIP_DEBUGF(TCP_QLEN_DEBUG, ("tcp_process: SYN-SENT --queuelen %"U16_F"\n", (u16_t)pcb->snd_queuelen)); rseg = pcb->unacked; pcb->unacked = rseg->next; /* If there's nothing left to acknowledge, stop the retransmit timer, otherwise reset it to start again */ if(pcb->unacked == NULL) pcb->rtime = -1; else { pcb->rtime = 0; pcb->nrtx = 0; } tcp_seg_free(rseg); /* Call the user specified function to call when sucessfully * connected. */ TCP_EVENT_CONNECTED(pcb, ERR_OK, err); tcp_ack_now(pcb); } /* received ACK? possibly a half-open connection */ else if (flags & TCP_ACK) { /* send a RST to bring the other side in a non-synchronized state. */ tcp_rst(ackno, seqno + tcplen, &(iphdr->dest), &(iphdr->src), tcphdr->dest, tcphdr->src); } break; case SYN_RCVD: if (flags & TCP_ACK && !(flags & TCP_RST)) { /* expected ACK number? */ if (TCP_SEQ_BETWEEN(ackno, pcb->lastack+1, pcb->snd_nxt)) { u16_t old_cwnd; pcb->state = ESTABLISHED; LWIP_DEBUGF(TCP_DEBUG, ("TCP connection established %"U16_F" -> %"U16_F".\n", inseg.tcphdr->src, inseg.tcphdr->dest)); #if LWIP_CALLBACK_API LWIP_ASSERT("pcb->accept != NULL", pcb->accept != NULL); #endif /* Call the accept function. */ TCP_EVENT_ACCEPT(pcb, ERR_OK, err); if (err != ERR_OK) { /* If the accept function returns with an error, we abort * the connection. */ tcp_abort(pcb); return ERR_ABRT; } old_cwnd = pcb->cwnd; /* If there was any data contained within this ACK, * we'd better pass it on to the application as well. */ accepted_inseq = tcp_receive(pcb); pcb->cwnd = ((old_cwnd == 1) ? (pcb->mss * 2) : pcb->mss); if ((flags & TCP_FIN) && accepted_inseq) { tcp_ack_now(pcb); pcb->state = CLOSE_WAIT; } } /* incorrect ACK number */ else { /* send RST */ tcp_rst(ackno, seqno + tcplen, &(iphdr->dest), &(iphdr->src), tcphdr->dest, tcphdr->src); } } break; case CLOSE_WAIT: /* FALLTHROUGH */ case ESTABLISHED: accepted_inseq = tcp_receive(pcb); if ((flags & TCP_FIN) && accepted_inseq) { /* passive close */ tcp_ack_now(pcb); pcb->state = CLOSE_WAIT; } break; case FIN_WAIT_1: tcp_receive(pcb); if (flags & TCP_FIN) { if (flags & TCP_ACK && ackno == pcb->snd_nxt) { LWIP_DEBUGF(TCP_DEBUG, ("TCP connection closed %"U16_F" -> %"U16_F".\n", inseg.tcphdr->src, inseg.tcphdr->dest)); tcp_ack_now(pcb); tcp_pcb_purge(pcb); TCP_RMV(&tcp_active_pcbs, pcb); pcb->state = TIME_WAIT; TCP_REG(&tcp_tw_pcbs, pcb); } else { tcp_ack_now(pcb); pcb->state = CLOSING; } } else if (flags & TCP_ACK && ackno == pcb->snd_nxt) { pcb->state = FIN_WAIT_2; } break; case FIN_WAIT_2: tcp_receive(pcb); if (flags & TCP_FIN) { LWIP_DEBUGF(TCP_DEBUG, ("TCP connection closed %"U16_F" -> %"U16_F".\n", inseg.tcphdr->src, inseg.tcphdr->dest)); tcp_ack_now(pcb); tcp_pcb_purge(pcb); TCP_RMV(&tcp_active_pcbs, pcb); pcb->state = TIME_WAIT; TCP_REG(&tcp_tw_pcbs, pcb); } break; case CLOSING: tcp_receive(pcb); if (flags & TCP_ACK && ackno == pcb->snd_nxt) { LWIP_DEBUGF(TCP_DEBUG, ("TCP connection closed %"U16_F" -> %"U16_F".\n", inseg.tcphdr->src, inseg.tcphdr->dest)); tcp_ack_now(pcb); tcp_pcb_purge(pcb); TCP_RMV(&tcp_active_pcbs, pcb); pcb->state = TIME_WAIT; TCP_REG(&tcp_tw_pcbs, pcb); } break; case LAST_ACK: tcp_receive(pcb); if (flags & TCP_ACK && ackno == pcb->snd_nxt) { LWIP_DEBUGF(TCP_DEBUG, ("TCP connection closed %"U16_F" -> %"U16_F".\n", inseg.tcphdr->src, inseg.tcphdr->dest)); /* bugfix #21699: don't set pcb->state to CLOSED here or we risk leaking segments */ recv_flags = TF_CLOSED; } break; default: break; } return ERR_OK; }