当前位置: 首页 > article >正文

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.统计并输出
}


http://www.kler.cn/a/310618.html

相关文章:

  • git之 revert和rebase
  • 项目模块十七:HttpServer模块
  • MicroPythonBLEHID使用说明——蓝牙鼠标
  • Spring框架之适配器模式 (Adapter Pattern)
  • Java Stream 流常用操作大全
  • 【C++ 算法进阶】算法提升十三
  • linux-软件包管理-编译与安装源码包
  • 人工智能-GPU版本机器学习、深度学习模型安装
  • swc 编译 es6为commonjs
  • 「数组」堆排序 / 大根堆优化(C++)
  • 双端队列double-ended queue
  • c/c++语言中extern的用法(VS编译)
  • 代码结构之结构体
  • 算法面经手撕系列(2)--手撕BatchNormlization
  • 【每日一诗】【诗词创作】【诗】《雨前秋夜》
  • 浅谈Linux中的环回设备
  • C++将32位深图片处理成灰度图
  • 构建自己的文生图工具:Python + Stable Diffusion + CUDA
  • 基于PHP+MySQL组合开发的在线客服源码系统 聊天记录实时保存 带完整的安装代码包以及搭建部署教程
  • JAVA-集合相关
  • 功能测试干了三年,快要废了。。。
  • 工号不够用了怎么办? - 华为OD统一考试(E卷)
  • 【代码随想录训练营第42期 续Day58打卡 - 图论Part8 - Dijkstra算法
  • 在 Linux 系统中目录架构说明
  • c语言--力扣简单题目(最后一个单词的长度)讲解
  • 【毕设】基于Java的超市管理系统