轻量级TCP/IP实现包Lwip之ARP

来源:互联网 发布:ipad编程软件 idea 编辑:程序博客网 时间:2024/05/29 06:48

Lwip主要函数调用关系图

ARP(Adrress ResolutionProtocol,地址解析协议),属于TCPIP协议族网络互联层协议,主要负责网络接口层与IP层之间地址映射和转换,因为协议是分层,各层都有自己的任务和算法。比如在以太网中,主要是解决的局域网通信问题,在网络中主机较少的情况,采用广播的方式就可以解决,但是当局域网主机较多的情况下,这种广播式通信就会因为共用介质的问题造成部分链路的闲置,如下图所示:

当节点A和B进行通信时,因为电路共享的原因,C和D无法就无法进行通信,除非在B和C之间有个节点能够有个“智能开关”,当检测到A和B通信时,自动与断开C节点, C和D就可正常进行通信。其实,这个“智能开关”就是以太网中的网桥和交换机,但是这种情况需要每台主机直接与交换机每个端口相连,交换机会自动建立起端口和主机MAC地址之间的关系。在共享同一条线路的情况下,就会出现介质争用的情况,以太网协议中采用“载波侦听“和”冲突检测”的方式,主机在每次发送数据前会检测线路是否是空闲,如果不是就等待一定的时间,再次进行检测,如果线路空闲就发送数据包,发送出去就不会管了。

1.网络接口层

网络接口层主要解决的是具体物理链路通信的问题,比如现在的采用RJ45接口以太网卡, 一根TX-线、一根TX+、一根RX-线、一根RX+线。TX-、TX+主要用于数据的发送,RX-、RX+主要用于数据的接口,这是接口的规定。基于这种连接方式的物理层数据通信协议有:RS232、RS485和IEEE 802.3等,都是异步通信协议(双方拥有自己独立的时钟,传输线路较长时,电流信号在传输过程中会因为阻抗的原因产生延迟和变形)。

2.IEEE802.3

IEEE802.3是以太网正式标准,对通信的数据格式进行如下图所示的规定:

7字节

1字节

6字节

6字节

2字节

46字节~1500字节

4字节

前同步码

SFD

目的地址

源地址

长度/类型

数据和填充

CRC

①前同步码:主要用于同步时钟信号。在异步通信中,由于通信双方采用自己的时钟,同一时刻信号的相位就无法确定,为了确保双方信号的相位保持一致,就需要锁相环对电信号进行分析,使通信双方的接收的信号保持一致。因此前同步码就是用于调整时钟,确保发送和接收双方的时钟同步。前同步码值固定为:

二 进 制:10101010 10101010 10101010 10101010 10101010 10101010 10101010

十六进制:AA AA AA AA AA AA AA

②SFD:帧首界定符。本质上也是属于同步码,只不过用于指示其之后的数据将是真正的有效数据,其值为10101011(0x AB),前同步码完成时钟同步后,如果检测到此信号时,硬件就开始切换内部的工作状态机,开始启动有效数据接口的过程。

③目的地址:数据发送方的地址,也叫作MAC地址,6个字节共48位。因为共享介质的原因,在传输过程中需要地址来进行标识,否则,没有谁知道数据是要给谁的。在以前的Hub网络中,每一个设备发出的数据包都会送达到每一个连接设备,设备在接收到数据包之后发现MAC地址不是自己的就直接进行丢弃,显然,数据通信采用的是一种广播式的方式,如前面所说,大部分链路出现闲置的情况。此时MAC地址仅仅只是一个地址标识的作用,我们完全可以使用高层的协议来实现。但是随着交换机的普及,这种广播式通信带来的链路闲置情况就得到了有效地遏制,交换机通过学习的方式(就是ARP协议),获得了与其连接的每一台设备的MAC地址,在通信过程中交换机可以自动判断然后定向的发送出去,通过这种交换数据的方式,充分提高了各链路的利用率。

④源地址:数据接收方的地址,用于标识数据接收方的MAC地址。

⑤长度/类型:当这两个字节值小于1518时,其值代表其后“数据和填充”字段的长度;当这两个字节值大于1518时,则表示以太网帧所封装的上层协议类型,如0x0800表示IP协议,0x0806表示ARP。

⑥数据和填充:以太网帧所封装的上层协议数据,最小长度为46字节,最大长度为1500字节。

⑦CRC:以太网帧的差错校验信息。

Lwip对以太网帧头部进行了定义:

