概述
ARP部分
IP&ICMP部分
UDP部分
TCP部分
1. ARP的简介
Address Resolution Protocol-地址解析协议
ARP为IP地址到对应的硬件地址之间提供动态映射。从逻辑Internet地址到对应的物理硬件地址需要进行翻译。这就是ARP的功能。ARP的功能是在32 bit的IP地址和采用不同网络技术的硬件地址之间提供动态映射。
2. ARP的应答流程
任何时候我们敲入下面这个形式的命令:
% ftp bsdi //示例而已
都会进行以下这些步骤。这些步骤的序号如图 4 - 2所示。
1) 应用程序FTP客户端调用函数gethostbyname(3)把主机名(bsdi)转换成32 bit的IP地址。这个函数在DNS(域名系统)中称作解析器,我们将在第1 4章对它进行介绍。这个转换过程或者使用DNS,或者在较小网络中使用一个静态的主机文件(/etc/hosts) 。
2) FTP客户端请求TCP用得到的IP地址建立连接。
3) TCP发送一个连接请求分段到远端的主机,即用上述 IP地址发送一份IP数据报(在第1 8章我们将讨论完成这个过程的细节) 。
4) 如果目的主机在本地网络上(如以太网、令牌环网或点对点链接的另一端) ,那么IP数据报可以直接送到目的主机上。如果目的主机在一个远程网络上,那么就通过 IP选路函数来确定位于本地网络上的下一站路由器地址,并让它转发 IP数据报。在这两种情况下,IP数据报都是被送到位于本地网络上的一台主机或路由器。
5) 假定是一个以太网,那么发送端主机必须把 32 bit的IP地址变换成48 bit的以太网地址。从逻辑Internet地址到对应的物理硬件地址需要进行翻译。这就是 ARP的功能。ARP本来是用于广播网络的,有许多主机或路由器连在同一个网络上。
6) ARP发送一份称作ARP请求的以太网数据帧给以太网上的每个主机。这个过程称作广播,如图 4 - 2中的虚线所示。 ARP请求数据帧中包含目的主机的IP地址(主机名为bsdi) ,其意思是“如果你是这个IP地址的拥有者,请回答你的硬件地址。 ”
7) 目的主机的ARP层收到这份广播报文后,识别出这是发送端在寻问它的 IP地址,于是发送一个ARP应答。这个ARP应答包含IP地址及对应的硬件地址。
8) 收到ARP应答后,使ARP进行请求—应答交换的IP数据报现在就可以传送了。
9) 发送IP数据报到目的主机。
3. ARP的分组格式
• 以太网报头中的前两个字段是以太网的源地址和目的地址。目的地址为全 1的特殊地址是广播地址。电缆上的所有以太网接口都要接收广播的数据帧。
• 两个字节长的以太网帧类型表示后面数据的类型。对于 A R P请求或应答来说,该字段的值为0 x 0 8 0 6。
• 硬件类型字段表示硬件地址的类型。它的值为 1即表示以太网地址。
• 协议类型字段表示要映射的协议地址类型。它的值为 0 x 0 8 0 0即表示I P地址。它的值与包含I P数据报的以太网数据帧中的类型字段的值相同,这是有意设计的(参见图 2 - 1) -忘了截过来了。
• 接下来的两个1字节的字段,硬件地址长度和协议地址长度分别指出硬件地址和协议地址的长度,以字节为单位。对于以太网上I P地址的ARP请求或应答来说,它们的值分别为6和4。
• 操作字段(op)指出四种操作类型,它们是 ARP请求(值为1) 、ARP应答(值为2) 、RARP请求(值为3)和R ARP应答(值为4) (我们在第5章讨论RARP) 。这个字段必需的,因为ARP请求和ARP应答的帧类型字段值是相同的。
• 接下来的四个字段是发送端的硬件地址(在本例中是以太网地址) 、发送端的协议地址(IP地址) 、目的端的硬件地址和目的端的协议地址。注意,这里有一些重复信息:在以太网的数据帧报头中和ARP请求数据帧中都有发送端的硬件地址。
对于一个ARP请求来说,除目的端硬件地址外的所有其他的字段都有填充值。当系统收到一份目的端为本机的 ARP请求报文后,它就把硬件地址填进去,然后用两个目的端地址分别替换两个发送端地址,并把操作字段置为 2,最后把它发送回去。
--------------------------------以上内容整理于《TCP/IP协议详解:卷1》----------------------------
理是那个那个理,但是过于抽象了,不过是基础,看完上面再看实现,那感觉很爽的~~~
------------------------------------------以下内容产生于代码及分析--------------------------------------
4. ARP的宏定义实现
以太网协议而非802.3协议,看ETH命名的头名字就晓得了,地址位置可以结合两个header算算就出来了
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
|
// ******* ARP ******* //ARP包长度 #define ETH_ARP_PACKET_LEN 28 //硬件地址长度值 #define ETHTYPE_ARP_L_V 0x06 //协议地址长度值 #define ETHTYPE_ARP_PROTOCOL_SIZE_V 0x04 //操作码位置 2字节 #define ETH_ARP_OPCODE_H_P 0x14 #define ETH_ARP_OPCODE_L_P 0x15 //ARP请求操作码值 #define ETH_ARP_OPCODE_REQUEST_V 0x0001 #define ETH_ARP_OPCODE_REQUEST_H_V 0x00 #define ETH_ARP_OPCODE_REQUEST_L_V 0x01 //ARP响应操作码值 #define ETH_ARP_OPCODE_REPLY_V 0x0002 #define ETH_ARP_OPCODE_REPLY_H_V 0x00 #define ETH_ARP_OPCODE_REPLY_L_V 0x02 // 发送者源硬件地址位置 6字节 #define ETH_ARP_SRC_MAC_P 0x16 //发送者源IP地址位置 4字节 #define ETH_ARP_SRC_IP_P 0x1c //目标硬件地址位置 6字节 #define ETH_ARP_DST_MAC_P 0x20 //目标IP地址位置 4字节 #define ETH_ARP_DST_IP_P 0x26 |
5. ARP的实现函数
以太网的header在ARP的header之前,很简单的,介绍先。
配置以太网的头,为14字节:6字节目的mac地址+6字节源mac地址+2字节协议类型,如图4-3
1
2 3 4 5 6 7 8 9 10 11 12 13 |
|
// make a return eth header from a received eth packet void make_eth( unsigned char *buf) { unsigned char i = 0; //copy the destination mac from the source and fill my mac into src while(i < sizeof(mac_addr)) { buf[ETH_DST_MAC + i] = buf[ETH_SRC_MAC + i]; buf[ETH_SRC_MAC + i] = macaddr[i]; i++; } } |
展开就是这样的,看看宏定义是否与此一一对应呢。
在判断为arp请求之后,填充以太网的头之后响应arp请求
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
|
void make_arp_answer_from_request(
unsigned
char *buf)
{ unsigned char i = 0; //配置以太网的头,为14字节:6字节目的mac地址+6字节源mac地址+2字节协议类型 make_eth(buf); buf[ETH_ARP_OPCODE_H_P] = ETH_ARP_OPCODE_REPLY_H_V; //arp 响应 buf[ETH_ARP_OPCODE_L_P] = ETH_ARP_OPCODE_REPLY_L_V; // 后面的 ARP_DEBUG插入此处即可。
// fill the mac addresses: while(i < sizeof(mac_addr)) { buf[ETH_ARP_DST_MAC_P + i] = buf[ETH_ARP_SRC_MAC_P + i]; buf[ETH_ARP_SRC_MAC_P + i] = macaddr[i]; i++; } i = 0; //fill the ipv4 addresses while(i < sizeof(ipv4_addr)) { buf[ETH_ARP_DST_IP_P + i] = buf[ETH_ARP_SRC_IP_P + i]; buf[ETH_ARP_SRC_IP_P + i] = ipaddr[i]; i++; } // eth+arp is 42 bytes: enc28j60PacketSend(ETH_HEADER_LEN + ETH_ARP_PACKET_LEN, buf); } |
当然,响应ARP请求的前提是你得确定有人向你发出ARP请求(下面那个函数就是了),并且这个人是谁,你是要知道的(通过发送者的IP和MAC地址),这个很容易,本协议是将地址放在几个全局变量里面的,大家就都知道了,虽然全局变量用起来很爽,但是对模块化以及后期维护带来的不便也是很大的。
检查是否为合法的eth,并且只接受发给本机的arp数据,此函数在上面那个函数之前被调用,再下面的代码就是演示的例程
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
//检查是否为合法的eth,并且只接受发给本机的arp数据 unsigned char eth_type_is_arp_and_my_ip( unsigned char *buf, unsigned int len) { unsigned char i = 0; // 帧长度不得小于以太网的最小帧长度值,即46-除以太网头和CRC检测 if(len < MIN_FRAMELEN) { return( 0); } if(buf[ETH_TYPE_H_P] != ETHTYPE_ARP_H_V || buf[ETH_TYPE_L_P] != ETHTYPE_ARP_L_V) { return( 0); } //不是发给本机IP地址的不接收,那么如此说来,我在这里可以设定监听其他IP的信息! while(i < sizeof(ipv4_addr)) { if(buf[ETH_ARP_DST_IP_P + i] != ipaddr[i]) { return( 0); } i++; } return( 1); } |
以上函数在别人向你发送任何请求之前都将被调用一次(原因是本协议只是实现了对IP和ARP的响应),所以需要在一个while死循环或者RTOS的一个thread/task/process里面。如下所示:
上层调用示例代码
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
|
/* 此部分为一部分代码 */ /*do something initial */ while( 1) { // get the next new packet: plen = enc28j60PacketReceive(BUFFER_SIZE, buf); // plen will be unequal to zero if there is a valid packet (without crc error) if(plen== 0) { continue; } // check if ip packets are for us: if(eth_type_is_ip_and_my_ip(buf,plen)== 0) { //丢弃本次获取的数据,再接下一个 continue; } // arp is broadcast if unknown but a host may also // verify the mac address by sending it to // a unicast address. //这里就是ARP的响应了,如果我们在这里加入串口调试, //就可以将谁在向我发送arp请求的数据打印到串口 //当然加一个选择宏,放在函数里面更方便一点 if(eth_type_is_arp_and_my_ip(buf,plen)) { make_arp_answer_from_request(buf); continue; } /*do other things */ } |
6. ARP实验调式
嗯,接者来看看在make_arp_answer_from_request函数里面加入串口调试信息来输出arp请求者的ip和mac地址。
加入到 make_arp_answer_from_request中的调试代码,用于输出ARP请求者的信息
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
|
#ifdef ARP_DEBUG
printf( "ARP请求者IP地址 : rn"); while(i < sizeof(ipv4_addr)) { printf( "%d", buf[ETH_ARP_SRC_IP_P + i]); if(i != sizeof(ipv4_addr) - 1) // 加入判断只是为了输出的形式好看点 { printf( "."); } else { printf( "rn"); } i++; } i = 0; printf( "ARP请求者MAC地址 :rn"); while(i < sizeof(mac_addr)) { printf( "%x", buf[ETH_ARP_SRC_MAC_P + i]); if(i != sizeof(mac_addr) - 1) { printf( ":"); } else { printf( "rn"); } i++; } i = 0; #endif |
PC端:测试arp请求需要先执行“arp -d”清楚本地的arp-ip对应列表,这样PC机才会发送ARP请求
ps:enj28j60的MAC地址是软件设定的,所以就不打码了。
串口端:显示调试信息而已
下面是我放着没动它,局域网内就有其他主机来找我的嵌入式Web Server了,O(∩_∩)O~
--------------------------------------------以下是我看代码看的不仔细的纠结,可跳过-----------------------------------------------
但是在实验过程中发现如下情况是不会调用
make_arp_answer_from_request
函数的:
PC机本地有arp-ip列表,就是ping过一次以后ping第二次的就不会去响应了,这个本来就是要如此的,希望结果就是这样的,你要问我是谁(MAC地址是多少),然后我告诉你一次,你记住就ok了么。但是问题出在我们的协议栈实现代码里面,本协议栈在通过
eth_type_is_arp_and_my_ip
判断了是发给本机的数据包之后,就会调用arp响应的,没有一个对以太网header的类型的判断啊。怎么会自己变的智能了呢?
代码如下
1
2 3 4 5 6 |
|
if(eth_type_is_arp_and_my_ip(buf, plen)) { make_arp_answer_from_request(buf); continue; } |
那么问题出在哪里呢?
1. 难道是进入
make_arp_answer_from_request 函数,但是没有发送出去么?当然也不会在
enc28j60PacketSend(ETH_HEADER_LEN + ETH_ARP_PACKET_LEN, buf); 里面,因为没有判别信息传递进去。(当然发不发,只要如函数之后就会有打印信息输出到串口)
2. 那么就是
eth_type_is_arp_and_my_ip 函数返回为0了;
但是ping命令还是可以得到响应的,哦,对了,还有ICMP(我本来就用的ping,都忘了,放在IP层),那么就好解释了,第一次是由ARP+ICMP响应,第二次及之后的只有ICMP响应。所以现象还是符合原理解释的。
但是,我用网页去刷新和点亮LED等,走的肯定是IP包,ARP还是不响应。
回去看代码,当时对自己就无语了
if
(buf[ETH_TYPE_H_P] != ETHTYPE_ARP_H_V || buf[ETH_TYPE_L_P] != ETHTYPE_ARP_L_V)
是ARP啊,我以为是对IP和ARP都会响应,而且人家的函数名是
eth_type_is_arp_and_my_ip ,我自己把它想成
eth_type_is_arp_and_ip ,╮(╯▽╰)╭
代码就在那里,自己SB了~~~
--------------------------------------------以上是我边想边实验边写的过程-----------------------------------------------
硬件环境:STM32+ENC28J60
软件环境:MDK4.70a
TCP/IP协议栈:开发者的网站已经关闭了,也没有命名~~~
给出作者信息:
/*********************************************
* modified: 2007-08-08
* Author : awake
* Copyright: GPL V2
* http://www.icdev.com.cn/?2213/
* Host chip: ADUC7026
**********************************************/
* modified: 2007-08-08
* Author : awake
* Copyright: GPL V2
* http://www.icdev.com.cn/?2213/
* Host chip: ADUC7026
**********************************************/
最后
以上就是欢喜饼干为你收集整理的分析TCP/IP协议栈代码之ARP(STM32平台)的全部内容,希望文章能够帮你解决分析TCP/IP协议栈代码之ARP(STM32平台)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复