打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
STM32NET学习笔记 IP ICMP部分
http://blog.csdn.net/xukai871105/article/details/19938133
2014

1.前言 
    嵌入式以太网开发是一个很有挑战性的工作。通过几个月的学习,我个人觉得大致有两条途径。第一条途径,先通过高级语言熟悉socket编程,例如C#或C++,对bind,listen,connect,accept等函数熟悉之后,应用 lwIP。第二种途径,通过分析嵌入式以太网代码,结合TCPIP协议栈规范逐步实践代码。第一种途径效率高,开发周期短,编写出来的代码性能稳定,第二种途径花的时间长,开发出来的代码功能不完善,但是由于紧紧结合TCPIP规范,可以了解的内容较多,适合学习。本文通过分析和修改AVRNET源码,逐步实现TCPIP协议栈的各个子部分,包括ETHERNET部分,ARP部分,IP部分,ICMP部分,UDP部分,TCP部分和HTTP部分。
 本文将实现IP部分和ICMP部分。

1.2 相关资料

1.3 代码仓库
    【代码仓库】——CSDN Code代码仓库。

2.IP部分实现
    IP层是TCP和UDP实现的基础。IP首部紧跟以太网首部,长度为20字节。IP首部具有最基本的两个任务,
    【第一】定义IP包的具体协议类型,例如ICMP,TCP或UDP等;
    【第二】定义IP报文从哪个IP地址来和到哪个IP地址去。
    需要强调,在同一个子网中即同一个物理网络中,IP报文中的目标IP地址和以太网首部中的目标MAC地址相对应,若不在同一个物理网路中,目标IP地址和目标MAC地址不同,目标MAC地址被路由器的MAC地址替代,意味着通过路由器转发报文。在IP首部中还包括很多其他内容,需要注意的是IP标识符,该标识符主要用于区分IP报文,最简单的算法即每发送一个IP报文后IP标识符累加。具体通过以下代码实现IP首部的填充。

