草庐IT

LWIP学习记录------ARP协议(1)基础知识

雨落城 2023-03-28 原文

关于LWIP网络协议在嵌入式设备使用越来越广泛,还是要好好学习一下,之前也看过一些资料,总是学了又忘(可能实践的太少了吧!!)。所以本文重新整理一下笔记。共同进步!

(一)ARP基础知识

(1)ARP协议的本质:

ARP协议的基本功能是使用目标主机的IP地址,查询其对应的MAC地址,来进行底层链路上数据包的通信工作。其中,ARP表的功能就是 记录IP地址MAC地址 的对应关系的表格。

​ 在以太网中,ARP数据包与IP数据包是两个独立的部分,他们都是封装在以太网中进行传送的。ARP数据包分为两类一个是ARP请求包,另一个是arp应答包

  • 所谓ARP请求包:就是它是通过 广播 的方式在以太网中进行传输,然后希望能得到目标主机的相应。已知IP地址,请求MAC地址。

  • 显然,ARP应答包的功能,就是收到ARP请求包的主机,会解析请求包的IP地址与本机IP地址做比较,若符号,则返回一个APR应答包,包含了请求的IP地址与对应的MAC地址。这样,源主机就知道目标主机的MAC地址了,并把它加入到自己的ARP表中。

(2)ARP表的建立过程:

  1. 阶段一:当系统初始化时,ARP表为空。主机会广播自己的 <IP,MAC>,这个数据包叫 无汇报 ARP请求包。其他主机收到后,会把这个数据加入到自己的ARP表中。

  2. 阶段二:当主机要发送一个IP数据包时,要先检查自己的ARP表有没有目标主机的MAC地址?要有,好,直接发送。要没有,就要广播一个ARP请求包,然后其他主机接收后,若和自己匹配,则返回一个ARP应答包。源主机就得到了这个IP对应的MAC地址。 如果该表项的缓冲队列上有未发送的数据,相应的数据会被发送出去(后面结合代码详解)

  3. 阶段三:由于网络硬件状态可能随时改变,所以ARP还需要采用一定的定时机制来保证 缓存表中地址的 有效性。要有定时机制。

(二)lwip中关于ARP的数据结构

​ etharp.c/h 文件中 实现了ARP协议的全部数据结构和函数定义。主要是ARP缓存表ARP报文

