uIP是什么?
uIP是一款TCP/IP协议套件,该套件为使用者的网络会话提供网络协议栈支持。
我曾尝试将uIP提供的TCP/IP协议栈移植到单片机中,当然在只有单片机而没有网卡(MAC+PHY)的情况下,这项工作是不能完成的。在做这项工作之前,我也在网络上搜索整理了很多资料,用以充实自己的知识体系。单方面看,TCP/IP协议栈是一组纵向生长的协议集合;多方面看,TCP/IP协议栈是实现双方正确,有效交互的一种机制,它实现了等层协议实体之间的透明传输。而uIP是一款轻型TCP/IP协议栈,设计目标是为无法运行开源或者付费系统的广大接入终端提供一种网络接入交互机制。鉴于国内几乎没有开发协议栈的工作,而国外一款比较好的协议栈大概在数十万美金,对于国内的很多软件开发,设备生产的公司来说,无疑是有点奢侈,因为我们的设备不需要对网络协议的完全支持。对接入设备而言,“通道”和对“通道上的流”的规则,是他们的关注重点。但是,就是这么简单的需求,对网络协议的最小化支持的软件,国内几乎无有。
以单片机为例,一个简单的网络模块就是给单片机外接资源增加一个网络控制器—一般是dm9000X或者marvell等厂家的以太网控制器,通过编程实现网卡驱动后(一般u-boot源码中都提供了这些厂家芯片的裸编程序),封装一个初始化、一个接收和发送接口,就可以同uIP整合,完成运行在单片机上的网络协议处理模块了。UIP对基本的网络协议支持的比较好,当然一个人不是一个团队,开发协议栈还是相当有挑战性的。我想将自己调试过程中遇到的问题,以及对uIP为什么这样做的理解,沿着我的思路做一个解说。笔墨浅显,仅供自己和需要的人参考。做这一系列的分析,前提需要一个主题框架。但是一时又想不起来该怎么系统的完成这些任务,所以编边写边看,边看边修改。
二层请求协议:地址解析协议-ARP。ARP/RARP作为一个二层协议,提供在局域网内请求对端MAC(ARP)地址或者逆向请求对端IP(RARP)地址的协议。请求和响应双方都需要对此类报文实现解析并提供机制完成整个过程。
uIP 中和 ARP 模块有关的方法介绍
uip_arp_init(): 初始化arp_table,arp_table是ARP缓存表,是网络二层维护的三元组地址表。
uip_arp_timer(): ARP缓存超时处理,删除存在时间大于UIP_ARP_MAXAGE的记录,更新ARP缓存表。
uip_arp_update(sip, smac): ARP缓存更新方法。
uip_arp_ipin(): 对IP报文的二、三层解封装入口。uIP收到IP包时,先经过该接口解析IP报文,学习以太网包的地址信息,更新ARP缓存。
uip_arp_ipin(): 对收到的ARP报文的处理。
uip_arp_out(): 对IP报文的二、三层封装入口。当uIP要发送IP报文时,先通过该接口添加二、三层头部信息。也包括了无法找到目的时候,发送广播请求ARP报文。
uIP 对 ARP 报文的处理过程
首先申请对端MAC地址的主机在局域网内广播ARP request报文,报文如图,包含了目的地址(全0xFF的广播地址)、发送者的MAC地址、二层封装协议类型(ARP是0x0806)、请求的硬件参数(0x01对应请求MAC地址)、请求参数的长度、opcode(报文类型,request或者reply)、请求者的MAC和IP、请求目标的MAC(需要目的主机填写)、请求目标的IP地址以及一些padding,添加校验后封装成以太网帧在局域网中传输;
收到请求广播的主机,通过解析报文,发现是在请求自己的MAC地址,就将该请求报文复制一份,修改目的MAC地址(发送者的),将自己的MAC 地址填充到源地址和发送者MAC域,修改opcode为0x02,表示对对端的响应,将自己的IP地址填充到发送者IP地址域,校验后封装成以太网帧,此时是单播报文,发送出去。如果没请求自己的MAC地址,则忽略;
收到响应ARP的主机,通过二层协议解析。该主机维护一个ARP缓存表,缓存表的每一项是一个三元组(IP,MAC,TIME),记录和本机有联系的主机的IP地址,MAC地址,以及记录生成的时间(时间提供给老化定时器)。主机查ARP缓存表,寻找对应的匹配项(存储和查找可以通过比较复杂的HASH实现)。找到后,更新TIME,没找到,则在缓存表中添加一个新的入口,将ARP响应报文解析的IP,MAC,以及当前TIME添加到该入口。
UIP更新ARP缓存的机制
uIP根据收到的ARP报文的源IP和源硬件地址为依据,遍历ARP缓存表中现存的记录。如果缓存表中有记录,则简单更新对应的TIME,然后返回;如果表中没有记录,且当前表还有未使用的记录入口,将IP和MAC以及当前时间写进该入口,然后返回;如果表中现存记录没有匹配的入口,且缓存表也已经写满,uIP就找一个记录时间最长的入口,将IP和MAC以及当前时间写入该入口,代替原有记录,然后返回。
这样,运行uIP的主机就学习到了当前和自己有联系的其他主机的IP和MAC。如果长时间其他主机和自己没有联系,那么根据ARP缓存表的特点,uip_arp_timer会定时删除在ARP缓存中存在时间最长的记录。
uIP 例子
为了更清楚的了解协议处理,可以通过构ARP请求响应帧来检查uIP对ARP协议的支持,同时也可以查看ARP缓存表的记录,检验uIP对ARP缓存功能的支持。
uIP-1.0源码:http://files.cnblogs.com/iTsihang/uip-1.0.tar.zip
作者修改后,测试ARP协议的代码:http://files.cnblogs.com/iTsihang/uip-1.0%5Barp%5D.tar.zip
基本分析
构造ARP报文,交给uip_buf。uip_buf是uIP的一个复用缓冲,缓冲大小422字节(宏控制)。uip_buf填充ARP类型的报文时,ARP模块使用该缓冲,将缓冲内容转义成ARP报文格式,ARP模块对报文内容进行解析:
报文传给uip后,uip先判断报文类型:ARP请求?ARP响应 ; 如果是ARP请求报文,比较请求报文的目标ip地址是不是自己的IP。如果是自己,更新自己的ARP缓存表(创建或者更新一个三元组),同时构造ARP响应报文:
1 BUF->opcode = HTONS(2); 2 3 memcpy(BUF->dhwaddr.addr, BUF->shwaddr.addr, 6); 4 5 memcpy(BUF->shwaddr.addr, uip_ethaddr.addr, 6); 6 7 memcpy(BUF->ethhdr.src.addr, uip_ethaddr.addr, 6); 8 9 memcpy(BUF->ethhdr.dest.addr, BUF->dhwaddr.addr, 6); 10 11 BUF->dipaddr[0] = BUF->sipaddr[0]; 12 13 BUF->dipaddr[1] = BUF->sipaddr[1]; 14 15 BUF->sipaddr[0] = uip_hostaddr[0]; 16 17 BUF->sipaddr[1] = uip_hostaddr[1]; 18 19 BUF->ethhdr.type = HTONS(UIP_ETHTYPE_ARP);
如果不是自己的,则忽略; 如果收到的是一个ARP响应报文,比较响应报文的目标ip地址是不是自己的IP。如果是,则更新自己的ARP缓存表(创建或者更新一三元组);如果不是,则忽略。
调试追踪处理过程
配置 uIP 主机地址信息
在主方法中调用uip_sethostaddr方法配置uIP主机IP地址为192.168.1.115,调用uip_setnetmask方法配置子网掩码为255.255.255.0(主要给后面ARP模块判断来自网络的ARP包是不是和自己在同一个网内,因为ARP报文跨网段请求是不生效的,在路右侧已经过滤)。
1 uip_ipaddr(ipaddr, 192,168,1,115); 2 uip_sethostaddr(ipaddr); 3
4 uip_ipaddr(ipaddr, 192,168,0,1); 5 uip_setdraddr(ipaddr); 6
7 uip_ipaddr(ipaddr, 255,255,255,0); 8 uip_setnetmask(ipaddr);
构造 ARP 请求报文
我没按照结构体的定义构造报文,这里通过wireshark工具抓得网络上的ARP包,模拟构造的,相对不方便,你可以根据现有结构定义初始化ARP报文
1 char arp_request_data[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* Arp broadcast */
2 0x00, 0x23, 0x24, 0x29, 0x44, 0xd3, /* src mac */
3 0x08, 0x06, 0x00, 0x01, 0x08, 0x00, /* ethtype, hardware type 1-mac, L3 proto */
4 0x06, 0x04, 0x00, 0x01, /* sizeof hardware, proto len, opcode-arp-requese */
5 0x40, 0x16, 0x9f, 0x4e, 0xa0, 0x01, /* sender mac */
6 0xc0, 0xa8, 0x01, 0xc0, /* sender ip */
7 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* target mac(need be written by reciver) */
8 0xc0, 0xa8, 0x01, 0x73, /* target ip */
9 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* padding */
10 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 11 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, NULL};
构造 ARP 响应报文
1 char arp_reply_data[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, /* Arp unicast */
2 0x40, 0x16, 0x9f, 0x4e, 0xa0, 0x01, /* src mac */
3 0x08, 0x06, 0x00, 0x01, 0x08, 0x00, /* ethtype, hardware type 1-mac, L3 proto */
4 0x06, 0x04, 0x00, 0x02, /* sizeof hardware, proto len, opcode-arp-reply */
5 0x40, 0x16, 0x9f, 0x4e, 0xa0, 0x01, /* sender mac */
6 0xc0, 0xa8, 0x01, 0xc0, /* sender ip */
7 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, /* target mac(need be written by reciver) */
8 0xc0, 0xa8, 0x01, 0x73, /* target ip */
9 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* padding */
10 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 11 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, NULL};
TCP/IP 报文
1 char tcp_ip_data[] = {0x3c, 0xd9, 0x2b, 0x62, 0xdf, 0xf6, /* dst mac */
2 0x00, 0x23, 0x24, 0x29, 0x44, 0xd3, /* src mac */
3 0x08, 0x00, 0x45, 0x00, 0x00, 0x28, /* ethtype, version, headlen, diffs, len of ip packet */
4 0x45, 0xb9, 0x40, 0x00, 0x80, 0x06, /* identify, flag, shift, ttl, L4 proto */
5 0x30, 0x93, 0xc0, 0xa8, 0x01, 0xc0, /* head checksum, srcip */
6 0xc0, 0xa8, 0x01, 0x73, 0x00, 0x50, /* dst ip, src port */
7 0x0a, 0xe2, 0x86, 0xef, 0x47, 0x6d, /* dst port, ...... */
8 0x25, 0xf5, 0x6a, 0x8f, 0x50, 0x11, 9 0xfd, 0xf3, 0xc3, 0x48, 0x00, 0x00, 10 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, NULL};
查看 ARP 缓存表
1 static void
2 uip_arp_echo_cache(void) 3 {//add by tsihang
4 register struct arp_entry *tabptr; 5 u8_t index = 0; 6 printf("_______________ARP CACHE________________________\n"); 7 printf("ID IPADDR MAC TIME\n"); 8 for(index = 0; index < UIP_ARPTAB_SIZE; index++) 9 { 10 tabptr = &arp_table[index]; 11
12 printf("%2d: %3d.%3d.%3d.%3d %02x:%02x:%02x:%02x:%02x:%02x %3d\n", index, 13 (tabptr->ipaddr[0] & 0x0FF), (tabptr->ipaddr[0] >> 8 & 0xFF), 14 (tabptr->ipaddr[1] & 0x0FF), (tabptr->ipaddr[1] >> 8 & 0xFF), 15 tabptr->ethaddr.addr[0], tabptr->ethaddr.addr[1],tabptr->ethaddr.addr[2], 16 tabptr->ethaddr.addr[3],tabptr->ethaddr.addr[4],tabptr->ethaddr.addr[5], 17 tabptr->time); 18 } 19 printf("________________________________________________\n"); 20 }
经过上面几个报文的来回后,uIP就会学习到请求方的(IP, MAC, TIME)这样一个三元组,进而在下次发送的时候,直接查找该缓存表就能找到目的路径。
周期处理的特点
对uIP来说,每个执行周期是1/2个单位时间。对ARP模块来说,uIP每隔10个单位时间遍历一次ARP缓存表,将存在时间大于UIP_ARP_MAXAGE的三元组记录从缓存表中删除,这是uIP的ARP缓存定时处理。但是当网络有新的源信息包传送到uIP的时候,且此时缓存记录已满,uIP会删除存在时间最长的记录,将新记录增加到缓存表,而不遵寻该规律。这样就很好的保证和反映当前局域网的实时拓扑。