2.1 IP首部填充
  1. // IP首部总长度  
  2. #define IP_HEADER_LEN 20  
  3. // 协议类型  
  4. // ICMP协议  
  5. #define IP_PROTO_ICMP_V 0x01  
  6. // TCP协议  
  7. #define IP_PROTO_TCP_V 0x06  
  8. // UDP协议  
  9. #define IP_PROTO_UDP_V 0x11  
  10. // IPV4版本  
  11. #define IP_V4_V 0x40  
  12. #define IP_HEADER_LENGTH_V 0x05  
  13. // IP版本号位置 以太网首部2+6+6  
  14. #define IP_P 0x0E  
  15. // 首部长度  
  16. #define IP_HEADER_VER_LEN_P 0x0E  
  17. // 服务类型  
  18. #define IP_TOS_P 0x0F  
  19. // IP总长度  
  20. #define IP_TOTLEN_H_P 0x10  
  21. #define IP_TOTLEN_L_P 0x11  
  22. // IP标识  
  23. #define IP_ID_H_P 0x12  
  24. #define IP_ID_L_P 0x13  
  25. //  
  26. #define IP_FLAGS_H_P 0x14  
  27. #define IP_FLAGS_L_P 0x15  
  28. // TTL生存时间  
  29. #define IP_TTL_P 0x16  
  30. // IP协议类型 例如ICMP TCP UDP  
  31. #define IP_PROTO_P 0x17  
  32. // 首部校验和  
  33. #define IP_CHECKSUM_H_P 0x18  
  34. #define IP_CHECKSUM_L_P 0x19  
  35. // 源IP地址  
  36. #define IP_SRC_IP_P 0x1A  
  37. // 目标IP地址  
  38. #define IP_DST_IP_P 0x1E  
  39. void ip_generate_header ( BYTE *rxtx_buffer, WORD_BYTES total_length, BYTE protocol, BYTE *dest_ip )  
  40. {  
  41.   BYTE i;  
  42.   // 校验结果  
  43.   WORD_BYTES ck;  
  44.    
  45.   // 版本号和首都长度  
  46.   rxtx_buffer[ IP_P ] = IP_V4_V | IP_HEADER_LENGTH_V;  
  47.   // 服务类型  
  48.   rxtx_buffer[ IP_TOS_P ] = 0x00;  
  49.   // 总长度  
  50.   rxtx_buffer [ IP_TOTLEN_H_P ] = total_length.byte.high;  
  51.   rxtx_buffer [ IP_TOTLEN_L_P ] = total_length.byte.low;  
  52.    
  53.   // IP标识  
  54.   rxtx_buffer [ IP_ID_H_P ] = ip_identfier >> 8;  
  55.   rxtx_buffer [ IP_ID_H_P ] = ip_identfier & 0x00ff;  
  56.   // 累加  
  57.   ip_identfier++;  
  58.    
  59.   // 标志和分片偏移  
  60.   rxtx_buffer [ IP_FLAGS_H_P ] = 0x00;  
  61.   rxtx_buffer [ IP_FLAGS_L_P ] = 0x00;  
  62.    
  63.   // 生存时间  
  64.   rxtx_buffer [ IP_TTL_P ] = 128;  
  65.    
  66.   // set ip packettype to tcp/udp/icmp...  
  67.   rxtx_buffer [ IP_PROTO_P ] = protocol;  
  68.    
  69.   // 设定目标地址和源地址  
  70.   for ( i = 0; i < 4; i++ )  
  71.   {  
  72.     rxtx_buffer[ IP_DST_IP_P + i ] = dest_ip[i];  
  73.     rxtx_buffer[ IP_SRC_IP_P + i ] = avr_ip.byte[ i ];  
  74.   }  
  75.    
  76.   // 校验结果  
  77.   rxtx_buffer[ IP_CHECKSUM_H_P ] = 0;  
  78.   rxtx_buffer[ IP_CHECKSUM_L_P ] = 0;  
  79.   ck.word = software_checksum ( &rxtx_buffer[ IP_P ], sizeof(IP_HEADER), 0 );  
  80.   rxtx_buffer[ IP_CHECKSUM_H_P ] = ck.byte.high;  
  81.   rxtx_buffer[ IP_CHECKSUM_L_P ] = ck.byte.low;  
  82. }  


2.2 IP报文查询
    IP报文查询功能对应于ARP报文查询功能,通过以太网首部中的最后2个字节判断该报文是否为IP报文;如果是IP报文则继续和本机IP地址相比较。如果两步检查均通过则认为是合法的IP报文,当然这其中舍弃了IP版本号和首部校验和的检查,虽然存在某些隐患但并不妨碍实现基本功能。
  1. BYTE ip_packet_is_ip ( BYTE *rxtx_buffer )  
  2. {  
  3.   unsigned char i;  
  4.    
  5.   // 检查该报文是否为IP报文  
  6.   if ( rxtx_buffer[ ETH_TYPE_H_P ] != ETH_TYPE_IP_H_V || rxtx_buffer[ ETH_TYPE_L_P ] != ETH_TYPE_IP_L_V)  
  7.     return 0;  
  8.    
  9.   // 检查该报文的IP地址是否为本机IP地址,逐个检查  
  10.   for ( i=0; i<sizeof(IP_ADDR); i++ )  
  11.   {  
  12.     if ( rxtx_buffer[ IP_DST_IP_P + i ] != avr_ip.byte[i] )  
  13.       return 0;  
  14.   }  
  15.    
  16.   // 若该报文为IP报文,且目标IP地址为本机地址,返回1  
  17.   return 1;  
  18. }  


