要模拟实现ping命令,就需要对ICMP协议有所了解:
ICMP:Internet控制报文协议,它是TCP/IP协议族中的一个子协议,用于在IP主机,路由之间传递信息的协议。
传输的信息包括:
1.目的不可达消息
2.超时消息
3.重定向消息
4.时间戳请求和时间戳响应消息
5.回显请求和回显响应消息。
ping命令 的机制就是回显请求和回显应答消息,具体是向网络上另一个主机上发送ICMP报文,如果指定的主机得到了这个报文,就将报文原封不动的发送回发送者。
ICMP报文格式:
类型:回显请求报文其中类型为0,代码为0
代码:回显应答报文其中类型为8,代码为0
校验和:包括数据在内整个ICMP协议数据包校验和
标识符:用于文艺标识ICMP报文,Linux中使用进程ID
序列号:报文的序列号
数据:ping中将发送报文的时间戳放入数据字段
通过下面的代码可以打印出IP协议头部的格式:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netinet/ip_icmp.h>
#include <netinet/ip.h>int main() {int sfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);if(sfd < 0){perror("socket");return 1;}int opt = 1;setsockopt(sfd, IPPROTO_IP, IP_HDRINCL, &opt, sizeof(opt));char buf[1500];while(1){memset(buf, 0x00, sizeof(buf));int ret = read(sfd, buf, 1500);if(ret <= 0){break;}struct iphdr* pip = (struct iphdr*)(buf);struct in_addr ad;ad.s_addr = pip->saddr;//printf("protocol: %hhd, %s <-----> ", pip->protocol, inet_ntoa(ad));printf("数据报为%s <===>",inet_ntoa(ad));ad.s_addr = pip->daddr;printf("%s\n", inet_ntoa(ad));}return 0;
}
打印出信息如下:
准备工作就绪,先看看系统的ping长得样子:
首先介绍几个函数:
1.得到主机的时间,精确到毫秒
int gettimeofday(struct timeval *tv, struct timezone *tz);//获取的时间为微秒级的,big存放在tv中。
struct timeval {time_t tv_sec; /* seconds */suseconds_t tv_usec; /* microseconds */};这是一个线程安全的函数。
2.类似DNS的域名解析,将输入的域名解析成ip地址
struct hostent *gethostbyname(const char *name); //函数返回给定主机名的hostent类型的结构。struct hostent {char *h_name; /* official name of host */char **h_aliases; /* alias list */int h_addrtype; /* host address type */int h_length; /* length of address */char **h_addr_list; /* list of addresses */
}
3.校验和:
校验算法可以分成两步来实现。
首先在发送端,有以下三步:
1.把校验和字段置为 0。
2.对需要校验的数据看成以 16bit 为单位的数字组成,依次进行二进制求和。
3.将上一步的求和结果取反,存入校验和字段。
其次在接收端,也有相应的三步:
1.对需要校验的数据看成以 16bit 为单位的数字组成,依次进行二进制求和,包括校验和字段。
2.将上一步的求和结果取反
3.判断最终结果是否为 0。如果为 0,说明校验和正确。如果不为 0,则协议栈会丢掉接收到的数据。
unsigned short chksum(unsigned short* addr, int len) //校验和
{unsigned int ret = 0;while(len > 1){ret += *addr++;len -= 2;}if(len == 1){ret += *(unsigned char*)addr;}ret = (ret >> 16) + (ret & 0xffff);ret += (ret >> 16);return (unsigned short)~ret;
}
万事具备:完整代码链接
代码执行结果:
至此,整个ping命令就全部模仿出来了,ping命令基本的结构都有了。