LWIP -- tcp_input()函数分析

2019-07-14 09:38发布

调用流程:    数据包首先调用ethernet_input()函数到达数据链路层,去掉以太网头部;
    根据以太网头部类型判断:如果是ARP报文传给调用arp_input()交给ARP协议处理,如果是IP报文就调用ip_input()进入IP层处理;
    ip_input()函数中比较数据报文的目的IP地址,如果与某个网络接口的IP地址相同,则接收这个报文,依照IP头部的协议字段,调用各自协议的输入处理函数;
    如果是TCP类型,调用tcp_input()。

函数简析:
    tcp_input接收IP层递交上来的数据包,并根据数据包查找相应TCP控制块,并根据相关控制块所处的状态调用函数tcp_timewait_inputtcp_listen_inputtcp_process进行处理。
    如果是调用的前两个函数,则tcp_input在这两个函数返回后就结束了,但若调用的是tcp_process函数,则函数返回后,tcp_input还要进行许多相应的处理。

具体分析:    (在源码中有详细注释)    1.检查报文会否有效(pbuf大小是否小于TCP报头20字节,不包括选项);
    2.判断是否是多播/单播报文,丢弃;
    3.是否开启TCP校验(宏配置),若开启调用ip_chksum_pseudo函数进行TCP校验和验证;
    4.获取TCP首部长度(包括选项部分);
    5.将p指针移向pbuf的有效数据部分(除去TCP报文头和选项部分,若选项有一部分在第二个pbuf中,代码上操作较复杂);
    6.网络字节序转主机字节序;
    7.遍历tcp_active_pcbs链表,是否匹配(若匹配有操作将这个pcb移到链表前端,下次找到更快,可以借鉴);
    8.若不在tcp_active_pcbs链表,遍历tcp_tw_pcbs和tcp_listen_pcbs链表,是否匹配,
       若在tcp_tw_pcbs链表中匹配,调用tcp_timewait_input函数处理,若在tcp_listen_pcbs链表中匹配,调用tcp_listen_input函数处理,
       在遍历tcp_listen_pcbs链表中还会判断本地ip是否设置任意IP地址,操作不同;
    9.接上面第7.点,若在tcp_active_pcbs链表匹配,经过处理后进入tcp_process函数;
    10.若是进入tcp_timewait_input函数,tcp_listen_input函数,tcp_input在这两个函数返回后就结束了,若是进入tcp_process函数,返回后还要做很多处理,
       判断返回值类型(收到复位报文双方连接成功关闭收到对方的FIN请求),做对应操作,
    11.  若在3张链表里都未找到匹配的pcb,则调用tcp_rst函数向源主机发送一个TCP复位数据包

