嵌入式LwIP ARP协议1

一、ARP协议简介  

ARP,全称 Address Resolution Protocol,译作地址解析协议,ARP 协议与底层网络接口密切相关。TCP/IP 标准分层结构中,把 ARP 划分为了网络层的重要组成部分。 当一个主机上的应用程序要向目标主机发送数据时,它只知道目标主机的 IP 地址,而在协议栈底层接口发送数据包时,需要将该 IP 地址转换为目标主机对应的 MAC 地址,这样才能在数据链路上选择正确的通道将数据包传送出去,在整个转换过程中发挥关键作用的就是 ARP 协议了。 本次的学习内容有:

ARP 协议的原理;

ARP 缓存表及其创建、维护、查询;

ARP 报文结构。

1.1、物理地址与网络地址

  网卡的 48 位 MAC 地址都保存在网卡的内部存储器中,TCP/IP 协议有32bit 的 IP 地址(网络地址),网络层发送数据包时只知道目的主机的 IP 地址,而底层接口(如以太网驱动程序)必须知道对方的硬件地址才能将数据发送出去。

  为了解决地址映射的问题,ARP 协议提供了一种地址动态解析的机制,在32 bit的 IP 地址和采用不同网络技术的硬件地址之间提供动态映射,为上层将底层的物理地址差异屏蔽起来,这样上层的因特网协议便可以灵活的使用 IP 地址进行通信。

1.2、ARP协议的本质

  ARP 协议使用目标主机的 IP 地址,查询其对应的 MAC 地址,保证底层链路上数据包通信的进行。

  假如我们的主机(192.168.1.78)需要向开发板(192.168.1.37)发送一个 IP 数据包,当发送数据时,主机会在自己的 ARP 缓存表中寻找是否有目标IP地址。如果找到了,也就知道了目标 MAC 地址为(00­80­48­12­34­56),此时主机直接把目标 MAC 地址写入以太网帧首部发送就可以了;如果在 ARP 缓存表中没有找到相对应的 IP 地址,此时比较不幸,我们的数据需要被延迟发送,随后主机会先在网络上发送一个广播(ARP 请求,以太网目的地址为 FF­FF­FF­FF­FF­FF),广播的 ARP 请求表示同一网段内的所有主机将会收到这样一条信息:“192.168.1.37 的 MAC 地址是什么?请回答”。网络 IP 地址为 192.168.1.37(开发板)的主机接收到这个帧后,它有义务做出这样的回答(ARP 应答):“192.168.1.37 的 MAC 地址是(00­80­48­12­34­56)”。 这样,主机就知道了开发板的 MAC 地址,先前被延迟的数据包就可以发送了,此外,主机会将这个地址对保存在缓存表中以便后续数据包发送时使用。 ARP 的实质就是对缓存表的建立、更新、查询等操作。

二、数据结构

  头文件etharp.h 文件实现了以太网中 ARP 协议的全部数据结构,ARP 协议实现过程中有两个重要的数据结构,即 ARP 缓存表和 ARP 报文。

2.1、ARP表

  ARP协议的实质就是对缓存表的建立、更新、查询等操作。ARP 缓存表由缓存表项(entry)组成,每个表项记录了一组 IP 地址和 MAC 地址绑定信息,还包含了与数据包发送控制、缓存表项管理相关的状态、控制信息。LwIP中描述缓存表项的数据结构叫 etharp_entry,如下所示:

structetharp_entry

