学习TCP/IP的第2步:ICMP数据包

学习TCP/IP,最好找ENC28J60这样的芯片,因为它没有硬件TCP/IP栈,有利于我们深入学习各种数据包的结构。我学习过W5500,它有硬件TCP/IP栈,学完之后,并不了解以太网数据的结构。在接触uip之后,我才明白以太网结构。

在"https://blog.csdn.net/weixin_42550185/article/details/156680883?spm=1001.2014.3001.5502"已经介绍了ARP数据包,这里主要讲ICMP数据包,实现ENC28J60和计算机互相使用ping命令来完成通讯。

三、学习TCP/IP的第2步是学习ICMP数据包

当ENC28J60主动ping计算机时,一定要关闭计算机的防火墙,否则,ICMP Ping会失败。因为ICMP Ping: ICMP(Internet Control Message Protocol)是一种网络层协议,主要用于发送错误消息和操作信息。Ping命令通过ICMP回显请求(Echo Request)和响应(Echo Reply)来测试主机连通性。

3.1、ICMP驱动

/*

计算机发送ping命令给ENC28J60

如果计算机不知道我的MAC地址,则先发送ARP请求数据包,再发送ICMP请求数据包;

如果计算机知道我的MAC地址,则先发送ICMP请求数据包,再发送ARP请求数据包;

1、接收的到的ARP请求数据包:

0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xB4 0x2E 0x99 0x59 0xEC 0x1E 0x08 0x06

0x00 0x01 0x08 0x00 0x06 0x04 0x00 0x01 0xB4 0x2E 0x99 0x59 0xEC 0x1E

0xC0 0xA8 0x01 0xBE 0x00 0x00 0x00 0x00 0x00 0x00 0xC0 0xA8 0x01 0x11

0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00

2、发送ARP应答数据包:

0xB4 0x2E 0x99 0x59 0xEC 0x1E 0x00 0x08 0xDC 0x11 0x11 0x02 0x08 0x06

0x00 0x01 0x08 0x00 0x06 0x04 0x00 0x02 0x00 0x08 0xDC 0x11 0x11 0x02

0xC0 0xA8 0x01 0x11 0xB4 0x2E 0x99 0x59 0xEC 0x1E 0xC0 0xA8 0x01 0xBE

3、接收到的ICMP请求数据包:

0x00 0x08 0xDC 0x11 0x11 0x02 0xB4 0x2E 0x99 0x59 0xEC 0x1E 0x08 0x00

0x45 0x00 0x00 0x3C 0x43 0xE3 0x00 0x00 0x40 0x01 0xB2 0xBE

0xC0 0xA8 0x01 0xBE

0xC0 0xA8 0x01 0x11

0x08 0x00 0x4D 0x56 0x00 0x01 0x00 0x05

abcdefghijklmnopqrstuvwabcdefghi

4、发送ICMP应答数据包:

0xB4 0x2E 0x99 0x59 0xEC 0x1E 0x00 0x08 0xDC 0x11 0x11 0x02 0x08 0x00

0x45 0x00 0x00 0x3C 0x00 0x01 0x00 0x00 0x40 0x01 0xF6 0xA0

0xC0 0xA8 0x01 0x11

0xC0 0xA8 0x01 0xBE

0x00 0x00 0x55 0x56 0x00 0x01 0x00 0x05

abcdefghijklmnopqrstuvwabcdefghi

5、接收到的ICMP请求数据包:

0x00 0x08 0xDC 0x11 0x11 0x02 0xB4 0x2E 0x99 0x59 0xEC 0x1E 0x08 0x00

0x45 0x00 0x00 0x3C 0x43 0xE4 0x00 0x00 0x40 0x01 0xB2 0xBD

0xC0 0xA8 0x01 0xBE

0xC0 0xA8 0x01 0x11

0x08 0x00 0x4D 0x55 0x00 0x01 0x00 0x06

abcdefghijklmnopqrstuvwabcdefghi

6、发送ICMP应答数据包:

0xB4 0x2E 0x99 0x59 0xEC 0x1E 0x00 0x08 0xDC 0x11 0x11 0x02 0x08 0x00

0x45 0x00 0x00 0x3C 0x00 0x02 0x00 0x00 0x40 0x01 0xF6 0x9F

0xC0 0xA8 0x01 0x11

0xC0 0xA8 0x01 0xBE

0x00 0x00 0x55 0x55 0x00 0x01 0x00 0x06

abcdefghijklmnopqrstuvwabcdefghi

7、接收到的ICMP请求数据包:

0x00 0x08 0xDC 0x11 0x11 0x02 0xB4 0x2E 0x99 0x59 0xEC 0x1E 0x08 0x00

0x45 0x00 0x00 0x3C 0x43 0xE5 0x00 0x00 0x40 0x01 0xB2 0xBC

0xC0 0xA8 0x01 0xBE

0xC0 0xA8 0x01 0x11

0x08 0x00 0x4D 0x54 0x00 0x01 0x00 0x07

abcdefghijklmnopqrstuvwabcdefghi

8、发送ICMP应答数据包:

0xB4 0x2E 0x99 0x59 0xEC 0x1E 0x00 0x08 0xDC 0x11 0x11 0x02 0x08 0x00

0x45 0x00 0x00 0x3C 0x00 0x03 0x00 0x00 0x40 0x01 0xF6 0x9E

0xC0 0xA8 0x01 0x11

0xC0 0xA8 0x01 0xBE

0x00 0x00 0x55 0x54 0x00 0x01 0x00 0x07

abcdefghijklmnopqrstuvwabcdefghi

9、接收到的ICMP请求数据包:

0x00 0x08 0xDC 0x11 0x11 0x02 0xB4 0x2E 0x99 0x59 0xEC 0x1E 0x08 0x00

0x45 0x00 0x00 0x3C 0x43 0xE6 0x00 0x00 0x40 0x01 0xB2 0xBB

0xC0 0xA8 0x01 0xBE

0xC0 0xA8 0x01 0x11

0x08 0x00 0x4D 0x53 0x00 0x01 0x00 0x08

abcdefghijklmnopqrstuvwabcdefghi

10、发送ICMP应答数据包:

0xB4 0x2E 0x99 0x59 0xEC 0x1E 0x00 0x08 0xDC 0x11 0x11 0x02 0x08 0x00

0x45 0x00 0x00 0x3C 0x00 0x04 0x00 0x00 0x40 0x01 0xF6 0x9D

0xC0 0xA8 0x01 0x11

0xC0 0xA8 0x01 0xBE

0x00 0x00 0x55 0x53 0x00 0x01 0x00 0x08

abcdefghijklmnopqrstuvwabcdefghi

11、接收"来自计算机的ARP请求"

0x00 0x08 0xDC 0x11 0x11 0x02 0xB4 0x2E 0x99 0x59 0xEC 0x1E 0x08 0x06

0x00 0x01 0x08 0x00 0x06 0x04 0x00 0x01 0xB4 0x2E 0x99 0x59 0xEC 0x1E

0xC0 0xA8 0x01 0xBE 0x00 0x08 0xDC 0x11 0x11 0x02 0xC0 0xA8 0x01 0x11

0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00

12、发送ARP应答

0xB4 0x2E 0x99 0x59 0xEC 0x1E 0x00 0x08 0xDC 0x11 0x11 0x02 0x08 0x06

0x00 0x01 0x08 0x00 0x06 0x04 0x00 0x02 0x00 0x08 0xDC 0x11 0x11 0x02

0xC0 0xA8 0x01 0x11 0xB4 0x2E 0x99 0x59 0xEC 0x1E 0xC0 0xA8 0x01 0xBE

*/

