DIY TCP/IP ARP模块2

上一篇:DIY TCP/IP ARP模块1添加链接描述
7.3 发送ARP Reply
本节实现ARP Reply数据帧的发送,当接收到ARP Request数据帧的目的IP地址与DIY TCP/IP的虚拟IP地址相等时,通过第六章实现的pdbuf的接口函数,构建ARP Reply数据帧。修改网络设备模块dev_process_txpkt的实现,通过PF_PACKET类型的socket回复ARP Reply数据帧到发出ARP Request数据帧的主机,从而在发送ARP Request的主机上建立虚拟IP地址和运行DIY TCP/IP的主机的物理地址的映射。第五章实现的网络设备模块的发送队列和第六章实现的pdbuf模块的功能都会在本节得到验证。
7.2节实现了ARP Request数据帧的解析,打印出 is at 字样,首先把打印部分替换成构建ARP Reply数据帧的实现。
修改process_arp_request函数

  static int process_arp_request(arphdr_t *pkt)
  {
      int ret = 0;
      unsigned char *hwaddr = NULL;
      unsigned char *ipaddr = NULL;
      pdbuf_t *pdbuf = NULL;
      arphdr_t *reply = NULL;
  
      log_printf(VERBOSE, "Who has "IPSTR"? TELL "IPSTR"\n",
          IP2STR(pkt->target_ip), IP2STR(pkt->sender_ip));
      if ((ipaddr = netdev_ipaddr()) == NULL) {
          log_printf(ERROR, "Failed to get local ip address\n");
          ret = -1;
          goto out;
      }
      if ((hwaddr = netdev_hwaddr()) == NULL) {
          log_printf(ERROR, "Failed to get net device hw address\n");
          ret = -1;
          goto out;
      }
      if (memcmp(pkt->target_ip, ipaddr, ARP_PROTO_SZ))
          goto out;
      /* build ARP reply */
      pdbuf = pdbuf_alloc(ARP_PADDING_SZ, 0);
      if (pdbuf == NULL) {
          ret = -1;
          goto out;
      }
      pdbuf_push(pdbuf, sizeof(arphdr_t) + ARP_PADDING_SZ);
      reply = (arphdr_t *)pdbuf->payload;
      memcpy(reply, pkt, sizeof(arphdr_t));
      reply->op_code = HSTON(ARP_REPLY);
      memcpy(reply->sender_mac, hwaddr, ARP_HW_SZ);
      memcpy(reply->sender_ip, ipaddr, ARP_PROTO_SZ);
      memcpy(reply->target_mac, pkt->sender_mac, ARP_HW_SZ);
      memcpy(reply->target_ip, pkt->sender_ip, ARP_PROTO_SZ);
      build_ethernet_header(pdbuf, ETHERNET_ARP,
              reply->sender_ip, reply->target_ip,
              reply->sender_mac, reply->target_mac);
      netdev_tx_pkt(pdbuf);
  out:
      return ret;
  }