源码:
void tcp_input(struct pbuf *p, struct netif *inp) { struct tcp_pcb *pcb, *prev; struct tcp_pcb_listen *lpcb; #if SO_REUSE struct tcp_pcb *lpcb_prev = NULL; struct tcp_pcb_listen *lpcb_any = NULL; #endif /* SO_REUSE */ u8_t hdrlen_bytes; err_t err; LWIP_UNUSED_ARG(inp); PERF_START; TCP_STATS_INC(tcp.recv); MIB2_STATS_INC(mib2.tcpinsegs); /*获取TCP头,这边使用了一个全局变量tcphdr*/ tcphdr = (struct tcp_hdr *)p->payload; #if TCP_INPUT_DEBUG tcp_debug_print(tcphdr); #endif /* 判断数据包长度是否小于TCP报头长度 * (检查报文是否有效) */ if (p->len < TCP_HLEN) { /* 若报文无效,丢弃 */ LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_input: short packet (%"U16_F" bytes) discarded ", p->tot_len)); TCP_STATS_INC(tcp.lenerr); goto dropped; } /* 若是多播包/广播包,丢弃 * (不处理多播/广播报文)*/ if (ip_addr_isbroadcast(ip_current_dest_addr(), ip_current_netif()) || ip_addr_ismulticast(ip_current_dest_addr())) { TCP_STATS_INC(tcp.proterr); goto dropped; } /* (通过宏配置是否开启TCP校验) */ #if CHECKSUM_CHECK_TCP IF__NETIF_CHECKSUM_ENABLED(inp, NETIF_CHECKSUM_CHECK_TCP) { /* TCP校验和验证 */ u16_t chksum = ip_chksum_pseudo(p, IP_PROTO_TCP, p->tot_len, ip_current_src_addr(), ip_current_dest_addr()); if (chksum != 0) { LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_input: packet discarded due to failing checksum 0x%04"X16_F" ", chksum)); tcp_debug_print(tcphdr); TCP_STATS_INC(tcp.chkerr); goto dropped; } } #endif /* CHECKSUM_CHECK_TCP */ /* 获取TCP首部长度 * (若无其他选项,最小为20字节) */ hdrlen_bytes = TCPH_HDRLEN(tcphdr) * 4; /* 若获取到的TCP报头小于20字节或者TCP报头长度大于报文总长度,为无效报文,丢弃 */ if ((hdrlen_bytes < TCP_HLEN) || (hdrlen_bytes > p->tot_len)) { LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_input: invalid header length (%"U16_F") ", (u16_t)hdrlen_bytes)); TCP_STATS_INC(tcp.lenerr); goto dropped; } /* 将有效载荷指针移动到pbuf中,这样它就指向了 TCP数据而不是TCP报头。 */ tcphdr_optlen = hdrlen_bytes - TCP_HLEN; //tcphdr_optlen = TCP报头选项长度 (TCP报头总长度 - TCP标准报头20字节) tcphdr_opt2 = NULL; //tcphdr_opt2 指向NULL /* 判断TCP报头是否在一个pbuf中 */ if (p->len >= hdrlen_bytes) { /* 若TCP报头在第一个pbuf中 */ tcphdr_opt1len = tcphdr_optlen; //tcphdr_opt1len = TCP报头选项长度 pbuf_header(p, -(s16_t)hdrlen_bytes); //将指针移动到pbuf数据中 } else { u16_t opt2len; /* 若TCP报头在多个pbuf中 */ LWIP_ASSERT("p->next != NULL", p->next != NULL); /* 去除TCP标准报头(不会失败) */ pbuf_header(p, -TCP_HLEN); /* 去掉第一个pbuf的长度 */ tcphdr_opt1len = p->len; opt2len = tcphdr_optlen - tcphdr_opt1len; /* options continue in the next pbuf: set p to zero length and hide the options in the next pbuf (adjusting p->tot_len) */ pbuf_header(p, -(s16_t)tcphdr_opt1len); /* 检查TCP报头选项部分是否在第二个pbuf中 */ if (opt2len > p->next->len) { /* 丢弃过短的报文 */ LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_input: options overflow second pbuf (%"U16_F" bytes) ", p->next->len)); TCP_STATS_INC(tcp.lenerr); goto dropped; } /* 记住指向TCP报头选项的第二部分的指针 * (有部分选项在第二个pbuf中,记录TCP报头选项的开始部分) */ tcphdr_opt2 = (u8_t*)p->next->payload; /* 将第二个pbuf的指针指向pbuf 的数据部分 */ pbuf_header(p->next, -(s16_t)opt2len); p->tot_len -= opt2len; LWIP_ASSERT("p->len == 0", p->len == 0); LWIP_ASSERT("p->tot_len == p->next->tot_len", p->tot_len == p->next->tot_len); } /*将TCP报头中的数据由网络字节序转换为主机字节序 */ tcphdr->src = lwip_ntohs(tcphdr->src); tcphdr->dest = lwip_ntohs(tcphdr->dest); seqno = tcphdr->seqno = lwip_ntohl(tcphdr->seqno); ackno = tcphdr->ackno = lwip_ntohl(tcphdr->ackno); tcphdr->wnd = lwip_ntohs(tcphdr->wnd); /* 6位标志位 */ flags = TCPH_FLAGS(tcphdr); /* TCP数据包中数据的总长度,对于有FIN或SYN标志的数据包,该长度要加1 */ tcplen = p->tot_len + ((flags & (TCP_FIN | TCP_SYN)) ? 1 : 0); /* Demultiplex an incoming segment. First, we check if it is destined * for an active connection. * (以下就是对接收到的数据包进行分类处理,也就是寻找合适的接口,根据IP,port) */ prev = NULL; /* 遍历tcp_active_pcbs链表 (活动状态下的链表)*/ for (pcb = tcp_active_pcbs; pcb != NULL; pcb = pcb->next) { LWIP_ASSERT("tcp_input: active pcb->state != CLOSED", pcb->state != CLOSED); LWIP_ASSERT("tcp_input: active pcb->state != TIME-WAIT", pcb->state != TIME_WAIT); LWIP_ASSERT("tcp_input: active pcb->state != LISTEN", pcb->state != LISTEN); /* 若端口号匹配,并且IP地址匹配 (就把这个移到pcb链表的前面,下次查找更快) 可以借鉴*/ if (pcb->remote_port == tcphdr->src && pcb->local_port == tcphdr->dest && ip_addr_cmp(&pcb->remote_ip, ip_current_src_addr()) && ip_addr_cmp(&pcb->local_ip, ip_current_dest_addr())) { /* 把这个PCB移到列表的前面,这样后面的 查找速度更快(我们在TCP段中使用了位置 移位) */ LWIP_ASSERT("tcp_input: pcb->next != pcb (before cache)", pcb->next != pcb); if (prev != NULL) { prev->next = pcb->next; pcb->next = tcp_active_pcbs; tcp_active_pcbs = pcb; } else { TCP_STATS_INC(tcp.cachehit); } LWIP_ASSERT("tcp_input: pcb->next != pcb (after cache)", pcb->next != pcb); break; } prev = pcb; } /* 若不在tcp_active_pcbs链表中,遍历tcp_tw_pcbs和tcp_listen_pcbs链表 (等待状态下的链表) */ if (pcb == NULL) { /* 如果它没有连接到一个活动连接,我们检查连接 时候的状态。*/ for (pcb = tcp_tw_pcbs; pcb != NULL; pcb = pcb->next) { LWIP_ASSERT("tcp_input: TIME-WAIT pcb->state == TIME-WAIT", pcb->state == TIME_WAIT); /* 若端口&IP匹配 */ if (pcb->remote_port == tcphdr->src && pcb->local_port == tcphdr->dest && ip_addr_cmp(&pcb->remote_ip, ip_current_src_addr()) && ip_addr_cmp(&pcb->local_ip, ip_current_dest_addr())) { /* 我们不太关心把PCB移到前面 因为我们不太可能接受这个列表 有许多段时间等待连接 * (在此处不需要把该链表移到前面,因为需要等待连接) */ LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_input: packed for TIME_WAITing connection. ")); /* 等待状态下的pcb进入tcp_timewait_input */ tcp_timewait_input(pcb); pbuf_free(p); return; } } /* 最后,如果我们仍然没有匹配,我们会检查所有的PCBs 正在收听即将到来的连接。(监听状态下的链表) */ prev = NULL; for (lpcb = tcp_listen_pcbs.listen_pcbs; lpcb != NULL; lpcb = lpcb->next) { /* 匹配端口号,否则是指向本地的端口 */ if (lpcb->local_port == tcphdr->dest) { /* 本地IP是否设置的任意IP地址(这边是类型标志?) */ if (IP_IS_ANY_TYPE_VAL(lpcb->local_ip)) { /* 找到任何类型(ipv4-ipv6)匹配 */ #if SO_REUSE lpcb_any = lpcb; lpcb_prev = prev; #else /* SO_REUSE */ break; #endif /* SO_REUSE */ } else if (IP_ADDR_PCB_VERSION_MATCH_EXACT(lpcb, ip_current_dest_addr())) { /* 不是任意类型的IP地址,匹配IP */ if (ip_addr_cmp(&lpcb->local_ip, ip_current_dest_addr())) { /* 找到精确匹配,break */ break; } else if (ip_addr_isany(&lpcb->local_ip)) { /* 本地IP是否设置的任意IP地址,找到任意IP地址 */ #if SO_REUSE lpcb_any = lpcb; lpcb_prev = prev; #else /* SO_REUSE */ break; #endif /* SO_REUSE */ } } } prev = (struct tcp_pcb *)lpcb; } #if SO_REUSE /* 首先尝试任意的本地IP * (本地IP地址为任意IP的pcb) */ if (lpcb == NULL) { /* 如果没有找到特定的本地IP,则只传递给任何一个 */ lpcb = lpcb_any; prev = lpcb_prev; } #endif /* SO_REUSE */ /* 如果是精确匹配的本地IP地址 */ if (lpcb != NULL) { /* 把这个PCB移到列表的前面,这样后面的 查找速度更快. */ if (prev != NULL) { ((struct tcp_pcb_listen *)prev)->next = lpcb->next; /* our successor is the remainder of the listening list */ lpcb->next = tcp_listen_pcbs.listen_pcbs; /* put this listening pcb at the head of the listening list */ tcp_listen_pcbs.listen_pcbs = lpcb; } else { TCP_STATS_INC(tcp.cachehit); } LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_input: packed for LISTENing connection. ")); /* 监听链表下的pcb进入tcp_listen_input */ tcp_listen_input(lpcb); pbuf_free(p); return; } } #if TCP_INPUT_DEBUG LWIP_DEBUGF(TCP_INPUT_DEBUG, ("+-+-+-+-+-+-+-+-+-+-+-+-+-+- tcp_input: flags ")); tcp_debug_print_flags(TCPH_FLAGS(tcphdr)); LWIP_DEBUGF(TCP_INPUT_DEBUG, ("-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ")); #endif /* TCP_INPUT_DEBUG */ /* 若在tcp_active_pcbs链表中找到了,则经过处理后进入tcp_process */ if (pcb != NULL) { /* The incoming segment belongs to a connection. */ #if TCP_INPUT_DEBUG tcp_debug_print_state(pcb->state); #endif /* TCP_INPUT_DEBUG */ /* 建立一个tcpseg结构 */ inseg.next = NULL; // 关闭报文段队列功能 inseg.len = p->tot_len; // 设置该报文段的数据长度 inseg.p = p; // 设置报文段数据链表头指针 inseg.tcphdr = tcphdr; // 报文段的TCP头 recv_data = NULL; // 数据接收结果被保存在该全局变量,然后往上层提交 recv_flags = 0; // tcp_process执行完后的结果(控制块的状态变迁)将会被保存在该全局变量,首先在这里被清0 recv_acked = 0; if (flags & TCP_PSH) { p->flags |= PBUF_FLAG_PUSH; } /* If there is data which was previously "refused" by upper layer * (tcp_pcb的refused_data指针上是否还记录有尚未往上层递交的数据)*/ if (pcb->refused_data != NULL) { /* 有的话回调用户recv函数接收未递交的数据 * 判断处理recv函数的处理结果,成功refused_data指针清空,继续往下执行tcp_process * 失败意味着tcp_pcb都被占用满,丢弃接收包不再处理,直接返回*/ if ((tcp_process_refused_data(pcb) == ERR_ABRT) || ((pcb->refused_data != NULL) && (tcplen > 0))) { /* pcb has been aborted or refused data is still refused and the new segment contains data */ /* 若接收窗口声明为0,调动tcp_send_empty_ack,下次看 */ if (pcb->rcv_ann_wnd == 0) { /* this is a zero-window probe, we respond to it with current RCV.NXT and drop the data segment */ tcp_send_empty_ack(pcb); } TCP_STATS_INC(tcp.drop); MIB2_STATS_INC(mib2.tcpinerrs); goto aborted; } } tcp_input_pcb = pcb; // 记录处理当前报文的控制块 /* 这里就是进入tcp_process处理接收包环节了(解析下次看),该函数实现了TCP状态转换功能 */ err = tcp_process(pcb); /* (若返回值为ERR_ABRT,说明控制块已经被完全删除(tcp_abort()),什么也不需要做) * 返回值不为ERR_ABRT时,判断报文处理的3种结果 */ if (err != ERR_ABRT) { /* 接收到对方的复位报文 (第一种情况) */ if (recv_flags & TF_RESET) { /* 回调用户的errf函数 */ TCP_EVENT_ERR(pcb->errf, pcb->callback_arg, ERR_RST); /* 删除控制块 */ tcp_pcb_remove(&tcp_active_pcbs, pcb); /* 释放控制块空间 */ memp_free(MEMP_TCP_PCB, pcb); } else { err = ERR_OK; /* If the application has registered a "sent" function to be called when new send buffer space is available, we call it now. (如果应用程序已经注册了一个“发送”函数当新的发送缓冲区空间可用时,我们调用它现在)*/ if (recv_acked > 0) { u16_t acked16; #if LWIP_WND_SCALE /* 被重新调用的是u32t,但是发送的回调只需要u16t, 所以我们可能要多次调用它。 */ u32_t acked = recv_acked; while (acked > 0) { acked16 = (u16_t)LWIP_MIN(acked, 0xffffu); acked -= acked16; #else { acked16 = recv_acked; #endif TCP_EVENT_SENT(pcb, (u16_t)acked16, err); if (err == ERR_ABRT) { goto aborted; } } recv_acked = 0; } /* 双方连接成功断开(第二种情况) */ if (recv_flags & TF_CLOSED) { /* 连接已经关闭,我们将重新分配 PCB */ if (!(pcb->flags & TF_RXCLOSED)) { /* Connection closed although the application has only shut down the tx side: call the PCB's err callback and indicate the closure to ensure the application doesn't continue using the PCB. (连接关闭尽管应用程序只关闭了 tx端:调用PCB的错误回调,并指示关闭 确保应用程序不会继续使用PCB。) */ TCP_EVENT_ERR(pcb->errf, pcb->callback_arg, ERR_CLSD); } tcp_pcb_remove(&tcp_active_pcbs, pcb); memp_free(MEMP_TCP_PCB, pcb); goto aborted; } #if TCP_QUEUE_OOSEQ && LWIP_WND_SCALE while (recv_data != NULL) { struct pbuf *rest = NULL; pbuf_split_64k(recv_data, &rest); #else /* TCP_QUEUE_OOSEQ && LWIP_WND_SCALE */ if (recv_data != NULL) { #endif /* TCP_QUEUE_OOSEQ && LWIP_WND_SCALE */ LWIP_ASSERT("pcb->refused_data == NULL", pcb->refused_data == NULL); if (pcb->flags & TF_RXCLOSED) { /* 如果本地TCP控制块已经处于TF_RXCLOSED状态,则后续接收到的数据都作废 */ pbuf_free(recv_data); #if TCP_QUEUE_OOSEQ && LWIP_WND_SCALE if (rest != NULL) { pbuf_free(rest); } #endif /* TCP_QUEUE_OOSEQ && LWIP_WND_SCALE */ tcp_abort(pcb); goto aborted; } /* 通知应用程序已接收到数据。 */ TCP_EVENT_RECV(pcb, recv_data, ERR_OK, err); if (err == ERR_ABRT) { #if TCP_QUEUE_OOSEQ && LWIP_WND_SCALE if (rest != NULL) { pbuf_free(rest); } #endif /* TCP_QUEUE_OOSEQ && LWIP_WND_SCALE */ goto aborted; } /* 如果上层无法接收到这些数据,就存储它 */ if (err != ERR_OK) { #if TCP_QUEUE_OOSEQ && LWIP_WND_SCALE if (rest != NULL) { pbuf_cat(recv_data, rest); } #endif /* TCP_QUEUE_OOSEQ && LWIP_WND_SCALE */ pcb->refused_data = recv_data; LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_input: keep incoming packet, because pcb is "full" ")); #if TCP_QUEUE_OOSEQ && LWIP_WND_SCALE break; } else { /* Upper layer received the data, go on with the rest if > 64K */ recv_data = rest; #endif /* TCP_QUEUE_OOSEQ && LWIP_WND_SCALE */ } } /* 如果收到对方的FIN请求 */ if (recv_flags & TF_GOT_FIN) { if (pcb->refused_data != NULL) { /* 如果我们拒绝了数据,就推迟这个时间。 */ pcb->refused_data->flags |= PBUF_FLAG_TCP_FIN; } else { /* 纠正接收窗口 */ if (pcb->rcv_wnd != TCP_WND_MAX(pcb)) { pcb->rcv_wnd++; } /* 用一个NULL指针回调用户的recv函数,通过这种方式用户程序可以知道对方的关闭请求 */ TCP_EVENT_CLOSED(pcb, err); if (err == ERR_ABRT) { goto aborted; } } } tcp_input_pcb = NULL; //当前报文到此处理完毕,清空当前报文的控制块 /* Try to send something out. */ tcp_output(pcb); //输出报文 #if TCP_INPUT_DEBUG #if TCP_DEBUG tcp_debug_print_state(pcb->state); #endif /* TCP_DEBUG */ #endif /* TCP_INPUT_DEBUG */ } } /* Jump target if pcb has been aborted in a callback (by calling tcp_abort()). Below this line, 'pcb' may not be dereferenced! */ aborted: tcp_input_pcb = NULL; recv_data = NULL; /* give up our reference to inseg.p */ if (inseg.p != NULL) { pbuf_free(inseg.p); inseg.p = NULL; } } else { /* 如果在3张链表里都未找到匹配的pcb,则调用tcp_rst向源主机发送一个TCP复位数据包 */ LWIP_DEBUGF(TCP_RST_DEBUG, ("tcp_input: no PCB match found, resetting. ")); if (!(TCPH_FLAGS(tcphdr) & TCP_RST)) { TCP_STATS_INC(tcp.proterr); TCP_STATS_INC(tcp.drop); tcp_rst(ackno, seqno + tcplen, ip_current_dest_addr(), ip_current_src_addr(), tcphdr->dest, tcphdr->src); } pbuf_free(p); } LWIP_ASSERT("tcp_input: tcp_pcbs_sane()", tcp_pcbs_sane()); PERF_STOP("tcp_input"); return; dropped: TCP_STATS_INC(tcp.drop); MIB2_STATS_INC(mib2.tcpinerrs); pbuf_free(p); }