UNP1里面给出了一个ping程序的实现,里面包含了ipv4和ipv6两个版本。
经过学习,对里面的代码做了一点点小得修改(还原了基本的API),再加了一点注释,测试可以通过。
经过手敲了这段代码,收获还是很大的。对raw socket的编程有了基本的概念,同时也对icmp包和ip包有了更深入的了解。
修改后的代码如下,总共分为三个文件:
ping.h
#include<stdio.h>
#include<time.h>
#include<errno.h>
#include<unistd.h>
#include<stdlib.h>
#include<netdb.h>
#include<string.h>
#include<strings.h>
#include<netinet/in.h>
#include<pthread.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<netinet/in_systm.h>
#include<netinet/ip.h>
#include<netinet/ip_icmp.h>
#include<signal.h>
#define BUFSIZE 1500
char sendbuf[BUFSIZE];
int datalen;
char *host;
int nsent;
pid_t pid;
int sockfd;
int verbose;
void proc_v4(char *,ssize_t,struct msghdr*,struct timeval *);
void send_v4(void);
void readloop(void);
void sig_alarm(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;
struct sockaddr *sarecv;
socklen_t salen;
int icmpproto;
}*pr;
main.c:获取目标ip
#include"ping.h"
struct proto proto_v4={proc_v4,send_v4,NULL,NULL,NULL,0,IPPROTO_ICMP};
int datalen=56;
struct addrinfo *host_serv(const char *host,const char *serv,int family,int socktype)
{
int n;
struct addrinfo hints,*res;
bzero(&hints,sizeof(hints));
hints.ai_flags=AI_CANONNAME;
hints.ai_family=family;
hints.ai_socktype=socktype;
if((n=getaddrinfo(host,serv,&hints,&res))!=0){
return NULL;
}
return (res);
}
int main(int argc,char *argv[])
{
int c;
struct addrinfo *ai;
char h[20]={0};
opterr=0;
while((c=getopt(argc,argv,"v"))!=-1){
switch(c){
case 'v':
verbose++;
break;
case '?':
printf("unrecognized option: %c\n",c);
return 0;
}
}
if(optind!=argc-1)
printf("usage: ping [-v] <hostname>\n");
host=argv[optind];
pid=getpid() & 0xffff;
signal(SIGALRM,sig_alarm);
ai=host_serv(host,NULL,0,0);
inet_ntop(AF_INET,&((struct sockaddr_in*)(ai->ai_addr))->sin_addr,h,sizeof(h));
printf("PING %s (%s):%d data bytes\n",
ai->ai_canonname?ai->ai_canonname:h,h,datalen);
if(ai->ai_family==AF_INET){
pr=&proto_v4;
}else{
printf("unknown address family %d\n",ai->ai_family);
}
pr->sasend=ai->ai_addr;
pr->sarecv=(struct sockaddr*)calloc(1,ai->ai_addrlen);
pr->salen=ai->ai_addrlen;
readloop();
exit(0);
}
readloop.c:发送和接收icmp包
#include"ping.h"
//get rtt
void tv_sub(
struct timeval *out, //time,tv_sev is microseconds
struct timeval *in)
{
if((out->tv_usec-=in->tv_usec)<0){
--out->tv_sec;
out->tv_sec+=1000000;
}
out->tv_sec-=in->tv_sec;
}
void proc_v4(char *ptr,ssize_t len,struct msghdr *msg,struct timeval * tvrecv)
{
int hlen1,icmplen;
char host[20];
double rtt;
struct ip *ip;
struct icmp *icmp;
struct timeval *tvsend;
ip=(struct ip*)ptr;
hlen1=ip->ip_hl<<2;//get ipdatagram length,include option
if(ip->ip_p!=IPPROTO_ICMP){
return;
}
icmp=(struct icmp*)(ptr+hlen1);
if((icmplen=len-hlen1)<8)
return;
if(icmp->icmp_type==ICMP_ECHOREPLY){
if(icmp->icmp_id!=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,inet_ntop(AF_INET,&((struct sockaddr_in*)(pr->sarecv))->sin_addr,host,sizeof(host)),icmp->icmp_seq,ip->ip_ttl,rtt);
}
}
//call send data func
void sig_alarm(int signo)
{
(*pr->fsend)();
alarm(1);
return;
}
//create checksum
uint16_t in_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;
}
//send icmp data
void send_v4(void){
int len;
struct icmp *icmp;
//init icmp datagram
icmp=(struct icmp*)sendbuf;
icmp->icmp_type=ICMP_ECHO; //type
icmp->icmp_code=0; //code
icmp->icmp_id=pid;
icmp->icmp_seq=nsent++;
gettimeofday((struct timeval *)icmp->icmp_data,NULL);//get send time
len=8+datalen;
icmp->icmp_cksum=0;
icmp->icmp_cksum=in_cksum((u_short *)icmp,len);
sendto(sockfd,sendbuf,len,0,pr->sasend,pr->salen);//send data
}
void readloop(void){
int i=0;
int size;
char recvbuf[BUFSIZE]; //get the response
char controlbuf[BUFSIZE];
struct msghdr msg;
struct iovec iov; //send data
ssize_t n;
struct timeval tval;
sockfd=socket(pr->sasend->sa_family,SOCK_RAW,pr->icmpproto);
//set effective uid to real uid
setuid(getuid());
if(pr->finit)
(*pr->finit)();
size=60*1024;
setsockopt(sockfd,SOL_SOCKET,SO_RCVBUF,&size,sizeof(size));//set recvbuf size
sig_alarm(SIGALRM);//cal fun
//init buffer
iov.iov_base=recvbuf; //set recvbuf
iov.iov_len=sizeof(recvbuf);//recvbuf len
msg.msg_name=pr->sarecv;//sockaddr
msg.msg_iov=&iov;
msg.msg_iovlen=1;
msg.msg_control=controlbuf;
//loop ping
for(;;){
msg.msg_namelen=pr->salen;
msg.msg_controllen=sizeof(controlbuf);
n=recvmsg(sockfd,&msg,0); //receive data
if(n<0){
if(errno=EINTR)
continue;
else
printf("recvmsg error\n");
}
gettimeofday(&tval,NULL);//receive time
(*pr->fproc)(recvbuf,n,&msg,&tval);//handle receive data
}
}