<2>生存时间TTL(Time To Live),是以秒为单位,指出IP数据报能在网络上停留的最长时间,其值由发送方设定,并在经过路由的每一个节点减一,当该值为0时,数据报将被丢弃,是上述IP数据结构的ip_ttl变量。
B. ICMP报文
struct icmp
{
u_int8_t icmp_type; /* type of message, see below */
u_int8_t icmp_code; /* type sub code */
u_int16_t icmp_cksum; /* ones complement checksum of struct */
union
{
u_char ih_pptr; /* ICMP_PARAMPROB */
struct in_addr ih_gwaddr; /* gateway address */
struct ih_idseq /* echo datagram */
{
u_int16_t icd_id;
u_int16_t icd_seq;
} ih_idseq;
u_int32_t ih_void;
/* ICMP_UNREACH_NEEDFRAG -- Path MTU Discovery (RFC1191) */
struct ih_pmtu
{
u_int16_t ipm_void;
u_int16_t ipm_nextmtu;
} ih_pmtu;
struct ih_rtradv
{
u_int8_t irt_num_addrs;
u_int8_t irt_wpa;
u_int16_t irt_lifetime;
} ih_rtradv;
} icmp_hun;
#define icmp_pptr icmp_hun.ih_pptr
#define icmp_gwaddr icmp_hun.ih_gwaddr
#define icmp_id icmp_hun.ih_idseq.icd_id(标识一个ICMP报文,一般我们用PID标识)
#define icmp_seq icmp_hun.ih_idseq.icd_seq(发送报文的序号)
#define icmp_void icmp_hun.ih_void
#define icmp_pmvoid icmp_hun.ih_pmtu.ipm_void
#define icmp_nextmtu icmp_hun.ih_pmtu.ipm_nextmtu
#define icmp_num_addrs icmp_hun.ih_rtradv.irt_num_addrs
#define icmp_wpa icmp_hun.ih_rtradv.irt_wpa
#define icmp_lifetime icmp_hun.ih_rtradv.irt_lifetime
union
{
struct
{
u_int32_t its_otime;
u_int32_t its_rtime;
u_int32_t its_ttime;
} id_ts;
struct
{
struct ip idi_ip;
/* options and then 64 bits of data */
} id_ip;
struct icmp_ra_addr id_radv;
u_int32_t id_mask;
u_int8_t id_data[1];
} icmp_dun;
#define icmp_otime icmp_dun.id_ts.its_otime
#define icmp_rtime icmp_dun.id_ts.its_rtime
#define icmp_ttime icmp_dun.id_ts.its_ttime
#define icmp_ip icmp_dun.id_ip.idi_ip
#define icmp_radv icmp_dun.id_radv
#define icmp_mask icmp_dun.id_mask
#define icmp_data icmp_dun.id_data(可以看到id_data是含有一个元素的数组名,为什么这样干呀?思考...)
};
<1>协议头校验和算法
unsigned short chksum(addr,len)
unsigned short *addr; // 校验数据开始地址(注意是以2字节为单位)
int len; // 校验数据的长度大小,以字节为单位
{
int sum = 0; // 校验和
int nleft = len; // 未累加的数据长度
unsigned short *p; // 走动的临时指针,2字节为单位
unsigned short tmp = 0; // 奇数字节长度时用到
while( nleft > 1)
{
sum += *p++; // 累加
nleft -= 2;
}
// 奇数字节长度
if(nleft == 1)
{
// 将最后字节压如2字节的高位
*(unsigned char *)&tmp = *(unsigned char *)p;
sum += tmp;
}
//高位低位相加
sum = (sum >> 16) + (sum & 0xffff);
// 上一步溢出时(十六进制相加进位),将溢出位也加到sum中
sum += sum >> 16;
// 注意类型转换,现在的校验和为16位
tmp = ~sum;
return tmp;
}
网际校验和算法,把被校验的数据16位进行累加,然后取反码,若数据字节长度为奇数,则数据尾部补一个字节的0以凑成偶数。此算法适用于IPv4、ICMPv4、IGMPV4、ICMPv6、UDP和TCP校验和,更详细的信息请参考RFC1071。
<2>rtt往返时间
为了实现这一功能,可利用ICMP数据报携带一个时间戳。使用以下函数生成时间戳:
获取系统时间,放在struct timeval的变量中,第二个参数tzp指针表示时区,一般都是NULL,大多数代码都是这样,我也没关注过。
其中tv_sec为秒数,tv_usec微秒数。在发送和接收报文时由gettimeofday分别生成两个timeval结构,两者之差即为往返时间,即ICMP报文发送与接收的时间差。
<3>数据统计
系统自带的ping命令当它发送完所有ICMP报文后,会对所有发送和所有接收的ICMP报文进行统计,从而计算ICMP报文丢失的比率。
注意:为达到此目标,我们在编写代码时,定义两个全局变量:接收计数器和发送计数器,用于记录ICMP报文接收和发送数目。丢失数目 = 发送总数 - 接收总数,丢失比率 = 丢失数目 / 发送总数。
四、myping的实现
<1>补充知识
判断一个字符串是否是 string :"192.168.1.45" 这样的字符串
if( inet_addr(string) == INADDR_NONE )
{
.........
}
<2>补充知识
通过协议名如"icmp"获取对应的协议编号
解释如下:
注意:
创建原始套接字的时候,就需要指定其协议编号.
struct protoent *protocol;
int sockfd_ram;
if((protocol = getprotobyname("icmp")) == NULL)
{
perror("Fail to getprotobyname");
exit(EXIT_FAILURE);
}
//我们一般在创建,流套接字和数据包套接字时指定的是0,代表让系统自己自动去识别
if((sockfd_ram = socket(AF_INET,SOCK_RAM,protocol->p_proto)) < 0)
{
perror("Fail to socket");
exit(EXIT_FAILURE);
}
<3>补充知识
通过主机名或域名获取其对应的ip地址
这个函数的传入值是域名或者主机名,例如"www.google.cn"等等。传出值,是一个hostent的结构。如果函数调用失败,将返回NULL。 hostent->h_name 表示的是主机的规范名。例如www.google.com的规范名其实是www.l.google.com。 hostent->h_aliases 表示的是主机的别名.www.google.com就是google他自己的别名。有的时候,有的主机可能有好几个别名,这些,其实都是为了易于用户记忆而为自己的网站多取的名字。 hostent->h_addrtype 表示的是主机ip地址的类型,到底是ipv4(AF_INET),还是pv6(AF_INET6) hostent->h_length 表示的是主机ip地址的长度 hostent->h_addr_lisst 表示的是主机的ip地址,注意,这个是以网络字节序存储的。千万不要直接用printf带%s参数来打这个东西,会有问题的哇。所以到真正需要打印出这个IP的话,需要调用inet_ntop()。 这个函数,是将类型为af的网络地址结构src,转换成主机序的字符串形式,存放在长度为cnt的字符串中。返回指向dst的一个指针。如果函数调用错误,返回值是NULL。#include <netdb.h>
#include <sys/socket.h>
#include <stdio.h>
int main(int argc, char **argv)
{
char *ptr, **pptr;
struct hostent *hptr;
char str[32];
ptr = argv[1];
if((hptr = gethostbyname(ptr)) == NULL)
{
printf(" gethostbyname error for host:%s\n", ptr);
return 0;
}
printf("official hostname:%s\n",hptr->h_name);
for(pptr = hptr->h_aliases; *pptr != NULL; pptr++)
printf(" alias:%s\n",*pptr);
switch(hptr->h_addrtype)
{
case AF_INET:
case AF_INET6:
pptr=hptr->h_addr_list;
for(; *pptr!=NULL; pptr++)
printf(" address:%s\n",
inet_ntop(hptr->h_addrtype, *pptr, str, sizeof(str)));
printf(" first address: %s\n",
inet_ntop(hptr->h_addrtype, hptr->h_addr, str, sizeof(str)));
break;
default:
printf("unknown address type\n");
break;
}
return 0;
}编译运行-----------------------------# gcc test.c# ./a.out www.baidu.comofficial hostname:www.a.shifen.comalias:www.baidu.comaddress:121.14.88.11address:121.14.89.11first address: 121.14.88.11
<4>myping源码- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <errno.h>
- #include <sys/socket.h>
- #include <sys/types.h>
- #include <netinet/in.h>
- #include <arpa/inet.h>
- #include <netdb.h>
- #include <sys/time.h>
- #include <netinet/ip_icmp.h>
- #include <unistd.h>
- #include <signal.h>
- #define MAX_SIZE 1024
- char send_buf[MAX_SIZE];
- char recv_buf[MAX_SIZE];
- int nsend = 0,nrecv = 0;
- int datalen = 56;
- //统计结果
- void statistics(int signum)
- {
- printf("\n----------------PING statistics---------------\n");
- printf("%d packets transmitted,%d recevid,%%%d lost\n",nsend,nrecv,(nsend - nrecv)/nsend * 100);
- exit(EXIT_SUCCESS);
- }
- //校验和算法
- int calc_chsum(unsigned short *addr,int len)
- {
- int sum = 0,n = len;
- unsigned short answer = 0;
- unsigned short *p = addr;
-
- //每两个字节相加
- while(n > 1)
- {
- sum += *p ++;
- n -= 2;
- }
-
- //处理数据大小是奇数,在最后一个字节后面补0
- if(n == 1)
- {
- *((unsigned char *)&answer) = *(unsigned char *)p;
- sum += answer;
- }
-
- //将得到的sum值的高2字节和低2字节相加
- sum = (sum >> 16) + (sum & 0xffff);
-
- //处理溢出的情况
- sum += sum >> 16;
- answer = ~sum;
- return answer;
- }
- int pack(int pack_num)
- {
- int packsize;
- struct icmp *icmp;
- struct timeval *tv;
- icmp = (struct icmp *)send_buf;
- icmp->icmp_type = ICMP_ECHO;
- icmp->icmp_code = 0;
- icmp->icmp_cksum = 0;
- icmp->icmp_id = htons(getpid());
- icmp->icmp_seq = htons(pack_num);
- tv = (struct timeval *)icmp->icmp_data;
- //记录发送时间
- if(gettimeofday(tv,NULL) < 0)
- {
- perror("Fail to gettimeofday");
- return -1;
- }
-
- packsize = 8 + datalen;
- icmp->icmp_cksum = calc_chsum((unsigned short *)icmp,packsize);
-
- return packsize;
- }
- int send_packet(int sockfd,struct sockaddr *paddr)
- {
- int packsize;
-
- //将send_buf填上a
- memset(send_buf,‘a‘,sizeof(send_buf));
- nsend ++;
- //打icmp包
- packsize = pack(nsend);
- if(sendto(sockfd,send_buf,packsize,0,paddr,sizeof(struct sockaddr)) < 0)
- {
- perror("Fail to sendto");
- return -1;
- }
- return 0;
- }
- struct timeval time_sub(struct timeval *tv_send,struct timeval *tv_recv)
- {
- struct timeval ts;
- if(tv_recv->tv_usec - tv_send->tv_usec < 0)
- {
- tv_recv->tv_sec --;
- tv_recv->tv_usec += 1000000;
- }
- ts.tv_sec = tv_recv->tv_sec - tv_send->tv_sec;
- ts.tv_usec = tv_recv->tv_usec - tv_send->tv_usec;
- return ts;
- }
- int unpack(int len,struct timeval *tv_recv,struct sockaddr *paddr,char *ipname)
- {
- struct ip *ip;
- struct icmp *icmp;
- struct timeval *tv_send,ts;
- int ip_head_len;
- float rtt;
- ip = (struct ip *)recv_buf;
- ip_head_len = ip->ip_hl << 2;
- icmp = (struct icmp *)(recv_buf + ip_head_len);
-
- len -= ip_head_len;
- if(len < 8)
- {
- printf("ICMP packets\‘s is less than 8.\n");
- return -1;
- }
-
- if(ntohs(icmp->icmp_id) == getpid() && icmp->icmp_type == ICMP_ECHOREPLY)
- {
- nrecv ++;
- tv_send = (struct timeval *)icmp->icmp_data;
- ts = time_sub(tv_send,tv_recv);
- rtt = ts.tv_sec * 1000 + (float)ts.tv_usec/1000;//以毫秒为单位
- printf("%d bytes from %s (%s):icmp_req = %d ttl=%d time=%.3fms.\n",
- len,ipname,inet_ntoa(((struct sockaddr_in *)paddr)->sin_addr),ntohs(icmp->icmp_seq),ip->ip_ttl,rtt);
- }
-
- return 0;
- }
- int recv_packet(int sockfd,char *ipname)
- {
- int addr_len ,n;
- struct timeval tv;
- struct sockaddr from_addr;
-
- addr_len = sizeof(struct sockaddr);
- if((n = recvfrom(sockfd,recv_buf,sizeof(recv_buf),0,&from_addr,&addr_len)) < 0)
- {
- perror("Fail to recvfrom");
- return -1;
- }
- if(gettimeofday(&tv,NULL) < 0)
- {
- perror("Fail to gettimeofday");
- return -1;
- }
- unpack(n,&tv,&from_addr,ipname);
- return 0;
- }
- int main(int argc,char *argv[])
- {
- int size = 50 * 1024;
- int sockfd,netaddr;
- struct protoent *protocol;
- struct hostent *host;
- struct sockaddr_in peer_addr;
-
- if(argc < 2)
- {
- fprintf(stderr,"usage : %s ip.\n",argv[0]);
- exit(EXIT_FAILURE);
- }
-
- //获取icmp的信息
- if((protocol = getprotobyname("icmp")) == NULL)
- {
- perror("Fail to getprotobyname");
- exit(EXIT_FAILURE);
- }
-
- //创建原始套接字
- if((sockfd = socket(AF_INET,SOCK_RAW,protocol->p_proto)) < 0)
- {
- perror("Fail to socket");
- exit(EXIT_FAILURE);
- }
- //回收root权限,设置当前用户权限
- setuid(getuid());
- /*
- 扩大套接子接收缓冲区到50k,这样做主要为了减少接收缓冲区溢出的可能性
- 若无影中ping一个广播地址或多播地址,将会引来大量应答
- */
- if(setsockopt(sockfd,SOL_SOCKET,SO_RCVBUF,&size,sizeof(size)) < 0)
- {
- perror("Fail to setsockopt");
- exit(EXIT_FAILURE);
- }
- //填充对方的地址
- bzero(&peer_addr,sizeof(peer_addr));
- peer_addr.sin_family = AF_INET;
- //判断是主机名(域名)还是ip
- if((netaddr = inet_addr(argv[1])) == INADDR_NONE)
- {
- //是主机名(域名)
- if((host = gethostbyname(argv[1])) == NULL)
- {
- fprintf(stderr,"%s unknown host : %s.\n",argv[0],argv[1]);
- exit(EXIT_FAILURE);
- }
- memcpy((char *)&peer_addr.sin_addr,host->h_addr,host->h_length);
-
- }else{//ip地址
- peer_addr.sin_addr.s_addr = netaddr;
- }
-
- //注册信号处理函数
- signal(SIGALRM,statistics);
- signal(SIGINT,statistics);
- alarm(5);
- //开始信息
- printf("PING %s(%s) %d bytes of data.\n",argv[1],inet_ntoa(peer_addr.sin_addr),datalen);
- //发送包文和接收报文
- while(1)
- {
- send_packet(sockfd,(struct sockaddr *)&peer_addr);
- recv_packet(sockfd,argv[1]);
- alarm(5);
- sleep(1);
- }
- exit(EXIT_SUCCESS);
- }
注意:由于原始套接字的创建只能是拥有超级权限的进程创建,所以我们需要将我们编译好的可执行文件,把其文件所有者改为root,再将其set-uid-bit位进行设置。操作如下: