半字长的数据从网络字节顺序转换到我们的处理器支持的字节顺序
任何网络的通信都是基于底层硬件链路的,底层的数据链路有着自己的一套寻址机制,在以太网中,往往是通过一个48位的MAC地址来标示不同的网络通信设备的。而TCP/IP协议的上层是使用IP地址作为各个主机间通信寻址机制的。
ARP缓存表
LWIP中描述缓存表项的数据结构叫etharp_entry struct etharp_entry {
#if ARP_QUEUEING //ARP_QUEUEING是编译选项,表示是否允许缓存表项有数据包缓冲队列
struct etharp_q_entry *q; // 数据包缓冲队列指针 #endif
struct ip_addr ipaddr; // 目标IP地址 struct eth_addr ethaddr; // MAC 地址 enum etharp_state state; // 描述该entry的状态 u8_tctime; // 描述该entry的时间信息 struct netif *netif; // 相应网络接口信息 };
ipaddr 和ethaddr字段就是分别用于存储IP地址和MAC地址的,它们是ARP缓存表项的核心部分了
LWIP内核通过数组的方式来创建ARP缓存表,如下,
static struct etharp_entry arp_table[ARP_TABLE_SIZE]; 要发往该表项中IP 地址处的数据包会被连接在表项对应的数据包缓冲队列上,当等到该表项稳定后,这些数据包才会被发送出去。这就是为什么每个表项需要有数据包缓冲队列指针了。
ctime字段记录表项处于某个状态的时间,当某表项的ctime值大于规定的表项最大生存值时,该表项会被内核删除 目的地址为全 1 的特殊地址是广播地址。在ARP表项建立前,源主机只知道目的主机的IP 地址,并不知道其MAC地址,所以在数据链路上,源主机只有通过广播的方式将ARP请求数据包发送出去。电缆上的所有以太网接口都要接收广播的数据包,并检测数据包是否是发给自己的,这点通过对照目的IP 地址来实现,如果是发给自己的,目的主机需要回复一个ARP应答数据包给源主机,以告诉源主机自己的MAC地址。
对于一个ARP请求来说,除目的端MAC地址外的所有其他的字段都有填充值。当目的主机收到一份给自己的ARP请求报文后,它就把自己的硬件地址填进去,然后将该请求数据包的源主机信息和目的主机信息交换位置,并把操作字段op置为2,最后把该新构建的数据包发送回去,这就是ARP响应。
ARP搜索
find_entry,该函数主要功能是寻找一个匹配的ARP表项或
者创建一个新的ARP表项,并返回该表项的索引号 LWIP中有个全局的变量etharp_cached_entry,它始终保存着上次用到的索引号
etharp_query,该函数的功能是向给定的IP地址发送一个数据包或者发送一个ARP请求如果给定的IP地址不在ARP表中,则一个新的ARP表项会被创建,此时该表项处于pending状态,同时一个关于该IP地址的ARP请求包会被广播出去, 再同时要发送的数据包会被挂接在该表项的数据缓冲指针上;如果IP地址在ARP表中有相应的表项存在,但该表项处于pending状态,则操作与前者相同,即发送一个ARP请求和挂接数据包;如果IP地址在ARP表中有相应的表项存在,且表项处于stable状态,此时再来判断给定的数据包是否为空,不为空则直接将该数据包发送出去,为空则向该IP 地址发送一个ARP请求
update_arp_entry,该函数用于更新ARP缓存表中的表项或者在缓存表中插入一个新的表项。该函数会在收到一个IP数据包或ARP数据包后被调用
arp_table[i].q = q->next; // 缓冲链表表头指向下一个节点 ipaddr 和ethaddr分别对应的ip地址和mac地址, update_arp_entry的流程如下:先通过调用find_entry 找到对应ipaddr 对应的表项,并设置相应的arp表项的成员(主
要是state, netif, ethaddr, cttime)
LWIP利用netif.input指向的函数接收以太网数据包,通常这个函数是ethernet_input。ethernet_input 根据以太网首部的类型字段判断收到的数据包的类型,如果是IP 包,则将该包递交给etharp_ip_input,如果是ARP包,则将该包递交给 etharp_arp_input并不是说ethernet_input 直接与底层硬件交互接收数据包,而是更底层的函数接收到数据包后将数据包递交给ethernet_input,ethernet_input再对其进行处理 ARP应答包,主要的工作就是更新arp表
etif.output指向的函数发送IP数据包,通常这个函数是etharp_output。注意,这里并不是说etharp_output直接与底层硬件交互发送数据包,而是将数据包做相应的处理,主要是将IP数据包打包成以太网帧数据,最终递交给netif.linkoutput函数来发送的。
IP层的输入
8位协议字段用来描述该IP 数据包是来自于上层的哪个协议,该值为1表示为ICMP协议,该值为2表示IGMP协议,该值为6表示TCP协议,该值为17表UDP协议
LWIP中是怎么样来描述这个IP数据报头的,使用的结构叫ip_hdr
从以太网底层进来的数据包经过ethernet_input函数分发给
IP模块或者ARP模块,分发给IP 模块是通过调用ip_input 函数完成的,当然在递交前,ethernet_input需要将数据包去掉以太网头
对IP 数据报头做校验,该工作是函数inet_chksum 完成的 ip_input 函数会遍历netif_list链表上的netif结构以找到匹配的IP地址,并记录该netif结构体变量,也即记录该网卡 这就是IP 分片数据包的重装,ip_input函数通过数据包的3位标志和13位片偏移字段判断发给自己的该IP包是不是分片包,如果是,则需要将该分片包暂存,等到接收完所有分片包后,统一将整个数据包递交给上层应用程序
ip_input函数根据IP数据包头内部的协议字段判断该数据包应该被递交给哪个上层协议,并调用相应的函数递交数据包。是UDP协议,则调用udp_input函数;是TCP协议,则调用 tcp_input 函数;是ICMP协议,则调用icmp_input 函数;是IGMP协议,则调用igmp_input函数;如果都不是,则调用函数icmp_dest_unreach 返回一个协议不可达ICMP 数据包给源主机,同时删除数据包。
TCP的建立与断开
源端口号和目的端口号,用于标识发送端和接收端的应用进程。这两个值加上IP 首部中的源IP地址和目的IP地址就能