天天看點

DIY TCP/IP ARP子產品3

上一篇:DIY TCP/IP ARP子產品2

7.4 ARP表的實作

先來概括介紹下ARP表,和ARP表的查找過程。ARP表存放區域網路中IP位址和硬體位址的映射,構造以太網頭部時需要根據目标IP位址查找ARP表擷取目标硬體位址。如果目标IP位址與網絡接口的IP位址在同一個區域網路中,則查找ARP表,擷取對應的目标硬體位址,如果查找失敗則發送ARP Request,擷取目标IP位址對應的硬體位址。收到ARP Reply後更新ARP表項。如果目标IP位址與網絡接口的IP位址不在同一個區域網路中,則查找路由表,得到下一跳網絡接口IP位址,再查找下一跳的IP位址對應的硬體位址,構造相應的以太網頭部。

DIY TCP/IP的ARP表的實作相對簡化,由于DIY TCP/IP隻運作在區域網路中,是以暫不實作目标IP位址與網絡接口IP位址不在同一網段的處理。另外ARP表的更新,不是通過先發送ARP Request,在得到ARP Reply之後,更新ARP表。而是根據ARP Requset更新ARP表,當DIY TCP/IP的ARP子產品收到ARP Request資料幀後,判斷請求的IP位址和虛拟IP位址相等時,就将ARP Request資料幀中的源IP位址和源硬體位址做為ARP表項,更新到ARP表中,簡化查表失敗時先發送ARP Reuqest再根據ARP Reply更新ARP表的過程。

本節的目标是改寫7.1.3節中的build_ethernet_header函數,該函數暴露給其他子產品使用,構造以太網頭部。7.1.3節是在已知源硬體位址和目标硬體位址的情況下調用該函數,是以沒有涉及ARP表的查找操作。圍繞該函數的改寫,引入ARP表項資料結構的定義,以及在該資料結構的基礎上實作的ARP表,表項的新增,删除和查找操作。

先在arp.c中增加ARP表項資料結構的定義

typedef struct _arp_entry {
      unsigned char ip[ARP_PROTO_SZ];
      unsigned char mac[ARP_HW_SZ];
  } arp_entry_t;
  
  static arp_entry_t *arp_table = NULL;
  static unsigned int num_arp_entry = 0;
  static arp_entry_t null_entry = {
      .ip = {0, 0, 0, 0},
      .mac = {0, 0, 0, 0, 0, 0},
  };

           

Line 1-4: arp_entry_t結構體包括兩個固定長度的unsigned char數組,ip長度為4個位元組,mac長度為6個位元組,分别用于存放ARP表項的IP位址和對應的硬體位址。

Line 6-7: arp_entry_t類型的指針,arp_table指向的記憶體存放ARP表,num_arp_entry是ARP表項數目。

Line 8-11: null_entry是arp_entry_t類型,ip和mac均初始化為0,用于比對ARP表中的空閑表項。

圍繞arp_entry_t資料結構的定義,實作ARP表的初始化,銷毀,表項的新增和删除函數。

static int init_arp_table()
  {
      int ret = 0;
      if (arp_table != NULL)
          goto out;
      arp_table = (arp_entry_t *)malloc(sizeof(arp_entry_t));
      if (arp_table == NULL) {
          log_printf(ERROR, "No memory for arp table, %s (%d\n",
                  strerror(errno), errno);
          ret = -1;
          goto out;
      }
      memset(arp_table, 0, sizeof(arp_entry_t));
      num_arp_entry = 1;
  out:
      return ret;
  }
  
  static void deinit_arp_table()
  {
      if (arp_table == NULL)
          return;
      log_printf(INFO, "Destroy ARP table, %u %s\n",
          num_arp_entry, num_arp_entry > 1 ? "entries" : "entry");
      free(arp_table);
  }

           

Line 1-17: init_arp_table函數初始化ARP表, arp_table指針為空時,通過malloc申請能夠存放一個ARP表項的記憶體空間,arp_table指向該記憶體空間的首位址。malloc執行失敗時,列印出錯資訊,結束執行。malloc成功時,初始化arp_table指向的記憶體空間為全0,num_arp_entry為1。

Line 19-26: deinit_arp_table,銷毀ARP表,列印ARP表中的表項數目,然後調用free釋放arp_table占用的記憶體空間。

init_arp_table和deinit_arp_table是ARP子產品的靜态函數,分别在ARP子產品的初始化函數arp_init和銷毀函數arp_deinit中被調用。