struct uip_icmpIPV4_hdr

{

u8 destMAC_Address[6]; //存放目的MAC地址

u8 srcMAC_Address[6]; //存放源MAC地址

u16 type;

//太网帧的类型

//0x0800表示后面跟着的是IPV4数据包;

//0x0806表示后面跟着的是ARP数据包;

//0x86dd表示后面跟着的是IPV6数据包;

///////IPV4头部(IPv4 header),占20个字节///////

u8 vhl;

//IP版本和IP头部长度:

//版本(Version):0x45 的高4位为0x04表示IPv4版本

//头部长度(Header Length):低4位为0x05,表示IPv4头部长度,单位为32位字

//即IP头部/IPV4头部为:5 * 4 = 20 字节。

u8 tos;//服务类型

u8 len[2];//从"vhl开始到用户数据"的字节总数

u8 ipid[2];//标识,2字节,用于分片重组

u8 ipoffset[2];//片偏移,2字节,包含DF/MF标志和偏移量

u8 ttl;//生存时间

u8 protocol;//协议:如1表示ICMP,6表示TCP,17表示UDP

u16 ipchksum;//IP检验和

u16 SendIP_Address[2];//发送方IP地址,4字节

u16 ReceiveIP_Address[2];//接收方IP地址,4字节

//////ICMP (echo) header/////

u8 ICMPtype;

//ICMP请求填ICMP_ECHO=8,ICMP应答填ICMP_ECHO_REPLY=0;

u8 icode; //这里填0即可

u16 icmpchksum; //包括数据在内的整个ICMP数据包的校验和

u16 id; //id可以固定为1

u16 seqno;//序列号

};

