1、lwip ip报文输入
1.1 ip报文输入
lwip线程ethernetif_input从网卡读取数据,根据ethhdr->type判断是否为ip报文,如果是ip报文,调用netif->input即tcpip_input通过邮箱发送数据给tcpip_thread线程处理。
static void
ethernetif_input(struct netif *netif)
{
struct ethernetif *ethernetif;
struct eth_hdr *ethhdr;
struct pbuf *p;
ethernetif = netif->state;
/* move received packet into a new pbuf */
while (TRUE)
{
p = low_level_input(netif); // 从网卡读取数据
/* no packet could be read, silently ignore this */
if (p == NULL)
continue;
/* points to packet payload, which starts with an Ethernet header */
ethhdr = p->payload;
switch (htons(ethhdr->type)) {
/* IP or ARP packet? */
case ETHTYPE_IP: // ip帧
/* update ARP table */
etharp_ip_input(netif, p); // arp表更新(如果是发送给自己的报文,更新源ip对应的arp表项(状态、更新时间))
/* skip Ethernet header */
//pbuf_header(p, -(14+ETH_PAD_SIZE));
/* pass to network layer */
netif->input(p, netif); // 发送报文给tcp/ip线程处理
break;
1.2 ip报文发送到网络层(tcpip_input)
消息类型设置为TCPIP_MSG_INPKT(tcp/ip报文),消息数据指针指向从网卡收到的数据,输入网卡设置为接收到报文的网卡,调用sys_mbox_trypost发送报文给tcp/ip线程。
err_t
tcpip_input(struct pbuf *p, struct netif *inp)
{
struct tcpip_msg *msg;
if (mbox != SYS_MBOX_NULL) { // tcp/ip线程的mail box,用于接收上下层的数据
msg = memp_malloc(MEMP_TCPIP_MSG_INPKT);
if (msg == NULL) {
return ERR_MEM;
}
msg->type = TCPIP_MSG_INPKT;
msg->msg.inp.p = p;
msg->msg.inp.netif = inp;
if (sys_mbox_trypost(mbox, msg) != ERR_OK) { // 发送数据到tcp/ip线程
memp_free(MEMP_TCPIP_MSG_INPKT, msg);
return ERR_MEM;
}
return ERR_OK;
}
return ERR_VAL;
}
2、ip报文预处理
tcp/ip线程tcpip_thread处理网络层及传输层协议,tcpip_thread循环调用sys_mbox_fetch从其他线程读取数据(包括链路层及应用层),lwip下层传递整个以太网帧给tcp/ip线程,处理ip报文前先要剥去以太网帧头部数据。
2.1 以太网帧格式
如下图所示,除去目的地址、源地址、类型字段外才是ip数据报,lwip链路层发送数据给tcp/ip线程时,是整个报文发送的。

2.2 tcp/ip线程报文输入
对于TCPIP_MSG_INPKT类型的消息调用ethernet_input处理报文(包含帧的目的地址、源地址、类型字段);
LOCK_TCPIP_CORE();
while (1) { /* MAIN Loop */
sys_mbox_fetch(mbox, (void *)&msg); // 从上下层接收数据
switch (msg->type) {
#if LWIP_NETCONN
case TCPIP_MSG_API:
LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: API message %p\n", (void *)msg));
msg->msg.apimsg->function(&(msg->msg.apimsg->msg));
break;
#endif /* LWIP_NETCONN */
case TCPIP_MSG_INPKT: // tcp/ip报文消息
LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: PACKET %p\n", (void *)msg));
#if LWIP_ARP
if (msg->msg.inp.netif->flags & NETIF_FLAG_ETHARP) { // 使用arp通信的网卡(报文数据包含目的地址、源地址、类型字段,这部分不是ip报文的内容,调用ethernet_input释放这部分数据,再调用ip报文处理函数)
ethernet_input(msg->msg.inp.p, msg->msg.inp.netif); // 调用ethernet_input处理以太网报文
} else
#endif /* LWIP_ARP */
根据ethhdr->type判断帧的类型,对于ip报文,释放帧的头部(只保留ip报文数据),调用ip_input处理ip报文数据。
err_t
ethernet_input(struct pbuf *p, struct netif *netif)
{
struct eth_hdr* ethhdr;
/* points to packet payload, which starts with an Ethernet header */
ethhdr = p->payload;
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE,
("ethernet_input: dest:%02x:%02x:%02x:%02x:%02x:%02x, src:%02x:%02x:%02x:%02x:%02x:%02x, type:%2hx\n",
(unsigned)ethhdr->dest.addr[0], (unsigned)ethhdr->dest.addr[1], (unsigned)ethhdr->dest.addr[2],
(unsigned)ethhdr->dest.addr[3], (unsigned)ethhdr->dest.addr[4], (unsigned)ethhdr->dest.addr[5],
(unsigned)ethhdr->src.addr[0], (unsigned)ethhdr->src.addr[1], (unsigned)ethhdr->src.addr[2],
(unsigned)ethhdr->src.addr[3], (unsigned)ethhdr->src.addr[4], (unsigned)ethhdr->src.addr[5],
(unsigned)htons(ethhdr->type)));
switch (htons(ethhdr->type)) { // ip报文
/* IP packet? */
case ETHTYPE_IP:
#if ETHARP_TRUST_IP_MAC
/* update ARP table */
etharp_ip_input(netif, p);
#endif /* ETHARP_TRUST_IP_MAC */
/* skip Ethernet header */
if(pbuf_header(p, -(s16_t)sizeof(struct eth_hdr))) { // 释放帧头部eth_hdr,剩下的即是ip报文
LWIP_ASSERT("Can't move over header in packet", 0);
pbuf_free(p);
p = NULL;
} else {
/* pass to IP layer */
ip_input(p, netif); // 调用ip_input处理ip输入报文
}
break;
3、ip报文校验
3.1、ip报文格式
ip报文格式如下

3.2、ip报文数据结构
ip报文头部数据结构ip_hdr(3.1小结图中32位源IP地址字段及之前的字段为ip报文头部)
struct ip_hdr {
/* version / header length / type of service */
PACK_STRUCT_FIELD(u16_t _v_hl_tos);
/* total length */
PACK_STRUCT_FIELD(u16_t _len);
/* identification */
PACK_STRUCT_FIELD(u16_t _id);
/* fragment offset field */
PACK_STRUCT_FIELD(u16_t _offset);
#define IP_RF 0x8000 /* reserved fragment flag */
#define IP_DF 0x4000 /* dont fragment flag */
#define IP_MF 0x2000 /* more fragments flag */
#define IP_OFFMASK 0x1fff /* mask for fragmenting bits */
/* time to live / protocol*/
PACK_STRUCT_FIELD(u16_t _ttl_proto);
/* checksum */
PACK_STRUCT_FIELD(u16_t _chksum);
/* source and destination IP addresses */
PACK_STRUCT_FIELD(struct ip_addr src);
PACK_STRUCT_FIELD(struct ip_addr dest);
} PACK_STRUCT_STRUCT;
3.3、ip报文校验
ip报文头部指向数据起始地址。
/* identify the IP header */
iphdr = p->payload;
判断ip协议版本,非ipv4直接返回。
if (IPH_V(iphdr) != 4) {
LWIP_DEBUGF(IP_DEBUG | 1, ("IP packet dropped due to bad version number %"U16_F"\n", IPH_V(iphdr)));
ip_debug_print(p);
pbuf_free(p);
IP_STATS_INC(ip.err);
IP_STATS_INC(ip.drop);
snmp_inc_ipinhdrerrors();
return ERR_OK;
}
接下来一段代码检查接收到的数据是否与协议中的长度一致,检查校验和是否相等,报文错误则直接返回错误。
4、ip报文处理(ip_input)
4.1、查找目的ip的网卡
ip报文校验无误后,检查目的地址是否是本机up状态的网卡地址,netif记录找到的网卡。
int first = 1;
netif = inp;
do {
LWIP_DEBUGF(IP_DEBUG, ("ip_input: iphdr->dest 0x%"X32_F" netif->ip_addr 0x%"X32_F" (0x%"X32_F", 0x%"X32_F", 0x%"X32_F")\n",
iphdr->dest.addr, netif->ip_addr.addr,
iphdr->dest.addr & netif->netmask.addr,
netif->ip_addr.addr & netif->netmask.addr,
iphdr->dest.addr & ~(netif->netmask.addr)));
/* interface is up and configured? */
if ((netif_is_up(netif)) && (!ip_addr_isany(&(netif->ip_addr)))) {
/* unicast to this interface address? */
if (ip_addr_cmp(&(iphdr->dest), &(netif->ip_addr)) ||
/* or broadcast on this interface network address? */
ip_addr_isbroadcast(&(iphdr->dest), netif)) {
LWIP_DEBUGF(IP_DEBUG, ("ip_input: packet accepted on interface %c%c\n",
netif->name[0], netif->name[1]));
/* break out of for loop */
break;
}
}
if (first) {
first = 0;
netif = netif_list;
} else {
netif = netif->next;
}
if (netif == inp) {
netif = netif->next;
}
} while(netif != NULL);
}
4.2、检查源ip地址是否为广播/多播地址
如果源ip地址是广播或者多播地址,那么不处理,直接返回ERR_OK。
{ if ((ip_addr_isbroadcast(&(iphdr->src), inp)) ||
(ip_addr_ismulticast(&(iphdr->src)))) {
/* packet source is not valid */
LWIP_DEBUGF(IP_DEBUG | LWIP_DBG_TRACE | 1, ("ip_input: packet source is not valid.\n"));
/* free (drop) packet pbufs */
pbuf_free(p);
IP_STATS_INC(ip.drop);
snmp_inc_ipinaddrerrors();
snmp_inc_ipindiscards();
return ERR_OK;
}
}
4.3、报文转发(ip_forward)
在4.1中,没有找到目的ip地址的网卡(报文不是发送给自己),那么对于目的地址为非广播地址的报文进行转发(从报文输入的网卡转发报文),并返回。
/* packet not for us? */
if (netif == NULL) { // 没有找到目的ip地址对应的网卡
/* packet not for us, route or discard */
LWIP_DEBUGF(IP_DEBUG | LWIP_DBG_TRACE | 1, ("ip_input: packet not for us.\n"));
#if IP_FORWARD
/* non-broadcast packet? */
if (!ip_addr_isbroadcast(&(iphdr->dest), inp)) { // 报文目的地址非广播地址
/* try to forward IP packet on (other) interfaces */
ip_forward(p, iphdr, inp); // 对报文进行转发
} else
#endif /* IP_FORWARD */
{
snmp_inc_ipinaddrerrors();
snmp_inc_ipindiscards();
}
pbuf_free(p);
return ERR_OK;
}
4.4、ip报文重组
如下图所示,标志和分片偏移(ip_off)标记ip报文是否分片以及分片的偏移。

/* packet consists of multiple fragments? */
if ((IPH_OFFSET(iphdr) & htons(IP_OFFMASK | IP_MF)) != 0) { // 检查ip_off分片标记
#if IP_REASSEMBLY /* packet fragment reassembly code present? */
LWIP_DEBUGF(IP_DEBUG, ("IP packet is a fragment (id=0x%04"X16_F" tot_len=%"U16_F" len=%"U16_F" MF=%"U16_F" offset=%"U16_F"), calling ip_reass()\n",
ntohs(IPH_ID(iphdr)), p->tot_len, ntohs(IPH_LEN(iphdr)), !!(IPH_OFFSET(iphdr) & htons(IP_MF)), (ntohs(IPH_OFFSET(iphdr)) & IP_OFFMASK)*8));
/* reassemble the packet*/
p = ip_reass(p); // 重组ip报文(ip分片保存在reassdatagrams,所有分片完整后,返回重组后的ip报文,否则返回NULL)
/* packet not fully reassembled yet? */
if (p == NULL) { // p为NULL,表示分片重组没有完成,有待接收的分片
return ERR_OK; // 直接返回,等待其他分片
}
iphdr = p->payload; // iphdr指向重组后的ip报文
#else /* IP_REASSEMBLY == 0, no packet fragment reassembly code present */
pbuf_free(p);
LWIP_DEBUGF(IP_DEBUG | 2, ("IP packet dropped since it was fragmented (0x%"X16_F") (while IP_REASSEMBLY == 0).\n",
ntohs(IPH_OFFSET(iphdr))));
IP_STATS_INC(ip.opterr);
IP_STATS_INC(ip.drop);
/* unsupported protocol feature */
snmp_inc_ipinunknownprotos();
return ERR_OK;
#endif /* IP_REASSEMBLY */
}
5、ip报文传递给传输层
协议(ip_p)字段标记报文协议类型(tcp/udp...) , 字段所在位置如下图。

对ip检查及重组完成后,对于发送给本机的报文,获取协议类型,调用协议处理函数xxx_input(udp_input处理udp报文,tcp_input处理tcp报文)。
switch (IPH_PROTO(iphdr)) { // 根据ip_p判断协议类型
#if LWIP_UDP
case IP_PROTO_UDP:
#if LWIP_UDPLITE
case IP_PROTO_UDPLITE:
#endif /* LWIP_UDPLITE */
snmp_inc_ipindelivers();
udp_input(p, inp); // udp协议,调用udp_input处理udp报文
break;
#endif /* LWIP_UDP */
#if LWIP_TCP
case IP_PROTO_TCP:
snmp_inc_ipindelivers();
tcp_input(p, inp); // tcp协议,调用tcp_input处理tcp报文
break;
#endif /* LWIP_TCP */
#if LWIP_ICMP
case IP_PROTO_ICMP:
snmp_inc_ipindelivers();
icmp_input(p, inp);
break;
#endif /* LWIP_ICMP */
#if LWIP_IGMP
case IP_PROTO_IGMP:
igmp_input(p,inp,&(iphdr->dest));
break;
#endif /* LWIP_IGMP */
default:
#if LWIP_ICMP
/* send ICMP destination protocol unreachable unless is was a broadcast */
if (!ip_addr_isbroadcast(&(iphdr->dest), inp) &&
!ip_addr_ismulticast(&(iphdr->dest))) {
p->payload = iphdr;
icmp_dest_unreach(p, ICMP_DUR_PROTO);
}
#endif /* LWIP_ICMP */
pbuf_free(p);
LWIP_DEBUGF(IP_DEBUG | 2, ("Unsupported transport protocol %"U16_F"\n", IPH_PROTO(iphdr)));
IP_STATS_INC(ip.proterr);
IP_STATS_INC(ip.drop);
snmp_inc_ipinunknownprotos();
}
}
6、ip报文的输出(ip_output)
6.1、路由获取(ip_route)
ip_output的pbuf p为上一层协议的报文数据(tcp/udp...数据),dest为目的主机ip。lwip路由比较简单,通过网卡的子网掩码获取网卡的ip地址与目的主机的ip地址dest是否在一个子网,在一个子网那么返回该网卡(报文通过该网卡输出,目的主机与该网卡在一个子网里面),否则返回默认网卡(目的主机与本机不在一个子网里面)。
ip_output(struct pbuf *p, struct ip_addr *src, struct ip_addr *dest,
u8_t ttl, u8_t tos, u8_t proto)
{
struct netif *netif;
if ((netif = ip_route(dest)) == NULL) { // 目的主机路由获取(获取发送报文的网卡)
LWIP_DEBUGF(IP_DEBUG, ("ip_output: No route to 0x%"X32_F"\n", dest->addr));
IP_STATS_INC(ip.rterr);
return ERR_RTE; // 不在一个子网,并且没有默认网,那么找不到路由,直接丢弃报文
}
return ip_output_if(p, src, dest, ttl, tos, proto, netif); // 调用ip_output_if发送报文
}
6.2、ip报文发送(ip_output_if)
如下为tcp/ip报文的格式,要将tcp报文组装成ip报文,很明显只要加上ip首部即可。

6.2.1 ip首部填充
ip首部各字段填充。
/* generate IP header */
if (pbuf_header(p, IP_HLEN)) { // 在上层协议报文(tcp/udp...)数据前面申请一个ip头部的内存空间
LWIP_DEBUGF(IP_DEBUG | 2, ("ip_output: not enough room for IP header in pbuf\n"));
IP_STATS_INC(ip.err);
snmp_inc_ipoutdiscards();
return ERR_BUF;
}
iphdr = p->payload;
LWIP_ASSERT("check that first pbuf can hold struct ip_hdr",
(p->len >= sizeof(struct ip_hdr)));
IPH_TTL_SET(iphdr, ttl); // 设置ttl
IPH_PROTO_SET(iphdr, proto); // 设置协议
ip_addr_set(&(iphdr->dest), dest); // 设置目的ip地址
IPH_VHLTOS_SET(iphdr, 4, ip_hlen / 4, tos);
IPH_LEN_SET(iphdr, htons(p->tot_len));
IPH_OFFSET_SET(iphdr, 0);
IPH_ID_SET(iphdr, htons(ip_id));
++ip_id;
if (ip_addr_isany(src)) { // 0.0.0.0地址
ip_addr_set(&(iphdr->src), &(netif->ip_addr)); // 使用发送报文的网卡地址填充源ip地址
} else {
ip_addr_set(&(iphdr->src), src); // 使用上层协议传送的源ip地址填充
}
IPH_CHKSUM_SET(iphdr, 0);
#if CHECKSUM_GEN_IP
IPH_CHKSUM_SET(iphdr, inet_chksum(iphdr, ip_hlen));
#endif
} else {
/* IP header already included in p */
iphdr = p->payload;
dest = &(iphdr->dest);
ip分片。
#if IP_FRAG
/* don't fragment if interface has mtu set to 0 [loopif] */
if (netif->mtu && (p->tot_len > netif->mtu))
return ip_frag(p,netif,dest);
#endif
发送给自己的报文调用netif_loop_output发送给自己。
#if (LWIP_NETIF_LOOPBACK || LWIP_HAVE_LOOPIF)
if (ip_addr_cmp(dest, &netif->ip_addr)) {
/* Packet to self, enqueue it for loopback */
LWIP_DEBUGF(IP_DEBUG, ("netif_loop_output()"));
return netif_loop_output(netif, p, dest);
} else
#endif /* (LWIP_NETIF_LOOPBACK || LWIP_HAVE_LOOPIF) */
不是发送给自己的报文,调用etharp_output发送报文到链路层(netif->output)
{
LWIP_DEBUGF(IP_DEBUG, ("netif->output()"));
return netif->output(netif, p, dest);
}
7、链路层报文输出(etharp_output)
etharp_output与ip_output_if类似,在ip报文的头部申请一段链路层头部内存空间,对于非广播或者多播的目的地址,调用etharp_query查询目的ip地址的mac地址,如果查找到etharp_query将发送报文,否则加入待发送队列,发送arp请求获取目的ip地址的以太网地址。
7.1、网关地址获取
对于目的ip地址不在一个子网的报文,获取网卡的网关地址。
if (!ip_addr_netcmp(ipaddr, &(netif->ip_addr), &(netif->netmask))) { // 与发送网卡不在一个子网(需要经过网关转发)
/* interface has default gateway? */
if (netif->gw.addr != 0) {
/* send to hardware address of default gateway IP address */
ipaddr = &(netif->gw); // ipaddr指向网关地址
/* no default gateway available */
} else {
/* no route to destination error (default gateway missing) */
return ERR_RTE; // 没有设置网关地址,返回错误
}
}
7.2、链路层报文发送(etharp_query)
非广播或者多播报文需要获取目的ip地址或者网关的mac地址,调用etharp_query发送报文。
/* queue on destination Ethernet address belonging to ipaddr */
return etharp_query(netif, ipaddr, q); // 在一个子网的目的ip地址,ipaddr就是网络层发送过来的目的ip地址,不在一个子网的目的ip地址,ipaddr在上面代码中设置为网关地址gw;ipaddr仅用于查找mac地址使用,报文中的ip地址仍为报文的目的ip地址,mac地址用于网卡间通信;简单说,二层协议的交换机仅根据mac地址将报文发送到对应的网卡,报文发送到网络层时,对于一般主机,如果目的ip地址不是本机ip地址,则丢弃,对于路由器(网关),则根据路由规则进行转发
对于链路层输出具体参考“TCP/IP链路层ARP协议实现(lwip)“ TCP/IP链路层ARP协议实现(lwip)_lwip实现arp-CSDN博客;
当获取到目的ip地址或者网关的mac地址时,调用"etharp_send_ip(struct netif *netif, struct pbuf *p, struct eth_addr *src, struct eth_addr *dst)"填充以太网头部发送报文到网卡。
8、总结
网络层的输入主要校验报文、检查目的ip地址是否是发送给本机的,然后发送给传输层处理;网络层的输出主要对传输层报文添加ip首部信息、获取目的ip地址的路由(输出网卡/网关)、发送数据到链路层,由链路层实现网卡间的数据发送。
本文详细介绍了lwip网络层处理IP协议的过程,从以太网帧输入开始,经过tcpip_input函数将数据发送到tcp/ip线程,接着在tcp/ip线程中,ethernet_input剥离以太网帧头并根据IP类型调用ip_input进行报文预处理。ip_input校验IP报文,检查目的IP地址,并转发或交给传输层处理。最后,ip_output和etharp_output处理IP报文的输出,包括填充IP头部、路由选择和链路层输出。整个过程涉及IP报文的校验、重组、转发和传输层协议处理。
4543

被折叠的 条评论
为什么被折叠?



