UNP1:linux下实现ping程序

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

原文地址:https://www.cnblogs.com/aLittleBitCool/p/2165423.html