#define ICMP_ECHO_REPLY 0 //ICMP应答

#define ICMP_ECHO 8 //ICMP请求

typedef u16 uip_ip6addr_t[8];

//uip_ip6addr_t[]数组有8个元素,每个元素都是u16型。

//使用typedef修饰后,uip_ip6addr_t就变成了数据类型,可以用来声明u16型数组,且数组元素数量为8个

struct uip_icmpIPV6_hdr

{

/* IPv6 header. */

u8 vtc;

u8 tcf;

u16 flow;

u8 len[2];

u8 protocol, ttl;

uip_ip6addr_t srcipaddr, destipaddr;

/* ICMP (echo) header. */

u8 type; //ICMP请求填ICMP_ECHO=8,ICMP应答填ICMP_ECHO_REPLY=0;

u8 icode; //这里填0即可

u16 icmpchksum; //包括数据在内的整个ICMP数据包的校验和

u8 flags;

u8 reserved1;

u8 reserved2;

u8 reserved3;

u8 icmp6data[16];

u8 options[1];

};

/*

ICMP Ping: ICMP(Internet Control Message Protocol)是一种网络层协议,

主要用于发送错误消息和操作信息。Ping命令通过ICMP回显请求(Echo Request)

和响应(Echo Reply)来测试主机连通性。

*/

u16 ICMP_SequenceNumber;

//计算"ICMP4头数据"的校验和

u16 ICMP4_TX_chksum(void)

{

u16 upper_layer_len;

u16 sum;

struct uip_icmpIPV4_hdr *pICMPHeaderTX;

pICMPHeaderTX=(struct uip_icmpIPV4_hdr *)&TCP_TX_buf[0];

upper_layer_len = ((u16)(pICMPHeaderTX->len[0]) << 8) + pICMPHeaderTX->len[1];

upper_layer_len = upper_layer_len - UIP_IPH_LEN;

//上层长度upper_layer_len = ICMP报文总长度 - IP头的大小20

sum=0;

sum = chksum(sum, &TCP_TX_buf[UIP_IPH_LEN + UIP_LLH_LEN],upper_layer_len);

//&TCP_TX_buf[34]为TCP头部在TCP_TX_buf[]中的首地址

/*累加"TCP头和数据"时需要考虑加法进位。 Sum TCP header and data. */

return (sum == 0) ? 0xffff : HTONS(sum);

}

//计算"ICMP头数据"的校验和

u16 ICMP4_RX_chksum(void)

{

u16 upper_layer_len;

u16 sum;

struct uip_icmpIPV4_hdr *pICMPHeaderRX;

pICMPHeaderRX=(struct uip_icmpIPV4_hdr *)&TCP_RX_buf[0];

upper_layer_len = ((u16)(pICMPHeaderRX->len[0]) << 8) + pICMPHeaderRX->len[1];

upper_layer_len = upper_layer_len - UIP_IPH_LEN;

//上层长度upper_layer_len = ICMP报文总长度 - IP头的大小20

sum=0;

sum = chksum(sum, &TCP_RX_buf[UIP_IPH_LEN + UIP_LLH_LEN],upper_layer_len);

//&TCP_RX_buf[35]为TCP头部在TCP_RX_buf[]中的首地址

return (sum == 0) ? 0xffff : HTONS(sum);

}

//"接收ICMP数据包之ICMP4头部校验和"正确,返回1

u8 Check_the_ICMP4_header_checksume(void)

{

u8 ret;

u16 sum;

u16 upper_layer_len;

struct uip_icmpIPV4_hdr *pICMPHeaderRX;

pICMPHeaderRX=(struct uip_icmpIPV4_hdr *)&TCP_RX_buf[0];

upper_layer_len = ((u16)(pICMPHeaderRX->len[0]) << 8) + pICMPHeaderRX->len[1];

upper_layer_len = upper_layer_len - UIP_IPH_LEN;

//上层长度upper_layer_len = ICMP报文总长度 - IP头的大小20

sum=0;

sum = chksum(sum, &TCP_RX_buf[UIP_IPH_LEN + UIP_LLH_LEN],upper_layer_len);

//&TCP_RX_buf[35]为TCP头部在TCP_RX_buf[]中的首地址

sum = (sum == 0) ? 0xffff : HTONS(sum);

ret=0;

if(sum == 0xffff)//"ICMP4头部校验和"正确

{

ret=1;//"ICMP4头部校验和"正确,返回1

return(ret);

}

return(ret);

}