Line 1-8: 添加局部变量reply和pdbuf指针,reply指向构建的ARP Reply数据帧。ARP Reply的数据结构和ARP Request一致,在7.1.1节介绍过。pdbuf数据结构也在6.1节介绍过。
Line 9-22: 与7.2节的实现一样,获取本地IP地址和硬件地址后,判断ARP Request数据帧中请求中的目标IP地址和DIY TCP/IP的虚拟IP地址是否相等。若不相等,process_arp_request直接返回,若相等则构造ARP Reply数据帧,再将ARP Reply放入网络设备模块的发送队列中。
Line 23-28: 调用pdbuf_alloc申请ARP_PADDING_SZ个字节的内存空间,ARP_PADDNG_SZ为18,全部填0填充在ARP数据帧的末尾。18字节的填充与以太网的最小帧长度有关。以太网的最大帧长度为1518。以太网的最大协议传输单元为1500字节。18包括14个字节的以太网头部和4个字节的以太网校验值。以太网的最小帧长度为64,这已经是以太网的历史,传统以太网的拓扑结构是总线拓扑结构,多个节点共享一条物理媒介。为检测碰撞,IEEE规定同一个碰撞域中相隔最远距离的两个节点监测碰撞的最小帧长度为64字节。现在的以太网已经是星型结构,不同节点不再共享物理媒介。此处仍按照64字节的以太网最小帧长度填充ARP数据帧。pdbuf_alloc已经在6.3节中详细介绍过,第一个参数是18,第二个参数是0,代表分配内存空间最大为1500字节,pdbuf_alloc除了分配18个字节的内存空间之外,还会额外分配能够存放layer 3,layer 4最大协议头部数据的内存空间,以及以太网头部的内存空间。
Line 29-31: pdbuf_push调整pdbuf->payload指针向地址减小的方向移动,此时pdbuf->payload到pdbuf->end可以存放ARP数据帧和18个字节的填充。reply指向pdbuf->payload,将收到的ARP Request数据帧拷贝到reply指向的内存空间。由于ARP Reply数据帧的结构和ARP Request数据帧结构一致,先拷贝ARP Reqeuest的内容,再做修改即可。
Line 32-36: 修改op code为2,与7.1.1节介绍的ARP Reply数据帧op code的数值一致。源硬件地址为运行DIY TCP/IP主机的网络接口的硬件地址,源IP地址为DIY TCP/IP的虚拟IP地址。目标硬件地址为ARP Request数据帧的源硬件地址,目标IP地址为ARP Request数据帧的源IP地址。
Line 37-43:ARP Reply数据帧的内容已经填充完毕。DIY TCP/IP的网络设备模块是通过PF_PACKET类型的socket发送数据,用户空间的数据通过该种类型的socket发送时,数据不经过Linux kernel的TCP/IP协议栈,Linux kernel也不对用户空间的数据做任何修改,直接发送到Linux kernel的网络设备驱动中。所以还需为ARP Reply添加以太网头部,调用build_ethernet_header填充以太网头部的源,目标硬件地址,以及以太网头部协议类型,再调用netdev_tx_pkt将数据帧发送到DIY TCP/IP的网络设备模块。
build_ethernet_header是本节的新增函数,也是ARP模块暴露给其他模块使用的接口函数,用于构建以太网头部,该函数的实现在arp.c中。

  static int build_ethernet_header(void *buf, unsigned short type,
              unsigned char *src_ip, unsigned char *dst_ip,
              unsigned char *src_mac, unsigned char *dst_mac)
  {
      int ret = 0;
      pdbuf_t *pdbuf = NULL;
      ethhdr_t *ethhdr = NULL;
  
      if (buf == NULL || (dst_ip == NULL && dst_mac == NULL )) {
          log_printf(ERROR, "Invalid parameters to build ethernet header\n");
          ret = -1;
          goto out;
      }
  
      if (src_mac == NULL)
          src_mac = netdev_hwaddr();
      if (src_mac && dst_mac)
          goto build_hdr;
      else
          goto out;
      // lookup arp table by dst_ip
  build_hdr:
      pdbuf = buf;
      pdbuf_push(pdbuf, sizeof(ethhdr_t));
      ethhdr = (ethhdr_t *)pdbuf->payload;
      memcpy(ethhdr->dst, dst_mac, sizeof(ethhdr->dst));
      memcpy(ethhdr->src, src_mac, sizeof(ethhdr->src));
      ethhdr->type = HSTON(type);
      log_printf(VERBOSE, "eth hdr, dst: "MACSTR" src: "MACSTR" type: %04x\n",
          MAC2STR(ethhdr->dst), MAC2STR(ethhdr->src), NTOHS(ethhdr->type));
  out:
      return ret;
  }