3.ICMP部分实现
    虽然ICMP具有很多的子协议,但是其中最著名的要数ping程序,即ICMP回显请求和应答报文。通过使用ping命令来判断报文是否可以到达目标地址。ICMP的实现是一个逐步遵守规则的过程,即向固定的字节填充数据。
  1. // 回显应答  
  2. #define ICMP_TYPE_ECHOREPLY_V 0  
  3. // 回显请求  
  4. #define ICMP_TYPE_ECHOREQUEST_V 8  
  5. // ICMP首部长度  
  6. #define ICMP_PACKET_LEN 40  
  7. // ICMP类型  
  8. #define ICMP_TYPE_P 0x22  
  9. // ICMP代码  
  10. #define ICMP_CODE_P 0x23  
  11. // ICMP首部校验和  
  12. #define ICMP_CHECKSUM_H_P 0x24  
  13. #define ICMP_CHECKSUM_L_P 0x25  
  14. // ICMP标识符  
  15. #define ICMP_IDENTIFIER_H_P 0x26  
  16. #define ICMP_IDENTIFIER_L_P 0x27  
  17. // ICMP序号  
  18. #define ICMP_SEQUENCE_H_P 0x28  
  19. #define ICMP_SEQUENCE_L_P 0x29  
  20. #define ICMP_DATA_P 0x2A  


3.1 ICMP首部填充
ICMP首部填充需要根据协议类型填充不同的内容,对于回显请求而言只需在ICMP协议类型部分填充0即可,当然ICMP部分也包括ICMP首部校验和。
  1. void icmp_generate_packet ( BYTE *rxtx_buffer ,BYTE type)  
  2. {  
  3.   BYTE i;  
  4.   WORD_BYTES ck;  
  5.    
  6.   // ICMP回显请求  
  7.   if( type == ICMP_TYPE_ECHOREQUEST_V )  
  8.   {  
  9.     rxtx_buffer[ ICMP_TYPE_P ] == ICMP_TYPE_ECHOREQUEST_V;  
  10.     rxtx_buffer[ ICMP_CODE_P ] = 0;  
  11.     rxtx_buffer[ ICMP_IDENTIFIER_H_P ] = icmp_id;  
  12.     rxtx_buffer[ ICMP_IDENTIFIER_L_P ] = 0;  
  13.     rxtx_buffer[ ICMP_SEQUENCE_H_P ] = icmp_seq;  
  14.     rxtx_buffer[ ICMP_SEQUENCE_L_P ] = 0;  
  15.     icmp_id++;  
  16.     icmp_seq++;  
  17.     for ( i=0; i<ICMP_MAX_DATA; i++ )  
  18.     {  
  19.       rxtx_buffer[ ICMP_DATA_P + i ] = 'A' + i;  
  20.     }  
  21.   }  
  22.   // ICMP回显  
  23.   if(type == ICMP_TYPE_ECHOREPLY_V)  
  24.   {  
  25.     rxtx_buffer[ ICMP_TYPE_P ] = ICMP_TYPE_ECHOREPLY_V;  
  26.   }  
  27.    
  28.   // ICMP首部校验和  
  29.   rxtx_buffer[ ICMP_CHECKSUM_H_P ] = 0;  
  30.   rxtx_buffer[ ICMP_CHECKSUM_L_P ] = 0;  
  31.   ck.word = software_checksum ( &rxtx_buffer[ ICMP_TYPE_P ], sizeof(ICMP_PACKET), 0 );  
  32.   rxtx_buffer[ ICMP_CHECKSUM_H_P ] = ck.byte.high;  
  33.   rxtx_buffer[ ICMP_CHECKSUM_L_P ] = ck.byte.low;  
  34. }  


