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