美文网首页
嵌入式LwIP ARP协议1

嵌入式LwIP ARP协议1

作者: 天心_3a2d | 来源:发表于2019-02-28 21:58 被阅读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,&ethzero,ipaddr,ARP_REQUEST);

    }

    相关文章

      网友评论

          本文标题:嵌入式LwIP ARP协议1

          本文链接:https://www.haomeiwen.com/subject/ymdluqtx.html