轉載自:http://blog.chinaunix.net/uid-26833883-id-3237924.html
這兩種報文格式如下:
通過wirshark抓包格式如下:
ICMP報頭在linux定義如下:
while( nleft > 1)
// 奇數字節長度
// 將最後字節壓如2字節的高位
//高位低位相加
// 上一步溢出時(十六進制相加進位),將溢出位也加到sum中
sum += sum >> 16;
// 注意類型轉換,現在的校驗和爲16位
return tmp;
獲取系統時間,放在struct timeval的變量中,第二個參數tzp指針表示時區,一般都是NULL,大多數代碼都是這樣,我也沒關注過。
解釋如下:
這個函數的傳入值是域名或者主機名,例如"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
#include
#include
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.com
official hostname:www.a.shifen.com
alias:www.baidu.com
address:121.14.88.11
address:121.14.89.11
first 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位進行設置。操作如下: