ping程序剖析

   在剖析ping之前我们先补充一点知识。。。

    (1)结构体addinfo

     头文件:#include<netdb.h>

    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<unistd.h>

           函数原型: int  getopt (int argc,char* const argv[ ],const char*optstring);

           extern char* optarg;

           extern  int optind,opterr,optopt;

           getopt()所设置的全局变量包括:

             optarg-----指向当前选项参数(如果有)的指针。optind------再次调用getopt()时的下一个指针的索引。

             optopt-----最后一个未知选项。

          补充说明:

optstring中的指定的内容的意义(例如getopt(argc, argv, "ab:c:de::");)

1.单个字符,表示选项,(如上例中的abcde各为一个选项)

2.单个字符后接一个冒号:表示该选项后必须跟一个参数。参数紧跟在选项后或者以空格隔开。该参数的指针赋给optarg。(如上例中的b:c:)

3. 单个字符后跟两个冒号,表示该选项后可以跟一个参数,也可以不跟。如果跟一个参数,参数必须紧跟在选项后不能以空格隔开。该参数的指针赋给optarg。(如上例中的e::,如果没有跟参数,则optarg = NULL).

(3)getaddrinfo函数

         gethostbyname和gethostbyaddr这两个函数仅仅支持IPv4,getaddrinfo函数能够处理名字到地址以及服务到端口这两种转换,返回的是一个sockaddr结构的链表而不是一个地址清单。

          头文件:#include<netdb.h>------>linux下

                          #include<ws2tcpip.h>------->windows下

         函数原型:

int   getaddrinfo(const char *hostname,const char* service,const struct addrinfo *hints,

                                           struct addrinfo**result)

       参数说明:

hostname:一个主机名或者地址串(IPv4点分十进制串或者IPv6的16进制串)

service:服务名可以是十进制的端口号,也可以是已定义的服务名称,如ftp、http等

hints:可以是一个空指针,也可以是一个指向某个addrinfo结构体的指针,调用者在这个结构中填入关于期望返回的信息类型的暗示。举例来说:指定的服务既可支持TCP也可支持UDP,所以调用者可以把hints结构中的ai_socktype成员设置成SOCK_DGRAM使得返回的仅仅是适用于数据报套接口的信息。

result:本函数通过result指针参数返回一个指向addrinfo结构体链表指针

返回值:0——成功,非0——出错

(4)setuid函数:

setuid函数设置实际用户ID和有效用户ID。Linux的setuid函数和Unix中的setuid函数的行为是不同的。在Linux中, setuid(uid)函数的执行步骤为:(1)如果由普通用户调用,将当前进程的有效ID设置为uid. (2)如果由有效用户ID符为0的进程调用,则将真实,有效和已保存用户ID都设置为uid.

在Unix中.setuid(uid)函数的行为为: (1)如果进程没有超级用户特权,且uid等于实际用户ID或已保存用户ID,则将有效的用户ID设置为uid.否则返回错误.(2)如果进程是有超级用户特权,则将真实、有效和已保存用户表示符都设置为uid.如果两个条件都不满足,则设置errno为EPERM。

函数在执行成功的时候返回0,在出错的时候返回-1

(5)结构体struct msghdr,用于接收数据包,其定义如下:

struct msghdr{

void   *msg_name;//保存数据包的目的地址

int        msg_namelen;//地址长度

struct iovec *msg_iov;

__kernal_size_t  msg_iovlen;

void     *msg_control;

__kernal_size_t msg_controllen;

unsigned             msg_flags;

};

该结构体可以分为四组:

第一组是msg_name和msg_namelen,记录这个消息的名字,其实就是数据包的目的地址。msg_name是指向一个结构体struct sockaddr的指针。

第二组msg_iov和msg_iovlen,记录这个消息的内容。msg_iov是一个指向结构体struct iovec的指针,实际 上,确切的说,应该是一个结构体struct iovec的数组。下面是该结构体的定义:

struct iovec{

void__user *iov_base;

__kernal_size_t iov_len;

}

iov_base指向数据包缓冲区,即参数buff,iov_len是buff的长度。msghdr中允许一次传递多个buff,以数组的形式组织在 msg_iov中,msg_iovlen就记录数组的长度(即有多少个buff)。在我们的ping程序的实例中:

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	<netinet/in_systm.h>
#include	<netinet/ip.h>
#include	<netinet/ip_icmp.h>

#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	<netinet/ip6.h>
#include	<netinet/icmp6.h>

#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 ] <hostname>");
        //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);
}

Host_serv.c:

/* 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 */
}

sock_ntop.c:

#include	"unp.h"

#ifdef	HAVE_SOCKADDR_DL_STRUCT
#include	<net/if_dl.h>
#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);
}

proc_v4.c:

#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);
	}
}

sig_alarm.c

#include	"ping.h"

void
sig_alrm(int signo)
{
	(*pr->fsend)();

	alarm(1);
	return;
}

send_v4.c:

#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的功能那么完整,至少能实现基本的检测。。。。。

上图就是我们整个程序的大致流程。。。。

时间: 2024-09-29 05:02:53

ping程序剖析的相关文章

