使⽤linux的ICMP实现ping功能
1.ICMP(Internet Control Message,⽹际控制报⽂协议)是为⽹关和⽬标主机⽽提供的⼀种差错控制机制,使它们在遇到差错时能把错误报告给报⽂源发⽅。ICMP协议是IP层的⼀个协议,但是由于差错报告在发送给报⽂源发⽅时可能也要经过若⼲⼦⽹,因此牵涉到路由选择等问题,所以ICMP报⽂需通过IP协议来发送。ICMP数据报协议由ICMP报头和IP报⽂封装⽽成。
2.IP层协议是⼀种点对点的协议,⽽⾮端对端协议,它提供⽆连接数据报服务,极少使⽤bind()和connect(),发送使⽤sendto()函数,接收使⽤recvfrom()函数
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数据报能在⽹络上停留的最长时间,其值由发送⽅设定,并在经过路由的每⼀个节点时减⼀,当该值为0时,数据报将被丢弃,是上述IP数据结构的ip_ttl变量。
3.ICMP报⽂分为两种:⼀种是错误报告报⽂,⼆是查询报⽂。每个ICMP报⽂包含类型、编码和校验和三个内容。Ping命令只使⽤众多ICMP报⽂的两种:请求回送(ICMP_ECHO)和请求回应(ICMP_ECHOREPLY)。
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 <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <string.h>
#include <signal.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <netdb.h>
#include <setjmp.h>
#include <error.h>
#include <errno.h>
#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 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!\n");
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)
{linux下的sleep函数
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地址,如“d {
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.\n",argv[1],
inet_ntoa(dest_addr.sin_addr),datalen);
send_packet();
recv_packet();
recv_packet();
statistics(SIGALRM);
return0;
}
/*统计收发计数*/
void statistics(int signo)
{
printf("\n-------------PING statistics------------\n");
printf("%d packets transmitted,%d receive,%%%d lost\n,",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(nsend<MAX_NO_PACKETS)
{
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论