static void dump_arp_table()
  {
      unsigned int i = 0;
      log_printf(INFO, "ARP Table\n");
      log_printf(INFO, "IP Address\t\tMAC Address\n");
      for (i = 0; i < num_arp_entry; i ++) {
          if (memcmp(&arp_table[i], &null_entry, sizeof(arp_entry_t)) == 0)
              continue;
          log_printf(INFO, IPSTR"\t\t"MACSTR"\n",
                  IP2STR(arp_table[i].ip),
                  MAC2STR(arp_table[i].mac));
      }
  }

           

Line 1-13: dump_arp_table周遊ARP表,列印ARP表項的内容,跳過内容為全0的null_entry表項。

static int add_arp_entry(unsigned char *ip, unsigned char *mac)
  {
      int ret = 0;
      unsigned int i = 0;
      arp_entry_t tmp_entry;
  
      memcpy(tmp_entry.ip, ip, ARP_PROTO_SZ);
      memcpy(tmp_entry.mac, mac, ARP_HW_SZ);
      for (i = 0; i < num_arp_entry; i ++) {
          if (memcmp(&tmp_entry, &arp_table[i], sizeof(arp_entry_t)) == 0) {
              log_printf(INFO, "ARP entry: "IPSTR" "MACSTR" already exists\n",
                  IP2STR(ip), MAC2STR(mac));
              goto out;
          }
      }
      /* look for null entry */
      for (i = 0; i < num_arp_entry; i ++)
          if (memcmp(&arp_table[i], &null_entry, sizeof(arp_entry_t)) == 0)
              break;
      /* extend arp table */
      if (i >= num_arp_entry) {
          arp_table = realloc(arp_table, (num_arp_entry + 1) * sizeof(arp_entry_t));
          if (arp_table == NULL) {
              log_printf(ERROR, "No memory for new arp entry, %s (%d)\n",
                  strerror(errno), errno);
              ret = -1;
              goto out;
          }
          memset(&arp_table[i], 0, sizeof(arp_entry_t));
          num_arp_entry += 1;
      }
      memcpy(&arp_tabe[i], &tmp_entry, sizeof(arp_entry_t));
  out:
      return ret;
  }

           

add_arp_entry新增ARP表項,入參為IP位址和硬體位址的指針。定義局部變量tmp_entry,通過入參ip和mac初始化tmp_entry,這樣在查找ARP表做比較時可以用ARP表項為機關比較。周遊ARP表,如果tmp_entry已經在ARP表中,傳回ret=0。如果沒有找到tmp_entry,則tmp_entry做為新增ARP表項加入ARP表中,首先在ARP表中查找null_entry表項,如果找到,則将temp_entry複制到null_entry的位置。如果沒有找到null_entry表項,則通過realloc擴充arp_table的記憶體空間。realloc擴充arp_table失敗時,列印提示資訊,傳回ret=-1,成功時擴充的記憶體空間大小為一個ARP表項。将擴充的記憶體空間清0後,累加num_arp_entry,最後将temp_entry複制到新擴充的表項位置,完成ARP表項的添加。

static int del_arp_entry(unsigned char *ip, unsigned char *mac)
 {
     int ret = -1;
     unsigned int i = 0;
     for (i = 0; i < num_arp_entry; i ++) {
         if (memcmp(ip, arp_table[i].ip, ARP_PROTO_SZ) == 0 &&
         memcmp(mac, arp_table[i].mac, ARP_HW_SZ) == 0) {
             log_printf(INFO, "Delete ARP entry: "IPSTR" "MACSTR"\n",
             IP2STR(ip), MAC2STR(mac));
             memset(&arp_table[i], 0, sizeof(arp_entry_t));
             ret = 0;
             break;
         }
     }
     return ret;
 }

           

del_arp_entry,删除ARP表項,入參為IP位址和硬體位址的指針。周遊arp_table的每個表項,比較ip位址和硬體位址和入參IP和硬體位址相等的表項,找到對應表項時将其置為null_entry。

static int update_arp_table(unsigned char *ip, unsigned char *mac, unsigned int add)
 {
     int ret = 0;
     if (ip == NULL || mac == NULL) {
         log_printf(ERROR, "Invalid params to update ARP table\n");
         return -1;
     }
     if (add)
         ret = add_arp_entry(ip, mac);
     else
         ret = del_arp_entry(ip, mac);
     dump_arp_table();
     return ret;
 }

           

update_arp_table,更新ARP表,入參為IP位址和硬體位址的指針,add标記更新操作是添加還是删除操作。檢查ip和mac入參均不為空時,如果add不為0,則将調用arp_add_entry添加新的ARP表項,如果add為0,則調用add_del_entry删除表項。函數末尾調用dump_arp_table列印更新後的arp表,做為debug使用。