build_ethnet_header的入参较多,buf指向pdbuf,type是以太网头部类型,src_ip和dst_ip为源IP地址和目标IP地址。src_mac和dst_mac为源硬件地址和目标硬件地址。build_ethernet_header是ARP模块暴露给其他模块使用的接口,其实主要是IP模块,layer 4的数据帧构造完成之后都要加上layer 3的IP头部。IP数据帧构造完成之后,调用build_ethernet_header构造以太网头部,再通过网络设备模块发送。
Line 5-14: 定义pdbuf和ethhdr指针,调用build_ethernet_header时,至少要提供目标硬件地址或目标IP地址。例如ARP Reply数据帧填充完成后,将ARP Request的源硬件地址做为目标硬件地址传入build_ethernet_header,构造以太网头部。IP模块调用时,由于硬件地址对于IP模块和IP模块之上的协议模块是透明的,所以只能传入目标IP地址,通过查找ARP表,得到与IP地址对应的目标硬件地址。如果dst_ip和dst_mac都为空时,出错返回。
Line 15-21: 如果源硬件地址为空,默认源硬件地址为运行DIY TCP/IP主机的网络接口的硬件地址。DIY TCP/IP现阶段的实现只是用于局域网中的非路由器设备,所以源硬件地址也只能pcap设备指定的网络接口的硬件地址。如果源硬件地址和目标硬件地址都已经具备,则跳转到build_hdr构造以太网头部。否则出错返回,这里预留了一行注释,下节扩展实现查找ARP表。
Line 22-33: 将入参buf强制转换为pdbuf类型,此时的pdbuf->payload指向ARP Reply数据帧的首字节地址,调用pdbuf_push调整pdbuf->payload向地址减小的方向移动sizeof(ethhdr_t)个字节,也就是在ARP Reply数据帧的前面添加以太网头部数据。拷贝目标硬件地址和源硬件地址到以太网头部数据结构中,再将以太网头部类型0x0806转换为大端格式,完成以太网头部的构造。
build_ethnet_header完成之后,pdbuf的结构如下图所示pdbuf->payload指向以太网头部,以太网头部后面是ARP数据帧,再后面是18个字节的填充数据。由于分配pdbuf时,预留的协议头部的空间是layer 3和layer 4协议头部的最大空间,所以pdbuf->payload指向的内存地址,应比pdbuf->buf地址大。
DIY TCP/IP PDBUF ARP Reply
完成以太网头部的构造之后,就可以调用netdev_tx_pkt将pdbuf放入DIY TCP/IP网络设备模块的发送对列。网络设备模块唤醒发送进程,通过PF_PACKET类型的socket将数据帧发送到Linux kernel的网络设备的驱动中。接下来介绍网络设备模块对pdbuf的处理。
netdev_tx_pkt将pdbuf放入DIY TCP/IP网络设备模块的发送队列中,唤醒发送进程处理发送队列中的pdbuf。5.4.2节dev_process_txpkt函数仅仅是取出发送队列中的数据帧,打印提示信息,然后释放数据帧的内存空间,本节对dev_process_txpkt做如下修改:

 static void dev_process_txpkt(dev_txpkt_t *txpkt)
 {
     pdbuf_t *pdbuf = NULL;
     net_device_t *ndev = NULL;
     unsigned int pkt_len = 0;
     struct sockaddr_ll sk_addr;
     ethhdr_t *ethhdr = NULL;
 
     if (txpkt == NULL || txpkt->pdbuf == NULL)
         return;
     pdbuf = (pdbuf_t *)txpkt->pdbuf;
     ndev = netdev_get();
     if (ndev == NULL ||
         ndev->tx_sock < 0 ||
         ndev->ifindex < 0) {
         log_printf(ERROR, "Invalid net device parameters\n");
         goto out;
     }
     pkt_len = (unsigned int)(pdbuf->end - pdbuf->payload);
 
     ethhdr = (ethhdr_t *)pdbuf->payload;
     memset(&sk_addr, 0, sizeof(sk_addr));
     sk_addr.sll_ifindex = ndev->ifindex;
     sk_addr.sll_halen = ETH_ALEN;
     memcpy(sk_addr.sll_addr, ethhdr->dst, ETH_ALEN);
 
     log_printf(INFO, "dev tx, ethernet type: %04x, "MACSTR" --> "MACSTR"\n",
         NTOHS(ethhdr->type), MAC2STR(ethhdr->src), MAC2STR(ethhdr->dst));
     dump_buf(pdbuf->payload, pkt_len);
     if ((sendto(ndev->tx_sock, pdbuf->payload, pkt_len,
             0, (struct sockaddr*)&sk_addr, sizeof(sk_addr))) < 0)
         log_printf(ERROR, "sendto error: %s (%d)\n", strerror(errno), errno);
 out:
     if (pdbuf)
         pdbuf_free(pdbuf);
     free(txpkt);
 }


