Linux網路程式設計之原始套接字-ping協議實現
1.概述
PING協議是用來檢驗本地主機與遠端主機是否連線,傳送的是ICMP ECHO_REQUEST包。普通的套接字是基於TCP或者是UDP的,無法傳送ICMP包,所以必須用原始套接字來實現。PING協議的客戶端型別值為 8,程式碼值為0,表示請求。而PING協議的響應端型別值為0,程式碼值也為0,表示應答. 乙太網資料部分的最小值為46位元組,而IP首部佔20個位元組,ICMP的首部佔8個位元組,所以PING的資料部分至少為4位元組。
2.實現細節
主機端:
(1)建立原始套接字 socket(AF_INET,SOCK_RAW,htons(proto)),能夠直接得到IP包
(2)填寫ICMP首部和資料部分,即icmp_type(8),icmp_code(0)和icmp_data部分
(3)封裝後傳送ICMP請求包
響應端:
(1)建立原始套接字socket(AF_INET,SOCK_RAW,htons(proto))
(2)填寫ICMP首部和資料部分,即icmp_type(0),icmp_code(0),icmp_data
(3)傳送ICMP響應包
主機端收到ICMP響應包之後,即原始的IP包,將收到包的時間減去包的傳送時間就可以得到響應時延。
3. PING協議的實現例子
#include#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/**
通過原始套接字傳送ICMP回顯請求報文來實現ping協議
ICMP回顯報文的結構
struct icmp{
u_int8_t icmp_type;//訊息型別
u_int8_t icmp_code;//訊息型別的子碼
u_int16_t icmp_cksum;//校驗和
union{
struct ih_idseq{//顯示資料包
u_int16_t icd_id;//資料包所在程式的ID
u_int16_6 icd_seq;//資料包序號
}ih_idseq;
}icmp_hun;
#define icmp_id icmp_hun.ih_idseq.icd_id
#define icmp_seq icmp_hun.ih_idseq.icd_seq
union{
u_int8_t id_data[i];//資料
}icmp_dun;
#define icmp_data icmp_dun.id_data
}
**/
typedef struct pingm_packet{
struct timeval tv_begin;//傳送的時間
struct timeval tv_end;//接收到響應包的時間
short seq;//序號值
int flag;//1表示已經傳送但沒有接收到回應包,0表示接收到響應包
}pingm_packet;
//儲存已經傳送包的狀態值
static pingm_packet pingpacket[128];//定義一個包陣列
static pingm_packet *icmp_findpacket(int seq);
static unsigned short icmp_cksum(unsigned char*data,int len);
static struct timeval icmp_tvsub(struct timeval end,struct timeval begin);
static void icmp_statistics(void);
static void icmp_pack(struct icmp*icmph,int seq,struct timeval *tv,int length);
static int icmp_unpack(char*buf,int len);
static void* icmp_recv(void*argv);
static void* icmp_send(void*argv);
static void icmp_sigint(int signo);
static void icmp_usage();
#define K 1024
#define BUFFERSIZE 512
static unsigned char send_buff[BUFFERSIZE];//定義傳送緩衝區的大小
static unsigned char recv_buff[2*K];//定義接收緩衝區的大小,為防止接收端溢位,接收緩衝區稍微大一些
static struct sockaddr_in dest;//目的地址
static int rawsock=0;//原始套接字描述符
static pid_t pid=0;//程式id
static int alive=0;//是否接收到退出訊號
static short packet_send=0;//已經傳送的資料包數目
static short packet_recv=0;//已經接收的資料包數目
static char dest_str[80];//目的主機字串
static struct timeval tv_begin,tv_end,tv_interval;//本程式開始傳送,結束時間和時間間隔
static void icmp_usage(){
printf("ping aaa.bbb.ccc.ddd\n");
}
//計算ICMP首部校驗和
static unsigned short icmp_cksum(unsigned char* data,int len){
int sum=0;
int odd=len&0x01;
unsigned short *value=(unsigned short*)data;
while(len&0xfffe){
sum+=*(unsigned short*)data;
data+=2;
len-=2;
}
if(odd){
unsigned short tmp=((*data) sum+=tmp;
}
sum=(sum>>16)+(sum&0xffff);
sum+=(sum>>16);
return ~sum;
}
//設定ICMP報頭
static void icmp_pack(struct icmp *icmph,int seq,struct timeval*tv,int length){
unsigned char i=0;
icmph->icmp_type=ICMP_ECHO;//ICMP回顯請求
icmph->icmp_code=0;//code為0
icmph->icmp_cksum=0;//cksum值
icmph->icmp_seq=htons(seq);//資料包的序列號
icmph->icmp_id=htons(pid&0xffff);//資料包的ID
for(i=0;i icmph->icmp_data[i]=htons(i);//注意主機位元組序轉換成網路位元組序
}//傳送的資料
//計算校驗和
icmph->icmp_cksum=icmp_cksum((unsigned char*)icmph,length+8);
}
//計算時間差函式
static struct timeval icmp_tvsub(struct timeval end,struct timeval begin){
struct timeval tv;
tv.tv_sec=end.tv_sec-begin.tv_sec;
tv.tv_usec=end.tv_usec-begin.tv_usec;
if(tv.tv_usec tv.tv_sec--;
tv.tv_usec+=1000000;
}
return tv;
}
//傳送報文
static void *icmp_send(void *argv){
struct timeval tv;
tv.tv_usec=0;
tv.tv_sec=1;//每隔一秒傳送報文
gettimeofday(&tv_begin,NULL);//儲存程式開始傳送資料的時間
while(alive){
memset(send_buff,0,sizeof(send_buff));
int size=0;
struct timeval tv;
gettimeofday(&tv,NULL);//當前包傳送的時間
icmp_pack((struct icmp*)send_buff,packet_send,&tv,203);//packet_send為傳送包的序號,傳送的資料長度為64個位元組,填充ICMP首部資訊
size=sendto(rawsock,send_buff,203+8,0,(struct sockaddr*)&dest,sizeof(dest));//dest為ICMP包傳送的目的地址
if(size perror("sendto error");
continue;
}
else{
//在傳送包的狀態陣列找一空閒位置記錄傳送狀態資訊
pingm_packet *packet=icmp_findpacket(-1);
if(packet){
packet->seq=packet_send;
packet->flag=1;
gettimeofday(&packet->tv_begin,NULL);
packet_send++;//傳送序號+1
}
}
sleep(1);
}
}
//尋找一個空閒位置,seq=-1表示空閒位置
static pingm_packet* icmp_findpacket(int seq){
int i=0;
pingm_packet* found=NULL;
if(seq==-1){
for(i=0;i if(pingpacket[i].flag==0){
found=&pingpacket[i];
break;
}
}
}else if(seq>=0){//查詢對應seq的資料包
for(i=0;i if(pingpacket[i].seq==seq){
found=&pingpacket[i];
break;
}
}
}
return found;
}
//獲得ICMP接收報文,buf存放的除去了乙太網部分的IP資料包文,len為資料長度,ip_hl標識IP頭部長度以4位元組為單位,獲得ICMP資料包後判斷是否為ICMP_ECHOREPLY並檢查是否為本程式的ID
static int icmp_unpack(char*buf,int len){
int i,iphdrlen;
struct ip *ip=NULL;
struct icmp *icmp=NULL;
int rtt;//計算往返時延
ip=(struct ip*)buf;
iphdrlen=ip->ip_hl*4;//IP頭部長度
icmp=(struct icmp*)(buf+iphdrlen);//ICMP報文的地址
len-=iphdrlen;//ICMP報文的長度,ICMP報文至少8個位元組
if(len printf("ICMP packets\'s length is less than 8\n ");
return -1;
}
//判斷ICMP報文的型別是否為ICMP_ECHOREPLY並且為本程式的PID
if((icmp->icmp_type==ICMP_ECHOREPLY)&&(icmp->icmp_id==pid)){
struct timeval tv_interval,tv_recv,tv_send;
//在傳送陣列中查詢已經傳送的包
pingm_packet*packet=icmp_findpacket(ntohs(icmp->icmp_seq));//網路位元組序轉換成主機位元組序
if(packet==NULL){
return -1;
}
packet->flag=0;//表示已經響應了
//本包的傳送時間
tv_send=packet->tv_begin;
//讀取收到響應包的時間
gettimeofday(&tv_recv,NULL);
tv_interval=icmp_tvsub(tv_recv,tv_send);
//計算往返時延,即RTT
rtt=tv_interval.tv_sec*1000+tv_interval.tv_usec/1000;
//列印ICMP段長度,源IP,包的序列號,TTL,時間差
printf("%d byte from %s:icmp_seq=%u ttl=%d rtt=%d ms\n",len,inet_ntoa(ip->ip_src),icmp->icmp_seq,ip->ip_ttl,rtt);
packet_recv++;//接收包的數量加1
}
else{
return -1;
}
}
//接收報文
static void *icmp_recv(void*argv){
struct timeval tv;
tv.tv_usec=200;//輪循時間
tv.tv_sec=0;
fd_set readfd;
while(alive){
int ret=0;
tv.tv_usec=200;//輪循時間
tv.tv_sec=0;
FD_ZERO(&readfd);
FD_SET(rawsock,&readfd);
ret=select(rawsock+1,&readfd,NULL,NULL,&tv);
int fromlen=0;
struct sockaddr from;
switch(ret){
case -1://發生錯誤
break;
case 0://超時
//printf("timeout\n");
break;
default://收到資料包
fromlen=sizeof(from);
int size=recvfrom(rawsock,recv_buff,sizeof(recv_buff),0,(struct sockaddr*)&from,&fromlen);//利用原始套接字,原始套接字與IP層網路協議棧核心打交道
if(errno==EINTR){
perror("recvfrom error");
}
//解包,得到RTT
ret=icmp_unpack(recv_buff,size);
if(ret==-1){
continue;
}
break;
}
}
}
//統計資料結果,成功傳送的報文數量,成功接收的報文數量,丟失報文百分比和程式總共執行時間
static void icmp_statistics(void){
long time=(tv_interval.tv_sec*1000)+(tv_interval.tv_usec/1000);
printf("--- %s ping statistics ---\n",dest_str);//目的IP
printf("%d packets transmitted, %d recevied, %d%c packet loss, time %d ms\n",packet_send,packet_recv,(packet_send-packet_recv)*100/packet_send,'%',time);
}
//訊號處理函式
static void icmp_sigint(int signo){
alive=0;//alive=0程式將會終止
gettimeofday(&tv_end,NULL);//程式結束時間
tv_interval=icmp_tvsub(tv_end,tv_begin);//計算程式一共執行了多長時間
return;
}
//主函式實現
int main(int argc,char*argv[]){
struct hostent *host=NULL;
struct protoent*protocol=NULL;
char protoname[]="icmp";
unsigned long inaddr=1;
int size=128*K;
int ret;
if(argc icmp_usage();
return -1;
}
//獲取協議型別ICMP,協議型別的值作為設定原始套接字的第3個引數,type型別下的具體協議值不止一個,當type為SOCK_RAW
protocol=getprotobyname(protoname);
if(protocol==NULL){
perror("getprotobyname()");
return -1;
}
//複製目的地址
memcpy(dest_str,argv[1],strlen(argv[1])+1);
memset(pingpacket,0,sizeof(pingm_packet)*128);//pingpacket陣列初始化
//建立原始套接字
rawsock=socket(AF_INET,SOCK_RAW,protocol->p_proto);
if(rawsock perror("raw sock error");
return -1;
}
//得到程式的pid
pid=getuid();
//增大接收端緩衝區防止接收的包被覆蓋
ret=setsockopt(rawsock,SOL_SOCKET,SO_RCVBUF,&size,sizeof(size));
if(ret==-1){
perror("SO_RCVBUF ERROR");
return -1;
}
//輸入的目的IP
inaddr=inet_addr(argv[1]);//轉換成二進位制IP
bzero(&dest,sizeof(dest));
dest.sin_family=AF_INET;//設定地址族
if(inaddr==INADDR_NONE){
//輸入的是DNS
host=gethostbyname(argv[1]);
if(host==NULL){
perror("gethostbyname");
return -1;
}
memcpy((char*)&dest.sin_addr,host->h_addr,host->h_length);
}
else{
memcpy((char*)&dest.sin_addr,&inaddr,sizeof(inaddr));
}
inaddr=dest.sin_addr.s_addr;
//由於是ICMP不涉及到埠繫結
printf("PING %s (%d.%d.%d.%d) 56(84)bytes of data.\n",dest_str,(inaddr&0x000000FF)>>0,(inaddr&0x0000FF00)>>8,(inaddr&0x00FF0000)>>16,(inaddr&0xFF000000)>>24);
signal(SIGINT,icmp_sigint);
alive=1;
//定義兩個執行緒,分別用於傳送資料與接收資料
pthread_t send_id,recv_id;
int err=0;
err=pthread_create(&send_id,NULL,icmp_send,NULL);
if(err return -1;
}
err=pthread_create(&recv_id,NULL,icmp_recv,NULL);
if(err return -1;
}
pthread_join(send_id,NULL);//等待子執行緒結束send
pthread_join(recv_id,NULL);//等待子執行緒的結束recv
close(rawsock);
icmp_statistics();
return 0;
}
執行結果:
PING 222.27.253.1 (222.27.253.1) 56(84)bytes of data.
60 byte from 222.27.253.1:icmp_seq=0 ttl=255 rtt=5 ms
60 byte from 222.27.253.1:icmp_seq=1 ttl=255 rtt=12 ms
60 byte from 222.27.253.1:icmp_seq=2 ttl=255 rtt=5 ms
60 byte from 222.27.253.1:icmp_seq=3 ttl=255 rtt=7 ms
60 byte from 222.27.253.1:icmp_seq=4 ttl=255 rtt=2 ms
60 byte from 222.27.253.1:icmp_seq=5 ttl=255 rtt=23 ms
60 byte from 222.27.253.1:icmp_seq=6 ttl=255 rtt=27 ms
60 byte from 222.27.253.1:icmp_seq=7 ttl=255 rtt=10 ms
60 byte from 222.27.253.1:icmp_seq=8 ttl=255 rtt=17 ms
60 byte from 222.27.253.1:icmp_seq=9 ttl=255 rtt=3 ms
60 byte from 222.27.253.1:icmp_seq=10 ttl=255 rtt=6 ms
60 byte from 222.27.253.1:icmp_seq=11 ttl=255 rtt=10 ms
60 byte from 222.27.253.1:icmp_seq=12 ttl=255 rtt=3 ms
60 byte from 222.27.253.1:icmp_seq=13 ttl=255 rtt=2 ms
60 byte from 222.27.253.1:icmp_seq=14 ttl=255 rtt=2 ms
60 byte from 222.27.253.1:icmp_seq=15 ttl=255 rtt=6 ms
60 byte from 222.27.253.1:icmp_seq=16 ttl=255 rtt=8 ms
60 byte from 222.27.253.1:icmp_seq=17 ttl=255 rtt=9 ms
60 byte from 222.27.253.1:icmp_seq=18 ttl=255 rtt=25 ms
60 byte from 222.27.253.1:icmp_seq=19 ttl=255 rtt=24 ms
--- 222.27.253.1 ping statistics ---
20 packets transmitted, 20 recevied, 0% packet loss, time 19249 ms
相關文章
- Linux網路程式設計--原始套接字(轉)Linux程式設計
- Linux系統程式設計(37)—— socket程式設計之原始套接字Linux程式設計
- Python原始套接字程式設計Python程式設計
- Linux 程式設計之 Ping 的實現Linux程式設計
- 【網路協議】ICMP協議、Ping、Traceroute協議
- 計算機網路之十一:套接字Socket計算機網路
- 《UNIX網路程式設計》筆記 - 套接字選項/UDP套接字程式設計筆記UDP
- Java套接字程式設計Java程式設計
- 網路套接字
- Linux網路程式設計--高階套接字函式(轉)Linux程式設計函式
- okhttp 原始碼解析 - 網路協議的實現 - HTTP 之 cookie 管理HTTP原始碼協議Cookie
- linux網路程式設計之socket(十一):套接字I/O超時設定方法和用select實現超時Linux程式設計
- UDP協議網路Socket程式設計(java實現C/S通訊案例)UDP協議程式設計Java
- UNIX網路程式設計(6)--套接字地址結構、通用套接字地址結構程式設計
- Java:基於TCP協議網路socket程式設計(實現C/S通訊)JavaTCP協議程式設計
- 計算網路之MSTP協議與VRRP協議協議VR
- 基本TCP套接字程式設計APITCP程式設計API
- 《Linux網路開發必學教程》8_應用協議設計與實現Linux協議
- Linux網路程式設計--TCP/IP協議(轉)Linux程式設計TCP協議
- 【網路協議之OSPF】協議
- 網路協議之TCP協議TCP
- 計算機網路之十二:HTTP協議計算機網路HTTP協議
- 計算機網路之十:路由協議計算機網路路由協議
- 計算機網路之六:UDP協議計算機網路UDP協議
- 計算機網路之四:ICMP協議計算機網路協議
- Java網路通訊套接字Java
- 網路協議之:socket協議詳解之Datagram Socket協議
- SMTP協議初探(二)----linux下c程式設計實現發郵件協議LinuxC程式程式設計
- 網路協程程式設計程式設計
- 網路程式設計UDP協議方式程式設計UDP協議
- 計算機網路之八:TCP協議(2) TCP可靠傳輸的實現計算機網路TCP協議
- 網路基礎之網路協議協議
- 14.1 Socket 套接字程式設計入門程式設計
- Python 網路資料傳輸協議 TCP 程式設計Python協議TCP程式設計
- 網路協議之:socket協議詳解之Unix domain Socket協議AI
- 網路協議之:haproxy的Proxy Protocol代理協議協議Protocol
- 計算機網路之十三:HTTPS協議計算機網路HTTP協議
- 計算機網路之七:TCP協議(1)計算機網路TCP協議