(1)ARP表

  1. ARP表是由缓存表项(entry)组成。LWIP只描述缓存表项的数据结构叫做 etharp_entry 。单个缓存表项的结构如下:

    struct etharp_entry {
      struct etharp_q_entry *q; 	  //**数据包缓冲队列指针**;
      struct pbuf *q;                 //
      ip_addr_t ipaddr;               //目标IP地址
      struct netif *netif;            //对应的网络接口信息
      struct eth_addr ethaddr;        //目标MAC地址
      u8_t state;                     //该entry 表项的状态
      u8_t ctime;                     //该entry的时间信息
    };
    
    • 第一个成员:"*q" 指向缓存表项的数据包缓存队列。因为当主机发送一个IP数据包时候,发现缓存表中并没有对应的MAC地址,那该怎么办呢?于是在得到对应的MAC地址之前,主机会新建立一个缓存表项,然后把要发送的数据 挂在这个缓存队列指针上。当接收到ARP应答包后,再发送出去。

    struct etharp_q_entry 结构是一个链表,包含一个*next 指针和一个 指向pbuf 数据包的指针。系统为 etharp_q_entry 结构开辟了一些 MEMP_APR_QUEUE类型的内存池。


    • state有四种状态,分别为empty 状态、Pending状态、stable状态、stable状态且发送了一个ARP请求。

      初始时,是以数组形式定义了10条ARP表项。这都是空的,没有记录任何信息。

      enum etharp_state {
        ETHARP_STATE_EMPTY = 0,        //empty状态
        ETHARP_STATE_PENDING,
        ETHARP_STATE_STABLE,
        ETHARP_STATE_STABLE_REREQUESTING
      };
      
      • pending状态:不稳定状态,此时只是找到了IP地址,正在寻找MAC地址;

      • stable状态:当Pending状态的表项接收到ARP应答后,就会变成stable稳定状态

      • stabl_rerequesting状态:系统定时更新ARP表项,当时间到了之后,会向目标主机发送一个ARP请求,来验证表项的有效性,在验证期间就会变成 stable_rerequesting状态。


    • ctime 成员:用来计时,系统会删除到时的 ARP表项。内核每5秒一次调用eth_tmr()函数,他会为每个 ARP表项 的ctime 值加1,当改值大于系统规定的值时,就会产生相应的动作。

      void etharp_tmr(void)
      {
        u8_t i;
      
        for (i = 0; i < ARP_TABLE_SIZE; ++i) {
          u8_t state = arp_table[i].state;
          if (state != ETHARP_STATE_EMPTY)  //表项不为空,说明被使用。
          {
            arp_table[i].ctime++;
            if ((arp_table[i].ctime >= ARP_MAXAGE) ||             //表项大于生存时间20分钟
                ((arp_table[i].state == ETHARP_STATE_PENDING)  &&  //达到pending 最大时间10s
                 (arp_table[i].ctime >= ARP_MAXPENDING))) 
            {
              	etharp_free_entry(i);                          //删除表项
            }
            else if (arp_table[i].state == ETHARP_STATE_STABLE_REREQUESTING)
            {
              /* Reset state to stable, so that the next transmitted packet will
                 re-send an ARP request. */
              arp_table[i].state = ETHARP_STATE_STABLE;
            }
      
          }
        }
      }
      

      (2)ARP报文

    ​ 1. 前面提到的ARP请求和应答是组装在一个ARP数据包中发送的。如下图所示是一个APR包的组成;

    以太网目的地址(MAC) 以太网源地址(MAC) 帧类型 硬件协议 协议类型 硬件地址长度 协议地址长度 OP 发送方以太网地址 发送方IP 接收方以太网地址 接收方IP
    6字节 6 2 2 2 1 1 2 6 4 6 4

    前面 14个字节是以太网首部,后面28个字节是ARP数据包。

    • 帧类型:对于ARP包是0X806,对于IP包是0X0800。

    • 硬件协议:发送方想要知道的硬件接口类型,对于以太网是 1

    • 协议类型:表示要映射的协议地址类型,为0X0800,代表映射为IP地址

    • 操作字段op:表示数据包类型。ARP请求包为 1,ARP应答包为2.

    • 后面字段含义较为明显,不再赘述。

    以太网首部用结构eth_hdr表示

    struct eth_hdr {
      PACK_STRUCT_FIELD(struct eth_addr dest);  //以太网目的地址,6字节
      PACK_STRUCT_FIELD(struct eth_addr src);   //以太网源地址,6字节。
      PACK_STRUCT_FIELD(u16_t type);            //帧类型
    } PACK_STRUCT_STRUCT;
    

    (PACK_STRUCT_FIELD宏定义来禁止编译器自动对齐)

    ARP数据包部分用结构etharp_hdr 表示:

    struct etharp_hdr {
      PACK_STRUCT_FIELD(u16_t hwtype);
      PACK_STRUCT_FIELD(u16_t proto);
      PACK_STRUCT_FIELD(u8_t  hwlen);
      PACK_STRUCT_FIELD(u8_t  protolen);
      PACK_STRUCT_FIELD(u16_t opcode);
      PACK_STRUCT_FIELD(struct eth_addr shwaddr);
      PACK_STRUCT_FIELD(struct ip_addr2 sipaddr);
      PACK_STRUCT_FIELD(struct eth_addr dhwaddr);
      PACK_STRUCT_FIELD(struct ip_addr2 dipaddr);
    } PACK_STRUCT_STRUCT;
    

    1. 结合源码,来看看ARP请求包是怎么发送出去的。ARP请求包是 调用etharp_requeset()实现。

      etharp_request(struct netif *netif, ip_addr_t *ipaddr)
      {
        return etharp_raw(netif, (struct eth_addr *)netif->hwaddr, &ethbroadcast,
                          (struct eth_addr *)netif->hwaddr, &netif->ip_addr, &ethzero,
                          ipaddr, ARP_REQUEST);
      }
      

      好家伙,里面原来是调用是了etharp_raw()。那我们看看etharp_raw()具体实现。

      重头戏来了!

      /**
      
       * @param netif       the lwip network interface on which to send the ARP packet
       * @param ethsrc_addr the source MAC address for the ethernet header
       * @param ethdst_addr the destination MAC address for the ethernet header
       * @param hwsrc_addr the source MAC address for the ARP protocol header
       * @param ipsrc_addr the source IP address for the ARP protocol header
       * @param hwdst_addr the destination MAC address for the ARP protocol header
       * @param ipdst_addr the destination IP address for the ARP protocol header
       * @param opcode the type of the ARP packet
       * @return ERR_OK if the ARP packet has been sent
       *         ERR_MEM if the ARP packet couldn't be allocated
       *         any other err_t on failure
       */
      
      err_t etharp_raw(struct netif *netif, const struct eth_addr *ethsrc_addr,
                 const struct eth_addr *ethdst_addr,
                 const struct eth_addr *hwsrc_addr, const ip_addr_t *ipsrc_addr,
                 const struct eth_addr *hwdst_addr, const ip_addr_t *ipdst_addr,
                 const u16_t opcode)
      {
        struct pbuf *p;
        err_t result = ERR_OK;
        struct eth_hdr *ethhdr;  //以太网帧首部结构体指针
        struct etharp_hdr *hdr;  //ARP数据包结构体指针
      
        /* 先在内存堆中,为ARP包分配空间 */
        p = pbuf_alloc(PBUF_RAW, SIZEOF_ETHARP_PACKET, PBUF_RAM); //14+28个字节
        
          /* 若分配失败,返回err */
        if (p == NULL) {
          return ERR_MEM;
        }
      
      
        ethhdr = (struct eth_hdr *)p->payload;        //ethhdr指向以太网帧首部区域
        hdr = (struct etharp_hdr *)((u8_t*)ethhdr + SIZEOF_ETH_HDR); //hdr 指向arp数据包首部区域
        hdr->opcode = htons(opcode);                //填写op字段。
          
         下面是继续填写数据包中字段:
             
        /* Write the ARP MAC-Addresses */
        ETHADDR16_COPY(&hdr->shwaddr, hwsrc_addr);
        ETHADDR16_COPY(&hdr->dhwaddr, hwdst_addr);
          
        /* Write the Ethernet MAC-Addresses */  
        ETHADDR16_COPY(&ethhdr->src, ethsrc_addr);
        IPADDR2_COPY(&hdr->sipaddr, ipsrc_addr);
        IPADDR2_COPY(&hdr->dipaddr, ipdst_addr);
      
        hdr->hwtype = PP_HTONS(HWTYPE_ETHERNET);
        hdr->proto = PP_HTONS(ETHTYPE_IP);
        /* set hwlen and protolen */
        hdr->hwlen = ETHARP_HWADDR_LEN;
        hdr->protolen = sizeof(ip_addr_t);
      
        ethhdr->type = PP_HTONS(ETHTYPE_ARP);  //以太网帧类型ARP包
        
          /* 发送!!send ARP query */
        result = netif->linkoutput(netif, p);
        /* 释放!free ARP query packet */
        pbuf_free(p);
        p = NULL;
        return result;
      }
      

    到此为止,总结就是 先分配内存,然后填充这个内存中各个字段的数据信息(保存在pbuf 中),然后再调用netif->linkoutput()底层数据包发送函数,最后再释放掉pbuf。