/** * struct eth_ addr * 以太网数据帧地址结构体定义 */struct eth_addr {u8_t addr[ETHARP_HWADDR_LEN];}/*** struct eth_hdr* 以太网数据帧首部结构体定义*/struct eth_hdr {struct eth_addr dest; // 以太网目的地址 6Bstruct eth_addr src; // 以太网源地址 6Bu16_t type; // 帧类型 2B}

3.IP层

IP层解决的主要是广域网通信问题,在主机数量多的情况下,为了解决线路闲置的情况,就需要对整个大的网络进行划分,规定好哪些主机的数据包在指定范围内流动,各个划分的网络由专门的设备(如路由器)连接起来,跨范围通信需要由衔接点设备进行转发。网络协议采用的分层设计思想,因此在IP层就有专门的通信地址(或者说设备唯一标识),这就是我们经常所说的IP地址,IPv4采用的是长度为32位的地址,分为A、B、C、D、E共五类网络。里面还规定单播、广播、组播地址,私网和公网地址。

IP层通信中,数据发送方和数据接收方采用的是32位的IP地址,数据流动在逻辑网络层面。当数据包在物理链路传输过程中,需要采用物理MAC地址。一般地,一个IP地址对应一个MAC地址,但是多个IP地址也可以对应一个MAC地址(绑定多个IP),或者一个IP地址对应多个MAC地址(多路径链路聚合)。这种IP与MAC映射关系的维护就是由ARP和RARP协议负责完成的。

4. ARP报文结构

2字节

2字节

1字节

1字节

2字节

6字节

4字节

6字节

4字节

硬件类型

协议类型

硬件地址长度

协议地址长度

OP

发送方MAC

发送方IP

接收方MAC

接收方IP

/** * struct etharp_hdr * ARP数据包结构体定义 */struct etharp _hdr {u16_t hwtype;    // 硬件类型 2Bu16_t proto;     // 协议类型 1Bu8_t  hwlen;     // 硬件地址长度 1Bu8_t  protolen;  // 协议地址长度 1Bu16_t opcode;    // 操作字段 2Bstruct eth_addr shwaddr; // 发送方MACstruct ip_addr2 sipaddr; // 发送方IPstruct eth_addr dhwaddr; // 接收方MACstruct ip_addr2 dipaddr; // 接收方IP}

5.ARP工作流程介绍

如下图所示,为ARP工作流程的简要示意图:

下图为Lwip对ARP协议的实现,简要的整理了各函数之间的调用关系:


网卡收到数据帧后,通过中断方式通知主程序,或者由主程序主动进行查询。此时会调用到ethernetif_input函数,具体代码如下:

/*** ethernetif_input* @param struct netif *netif 全局变量 当前网络接口指针 不能为空*/static void ethernetif_input (struct netif *netif) {struct ethernetif *ethernetif;struct eth_hdr *ethhdr;struct pbuf *p;ethernetif = netif->state;p = low_level_input(netif);//调用网卡驱动,读取接收到的数据if (NULL == p) return;ethhdr = p->payload; // 获取数据域头部,ethhdr指向数据域以太网帧头部switch(htons(ethhdr->type)){case ETHTYPE_IP :case ETHTYPE_ARP:if (netif->input(p, netif) != ERR_OK) {pbuf_free(p);p = NULL;}break;default:pbuf_free(p);p = NULL;break;}}

在thernetif_input函数中,根据以太网帧头部协议类型字段的值进行处理,如果为IP或ARP协议类型,那么就通过去全局网络接口指针注册的input函数对数据进行处理,也就是ethernet_input函数,具体代码如下:

/*** ethernet_input* @param struct pbuf  *p 指向接收到的数据包* @param struct netif *netif 全局变量 当前网络接口指针 不能为空*/err_t ethernet_input (struct pbuf *p, struct netif *netif) {struct eth_hdr *ethhdr;u16_t type;if (p->len <= SIZEOF_ETH_HDR) {goto free_and_return;}ethhdr = p->payload;type   = htons(ethhdr->type);switch (type) {case ETHTYPE_IP : #if ETHARP_TRUST_IP_MACetharp_ip_input(netif, ip);#endifif (pbuf_header(p, -(s16_t)SIZEOF_ETH_HDR)) {goto free_and_return;} else {ip_input(p, netif);}break;case ETHTYPE_ARP :etharp_arp_input(netif, (struct eth_addr*)(netif->hwaddr), p);break;default:goto free_and_return;}return ERR_OK;free_and_return:pbuf_free(p);return ERR_OK;}

ethernet_input函数与ethernetif_input类似,只不过在处理的更细一些。如果以太网帧头部协议类型为IP,则调用etharp_ip_input函数更新ARP缓存,而后调用ip_input函数提交IP数据包到上层处理,主要是;如果协议类型为ARP,则调用etharp_arp_input函数进行处理。

/*** etharp_arp_input* @param struct netif *netif 全局变量 当前网络接口指针 不能为空* @param struct eth_addr *ethaddr全局变量 当前网络接口MAC地址* @param struct pbuf  *p 指向接收到的数据包*/err_t ethernet_input (struct netif *netif, struct eth_addr *ethaddr, struct pbuf *p){struct etharp_hdr *hdr;      // 指向ARP数据包头部struct eth_hdr *ethhdr;      // 指向以太网帧头ip_addr_t sipaddr, dipaddr; // 暂存源IP地址和目的IP地址}