static unsigned char *lookup_arp_table(unsigned char *ip)
 {
     unsigned int i = 0;
     unsigned char *mac = NULL;
     unsigned char *local_ip = NULL;
     /* hardcode subnet mask 255.255.255.0 */
     unsigned char subnet_mask[4] = {0xff, 0xff, 0xff, 0x0};
 
     if (ip == NULL)
         goto out;
     local_ip = netdev_ipaddr();
     if (local_ip == NULL)
         goto out;
     /* within same subnet */
     if (((ip[0] & subnet_mask[0]) == local_ip[0]) &&
         ((ip[1] & subnet_mask[1]) == local_ip[1]) &&
         ((ip[2] & subnet_mask[2]) == local_ip[2])) {
         for (i = 0; i < num_arp_entry; i ++) {
             if (memcmp(ip, arp_table[i].ip, ARP_PROTO_SZ) == 0) {
                 mac = arp_table[i].mac;
                 break;
             }
         }
         /* no arp entry found, send out arp request*/
         if (i >= num_arp_entry) {
             /* todo send out arp request */
         }
     } else {
         /* lookup route table for next hop ip address */
         log_printf(WARNING, "Need to lookup route table\n");
     }
 out:
     return mac;
 }

           

lookup_arp_table查找ARP表,本節開始時概括介紹過ARP的查表過程,現在來看其具體實作。

lookup_arp_table的入參為IP位址的指針,傳回值是unsigned char *指針,傳回null時查表失敗,成功時傳回值指向ARP表中與入參IP位址對應的硬體位址。

Line 3-7: 定于局部變量i,mac初始值為null,local_ip是DIY TCP/IP中pcap_t裝置的網絡接口ip位址,subnet_mask子網路遮罩,被寫死為255.255.255.0。用于比較入參IP位址和local_ip位址的網絡字首是否相等。

Line 9-13: 判斷入參IP是否為空,以及調用網絡裝置子產品的netdev_ipaddr擷取網絡接口的IP位址的指針,指派給local_ip。

Line 14-34: 首先比較入參IP和local_ip的24位的網絡字首是否相等,判斷入參IP位址和local_ip是否在相同區域網路中。如果不再同一區域網路中,則需要查找路由表,擷取下一跳網絡接口的IP位址,該部分功能暫不實作。如果在同一區域網路中,則周遊arp_table,查找與入參IP相等的ARP表項,查找成功時将ARP表項中的硬體位址的指針指派給mac,查找失敗時需要發送ARP Request擷取與入參IP對應的硬體位址,該部分記憶體簡化實作為通過接收ARP Request更新ARP表項。暫時不實作的内容和簡化實作的内容本節開始已經介紹過原因,該函數最後傳回mac指針的值。

以上内容介紹了圍繞arp_entry_t資料結構的定義,實作的init_arp_table,deinit_arp_table,dump_arp_table,add_arp_entry,del_arp_entry,update_arp_table和lookup_arp_table。這些函數都是ARP子產品内部的靜态函數,僅在ARP子產品内部使用。

先來看update_arp_table在ARP子產品内部的使用,本節開頭部分已經介紹了ARP表的更新不僅僅依賴于收到的ARP Reply資料幀,當收到ARP Request資料幀,判斷需要回複ARP Reply資料幀時,同樣更新ARP表,簡化ARP表失查找敗時,發送ARP Request的處理。是以要修改proces_arp_request函數,加入update_arp_table的調用,更新ARP表。