//"接收ICMP数据包之ICMP6头部校验和"正确,返回1

//未测试

u8 Check_the_ICMP6_header_checksume(void)

{

u8 ret;

u16 sum;

u16 upper_layer_len;

struct uip_icmpIPV6_hdr *pICMPHeaderRX;

pICMPHeaderRX=(struct uip_icmpIPV6_hdr *)&TCP_RX_buf[0];

upper_layer_len = (((u16)(pICMPHeaderRX->len[0]) << 8) + pICMPHeaderRX->len[1]);

sum = upper_layer_len + UIP_PROTO_ICMP6;

/*累加"上层长度和IP协议"时,无需考虑进位。

IP protocol and length fields. This addition cannot carry. */

sum = chksum(sum, (u8 *)&pICMPHeaderRX->srcipaddr[0],2 * sizeof(uip_ip6addr_t));

/* 发送方IP和接收方IP需要考虑累加时考虑进位。Sum IP source and destination addresses. */

sum = chksum(sum, &TCP_RX_buf[UIP_IPH_LEN + UIP_LLH_LEN],upper_layer_len);

//&TCP_RX_buf[34]为ICMP头部在TCP_RX_buf[]中的首地址

/*累加"ICMP头和数据"时需要考虑加法进位。 Sum TCP header and data. */

sum = (sum == 0) ? 0xffff : HTONS(sum);

ret=0;

if(sum == 0xffff)//"ICMP6头部校验和"正确

{

ret=1;//"ICMP6头部校验和"正确,返回1

return(ret);

}

return(ret);

}

//当计算机使用"ping 192.168.1.17"时,调用该函数

void Receive_ICMP_REQUEST_And_Send_ICMP_Reply(void)

