您的位置:首页 > 其它

lwIP ARP协议分析

2012-11-08 21:23 399 查看
/article/4791085.html

ARP 协议分析

总的来说,lwip将链路层ethernet的协议分组格式分为ether和etherarp

分开处理。ip分组先进入etharp_ip_input更新一下arp表项,然后直接进入

netif的input传递给上层ip层。arp分组直接进入etharp_arp_input。

不送如ip层。

奇怪的是,lwip把ether header的结构定义在etharp中。

-ARP 数据结构

-- arp表状态

enum etharp_state {

ETHARP_STATE_EMPTY, /* 表项空 */

ETHARP_STATE_PENDING,

ETHARP_STATE_STABLE, /* 稳定状态表项,该表项中MAC值可直接取出 */

ETHARP_STATE_EXPIRED /* 超时表项 */

};

-- arp表项结构

struct etharp_entry {

struct pbuf *p; /* arp 请求队列 */

struct ip_addr ipaddr;

struct eth_addr ethaddr;

enum etharp_state state;

u8_t ctime; /* 超时值 */

};

-- ARP 链路层协议分组

struct etharp_hdr {

struct eth_hdr ethhdr; /* ether header */

u16_t hwtype;

u16_t proto;

u16_t _hwlen_protolen;

u16_t opcode;

struct eth_addr shwaddr;

struct ip_addr2 sipaddr;

struct eth_addr dhwaddr;

struct ip_addr2 dipaddr;

}

- ARP 函数

void etharp_init();

初始化所有静态ARP表项,状态为EMPTY。

static err_t update_arp_entry(struct netif *netif, struct ip_addr *ipaddr, struct eth_addr *ethaddr, u8_t flags);

首先排除广播、多播及any地址。然后,

将ipaddr及ethaddr对加入arp表项中,该表项索引由find_entry获得。

加入后,将该arp表项中还未发送的IP包(这些IP包是由etharp_ouput函数,在

处理IP包发送时,由于未找到对应ip的mac地址,由etharp_query加入到

pending arp的未发送IP包链表中。现在我们得到mac和ip对应值后,就

可以将这些IP包发送出去),发送到netif驱动。

该函数被etharp_ip_input及etharp_arp_input调用。

static s8_t find_entry(struct ip_addr *ipaddr, u8_t flags);

查询包含ipaddr的表项。

查询优先级:1. pending 2. stable 3. empty。

find_entry总是根据这些优先级查找是否由匹配项,如果有,立刻返回该项

索引。否则根据优先级产看是否有过期表项,并覆盖它。

void etharp_ip_input(struct netif *netif, struct pbuf *p);

该函数是再ip分组传上ip层之前,将ip分组的mac和ip映射到

arp表项中做更新。这样的设计好像比较浪费。完全可以加大arp

表项,去除该过程

void etharp_arp_input(struct netif *netif,

struct eth_addr *ethaddr, struct pbuf *p);

arp分组到来,直接交给该函数处理。

无论netif的ip地址是否被配置过,都将该ARP分组的源ip和mac地址

加入到ARP表项中。如果一个ARP请求的目的ip地址是本地ip地址。

则发送response ARP分组。

err_t etharp_output(struct netif *netif, struct ip_addr *ipaddr, struct pbuf *q);

该函数分开处理两种地址类型的网络层包,

1)多播及广播。直接调用netif->linkoutput发送出去,该函数为网卡驱动。

2)单播。单播的IP包又分三种处理方式。a. 目的ip在arp表项中,并且stable。

则直接构造以太头,调用网卡驱动,发送以太帧。b. 目的ip不在arp表项中,

修改arp表项状态为pending,并调用etharp_request,发送ARP REQUEST。

c. 目的ip在ARP表项中。但状态为pending(这个状态是由b条件引起的,可能

ARP RESPONSE在处理该条件时还为返回)。将待发送的IP包的缓冲区PBUF_REF

替换成PBUF_POOL或PBUF_RAM(暂时不知道为啥)。这些包将会在update_arp_input

中被发送。

- ARP 协议处理流程图





1. ARP:

从功能上来说,arp可以简单的分成两个部分:

a. 当我要向目的ip发送一个数据包的时候,需要通过arp实现ip到物理地址(一般为mac地址)的映射------------》ethernet_output函数

b. 处理输入包,更新arp缓存,如果是ip包后递交给ip层,如果是arp包,对于不同的arp操作做相应的相应------------》etharp_input函数。





ethernet_input函数:

以太网的帧类型可以是:IP,ARP 甚至可以是pppoe, wlan等。这里主要分为IP, ARP(注意:ip arp在以太网的帧类型中是并列的,所以在input这个函数中分为ip,arp两大部分)

对于ip类型的:主要工作就是看是否开启了ETHARP_TRUST_IP_MAC这个选项,如果开启了就是要用这个帧中的信息来更新arp缓冲(利用帧首部的源mac地址和帧数据中ip报文中的源ip地址),然后丢弃以太网帧首部传递给ip层(即ip_input)。

对于arp类型的:同样先更新arp缓存,然后判断arp报文的操作类型,在lwip中对于arp数据包实现了两种操作:(rarp请求,rarp响应已经几乎淘汰了)

a.arp请求:首先判断这个包是不是给本机的,如果是给本机的,在原有包的基础上重组一个回应包并发出(注意此处并没有重新分配一个pbuf,而是借用了原来的缓冲结构)。如果不是本机的忽略。、

b.arp回应:主要的工作是更新arp缓存,但是这一步已经在arp包刚进来的时候就处理了,所以这里不需要再重复做,这里有一些dhcp的东东,暂时还未涉及到。

这里无论是ip类型,还是arp类型,都会更新arp缓存,也就是我们的

update_arp_entry(struct netif *netif, struct ip_addr *ipaddr, struct eth_addr *ethaddr, u8_t flags),其中netif是对应的网络接口,ipaddr和ethaddr分别是对应的ip地址和mac地址。这个函数中会去更新arp缓存,并且把这个arp表项的等待的队列通过发送函数发送出去()。

update_arp_entry:先通过调用find_entry找到对应ipaddr对应的表项——>设置相应的arp选项的成员(主要是state,netif, ethaddr, cttime)——>如果定义了arp_queue的话,并且这个arp表项上有未发送队列的话,把这些队列发送出去。其中find_entry这个函数蛮重要的,下面解释下这个函数的流程。

find_entry:

1. lwip有一个比较巧妙的地方,它并不是冲上去就是就把arp缓存中所有的表项搜索一遍,而是做了一个假设,假设这次的表项索引还是上一次的,if so ,we are really fast!(因为在很多情况下就是这样的)

2. 首先搜索分成三类,empty,suspending,stable。第一个是对状态为empty的检查,arp表项中第一个状态为empty的索引号,第二个对状态为suspend的检查,分成三部分,首先判断是否恰好为此次想要的ip对应的索引,如果是直接返回;不是的话又分这个索引是有queue还是没有queue,分别记录这两种类型中cttime最大的一个索引,第三个是对状态为stable的检查,分成两部分,首先判断是否恰好为此次想要的ip对应的索引,如果是直接返回;不是的话,记录状态为stable中cttime最大的(时间戳,最老的)。

因此这部分,主要做了两件事:

●如果arp缓存中有现成的索引,则直接返回(状态时suspend和stable);

●通过索引记录几个重要的参数:a. arp表项中第一个状态为empty的索引号 b. arp表项中最老的状态为suspend的有queue的索引号 c. arp表项中最老的状态为suspend的没有queue的索引号 d. arp表项中最老的状态为stable的索引号。这些参数是在arp缓存没有现成索引号时,会根据优先级来对这四个参数来选择或删除表项。优先级等级一次为:empty——》oldest stable——》oldest pending without queue——》oldest pending
with queue

3. arp没有现存的缓存,而状态又不是empty的选项,意味着需要在里面删除现有的arp选项,这里则需要调用snmp_delete****,由于snmp的东东暂时还未看到,这里就不详细讲了。

4. 最后更新表项的一些成员,有状态,时间戳,索引缓存

ethernet_output函数:

由于是发送ip数据包,所以一开始需要增加缓冲区大小,大小为以太网的数据首部的大小。然后检查ip地址,可以分为广播包,多播包,单播包(单播包又分为是局域网内部还是局域网外面)

广播包:判断目的ip地址是不是为全1,或者是全0(老版本中使用的),如果是广播包则目的ip的mac地址不需要查询arp缓存或者发送arprequest,mac地址为全一,即0xff,0xff,0xff,0xff,0xff,0xff。

多播包:判断目的ip地址是不是d类地址,即eXXXX,如果是多播的话,mac地址也是确定的,即将ip地址的低23位映射到mac地址为01-00-5e-00-00-00的低23位上。

单播包:要比较目的ip和本地ip地址,看是否是局域网内的,不是局域网内的,则调用默认网关的地址,然后再统一调用etharp_query(netif, ipaddr, q);函数

而广播包和多播包则无需调用etharp_query(netif, ipaddr, q);函数,因为已经得到了明确的mac地址,基本只需要添加以太网帧首部然后发送即可。

etharp_query:大概流程如下:

a. 先通过ipaddr利用函数find_entry找到arp缓存中的索引号

b. 根据索引号就能得到arp的对应项,此时根据项的state分成三大类,

empty:说明原来表项中是没有这个arp缓存的,所以把表项状态切换为pending并发送arp_requst包,把待发送的数据放在这个entry(表项)的队列上,系统在input的时候解析了这个ip后会发送(具体可以看前面input中的讲解);

pending:说明这个ip原来就有了,我们再重新发一次arp_request,同样把待发送数据放在队列上;

stable:说明在arp缓存中ip地址已经有了解析的mac地址,此时又分成两类,一类是数据包不为空,则直接调用etharp_send_ip函数发送;第二类是如果数据包为空,则说明是一个request包,还是调用arp_request
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: