UIP源码之ARP过程分析
之前我们使用UIP实现了tcp和udp通讯今天来说说UIP的实现流程,当然,这篇文章里面只会涉及tcp和udp,暂时还没办法说DHCP,因为UIP的DHCP实现使用了协程的概念,下一章将协程之后再说DHCP.
void uip_init(void) { for(c = 0; c < UIP_LISTENPORTS; ++c) { uip_listenports[c] = 0; //首先,清零监听的全部tcp端口 } for(c = 0; c < UIP_CONNS; ++c) { uip_conns[c].tcpstateflags = UIP_CLOSED;//将全部的tcp连接设置为关闭 } #if UIP_ACTIVE_OPEN lastport = 1024;//设置用于tcp链接的端口(tcp客户端连接服务器的时候,客户端这边应该有一个端口,这个端口不需要用户手动指定) #endif /* UIP_ACTIVE_OPEN */ #if UIP_UDP for(c = 0; c < UIP_UDP_CONNS; ++c) { uip_udp_conns[c].lport = 0;//如果使能了UDP,将UDP监听端口清零 } #endif /* UIP_UDP */ /* IPv4 initialization. */ #if UIP_FIXEDADDR == 0 /* uip_hostaddr[0] = uip_hostaddr[1] = 0;*/ #endif /* UIP_FIXEDADDR */ }
系统启动之后就可以进入循环过程中,循环过程就是uip_polling,我们的接下来的处理应该按照网络的分层结构来,首先肯定是要实现arp的请求,包括四个主要函数
uip_arp_ipin(); //这个函数是一个空宏,不重要哈
uip_arp_arpin();//处理arp输入
uip_arp_out();//加以太网头结构,在主动连接时可能要构造ARP请求
uip_arp_timer();//定时更新arp缓存
首先是uip_arp_out,当我们的设备接入互联网的时候,本地机器会构造ip包发出去,在构造ip包之前要先构造arp包,说明如下
首先,uip启动的时候先要进行初始化,初始化代码如下
void uip_arp_out(void) { struct arp_entry *tabptr; // 在ARP表中找到目的IP地址,构成以太网头.如果目的IP地址不在局域网中,则使用默认路由的IP. // 如果ARP表中找不到,则将原来的IP包替换成一个ARP请求. // 首先检查目标是不是广播 if(uip_ipaddr_cmp(IPBUF->destipaddr, broadcast_ipaddr)) { memcpy(IPBUF->ethhdr.dest.addr, broadcast_ethaddr.addr, 6); } else { //检查发送的目标地址是否在局域网内 if(!uip_ipaddr_maskcmp(IPBUF->destipaddr, uip_hostaddr, uip_netmask)) { //如果不在局域网内,那么使用默认的路由器IP地址取代目标地址 uip_ipaddr_copy(ipaddr, uip_draddr); } else { //在局域网内,使用目标的ip地址 uip_ipaddr_copy(ipaddr, IPBUF->destipaddr); } //此时,遍历ARP缓存表,比较目标IP和ARP缓存表中的IP for(i = 0; i < UIP_ARPTAB_SIZE; ++i) { tabptr = &arp_table[i]; if(uip_ipaddr_cmp(ipaddr, tabptr->ipaddr)) { break; } } //如果遍历到表头也没有找到目标ip在arp缓存表中的缓存 if(i == UIP_ARPTAB_SIZE) { //将原IP请求替换成ARP请求并返回 memset(BUF->ethhdr.dest.addr, 0xff, 6);//以太网目的地址 memset(BUF->dhwaddr.addr, 0x00, 6);//目的以太网地址 memcpy(BUF->ethhdr.src.addr, uip_ethaddr.addr, 6); memcpy(BUF->shwaddr.addr, uip_ethaddr.addr, 6);//源以太网地址 uip_ipaddr_copy(BUF->dipaddr, ipaddr);//目的IP地址 uip_ipaddr_copy(BUF->sipaddr, uip_hostaddr);//源IP地址 BUF->opcode = HTONS(ARP_REQUEST); //在这里将请求切换为ARP请求 BUF->hwtype = HTONS(ARP_HWTYPE_ETH);//硬件类型ARP_ETH BUF->protocol = HTONS(UIP_ETHTYPE_IP);//0x0800表示IP类型,这个字段标识协议类型 BUF->hwlen = 6; BUF->protolen = 4; BUF->ethhdr.type = HTONS(UIP_ETHTYPE_ARP); uip_appdata = &uip_buf[UIP_TCPIP_HLEN + UIP_LLH_LEN]; uip_len = sizeof(struct arp_hdr); return;//可以看到,在此处返回了 } //建立最终的以太网头 memcpy(IPBUF->ethhdr.dest.addr, tabptr->ethaddr.addr, 6); } //如果在局域网中并且arp缓存表中有,那么直接构造以太网投 memcpy(IPBUF->ethhdr.src.addr, uip_ethaddr.addr, 6); IPBUF->ethhdr.type = HTONS(UIP_ETHTYPE_IP); uip_len += sizeof(struct uip_eth_hdr); }
这里我们可以看到,uip里面自建了一个arp的缓存表,定义如下
struct arp_entry { u16_t ipaddr[2];//IP地址 struct uip_eth_addr ethaddr;//网卡信息 u8_t time; }; struct uip_eth_addr { u8_t addr[6];//mac地址 };
那么这个缓存表在什么时候用,就要看arp的响应,也就是uip_arp_arpin这个函数了,
void uip_arp_arpin(void) { //检测ARP的头长度对不对 if(uip_len < sizeof(struct arp_hdr)) { uip_len = 0; return; } uip_len = 0; //检测操作码 switch(BUF->opcode) { case HTONS(ARP_REQUEST)://如果是一个arp请求,则发送应答 if(uip_ipaddr_cmp(BUF->dipaddr, uip_hostaddr)) { //首先我们将发送ARP请求的主机更新到ARP缓存表中,因为接下来和可能有更多的交流 uip_arp_update(BUF->sipaddr, &BUF->shwaddr); //ARP请求回应的操作码为2 BUF->opcode = HTONS(2); //将受到的ARP包的源以太网地址当成我们这次回应的目的以太网地址 memcpy(BUF->dhwaddr.addr, BUF->shwaddr.addr, 6); //加入自身的以太网地址做源以太网地址 memcpy(BUF->shwaddr.addr, uip_ethaddr.addr, 6); //对应以太网源地址 memcpy(BUF->ethhdr.src.addr, uip_ethaddr.addr, 6); //对应以太网目的地址 memcpy(BUF->ethhdr.dest.addr, BUF->dhwaddr.addr, 6); BUF->dipaddr[0] = BUF->sipaddr[0]; BUF->dipaddr[1] = BUF->sipaddr[1]; BUF->sipaddr[0] = uip_hostaddr[0]; BUF->sipaddr[1] = uip_hostaddr[1]; //构造回应包之后等待下一次的发送 BUF->ethhdr.type = HTONS(UIP_ETHTYPE_ARP); uip_len = sizeof(struct arp_hdr); } break; //如果现在我们接受到的是一个ARP回应,那么将这个回应的ip加入arp缓存中 case HTONS(ARP_REPLY): if(uip_ipaddr_cmp(BUF->dipaddr, uip_hostaddr)) { uip_arp_update(BUF->sipaddr, &BUF->shwaddr); } break; } return; }
这其中我们使用了uip_arp_update来更新缓存,基本原理就是先找找已经存在的arp缓存中有没有这个ip的主机,没有加进去,有不加进去,有一半更新缓存表
接下来是arp的周期性处理函数uip_arp_timer,因为arp协议规定每过多长时间更新一次arp表,所以有这个操作,代码如下
void uip_arp_timer(void) { struct arp_entry *tabptr; ++arptime; //遍历arp表 for(i = 0; i < UIP_ARPTAB_SIZE; ++i) { tabptr = &arp_table[i]; //检测缓存时间有没有超时 if((tabptr->ipaddr[0] | tabptr->ipaddr[1]) != 0 && arptime - tabptr->time >= UIP_ARP_MAXAGE) { //超时清空这个arp条目 memset(tabptr->ipaddr, 0, 4); } } }
此时ARP的过程基本上就OK了,
然后就是UIP的数据处理了,主要是一个函数
uip_process(),该函数很复杂,还没弄明白,但是不想去改源码的也不用看明白只需要知道,在uip_process中调用了叫做UIP_APPCALL();的宏,这是一个空宏,需要用户实现,当使用uip的时 候,实际上的协议uip_process实现,然后实际性的数据有uip_appcall宏实现,当使用udp的时候还有一个宏uip_udp_periodic中的UDP_APPCALL(),用户实现该宏(实质上是一个函数),进而参与到通信处理的过程中来