?? ping.c
字號:
(1)主體代碼
ping代碼的主體部分可以四部分,首先是一些頭函數及宏定義:
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/file.h>
#include <sys/time.h>
#include <sys/signal.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <errno.h>
#include <getopt.h>
#include <resolv.h>
#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
…
接下來的第二部分是建立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");
exit(2);
}
break;
…
default:
usage();
}
argc -= optind;
argv += optind;
if (argc != 1)
usage();
target = *argv;
接下來的第三部分是用于獲取地址,這里主要使用了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();
/*設定等待實踐*/
timeout.tv_sec = 0;
timeout.tv_usec = 10000;
fdmask = 1 << s;
/*調用select函數*/
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 */
/* 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 = tv.tv_sec * 10000 + (tv.tv_usec / 100);
tsum += triptime;
if (triptime < tmin)
tmin = triptime;
if (triptime > tmax)
tmax = triptime;
}
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
減小字號
Ctrl + -