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,同样进行初始化。
同样,我在ourdev.cn上下载了,一份总结,一点一点上传,感谢ourdev.cn。
uip_arp_init(); arp协议的初始化,其中进行的是构造arp协议的缓存。
在配置UIP协议的时候要主要配置超时。
// 摘自uip协议包main.c
还要进行的配置是,比如配置主机地址,ip地址,还有掩码,以太网mac地址等信息,或者配置dhcp。
这些配置完成之后,进入协议的主循环,接受,和发送等等的过程了。
要应用到实际的使用中,还需要结合硬件,比如CP2200芯片,使用过程中,需要有接收,和发送函
数,这个需要自己实现,循环的流程如下:
结合如CP2200芯片的网卡芯片,组成嵌入式网卡,硬件提供能力,UIP提供的是策略。
由上往下逐步封装用户的数据,如:
应用层----------传输层--------网络层------数据链路层-----物理层
应用数据---TCP封装头部---IP封装头部-----mac封装+尾部-----发送
任何的事物需要经过一定的初始阶段,在UIP协议里面通过uip_init()来初始化。
在uip_init()函数里面主要工作是:
1. 将uip_state结构体全部清零。
2. 初始化用于TCP链接的uip_conn结构体,将连接状态置为close。
3. 设置用于TCP链接的端口lastport = 4096; 应该是最大的端口号,待查证。
4. 如果定义了UDP,同样进行初始化。
- void uip_init(void) {
- // clean statistics
- char* ptr= (char*) &uip_stat;
- for (int i = 0; i<sizeof (uip_stat); i++) {
- ptr[i] = 0;
- }
-
- for (c = 0; c < UIP_LISTENPORTS; ++c) {
- uip_listenports[c] = 0;
- }
- for (c = 0; c < UIP_CONNS; ++c) {
- uip_conns[c].tcpstateflags = UIP_CLOSED;
- }
- lastport = 4096;
-
- #if UIP_UDP
- for (c = 0; c < UIP_UDP_CONNS; ++c) {
- uip_udp_conns[c].lport = 0;
- }
- #endif /* UIP_UDP */
-
-
- /* IPv4 initialization. */
- #if UIP_FIXEDADDR == 0
- /* uip_hostaddr[0] = uip_hostaddr[1] = 0;*/
- #endif /* UIP_FIXEDADDR */
-
- }
uip_arp_init(); arp协议的初始化,其中进行的是构造arp协议的缓存。
在配置UIP协议的时候要主要配置超时。
// 摘自uip协议包main.c
- struct timer periodic_timer, arp_timer;
- timer_set(&periodic_timer, CLOCK_SECOND / 2);
- timer_set(&arp_timer, CLOCK_SECOND * 10);
这些配置完成之后,进入协议的主循环,接受,和发送等等的过程了。
要应用到实际的使用中,还需要结合硬件,比如CP2200芯片,使用过程中,需要有接收,和发送函
数,这个需要自己实现,循环的流程如下:
- while(1)
- {
- uip_len = tapdev_read(); // 接收的函数
- if(uip_len > 0)
- {
- if(BUF->type == htons(UIP_ETHTYPE_IP))
- {
- uip_arp_ipin();
- uip_input(); // 这个是实际的从上往下封装包的函数
- /* If the above function invocation resulted in data that
- should be sent out on the network, the global variable
- uip_len is set to a value > 0. */
- if(uip_len > 0)
- {
- uip_arp_out();
- tapdev_send(); // 发送的实际函数
- }
- }
- else if(BUF->type == htons(UIP_ETHTYPE_ARP))
- {
- uip_arp_arpin();
- /* If the above function invocation resulted in data that
- should be sent out on the network, the global variable
- uip_len is set to a value > 0. */
- if(uip_len > 0)
- {
- tapdev_send();
- }
- }
-
- }
- else if(timer_expired(&periodic_timer))
- {
- timer_reset(&periodic_timer);
- for(i = 0; i < UIP_CONNS; i++)
- {
- uip_periodic(i);
- /* If the above function invocation resulted in data that
- should be sent out on the network, the global variable
- uip_len is set to a value > 0. */
- if(uip_len > 0)
- {
- uip_arp_out();
- tapdev_send();
- }
- }
-
- #if UIP_UDP
- for(i = 0; i < UIP_UDP_CONNS; i++)
- {
- uip_udp_periodic(i);
- /* If the above function invocation resulted in data that
- should be sent out on the network, the global variable
- uip_len is set to a value > 0. */
- if(uip_len > 0)
- {
- uip_arp_out();
- tapdev_send();
- }
- }
- #endif /* UIP_UDP */
-
- /* Call the ARP timer function every 10 seconds. */
- if(timer_expired(&arp_timer))
- {
- timer_reset(&arp_timer);
- uip_arp_timer();
- }
- }
- }
1. 网卡如何与uIP协议交互(包括arp, icmp等)
在我看来,CP2200提供了读取网络数据的能力,而UIP提供的是一种如何封装网路数据的策略。对用户数
据不断封装,最后交给CP2200发送,在UIP协议中有一个uip_buf缓冲用来接收和发送数据。
(转自:维库电子开发网>电子通列表 > 协议栈)
ARP请求和应答
在UIP协议中定义了一个ARP的struct。维护了一张缓存表。
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指定.
在我看来,CP2200提供了读取网络数据的能力,而UIP提供的是一种如何封装网路数据的策略。对用户数
据不断封装,最后交给CP2200发送,在UIP协议中有一个uip_buf缓冲用来接收和发送数据。
(转自:维库电子开发网>电子通列表 > 协议栈)
ARP请求和应答
在UIP协议中定义了一个ARP的struct。维护了一张缓存表。
- struct arp_entry {
- u16_t ipaddr[2]; // 保存的是IP地址
- struct uip_eth_addr ethaddr; // 保存的是mac地址
- u8_t time; // 缓存更新时间
- };
* 为传出的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指定.
- void uip_arp_out(void)
- {
- struct arp_entry *tabptr=0;
- // 在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))
- {
- /* 目的地址不在局域网内,所以保用默认路由器的地址来确在MAC地址 */
- uip_ipaddr_copy(ipaddr, uip_draddr);
- }
- else
- {
- /* 否则,使用目标IP地址 */
- uip_ipaddr_copy(ipaddr, IPBUF->destipaddr);
- }
- //这里遍历表,对比目的IP与ARP缓存表中的IP.
- for(i = 0; i < UIP_ARPTAB_SIZE; ++i)
- {
- tabptr = &arp_table[i];
- if(uip_ipaddr_cmp(ipaddr, tabptr->ipaddr))
- {
- break;
- }
- }
- 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); // 硬件类型 值为1
- BUF->protocol = HTONS(UIP_ETHTYPE_IP); // 协议类型 值为0x8000表示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;
- }
-
- // 如果是在局域网中,且在ARP缓存中找到了(如果没找到进行不到这一步,在上面就返回了),则构建以太网头
- memcpy(IPBUF->ethhdr.dest.addr, tabptr->ethaddr.addr, 6);
- }
- 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, icmp等)
接上文
接下来看看UIP如何处理ARP应答的情况,在主循环中一段代码:
在uip_arp_arpin()函数中主要是处理ARP应答。
这个函数是在设备接收到ARP包时,由驱动程序调用的.如果收到是ARP包是一个对本地主机上次发送的ARP请求的应答,那么就从包中取得自己想要的主机的MAC地址,加入自己的ARP缓存表中.如果收到是一个ARP请求,那就把自己的MAC地址打包成一个ARP应答,发送给请求的主机.看代码uip_arp.c的254行:
还有一个是ARP周期处理函数,在主循环中代码如下:
下面说说网卡如何与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”类型,重新计算校验和修改校验和字段。
主要的处理过程在uip_process()函数中,UIP同样将收到的ICMP的数据包放到uip_buf中。
接下来看看UIP如何处理ARP应答的情况,在主循环中一段代码:
- else if(BUF->type == htons(UIP_ETHTYPE_ARP))
- {
- uip_arp_arpin(); // 处理ARP应答
- /* If the above function invocation resulted in data that
- should be sent out on the network, the global variable
- uip_len is set to a value > 0. */
- // 如果上面的函数返回的结果需要发送到网络上,那么uip_len就必须设置 > 0
- if(uip_len > 0)
- {
- network_device_send(); // 回应ARP包
- }
- }
-
这个函数是在设备接收到ARP包时,由驱动程序调用的.如果收到是ARP包是一个对本地主机上次发送的ARP请求的应答,那么就从包中取得自己想要的主机的MAC地址,加入自己的ARP缓存表中.如果收到是一个ARP请求,那就把自己的MAC地址打包成一个ARP应答,发送给请求的主机.看代码uip_arp.c的254行:
- void uip_arp_arpin(void)
- {
- 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缓存表中,因为我们很
- // 可能要跟它要有更多的交流
- uip_arp_update(BUF->sipaddr, &BUF->shwaddr);
-
- // 回应的操作码是 2.
- BUF->opcode = HTONS(2);
- // 将收到的ARP包的发送端以太网地址,变为目的以太网地址
- memcpy(BUF->dhwaddr.addr, BUF->shwaddr.addr, 6);
- // 将自己的以太网地址,赋值给ARP包的发送端以太网地址
- 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应答,而且也是我们所要的应答的话,就插件
- // 并更新ARP缓存表
- case HTONS(ARP_REPLY):
- if(uip_ipaddr_cmp(BUF->dipaddr, uip_hostaddr))
- {
- uip_arp_update(BUF->sipaddr, &BUF->shwaddr);
- }
- break;
- }
-
- return;
- }
// 每10秒运行一次
if(timer_expired(&arp_timer))
{
timer_reset(&arp_timer);
uip_arp_timer();
}
具体的代码:if(timer_expired(&arp_timer))
{
timer_reset(&arp_timer);
uip_arp_timer();
}
- void uip_arp_timer(void)
- {
- struct arp_entry *tabptr;
- ++arptime; // 这个是个全局变量,结合uip_arp_update来更新缓存表
- for(i = 0; i < UIP_ARPTAB_SIZE; ++i)
- {
- tabptr = &arp_table[i];
- // 把超过20分钟都没有更新的项扔掉
- if((tabptr->ipaddr[0] | tabptr->ipaddr[1]) != 0 &&
- arptime - tabptr->time >= UIP_ARP_MAXAGE)
- {
- memset(tabptr->ipaddr, 0, 4);
- }
- }
- }
----------------------------------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”类型,重新计算校验和修改校验和字段。
- #define ICMPBUF ((struct uip_icmpip_hdr *)&uip_buf[UIP_LLH_LEN])
写到这里本来不想再写下去了,不过还是有些没明白的地方。比如,我只看到了设备接收对方发过来的数据包,但是,UIP如何将数据包发送出去?还有那个uip_process()函数好长,很多没弄明白,今天继续翻看了另外一些代码,发现一个宏UIP_APPCALL。
都是自己的疏忽,在uip文档里面搜索UIP_APPCALL就提到了,不同的事件调用不懂的函数,UIP_APPCALL被定义成一个宏,当要用到应用层序的时候,就将UIP_APPCALL定义成相应的函数,比如:
example1_app应用函数:
UIP_APPCALL宏
显然,就是将example1_app定义成UIP_APPCALL宏来使用,这样在uip_process()就可以直接使用UIP_APPCALL了。
自己定义的example1_app函数中使用UIP应用层函数,就能在网络上交换数据了。
都是自己的疏忽,在uip文档里面搜索UIP_APPCALL就提到了,不同的事件调用不懂的函数,UIP_APPCALL被定义成一个宏,当要用到应用层序的时候,就将UIP_APPCALL定义成相应的函数,比如:
example1_app应用函数:
- void example1_app(void)
- {
- if(uip_newdata() || uip_rexmit())
- {
- uip_send("ok\n", 3);
- }
- }
- #define UIP_APPCALL example2_app
自己定义的example1_app函数中使用UIP应用层函数,就能在网络上交换数据了。
总算一点一点看完了UIP协议,期间各大网站,各位coder的代码翻了好几个,在此感谢。
首先,应清楚UIP协议在代码中扮演的是什么角色,我觉得流水线一样,将应用层的数据,通过流水线不断包装。
TCP---IP---MAC--->发送。
uip_buf就是实际的原料了。在UIP协议就使用了一个缓冲区。其实是char类型的数组,然后各种strut将它转换成自己的结构,如:
所有的数据都保存在uip_buf数组中。通过uip_process函数来封装。
uip_len是uip_buf接收到的数据的长度。
具体的发送到网络上的函数需要根据自己的平台具体实现,整体的循环不需要改动多少。
首先,应清楚UIP协议在代码中扮演的是什么角色,我觉得流水线一样,将应用层的数据,通过流水线不断包装。
TCP---IP---MAC--->发送。
uip_buf就是实际的原料了。在UIP协议就使用了一个缓冲区。其实是char类型的数组,然后各种strut将它转换成自己的结构,如:
- #define BUF ((struct uip_eth_hdr *)&uip_buf[0])
- #define ICMPBUF ((struct icmpip_hdr *)&uip_buf[UIP_LLH_LEN])
- #define UDPBUF ((struct uip_udpip_hdr *)&uip_buf[UIP_LLH_LEN])
uip_len是uip_buf接收到的数据的长度。
具体的发送到网络上的函数需要根据自己的平台具体实现,整体的循环不需要改动多少。