static int process_arp_request(arphdr_t *pkt)
 {
…
     if (memcmp(pkt->target_ip, ipaddr, ARP_PROTO_SZ))
         goto out;
     log_printf(INFO, IPSTR " is at "MACSTR"\n",
         IP2STR(pkt->target_ip), MAC2STR(hwaddr));
     update_arp_table(pkt->sender_ip, pkt->sender_mac, 1);
     /* 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 8: 判斷ARP Reuquest請求的IP位址和虛拟IP位址相等後,建構ARP Reply資料幀之前,調用update_arp_table,将發送ARP Reply資料幀的源IP位址和源硬體位址,加入ARP表中,更新ARP表。process_arp_reuquest其餘部分的代碼與7.1.3節一緻,不再細述。

再來看ARP子產品暴露給其他子產品使用的新增函數:arp_init,arp_deinit,分别調用arp_init_table和arp_deinit_table完成ARP表的初始化,和ARP表的銷毀操作。

int arp_init()
 {
     return init_arp_table();
 }
 
 void arp_deinit()
 {
     deinit_arp_table();
 }

           

再來看ARP子產品暴露給其他子產品使用的一個重要函數,build_ethernet_header,當其他子產品調用該函數的入參dst_mac,目标硬體位址為空時,增加查找ARP表的過程。

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 && dst_mac)
         goto build_hdr;
     if (src_mac == NULL)
         src_mac = netdev_hwaddr();
     if (dst_mac == NULL)
         dst_mac = lookup_arp_table(dst_ip);
     if (dst_mac == NULL || src_mac == NULL) {
         ret = -1;
         goto out;
     }
 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_ethernet_header函數做的修改,其餘部分與7.1.3節的實作一緻。高亮部分首先判斷build_ether_header的入參,源硬體位址src_mac和目标硬體位址dst_mac是否都不為空,如果都不為空則 直接跳轉到build_hdr标号處建構以太網頭部。如果源硬體位址為空,則調用網絡裝置子產品的函數netdev_hwaddr擷取網絡接口硬體位址,如果目标硬體位址為空,則調用lookup_arp_table根據目标IP位址查找對應的目标硬體位址。成功擷取源硬體位址和目标硬體位址的條件下,建構以太網頭部資料。line188行判斷如果src_mac和dst_mac任意一個為空,則傳回ret=-1。

build_ethernet_header與arp_init,arp_deinit一樣,都是ARP子產品暴露給其他子產品使用的函數接口,build_ethernet_header将在IP子產品發送資料幀時被調用,arp_init和arp_deinit在DIY TCP/IP的初始化代碼中被調用,來看init.c中main函數的修改。

int main(int argc, char *argv[])
 {
…
 skip_option:
  signal(SIGINT, signal_handler);
 
  ndev = netdev_init(DEFAULT_IFNAME);
  if (ndev == NULL) {
      ret = -1;
      goto out;
  }
  if (ip_cfg)
      netdev_set_ipaddr(ndev, fake_ip, sizeof(fake_ip));
 
  pdbuf_init();
  arp_init();
  netdev_start_loop(ndev);
 out:
  if (ndev)
      netdev_deinit(ndev);
  arp_deinit();
  pdbuf_deinit();
  return ret;
 } 

           

DIY TCP/IP的初始化代碼,init.c中的main函數,已經被多次修改過,随着各個子產品的實作,加入了子產品的初始化代碼和銷毀代碼的調用。最近一次修改是在7.2節,加入了main函數入參解析的實作。本節在pdbuf子產品的初始化之後,加入arp_init,對應的在pdbuf_deinit子產品銷毀之前加入arp_deinit的調用。pdbuf子產品管理DIY TCP/IP其他子產品對buffer的配置設定和釋放,pdbuf子產品被銷毀時将統計buffer配置設定的總數和釋放的總數,提示有無記憶體洩漏,是以應當是銷毀協定棧時最後一個被銷毀的子產品。

編譯運作結果如下:

[email protected]:~/guojia/tasks/DIY_USER_SPACE_TCPIP/ch4/3$ 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
 192.168.0.7 is at 00:0c:29:2e:0a:ed
 ARP Table
 IP Address       MAC Address
 192.168.0.107        8c:a9:82:11:d1:de
 ^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
 Destroy ARP table, 1 entry
 
 #Internal Buffer Management#
 Alloc: 1, Free: 1

           

本節的測試方法與7.1.3節一樣,設定區域網路中不存存在的IP位址192.168.0.7為DIY TCP/IP的虛拟IP位址,在同一區域網路中的另外一台win7機器上ping 192.168.0.7,DIY TCP/IP收到ARP Request請求後,判斷請求IP位址與虛拟IP位址相等,列印出192.168.0.7 is at 00:0c:29:2e:0a:ed,緊接着調用update_arp_table更新ARP表,update_arp_table函數的末尾調用dump_arp_table,列印ARP表的内容。輸入ctrl+c,結束DIY TCP/IP運作時,arp_deinit釋放ARP表占用的記憶體,并統計ARP表中表項的數目為1。

7.5 小結

本章介紹了DIY TCP/IP的ARP子產品的實作,包括ARP資料幀的結構,ARP Request資料幀的接收,解析,ARP Reply資料幀的發送,ARP表的實作。新增了DIY TCP/IP初始化子產品的參數解析的實作,修改了網絡裝置結構體的定義,以及網絡裝置子產品通過PF_PACKET發送資料幀的實作。經驗證,DIY TCP/IP可以正确接收ARP Request資料幀,并能夠在ARP Request請求的目标IP位址與DIY TCP/IP的虛拟IP位址相等時,建構并回複正确的ARP Reply資料幀。在同一區域網路中發送ARP Request的主機上的ARP表中,能夠查找到DIY TCP/IP的虛拟IP和運作DIY TCP/IP的主機的網絡接口硬體位址的映射。本章的ARP子產品的覆寫了DIY TCP/IP目前實作的網絡裝置子產品,pdbuf子產品,debug子產品,utility子產品的代碼實作,正确回複ARP Reply資料幀,表明這些子產品的代碼實作是正确的。

目前目錄結構

DIY TCP/IP ARP子產品3

下一篇:DIY TCP/IP IP子產品和ICMP子產品的實作0