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 -----------*/
阅读(2884) | 评论(0) | 转发(0) |