icmp(internet control message,网际控制报文协议)是为网关和目标主机而提供的一种差错控制机制,使它们在遇到差错时能把错误报告给报文源发方。icmp协议是ip层的一个协议,但是由于差错报告在发送给报文源发方时可能也要经过若干子网,因此牵涉到路由选择等问题,所以icmp报文需通过ip协议来发送。icmp数据报的数据发送前需要两级封装:首先添加icmp报头形成icmp报文,再添加ip报头形成ip数据报。
ip报头格式
由于ip层协议是一种点对点的协议,而非端对端的协议,它提供无连接的数据报服务,没有端口的概念,因此很少使用bind()和connect() 函数,若有使用也只是用于设置ip地址。发送数据使用sendto()函数,接收数据使用recvfrom()函数。ip报头格式如下图:
其中ping程序只使用以下数据:
-
ip报头长度ihl(internet header length)?d?d以4字节为一个单位来记录ip报头的长度,是上述ip数据结构的ip_hl变量。
-
生存时间ttl(time to live)?d?d以秒为单位,指出ip数据报能在网络上停留的最长时间,其值由发送方设定,并在经过路由的每一个节点时减一,当该值为0时,数据报将被丢弃,是上述ip数据结构的ip_ttl变量。
icmp报头格式
icmp报文分为两种,一是错误报告报文,二是查询报文。每个icmp报头均包含类型、编码和校验和这三项内容,长度为8位,8位和16位,其余选项则随icmp的功能不同而不同。
ping命令只使用众多icmp报文中的两种:"请求回送'(icmp_echo)和"请求回应'(icmp_echoreply)。在linux中定义如下:
#define icmp_echo 0
#define icmp_echoreply 8
|
这两种icmp类型报头格式如下:
下面是自己实现的一个ping的演示程序,该程序需要root权限运行(raw socket, 系统的ping工具为何不需要root权限:因为ping工具的所有者是root,且其带有-s标记,所以使用时不需要加root权限):
-
#include <stdio.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 <errno.h>
-
-
#define packet_size 4096
-
#define max_wait_time 5
-
#define max_no_packets 4
-
-
char g_sendpacketbuf[packet_size];
-
char g_recvpacket[packet_size];
-
int g_sockfd;
-
int g_datalen = 56;
-
int g_nsend = 0;
-
int g_nreceived = 0;
-
struct sockaddr_in dest_addr;
-
pid_t pid;
-
struct sockaddr_in from;
-
struct timeval tvrecv;
-
-
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);
-
-
void statistics(int signo)
-
{
-
printf("/n--------------------ping statistics-------------------\n");
-
printf("%d packets transmitted, %d received , %%%d lost\n", g_nsend, g_nreceived,
-
(g_nsend - g_nreceived) / g_nsend * 100);
-
close(g_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报头为奇数个字节,会剩下最后一字节。把最后一个字节视为一个2字节数据的高字节,这个2字节数据的低字节为0,继续累加 */
-
if (nleft == 1)
-
{
-
*(unsigned char *)(&answer) = *(unsigned char *)w;
-
sum = answer;
-
}
-
sum = (sum >> 16) (sum & 0xffff);
-
sum = (sum >> 16);
-
answer = ~sum;
-
return answer;
-
}
-
-
/*设置icmp报头*/
-
int pack(int pack_no)
-
{
-
int i, packsize;
-
struct icmp *icmp;
-
struct timeval *tval;
-
-
icmp = (struct icmp *)g_sendpacketbuf;
-
icmp->icmp_type = icmp_echo;
-
icmp->icmp_code = 0;
-
icmp->icmp_cksum = 0;
-
icmp->icmp_seq = pack_no;
-
icmp->icmp_id = pid;
-
packsize = 8 g_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()
-
{
-
int packetsize;
-
-
while (g_nsend < max_no_packets)
-
{
-
g_nsend;
-
packetsize = pack(g_nsend); /*设置icmp报头 */
-
if (sendto(g_sockfd, g_sendpacketbuf, packetsize, 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr)) < 0)
-
{
-
perror("sendto error");
-
continue;
-
}
-
sleep(1); /*每隔一秒发送一个icmp报文 */
-
}
-
}
-
-
/*接收所有icmp报文*/
-
void recv_packet()
-
{
-
int n, fromlen;
-
extern int errno;
-
-
signal(sigalrm, statistics);
-
fromlen = sizeof(from);
-
while (g_nreceived < g_nsend)
-
{
-
alarm(max_wait_time);
-
if ((n = recvfrom(g_sockfd, g_recvpacket, sizeof(g_recvpacket), 0, (struct sockaddr *)&from, &fromlen)) < 0)
-
{
-
if (errno == eintr)
-
continue;
-
perror("recvfrom error");
-
continue;
-
}
-
gettimeofday(&tvrecv, null); /*记录接收时间 */
-
if (unpack(g_recvpacket, n) == -1)
-
continue;
-
g_nreceived;
-
}
-
-
}
-
-
/*剥去icmp报头*/
-
int unpack(char *buf, int len)
-
{
-
int i, iphdrlen;
-
struct ip *ip;
-
struct icmp *icmp;
-
struct timeval *tvsend;
-
double rtt;
-
-
ip = (struct ip *)buf;
-
iphdrlen = ip->ip_hl << 2; /*求ip报头长度,即ip报头的长度标志乘4 */
-
icmp = (struct icmp *)(buf iphdrlen); /*越过ip报头,指向icmp报头 */
-
len -= iphdrlen; /*icmp报头及icmp数据报的总长度 */
-
if (len < 8) /*小于icmp报头长度则不合理 */
-
{
-
printf("icmp packets/'s length is less than 8\n");
-
return -1;
-
}
-
/*确保所接收的是我所发的的icmp的回应 */
-
if ((icmp->icmp_type == icmp_echoreply) && (icmp->icmp_id == pid))
-
{
-
tvsend = (struct timeval *)icmp->icmp_data;
-
tv_sub(&tvrecv, tvsend); /*接收和发送的时间差 */
-
rtt = tvrecv.tv_sec * 1000 tvrecv.tv_usec / 1000; /*以毫秒为单位计算rtt */
-
/*显示相关信息 */
-
printf("%d byte from %s: icmp_seq=%u ttl=%d rtt=%.3f ms\n",
-
len, inet_ntoa(from.sin_addr), icmp->icmp_seq, ip->ip_ttl, rtt);
-
}
-
else
-
return -1;
-
}
-
-
int main(int argc, char *argv[])
-
{
-
struct hostent *host;
-
struct protoent *protocol;
-
unsigned long inaddr = 0l;
-
int waittime = max_wait_time;
-
int size = 50 * 1024;
-
-
if (argc < 2)
-
{
-
printf("usage:%s hostname/ip address\n", argv[0]);
-
exit(1);
-
}
-
-
if ((protocol = getprotobyname("icmp")) == null)
-
{
-
perror("getprotobyname");
-
exit(1);
-
}
-
/*生成使用icmp的原始套接字,这种套接字只有root才能生成 */
-
if ((g_sockfd = socket(af_inet, sock_raw, protocol->p_proto)) < 0)
-
{
-
perror("socket error");
-
exit(1);
-
}
-
-
printf("user id :%d\n", getuid());
-
/* 回收root权限,设置当前用户权限 */
-
setuid(getuid());
-
-
printf("userid:%d,effect user:%d\n", getuid(), geteuid());
-
-
/*
-
* 扩大套接字接收缓冲区到50k这样做主要为了减小接收缓冲区溢出的
-
* 的可能性,若无意中ping一个广播地址或多播地址,将会引来大量应答
-
*/
-
setsockopt(g_sockfd, sol_socket, so_rcvbuf, &size, sizeof(size));
-
bzero(&dest_addr, sizeof(dest_addr));
-
dest_addr.sin_family = af_inet;
-
-
/*判断是主机名还是ip地址 */
-
if ((inaddr = inet_addr(argv[1])) == inaddr_none)
-
{
-
if ((host = gethostbyname(argv[1])) == null) /*是主机名 */
-
{
-
perror("gethostbyname error");
-
exit(1);
-
}
-
memcpy((char *)&dest_addr.sin_addr, host->h_addr, host->h_length);
-
}
-
else /*是ip地址 */
-
dest_addr.sin_addr.s_addr = inaddr;
-
-
/*获取main的进程id,用于设置icmp的标志符 */
-
pid = getpid();
-
printf("ping %s(%s): %d bytes data in icmp packets.\n", argv[1], inet_ntoa(dest_addr.sin_addr), g_datalen);
-
send_packet(); /*发送所有icmp报文 */
-
recv_packet(); /*接收所有icmp报文 */
-
statistics(sigalrm); /*进行统计 */
-
-
return 0;
-
-
}
-
-
/*两个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;
-
}
-
-
/*------------- the end -----------*/
阅读(2869) | 评论(0) | 转发(0) |