• linux网络协议栈源码分析 - 链路层ARP地址解析协议


    1、ARP报文格式

     

    1.1、ARP报文格式

    ARP报文的格式如下:

    (具体各字段的含义参考《TCP/IP详解卷 1:协议》第4章 ARP:地址解析协议)

    1.2、内核定义

    op定义如下:

    1. #define ARPOP_REQUEST 1 /* ARP request */
    2. #define ARPOP_REPLY 2 /* ARP reply */

    协议类型定义如下:

    1. #define ETH_P_IP 0x0800 /* Internet Protocol packet */
    2. #define ETH_P_ARP 0x0806 /* Address Resolution packet */


     

    2、ARP报文的创建(arp_create)

    2.1、ARP首部结构体

    1. struct arphdr {
    2. __be16 ar_hrd; /* format of hardware address */
    3. __be16 ar_pro; /* format of protocol address */
    4. unsigned char ar_hln; /* length of hardware address */
    5. unsigned char ar_pln; /* length of protocol address */
    6. __be16 ar_op; /* ARP opcode (command) */
    7. };

    2.2、ARP报文创建(arp_create)

            ARP报文的创建主要就是为ARP报文分配内存并设置以太网首部、28字节ARP请求/应答的各个字段。arp_create函数代码如下:

    1. struct sk_buff *arp_create(int type, int ptype, __be32 dest_ip, // type: op(ARPOP_REQUEST/ARPOP_REPLY), ptype: 帧类型(ETH_P_IP/ETH_P_ARP), dest_ip: 目的IP地址
    2. struct net_device *dev, __be32 src_ip, // dev: 输出网卡设备, src_ip: 发送端IP地址
    3. const unsigned char *dest_hw, // dest_hw: 以太网目的地址(以太网首部)
    4. const unsigned char *src_hw, // 发送端以太网地址
    5. const unsigned char *target_hw) // target_hw: 目的以太网地址(ARP应答报文设置, ARP请求为空)
    6. {
    7. struct sk_buff *skb;
    8. struct arphdr *arp;
    9. unsigned char *arp_ptr;
    10. int hlen = LL_RESERVED_SPACE(dev);
    11. int tlen = dev->needed_tailroom;
    12. /*
    13. * Allocate a buffer
    14. */
    15. skb = alloc_skb(arp_hdr_len(dev) + hlen + tlen, GFP_ATOMIC); // 分配ARP报文内存(以太网首部+28字节ARP请求/应答)
    16. if (!skb)
    17. return NULL;
    18. skb_reserve(skb, hlen); // 预留hlen长度的内存(以太网首部)
    19. skb_reset_network_header(skb);
    20. arp = (struct arphdr *) skb_put(skb, arp_hdr_len(dev)); // 预留ARP首部的内存空间(获取skb内存地址,skb内存地址后移ARP首部大小)
    21. skb->dev = dev;
    22. skb->protocol = htons(ETH_P_ARP); // 帧类型(ETH_P_ARP, ARP请求或应答)
    23. if (!src_hw)
    24. src_hw = dev->dev_addr;
    25. if (!dest_hw) // 目的以太网地址(ARP请求没有目的以太网地址,设置为广播地址FF:FF:FF:FF:FF:FF)
    26. dest_hw = dev->broadcast; // 广播地址FF:FF:FF:FF:FF:FF
    27. /*
    28. * Fill the device header for the ARP frame
    29. */
    30. if (dev_hard_header(skb, dev, ptype, dest_hw, src_hw, skb->len) < 0) // 调用eth_header填充以太网首部(ptype: 帧类型, dest_hw: 以太网目的地址, src_hw: 以太网源地址)
    31. goto out;
    32. /*
    33. * Fill out the arp protocol part.
    34. *
    35. * The arp hardware type should match the device type, except for FDDI,
    36. * which (according to RFC 1390) should always equal 1 (Ethernet).
    37. */
    38. /*
    39. * Exceptions everywhere. AX.25 uses the AX.25 PID value not the
    40. * DIX code for the protocol. Make these device structure fields.
    41. */
    42. switch (dev->type) {
    43. default:
    44. arp->ar_hrd = htons(dev->type); // 设置ARP首部硬件类型(ARPHRD_ETHER)
    45. arp->ar_pro = htons(ETH_P_IP); // 设置ARP首部协议类型(ETH_P_IP)
    46. break;
    47. #if IS_ENABLED(CONFIG_AX25)
    48. case ARPHRD_AX25:
    49. arp->ar_hrd = htons(ARPHRD_AX25);
    50. arp->ar_pro = htons(AX25_P_IP);
    51. break;
    52. #if IS_ENABLED(CONFIG_NETROM)
    53. case ARPHRD_NETROM:
    54. arp->ar_hrd = htons(ARPHRD_NETROM);
    55. arp->ar_pro = htons(AX25_P_IP);
    56. break;
    57. #endif
    58. #endif
    59. #if IS_ENABLED(CONFIG_FDDI)
    60. case ARPHRD_FDDI:
    61. arp->ar_hrd = htons(ARPHRD_ETHER);
    62. arp->ar_pro = htons(ETH_P_IP);
    63. break;
    64. #endif
    65. }
    66. arp->ar_hln = dev->addr_len; // 设置ARP首部硬件地址长度(6, 物理地址长度)
    67. arp->ar_pln = 4; // 设置ARP首部协议地址长度(4, IP地址长度)
    68. arp->ar_op = htons(type); // op(ARPOP_REQUEST/ARPOP_REPLY)
    69. arp_ptr = (unsigned char *)(arp + 1); // arp_ptr指针指向发送端以太网地址
    70. memcpy(arp_ptr, src_hw, dev->addr_len); // 拷贝发送端以太网地址到ARP报文发送端以太网地址里面
    71. arp_ptr += dev->addr_len; // 发送端以太网地址指针+地址长度(6),arp_ptr指针指向发送端IP地址
    72. memcpy(arp_ptr, &src_ip, 4); // 拷贝发送端IP地址到ARP报文发送端地址里面
    73. arp_ptr += 4; // 发送端IP地址指针+4,arp_ptr指针指向目的以太网地址
    74. switch (dev->type) {
    75. #if IS_ENABLED(CONFIG_FIREWIRE_NET)
    76. case ARPHRD_IEEE1394:
    77. break;
    78. #endif
    79. default:
    80. if (target_hw) // 目的以太网地址不为空(ARP应答报文)
    81. memcpy(arp_ptr, target_hw, dev->addr_len); // 拷贝目的以太网地址到ARP的目的以太网地址里面
    82. else // 目的以太网地址为空(ARP请求报文)
    83. memset(arp_ptr, 0, dev->addr_len); // ARP请求报文,设置目的以太网地址为00:00:00:00:00:00
    84. arp_ptr += dev->addr_len; // 目的以太网地址指针+地址长度(6),arp_ptr指针指向目的IP地址
    85. }
    86. memcpy(arp_ptr, &dest_ip, 4); // 拷贝目的IP地址到ARP报文的目的IP地址里面
    87. return skb;
    88. out:
    89. kfree_skb(skb);
    90. return NULL;
    91. }

    3、ARP报文的发送

    3.1、ARP请求报文的发送

            arp_send_dst调用arp_create创建ARP报文后,调用arp_xmit发送ARP报文,最后调用__dev_queue_xmit将报文发送到网卡的发送队列里面。

             ARP报文最后通过调用网卡设备驱动函数smsc911x_hard_start_xmit发送到网卡里面,smsc911x_hard_start_xmit调用栈如下:

           

     3.2、ARP应答报文的发送

            首先网卡收到报文触发硬件中断,调用网卡的中断处理函数smsc911x_irqhandler(smsc911x_drv_probe注册的中断处理函数),smsc911x_irqhandler最终调用____napi_schedule触发NET_RX_SOFTIRQ软件中断,在硬件中断返回时,调用软件中断处理函数。

            __do_softirq软件中断调用栈如下:

            smsc911x_poll从网卡读取报文并设置帧类型skb->protocol,__netif_receive_skb_core调用deliver_ptype_list_skb找到对应协议的packet_type,packet_type报文对应协议的输入函数,对于ARP协议的packet_type就是arp_packet_type,处理函数就是arp_rcv,arp_rcv最终调用arp_process处理ARP报文,对于ARP请求,调用arp_send_dst发送ARP应答报文,调用代码如下:

             ARP请求输入到ARP应答再到发送到网卡的调用栈如下:

  • 相关阅读:
    Java真的不难(四十九)Redis的入门及使用(2)
    Linux高性能服务器编程——ch5笔记
    【开源】课程管理平台 JAVA+Vue.js+SpringBoot+MySQL
    行为型模式-解释器模式
    Qt QScrollBar滚动条样式设置
    mybatis入门
    kafka优化配置,Kafka 的消费者客户端详解
    Taro小程序隐私协议开发指南填坑
    Vue(四)——全局事件总线, 消息订阅与发布 ,nextTick
    汉诺塔问题
  • 原文地址:https://blog.csdn.net/arm7star/article/details/126128722