linuxのICMPでping機能を実現
1.ICMP(Internet Control Message、インターネット制御メッセージプロトコル)は、ゲートウェイとターゲットホストのために提供されるエラー制御メカニズムであり、エラーが発生した場合にエラーをメッセージソース送信者に報告できるようにする.ICMPプロトコルはIP層のプロトコルであるが、エラーレポートは、メッセージ送信元に送信される際にもいくつかのサブネットを経由する可能性があるため、ルーティングなどの問題にかかわるため、ICMPメッセージはIPプロトコルで送信する必要がある.ICMPデータグラムプロトコルは、ICMPヘッダとIPメッセージをカプセル化したものである.2.IP層プロトコルは、エンドツーエンドプロトコルではなく、エンドツーエンドプロトコルであり、bind()とconnect()の使用は極めて少なく、sendto()関数の使用、recvfrom()関数の使用を送信する.
IPヘッダ長IHL(Internet Header Length)は、4バイト単位でIPヘッダの長さを記録するものであり、上記IPデータ構造のip_hl変数である.生存時間TTL(Time To Live)は秒単位で、IPデータがネットワーク上に留まることができる最長時間を示し、その値は送信者によって設定され、ルーティングされたノードごとに1ずつ減算され、この値が0の場合、データは破棄され、上述したIPデータ構造のip_ttl変数である.
3.ICMPメッセージは2種類に分けられます.1つはエラーレポート、2つはクエリーメッセージです.各ICMPメッセージには、タイプ、符号化、および検証の3つの内容が含まれる.Pingコマンドは、要求返信(ICMP_ECHO)と要求応答(ICMP_ECHOREPLY)の2種類のICMPメッセージのみを使用します.
7、Protocolent
格式:struct protoent*getprotobyname(const char*name);パラメータ:name通信協定名返信値:成功-struct protoentへのポインタ失敗-NULL説明:通信協定の名前を利用してその通信協定の別名、番号などの資料を知る.getprotobynumber():通信協定の番号に従って、その通信協定の他の資料を取得します.格式:struct protoent*getprotobynumber(int number);パラメータ:numberはhost配列方式の通信協定番号で値を返す:成功-struct protoentへのポインタ失敗-NULL説明:通信協定の番号を利用してその通信協定の名前、別名などの資料を知る.プロトコル名に基づいて「/etc/protocols」に一致し、一致に成功し、struct protoentポインタに戻り、失敗して空に戻ります.
最終的には、linuxで実装されたPing機能、コードは以下の通りです.
実装結果:
特に注意:socket()関数を使用して元のソケットを生成できるのはrootユーザーのみです.
———————————-END——————————————————————
struct ip
{
#if __BYTE_ORDER == __LITTLE_ENDIAN
unsigned int ip_hl:4; /* header length */
unsigned int ip_v:4; /* version */
#endif
#if __BYTE_ORDER == __BIG_ENDIAN
unsigned int ip_v:4; /* version */
unsigned int ip_hl:4; /* header length */
#endif
u_int8_t ip_tos; /* type of service */
u_short ip_len; /* total length */
u_short ip_id; /* identification */
u_short ip_off; /* fragment offset field */
#define IP_RF 0x8000 /* reserved fragment flag */
#define IP_DF 0x4000 /* dont fragment flag */
#define IP_MF 0x2000 /* more fragments flag */
#define IP_OFFMASK 0x1fff /* mask for fragmenting bits */
u_int8_t ip_ttl; /* time to live */
u_int8_t ip_p; /* protocol */
u_short ip_sum; /* checksum */
struct in_addr ip_src, ip_dst; /* source and dest address */
};
,ping :
IPヘッダ長IHL(Internet Header Length)は、4バイト単位でIPヘッダの長さを記録するものであり、上記IPデータ構造のip_hl変数である.生存時間TTL(Time To Live)は秒単位で、IPデータがネットワーク上に留まることができる最長時間を示し、その値は送信者によって設定され、ルーティングされたノードごとに1ずつ減算され、この値が0の場合、データは破棄され、上述したIPデータ構造のip_ttl変数である.
3.ICMPメッセージは2種類に分けられます.1つはエラーレポート、2つはクエリーメッセージです.各ICMPメッセージには、タイプ、符号化、および検証の3つの内容が含まれる.Pingコマンドは、要求返信(ICMP_ECHO)と要求応答(ICMP_ECHOREPLY)の2種類の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
#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
};
,ICMP 。 , , , , 1( ), , 1。 , , !
4. : Ping ICM , ICMP , ICMP 。
5. linux , , gethostbyname()、gethostbyaddr() , IPv4 IPv6 。 gethostbyname() IP ,gethostbyaddr() , IP 。
struct hostent
{
char *h_name; /* */
char **h_aliases; /* */
int h_addrtype; /* IP IPv4 AF_INET*/
int h_length; /* IP , IPv4 4 , 32 */
char **h_addr_list; /* IP */
}
7、Protocolent
struct protoent {
char * p_name; //
char * p_aliases; //
short * p_proto; //
}
格式:struct protoent*getprotobyname(const char*name);パラメータ:name通信協定名返信値:成功-struct protoentへのポインタ失敗-NULL説明:通信協定の名前を利用してその通信協定の別名、番号などの資料を知る.getprotobynumber():通信協定の番号に従って、その通信協定の他の資料を取得します.格式:struct protoent*getprotobynumber(int number);パラメータ:numberはhost配列方式の通信協定番号で値を返す:成功-struct protoentへのポインタ失敗-NULL説明:通信協定の番号を利用してその通信協定の名前、別名などの資料を知る.プロトコル名に基づいて「/etc/protocols」に一致し、一致に成功し、struct protoentポインタに戻り、失敗して空に戻ります.
最終的には、linuxで実装されたPing機能、コードは以下の通りです.
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define PACKET_SIZE 4096
#define MAX_WAIT_TIME 5
#define MAX_NO_PACKETS 3
char SendPacket[PACKET_SIZE];
char RecvPacket[PACKET_SIZE];
int sockfd,datalen=56;
int nsend=0,nreceived=0;
struct sockaddr_in dest_addr;
pid_t pid; /* */
struct sockaddr_in from_addr;
struct timeval timerecv;
void statistics(int signo);
unsigned short cal_chksum(unsigned short *addr,int len);
int pack(int pack_no);
void send_packet(void);
void recv_packet(void);
int unpack(char *buf,int len);
void tv_sub(struct timeval *out,struct timeval *in);
int main(int argc,char* argv[])
{
if(argc<2)
{
printf("error_argc!
");
exit(1);
}
struct hostent *host;
struct protoent *proto;
unsigned long inaddr=01;
int waittime=MAX_WAIT_TIME;
int size=50*1024;
if((proto=getprotobyname("icmp"))==NULL)
{
perror("getprotobyname!");
exit(1);
}
/* ICMP , root */
if(-1 == (sockfd=socket(AF_INET,SOCK_RAW,proto->p_proto))) /* */
{
perror("socket!");
exit(1);
}
/* root , */
setuid(getuid());
/* 50k, , ping , */
setsockopt(sockfd,SOL_SOCKET,SO_RCVBUF,&size,sizeof(size));/* socket */
bzero(&dest_addr,sizeof(dest_addr));
dest_addr.sin_family=AF_INET;
/* ip */
if(inaddr = inet_addr(argv[1])==INADDR_NONE) /* , long *//* Internet , “a.b.c.d” 255, inet_addr() INADDR_NONE。*/
{
if((host=gethostbyname(argv[1]))==NULL)/* , IP */
{
perror("gethostbyname!");
exit(1);
}
memcpy((char *)&dest_addr.sin_addr,host->h_addr,host->h_length);
}
else
{
dest_addr.sin_addr.s_addr = inet_addr(argv[1]);
//memcpy((char *)&dest_addr.sin_addr,argv[1],sizeof(argv[1]));
}
/* id , ICMP */
pid=getpid();
printf("PING %s(%s): %d bytes data in ICMP packets.
",argv[1],
inet_ntoa(dest_addr.sin_addr),datalen);
send_packet();
recv_packet();
statistics(SIGALRM);
return 0;
}
/* */
void statistics(int signo)
{
printf("
-------------PING statistics------------
");
printf("%d packets transmitted,%d receive,%%%d lost
,",nsend,nreceived, (nsend-nreceived)/nsend*100);
close(sockfd);
exit(1);
}
/* */
unsigned short cal_chksum(unsigned short *addr,int len)
{
int nleft = len;
int sum = 0;
unsigned short *w = addr;
unsigned short answer = 0;
/* ICMP 2 */
while(nleft>1)
{
sum+=*w++;
nleft-=2;
}
/* ICMP , , ‘0’ */
if(1 == nleft)
{
*(unsigned char *)(&answer) = *(unsigned char *)w;
sum+=answer;
}
sum=(sum>>16)+(sum&0xFFFF);
sum+=(sum>>16);
answer=~sum;
return answer;
}
/* */
int pack(int pack_no)
{
int i,packsize;
struct icmp *icmp;
struct timeval *tval;
icmp=(struct icmp*)(SendPacket);
icmp->icmp_type=ICMP_ECHO;
icmp->icmp_code=0;
icmp->icmp_cksum=0;
icmp->icmp_seq=pack_no;
icmp->icmp_id=pid;
packsize=8+datalen;
tval=(struct timeval*)(icmp->icmp_data);
gettimeofday(tval,NULL); /* */
icmp->icmp_cksum=cal_chksum((unsigned short*)icmp,packsize);
return packsize;
}
/* ICMP */
void send_packet(void)
{
int packetsize;
while(nsendif(sendto(sockfd,SendPacket,packetsize,0,(struct sockaddr *)&dest_addr,sizeof(dest_addr))<0)
{
perror("sendto!");
continue;
}
sleep(1); /* ICMP */
}
}
/* ICMP */
void recv_packet(void)
{
int n,fromlen;
extern int errno;
signal(SIGALRM,statistics); /* s */
fromlen=sizeof(from_addr);
while(nreceived/* , SIGALRM */
if((n=recvfrom(sockfd,RecvPacket,sizeof(RecvPacket),0,(struct sockaddr *)&from_addr,&fromlen))<0)
{
if(errno==EINTR) continue; /* , */
perror("recvfrom!");
continue;
}
gettimeofday(&timerecv,NULL);/* */
if(-1==unpack(RecvPacket,n))
{
continue;
}
nreceived++;
}
}
/* ICMP */
int unpack(char *buf,int len)
{
int i,iphdrlen;
struct ip *ip;
struct icmp *icmp;
struct timeval *timesend;
double rtt;
ip=(struct ip*)buf;
iphdrlen=ip->ip_hl<<2;/* ip , ip 2*/
icmp=(struct icmp*)(buf+iphdrlen);
len-=iphdrlen;
if(len<8)
{
printf("ICMP packet\'s length is less than 8
");
return -1;
}
/* ICMP */
if( (icmp->icmp_type == ICMP_ECHOREPLY) && (icmp->icmp_id == pid))
{
timesend=(struct timeval*)(icmp->icmp_data);
tv_sub(&timerecv,timesend);/* */
rtt=timerecv.tv_sec*1000+timerecv.tv_usec/1000;/* ms */
printf("%d byte from %s:icmp_seq=%u ttl=%d rtt=%.3f ms
",
len,
inet_ntoa(from_addr.sin_addr),
icmp->icmp_seq,
ip->ip_ttl,
rtt);
}
else
{
return -1;
}
}
/* timeval */
void tv_sub(struct timeval *out,struct timeval *in)
{
if((out->tv_usec-=in ->tv_usec)<0)
{
--out->tv_sec;
out->tv_usec+=1000000;
}
out->tv_sec-=in->tv_sec;
}
実装結果:
./MyPing.exe www.baidu.com
PING www.baidu.com(183.232.231.173): 56 bytes data in ICMP packets.
64 byte from 183.232.231.173:icmp_seq=1 ttl=53 rtt=3002.000 ms
64 byte from 183.232.231.173:icmp_seq=2 ttl=53 rtt=2053.000 ms
64 byte from 183.232.231.173:icmp_seq=3 ttl=53 rtt=1053.000 ms
-------------PING statistics------------
3 packets transmitted,3 receive,%0 lost
特に注意:socket()関数を使用して元のソケットを生成できるのはrootユーザーのみです.
———————————-END——————————————————————