{

  struct etharp_q_entry *q;              //数据包缓冲队列指针

  struct ip_addr ipaddr;                 //目标 IP 地址

  struct eth_addr ethaddr;               //MAC 地址

  enum etharp_state state;                //描述该 entry 的状态

u8_t

ctime;                            //描述该 entry 的时间信息

  struct netif *netif;                   //对应网络接口信息

  描述缓冲队列的数据结构叫做 etharp_q_entry,该结构的定义如下:

structetharp_q_entry

{

  structetharp_q_entry *next;     //指向下一个缓冲数据包

  struct pbuf

*p;                  //指向数据包 pbuf

用一个图来看看 etharp_q_entry 结构在缓存表数据队列中的作用,如图所示:

        [if !vml]

[endif]

  state 是个枚举类型,它描述该缓存表项的状态,LwIP 中定义一个缓存表项可能有三种不同的状态,用枚举型 etharp_state 进行描述。

enumetharp_state

{

ETHARP_STATE_EMPTY

= 0,       //empty 状态

ETHARP_STATE_PENDING,                  //pending 状态

ETHARP_STATE_STABLE           //stable 状态

  编译器为ARP表预先定义了ARP_TABLE_SIZE(10)个表项空间,因此ARP缓存表内部最多只能存放ARP_TABLE_SIZE条IP 地址与MAC地址配对信息。

static struct etharp_entry

arp_table[ARP_TABLE_SIZE]; //定义 ARP 缓存表

  ETHARP_STATE_EMPTY 状态:初始化的时候为empty状态。

  ETHARP_STATE_PENDING状态:表示该表项处于不稳定状态,此时该表项只记录到了IP 地址,但是还未记录到对应的MAC地址。 很可能的情况是,LwIP 内核已经发出一个关于该 IP地址的 ARP 请求到数据链路上,但是还未收到 ARP应答。

  ETHARP_STATE_STABLE 状态:当 ARP表项被更新后,它就记录了一对完整的IP 地址和MAC地址。

  在ETHARP_STATE_PENDING 状态下会设定超时时间(10秒),当计数超时后,对应的表项将被删除;在ETHARP_STATE_STABLE状态下也会设定超时时间(20分钟),当计数超时后,对应的表项将被删除。

  网络接口结构指针 netif,该结构中包含了网络接口的 MAC地址和IP地址等信息,在发送数据包的时候,这些信息都起着至关重要的作用。 

  ctime为每个表项的计数器,周期性的去调用一个etharp_tmr函数,这个函数以5秒为周期被调用,在这个函数中,它会将每个ARP 缓存表项的 ctime 字段值加 1,当相应表项的生存时间计数值 ctime 大于系统规定的某个值时,系统将删除对应的表项。


//稳定状态表项的最大生存时间计数值:240*5s=20min

#defineARP_MAXAGE       240

//PENDING状态表项的最大生存时间计数值:2*5s=10s

#defineARP_MAXPENDING   2

void etharp_tmr(void)

{

u8_t

i;

  for (i = 0; i

< ARP_TABLE_SIZE; ++i) //对每个表项操作,包括空闲状态的表项{

arp_table[i].ctime++; //先将表项 ctime 值加1

//如果表项是 stable 状态,且生存值大于 ARP_MAXAGE,

//或者是 pending 状态且其生存值大于 ARP_MAXPENDING,则删除表项

    if(((arp_table[i].state == ETHARP_STATE_STABLE) && //stable 状态

(arp_table[i].ctime >= ARP_MAXAGE))||

((arp_table[i].state == ETHARP_STATE_PENDING) && //pending 状态

(arp_table[i].ctime >= ARP_MAXPENDING)) )

{

      if(arp_table[i].q != NULL)   //如果表项上的数据队列中有数据{

free_etharp_q(arp_table[i].q);     //则释放队列中的所有数据

arp_table[i].q = NULL;             //队列设置为空

}

arp_table[i].state = ETHARP_STATE_EMPTY; //将表项状态改为未用

}//if

}//for

}

2.2、ARP报文

   ARP 请求和 ARP 应答,它们都是被组装在一个 ARP 数据包中发送的,一个典型的 ARP 包的组成结构如图所示:

[if !vml]

[endif]

  以太网目的地址和以太网源地址:分别表示以太网目的MAC地址和源MAC地址,目的地址全1时是特殊地址以太网广播地址。在 ARP 表项建立前,源主机只知道目的主机的 IP 地址,并不知道其 MAC 地址,所以在数据链路上,源主机只有通过广播的方式将 ARP请求数据包发送出去,同一网段上的所有以太网接口都会接收到广播的数据包。

  桢类型:ARP-0x0806、IP-0x0800、PPPoE-0x8864。

  硬件类型:表示发送方想要知道的硬件类型。

  协议类型:表示要映射的协议地址类型,0x0800-表示要映射为IP地址 。

  硬件地址长度和协议地址长度:以太网ARP请求和应答分别为6和4,代表MAC地址长度和IP地址长度。

  op:指出ARP数据包的类型,ARP请求(1),ARP应答(2)。

  在以太网的数据帧头部中和 ARP 数据包中都有发送端的以太网MAC 地址。对于一个 ARP 请求包来说,除接收方以太网地址外的所有字段都应该被填充相应的值。当接收方主机收到一份给自己的 ARP 请求报文后,它就把自己的硬件地址填进去,然后将该请求数据包的源主机信息和目的主机信息交换位置,并把操作字段 op 置为 2,最后把该新构建的数据包发送回去,这就是 ARP 应答。

  在 ARP 中用了一大堆的数据结构和宏来描述上图的结构。

#ifndefETHARP_HWADDR_LEN

#define ETHARP_HWADDR_LEN 6 //以太网物理地址长度

#endif

PACK_STRUCT_BEGIN//我们移植时实现的结构体封装宏

structeth_addr

{

//定义以太网 MAC 地址结构体 eth_addr,禁止编译器自对齐

PACK_STRUCT_FIELD(u8_t

addr[ETHARP_HWADDR_LEN]);

}

PACK_STRUCT_STRUCT;

PACK_STRUCT_END

PACK_STRUCT_BEGIN//定义以太网数据帧首部结构体 eth_hdr,禁止编译器自对齐

structeth_hdr

{

PACK_STRUCT_FIELD(struct eth_addr dest); //以太网目的地址(6 字节)

PACK_STRUCT_FIELD(struct eth_addr src); //以太网源地址(6 字节)

PACK_STRUCT_FIELD(u16_t

type); //帧类型(2 字节)

}

PACK_STRUCT_STRUCT;

PACK_STRUCT_END

//定义以太网帧头部长度宏,其中 ETH_PAD_SIZE 已定义为 0

#defineSIZEOF_ETH_HDR (14 + ETH_PAD_SIZE)

PACK_STRUCT_BEGIN//定义 ARP 数据包结构体 etharp_hdr,禁止编译器自对齐

structetharp_hdr

{

PACK_STRUCT_FIELD(u16_t

hwtype); //硬件类型(2 字节)

PACK_STRUCT_FIELD(u16_t

proto); //协议类型(2 字节)

PACK_STRUCT_FIELD(u16_t

_hwlen_protolen); //硬件+协议地址长度(2 字节)

PACK_STRUCT_FIELD(u16_t

opcode); //操作字段 op(2 字节)

PACK_STRUCT_FIELD(struct eth_addr shwaddr); //发送方 MAC 地址(6 字节)

PACK_STRUCT_FIELD(struct ip_addr2 sipaddr); //发送方 IP 地址(4 字节)

PACK_STRUCT_FIELD(struct eth_addr dhwaddr); //接收方 MAC 地址(6 字节)

PACK_STRUCT_FIELD(struct ip_addr2 dipaddr); //接收方 IP 地址(4 字节)

}

PACK_STRUCT_STRUCT;

PACK_STRUCT_END

#define SIZEOF_ETHARP_HDR 28 //宏,ARP 数据包长度

//宏,包含 ARP 数据包的以太网帧长度

#defineSIZEOF_ETHARP_PACKET (SIZEOF_ETH_HDR +SIZEOF_ETHARP_HDR)

#define ARP_TMR_INTERVAL 5000 //定义 ARP 定时器周期为 5 秒,不同帧类型的宏定义

#defineETHTYPE_ARP 0x0806

#defineETHTYPE_IP 0x0800

//ARP 数据包中 OP 字段取值宏定义

#define ARP_REQUEST 1 //ARP 请求

#defineARP_REPLY 2 //ARP 应答

  发送 ARP 请求数据包的函数叫 etharp_request,它通过调用 etharp_raw 函数来实现,调用后者时,需要为它提供 ARP数据包中各个字段的值,后者直接将各个字段的值填写到在一个 ARP 包中发送(该函数并不知道发送的是 ARP 请求还是 ARP 响应,它只管组装并发送,所以称之为 raw)

//函数功能:根据各个参数字段组织一个 ARP 数据包并发送

//参数 netif:发送 ARP 包的网络接口结构

//参数 ethsrc_addr:以太网帧首部中的以太网源地址值

//参数 ethdst_addr:以太网帧首部中的以太网目的地址值

//参数hwsrc_addr:ARP 数据包中的发送方 MAC 地址

//参数 ipsrc_addr:ARP 数据包中的发送方 IP 地址

//参数 hwdst_addr:ARP 数据包中的接收方 MAC 地址

//参数 ipdst_addr:ARP 数据包中的接收方 IP 地址

//参数 opcode:ARP 数据包中的 OP 字段值,请求ARP为1,应答ARP为2

//注:ARP 数据包中其他字段使用预定义值,例如硬件地址长度为 6,协议地址长度为 4

err_t

etharp_raw(struct netif *netif, const structeth_addr *ethsrc_addr,

const struct eth_addr *ethdst_addr, const structeth_addr *hwsrc_addr,

const struct ip_addr *ipsrc_addr, const structeth_addr *hwdst_addr,

const struct ip_addr *ipdst_addr, constu16_t opcode)

{

  struct pbuf *p; //数据包指针

err_t

result = ERR_OK; //返回结果

u8_t

  struct eth_hdr *ethhdr; //以太网数据帧首部结构体指针

  struct etharp_hdr *hdr; // ARP 数据包结构体指针

//先在内存堆中为 ARP 包分配空间,大小为包含 ARP 数据包的以太网帧总大小

p

= pbuf_alloc(PBUF_RAW, SIZEOF_ETHARP_PACKET, PBUF_RAM);

  if(p == NULL)  //若分配失败则返回内存错误{

    return ERR_MEM;

}

  //到这里,内存分配成功

ethhdr

= p­>payload; // ethhdr 指向以太网帧首部区域

hdr

= (struct etharp_hdr *)((u8_t*)ethhdr +

SIZEOF_ETH_HDR);// hdr 指向 ARP 首部

hdr­>opcode

= htons(opcode); //填写 ARP 包的 OP 字段,注意大小端转换

k

= ETHARP_HWADDR_LEN;     //循环填写数据包中各个 MAC 地址字段

  while(k > 0)

{

k--­­;

hdr­>shwaddr.addr[k]

= hwsrc_addr­>addr[k]; //ARP 头部的发送方 MAC 地址

hdr­>dhwaddr.addr[k]

= hwdst_addr­>addr[k]; //ARP 头部的接收方 MAC 地址

ethhdr­>dest.addr[k]

= ethdst_addr­>addr[k]; //以太网帧首部中的目的地址

ethhdr­>src.addr[k]

= ethsrc_addr­>addr[k]; //以太网帧首部中的以太网源地址

}

hdr­>sipaddr

= *(struct ip_addr2 *)ipsrc_addr; //填写 ARP 头部发送方 IP 地址

hdr­>dipaddr

= *(struct ip_addr2 *)ipdst_addr; //填写 ARP 头部接收方 IP 地址

//下面填充一些固定字段的值

hdr­>hwtype

= htons(HWTYPE_ETHERNET); //ARP 头部的硬件类型为 1,即以太网

hdr­>proto

= htons(ETHTYPE_IP); //ARP 头部的协议类型为0x0800

//设置两个长度字段

hdr­>_hwlen_protolen=htons((ETHARP_HWADDR_LEN<<8)| sizeof(struct ip_addr));

ethhdr­>type

= htons(ETHTYPE_ARP); //以太网帧首部中的帧类型字段,ARP 包

result

= netif­>linkoutput(netif, p); //调用底层数据包发送函数

pbuf_free(p); //释放数据包

p

= NULL;

  return result; //返回发送结果

}

//特殊 MAC 地址的定义,以太网广播地址

const struct eth_addr ethbroadcast = {{0xff,0xff,0xff,0xff,0xff,0xff}};

//该值用于填充 ARP 请求包的接收方MAC 字段,无实际意义

const struct eth_addr ethzero = {{0,0,0,0,0,0}};

//函数功能:发送 ARP 请求

//参数 netif:发送 ARP 请求包的接口结构

//参数 ipaddr:请求具有该 IP 地址主机的 MAC

err_t

etharp_request(struct netif *netif, structip_addr *ipaddr)

{

 //该函数只是简单的调用函数etharp_raw,为函数提供所有相关参数

 return etharp_raw(netif, (structeth_addr *)netif­>hwaddr,ðbroadcast,

(struct eth_addr *)netif­>hwaddr,&netif­>ip_addr,ðzero,ipaddr,ARP_REQUEST);

}

你可能感兴趣的:(嵌入式LwIP ARP协议1)