在剖析ping之前我们先补充一点知识。。。
(1)结构体addinfo
头文件:#include
struct addrinfo
{
int ai_flags;
int ai_family; //AF_INET,AF_INET6,UNIX etc
int ai_socktype; //STREAW,DATAGRAM,RAW
int ai_protocol; //IPPROTO_IP,IPPROTO_IPV4,IPPROTO_IPV6 etc
size_t ai_addrlen; //length of ai_addr
char* ai_canonname; //full hostname
struct sockaddr* ai_addr; //addr of host
struct addrinfo* ai_next;
}
参数
|
取值
|
值
|
说明
|
ai_family
|
AF_INET
|
2
|
IPv4
|
AF_INET6
|
23
|
IPv6
|
|
AF_UNSPEC
|
0
|
协议无关
|
|
ai_protocol
|
IPPROTO_IP
|
0
|
IP协议
|
IPPROTO_IPV4
|
4
|
IPv4
|
|
IPPROTO_IPV6
|
41
|
IPv6
|
|
IPPROTO_UDP
|
17
|
UDP
|
|
IPPROTO_TCP
|
6
|
TCP
|
|
ai_socktype
|
SOCK_STREAM
|
1
|
流
|
SOCK_DGRAM
|
2
|
数据报
|
|
ai_flags
|
AI_PASSIVE
|
1
|
被动的,用于bind,通常用于server socket
|
AI_CANONNAME
|
2
|
用于返回主机的规范名称 | |
AI_NUMERICHOST
|
4
|
地址为数字串
|
(2)getopt函数(分析命令行参数):
头文件:#include
函数原型: int getopt (int argc,char* const argv[ ],const char*optstring);
extern char* optarg;
extern int optind,opterr,optopt;
getopt()所设置的全局变量包括:
optarg-----指向当前选项参数(如果有)的指针。optind------再次调用getopt()时的下一个指针的索引。
optopt-----最后一个未知选项。
补充说明:
gethostbyname和gethostbyaddr这两个函数仅仅支持IPv4,getaddrinfo函数能够处理名字到地址以及服务到端口这两种转换,返回的是一个sockaddr结构的链表而不是一个地址清单。
头文件:#include
#include
函数原型:
int getaddrinfo(const char *hostname,const char* service,const struct addrinfo *hints,
struct addrinfo**result)
参数说明:
msg.msg_iov = { struct iovec = { iov_base = { icmp头+填充字符'E' }, iov_len = 40 } }
msg.msg_len = 1
第三组是msg_control和msg_controllen,它们可被用于发送任何的控制信息,在我们的例子中,没有控制信息要发送。暂时略过。
第四组是msg_flags。其值即为传入的参数flags。raw协议不支持MSG_OOB向标志,即带外数据
(6)下来我们分别来看ip的报文格式和icmp报文格式:在其中我们可以看到icmp报文是作为ip数据报的数据,所以icmp的报头起始位置等于ip报头加上其报文的长度。。。。
下面给出部分的源码(主要是针对IPv4进行注释):
ping.h:
#include "unp.h"
#include
#include
#include
#define BUFSIZE 1500
/* globals */
char sendbuf[BUFSIZE];
int datalen; /* # bytes of data following ICMP header */
char *host;
int nsent; /* add 1 for each sendto() */
pid_t pid; /* our PID */
int sockfd;
int verbose;
/* function prototypes */
void init_v6(void);
void proc_v4(char *, ssize_t, struct msghdr *, struct timeval *);
void proc_v6(char *, ssize_t, struct msghdr *, struct timeval *);
void send_v4(void);
void send_v6(void);
void readloop(void);
void sig_alrm(int);
void tv_sub(struct timeval *, struct timeval *);
struct proto {
void (*fproc)(char *, ssize_t, struct msghdr *, struct timeval *);
void (*fsend)(void);
void (*finit)(void);
struct sockaddr *sasend; /* sockaddr{} for send, from getaddrinfo */
struct sockaddr *sarecv; /* sockaddr{} for receiving */
socklen_t salen; /* length of sockaddr{}s */
int icmpproto; /* IPPROTO_xxx value for ICMP */
} *pr;
#ifdef IPV6
#include
#include
#endif
main.c
#include "ping.h"
struct proto proto_v4 = { proc_v4, send_v4, NULL, NULL, NULL, 0, IPPROTO_ICMP };
int datalen = 56; /* data that goes with ICMP echo request */
int main(int argc, char **argv)
{
int c;
struct addrinfo *ai;
char *h;
opterr = 0; /* don't want getopt() writing to stderr */
//处理命令行参数
//例如:./ping 127.0.0.1
while ( (c = getopt(argc, argv, "v")) != -1) {
switch (c) {
case 'v':
verbose++;
break;
case '?':
err_quit("unrecognized option: %c", c);
}
}
if (optind != argc-1)
err_quit("usage: ping [ -v ] ");
//host保存ip地址
host = argv[optind];
//获取当前的进程号
pid = getpid() & 0xffff; /* ICMP ID field is 16 bits */
//发送信号函数
Signal(SIGALRM, sig_alrm);
//在命令行参数中必须有一个主机名或ip地址,调用Host_serv函数进行处理
//返回addrinfo结构
ai = Host_serv(host, NULL, 0, 0);
h = Sock_ntop_host(ai->ai_addr, ai->ai_addrlen);
printf("PING %s (%s): %d data bytes\n",
ai->ai_canonname ? ai->ai_canonname : h,
h, datalen);
/* 4initialize according to protocol */
//初始化协议结构体pr
if (ai->ai_family == AF_INET) {
pr = &proto_v4;
} else
err_quit("unknown address family %d", ai->ai_family);
pr->sasend = ai->ai_addr;
pr->sarecv = Calloc(1, ai->ai_addrlen);
pr->salen = ai->ai_addrlen;
//调用函数
readloop();
exit(0);
}
/* include host_serv */
#include "unp.h"
struct addrinfo *
host_serv(const char *host, const char *serv, int family, int socktype)
{
int n;
struct addrinfo hints, *res;
//清零
bzero(&hints, sizeof(struct addrinfo));
//用于返回主机的规范名称
hints.ai_flags = AI_CANONNAME; /* always return canonical name */
//其值为0代表:协议无关
hints.ai_family = family; /* AF_UNSPEC, AF_INET, AF_INET6, etc. */
hints.ai_socktype = socktype; /* 0, SOCK_STREAM, SOCK_DGRAM, etc. */
if ( (n = getaddrinfo(host, serv, &hints, &res)) != 0)
return(NULL);
return(res); /* return pointer to first on linked list */
}
/* end host_serv */
/*
* There is no easy way to pass back the integer return code from
* getaddrinfo() in the function above, short of adding another argument
* that is a pointer, so the easiest way to provide the wrapper function
* is just to duplicate the simple function as we do here.
*/
struct addrinfo *
Host_serv(const char *host, const char *serv, int family, int socktype)
{
int n;
struct addrinfo hints, *res;
bzero(&hints, sizeof(struct addrinfo));
hints.ai_flags = AI_CANONNAME; /* always return canonical name */
hints.ai_family = family; /* 0, AF_INET, AF_INET6, etc. */
hints.ai_socktype = socktype; /* 0, SOCK_STREAM, SOCK_DGRAM, etc. */
if ( (n = getaddrinfo(host, serv, &hints, &res)) != 0)
err_quit("host_serv error for %s, %s: %s",
(host == NULL) ? "(no hostname)" : host,
(serv == NULL) ? "(no service name)" : serv,
gai_strerror(n));
return(res); /* return pointer to first on linked list */
}
#include "unp.h"
#ifdef HAVE_SOCKADDR_DL_STRUCT
#include
#endif
/* include sock_ntop */
char *
sock_ntop(const struct sockaddr *sa, socklen_t salen)
{
char portstr[8];
static char str[128]; /* Unix domain is largest */
switch (sa->sa_family) {
//当是IPv4协议时
case AF_INET: {
struct sockaddr_in *sin = (struct sockaddr_in *) sa;
//点分十进制与二进制的转化
if (inet_ntop(AF_INET, &sin->sin_addr, str, sizeof(str)) == NULL)
return(NULL);
//将端口的网络字节序转换为主机字节序
if (ntohs(sin->sin_port) != 0) {
snprintf(portstr, sizeof(portstr), ":%d", ntohs(sin->sin_port));
strcat(str, portstr);
}
return(str);
}
/* end sock_ntop */
#ifdef IPV6
case AF_INET6: {
struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) sa;
str[0] = '[';
if (inet_ntop(AF_INET6, &sin6->sin6_addr, str + 1, sizeof(str) - 1) == NULL)
return(NULL);
if (ntohs(sin6->sin6_port) != 0) {
snprintf(portstr, sizeof(portstr), "]:%d", ntohs(sin6->sin6_port));
strcat(str, portstr);
return(str);
}
return (str + 1);
}
#endif
#ifdef AF_UNIX
case AF_UNIX: {
struct sockaddr_un *unp = (struct sockaddr_un *) sa;
/* OK to have no pathname bound to the socket: happens on
every connect() unless client calls bind() first. */
if (unp->sun_path[0] == 0)
strcpy(str, "(no pathname bound)");
else
snprintf(str, sizeof(str), "%s", unp->sun_path);
return(str);
}
#endif
#ifdef HAVE_SOCKADDR_DL_STRUCT
case AF_LINK: {
struct sockaddr_dl *sdl = (struct sockaddr_dl *) sa;
if (sdl->sdl_nlen > 0)
snprintf(str, sizeof(str), "%*s (index %d)",
sdl->sdl_nlen, &sdl->sdl_data[0], sdl->sdl_index);
else
snprintf(str, sizeof(str), "AF_LINK, index=%d", sdl->sdl_index);
return(str);
}
#endif
default:
snprintf(str, sizeof(str), "sock_ntop: unknown AF_xxx: %d, len %d",
sa->sa_family, salen);
return(str);
}
return (NULL);
}
char *
Sock_ntop(const struct sockaddr *sa, socklen_t salen)
{
char *ptr;
if ( (ptr = sock_ntop(sa, salen)) == NULL)
err_sys("sock_ntop error"); /* inet_ntop() sets errno */
return(ptr);
}
#include "ping.h"
void
proc_v4(char *ptr, ssize_t len, struct msghdr *msg, struct timeval *tvrecv)
{
int hlen1, icmplen;
double rtt;
struct ip *ip;
struct icmp *icmp;
struct timeval *tvsend;
ip = (struct ip *) ptr; /* start of IP header */
//获取ip首部长度
hlen1 = ip->ip_hl << 2; /* length of IP header */
//如果不是ICMP协议,直接返回
if (ip->ip_p != IPPROTO_ICMP)
return; /* not ICMP */
//icmp的头部等于ip+ip长度
icmp = (struct icmp *) (ptr + hlen1); /* start of ICMP header */
if ( (icmplen = len - hlen1) < 8)
return; /* malformed packet */
//设置类型为回显
if (icmp->icmp_type == ICMP_ECHOREPLY) {
if (icmp->icmp_id != pid)
return; /* not a response to our ECHO_REQUEST */
if (icmplen < 16)
return; /* not enough data to use */
//计算往返时间
tvsend = (struct timeval *) icmp->icmp_data;
tv_sub(tvrecv, tvsend);
rtt = tvrecv->tv_sec * 1000.0 + tvrecv->tv_usec / 1000.0;
printf("%d bytes from %s: seq=%u, ttl=%d, rtt=%.3f ms\n",
icmplen, Sock_ntop_host(pr->sarecv, pr->salen),
icmp->icmp_seq, ip->ip_ttl, rtt);
} else if (verbose) {
printf(" %d bytes from %s: type = %d, code = %d\n",
icmplen, Sock_ntop_host(pr->sarecv, pr->salen),
icmp->icmp_type, icmp->icmp_code);
}
}
#include "ping.h"
void
sig_alrm(int signo)
{
(*pr->fsend)();
alarm(1);
return;
}
#include "ping.h"
void
send_v4(void)
{
int len;
struct icmp *icmp;
icmp = (struct icmp *) sendbuf;
icmp->icmp_type = ICMP_ECHO;
icmp->icmp_code = 0;
icmp->icmp_id = pid;
icmp->icmp_seq = nsent++;
memset(icmp->icmp_data, 0xa5, datalen); /* fill with pattern */
Gettimeofday((struct timeval *) icmp->icmp_data, NULL);
len = 8 + datalen; /* checksum ICMP header and data */
icmp->icmp_cksum = 0;
icmp->icmp_cksum = in_cksum((u_short *) icmp, len);
Sendto(sockfd, sendbuf, len, 0, pr->sasend, pr->salen);
}
分别使用自己的ping命令和系统的ping命令。。。该ping程序目前没有系统ping的功能那么完整,至少能实现基本的检测。。。。。
上图就是我们整个程序的大致流程。。。。