有关LWIP学习记录------ARP协议(1)基础知识的更多相关文章

  1. ruby - Sinatra:运行 rspec 测试时记录噪音 - 2

    Sinatra新手;我正在运行一些rspec测试,但在日志中收到了一堆不需要的噪音。如何消除日志中过多的噪音?我仔细检查了环境是否设置为:test,这意味着记录器级别应设置为WARN而不是DEBUG。spec_helper:require"./app"require"sinatra"require"rspec"require"rack/test"require"database_cleaner"require"factory_girl"set:environment,:testFactoryGirl.definition_file_paths=%w{./factories./test/

  2. ruby-on-rails - Rails 5 Active Record 记录无效错误 - 2

    我有两个Rails模型,即Invoice和Invoice_details。一个Invoice_details属于Invoice,一个Invoice有多个Invoice_details。我无法使用accepts_nested_attributes_forinInvoice通过Invoice模型保存Invoice_details。我收到以下错误:(0.2ms)BEGIN(0.2ms)ROLLBACKCompleted422UnprocessableEntityin25ms(ActiveRecord:4.0ms)ActiveRecord::RecordInvalid(Validationfa

  3. postman接口测试工具-基础使用教程 - 2

    1.postman介绍Postman一款非常流行的API调试工具。其实,开发人员用的更多。因为测试人员做接口测试会有更多选择,例如Jmeter、soapUI等。不过,对于开发过程中去调试接口,Postman确实足够的简单方便,而且功能强大。2.下载安装官网地址:https://www.postman.com/下载完成后双击安装吧,安装过程极其简单,无需任何操作3.使用教程这里以百度为例,工具使用简单,填写URL地址即可发送请求,在下方查看响应结果和响应状态码常用方法都有支持请求方法:getpostputdeleteGet、Post、Put与Delete的作用get:请求方法一般是用于数据查询,

  4. 软件测试基础 - 2

    Ⅰ软件测试基础一、软件测试基础理论1、软件测试的必要性所有的产品或者服务上线都需要测试2、测试的发展过程3、什么是软件测试找bug,发现缺陷4、测试的定义使用人工或自动的手段来运行或者测试某个系统的过程。目的在于检测它是否满足规定的需求。弄清预期结果和实际结果的差别。5、测试的目的以最小的人力、物力和时间找出软件中潜在的错误和缺陷6、测试的原则28原则:20%的主要功能要重点测(eg:支付宝的支付功能,其他功能都是次要的)80%的错误存在于20%的代码中7、测试标准8、测试的基本要求功能测试性能测试安全性测试兼容性测试易用性测试外观界面测试可靠性测试二、质量模型衡量一个优秀软件的维度①功能性功

  5. LC滤波器设计学习笔记(一)滤波电路入门 - 2

    目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称

  6. CAN协议的学习与理解 - 2

    最近在学习CAN,记录一下,也供大家参考交流。推荐几个我觉得很好的CAN学习,本文也是在看了他们的好文之后做的笔记首先是瑞萨的CAN入门,真的通透;秀!靠这篇我竟然2天理解了CAN协议!实战STM32F4CAN!原文链接:https://blog.csdn.net/XiaoXiaoPengBo/article/details/116206252CAN详解(小白教程)原文链接:https://blog.csdn.net/xwwwj/article/details/105372234一篇易懂的CAN通讯协议指南1一篇易懂的CAN通讯协议指南1-知乎(zhihu.com)视频推荐CAN总线个人知识总

  7. 深度学习部署:Windows安装pycocotools报错解决方法 - 2

    深度学习部署:Windows安装pycocotools报错解决方法1.pycocotools库的简介2.pycocotools安装的坑3.解决办法更多Ai资讯:公主号AiCharm本系列是作者在跑一些深度学习实例时,遇到的各种各样的问题及解决办法,希望能够帮助到大家。ERROR:Commanderroredoutwithexitstatus1:'D:\Anaconda3\python.exe'-u-c'importsys,setuptools,tokenize;sys.argv[0]='"'"'C:\\Users\\46653\\AppData\\Local\\Temp\\pip-instal

  8. ES基础入门 - 2

    ES一、简介1、ElasticStackES技术栈:ElasticSearch:存数据+搜索;QL;Kibana:Web可视化平台,分析。LogStash:日志收集,Log4j:产生日志;log.info(xxx)。。。。使用场景:metrics:指标监控…2、基本概念Index(索引)动词:保存(插入)名词:类似MySQL数据库,给数据Type(类型)已废弃,以前类似MySQL的表现在用索引对数据分类Document(文档)真正要保存的一个JSON数据{name:"tcx"}二、入门实战{"name":"DESKTOP-1TSVGKG","cluster_name":"elasticsear

  9. ruby-on-rails - 事件记录 : Select max of limit - 2

    我正在尝试将以下SQL查询转换为ActiveRecord,它正在融化我的大脑。deletefromtablewhereid有什么想法吗?我想做的是限制表中的行数。所以,我想删除少于最近10个条目的所有内容。编辑:通过结合以下几个答案找到了解决方案。Temperature.where('id这给我留下了最新的10个条目。 最佳答案 从您的SQL来看,您似乎想要从表中删除前10条记录。我相信到目前为止的大多数答案都会如此。这里有两个额外的选择:基于MurifoX的版本:Table.where(:id=>Table.order(:id).

  10. Ruby 守护进程导致 ActiveRecord 记录器 IOError - 2

    我目前正在用Ruby编写一个项目,它使用ActiveRecordgem进行数据库交互,我正在尝试使用ActiveRecord::Base.logger记录所有数据库事件具有以下代码的属性ActiveRecord::Base.logger=Logger.new(File.open('logs/database.log','a'))这适用于迁移等(出于某种原因似乎需要启用日志记录,因为它在禁用时会出现NilClass错误)但是当我尝试运行包含调用ActiveRecord对象的线程守护程序的项目时脚本失败并出现以下错误/System/Library/Frameworks/Ruby.frame

随机推荐