这个程序主要运用了ICMPv4协议(回显请求)来测试本机到某服务器的网络是否连通,因为其中用到了原始套接字,所以运行该程序需要管理员权限。
PS:本程序只支持一种输入方式:./myping <hostname>,不支持其他参数。
思路:
1:根据hostname参数创建原始套接字。
2:每隔1秒钟向服务器发送一个ICMP回显请求。
3:循环接收从服务器返回的应答并处理其数据。
上代码:
PS:本程序只支持一种输入方式:./myping <hostname>,不支持其他参数。
思路:
1:根据hostname参数创建原始套接字。
2:每隔1秒钟向服务器发送一个ICMP回显请求。
3:循环接收从服务器返回的应答并处理其数据。
上代码:
#include <signal.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/un.h>
//各种缓冲区的长度
#define BUFSIZE 1500
//ICMP回显请求的长度
#define DATA_LEN 56
struct proto
{
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 */
};
//全局变量
pid_t g_pid;
int g_sockfd;
struct proto g_proto = { NULL, NULL, 0, IPPROTO_ICMP };
//处理服务器返回的ICMP回显信息
void proc_msg(char *, ssize_t, struct msghdr *, struct timeval *);
//发送ICMP回显请求
void send_msg(void);
//循环发送、接收信息
void readloop(void);
//定时器入门函数,每隔一秒一次发送ICMP请求
void sig_alrm(int);
//计算两个时间之间的间隔
void tv_sub(struct timeval *, struct timeval *);
//获取服务器的地址等信息
struct addrinfo *host_serv(const char *host,
const char *serv, int family, int socktype);
//根据服务器信息,得到服务器的IP地址
char *sock_ntop_host(const struct sockaddr *sa, socklen_t salen);
//计算校验和
uint16_t in_cksum(uint16_t *addr, int len);
//输出错误信息,退出程序
void error_quit(const char *str);
int main(int argc, char **argv)
{
int c;
struct addrinfo *ai;
struct sockaddr_in *sin;
char *ip_address;
char *host;
//本程序只支持一种输入方式:./myping <hostname>
if( argc != 2 )
error_quit("usage: myping <hostname>");
host = argv[1];
//将pid的高二位全置为0,ICMP的ID只有16位
g_pid = getpid() & 0xffff;
//设置定时器,每秒钟向服务器发送一次请求
signal(SIGALRM, sig_alrm);
//获取服务器的信息(addrinfo结构)
ai = host_serv(host, NULL, 0, 0);
ip_address = sock_ntop_host(ai->ai_addr, ai->ai_addrlen);
printf("PING %s (%s): %d data bytes\n",
ai->ai_canonname ? ai->ai_canonname : ip_address,
ip_address, DATA_LEN);
//如果返回的协议簇不是AF_INET(IPv4),则退出
if ( ai->ai_family != AF_INET )
error_quit("unknown address family");
//设置proto结构体
g_proto.sasend = ai->ai_addr;
g_proto.sarecv = calloc(1, ai->ai_addrlen);
g_proto.salen = ai->ai_addrlen;
//开始循环发送/接收请求
readloop();
return 0;
}
void readloop(void)
{
int size;
char recvbuf[BUFSIZE];
char controlbuf[BUFSIZE];
struct msghdr msg;
struct iovec iov;
ssize_t n;
struct timeval tval;
//创建一个IPv4的原始套接字
g_sockfd = socket(g_proto.sasend->sa_family, SOCK_RAW, g_proto.icmpproto);
if( -1 == g_sockfd )
error_quit("socket error");
//放弃管理员权限
//这个程序中,只用创建原始套接字时需要管理员权限
setuid(getuid());
//设置socket的接收缓冲区
size = 60 * 1024;
setsockopt(g_sockfd, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size));
//发出第一个请求
sig_alrm(SIGALRM);
//为recvmsg调用设置msghdr结构
iov.iov_base = recvbuf;
iov.iov_len = sizeof(recvbuf);
msg.msg_name = g_proto.sarecv;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = controlbuf;
//开始死循环,不断读取和处理从服务器中返回的信息
while( 1 )
{
msg.msg_namelen = g_proto.salen;
msg.msg_controllen = sizeof(controlbuf);
n = recvmsg(g_sockfd, &msg, 0);
if (n < 0)
{
if (errno == EINTR)
continue;
else
error_quit("recvmsg error");
}
//分析返回内容,产生输出
gettimeofday(&tval, NULL);
proc_msg(recvbuf, n, &msg, &tval);
}
}
void proc_msg(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结构
ip = (struct ip *) ptr;
//得到IP表头的长度
hlen1 = ip->ip_hl << 2;
//如果不是ICMP的应答,则返回
if (ip->ip_p != IPPROTO_ICMP)
return;
icmp = (struct icmp *) (ptr + hlen1);
//长度不足,不是合法应答
if ( (icmplen = len - hlen1) < 8)
return;
//不是回显应答,返回
if (icmp->icmp_type != ICMP_ECHOREPLY)
return;
//不是我们发出请求的应答,返回
if (icmp->icmp_id != g_pid)
return;
//长度不足,非法应答
if (icmplen < 16)
return;
//计算网络延时
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(g_proto.sarecv, g_proto.salen),
icmp->icmp_seq, ip->ip_ttl, rtt);
}
void send_msg(void)
{
int len;
int res;
struct icmp *icmp;
char sendbuf[BUFSIZE];
static int nsent = 0;
//根据ICMPv4协议来设置发送信息
icmp = (struct icmp *) sendbuf;
//ICMP回显请求
icmp->icmp_type = ICMP_ECHO;
icmp->icmp_code = 0;
//ICMP标识符字段为本进程的PID
icmp->icmp_id = g_pid;
//ICMP序列号字段为不断递增的全局变量nsent
icmp->icmp_seq = nsent++;
//ICMP数据字段为当前时间截,空白部分填充0xa5
memset(icmp->icmp_data, 0xa5, DATA_LEN);
gettimeofday((struct timeval *)icmp->icmp_data, NULL);
//计算并填充校验和
len = 8 + DATA_LEN;
icmp->icmp_cksum = 0;
icmp->icmp_cksum = in_cksum((u_short *) icmp, len);
//发送数据
res = sendto(g_sockfd, sendbuf, len, 0, g_proto.sasend, g_proto.salen);
if( -1 == res )
error_quit("sendto error");
}
void sig_alrm(int signo)
{
send_msg();
alarm(1);
}
void tv_sub(struct timeval *out, struct timeval *in)
{
//将两个时间相减,并把结果存入第一个参数中( out -= in )
if ( (out->tv_usec -= in->tv_usec) < 0)
{
--out->tv_sec;
out->tv_usec += 1000000;
}
out->tv_sec -= in->tv_sec;
}
struct addrinfo *host_serv(const char *host, const char *serv, int family, int socktype)
{
int n;
struct addrinfo hints, *res;
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_flags = AI_CANONNAME;
hints.ai_family = family;
hints.ai_socktype = socktype;
n = getaddrinfo(host, serv, &hints, &res);
if ( n != 0 )
error_quit("getaddrinfo error");
return res;
}
char *sock_ntop_host(const struct sockaddr *sa, socklen_t salen)
{
static char str[128];
struct sockaddr_in *sin = (struct sockaddr_in *) sa;
//本程序只支持IPv4协议
if( sa->sa_family != AF_INET )
error_quit("sock_ntop_host: the type must be AF_INET");
if (inet_ntop(AF_INET, &sin->sin_addr, str, sizeof(str)) == NULL)
error_quit("inet_ntop error");
return str;
}
//《UNIX网络编程》书上的源码
uint16_t in_cksum(uint16_t *addr, int len)
{
int nleft = len;
uint32_t sum = 0;
uint16_t *w = addr;
uint16_t answer = 0;
/*
* Our algorithm is simple, using a 32 bit accumulator (sum), we add
* sequential 16 bit words to it, and at the end, fold back all the
* carry bits from the top 16 bits into the lower 16 bits.
*/
while (nleft > 1)
{
sum += *w++;
nleft -= 2;
}
/* 4mop up an odd byte, if necessary */
if (nleft == 1) {
*(unsigned char *)(&answer) = *(unsigned char *)w ;
sum += answer;
}
/* 4add back carry outs from top 16 bits to low 16 bits */
sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */
sum += (sum >> 16); /* add carry */
answer = ~sum; /* truncate to 16 bits */
return(answer);
}
void error_quit(const char *str)
{
//输出错误信息,退出程序
fprintf(stderr, "%s", str);
if( errno != 0 )
fprintf(stderr, " : %s", strerror(errno));
fprintf(stderr, "\n");
exit(1);
}
运行示例:
qch@LinuxMint ~/program/tcode $ gcc myping.c -o myping
qch@LinuxMint ~/program/tcode $ sudo ./myping www.baidu.com
PING www.a.shifen.com (115.239.210.26): 56 data bytes
64 bytes from 115.239.210.26: seq=0, ttl=128, rtt=31.272 ms
64 bytes from 115.239.210.26: seq=1, ttl=128, rtt=34.722 ms
64 bytes from 115.239.210.26: seq=2, ttl=128, rtt=30.822 ms
64 bytes from 115.239.210.26: seq=3, ttl=128, rtt=31.273 ms
64 bytes from 115.239.210.26: seq=4, ttl=128, rtt=29.995 ms
...........................