文档库 最新最全的文档下载
当前位置:文档库 › ping源码分析

ping源码分析

ping 源码分析
10.4.1 ping简介
Ping是网络中应用非常广泛的一个软件,它是基于ICMP协议的。下面首先对ICMP协
议做一简单介绍。
ICMP是IP层的一个协议,它是用来探测主机、路由维护、路由选择和流量控制的。ICMP
报文的最终报宿不是报宿计算机上的一个用户进程,而是那个计算机上的IP层软件。也就是
说,当一个带有错误信息的ICMP报文到达时,IP软件模块就处理本身问题,而不把这个ICMP
报文传送给应用程序。
ICMP报文类型有:回送(ECHO)回答(0);报宿不可到达(3);报源断开(4);重定向
(改变路由)(5);回送(ECHO)请求(8);数据报超时(11);数据报参数问题(12);时
间印迹请求(13);时间印迹回答(14);信息请求(15);信息回答(16);地址掩码请求(17);
地址掩码回答(18)。
虽然每种报文都有不同的格式,但它们开始都有下面三段:
??一个8位整数报文TYPE(类型)段;
??一个8位CODE(代码码)段,提供更多的报文类型信息;
??一个16 位CHECKSUM(校验和)段;
此外,报告差错的ICMP 报文还包含产生问题数据报的网际报头及前64 位数据。一个
ICMP回送请求与回送回答报文的格式如表10.17 所示。
表10.17 ICMP回送请求与回送回答报文格式
类型CODE 校验和[CHECKSUM]
标识符序列号
《嵌入式Linux应用程序开发详解》——第10章、嵌入式Linux网络编程

数据
10.4.2 ping源码分析
下面的ping.c 源码是在busybox 里实现的源码。在这个完整的ping.c 代码中有较多选项
的部分代码,因此,这里先分析除去选项部分代码的函数实现部分流程,接下来再给出完整
的ping代码分析。这样,读者就可以看到一个完整协议实现应该考虑到的各个部分。
1.Ping 代码主体流程
Ping.c主体流程图如下图10.8 所示。另外,由于ping是IP层的协议,因此在建立socket
时需要使用SOCK_RAW 选项。在循环等待回应信息处,用户可以指定“-f”洪泛选项,这
时就会使用select函数来指定在一定的时间内进行回应。
2.主要选项说明
Ping函数主要有以下几个选项:
??d:调试选项(F_SO_DEBUG)
??f:洪泛选项(F_FLOOD)
??i:等待选项(F_INTERVAL)
??r:路由选项(F_RROUTE)
??l:广播选项(MULTICAST_NOLOOP)
对于这些选项,尤其是路由选项、广播选项和洪泛选项都会有不同的实现代码。
另外,ping 函数可以接受用户使用的SIGINT 和SIGALARM 信号来结束程序,它们分
别指向了不同的结束代码,请读者阅读下面相关代码。
图10.8 ping主体流程图


3.源代码及注释
(1)主体代码
ping代码的主体部分可以四部分,首先是一些头函数及宏定义:
#include
#include

h>
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define F_FLOOD 0x001
#define F_INTERVAL 0x002
#define F_NUMERIC 0x004
#define F_PINGFILLED 0x008
#define F_QUIET 0x010
#define F_RROUTE 0x020
#define F_SO_DEBUG 0x040
#define F_SO_DONTROUTE 0x080
#define F_VERBOSE 0x100
/* 多播选项*/
int moptions;
#define MULTICAST_NOLOOP 0x001
#define MULTICAST_TTL 0x002
#define MULTICAST_IF 0x004

《嵌入式Linux应用程序开发详解》——第10章、嵌入式Linux网络编程

接下来的第2 部分是建立socket并处理选项:
Int main(int argc, char *argv[])
{
struct timeval timeout;
struct hostent *hp;
struct sockaddr_in *to;
struct protoent *proto;
struct in_addr ifaddr;
int i;
int ch, fdmask, hold, packlen, preload;
u_char *datap, *packet;
char *target, hnamebuf[MAXHOSTNAMELEN];
u_char ttl, loop;
int am_i_root;

static char *null = NULL;
/*__environ = &null;*/
am_i_root = (getuid()==0);
/*
*建立socket连接,并且测试是否是root用户
*/
if ((s = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) < 0) {
if (errno==EPERM) {
fprintf(stderr, "ping: ping must run as root\n");
}
else perror("ping: socket");
exit(2);
}

preload = 0;
datap = &outpack[8 + sizeof(struct timeval)];
while ((ch = getopt(argc, argv, "I:LRc:dfh:i:l:np:qrs:t:v")) != EOF)
switch(ch) {
case 'c':
npackets = atoi(optarg);
if (npackets <= 0) {


(void)fprintf(stderr,
"ping: bad number of packets to transmit.\n");
exit(2);
}
break;
/*调用选项*/
case 'd':
options |= F_SO_DEBUG;
break;
/*flood选项*/
case 'f':
if (!am_i_root) {
(void)fprintf(stderr,
"ping: %s\n", strerror(EPERM));
exit(2);
}
options |= F_FLOOD;
setbuf(stdout, NULL);
break;
/*等待选项*/
case 'i': /* wait between sending packets */
interval = atoi(optarg);
if (interval <= 0) {
(void)fprintf(stderr,
"ping: bad timing interval.\n");
exit(2);
}
options |= F_INTERVAL;
break;
case 'l':
if (!am_i_root) {
(void)fprintf(stderr,
"ping: %s\n", strerror(EPERM));
exit(2);
}
preload = atoi(optarg);
if (preload < 0) {
(void)fprintf(stderr,
"ping: bad preload value.\n");
《嵌入式Linux应用程序开发详解》——第10章、嵌入式Linux网络编程

exit(2);
}
break;

default:
usage();
}
argc -= optind;
argv += optind;
if (argc != 1)
usage();
target = *argv;
接下来的第3 部分是用于获取地址,这里主要使用了inet_aton函数,将点分十进制地址
转化为二进制地址。当然,作为完整的ping程序有较完善的出错处理:
memset(&whereto, 0, sizeof(struct sockaddr));
to = (struct sockaddr_in *)&whereto;
to->sin_family = AF_INET;
/*地址转换函

数*/
if (inet_aton(target, &to->sin_addr)) {
hostname = target;
}
else {
#if 0
char * addr = resolve_name(target, 0);
if (!addr) {
(void)fprintf(stderr,
"ping: unknown host %s\n", target);
exit(2);
}
to->sin_addr.s_addr = inet_addr(addr);
hostname = target;
#else
/*调用gethostbyname识别主机名*/
hp = gethostbyname(target);
if (!hp) {
(void)fprintf(stderr,
"ping: unknown host %s\n", target);


exit(2);
}
to->sin_family = hp->h_addrtype;
if (hp->h_length > (int)sizeof(to->sin_addr)) {
hp->h_length = sizeof(to->sin_addr);
}
memcpy(&to->sin_addr, hp->h_addr, hp->h_length);
(void)strncpy(hnamebuf, hp->h_name, sizeof(hnamebuf) - 1);
hostname = hnamebuf;
#endif
}
接下来的一部分主要是对各个选项(如路由、多播)的处理,这里就不做介绍了。再接
下来是ping函数的最主要部分,就是接收无限循环回应信息,这里主要用到了函数recvfrom。
另外,对用户中断信息也有相应的处理,如下所示:
if (to->sin_family == AF_INET)
(void)printf("PING %s (%s): %d data bytes\n", hostname,
inet_ntoa(*(struct in_addr *)&to->sin_addr.s_addr),
datalen);
else
(void)printf("PING %s: %d data bytes\n", hostname, datalen);
/*若程序接收到SIGINT或SIGALRM信号,调用相关的函数*/
(void)signal(SIGINT, finish);
(void)signal(SIGALRM, catcher);

/*循环等待客户端的回应信息*/
for (;;) {
struct sockaddr_in from;
register int cc;
int fromlen;
if (options & F_FLOOD) {
/*形成ICMP回应数据包,在后面会有讲解*/
pinger();
/*设定等待实践*/
https://www.wendangku.net/doc/3d1405659.html,_sec = 0;
https://www.wendangku.net/doc/3d1405659.html,_usec = 10000;
fdmask = 1 << s;
/*调用select函数*/
《嵌入式Linux应用程序开发详解》——第10章、嵌入式Linux网络编程

if (select(s + 1, (fd_set *)&fdmask, (fd_set *)NULL,
(fd_set *)NULL, &timeout) < 1)
continue;
}
fromlen = sizeof(from);
/*接收客户端信息*/
if ((cc = recvfrom(s, (char *)packet, packlen, 0,
(struct sockaddr *)&from, &fromlen)) < 0) {
if (errno == EINTR)
continue;
perror("ping: recvfrom");
continue;
}
pr_pack((char *)packet, cc, &from);
if (npackets && nreceived >= npackets)
break;
}
finish(0);
/* NOTREACHED */
return 0;
}
(2)其他函数
下面的函数也是ping 程序中用到的重要函数。首先catcher 函数是用户在发送SIGINT
时调用的函数,在该函数中又调用了SIGALARM信号的处理来结束程序。
static void
catcher(int ignore)
{
int waittime;
(void)ignore;
pinger();
/*调用catcher函数*/
(void)signal(SIGALRM, catcher);
if (!npackets || ntransmitted < npackets)
alarm((u_int)interval);
else {
if (nreceived) {
waittime = 2 * tmax / 1000;


if (!waittime)
waittime = 1;
if (waittime > MAXWAIT)
waittime = MAXWAIT;
} else
waittime = MAXWAIT;
/*调用finish函数,并设定一定的等待实践*/
(void)signal(SIGALRM, finish);
(void)alarm((u_int)waittime);
}
}
Pinger 函数也是一个非常重要的函数,用于

