uip协议分析

uip协议分析UIP 协议多用于嵌入式产品 nbsp nbsp nbsp nbsp 结合如 CP2200 芯片的网卡芯片 组成嵌入式网卡 硬件提供能力 UIP 提供的是策略 nbsp nbsp nbsp nbsp 由上往下逐步封装用户的数据 如 nbsp nbsp nbsp nbsp 应用层传输层网络层数据链路层 物理层 nbsp nbsp nbsp nbsp 应用数据 TCP 封装头部 IP 封装头部 mac 封装 尾部 发送 nbsp nbsp nbsp nbsp 任何的事

IP协议多用于嵌入式产品。

     结合如CP2200芯片的网卡芯片,组成嵌入式网卡,硬件提供能力,UIP提供的是策略。
     由上往下逐步封装用户的数据,如:
     应用层———-传输层——–网络层——数据链路层—–物理层
     应用数据—TCP封装头部—IP封装头部—–mac封装+尾部—–发送 
    任何的事物需要经过一定的初始阶段,在UIP协议里面通过uip_init()来初始化。
    在uip_init()函数里面主要工作是:
     1. 将uip_state结构体全部清零。
     2. 初始化用于TCP链接的uip_conn结构体,将连接状态置为close。
     3. 设置用于TCP链接的端口lastport = 4096; 应该是最大的端口号,待查证。
     4. 如果定义了UDP,同样进行初始化。

  1. void uip_init(void) {


  2.     // clean statistics

  3.     char* ptr= (char*) &uip_stat;

  4.     for (int i = 0; i<sizeof (uip_stat); i++) {


  5.         ptr[i] = 0;   

  6.     }



  7.     for (= 0; c < UIP_LISTENPORTS; ++c) {


  8.         uip_listenports[c] = 0;

  9.     }

  10.     for (= 0; c < UIP_CONNS; ++c) {


  11.         uip_conns[c].tcpstateflags = UIP_CLOSED;

  12.     }

  13.     lastport = 4096;



  14. #if UIP_UDP

  15.     for (= 0; c < UIP_UDP_CONNS; ++c) {


  16.         uip_udp_conns[c].lport = 0;

  17.     }

  18. #endif /* UIP_UDP */





  19.     /* IPv4 initialization. */

  20. #if UIP_FIXEDADDR == 0

  21.     /* uip_hostaddr[0] = uip_hostaddr[1] = 0;*/

  22. #endif /* UIP_FIXEDADDR */



  23. }

   同样,我在ourdev.cn上下载了,一份总结,一点一点上传,感谢ourdev.cn。

   uip_arp_init(); arp协议的初始化,其中进行的是构造arp协议的缓存。
   在配置UIP协议的时候要主要配置超时。
   // 摘自uip协议包main.c

  1. struct timer periodic_timer, arp_timer;

  2. timer_set(&periodic_timer, CLOCK_SECOND / 2);

  3. timer_set(&arp_timer, CLOCK_SECOND * 10);

   还要进行的配置是,比如配置主机地址,ip地址,还有掩码,以太网mac地址等信息,或者配置dhcp。
   这些配置完成之后,进入协议的主循环,接受,和发送等等的过程了。
   要应用到实际的使用中,还需要结合硬件,比如CP2200芯片,使用过程中,需要有接收,和发送函
   数,这个需要自己实现,循环的流程如下:
  

  1. while(1)
  2.  {


  3.     uip_len = tapdev_read();  // 接收的函数

  4.     if(uip_len > 0)
  5.     {


  6.       if(BUF>type == htons(UIP_ETHTYPE_IP))
  7.       {


  8.           uip_arp_ipin();

  9.           uip_input();  // 这个是实际的从上往下封装包的函数

  10.          /* If the above function invocation resulted in data that

  11.           should be sent out on the network, the global variable

  12.            uip_len is set to a value > 0. */

  13.           if(uip_len > 0)
  14.           {


  15.               uip_arp_out();

  16.               tapdev_send(); // 发送的实际函数 

  17.           }

  18.       } 
  19.       else if(BUF>type == htons(UIP_ETHTYPE_ARP))
  20.       {


  21.           uip_arp_arpin();

  22.           /* If the above function invocation resulted in data that

  23.           should be sent out on the network, the global variable

  24.           uip_len is set to a value > 0. */

  25.           if(uip_len > 0) 

  26.           {


  27.            tapdev_send();

  28.           }

  29.       }



  30.     } 
  31.     else if(timer_expired(&periodic_timer))
  32.      {


  33.          timer_reset(&periodic_timer);

  34.          for(= 0; i < UIP_CONNS; i++)
  35.          {


  36.              uip_periodic(i);

  37.              /* If the above function invocation resulted in data that

  38.              should be sent out on the network, the global variable

  39.              uip_len is set to a value > 0. */

  40.              if(uip_len > 0)
  41.              {


  42.                  uip_arp_out();

  43.                  tapdev_send();

  44.              }

  45.         }



  46. #if UIP_UDP

  47.       for(= 0; i < UIP_UDP_CONNS; i++)
  48.        {


  49.            uip_udp_periodic(i);

  50.            /* If the above function invocation resulted in data that

  51.             should be sent out on the network, the global variable

  52.              uip_len is set to a value > 0. */

  53.            if(uip_len > 0)
  54.             {


  55.                 uip_arp_out();

  56.                 tapdev_send();

  57.            }

  58.       }

  59. #endif /* UIP_UDP */



  60.       /* Call the ARP timer function every 10 seconds. */

  61.       if(timer_expired(&arp_timer))
  62.        {


  63.            timer_reset(&arp_timer);

  64.            uip_arp_timer();

  65.       }

  66.     }

  67.   }
1. 网卡如何与uIP协议交互(包括arp, icmp等)
   在我看来,CP2200提供了读取网络数据的能力,而UIP提供的是一种如何封装网路数据的策略。对用户数
据不断封装,最后交给CP2200发送,在UIP协议中有一个uip_buf缓冲用来接收和发送数据。
uip协议分析
(转自:维库电子开发网>电子通列表 > 协议栈)

   ARP请求和应答
在UIP协议中定义了一个ARP的struct。维护了一张


缓存表。



  1. struct arp_entry {


  2. u16_t ipaddr[2];  // 保存的是IP地址

  3. struct uip_eth_addr ethaddr; // 保存的是mac地址

  4. u8_t time;       // 缓存更新时间

  5. };

    
ARP请求发送函数:void uip_arp_out(void)

* 为传出的IP包添加以太网头并看是否需要发送ARP请求. 
* 此函数应该在发送IP包时调用,它会检查IP包的目的IP地址,看看以太网应该使用什么目的MAC地址.
* 如果目的IP地址是在局域网中(由IP地址与子网掩码的与逻辑决定),函数就会从ARP缓存表中查找有
* 无对应项.若有,就取对应的MAC地址,加上以太网头,并返回,否则uip_buf[]中的数据包会被替换成一个
* 目的IP在址的ARP请求.原来的IP包会被简单的仍掉,此函数假设高层协议(如TCP)会最终重传扔掉的包.
* 如果目标IP地址并非一个局域网IP,则会使用默认路由的IP地址.
* uip_len.函数返回时,uip_buf[]中已经有了一个包,其长度由uip_len指定.

  1. void uip_arp_out(void)

  2. {


  3.   struct arp_entry *tabptr=0;

  4.   // 在ARP表中找到目的IP地址,构成以太网头.如果目的IP地址不在局域网中,则使用默认路由的IP.
  5.   // 如果ARP表中找不到,则将原来的IP包替换成一个ARP请求.  

  6.   // 首先检查目标是不是广播
  7.   if(uip_ipaddr_cmp(IPBUF>destipaddr, broadcast_ipaddr))
  8.   {


  9.     memcpy(IPBUF>ethhdr.dest.addr, broadcast_ethaddr.addr, 6);

  10.   } 

  11.   else 
  12.   {


  13.         /* 检查目标地址是否在局域网内 */

  14.         if(!uip_ipaddr_maskcmp(IPBUF>destipaddr, uip_hostaddr, uip_netmask))
  15.         {


  16.               /* 目的地址不在局域网内,所以保用默认路由器的地址来确在MAC地址 */

  17.               uip_ipaddr_copy(ipaddr, uip_draddr);

  18.         } 

  19.         else 

  20.         {


  21.             /* 否则,使用目标IP地址 */

  22.           uip_ipaddr_copy(ipaddr, IPBUF>destipaddr);

  23.         }

  24.         //这里遍历表,对比目的IP与ARP缓存表中的IP.
  25.         for(= 0; i < UIP_ARPTAB_SIZE; ++i)
  26.         {


  27.              tabptr = &arp_table[i];

  28.              if(uip_ipaddr_cmp(ipaddr, tabptr>ipaddr))
  29.              {


  30.                  break;

  31.              }

  32.          }

  33.          
  34.          if(== UIP_ARPTAB_SIZE)
  35.          {


  36.             /* 如果遍历到头没找到,将原IP包替换为ARP请求并返回 */

  37.               memset(BUF>ethhdr.dest.addr, 0xff, 6);  // 以太网目的地址

  38.               memset(BUF>dhwaddr.addr, 0x00, 6);      // 目的以太网地址
  39.               memcpy(BUF>ethhdr.src.addr, uip_ethaddr.addr, 6);  //

  40.               memcpy(BUF>shwaddr.addr, uip_ethaddr.addr, 6);  // 源以太网地址
  41.     

  42.               uip_ipaddr_copy(BUF>dipaddr, ipaddr);  // 目的IP地址
  43.               uip_ipaddr_copy(BUF>sipaddr, uip_hostaddr);  // 源IP地址

  44.               BUF>opcode = HTONS(ARP_REQUEST);  // ARP 请求

  45.               BUF>hwtype = HTONS(ARP_HWTYPE_ETH);  // 硬件类型 值为1

  46.               BUF>protocol = HTONS(UIP_ETHTYPE_IP); // 协议类型 值为0x8000表示IP地址

  47.               BUF>hwlen = 6;  

  48.               BUF>protolen = 4

  49.               BUF>ethhdr.type = HTONS(UIP_ETHTYPE_ARP);  


  50.               uip_appdata = &uip_buf[UIP_TCPIP_HLEN + UIP_LLH_LEN];

  51.               uip_len = sizeof(struct arp_hdr);

  52.               return;

  53.           }



  54.         // 如果是在局域网中,且在ARP缓存中找到了(如果没找到进行不到这一步,在上面就返回了),则构建以太网头

  55.          memcpy(IPBUF>ethhdr.dest.addr, tabptr>ethaddr.addr, 6);

  56.   }

  57.   memcpy(IPBUF>ethhdr.src.addr, uip_ethaddr.addr, 6);

  58.   IPBUF>ethhdr.type = HTONS(UIP_ETHTYPE_IP);

  59.   uip_len += sizeof(struct uip_eth_hdr);

  60. }
网卡如何与UIP协议交互(包括arp, icmp等) 
接上文

    接下来看看UIP如何处理ARP应答的情况,在主循环中一段代码:


  1. else if(BUF>type == htons(UIP_ETHTYPE_ARP)) 
  2. {
       

  3.     uip_arp_arpin();  // 处理ARP应答

  4.     /* If the above function invocation resulted in data that

  5.        should be sent out on the network, the global variable

  6.        uip_len is set to a value > 0. */
  7.    // 如果上面的函数返回的结果需要发送到网络上,那么uip_len就必须设置 > 0

  8.     if(uip_len > 0) 

  9.     {


  10.         network_device_send(); // 回应ARP包

  11.     }

  12. }



在uip_arp_arpin()函数中主要是处理ARP应答。
    这个函数是在设备接收到ARP包时,由驱动程序调用的.如果收到是ARP包是一个对本地主机上次发送的ARP请求的应答,那么就从包中取得自己想要的主机的MAC地址,加入自己的ARP缓存表中.如果收到是一个ARP请求,那就把自己的MAC地址打包成一个ARP应答,发送给请求的主机.看代码uip_arp.c的254行:


  1. void uip_arp_arpin(void)

  2. {


  3.   if(uip_len < sizeof(struct arp_hdr))
  4.   {


  5.     uip_len = 0;

  6.     return;

  7.   }

  8.   uip_len = 0;

  9.   

  10.   switch(BUF>opcode)  // 操作码

  11.   {


  12.       case HTONS(ARP_REQUEST):

  13.       // 如果是一个ARP请求,则发送应答
  14.       if(uip_ipaddr_cmp(BUF>dipaddr, uip_hostaddr))
  15.       {


  16.           // 首先,我们将发送请求的主机注册到ARP缓存表中,因为我们很
  17.           // 可能要跟它要有更多的交流
  18.           uip_arp_update(BUF>sipaddr, &BUF>shwaddr);  
  19.       

  20.           // 回应的操作码是 2.

  21.           BUF>opcode = HTONS(2);

  22.           // 将收到的ARP包的发送端以太网地址,变为目的以太网地址

  23.           memcpy(BUF>dhwaddr.addr, BUF>shwaddr.addr, 6);

  24.           // 将自己的以太网地址,赋值给ARP包的发送端以太网地址

  25.           memcpy(BUF>shwaddr.addr, uip_ethaddr.addr, 6);
  26.           // 对应以太网源地址

  27.           memcpy(BUF>ethhdr.src.addr, uip_ethaddr.addr, 6);
  28.           // 对应以太网目的地址

  29.           memcpy(BUF>ethhdr.dest.addr, BUF>dhwaddr.addr, 6);

  30.       

  31.           BUF>dipaddr[0] = BUF>sipaddr[0];

  32.           BUF>dipaddr[1] = BUF>sipaddr[1];

  33.           BUF>sipaddr[0] = uip_hostaddr[0];

  34.           BUF>sipaddr[1] = uip_hostaddr[1];



  35.           BUF>ethhdr.type = HTONS(UIP_ETHTYPE_ARP);

  36.           uip_len = sizeof(struct arp_hdr);

  37.      }

  38.      break;
  39.     // 如果收到的是一个ARP应答,而且也是我们所要的应答的话,就插件
  40.     // 并更新ARP缓存表
  41.     case HTONS(ARP_REPLY):  

  42.     if(uip_ipaddr_cmp(BUF>dipaddr, uip_hostaddr))
  43.     {


  44.       uip_arp_update(BUF>sipaddr, &BUF>shwaddr);

  45.     }

  46.     break;

  47.   }



  48.   return;

  49. }

