参考 http://blog.csdn.net/qy532846454/article/details/5429700 学习 自己实现 linux ping 命令,本文主要总结实现过程中的几个重要知识点。
PingPro.c
/*
* Realize ping command
*
* ============================================================================
* Version: 0.0
* File: PingPro.c
* Author: yoyo
* Time: 2010-03-08
*/
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <string.h>
#include "ping.h"
#define MAX_SEND_TIME 5
void ping(char *ip);
void sigint(int sig);
void *signal_set(int signo, void (*func)(int));
void signal_init();
char ip[100];
int main(int argc, char *argv[])
{
// check parameter
if(argc != 2)
{
printf("Usage: ./pingy IP\n");
exit(1);
}
memcpy(ip, argv[1], sizeof(argv[1]));
ping(argv[1]);
return 0;
}
void ping(char *ip)
{
int sockfd;
struct sockaddr_in dstaddr;
int times = 0;
if((sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) == -1)
{
printf("Create Socket Fail!\n");
exit(1);
}
// fill dstination address
dstaddr.sin_family = AF_INET;
dstaddr.sin_port = htons(0);
if(inet_pton(AF_INET, ip, &dstaddr.sin_addr) == -1)
{
printf("Get IP %s Error!\n", ip);
exit(1);
}
// set signal
signal_init();
// print header
print_head(ip);
// ping
while(times < MAX_SEND_TIME)
{
send_ping(sockfd, &dstaddr);
recv_ping(sockfd, &dstaddr);
times++;
}
// print statistics
print_stat(ip);
}
/* ============================================================================ */
/* Singal Handle */
/* ============================================================================ */
void sigint(int sig)
{
print_stat(ip);
exit(0);
}
void *signal_set(int signo, void (*func)(int))
{
int ret;
struct sigaction sig;
struct sigaction osig;
sig.sa_handler = func;
sigemptyset(&sig.sa_mask);
sig.sa_flags = 0;
#ifdef SA_RESTART
sig.sa_flags |= SA_RESTART;
#endif /* SA_RESTART */
ret = sigaction(signo, &sig, &osig);
if (ret < 0)
return (SIG_ERR);
else
return (osig.sa_handler);
}
/* Initialization of signal handles. */
void signal_init()
{
signal_set(SIGINT, sigint);
signal_set(SIGTERM, sigint);
}
ping.c
#include <stdio.h>
#include <stdlib.h>
#include <netinet/ip_icmp.h>
#include <string.h>
#include "ping.h"
#define ICMP_ECHOREPLY 0
#define ICMP_ECHOREQ 8
// statistics
int nsend = 0, nrecv = 0;
int rrt[10];
int packsize;
void get_data(int rrt[], int size, int *min, int *max, int *avg);
void tv_sub(struct timeval *out,struct timeval *in);
// calculate checksum of icmp header
uint16_t cal_cksum(uint16_t *addr, int len)
{
int nleft = len;
uint32_t sum = 0;
uint16_t *w = addr;
uint16_t answer = 0;
while (nleft > 1) {
sum += *w++;
nleft -= 2;
}
if (nleft == 1) {
*(unsigned char *)(&answer) = *(unsigned char *)w ;
sum += answer;
}
sum = (sum >> 16) + (sum & 0xffff);
sum += (sum >> 16);
answer = ~sum;
return(answer);
}
void print_head(char *ip)
{
packsize = sizeof(struct icmp) + sizeof(struct timeval);
printf("Ping %s with %d bytes of data:\n", ip, packsize);
}
void print_stat(char *ip)
{
int min_rrt, max_rrt, avg_rrt;
int lost;
if(nsend < 1)
return;
get_data(rrt, nsend, &min_rrt, &max_rrt, &avg_rrt);
lost = nsend - nrecv;
printf("========================================================================\n");
printf("Ping Statistics for %s:\n", ip);
printf("\tPackets: Send = %d, Received = %d, Lost = %d(%.1f%% lost),\n", nsend, nrecv, lost, (lost / (nsend * 1.0) * 100.0));
printf("Approximate round trip times in milli-seconds:\n");
printf("\tMinimum = %dms, Maximum = %dms, Average = %dms\n", min_rrt, max_rrt, avg_rrt);
}
void send_ping(int sockfd, struct sockaddr_in *dstaddr)
{
char buf[100];
size_t len = sizeof(struct icmp);
socklen_t dstlen = sizeof(struct sockaddr_in);
struct icmp *echo;
memset(buf, 0, sizeof(buf));
echo = (struct icmp*)buf;
echo = (struct icmp *)buf;
echo->icmp_type = ICMP_ECHOREQ;
echo->icmp_code = 0;
echo->icmp_cksum = 0;
echo->icmp_id = getpid();
echo->icmp_seq = nsend;
struct timeval *tval= (struct timeval *)echo->icmp_data;
gettimeofday(tval,NULL);
echo->icmp_cksum = cal_cksum((uint16_t *)echo, packsize);
// send ping message
if(sendto(sockfd, buf, len, 0, (struct sockaddr*)dstaddr, dstlen) == -1)
printf("Send Ping Message Error!\n");
nsend++;
}
void recv_ping(int sockfd, struct sockaddr_in *dstaddr)
{
char buf[100];
ssize_t n;
struct ip *ip;
struct icmp *icmp;
socklen_t dstlen = sizeof(struct sockaddr_in);
int ttl;
fd_set rset;
int maxfd = sockfd + 1;
struct timeval timeo, *tvsend, tvrecv;
unsigned char *p;
unsigned char ipaddr[100];
int time;
memset(buf, 0, sizeof(buf));
timeo.tv_sec = 3;
timeo.tv_usec = 0;
FD_ZERO(&rset);
FD_SET(sockfd, &rset);
gettimeofday(&tvrecv, NULL);
while(1)
{
// set timeout 3s
if((n = select(maxfd, &rset, NULL, NULL, &timeo)) == -1)
{
printf("Select Error!\n");
exit(1);
}
if(n <= 0)
{
printf("Request Time Out!\n");
fflush(stdout);
break;
}
if((n = recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)dstaddr, &dstlen)) == -1)
printf("Recv Ping Message Error!\n");
// check if icmp
ip = (struct ip*)buf;
ttl = ip->ip_ttl;
// get src ip
p = (unsigned char *)&ip->ip_src;
sprintf(ipaddr, "%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
// check if loop
if(strcmp(ipaddr, "127.0.0.1") == 0)
{
//perror("Loop!");
continue;
}
// check if icmp packet
if(ip->ip_p != IPPROTO_ICMP)
{
//perror("Not ICMP Protocol!");
continue;
}
// check if icmp reply
icmp = (struct icmp*)(buf + sizeof(struct ip));
if(icmp->icmp_type == ICMP_ECHOREPLY)
{
// check id
if(icmp->icmp_id == getpid())
{
tvsend = (struct timeval *)icmp->icmp_data;
tv_sub(&tvrecv, tvsend);
time = tvrecv.tv_sec * 1000 + tvrecv.tv_usec / 1000;
nrecv++;
printf("\tReply from %s: bytes = %d time = %dms TTL = %d\n", ipaddr, n, time, ttl);
rrt[nsend - 1] = time;
break;
}
else
{
//perror("Not Expected Identifier!");
continue;
}
}
else
{
//perror("Not ICMP Reply Message!");
continue;
}
}
}
/* ================================================================ */
/* Auxiliary Function */
/* ================================================================ */
/*
* Function: get_data
* Purpose: get statistics from data rrt array, such as minimum rrt,
* maximum rrt, average rrt
* Parameters: rrt - round trip time
* size - array size
* min - store minimum time
* max - store maximum time
* avg - store average time
* Return: none
*/
void get_data(int rrt[], int size, int *min, int *max, int *avg)
{
int sum = 0;
int i;
*min = rrt[0], *max = rrt[0];
for(i = 0; i < size; i++)
{
sum += rrt[i];
if(rrt[i] < *min)
*min = rrt[i];
if(rrt[i] > *max)
*max = rrt[i];
}
*avg = sum / size;
}
void tv_sub(struct timeval *out,struct timeval *in)
{
if((out->tv_usec -= in->tv_usec) < 0)
{
--out->tv_sec;
out->tv_usec += 1000000;
}
out->tv_sec -= in->tv_sec;
}
ping 命令的实现主要就是 1) 使用 socket 创建 raw type socket, 使用 ICMP 协议,2) 填充 ICMP 协议数据包, 3) 发送和接收 ICMP 数据包。
if((sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) == -1)
用户空间调用socket 传入如上参数,就是指定 socket 使用 AF_INET/PF_INET,也就是IP protocol family, 指定了地址族/协议族。
/* Supported address families. */
#define AF_UNSPEC 0
#define AF_UNIX 1 /* Unix domain sockets */
#define AF_LOCAL 1 /* POSIX name for AF_UNIX */
#define AF_INET 2 /* Internet IP Protocol */
#define AF_AX25 3 /* Amateur Radio AX.25 */
#define AF_IPX 4 /* Novell IPX */
#define AF_APPLETALK 5 /* AppleTalk DDP */
#define AF_NETROM 6 /* Amateur Radio NET/ROM */
#define AF_BRIDGE 7 /* Multiprotocol bridge */
#define AF_ATMPVC 8 /* ATM PVCs */
#define AF_X25 9 /* Reserved for X.25 project */
#define AF_INET6 10 /* IP version 6 */
#define AF_ROSE 11 /* Amateur Radio X.25 PLP */
#define AF_DECnet 12 /* Reserved for DECnet project */
#define AF_NETBEUI 13 /* Reserved for 802.2LLC project*/
#define AF_SECURITY 14 /* Security callback pseudo AF */
#define AF_KEY 15 /* PF_KEY key management API */
#define AF_NETLINK 16
#define AF_ROUTE AF_NETLINK /* Alias to emulate 4.4BSD */
#define AF_PACKET 17 /* Packet family */
#define AF_ASH 18 /* Ash */
#define AF_ECONET 19 /* Acorn Econet */
#define AF_ATMSVC 20 /* ATM SVCs */
#define AF_RDS 21 /* RDS sockets */
#define AF_SNA 22 /* Linux SNA Project (nutters!) */
#define AF_IRDA 23 /* IRDA sockets */
#define AF_PPPOX 24 /* PPPoX sockets */
#define AF_WANPIPE 25 /* Wanpipe API Sockets */
#define AF_LLC 26 /* Linux LLC */
#define AF_CAN 29 /* Controller Area Network */
#define AF_TIPC 30 /* TIPC sockets */
#define AF_BLUETOOTH 31 /* Bluetooth sockets */
#define AF_IUCV 32 /* IUCV sockets */
#define AF_RXRPC 33 /* RxRPC sockets */
#define AF_ISDN 34 /* mISDN sockets */
#define AF_PHONET 35 /* Phonet sockets */
#define AF_IEEE802154 36 /* IEEE802154 sockets */
#define AF_MAX 37 /* For now.. */
/* Protocol families, same as address families. */
#define PF_UNSPEC AF_UNSPEC
#define PF_UNIX AF_UNIX
#define PF_LOCAL AF_LOCAL
#define PF_INET AF_INET
#define PF_AX25 AF_AX25
#define PF_IPX AF_IPX
#define PF_APPLETALK AF_APPLETALK
#define PF_NETROM AF_NETROM
#define PF_BRIDGE AF_BRIDGE
#define PF_ATMPVC AF_ATMPVC
#define PF_X25 AF_X25
#define PF_INET6 AF_INET6
#define PF_ROSE AF_ROSE
#define PF_DECnet AF_DECnet
#define PF_NETBEUI AF_NETBEUI
#define PF_SECURITY AF_SECURITY
#define PF_KEY AF_KEY
#define PF_NETLINK AF_NETLINK
#define PF_ROUTE AF_ROUTE
#define PF_PACKET AF_PACKET
#define PF_ASH AF_ASH
#define PF_ECONET AF_ECONET
#define PF_ATMSVC AF_ATMSVC
#define PF_RDS AF_RDS
#define PF_SNA AF_SNA
#define PF_IRDA AF_IRDA
#define PF_PPPOX AF_PPPOX
#define PF_WANPIPE AF_WANPIPE
#define PF_LLC AF_LLC
#define PF_CAN AF_CAN
#define PF_TIPC AF_TIPC
#define PF_BLUETOOTH AF_BLUETOOTH
#define PF_IUCV AF_IUCV
#define PF_RXRPC AF_RXRPC
#define PF_ISDN AF_ISDN
#define PF_PHONET AF_PHONET
#define PF_IEEE802154 AF_IEEE802154
#define PF_MAX AF_MAX
IPPROTO_ICMP 指定了 socket 使用IP 协议族中 具体的哪一个协议,这里是 ICMP。
/* Standard well-defined IP protocols. */
enum {
IPPROTO_IP = 0, /* Dummy protocol for TCP */
IPPROTO_ICMP = 1, /* Internet Control Message Protocol */
IPPROTO_IGMP = 2, /* Internet Group Management Protocol */
IPPROTO_IPIP = 4, /* IPIP tunnels (older KA9Q tunnels use 94) */
IPPROTO_TCP = 6, /* Transmission Control Protocol */
IPPROTO_EGP = 8, /* Exterior Gateway Protocol */
IPPROTO_PUP = 12, /* PUP protocol */
IPPROTO_UDP = 17, /* User Datagram Protocol */
IPPROTO_IDP = 22, /* XNS IDP protocol */
IPPROTO_DCCP = 33, /* Datagram Congestion Control Protocol */
IPPROTO_RSVP = 46, /* RSVP protocol */
IPPROTO_GRE = 47, /* Cisco GRE tunnels (rfc 1701,1702) */
IPPROTO_IPV6 = 41, /* IPv6-in-IPv4 tunnelling */
IPPROTO_ESP = 50, /* Encapsulation Security Payload protocol */
IPPROTO_AH = 51, /* Authentication Header protocol */
IPPROTO_BEETPH = 94, /* IP option pseudo header for BEET */
IPPROTO_PIM = 103, /* Protocol Independent Multicast */
IPPROTO_COMP = 108, /* Compression Header protocol */
IPPROTO_SCTP = 132, /* Stream Control Transport Protocol */
IPPROTO_UDPLITE = 136, /* UDP-Lite (RFC 3828) */
IPPROTO_RAW = 255, /* Raw IP packets */
IPPROTO_MAX
};
SOCK_RAW 指定创建何种类型的socket。
/**
* enum sock_type - Socket types
* @SOCK_STREAM: stream (connection) socket
* @SOCK_DGRAM: datagram (conn.less) socket
* @SOCK_RAW: raw socket
* @SOCK_RDM: reliably-delivered message
* @SOCK_SEQPACKET: sequential packet socket
* @SOCK_DCCP: Datagram Congestion Control Protocol socket
* @SOCK_PACKET: linux specific way of getting packets at the dev level.
* For writing rarp and other similar things on the user level.
*
* When adding some new socket type please
* grep ARCH_HAS_SOCKET_TYPE include/asm-* /socket.h, at least MIPS
* overrides this enum for binary compat reasons.
*/
enum sock_type {
SOCK_STREAM = 1,
SOCK_DGRAM = 2,
SOCK_RAW = 3,
SOCK_RDM = 4,
SOCK_SEQPACKET = 5,
SOCK_DCCP = 6,
SOCK_PACKET = 10,
};
因为我们要自己构建ICMP 网络层数据包,所以使用的是SOCK_RAW, raw socket.
通过上面这张图可以清晰看到各层协议所处的位置。
在 TCP/IP Guide 书中可以看到ICMP message class, type, code, 以及 ICMP Common Message Format
下面看下 用户空间 执行 socket 系统调用,linux 内核都做了些什么,
<umts_sholes_kernel-2.6.32.60>/net/socket.c
SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)
{
retval = sock_create(family, type, protocol, &sock);
int sock_create(int family, int type, int protocol, struct socket **res)
{
return __sock_create(current->nsproxy->net_ns, family, type, protocol, res, 0);
}
static int __sock_create(struct net *net, int family, int type, int protocol,
struct socket **res, int kern)
{
const struct net_proto_family *pf;
// selinux 走这
err = security_socket_create(family, type, protocol, kern);
//非 selinux 走这,获取具体地址族/协议族的 socket create 方法,这里需要看下各个地址族/协议族 各自注册socket 接口的地方
pf = rcu_dereference(net_families[family]);
// 执行 特定于某个协议族的socket create 方法
err = pf->create(net, sock, protocol);
下面看下协议族如何注册socket 接口
/**
* sock_register - add a socket protocol handler
* @ops: description of protocol
*
* This function is called by a protocol handler that wants to
* advertise its address family, and have it linked into the
* socket interface. The value ops->family coresponds to the
* socket system call protocol family.
*/
int sock_register(const struct net_proto_family *ops)
{
// 看来是通过这个接口注册 socket
net_families[ops->family] = ops;
---- sock_register Matches (32 in 30 files) ----
Af_ax25.c (net\ax25): sock_register(&ax25_family_ops);
Af_bluetooth.c (net\bluetooth): err = sock_register(&bt_sock_family_ops);
Af_can.c (net\can): sock_register(&can_family_ops);
Af_decnet.c (net\decnet): sock_register(&dn_family_ops);
Af_econet.c (net\econet): sock_register(&econet_family_ops);
Af_ieee802154.c (net\ieee802154): rc = sock_register(&ieee802154_family_ops);
Af_inet.c (net\ipv4): (void)sock_register(&inet_family_ops);
Af_inet6.c (net\ipv6): err = sock_register(&inet6_family_ops);
Af_ipx.c (net\ipx): sock_register(&ipx_family_ops);
Af_irda.c (net\irda): rc = sock_register(&irda_family_ops);
Af_iucv.c (net\iucv): err = sock_register(&iucv_sock_family_ops);
Af_key.c (net\key): err = sock_register(&pfkey_family_ops);
Af_llc.c (net\llc): rc = sock_register(&llc_ui_family_ops);
Af_netlink.c (net\netlink): sock_register(&netlink_family_ops);
Af_netrom.c (net\netrom): if (sock_register(&nr_family_ops)) {
Af_packet.c (net\packet): sock_register(&packet_family_ops);
Af_phonet.c (net\phonet): err = sock_register(&phonet_proto_family);
Af_rds.c (net\rds): ret = sock_register(&rds_family_ops);
Af_rose.c (net\rose): sock_register(&rose_family_ops);
Af_rxrpc.c (net\rxrpc): ret = sock_register(&rxrpc_family_ops);
Af_unix.c (net\unix): sock_register(&unix_family_ops);
Af_x25.c (net\x25): sock_register(&x25_family_ops);
Ddp.c (net\appletalk): (void)sock_register(&atalk_family_ops);
Net.h (include\linux):extern int sock_register(const struct net_proto_family *fam);
Pppox.c (drivers\net): return sock_register(&pppox_proto_family);
Pvc.c (net\atm): return sock_register(&pvc_family_ops);
Socket.c (drivers\isdn\misdn): err = sock_register(&mISDN_sock_family_ops);
Socket.c (net): * sock_register - add a socket protocol handler
Socket.c (net):int sock_register(const struct net_proto_family *ops)
Socket.c (net):EXPORT_SYMBOL(sock_register);
Socket.c (net\tipc): res = sock_register(&tipc_family_ops);
Svc.c (net\atm): return sock_register(&svc_family_ops);
af_inet.c
static int __init inet_init(void)
{
/*
* Tell SOCKET that we are alive...
*/
(void)sock_register(&inet_family_ops);
static struct net_proto_family inet_family_ops = {
.family = PF_INET,
.create = inet_create,
.owner = THIS_MODULE,
};
这样,IPv4 协议的 socket create, 其实是执行inet_create
打包好 ICMP message 之后, 通过 sendto 系统调用,发送 ICMP message
if(sendto(sockfd, buf, len, 0, (struct sockaddr*)dstaddr, dstlen) == -1)
/*
* Send a datagram to a given address. We move the address into kernel
* space and check the user space data area is readable before invoking
* the protocol.
*/
SYSCALL_DEFINE6(sendto, int, fd, void __user *, buff, size_t, len,
unsigned, flags, struct sockaddr __user *, addr,
int, addr_len)
{
err = sock_sendmsg(sock, &msg, len);
int sock_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)
{
ret = __sock_sendmsg(&iocb, sock, msg, size);
static inline int __sock_sendmsg(struct kiocb *iocb, struct socket *sock,
struct msghdr *msg, size_t size)
{
err = sock->ops->sendmsg(iocb, sock, msg, size);
static const struct proto_ops inet_sockraw_ops = {
.family = PF_INET,
.owner = THIS_MODULE,
.release = inet_release,
.bind = inet_bind,
.connect = inet_dgram_connect,
.socketpair = sock_no_socketpair,
.accept = sock_no_accept,
.getname = inet_getname,
.poll = datagram_poll,
.ioctl = inet_ioctl,
.listen = sock_no_listen,
.shutdown = inet_shutdown,
.setsockopt = sock_common_setsockopt,
.getsockopt = sock_common_getsockopt,
.sendmsg = inet_sendmsg,
.recvmsg = sock_common_recvmsg,
.mmap = sock_no_mmap,
.sendpage = inet_sendpage,
#ifdef CONFIG_COMPAT
.compat_setsockopt = compat_sock_common_setsockopt,
.compat_getsockopt = compat_sock_common_getsockopt,
#endif
};
/* Upon startup we insert all the elements in inetsw_array[] into
* the linked list inetsw.
*/
static struct inet_protosw inetsw_array[] =
{
{
.type = SOCK_STREAM,
.protocol = IPPROTO_TCP,
.prot = &tcp_prot,
.ops = &inet_stream_ops,
.capability = -1,
.no_check = 0,
.flags = INET_PROTOSW_PERMANENT |
INET_PROTOSW_ICSK,
},
{
.type = SOCK_DGRAM,
.protocol = IPPROTO_UDP,
.prot = &udp_prot,
.ops = &inet_dgram_ops,
.capability = -1,
.no_check = UDP_CSUM_DEFAULT,
.flags = INET_PROTOSW_PERMANENT,
},
{
.type = SOCK_RAW,
.protocol = IPPROTO_IP, /* wild card */
.prot = &raw_prot,
.ops = &inet_sockraw_ops,
.capability = CAP_NET_RAW,
.no_check = UDP_CSUM_DEFAULT,
.flags = INET_PROTOSW_REUSE,
}
};
static int __init inet_init(void)
{
for (q = inetsw_array; q < &inetsw_array[INETSW_ARRAY_LEN]; ++q)
inet_register_protosw(q);
int inet_sendmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *msg,
size_t size)
{
struct sock *sk = sock->sk;
/* We may need to bind the socket. */
if (!inet_sk(sk)->num && inet_autobind(sk))
return -EAGAIN;
return sk->sk_prot->sendmsg(iocb, sk, msg, size);
}
struct proto raw_prot = {
.name = "RAW",
.owner = THIS_MODULE,
.close = raw_close,
.destroy = raw_destroy,
.connect = ip4_datagram_connect,
.disconnect = udp_disconnect,
.ioctl = raw_ioctl,
.init = raw_init,
.setsockopt = raw_setsockopt,
.getsockopt = raw_getsockopt,
.sendmsg = raw_sendmsg,
.recvmsg = raw_recvmsg,
.bind = raw_bind,
.backlog_rcv = raw_rcv_skb,
.hash = raw_hash_sk,
.unhash = raw_unhash_sk,
.obj_size = sizeof(struct raw_sock),
.h.raw_hash = &raw_v4_hashinfo,
#ifdef CONFIG_COMPAT
.compat_setsockopt = compat_raw_setsockopt,
.compat_getsockopt = compat_raw_getsockopt,
#endif
};
static int raw_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
size_t len)
{