{

struct uip_icmpIPV4_hdr *pICMPHeaderRX;

struct uip_icmpIPV4_hdr *pICMPHeaderTX;

u16 tmpIP4_Address[2];

u8 i,ch1;

pICMPHeaderRX=(struct uip_icmpIPV4_hdr *)&TCP_RX_buf[0];

ch1=0;

if( (u8)pICMPHeaderRX->type==(u8)(UIP_ETHTYPE_IP4>>8) ) ch1++;

if( (u8)(pICMPHeaderRX->type>>8)==(u8)UIP_ETHTYPE_IP4 ) ch1++;

if(pICMPHeaderRX->ICMPtype == ICMP_ECHO) ch1++;

//是"ICMP请求数据包"

if( TCP_RX_Length>=sizeof(struct uip_icmpIPV4_hdr) ) ch1++;

if( (u8)pICMPHeaderRX->ReceiveIP_Address[0]==My_Network_Information.ip[0] ) ch1++;

if( (u8)(pICMPHeaderRX->ReceiveIP_Address[0]>>8)==My_Network_Information.ip[1]) ch1++;

if( (u8)pICMPHeaderRX->ReceiveIP_Address[1]==My_Network_Information.ip[2]) ch1++;

if( (u8)(pICMPHeaderRX->ReceiveIP_Address[1]>>8)==My_Network_Information.ip[3] ) ch1++;

if(ch1==8)//是"ICMP请求数据包"

{

pICMPHeaderTX=(struct uip_icmpIPV4_hdr *)&TCP_TX_buf[0];

/////////以太网头部//////////////

//设置"目的MAC地址"

pICMPHeaderTX->destMAC_Address[0]=pICMPHeaderRX->srcMAC_Address[0];

pICMPHeaderTX->destMAC_Address[1]=pICMPHeaderRX->srcMAC_Address[1];

pICMPHeaderTX->destMAC_Address[2]=pICMPHeaderRX->srcMAC_Address[2];

pICMPHeaderTX->destMAC_Address[3]=pICMPHeaderRX->srcMAC_Address[3];

pICMPHeaderTX->destMAC_Address[4]=pICMPHeaderRX->srcMAC_Address[4];

pICMPHeaderTX->destMAC_Address[5]=pICMPHeaderRX->srcMAC_Address[5];

memcpy(pICMPHeaderTX->srcMAC_Address, My_Network_Information.mac, 6);

//设置"源MAC地址"为ENC28J60的MAC地址

pICMPHeaderTX->type = HTONS(UIP_ETHTYPE_IP4);

//设置太网帧的类型为0x0800,表示后面跟着的是IPV4数据包;

/////////IPV4头部//////////////

pICMPHeaderTX->vhl=0x45;//设置"IP版本和头部长度"

pICMPHeaderTX->tos=0x00;//设置"服务类型"

pICMPHeaderTX->len[0]=pICMPHeaderRX->len[0]; pICMPHeaderTX->len[1]=pICMPHeaderRX->len[1];

//设置ICMP数据包的"IP头和ICMP头的长度"

++My_IPID;

pICMPHeaderTX->ipid[0] = My_IPID >> 8;

pICMPHeaderTX->ipid[1] = My_IPID & 0xff;//设置"标识"

pICMPHeaderTX->ipoffset[0] =0;

pICMPHeaderTX->ipoffset[1] = 0;//设置"标志字段和片偏移字段"

pICMPHeaderTX->ttl=UIP_TTL;//设置"生存时间值"

pICMPHeaderTX->protocol=UIP_PROTO_ICMP;

uip_ipaddr(tmpIP4_Address,My_Network_Information.ip); //准备"发送方ip地址"

pICMPHeaderTX->SendIP_Address[0] = tmpIP4_Address[0];pICMPHeaderTX->SendIP_Address[1] = tmpIP4_Address[1];

//设置"发送方ip地址"

pICMPHeaderTX->ReceiveIP_Address[0] = pICMPHeaderRX->SendIP_Address[0];

pICMPHeaderTX->ReceiveIP_Address[1] = pICMPHeaderRX->SendIP_Address[1];

//设置"接收方ip地址"

pICMPHeaderTX->ipchksum = 0;

pICMPHeaderTX->ipchksum = ~(ipV4chksum(&TCP_TX_buf[UIP_LLH_LEN]));

//设置"IPV4检验和"

/////////ICMP头部//////////////

pICMPHeaderTX->ICMPtype=ICMP_ECHO_REPLY;//ICMP报文类型:应答类型

pICMPHeaderTX->icode=0;

pICMPHeaderTX->id=pICMPHeaderRX->id;

pICMPHeaderTX->seqno=pICMPHeaderRX->seqno;

//添加用户数据"abcdefghijklmnopqrstuvwabcdefghi"

for(i=sizeof(struct uip_icmpIPV4_hdr);i<TCP_RX_Length;i++)

{

TCP_TX_buf[i]=TCP_RX_buf[i];

}

/*

if(pICMPHeaderRX->icmpchksum >= HTONS(0xffff - (ICMP_ECHO << 8)))

{//设置"ICMP应答数据的校验和"

pICMPHeaderRX->icmpchksum += HTONS(ICMP_ECHO << 8) + 1;

}

else

{//设置"ICMP应答数据的校验和"

pICMPHeaderRX->icmpchksum += HTONS(ICMP_ECHO << 8);

}

pICMPHeaderTX->icmpchksum=pICMPHeaderRX->icmpchksum;

*/

pICMPHeaderTX->len[0]=pICMPHeaderRX->len[0];

pICMPHeaderTX->len[1]=pICMPHeaderRX->len[1];

//设置ICMP数据包的"IP头部,ICMP头部和用户数据的长度"

pICMPHeaderTX->icmpchksum=0; //准备ICMP4校验和

pICMPHeaderTX->icmpchksum=~ICMP4_TX_chksum();

//设置ICMP4校验和

TCP_TX_Length = TCP_RX_Length;

ENC28J60_Packet_Send(TCP_TX_Length,TCP_TX_buf);

delay_us(TCP_TX_Length);

#if ICMP_Debug == 1

Print_Receive_Package(TCP_RX_buf,TCP_RX_Length);

Print_Send_Package(TCP_TX_buf,TCP_TX_Length);

#endif

TCP_TX_Length=0;

}

}

u8 Receive_ICMP_REPLY(void)

