天天看点

原始套接字发送IP数据报IPv4头部ICMP头部原始套接字发送IP数据报实验附录1参考

    IP是TCP/IP协议族中的核心协议。所有TCP、UDP、ICMP和IGMP数据都通过IP数据报传输。IP提供了一种尽力而为、无连接的数据报交付服务。

IPv4头部

原始套接字发送IP数据报IPv4头部ICMP头部原始套接字发送IP数据报实验附录1参考
图1 IPv4头部

    图1显示了IPv4数据报的头部,各个字段的如下:

  1. 版本:IP数据报的版本号。IPv4为4。
  2. Internet头部长度:IP头部的长度,以4字节为单位。
  3. 服务类型:指定了一个等效的通信类型。未被广泛使用。
  4. 总长度:IP数据报的总长度,以字节为单位。通过这个字段和Internet头部长度字段,可以知道数据报的数据部分从哪里开始。
  5. 标识:为了避免将一个数据报分片和其他数据报分片混淆,发送主机通常在每次发送数据报时都将一个内部计数器加1,并将该计数器值复制到IPv4标识字段。
  6. 标志:三个比特位。

    bit0:保留位。必须为0。

    bit1:DF位。为0表示可能分片。为1表示不能分片。

    bit2:MF位。为0表示最后一个分片。为1表示还有分片。

  7. 分片偏移:指示数据部分的偏移值,以8字节为单位。IP分片机制可以参考《IP分片》。
  8. 生存周期:设置一个数据报可经过的路由器数量的上限。每台路由器在转发数据报时将该值减1。
  9. 协议:IP的上层协议字段。例如下文讨论的ICMP为1。
  10. Internet校验和:计算的是IPv4头部的校验和。校验算法为先将校验和字段置为0,然后将头部各字节以2字节为一组进行反码加码,即溢出的高位还要加到末位。最后将计算的结果取反存入校验和字段。
for (int i = 0; i <= count - 2; i += 2)
	{
		num = (HEADER[i] << 8) + HEADER[i+1];
		checkSum += num;

		checkSum = (checkSum & 0xffff) + (checkSum >> 16);
	}

	checkSum = (~checkSum) & 0xffff;
           
  1. 源IP地址。
  2. 目标IP地址。
  3. IP选项。

ICMP头部

    由于自己构造IP数据报,需要一个上层协议进行验证,这里选取ICMP的回显服务。ICMP在IP数据报里的封装如图2所示。

原始套接字发送IP数据报IPv4头部ICMP头部原始套接字发送IP数据报实验附录1参考
图2 ICMP封装

    一种最为常见的ICMP报文对就是回显请求和回显响应,如图3所示。在ICMPv4中,它们的类型分别为8和10。ICMP的回显请求报文大小是任意的。

    校验和字段的算法和IP的校验和算法一样,从ICMP首部开始到数据部分进行计算;标识符字段,使ping应用程序识别返回的应答,因为ICMP协议不像传输层协议那样有端口号;序列号字段,每发送一个回显请求报文便增加1。

原始套接字发送IP数据报IPv4头部ICMP头部原始套接字发送IP数据报实验附录1参考
图3 ICMP Ping头部格式

原始套接字发送IP数据报

    思路与《原始套接字发送ARP数据包》一样:构造套接字,填写地址数据结构,填充构造发送报文,调用sendto发送。

    通过以下构造套接字:

int sockfd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP) );
           

    由于我们想要自己构造IP头,自己计算校验和,所以第一个参数为PF_PACKET。对应的地址也要用sockaddr_ll结构。完整代码见附录1。

实验

    将目的IP地址选为www.baidu.com的IP地址。打开wireshark进行抓包,运行程序发送echo请求报文,观察是否有应答。

    如图4所示,对方进行了应答。

原始套接字发送IP数据报IPv4头部ICMP头部原始套接字发送IP数据报实验附录1参考
图4 Wireshark抓包

附录1

#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <netinet/ip_icmp.h>
#include <arpa/inet.h>
#include <linux/if.h>
#include <linux/if_ether.h>
#include <linux/if_packet.h>
#include <net/ethernet.h>

#include <string.h>
#include <stdio.h>

int main()
{
	//套接字
	int sockfd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP) );
	if (sockfd == -1)
	{
		printf("error at socket().\n");
		return 0;
	}

	//地址
	sockaddr_ll addr_ll;
	memset(&addr_ll, 0, sizeof(sockaddr_ll) );
	addr_ll.sll_family = PF_PACKET;

	ifreq ifr;
	strcpy(ifr.ifr_name, "ens33");
	if (ioctl(sockfd, SIOCGIFINDEX, &ifr) == -1)
	{
		printf("error ioctl SIOCGIFINDEX\n"); return 0;
	}
	addr_ll.sll_ifindex = ifr.ifr_ifindex; //接口索引

	if (ioctl(sockfd, SIOCGIFADDR, &ifr) == -1)
	{
		printf("error ioctl SIOCGIFADDR\n"); return 0;
	}
	char* ipSrc = inet_ntoa(((struct sockaddr_in*)(&(ifr.ifr_addr)))->sin_addr);
	printf("ip address : %s\n", ipSrc); //source ip

	if (ioctl(sockfd, SIOCGIFHWADDR, &ifr) == -1)
	{
		printf("error ioctl SIOCGIFHWADDR\n"); return 0;
	}
	unsigned char macDst[ETH_ALEN] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
	unsigned char macSrc[ETH_ALEN];
	memcpy(macSrc, ifr.ifr_hwaddr.sa_data, ETH_ALEN); //mac address
	printf("mac address");
	for (int i = 0; i < ETH_ALEN; i++)
		printf(":%02x", macSrc[i]);
	printf("\n");

	//填充以太网头部
	ethhdr ethheader;
	memcpy(ethheader.h_source, macSrc, ETH_ALEN);
	memcpy(ethheader.h_dest, macDst, ETH_ALEN);
	ethheader.h_proto = htons(ETHERTYPE_IP);

	//填充IP头部
	iphdr ipheader;
	ipheader.version = 0x4;
	ipheader.ihl = 0x5;
	ipheader.tos = 0x00;
	ipheader.tot_len = htons(60);  //20 + 8 + 32
	ipheader.id = 0x1000;
	ipheader.frag_off = 0x0000;
	ipheader.ttl = 128;
	ipheader.protocol = 0x01;
	ipheader.check = 0;
	ipheader.saddr = inet_addr(ipSrc);
	ipheader.daddr = inet_addr("115.239.211.112");

	unsigned int checkSum = 0;
	unsigned int num;
	unsigned char* p = (unsigned char*)&ipheader;
	int i;
	for (i = 0; i <= 18; i += 2)
	{
		num = (p[i] << 8) + p[i + 1];
		checkSum += num;
		checkSum = (checkSum & 0xffff) + (checkSum >> 16);
	}
	checkSum = (~checkSum) & 0xffff;
	ipheader.check = htons((unsigned short)checkSum);

	//填充ICMP头部
	icmphdr icmpheader;
	icmpheader.type = ICMP_ECHO;
	icmpheader.code = 0;
	icmpheader.checksum = 0;
	icmpheader.un.echo.id = 0x1000;
	icmpheader.un.echo.sequence = 0x0001;

	checkSum = 0;
	p = (unsigned char*)&icmpheader;
	for (i = 0; i <= 6; i += 2)
	{
		num = (p[i] << 8) + p[i + 1];
		checkSum += num;
		checkSum = (checkSum & 0xffff) + (checkSum >> 16);
	}

	//echo data
	unsigned char echo[32];
	for (i = 0; i < 32; i++)
		echo[i] = (unsigned char)(0x10 + i);

	for (i = 0; i <= 30; i += 2)
	{
		num = (echo[i] << 8) + echo[i + 1];
		checkSum += num;
		checkSum = (checkSum & 0xffff) + (checkSum >> 16);
	}

	checkSum = (~checkSum) & 0xffff;
	icmpheader.checksum = htons((unsigned short)checkSum);

	//发送
	unsigned char sendBuf[sizeof(ethhdr) + 60];
	memcpy(sendBuf, &ethheader, sizeof(ethhdr) );
	memcpy(sendBuf + sizeof(ethhdr), &ipheader, sizeof(iphdr) );
	memcpy(sendBuf + sizeof(ethhdr) + sizeof(iphdr), &icmpheader, 8);
	memcpy(sendBuf + sizeof(ethhdr) + sizeof(iphdr) + 8, echo, 32);

	int len = sendto(sockfd, sendBuf, sizeof(sendBuf), 0, (const sockaddr*)&addr_ll, sizeof(sockaddr_ll));
	if (len > 0)
	{
		printf("send success.\n");
	}

	return 0;
}
           

参考

  1. RFC 791
  2. RFC 792
  3. TCP/IP详解 卷1

继续阅读