计算机网络踩坑:ARP 响应与 ICMP 回包开发

ARP 响应与 ICMP 回包开发

因公司项目需求,需要使用UDP服务与UDP组播,并且监听多端口数据,最开始没考虑那么多,直接就上了 LWIP 轻量级协议栈,参考正点原子的STM32F4开发板的 LWIP 移植,完成之后,实现单独的 UDP 服务或单独的 UDP 组播,是完全没有问题的,不过问题就在于既需要使用 UDP 多端口服务,又要使用 UDP 多端口组播,没有相对应的使用示例,自己对 LWIP 不是很了解,怕把问题搞复杂了,于是自己参考了 LWIP 的部分读包技巧与公司提供的上一版程序的开发思路,重新自己开发ARP响应与ICMP回包服务,下面是踩过的一些坑,这里记录一下。

一、关于 ARP 响应中遇到的一些问题

  1. ARP 响应之前,判断一下目标 IP 是否与自己相关,如果不相关,就把报文给抛掉,不要一遇见 ARP 请求报文就回复,可能会造成安全隐患,同时对系统也是一大笔开销,开发的时候可能会忽略这种小问题;
  2. ARP 响应的时候,不仅需要把目标和源的 IP、MAC 对调,记得修改 ARP 的操作码,不然就是一个无效的响应;
  3. 记得填充自己的 IP 与 MAC 到源地址,不然返回的数据中会存在问题,PC 端的 ARP 会得到一个错误的数据地址,数据请求可能会出现问题。

二、关于 ICMP 回包开发中遇到的一些问题

  1. 根据参考案例 ICMP 回包之前会更新一次 ARP 数据表,为什么会这样操作,我也不懂,这里略过;
  2. ICMP 回包的第一步,将以太网头部的源 MAC 地址改为本机 MAC 地址,将以太网头部的目标 MAC 地址改为接收数据帧中的源 MAC 地址(也可以将接收数据帧中的目标 MAC 地址与源 MAC 地址互换,个人更偏向于括号外的做法),协议类型不变,照搬;
  3. 下一步是更新 IP 头部,版本号与字节长度保持不变 0x45,服务类型不变,ID 可执行加一操作,也可以保持不变,偏移不变 0x00,TTL 根据实际需求调整,协议类型也不变,校验位先填充 0x0000,等 IP 头部完全填充后再做修改,源 IP 地址与目的 IP 地址和 MAC 一样,先将 IP 头部的源 IP 地址改为本机 IP 地址,再将 IP 头部的目标 IP 地址改为接收数据帧中的源 IP 地址(也可以将接收数据帧中的目标 IP 地址与源 IP地址互换,个人更偏向于括号外的做法),这个时候,IP 头部就算填充完成了;
  4. (重点)IP 头部完成了之后,一定要做 IP 头部的校验和运算,不然的话,用抓包工具能看见 ICMP 报文正常发送回包,但是 ping 不通,请求超时!!!校验和运算完了记得高低位互换,不然还是会出问题,这里卡了我一天,默认抓包工具是不做IP头部的校验和的,一直在找各种各样的原因,最后开启了IP头部的校验,才知道这里可能存在问题(为了避免一些不必要的麻烦,这里就不上图了),等这里校验没有问题了以后,ping 完全没有问题!!!
  5. 接下来就是 ICMP 部分了,这部分只需要做两个改动,一个是改 ICMP 操作位,一个是改校验位,操作和 IP 头部的校验一样,也是先将 ICMP 操作位改为 0x00, 然后校验位填充 0x0000 ,做完校验和后,将校验位高低位互换,然后重新填充,就 OK 啦!

经过以上流程以后,ping 功能就算完工啦,感谢各位的观看,为了一些不必要的麻烦,这里就不上图了,代码处理在下面,可以参考,仅提供功能的核心模块逻辑,剩下的需要靠自己去完善啦,大部分思路是参考的 LWIP, 但是 LWIP,无法实现我的功能需求(更大的可能性是我太菜,没有把LWIP玩明白),所以得自己重写。

公共部分

struct eth_hdr {
	struct mac_addr dest; /* Destination MAC address */
	struct mac_addr src;  /* Source MAC address      */
	uint16_t        type; /* Ethernet protocol type  */
};

enum
{
	ETH_PROTO_ARP = 0,
	ETH_PROTO_IP,
	ETH_PROTO_NUM
};