{

struct uip_icmpIPV4_hdr *pICMPHeaderRX;

u8 i,ch1;

pICMPHeaderRX=(struct uip_icmpIPV4_hdr *)&TCP_RX_buf[0];

ch1=0;

if( (u8)pICMPHeaderRX->type==(u8)(UIP_ETHTYPE_IP4>>8) ) ch1++;

if( (u8)(pICMPHeaderRX->type>>8)==(u8)UIP_ETHTYPE_IP4 ) ch1++;

if(pICMPHeaderRX->ICMPtype == ICMP_ECHO_REPLY) ch1++;

//是"ICMP应答数据包"

if( TCP_RX_Length>=sizeof(struct uip_icmpIPV4_hdr) ) ch1++;

if(pICMPHeaderRX->vhl==0x45) ch1++;//是"IPV4头"

if( Check_the_ipV4_header_checksume(&TCP_TX_buf[UIP_LLH_LEN]) ) ch1++;

//"IPV4头部校验和"正确,返回1

if( Check_the_ICMP4_header_checksume() ) ch1++;

//"接收ICMP数据包之IPV4头部校验和"正确,返回1

if( (u8)pICMPHeaderRX->ReceiveIP_Address[0]==My_Network_Information.ip[0] ) ch1++;

if( (u8)(pICMPHeaderRX->ReceiveIP_Address[0]>>8)==My_Network_Information.ip[1]) ch1++;

if( (u8)pICMPHeaderRX->ReceiveIP_Address[1]==My_Network_Information.ip[2]) ch1++;

if( (u8)(pICMPHeaderRX->ReceiveIP_Address[1]>>8)==My_Network_Information.ip[3] ) ch1++;

if(ch1==11)//是"ICMP应答数据包"

{

#if ICMP_Debug == 1

if(TCP_RX_Length)

{

Print_Send_Package(TCP_TX_buf,TCP_TX_Length);

Print_Receive_Package(TCP_RX_buf,TCP_RX_Length);

}

#endif

i=1;

TCP_TX_Length=0;

}

else i=0;

return(i);

}

/*

发送ICMP请求:

0xB4 0x2E 0x99 0x59 0xEC 0x1E 0x00 0x08 0xDC 0x11 0x11 0x02 0x08 0x00

0x45 0x00 0x00 0x3C 0x00 0x02 0x00 0x00 0x40 0x01 0xF6 0x9F

0xC0 0xA8 0x01 0x11

0xC0 0xA8 0x01 0xBE

0x08 0x00 0x4B 0x5B 0x00 0x01 0x02 0x00

abcdefghijklmnopqrstuvwabcdefghi

接收ICMP应答:

0x00 0x08 0xDC 0x11 0x11 0x02 0xB4 0x2E 0x99 0x59 0xEC 0x1E 0x08 0x00

0x45 0x00 0x00 0x3C 0x1E 0xB8 0x00 0x00 0x80 0x01 0x97 0xE9

0xC0 0xA8 0x01 0xBE

0xC0 0xA8 0x01 0x11

0x00 0x00 0x54 0x5B 0x00 0x01 0x01 0x00

abcdefghijklmnopqrstuvwabcdefghi

*/

const char ICMP_REQUEST_Data_REG[]="abcdefghijklmnopqrstuvwabcdefghi";

//注意:要关闭计算机的防火墙,否则通讯会失败

void Send_ICMP_REQUEST_In_Known_RemoteMACaddress(u8 *tmpRemoteIP_Address)