还有一个是ARP周期处理函数,在主循环中代码如下:


// 每10秒运行一次

if(timer_expired(&arp_timer)) 
{

    timer_reset(&arp_timer); 





    uip_arp_timer();
}

具体的代码:

  1. void uip_arp_timer(void)

  2. {


  3.   struct arp_entry *tabptr;

  4.   ++arptime;    // 这个是个全局变量,结合uip_arp_update来更新缓存表
  5.   for(= 0; i < UIP_ARPTAB_SIZE; ++i)
  6.   {


  7.     tabptr = &arp_table[i];
  8.     // 把超过20分钟都没有更新的项扔掉

  9.     if((tabptr>ipaddr[0] | tabptr>ipaddr[1]) != 0 &&

  10.        arptime  tabptr>time >= UIP_ARP_MAXAGE)
  11.     {


  12.       memset(tabptr>ipaddr, 0, 4);

  13.     }

  14.   }

  15. }

下面说说网卡如何与UIP协议交互中的ICMP情况,首先必须知道什么叫ICMP,在百科上的介绍是:

   ———————————-ICMP——————————————————
 
   ICMP是(Internet Control Message Protocol)Internet控制报文协议。它是TCP/IP协议族的一个子协议,用于在IP主机、路由器之间传递控制消息。控制消息是指网络通不通、主机是否可达、路由是否可用等网络本身的消息。这些控制消息虽然并不传输用户数据,但是对于用户数据的传递起着重要的作用。
    ICMP协议是一种面向无连接的协议,用于传输出错报告控制信息。它是一个非常重要的协议,它对于网络安全具有极其重要的意义。   
    它是TCP/IP协议族的一个子协议,属于网络层协议,主要用于在主机与路由器之间传递控制信息,包括报告错误、交换受限控制和状态信息等。当遇到IP数据无法访问目标、IP路由器无法按当前的传输速率转发数据包等情况时,会自动发送ICMP消息。
    ICMP 提供一致易懂的出错报告信息。发送的出错报文返回到发送原数据的设备,因为只有发送设备才是出错报文的逻辑接受者。发送设备随后可根据ICMP报文确定发生错误的类型,并确定如何才能更好地重发失败的数据包。但是ICMP唯一的功能是报告问题而不是纠正错误,纠正错误的任务由发送方完成。
  我们在网络中经常会使用到ICMP协议,比如我们经常使用的用于检查网络通不通的Ping命令(Linux和Windows中均有),这个“Ping”的过程实际上就是ICMP协议工作的过程。还有其他的网络命令如跟踪路由的Tracert命令也是基于ICMP协议的。
