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