3.2 ICMP回显应答
 ICMP回显应答需要做好两步,第一步检查IP首部中的协议类型是否为ICMP报文;第二,检查ICMP首部中的ICMP类型是否为ICMP请求,如果是则生成ICMP回显应答并通过以太网驱动芯片发送。为了便于调试,在接收到ICMP回显请求时通过串口输出发起方的IP地址,ping命令发起方的IP地址存在于IP首部中的源IP地址部分。

  1. BYTE icmp_send_reply ( BYTE *rxtx_buffer, BYTE *dest_mac, BYTE *dest_ip )  
  2. {  
  3.    
  4.   // IP首部中为ICMP协议类型  
  5.   if ( rxtx_buffer [ IP_PROTO_P ] != IP_PROTO_ICMP_V )  
  6.     return 0;  
  7.    
  8.   // 是否为ICMP回显请求  
  9.   if ( rxtx_buffer [ ICMP_TYPE_P ] != ICMP_TYPE_ECHOREQUEST_V )  
  10. return 0;  
  11. #ifdef ICMP_DEBUG  
  12.   printf("ICMP Request!\n");  
  13.   printf("Ping from:%d.%d.%d.%d\n",\  
  14.             rxtx_buffer[IP_SRC_IP_P+0],rxtx_buffer[IP_SRC_IP_P+1],\  
  15.             rxtx_buffer[IP_SRC_IP_P+2],rxtx_buffer[IP_SRC_IP_P+3]);  
  16. #endif  
  17.   // 生成以太网首部  
  18.   eth_generate_header ( rxtx_buffer, (WORD_BYTES){ETH_TYPE_IP_V}, dest_mac );  
  19.    
  20.   // 生成IP首部  
  21.   ip_generate_header ( rxtx_buffer, (WORD_BYTES){(rxtx_buffer[IP_TOTLEN_H_P]<<8)|rxtx_buffer[IP_TOTLEN_L_P]}, IP_PROTO_ICMP_V, dest_ip );  
  22.   // 生成ICMP首部  
  23.   icmp_generate_packet ( rxtx_buffer ,(BYTE)ICMP_TYPE_ECHOREPLY_V);  
  24.   // 发送报文  
  25.   enc28j60_packet_send ( rxtx_buffer, sizeof(ETH_HEADER) + sizeof(IP_HEADER) + sizeof(ICMP_PACKET) );  
  26.   return 1;  
  27. }  

4.实例
    通过ping命令可以测试报文是否可以到达目标主机。例如发送ping 192.168.1.115。
    在程序的无线循环中,需要层层进行查询。
【1】查询以太网中是否有数据,若无数据则返回。
【2】保存源MAC地址,待返回时使用。
【3】查询是否为ARP报文并返回ARP报文。
【4】保存源IP地址,待返回时使用。
【5】查询是否为IP报文,若非IP报文返回。
【6】查询是否为ICMP报文并返回ICMP回显应答。

  1. /* 获得新的IP报文 */  
  2.  plen = enc28j60_packet_receive( (BYTE*)&rxtx_buffer, MAX_RXTX_BUFFER );  
  3.  if(plen==0) return;  
  4.  /* 保存客服端的MAC地址 */  
  5.  memcpy ( (BYTE*)&client_mac, &rxtx_buffer[ ETH_SRC_MAC_P ], sizeof(MAC_ADDR) );  
  6.  /* 检查该报文是不是ARP报文 */  
  7.  if ( arp_packet_is_arp( rxtx_buffer, (WORD_BYTES){ARP_OPCODE_REQUEST_V} ) )  
  8.  {  
  9.    /* 向客户端返回ARP报文 */  
  10.    arp_send_reply ( (BYTE*)&rxtx_buffer, (BYTE*)&client_mac );  
  11.    return;  
  12.  }  
  13.  /* 保存客服端的IP地址 */  
  14.  memcpy ( (BYTE*)&client_ip, &rxtx_buffer[ IP_SRC_IP_P ], sizeof(IP_ADDR) );  
  15.  /* 检查该报文是否为IP报文 */  
  16.  if ( ip_packet_is_ip ( (BYTE*)&rxtx_buffer ) == 0 )  
  17.  {  
  18.    return;  
  19.  }  
  20.   
  21.  /* 如果是ICMP报文 向发起方返回数据 */  
  22.  if ( icmp_send_reply ( (BYTE*)&rxtx_buffer, (BYTE*)&client_mac, (BYTE*)&client_ip ) )  
  23.  {  
  24.    return;  
  25.  }  


【实验结果】
 

图1 PING命令
 

图2 串口输出


本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
用C语言实现Ping程序功能
计算机网络试题库含答案(个人整理打印版)
IP报文头结构体
ping源代码完全解析
Ping命令不能PING通的种种解惑
校验和算法
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服