————————————-ICMP—————————————————- 




  
    实现ICMP网络控制报文协议时,只实现echo(回响)服务。uIP在生成回响报文时并不重新分配存储器空间,而是直接修改echo请求报文来生成回响报文。将ICMP类型字段从“echo”类型改变成 “echo reply”类型,重新计算校验和修改校验和字段。

  1. #define ICMPBUF ((struct uip_icmpip_hdr *)&uip_buf[UIP_LLH_LEN])

    主要的处理过程在uip_process()函数中,UIP同样将收到的ICMP的数据包放到uip_buf中。




  写到这里本来不想再写下去了,不过还是有些没明白的地方。比如,我只看到了设备接收对方发过来的数据包,但是,UIP如何将数据包发送出去?还有那个uip_process()函数好长,很多没弄明白,今天继续翻看了另外一些代码,发现一个宏UIP_APPCALL。
    都是自己的疏忽,在uip文档里面搜索UIP_APPCALL就提到了,不同的事件调用不懂的函数,UIP_APPCALL被定义成一个宏,当要用到应用层序的时候,就将UIP_APPCALL定义成相应的函数,比如:
    example1_app应用函数: 



  1. void example1_app(void)

  2.  {


  3.      if(uip_newdata() || uip_rexmit())
  4.      {


  5.          uip_send(“ok\n”, 3);

  6.      }

  7. }

    UIP_APPCALL宏

  1. #define UIP_APPCALL example2_app

    显然,就是将example1_app定义成UIP_APPCALL宏来使用,这样在uip_process()就可以直接使用UIP_APPCALL了。
    自己定义的example1_app函数中使用UIP应用层函数,就能在网络上交换数据了。





 总算一点一点看完了UIP协议,期间各大网站,各位coder的代码翻了好几个,在此感谢。
    首先,应清楚UIP协议在代码中扮演的是什么角色,我觉得流水线一样,将应用层的数据,通过流水线不断包装。
    TCP—IP—MAC—>发送。
    uip_buf就是实际的原料了。在UIP协议就使用了一个缓冲区。其实是char类型的数组,然后各种strut将它转换成自己的结构,如:




  1.     #define BUF ((struct uip_eth_hdr *)&uip_buf[0])

  2.     #define ICMPBUF ((struct icmpip_hdr *)&uip_buf[UIP_LLH_LEN])

  3.     #define UDPBUF ((struct uip_udpip_hdr *)&uip_buf[UIP_LLH_LEN])

    
所有的数据都保存在uip_buf数组中。通过uip_process函数来封装。
    uip_len是uip_buf接收到的数据的长度。
    具体的发送到网络上的函数需要根据自己的平台具体实现,整体的循环不需要改动多少。


































版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/178223.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月26日 下午5:45
下一篇 2026年3月26日 下午5:45


相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号