回顾5.3.3节网络设备模块数据帧结构dev_txpkt_t的定义,netdev_tx_pkt将pdbuf封装在dev_txpkt_t数据结构中,通过dev_txpkt_t的queue_t成员放入发送队列。发送线程从发送队列中取出队列节点,通过container_of宏获得dev_txpkt_t的指针,传给dev_process_txpkt处理。
Line 3-8: 定义pdbuf指针,ndev指针,无符号整型pkt_len,ethhdr指针和sockaddr_ll。sockaddr_ll的数据结构定义在<linux/if_packet.h>头文件中,在GCC默认的头文件查找路径/user/include下,可以找到该头文件。使用PF_PACKET类型的socket发送数据时,sockaddr_ll指定网络接口,数据帧将被发送到Linux kernel中与指定网络接口对应的设备驱动。

 struct sockaddr_ll {
         unsigned short  sll_family;
         __be16          sll_protocol;
         int             sll_ifindex;
         unsigned short  sll_hatype;
         unsigned char   sll_pkttype;
         unsigned char   sll_halen;
         unsigned char   sll_addr[8];
 };

sll_family通常是PF_PACKET, sll_protocol指定以太网协议类型,sll_ifindex指定网络接口,sll_hatype指定硬件地址类型,sll_pkttype指定数据帧类型:广播,组播类型等。sll_halen指定硬件地址长度,ssl_addr存放硬件地址。
Line 9-18: 判断txpkt和txpkt->pdbuf是否为空,不为空时继续执行。netdev_get获取网络设备模块的静态指针gndev,在gndev不空的前提下,分别判断tx_sock套接字,ifindex网络接口索引是否合法,不合法时直接释放txpkt->pdbuf和txpkt占用的内存。
Line 19-21: 发送的数据帧的长度pkt_len,以ARP Reply为例,该长度包括18个0字节填充加上ARP Reply数据帧的长度,再加上以太网头部的长度。回顾pdbuf的分配和初始化,pdbuf->end指向为pdbuf分配的内存空间的末尾字节的下一个字节地址,pdbuf->payload初始值与pdbuf->end相等。在构建ARP Reply数据帧时反复调用pdbuf_push调整pdbuf->payload向地址减小的方向移动,所以通过pdbuf->end - pdbuf->payload计算得到需要发送的字节总数。此时pdbuf->payload指向以太网头部的第一个字节的地址,不仅仅是ARP Reply数据帧,DIY TCP/IP的上层模块通过pdbuf构建需要发送的数据帧,到达网络设备模块的发送队列中,被发送线程调度发送时,pdbuf->payload均应指向以太网头部的第一个字节地址。将pdbuf->payload强制转换为ethhdr_t指针类型赋值给ethhdr,获取以太网头部中的目标硬件地址。
Line 22-29: 初始化sock_addr,回顾5.4.1节创建tx_sock套接字时已经指定了套接字的domain为PF_PACKET,protocol为ETH_P_ALL。此处只需要指定ss_ifindex,sll_halen和sll_addr即可。网络接口索引ifndex通过ndev的ifindex成员获得,ifindex是网络设备结构体net_device_t的一个新增成员,在网络设备初始化时根据网络接口的名称获取其索引。ETH_ALEN是6,表示以太网硬件地址的长度,ETH_ALEN定义在<linux/if_ether.h>头文件中。复制目的硬件地址ethhdr->dst到sock_addr->sll_addr中,完成sock_addr的初始化。在调用sendto发送数据帧之前,通过dump_buf查看构造的数据帧是否正确,dump_buf是utils.c文件中的新增函数,以16进制形式打印指定长度的字节数组。完成dev_process_txpkt的介绍之后,再来介绍网络设备模块ifindex的相关修改,和utils.c中的相关修改。
Line 30-37: 调用sendto将数据帧发送到Linux kernel中与指定网络接口对应的设备驱动中,进而发送到对方主机。sendto是linux kernel提供给用户空间的系统调用,第一个参数是发送套接字,第二个参数是要发送的数据帧的内存地址,第三个参数是数据帧的长度,第四个参数是flags,可以指定flags为MSG_DONTWAIT以非阻塞方式发送,0代表sendto以阻塞方式发送,最后两个参数是描述地址信息的sock_addr和sock_addr的长度。感兴趣的朋友可以通过Linux man page 2查看sendto函数更详细的使用信息。
sendto系统调用的实现在linux kernel的net/socket.c文件中,由sll_ifindex找到Linux kernel中对应的net_device,调用PF_PACKET 的sendmsg函数,通过dev_hard_start_xmit将skb传给指定网络接口的设备驱动。sendto执行成功,返回发送的字节数目,失败时返回-1,通过errno全局变量查看失败原因。最后调用pdbuf_free释放txpkt->pdbuf占用的内存空间,再调用free释放txpkt的内存空间。
7.2节在DIY TCP/IP的网络设备结构体中添加了ip_addr和hw_addr成员,存放网络接口对应的IP地址和硬件地址,本节再向网络设备结构体中添加一个新的成员ifindex,存放网络接口的索引。

 typedef struct _net_device {
  pcap_t *pcap_dev;
  /* rx */
  pthread_t rx_thread;
  pthread_cond_t rxq_cond;
  pthread_mutex_t rxq_mutex;
  queue_t rxpkt_q;
  /* tx */
  int tx_sock;
  pthread_t tx_thread;
  pthread_cond_t txq_cond;
  pthread_mutex_t txq_mutex;
  queue_t txpkt_q;
  /* dev info */
  int ifindex;
  /* ip addr */
  unsigned char ip[4];
  /* hw addr */
  unsigned char hwaddr[6];
 } net_device_t;

Line 15: 整型值ifindex的初始化在netdev_init函数中,调用utils.c文件中的新增函数get_ifindex获得,该函数在获取硬件地址之后被调用,netdev_init函数修改如下。

 net_device_t * netdev_init(char *ifname)
 {
…
     /* hw addr init */
     if (get_hw_addr(ifname, ndev->hwaddr, sizeof(ndev->hwaddr)))
         goto out;
     /* get interface index */
     if ((ndev->ifindex = get_ifindex(ifname)) < 0)
         goto out;
 
 
     return ndev;
 out:
     netdev_deinit(ndev);
     return NULL;
 }

再来看utils.c中get_ifindex函数的实现,与7.2节中获取硬件地址的函数get_hw_addr一致,都是通过ioctl系统调用请求网络设备的参数,命令字为SIOCGIFINDEX。7.2节已经详细介绍了get_hw_addr的实现,get_ifindex仅仅是命令字不同,代码如下:

 int get_ifindex(char *ifname)
 {
         int fd = 0;
         int ifindex = -1;
         struct ifreq ifr;
         unsigned int ifname_sz;
 
         if (ifname == NULL)
                 return ifindex;
 
         if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
                 printf("Failed to create socket fd: %s (%d)\n",
                                 strerror(errno), errno);
                 goto out;
         }
         memset(&ifr, 0, sizeof(ifr));
         strncpy(ifr.ifr_name, ifname, MIN(strlen(ifname) + 1, sizeof(ifr.ifr_name)));
         if (ioctl(fd, SIOCGIFINDEX, &ifr) < 0) {
                 printf("Failed to get interface: %s index %s (%d)\n",
                                 ifr.ifr_name, strerror(errno), errno);
                 goto out;
         }
         ifindex = ifr.ifr_ifindex;
 out:
         if (fd)
                 close(fd);
         return ifindex;
 }


再来看dev_process_txpkt中用到的最后一个新增函数dump_buf,16进制打印字节数组。该函数主要用于debug,查看pdbuf构造的数据帧是否符合预期,dump_buf的实现也是在utils.c中。

 void dump_buf(void *buf, unsigned int size)
 {
 #define BUF_SIZE 48 /* 16 * 2 + 15 + 1 */
         int i;
         unsigned char *p = NULL;
         char hex_buf[BUF_SIZE];
         unsigned int offset = 0;
 
         if (buf == NULL || size == 0)
                 return;
         p = buf;
         memset(hex_buf, 0, BUF_SIZE);
         for (i = 0; i < size; i ++) {
                 offset = strlen(hex_buf);
                 if ( i && ((i + 1) % 16 == 0)) {
                         snprintf((hex_buf + offset), (BUF_SIZE - offset), "%02x", *p ++);
                         printf("%s\n", hex_buf);
                         memset(hex_buf, 0, BUF_SIZE);
                 } else {
                         snprintf((hex_buf + offset), (BUF_SIZE - offset), "%02x ", *p ++);
                 }
         }
         printf("%s\n", hex_buf);
 }

dump_buf的入参为void buf字节数组,size为字节数组对应的大小。dump_buf以16进制形式打印输出字节数组,将字节数组的每个字节转换为ascii码,以空格为分隔符,每输出16个数字换行。
Line 3-12: 定义宏BUF_SIZE,每行打印16个16进制数值。每个字节对应的16进制数值,转换为ASCII码后占2个字节。存放一行16进制数的ASCII码需要16
2个字节,每个16进制数值以空格分隔,共15个空格。最后一个1是整个字符串结尾的’\0’字符。hex_ buf字符数组的大小为BUF_SIZE,存放转换结果,指针p和offset是运算中用到的中间变量。对buf和size做合法性检查后,将hex_buf初始化为全0,p指向buf的第一个字节。
Line 13-24: 遍历buf字节数组,offset为hex_buf数组中存放下一个转换结果的下标值。通过snprintf将每个字节打印为宽度为2的16进制数,存放在hex_buf数组中,i+1取余16为0时,代表一行的最后一个16进制数,直接打印输出hex_buf。否则在通过snprintf打印16进制数字时后面还要跟一个空格做为分隔符。循环结束时再将hex_buf打印输出,确保buf字节数组的长度不是16的整数倍时,所有字节均被转换打印输出。
到这里ARP Reply数据帧的构造,以太网头部的构造,网络设备模块的发送处理都已经介绍完成,来运行查看结果,如果ARP Reply发送成功,则在发送ARP Request的主机上的ARP表中将能够查到DIY TCP/IP的虚拟IP地址和运行DIY TCP/IP主机的网络接口硬件地址的映射。
DIY TCP/IP DevB ARP Table
便于描述将运行DIY TCP/IP的主机记为主机A,与主机A处于同一局域网的另外一台主机记为主机B。主机B为本人的另外一台win7笔记本电脑,在主机B上打开命令行终端,输入arp –a查看ARP表,主机B的网络接口IP地址为192.168.0.107。ARP表的第一列是IP地址,第二列是硬件地址,第三列表示ARP表中的表项是动态生成的,或者是静态设置的。动态表示该IP地址到硬件地址的映射是主机B通过发送ARP Request请求,收到对方回复的ARP Reply后建立的表项,静态表示该表项是win7系统静态设置的映射表项。192.168.0.1是局域网中的网关,192.168.0.103是局域网中的一台手机设备。主机B所在的局域网是192.168.0/24,192.168.0.107对应的网络接口信息如下:
DIY TCP/IP DevB IPCONFIG
主机A的enss0的网络接口信息如下:
DIY TCP/IP DevA ifconfig
DIY TCP/IP从通过PF_PACKET类型的socket从链路层接收数据帧,和向Linux kernel的网路设备发送数据帧,均指定ens33为网络接口。
DIY TCP/IP ARP Ping Test
本人通过查看路由器分配的IP地址得知192.168.0.7是局域网中尚未分配的IP地址,在主机B上Ping该IP地址,-n表示只发送一个ICMP echo数据帧,从Ping的结果可以验证,192.168.0.7是局域网中不存在的IP地址,接下来将该IP地址指定为DIY TCP/IP的虚拟IP地址,再通过主机B ping 192.168.0.7,运行结果如下:

 gannicus@ubuntu:~/guojia/tasks/DIY_USER_SPACE_TCPIP/ch4/2$ sudo ./tcp_ip_stack -i 192.168.0.7
 [sudo] password for gannicus: 
 Network device init
 filter: ether proto 0x0800 or ether proto 0x0806
 Network device RX init
 Network device TX init
 Net device ip address: 192.168.0.7
 dev tx, ethernet type: 0806, 00:0c:29:2e:0a:ed --> 8c:a9:82:11:d1:de
 8c a9 82 11 d1 de 00 0c 29 2e 0a ed 08 06 00 01
 08 00 06 04 00 02 00 0c 29 2e 0a ed c0 a8 00 07
 8c a9 82 11 d1 de c0 a8 00 6b 00 00 00 00 00 00
 00 00 00 00 00 00 00 00 00 00 00 00 
 ^X^Cpcap_loop ended
 Network device deinit
 Network device RX deinit
 Dev rx routine exited
 Dev rxq flushed 0 packets
 Network device TX deinit
 Dev tx routine exited
 Dev txq flushed 0 packets
 
 #Internal Buffer Management#
 Alloc: 1, Free: 1