u8 ethhdr_pack(u8* buf, u8* dst_mac, u8* src_mac, u8 type)	/* 使用前请添加assert()断言数组长度大于13 */
{
	u8 eth_offset,
	   tmp_data[ETHHDR_FRAMETYPE_LEN] = {0};
	
	switch(type)
	{
		case ETH_PROTO_ARP:
			tmp_data[0] = PP_HTONS(ETHTYPE_ARP) & 0xFF;
			tmp_data[1] = PP_HTONS(ETHTYPE_ARP) >> 8;
			break;
		case ETH_PROTO_IP:
			tmp_data[0] = PP_HTONS(ETHTYPE_IP) & 0xFF;
			tmp_data[1] = PP_HTONS(ETHTYPE_IP) >> 8;
			break;
		default:
			break;
	}
	
	eth_offset = 0;
	memcpy(buf + eth_offset, dst_mac, ETHHDR_DSTMAC_LEN);
	
	eth_offset += ETHHDR_DSTMAC_LEN;
	memcpy(buf + eth_offset, src_mac, ETHHDR_SRCMAC_LEN);
	
	eth_offset += ETHHDR_SRCMAC_LEN;
	memcpy(buf + eth_offset, tmp_data, ETHHDR_FRAMETYPE_LEN);
	
	eth_offset += ETHHDR_FRAMETYPE_LEN;
	return eth_offset;
}

arp 部分

/** the ARP message, see RFC 826 ("Packet format") */
struct etharp_hdr {
  uint16_t hwtype;
  uint16_t proto;
  uint8_t  hwlen;
  uint8_t  protolen;
  uint16_t opcode;
  struct mac_addr shwaddr;
  struct ip_addr  sipaddr;
  struct mac_addr dhwaddr;
  struct ip_addr  dipaddr;
};