计算机网络(5)-----ICMP协议和PING程序

控制报文协议(Internet Control Message Protocol) 定义 它是TCP/IP协议族的一个子协议,用于在IP主机.路由器之间传递控制消息.控制消息是指网络通不通.主机是否可达.路由是否可用等网络本身的消息. 简介 ICMP报文就像是IP报文的小弟,总顶着IP报文的名头出来混.因为ICMP报文是在IP报文内部的,如图: IP协议并不是一个可靠的协议,它不保证数据被送达,那么,自然的,保证数据送达的工作应该由其他的模块来完成.其中一个重要的模块就是ICMP(网络控制报文)

Ping程序

一.概述 Ping程序是对两个TCP/IP系统连通性进行测试的基本工具.该程序发送一份ICMP回显请求报文给主机,并等待返回ICMP回显应答. 二.格式 大多数TCP/IP实现都在内核中直接支持Ping服务器--这种服务器不是一个用户进程. ? 在Unix中,把ICMP的标识符字段设置为发送进程的ID号,这样子即使在同一台主机上同时运行了多个Ping程序实例,也能正确识别出返回的信息.

《网络编程》原始套接字 ---ping程序实现

概述 基于字节流套接字(SOCK_STREAM)和数据报套接字(SOCK_DGRAM)不可以访问传输层协议,只是对应用层的报文进行操作,传输层的数据报格式都是由系统提供的协议栈实现,用户只需要填充相应的应用层报文,由系统完成底层报文首部的填充并发送.原始套接字(SOCK_RAW)可以访问位于基层的传输层协议,原始套接字没有端口号. 原始套接字(SOCK_RAW)是一种不同于 SOCK_STREAM.SOCK_DGRAM 的套接字,它实现于系统核心.原始套接字使进程可以读与写 ICMP.IGMP

ping程序的实现

构造icmp包,发送给自己在同一网段的主机,使用select函数,非阻塞方式接收回包.还包括反码算术求和求首部校验和的函数. 转载请注明出处. 可能的情况 1.在线 目的主机直接回复icmp包. 2.终点不可达(发送不到目的主机) 接收到路由器或本机的icmp的终点不可达回包. 3.接受不到回包(能发送到目的主机) 能发送到目的主机,但是被目的主机的防火墙拦截了,不做回复,所以收不到回包. 代码绝大部分是老师上课的内容,自己整理了一遍,加上了一些注释: 1 // SetAvailableIP.c

Ping程序与Traceroute程序的原理(五)

(参考文献)TCP/IP详解,卷1:协议 "ping"这个名字来源于声呐定位操作.目的是为了测试另外一台主机是否可以到达.该程序发送一份ICMP回显请求报文给主机,并等待返回ICMP回显应答. 一般来说,如果不能ping到某台主机那么就不能Telnet或者FTP到那台主机.反过来,如果不能Telnet到某台主机,那么通常可以用ping程序来确定问题出在哪里.Ping程序还能测出到这台主机的往返时间,以表明该主机离我们有"多远". Ping程序 我们称发送回显请求的p

Windows-MFC框架程序剖析

MFC框架程序剖析 1.MFC Microsoft Foundation Class,微软基础类库,是微软开发的C++集合,方便我们使用它来编写Windows程序. 下面是4个基本的类,我们的Visual C++程序基本都会有这4个类 2.MFC AppWizard 它是一个辅助我们生成源代码的工具,它可以帮助我们自动生成基于MFC的源代码,使我们可以定制自己的程序. 3.MFC中的WinMain函数 在MFC中找不到WinMain函数,因为MFC为我们封装了WinMain函数的调用,所以我们看

Linux下实现ping程序

今天参照大神的代码实现了一个ping程序. 总体是先发送一个ping请求,然后循环四次监听返回. send_ping函數 將icmp_hdr(icmp消息的header)的指針指向一片內存空間,然後定義各個屬性.通過memcpy函數將要發送的數據複製到data屬性中. 再通過sendto函數將icmp數據包發送到指定地址 sendto函數 #include <sys/types.h> #include <sys/socket.h> int sendto(int socketfd,

003.同时Ping多个IP(select实现IO复用,信号计时),ping程序升级版

写这个的目的主要是为了以后的方便: 1.信号计时函数的使用 2.ip头的构建和icmp头的构建 3.selec函数t的用法 代码实现: /src/ping.h 1 /* 2 * ping.h 3 * 4 * Created on: 2015年11月6日 5 * Author: root 6 */ 7 8 #ifndef PING_H_ 9 #define PING_H_ 10 11 #endif /* PING_H_ */ 12 13 #include <sys/types.h> 14 #in

TCP协议学习记录 (三) Ping程序 RR选项 记录路由hop

一开始想直接在上个程序改,自己构造IP包头,但后来发现不行,微软不让干了,所有后来选用libcap库来收发包 代码写的很乱.. 1 #pragma pack(4) 2 3 #define ECHO_REQUEST 8 4 #define DATASIZE 65500 5 #define PACKETSIZE 65535 6 #define IPCOUNT 9 7 #define MACSIZE 6 8 #define OPTION_RR 7 9 10 struct ethhdr 11 { 12