当前位置: 首页 > web >正文

TCP/IP网络层ip协议实现(lwip)

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_NETCONNcase 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_ARPif (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_UDPcase IP_PROTO_UDP:
#if LWIP_UDPLITEcase IP_PROTO_UDPLITE:
#endif /* LWIP_UDPLITE */snmp_inc_ipindelivers();udp_input(p, inp); // udp协议,调用udp_input处理udp报文break;
#endif /* LWIP_UDP */
#if LWIP_TCPcase IP_PROTO_TCP:snmp_inc_ipindelivers();tcp_input(p, inp); // tcp协议,调用tcp_input处理tcp报文break;
#endif /* LWIP_TCP */
#if LWIP_ICMPcase IP_PROTO_ICMP:snmp_inc_ipindelivers();icmp_input(p, inp);break;
#endif /* LWIP_ICMP */
#if LWIP_IGMPcase 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); // 设置ttlIPH_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_IPIPH_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地址的路由(输出网卡/网关)、发送数据到链路层,由链路层实现网卡间的数据发送。

http://www.xdnf.cn/news/11381.html

相关文章:

  • C# 常用的正则表达式
  • 深入了解:Java中BigDecimal比较大小的方法_bigdecimal compareto
  • 红客联盟是什么?红客需要传承!
  • 工作经验总结之C语言关键字的作用、特性和使用方式
  • OCCT基础
  • 高性能内存对象缓存
  • 从零开始学C--4初识操作符
  • 什么是L298N
  • unbuntu 命令汇总
  • FastMCP vs MCP:协议标准与实现框架的协同
  • virtualbox 下载安装
  • 关于 TRTC (实时音视频通话模式)在我司的实践
  • Java——位图
  • AC/DC、DC/DC转换器基础指南(一)
  • html点击按钮出现下拉框
  • 信息学奥赛一本通 1306:最长公共子上升序列 | OpenJudge NOI 2.6 2000:最长公共子上升序列
  • 8-Docker网络命令之disconnect
  • X11流程解读
  • Android ANR 实现机制详解
  • 信息安全基础:Host与HSM通信科普
  • Java 正则详解
  • FontAwesome.Sharp 使用教程
  • java——Zookeeper学习——zk概览转载
  • marquee标签弃用的替代(文字循环滚动--头部广告)
  • Autosar E2E及其实现(基于E2E_P01)
  • SHAP: 在我眼里,没有黑箱
  • fullcalendar的使用
  • Sphinx中文入门指南
  • bzoj 3876 [Ahoi2014]支线剧情
  • Loader引导加载程序