void arp_answer(struct etharp_hdr *arphdr)
{
	u8  answer_buf[ARP_PACK_LEN],
	    tmp_data[2];
	u32 arp_offset;
	
	memset(answer_buf, 0, ARP_PACK_LEN);

	arp_offset = ethhdr_pack(answer_buf, arphdr->shwaddr.addr, mjipdev.mac, ETH_PROTO_ARP);
	
	memcpy(answer_buf + arp_offset, &arphdr->hwtype, ARPHDR_HARDTYPE_LEN);
	arp_offset += ARPHDR_HARDTYPE_LEN;

	memcpy(answer_buf + arp_offset, &arphdr->proto, ARPHDR_PROTOTYPE_LEN);
	arp_offset += ARPHDR_PROTOTYPE_LEN;

	memcpy(answer_buf + arp_offset, &arphdr->hwlen, ARPHDR_HARDLEN_LEN);
	arp_offset += ARPHDR_HARDLEN_LEN;

	memcpy(answer_buf + arp_offset, &arphdr->protolen, ARPHDR_PROTOLEN_LEN);
	arp_offset += ARPHDR_PROTOLEN_LEN;

	tmp_data[0] = ARP_REPLY >> 8;
	tmp_data[1] = ARP_REPLY & 0xFF;

	memcpy(answer_buf + arp_offset, tmp_data, ARPHDR_OPTION_LEN);
	arp_offset += ARPHDR_OPTION_LEN;

	memcpy(answer_buf + arp_offset, mjipdev.mac, ARPHDR_SRCMAC_LEN);
	arp_offset += ARPHDR_SRCMAC_LEN;
	
	memcpy(answer_buf + arp_offset, mjipdev.ip, ARPHDR_SRCEIP_LEN);
	arp_offset += ARPHDR_SRCEIP_LEN;
	
	memcpy(answer_buf + arp_offset, arphdr->shwaddr.addr, ARPHDR_DSTMAC_LEN);
	arp_offset += ARPHDR_DSTMAC_LEN;
	
	memcpy(answer_buf + arp_offset, arphdr->sipaddr.addr, ARPHDR_DSTEIP_LEN);
	
	memcpy(send_buf, answer_buf, ARP_PACK_LEN);
	server_send(ARP_PACK_LEN);

icmp 部分

struct ip_hdr {
	/* version / header length */
	uint8_t _v_hl;
	/* type of service */
	uint8_t _tos;
	/* total length */
	uint16_t _len;
	/* identification */
	uint16_t _id;
	/* fragment offset field */
	uint16_t _offset;
	/* time to live */
	uint8_t _ttl;
	/* protocol*/
	uint8_t _proto;
	/* checksum */
	uint16_t _chksum;
	/* source and destination IP addresses */
	struct ip_addr src;
	struct ip_addr dest;
};

struct icmp_info
{
	uint8_t  type;
	uint8_t  opcode;
	uint16_t chksum;
};

uint8_t iphdr_pack(uint8_t* buf, struct ip_hdr* iphdr)
{
	u8  tmp_data[2];
	u16 chksum;
	u32 ip_offset = 0,
		chksum_offset;
	
	memcpy(buf + ip_offset, &iphdr->_v_hl, IPHDR_VHDRLEN_LEN);
	ip_offset += IPHDR_VHDRLEN_LEN;

	memcpy(buf + ip_offset, &iphdr->_tos, IPHDR_TOS_LEN);
	ip_offset += IPHDR_TOS_LEN;

	memcpy(buf + ip_offset, &iphdr->_len, IPHDR_TOTALLEN_LEN);
	ip_offset += IPHDR_TOTALLEN_LEN;

	iphdr->_id += 0x0100;
	memcpy(buf + ip_offset, &iphdr->_id, IPHDR_ID_LEN);
	ip_offset += IPHDR_ID_LEN;

	tmp_data[0] = IPHDR_OFFSET_VAL & 0xFF;
	tmp_data[1] = IPHDR_OFFSET_VAL >> 8;

	memcpy(buf + ip_offset, tmp_data, IPHDR_OFFSET_LEN);
	ip_offset += IPHDR_OFFSET_LEN;

	tmp_data[0] = IPHDR_TTL_VAL;

	memcpy(buf + ip_offset, tmp_data, IPHDR_TTL_LEN);
	ip_offset += IPHDR_TTL_LEN;
	
	memcpy(buf + ip_offset, &iphdr->_proto, IPHDR_PROTO_LEN);
	ip_offset += IPHDR_PROTO_LEN;

	tmp_data[0] = 0x00;
	tmp_data[1] = 0x00;
	chksum_offset = ip_offset;

	memcpy(buf + ip_offset, &tmp_data, IPHDR_CHKSUM_LEN);
	ip_offset += IPHDR_CHKSUM_LEN;
	
	memcpy(buf + ip_offset, mjipdev.ip, IPHDR_SRCIP_LEN);
	ip_offset += IPHDR_SRCIP_LEN;

	memcpy(buf + ip_offset, &iphdr->src.addr, IPHDR_DSTIP_LEN);
	ip_offset += IPHDR_DSTIP_LEN;
	
	//IP 校验和
	chksum = checksum(buf, IPHDR_MIN_LEN, CHECKSUM_IP);
	chksum = PP_HTONS(chksum);	//校验反转,否则 PC 端不通过,请求超时
	memcpy(buf + chksum_offset, &chksum, IPHDR_CHKSUM_LEN);
	
	return ip_offset + ETHHDR_LEN;
}

void icmp_reply(u8* buf, u32 len)
{
	u8  answer_buf[SERVER_TX_BUFSIZE],
	    tmp_data[2];
	u16 chksum;
	u32 icmp_offset,
	    icmp_chksum_offset;	//用于存放 ICMP 的 checksum 校验和

	struct eth_hdr*   ethhdr;
	struct ip_hdr*    iphdr;
	
	ethhdr   = (struct eth_hdr*)buf;
	iphdr    = (struct ip_hdr*)((u8*)ethhdr + ETHHDR_LEN);

	memset(answer_buf, 0, len);

	icmp_offset = ethhdr_pack(answer_buf, ethhdr->src.addr, mjipdev.mac, ETH_PROTO_IP);
	
	icmp_offset = iphdr_pack(answer_buf + icmp_offset, iphdr);
	
	tmp_data[0] = ICMP_OPT_REPLY;
	tmp_data[1] = ICMP_CODE_REPLY;
	
	memcpy(answer_buf + icmp_offset, tmp_data, ICMP_OPTCODE_LEN);
	icmp_offset += ICMP_OPTCODE_LEN;

	tmp_data[0] = 0x00;
	tmp_data[1] = 0x00;
	icmp_chksum_offset = icmp_offset;

	memcpy(answer_buf + icmp_offset, &tmp_data, ICMP_CHKSUM_LEN);

	icmp_offset += ICMP_CHKSUM_LEN;
	
	memcpy(answer_buf + icmp_offset, buf + icmp_offset, len - icmp_offset);

	//ICMP 校验和
	chksum = checksum(answer_buf + ETHHDR_LEN + IPHDR_MIN_LEN, len - ETHHDR_LEN - IPHDR_MIN_LEN, CHECKSUM_IP);
	chksum = PP_HTONS(chksum);	//校验反转,否则 PC 端不通过,请求超时
	memcpy(answer_buf + icmp_chksum_offset, &chksum, IPHDR_CHKSUM_LEN);

	memcpy(send_buf, answer_buf, len);
	server_send(len);
}

学习分享,一起成长!以上为小编的实验分享,若存在不当之处,请批评指正!

你可能感兴趣的:(网络学习,计算机网络,网络,udp,stm32)