形成ICMP回应数据包,其中ID 是该进程的
ID,数据段中的前8 字节用于存放时间间隔,从而可以计算ping程序从对端返回的往返时延
差,这里的数据校验用到了后面定义的in_cksum函数。其代码如下所示:
static void
pinger(void)
{
register struct icmphdr *icp;
register int cc;
int i;
/*形成icmp信息包,填写icmphdr结构体中的各项数据*/
icp = (struct icmphdr *)outpack;
icp->icmp_type = ICMP_ECHO;
icp->icmp_code = 0;
icp->icmp_cksum = 0;
icp->icmp_seq = ntransmitted++;
icp->icmp_id = ident; /* ID */
CLR(icp->icmp_seq % mx_dup_ck);
/*设定等待实践*/
if (timing)
(void)gettimeofday((struct timeval *)&outpack[8],
(struct timezone *)NULL);
cc = datalen + 8; /* skips ICMP portion */
《嵌入式Linux应用程序开发详解》——第10章、嵌入式Linux网络编程

/* compute ICMP checksum here */
icp->icmp_cksum = in_cksum((u_short *)icp, cc);
i = sendto(s, (char *)outpack, cc, 0, &whereto,
sizeof(struct sockaddr));
if (i < 0 || i != cc) {
if (i < 0)
perror("ping: sendto");
(void)printf("ping: wrote %s %d chars, ret=%d\n",
hostname, cc, i);
}
if (!(options & F_QUIET) && options & F_FLOOD)
(void)write(STDOUT_FILENO, &DOT, 1);
}
pr_pack 是数据包显示函数,分别打印出IP 数据包部分和ICMP 回应信息。在规范的程
序中通常将数据的显示部分独立出来,这样就可以很好地加强程序的逻辑性和结构性。
void
pr_pack(char *buf, int cc, struct sockaddr_in *from)
{
register struct icmphdr *icp;
register int i;
register u_char *cp,*dp;
/*#if 0*/
register u_long l;
register int j;
static int old_rrlen;
static char old_rr[MAX_IPOPTLEN];
/*#endif*/
struct iphdr *ip;
struct timeval tv, *tp;
long triptime = 0;
int hlen, dupflag;
(void)gettimeofday(&tv, (struct timezone *)NULL);
/* 检查IP数据包头信息*/
ip = (struct iphdr *)buf;


hlen = ip->ip_hl << 2;
if (cc < datalen + ICMP_MINLEN) {
if (options & F_VERBOSE)
(void)fprintf(stderr,
"ping: packet too short (%d bytes) from %s\n", cc,
inet_ntoa(*(struct in_addr *)&from->sin_addr.s_addr));
return;
}
/* ICMP部分显示*/
cc -= hlen;
icp = (struct icmphdr *)(buf + hlen);
if (icp->icmp_type == ICMP_ECHOREPLY) {
if (icp->icmp_id != ident)
return; /* 'Twas not our ECHO */
++nreceived;
if (timing) {
#ifndef icmp_data
tp = (struct timeval *)(icp + 1);
#else
tp = (struct timeval *)icp->icmp_data;
#endif
tvsub(&tv, tp);
triptime = https://www.wendangku.net/doc/3d1405659.html,_sec * 10000 + (https://www.wendangku.net/doc/3d1405659.html,_usec / 100);
tsum += triptime;
if (triptime < tmin)
tmin = triptime;
if (triptime > tmax)
tmax = triptime;
}
if (TST(icp->icmp_seq % mx_dup_ck)) {
++nrepeats;
--nreceived;
dupflag = 1;
} else {
SET(icp->icmp_seq % mx_dup_ck);
dupflag = 0;
}
《嵌入式Linux应用程序开发详解》——第10章、嵌入式Linux网络编程

if (options & F_QUIET)
return;
if (options & F_FLOOD)
(void)write(STDOUT_FILENO, &BSPACE, 1);
else {
(void)printf("

%d bytes from %s: icmp_seq=%u", cc,
inet_ntoa(*(struct in_addr *)&from->sin_addr.s_addr),
icp->icmp_seq);
(void)printf(" ttl=%d", ip->ip_ttl);
if (timing)
(void)printf(" time=%ld.%ld ms", triptime/10,
triptime%10);
if (dupflag)
(void)printf(" (DUP!)");
/* check the data */
#ifndef icmp_data
cp = ((u_char*)(icp + 1) + 8);
#else
cp = (u_char*)icp->icmp_data + 8;
#endif
dp = &outpack[8 + sizeof(struct timeval)];
for (i = 8; i < datalen; ++i, ++cp, ++dp) {
if (*cp != *dp) {
(void)printf("\nwrong data byte #%d should be 0x%x but was 0x%x",
i, *dp, *cp);
cp = (u_char*)(icp + 1);
for (i = 8; i < datalen; ++i, ++cp) {
if ((i % 32) == 8)
(void)printf("\n\t");
(void)printf("%x ", *cp);
}
break;
}
}
}
} else {
/* We've got something other than an ECHOREPLY */


if (!(options & F_VERBOSE))
return;
(void)printf("%d bytes from %s: ", cc,
pr_addr(from->sin_addr.s_addr));
pr_icmph(icp);
}
/*#if 0*/
/*显示其他IP选项*/
cp = (u_char *)buf + sizeof(struct iphdr);
for (; hlen > (int)sizeof(struct iphdr); --hlen, ++cp)
switch (*cp) {
case IPOPT_EOL:
hlen = 0;
break;
case IPOPT_LSRR:
(void)printf("\nLSRR: ");
hlen -= 2;
j = *++cp;
++cp;
if (j > IPOPT_MINOFF)
for (;;) {
l = *++cp;
l = (l<<8) + *++cp;
l = (l<<8) + *++cp;
l = (l<<8) + *++cp;
if (l == 0)
(void)printf("\t0.0.0.0");
else
(void)printf("\t%s", pr_addr(ntohl(l)));
hlen -= 4;
j -= 4;
if (j <= IPOPT_MINOFF)
break;
(void)putchar('\n');
}
break;
case IPOPT_RR:
《嵌入式Linux应用程序开发详解》——第10章、嵌入式Linux网络编程

j = *++cp; /* get length */
i = *++cp; /* and pointer */
hlen -= 2;
if (i > j)
i = j;
i -= IPOPT_MINOFF;
if (i <= 0)
continue;
if (i == old_rrlen
&& cp == (u_char *)buf + sizeof(struct iphdr) + 2
&& !memcmp((char *)cp, old_rr, i)
&& !(options & F_FLOOD)) {
(void)printf("\t(same route)");
i = ((i + 3) / 4) * 4;
hlen -= i;
cp += i;
break;
}
old_rrlen = i;
memcpy(old_rr, cp, i);
(void)printf("\nRR: ");
for (;;) {
l = *++cp;
l = (l<<8) + *++cp;
l = (l<<8) + *++cp;
l = (l<<8) + *++cp;
if (l == 0)
(void)printf("\t0.0.0.0");
else
(void)printf("\t%s", pr_addr(ntohl(l)));
hlen -= 4;
i -= 4;
if (i <= 0)
break;
(void)putchar('\n');
}
break;
case IPOPT_NOP:
(void)printf("\nNOP");


break;
default:
(void)printf("\nunknown option %x", *cp);
break;
}
/*#endif*/
if (!(options & F_FLOOD)) {
(void)putchar('\n');
(void)fflush(stdout);
}
}
in_cksum是数据校验程序,如下所示:
static int
in_cksum(u_short *addr, int len)
{
register int nleft = len;
register u_short *w = addr;
register int sum = 0;
u_short answer = 0;
/*这里的算法很简单,就采用32bit的加法*/
while (nleft > 1) {
sum += *w++;
nleft -= 2;
}
if (nleft == 1) {
*(u_char *)(&answer) = *(u_char *)w ;
sum += answer;
}
/*把高16bit加到低16bit上去*/
sum = (sum >> 16) + (sum & 0xffff);
sum += (sum >> 16);
answer = ~sum;
return(answer);
}
《嵌入式Linux应