{

struct arp_entry *pARP_Table;

u8 find,ch;

struct uip_icmpIPV4_hdr *pICMPHeaderTX;

u16 tmpIP4_Address[2],i;

uip_ipaddr(tmpIP4_Address,tmpRemoteIP_Address); //准备远程IP地址

find=0;

for(pARP_Table = &ARP_Table[0]; pARP_Table <= &ARP_Table[UIP_ARPTable_Size - 1];++pARP_Table)

{//在ARP_Table[]查找

ch=0;

if(tmpIP4_Address[0]==pARP_Table->RemoteIP_Address[0]) ch++;

if(tmpIP4_Address[1]==pARP_Table->RemoteIP_Address[1]) ch++;

if( ch==2 )

{

find=1;

break;

}

}

if(find)//如果ARP表里有与这个IP地址对应的MAC地址

{

pICMPHeaderTX=(struct uip_icmpIPV4_hdr *)&TCP_TX_buf[0];

/////////以太网头部//////////////

memcpy(pICMPHeaderTX->destMAC_Address,pARP_Table->RemoteMAC_Address, 6);

//设置"目的MAC地址"

memcpy(pICMPHeaderTX->srcMAC_Address, My_Network_Information.mac, 6);

//设置"源MAC地址"为ENC28J60的MAC地址

pICMPHeaderTX->type = HTONS(UIP_ETHTYPE_IP4);

//设置太网帧的类型为0x0800,表示后面跟着的是IPV4数据包;

/////////ICMP头部//////////////

pICMPHeaderTX->ICMPtype=ICMP_ECHO;//ICMP请求

pICMPHeaderTX->icode=0;

pICMPHeaderTX->id=HTONS(1);

ICMP_SequenceNumber++;

i=(u8)ICMP_SequenceNumber;

i=i<<8;

i=i+(u8)(ICMP_SequenceNumber>>8);

pICMPHeaderTX->seqno=HTONS(i);//设置发送序列号

//添加用户数据"abcdefghijklmnopqrstuvwabcdefghi"

i=sizeof(struct uip_icmpIPV4_hdr);//计算"以太网头部,IP头部和ICMP头部"的长度

strcpy( (char*)(&TCP_TX_buf[i]),ICMP_REQUEST_Data_REG );

////////计算"ICMP数据包"字节总数////////

TCP_TX_Length=strlen(ICMP_REQUEST_Data_REG);//计算用户数据的长度

TCP_TX_Length=TCP_TX_Length+i;//计算"ICMP数据包"字节总数

i=TCP_TX_Length-UIP_LLH_LEN;

//计算ICMP数据包的"IP头部,ICMP头部和用户数据"的长度

pICMPHeaderTX->len[0]=(u8)(i>>8);

pICMPHeaderTX->len[1]=(u8)(i);

//设置ICMP数据包的"IP头部,ICMP头部和用户数据" 的长度

pICMPHeaderTX->icmpchksum=0;

//准备计算"ICMP4头部和用户数数据"的校验和

pICMPHeaderTX->icmpchksum=~ICMP4_TX_chksum();

//设置"ICMP4头部和用户数数据"的校验和

/////////IPV4头部//////////////

pICMPHeaderTX->vhl=0x45;//设置"IP版本和头部长度"

pICMPHeaderTX->tos=0x00;//设置"服务类型"

My_IPID++;//IP数据包唯一ID

pICMPHeaderTX->ipid[0] = My_IPID >> 8;

pICMPHeaderTX->ipid[1] = My_IPID & 0xff;//设置"IP数据包唯一ID"

pICMPHeaderTX->ipoffset[0] =0;

pICMPHeaderTX->ipoffset[1] = 0;//设置"标志字段和片偏移字段"

pICMPHeaderTX->ttl=UIP_TTL;//设置"生存时间值"

pICMPHeaderTX->protocol=UIP_PROTO_ICMP;

uip_ipaddr(tmpIP4_Address,My_Network_Information.ip); //准备"发送方ip地址"

pICMPHeaderTX->SendIP_Address[0] = tmpIP4_Address[0];

pICMPHeaderTX->SendIP_Address[1] = tmpIP4_Address[1];

//设置"发送方ip地址"

uip_ipaddr(tmpIP4_Address,tmpRemoteIP_Address); //准备远程IP地址

pICMPHeaderTX->ReceiveIP_Address[0] = tmpIP4_Address[0];

pICMPHeaderTX->ReceiveIP_Address[1] = tmpIP4_Address[1];

//设置"接收方ip地址"

pICMPHeaderTX->ipchksum = 0;

pICMPHeaderTX->ipchksum = ~(ipV4chksum(&TCP_TX_buf[UIP_LLH_LEN]));

//设置"IPV4检验和"

ENC28J60_Packet_Send(TCP_TX_Length,TCP_TX_buf);

delay_us(TCP_TX_Length*2);//延时1个来回的时间

}

}

//如果知道计算机的MAC地址,则调用该函数,向计算机发送PING命令

//注意:要关闭计算机的防火墙,否则通讯会失败

void Send_PING_Command_In_Known_RemoteMACaddress(u8 *tmpRemoteIP_Address)

{

u8 loop,ret;

u16 cnt;

u8 i;

ENC28J60_Packet_Receive(MAX_UIP_BUFSIZE,TCP_RX_buf);//清空ENC28J60的接收缓存

for(i=0;i<4;)

{

Send_ICMP_REQUEST_In_Known_RemoteMACaddress(tmpRemoteIP_Address);

TCP_RX_Length=ENC28J60_Packet_Receive(MAX_UIP_BUFSIZE,TCP_RX_buf);

if(TCP_RX_Length)

{

ret=Receive_ICMP_REPLY();

if(ret)

{

i++;

}

}

}

ret=0;cnt=3000;loop=10;

while(loop)

{

if(cnt>=3000)

{

Send_ARP_REQUEST_In_Known_RemoteMACaddress(tmpRemoteIP_Address);

//发送ARP请求

cnt=0;

loop--;

}

ret=Receive_ARP_REPLY(tmpRemoteIP_Address);//接收ARP应答

if(ret==1)//读到远程设备的MAC地址

break;

cnt++;

}

}

//如果不知道计算机的MAC地址,则调用该函数,向计算机发送PING命令

//注意:要关闭计算机的防火墙,否则通讯会失败