从运行结果可以看出,DIY TCP/IP的网络设备模块收到了ARP Request数据帧,网络设备模块将ARP Request数据帧交给ARP模块。经7.2节的ARP Request的处理,判断出请求的192.168.0.7的IP地址与DIY TCP/IP的虚拟IP地址相等,构建ARP Reply数据帧,并将ARP Reply交给DIY TCP/IP的网络设备模块处理。网络设备模块的发送线程调用dev_process_txpkt处理该ARP Reply数据帧,在调用sendto发送之前,运行结果高亮显示的部分就是dump_buf打印出的ARP Reply数据帧的内容。来分析一下ARP Reply数据帧是否与pdbuf构建的数据帧相符。首先来看数据帧的长度为16*3 + 12,即ethnert header (14B) + ARP Reply (28 B) + padding (18 B),一共是50B,与dump_buf打印输出的长度相等。再来数据帧的内容,8c a9 82 11 d1 de是主机B的192.168.0.107的网络接口对应的硬件地址,00 0c 29 2d 0a ed是运行DIY TCP/IP的主机A的enss0网络接口的硬件地址,接下来的两个字节是以太网头部的类型字段,08 06代表是ARP协议。再接下来的28个字节是ARP Reply数据帧,00 01是Hardware type代表ethernet,08 00是protocol type代表IPv4,06 04分别是硬件地址长度和协议地址长度,00 02是ARP数据帧的opcode代表该数据帧是ARP Reply,再向后是ARP Reply数据帧中发送者的硬件地址00 0c 29 2d 0a ed,发送者的IP地址c0 a8 00 07(192.168.0.7),目的硬件地址8c a9 82 11 d1 de,目的IP地址c0 a8 00 6b(192.168.0.107),后面18个0是填充字节。完全符合ARP Reply数据帧构造的代码实现。该运行结果验证了pdbuf模块的函数接口和网络设备层发送队列的实现是符合预期的。
在这里插入图片描述
在主机B上再次查看ARP表,发现192.168.0.7对应的硬件地址是00 0c 29 0e 0a ed,主机B上已经能够建立DIY TCP/IP的虚拟IP地址与主机A的网络接口ens33的硬件地址的映射,从而验证了DIY TCP/IP的网络设备模块的发送,pdbuf模块的函数实现,都是符合预期的。
下一篇:DIY TCP/IP ARP模块3

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值