用程序开发详解》——第10章、嵌入式Linux网络编程

Finish程序是ping程序的结束程序,主要是打印出来一些统计信息,如下所示:
static void
finish(int ignore)
{
(void)ignore;
(void)signal(SIGINT, SIG_IGN);
(void)putchar('\n');
(void)fflush(stdout);
(void)printf("--- %s ping statistics ---\n", hostname);
(void)printf("%ld packets transmitted, ", ntransmitted);
(void)printf("%ld packets received, ", nreceived);
if (nrepeats)
(void)printf("+%ld duplicates, ", nrepeats);
if (ntransmitted)
if (nreceived > ntransmitted)
(void)printf("-- somebody's printing up packets!");
else
(void)printf("%d%% packet loss",
(int) (((ntransmitted - nreceived) * 100) /
ntransmitted));
(void)putchar('\n');
if (nreceived && timing)
(void)printf("round-trip min/avg/max = %ld.%ld/%lu.%ld/%ld.%ld
ms\n",
tmin/10, tmin%10,
(tsum / (nreceived + nrepeats))/10,
(tsum / (nreceived + nrepeats))%10,
tmax/10, tmax%10);
if (nreceived==0) exit(1);
exit(0);
}
#ifdef notdef
static char *ttab[] = {
"Echo Reply", /* ip + seq + udata */
"Dest Unreachable", /* net, host, proto, port, frag, sr + IP */
"Source Quench", /* IP */


"Redirect", /* redirect 类型, gateway, + IP */
"Echo",
"Time Exceeded", /*传输超时*/
"Parameter Problem", /* IP参数问题*/
"Timestamp", /* id + seq + three timestamps */
"Timestamp Reply", /* " */
"Info Request", /* id + sq */
"Info Reply" /* " */
};
#endif
pr_icmph函数是用于打印ICMP的回应信息,如下所示:
static void
pr_icmph(struct icmphdr *icp)
{
switch(icp->icmp_type) {
/*ICMP回应*/
case ICMP_ECHOREPLY:
(void)printf("Echo Reply\n");
/* XXX ID + Seq + Data */
break;
/*ICMP终点不可达*/
case ICMP_DEST_UNREACH:
switch(icp->icmp_code) {
case ICMP_NET_UNREACH:
(void)printf("Destination Net Unreachable\n");
break;
case ICMP_HOST_UNREACH:
(void)printf("Destination Host Unreachable\n");
break;
case ICMP_PROT_UNREACH:
(void)printf("Destination Protocol Unreachable\n");
break;

default:
(void)printf("Dest Unreachable, Unknown Code: %d\n",
icp->icmp_code);
break;
}
《嵌入式Linux应用程序开发详解》——第10章、嵌入式Linux网络编程

/* Print returned IP header information */
#ifndef icmp_data
pr_retip((struct iphdr *)(icp + 1));
#else
pr_retip((struct iphdr *)icp->icmp_data);
#endif
break;

default:
(void)printf("Redirect, Bad Code: %d", icp->icmp_code);
break;
}
(void)printf("(New addr: %s)\n",
inet_ntoa(icp->icmp_gwaddr));
#ifndef icmp_data
pr_retip((struct iphdr *)(icp + 1));
#else
pr_retip((struct iphdr *)icp->icmp_data);
#endif
break;
case ICMP_ECHO:
(void)printf("Echo Request\n");
/* XXX ID + Seq + Data */
break;
case ICMP_TIME_EXCEEDED:
switch(icp->icmp_code) {
case ICMP_EXC_TTL:
(void)printf("Time to live exceeded\n");
break;
case ICMP_EXC_FRAGTIME:
(void)printf("Frag reassembly time exceeded\n");
break;
default:
(void)printf("Time exceeded, Bad Code: %d\n",


icp->icmp_code);
break;
}

default:


(void)printf("Bad ICMP type: %d\n", icp->icmp_type);
}
}
pr_iph函数是用于打印IP数据包头选项,如下所示:
static void
pr_iph(struct iphdr *ip)
{
int hlen;
u_char *cp;
hlen = ip->ip_hl << 2;
cp = (u_char *)ip + 20; /* point to options */
(void)printf("Vr HL TOS Len ID Flg off TTL Pro cks Src Dst Data\n");
(void)printf(" %1x %1x %02x %04x %04x",
ip->ip_v, ip->ip_hl, ip->ip_tos, ip->ip_len, ip->ip_id);
(void)printf(" %1x %04x", ((ip->ip_off) & 0xe000) >> 13,
(ip->ip_off) & 0x1fff);
(void)printf(" %02x %02x %04x", ip->ip_ttl, ip->ip_p, ip->ip_sum);
(void)printf(" %s ", inet_ntoa(*((struct in_addr *) &ip->ip_src)));
(void)printf(" %s ", inet_ntoa(*((struct in_addr *) &ip->ip_dst)));
/* dump and option bytes */
while (hlen-- > 20) {
(void)printf("%02x", *cp++);
}
(void)putchar('\n');
}
pr_addr 是用于将ascii 主机地址转换为十进制点分形式并打印出来,这里使用的函数是
inet_ntoa,如下所示:
static char *
pr_addr(u_long l)
{
struct hostent *hp;
static char buf[256];
if ((options & F_NUMERIC) ||
!(hp = gethostbyaddr((char *)&l, 4, AF_INET)))
(void)sprintf(buf, /*sizeof(buf),*/ "%s",inet_ntoa(*(struct
《嵌入式Linux应用程序开发详解》——第10章、嵌入式Linux网络编程

in_addr *)&l));
else
(void)sprintf(buf, /*sizeof(buf),*/ "%s (%s)", hp->h_name,inet_
ntoa(*(struct in_addr *)&l));
return(buf);
}
Usage函数是用于显示帮助信息,如下所示:
static void
usage(void)
{
(void)fprintf(stderr,
"usage: ping [-LRdfnqrv] [-c count] [-i wait] [-l preload]\n\t[-p
pattern] [-s packetsize] [-t ttl] [-I interface address] host\n");
exit(2);
}

相关文档
相关文档 最新文档