void Send_PING_Command_In_Unknown_RemoteMACaddress(u8 *tmpRemoteIP_Address)

{

u8 loop,ret;

u16 cnt;

struct arp_hdr *pARPHeader;

ENC28J60_Packet_Receive(MAX_UIP_BUFSIZE,TCP_RX_buf);//清空ENC28J60的接收缓存

ret=0;cnt=3000;loop=10;

while(loop)

{

if(cnt>=3000)

{

Send_ARP_REQUEST_In_Unknown_RemoteMACaddress(tmpRemoteIP_Address);

//发送ARP请求

cnt=0;

loop--;

}

ret=Receive_ARP_REPLY(tmpRemoteIP_Address);//接收ARP应答

if(ret==1)//读到远程设备的MAC地址

break;

cnt++;

}

if(ret==1)//读到远程设备的MAC地址

{

pARPHeader=(struct arp_hdr *)&TCP_RX_buf[0];

update_ARP_Table(pARPHeader->SendIP_Address,pARPHeader->SendMAC_Address);

//更新ARP表

Print_ARP_Table();//打印ARP表

ENC28J60_Packet_Receive(MAX_UIP_BUFSIZE,TCP_RX_buf);//清空ENC28J60的接收缓存

for(cnt=0;cnt<4;)

{

Send_ICMP_REQUEST_In_Known_RemoteMACaddress(tmpRemoteIP_Address);

TCP_RX_Length=ENC28J60_Packet_Receive(MAX_UIP_BUFSIZE,TCP_RX_buf);

if(TCP_RX_Length)

{

ret=Receive_ICMP_REPLY();

if(ret)

{

cnt++;

}

}

}

}

}

3.2、测试ICMP的main.c

#include "stm32f10x.h"//使能uint8_t,uint16_t,uint32_t,uint64_t,int8_t,int16_t,int32_t,int64_t

#include "stdio.h" //getchar(),putchar(),scanf(),printf(),puts(),gets(),sprintf()

#include "delay.h"

#include "USART1.h"

#include "IWDG.h"

#include "ENC28J60_Variable.h"

#include "Timer4.h"

#include "enc28j60.h"

#include "ENC28J60_Interface.h"

#include "ARP.h"

//PLL配置: PLLCLK = HSE * 6 = 72 MHz,2025年11月8日修改

u8 Main_cnt;

const char CPU_Reset_REG[]="\r\nCPU reset!\r\n";

int main(void)

{

HSE_SetSysClock(RCC_PLLMul_6); // 设置系统时钟为12MHz * 6 = 72MHz

// SystemInit();//在使用IAP时,该语句需要屏蔽

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4

USART1_Serial_Interface_Enable(115200);

SysRstSrcRecord();//系统复位记录

delay_ms(1000);

while( Network_Init() )

{

}

Remote_ip[0]=192;Remote_ip[1]=168;Remote_ip[2]=1;Remote_ip[3]=190;

//远程IP地址,如:"192.168.1.190"

Send_PING_Command_In_Unknown_RemoteMACaddress(Remote_ip);

while(1)

{

TCP_RX_Length=ENC28J60_Packet_Receive(MAX_UIP_BUFSIZE,TCP_RX_buf);

//接收来自计算机的ping命令

if(TCP_RX_Length)

{

Receive_ARP_REQUEST_And_Send_ARP_Reply(); //接收"来自计算机的ARP请求"

Receive_ICMP_REQUEST_And_Send_ICMP_Reply();

//当计算机使用"ping 192.168.1.17"时,调用该函数

}

}

}

3.3、测试结果

相关推荐
近津薪荼20 小时前
优选算法——双指针专题7(单调性)
c++·学习·算法
峥嵘life20 小时前
Android 16 EDLA测试STS模块
android·大数据·linux·学习
赛德传动20 小时前
使用SNJ齿轮齿条升降机时,有哪些安全防护措施?
网络·安全·制造
invicinble20 小时前
学习的门道和思路
java·开发语言·学习
Zach_yuan20 小时前
UDP网络编程:从入门到精通
linux·网络·网络协议·udp
发光小北20 小时前
MS_F155_AM (TW)/MS_F155_VM (TW)特点与功能介绍
网络
B2_Proxy20 小时前
破解TikTok运营困境:静态住宅IP与封号限流深度解析
网络·网络协议·tcp/ip
wheeldown21 小时前
【Linux网络编程】 Linux TCP网络编程:客户端开发+守护进程实战
linux·网络·tcp/ip
sayang_shao21 小时前
Rust多线程编程学习笔记
笔记·学习·rust