Linux从入门到开发实战(C/C++)Day12-ICMP协议

ICMP协议:Internet Control Message Protocol
                       网络      控制       报文        协议
    作用:用来检测网络是否畅通

    ping 命令实现流程:
        1.创建socket
            TCP:SOCK_STREAM
            UDP:SOCK_DGRAM
            ICMP:SOCK_RAW SOCK_PACKET
            icmp协议只有root用户可以创建

        2.设置套字节
            setsockopt

        3.设置接收ip

        4.打包
            准备好要发送的数据包

        5.发包

        6.收包

        7.解包
            解析

        8.统计并输出

// 实现ping
// 计算校验和 crc32(格式都是固定的,看一下就行了)
u_short get_cksum(u_short *icmp, int packSize)
{
	int nleft = packSize;
	int sum = 0;
	u_short *w = icmp;
	u_short ans = 0;
	while (nleft > 1)
	{
		sum += *w++;
		nleft -= 2;
	}
	if (nleft == 1)
	{
		*(unsigned char *)(&ans) = *(unsigned char *)w;
		sum += *(u_char *)w;
		sum += ans;
	}
	sum = (sum >> 16) + (sum & 0xffff);
	sum += (sum >> 16);
	ans = ~sum;
	return ans;
}

// 打包
// 包的大小
#define PACKET_SIZE 4096
// ICMP结构体大小
#define ICMP_DATA_SIZE 56
// 要发送的包
char sendPacket[PACKET_SIZE] = {0};
int pack(int curSend)
{
	struct icmp *picmp = (struct icmp *)sendPacket;
	picmp->icmp_type = ICMP_ECHO;
	picmp->icmp_code = 0;
	picmp->icmp_cksum = 0;
	picmp->icmp_id = getuid();
	picmp->icmp_seq = curSend;
	int packSize = 8 + ICMP_DATA_SIZE;
	struct timeval *tval = (struct timeval *)picmp->icmp_data;
	gettimeofday(tval, NULL); // 发送事件设置到附加数据
	picmp->icmp_cksum = get_cksum((u_short *)picmp, packSize);
	return packSize;
}

// 最大发包量
#define MAX_PACKETS 4
int curSend = 0; // 当前已发包量
// 发包
void send_packet(int sfd, struct sockaddr_in *dest_addr, int len)
{
	int packet_size;
	while (curSend <= MAX_PACKETS)
	{
		curSend++;
		packet_size = pack(curSend);
		int r = sendto(sfd, sendPacket, packet_size, 0, (struct sockaddr *)dest_addr, len);
		if (r < 0)
		{
			printf("第%d个sendto失败\n", curSend);
			continue;
		}
		printf("第%d个sendto成功\n", curSend);
	}
	sleep(1);
}
// 收包
// 要接收的包
char recvPacket[PACKET_SIZE] = {0};
int curRecv = 0; // 当前已收包量
struct sockaddr_in fromAddr = {0};
struct timeval recvtimeVal = {0};
void signalrmHand(int s)
{
	printf("======ping======\n");
	printf("%d个包发送成功,%d个包接收成功\n", curSend, curRecv);
	printf("================\n");
	exit(-1);
}

// 解包
// 计算时间差,结果赋值到第一个参数
void tv_sub(struct timeval *out, struct timeval *in)
{
	if ((out->tv_usec -= in->tv_usec) < 0)
	{
		--out->tv_usec;
		out->tv_usec += 1000000;
	}
	out->tv_sec -= in->tv_sec;
}
int unpack(int len, int id)
{
	// 获取ip地址头长度
	struct ip *ip = (struct ip *)recvPacket;
	int iphdrlen = ip->ip_hl << 2;
	// 往后偏移,得到smtp地址头
	char *buf = recvPacket;
	struct icmp *icmp = (struct icmp *)(buf + iphdrlen);
	len -= iphdrlen;
	if (len < 8)
	{
		printf("icmp包小于8\n");
		return -1;
	}
	struct timeval *tvsend;
	if ((ICMP_ECHOREPLY == icmp->icmp_type) && (id == icmp->icmp_id))
	{
		tvsend = (struct timeval *)(icmp->icmp_data);
		tv_sub(&recvtimeVal, tvsend);
		printf("%d byte from %s:icmp_seq:%u ttl=%.3f ms\n",
			   len, inet_ntoa(fromAddr.sin_addr),
			   icmp->icmp_seq, ip->ip_ttl,
			   recvtimeVal.tv_sec * 1000 + recvtimeVal.tv_usec / 1000);
	}
	else
	{
		return -1;
	}
	return 0;
}
void recv_packet(int sfd)
{
	// 注册信号放置收包超时
	signal(SIGALRM, signalrmHand);
	int r;
	int recvlen = 0;
	while (curRecv < curSend)
	{
		alarm(5); // 最大延迟时间
		r = recvfrom(sfd, recvPacket, PACKET_SIZE - 1, 0, (struct sockaddr *)&fromAddr, &recvlen);
		if (r < 0)
		{
			printf("recvfrom失败%m\n");
			continue;
		}
		gettimeofday(&recvtimeVal, NULL);
		if (-1 == unpack(r, getuid()))
			continue;
		curRecv++;
	}
}
void _ping(int argc, char *argv[])
{
	if (argc < 2)
	{
		printf("请输入要ping的ip地址\n");
		exit(-1);
	}

	// 1.创建socket
	struct protoent *protocal = getprotobyname("icmp");
	if (protocal == NULL)
	{
		printf("getprotobyname失败\n");
		exit(-1);
	}
	int sfd = socket(AF_INET, SOCK_RAW, protocal->p_proto);
	if (-1 == sfd)
	{
		printf("创建socket失败\n");
		exit(-1);
	}
	printf("创建socket成功\n");
	// 	2.设置套字节
	int size = 1024 * 50;
	setsockopt(sfd, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size));
	if (-1 == sfd)
	{
		printf("设置套字节失败\n");
		exit(-1);
	}
	printf("设置套字节成功\n");
	// 	3.设置接收ip
	struct sockaddr_in dest_addr = {0};
	dest_addr.sin_family = AF_INET;
	in_addr_t iaddr = inet_addr(argv[1]);
	struct hostent *host = NULL;
	if (iaddr == INADDR_NONE)
	{
		// 说明不是一个ip
		// 检查是否是一个域名
		host = gethostbyname(argv[1]);
		if (host == NULL)
		{
			printf("错误的ip地址\n");
			exit(-1);
		}
		memcpy(&(dest_addr.sin_addr), host->h_addr_list[0], host->h_length);
		printf("域名解析成功:%s\n", inet_ntoa(dest_addr.sin_addr));
	}
	else
	{
		// 是一个ip地址
		dest_addr.sin_addr.s_addr = iaddr;
		printf("ip地址解析成功:%s\n", inet_ntoa(dest_addr.sin_addr));
	}

	// 	4.打包 5.发包
	send_packet(sfd, &dest_addr, sizeof(dest_addr));

	// 	6.收包 7.解包
	recv_packet(sfd);

	// 	8.统计并输出
}

你可能感兴趣的:(linux,c语言,c++)