Linux下实现ping程序

今天参照大神的代码实现了一个ping程序。

总体是先发送一个ping请求,然后循环四次监听返回。

send_ping函數

  1. 將icmp_hdr(icmp消息的header)的指針指向一片內存空間,然後定義各個屬性。通過memcpy函數將要發送的數據複製到data屬性中。

    再通過sendto函數將icmp數據包發送到指定地址

    sendto函數

    #include <sys/types.h>
    #include <sys/socket.h>
    int sendto(int socketfd, const void * msg, int len, unsigned int flags, const struct sockaddr * to, int tolen);
    

    socketfd:socket套接字描述符

    成功則返回發送的字符數,否則返回-1.

    與之對應的是recvfrom函數

    #include <sys/types.h>
    #include <sys/socket.h>
    int sendto(int socketfd, void* buffer, int len, unsigned int flags, const struct sockaddr * from, int fromlen);
    

    通過套接字接收信息,存放到buffer中。

handle_packet函數

  1. recv_reply函數將返回的ICMP消息存儲在recv_buf中,在處理返回結果時,將一個iphdr類型的指針指向這塊內存進行操作。

    這塊內存信息包含的數據有IP信息頭部長度,數據部分長度。數據部分包含了icmp的頭部信息和數據信息。icmp的起始地址便是recvbuf + ip頭的長度。然後要計算校驗和,如果檢驗和不為0,說明信息出現了錯誤。

    然後還要判斷icmp_id和icmp_type。

贴一下源码

#include<stdio.h>
#include<stdlib.h>
#include<sys/time.h>
#include<unistd.h>
#include<string.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<netdb.h>
#include<errno.h>
#include<arpa/inet.h>
#include<signal.h>
#include<netinet/in.h>

#ifndef _LITTLE_ENDIAN_BITFIELD
#define _LITTLE_ENDIAN_BITFIELD
#endif

#define IP_HSIZE sizeof(struct iphdr)   //定义IP_HSIZE为ip头部长度
#define IPVERSION  4   //定义IPVERSION为4,指出用ipv4

#define ICMP_ECHOREPLY 0 //Echo应答
#define ICMP_ECHO      8 //Echo请求

#define BUFSIZE 1500     //发送缓存最大值
#define DEFAULT_LEN 56   //ping 消息数据默认大小

//数据类型别名
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;

//ICMP消息头部

struct icmphdr
{
    u8 type;
    u8 code;
    u16 checksum;
    union
    {
        struct
        {
            u16 id;
            u16 sequence;
        }echo;

        u32 gateway;
        struct
        {
            u16 unused;
            u16 mtu;
        }frag; //pmtu发现
    }un;
    u32  icmp_timestamp[2];//时间戳
    //ICMP数据占位符
    u8 data[0];
#define icmp_id un.echo.id
#define icmp_seq un.echo.sequence
};

#define ICMP_HSIZE sizeof(struct icmphdr)
struct iphdr
{
#if defined _LITTLE_ENDIAN_BITFIELD
    u8 hlen:4,
ver: 4;
#elif defined _BIG_ENFIAN_BITFELD
    u8 ver:4,
hlen:4;
#endif

    u8 tos;
    u16 tot_len;
    u16 id;
    u16 frag_off;
    u8 ttl;
    u8 protocol;
    u16 check;
    u32 saddr;
    u32 daddr;
};
char hello[]="hello this is  a ping test.";
char *hostname; //被ping的主机
int  datalen=DEFAULT_LEN;//ICMP消息携带的数据长度
char sendbuf[BUFSIZE];
char recvbuf[BUFSIZE];
int nsent;//发送的ICMP消息序号
int nrecv;
pid_t pid;//ping程序的进程pid
struct timeval recvtime; //收到ICMP应答的时间戳
int sockfd; //发送和接收原始套接字
struct sockaddr_in dest;//被ping主机的ip
struct sockaddr_in from;//发送ping应答消息的主机ip

struct sigaction act_alarm;
struct sigaction act_int;

//设置的时间是一个结构体,倒计时设置,重复倒时,超时值设为1秒
struct itimerval val_alarm;

void alarm_handler(int);//SIGALRM处理程序
void int_handler(int);//SIGINT处理程序
void set_sighandler();//设置信号处理程序
void send_ping();//发送ping消息
void recv_reply();//接收ping应答
u16 checksum(u8 *buf,int len);//计算校验和
int handle_pkt();//ICMP应答消息处理
void get_statistics(int ,int);//统计ping命令的检测结果
void bail(const char *);//错误报告

void send_ping() {
    struct iphdr* ip_hdr;
    struct icmphdr* icmp_hdr;

    icmp_hdr = (struct icmphdr*)(sendbuf);
    icmp_hdr->type = ICMP_ECHO;
    icmp_hdr->code = 0;
    icmp_hdr->icmp_id = pid; //icmp_id = un.id,means process id
    icmp_hdr->icmp_seq = nsent++;
    gettimeofday((struct timeval*)icmp_hdr->icmp_timestamp,NULL);

    int len = ICMP_HSIZE + strlen(hello);
    icmp_hdr->checksum = 0;
    icmp_hdr->checksum = checksum((u8 *)icmp_hdr, len);

    sendto(sockfd, sendbuf, len, 0, (struct sockaddr*)&dest, sizeof(dest));
}

void recv_reply() {
    socklen_t len;
    int n = 0;
    nrecv = 0;
    int errno;
    len = sizeof(from);

    while (nrecv < 4) {
        if ((n = recvfrom(sockfd, recvbuf, sizeof(recvbuf), 0, (struct sockaddr*)&from, &len)) < 0) {
            if (errno == EINTR) {
                continue;
            }
            bail("recv from error");
        }
        gettimeofday(&recvtime, NULL);

        if (handle_pkt())
            continue;

        nrecv++;
    }
    get_statistics(nsent, nrecv);
}

u16 checksum(u8* buf, int len) {
    u32 sum=0;
    u16 *cbuf;

    cbuf=(u16 *)buf;

    while(len>1)
    {
        sum+=*cbuf++;
        len-=2;
    }

    if(len)
        sum+=*(u8 *)cbuf;

    sum=(sum>>16)+(sum & 0xffff);
    sum+=(sum>>16);

    return ~sum;

}

//handle ip response packet
int handle_pkt() {
    struct iphdr* ip;

    ip = (struct iphdr*)recvbuf;

    int ip_hlen = ip->hlen << 2;
    u16 ip_datalen = ntohs(ip->tot_len) - ip_hlen; 

    struct icmphdr* icmp;
    icmp = (struct icmphdr*)(recvbuf + ip_hlen);

    u16 sum = (u16)checksum((u8*)icmp, ip_datalen);

    if (sum) {
        printf("check is not equal to 0\n");
        return -1;
    }

    if (icmp->type != ICMP_ECHOREPLY) {
        return -1;
    }
    if (icmp->icmp_id != pid) {
        return -1;
    }

    struct timeval* sendtime = (struct timeval*)icmp->icmp_timestamp;
    double rtt;//round-trip time
    rtt = ((&recvtime)->tv_sec - sendtime->tv_sec) * 1000 + ((&recvtime)->tv_usec - sendtime->tv_usec) / 1000.0;

    printf("%dbytes from %s:icmp_seq = %u, ttl = %d, rrt = %.3f ms\n",ip_datalen, inet_ntoa(from.sin_addr), nsent, ip->ttl, rtt);

    return 0;
}

//signal handler
void set_sighandler() {
    act_alarm.sa_handler=alarm_handler;
    printf("alarm handle signal: %d\n", SIGALRM);
    if(sigaction(SIGALRM,&act_alarm,NULL)==-1)  //sigaction()会依参数signum指定的信号编号来设置该信号的处理函数。参数signum指所要捕获信号或忽略的信号,&act代表新设置的信号共用体,NULL代表之前设置的信号处理结构体。这里判断对信号的处理是否成功。
        bail("SIGALRM handler setting fails.");

    act_int.sa_handler=int_handler;
    printf("int handler handle signal: %d\n", SIGALRM);
    if(sigaction(SIGINT,&act_int,NULL)==-1)
        bail("SIGALRM handler setting fails.");

}

void get_statistics(int nsent, int nrecv) {
    printf("---%s ping statistics-----\n",inet_ntoa(dest.sin_addr));
    printf("%d packets transmitted,%d received, %0.0f%% loss\n",nsent, nrecv, 1.0 * (nsent - nrecv) / nsent * 100);
}

//error report
void bail(const char* incident) {
    fputs(strerror(errno), stderr);
    fputs(":", stderr);
    fputs(incident, stderr);
    fputc(‘\n‘, stderr);
    exit(1);
}

//interupt signal process program
void int_handler(int sig) {
    get_statistics(nsent, nrecv);
    close(sockfd);
    exit(1);
}

void alarm_handler(int signo) {
    send_ping();
}

int main(int argc, char** argv) {
    val_alarm.it_interval.tv_sec = 1;
    val_alarm.it_interval.tv_usec = 0;
    val_alarm.it_value.tv_sec = 0;
    val_alarm.it_value.tv_usec = 1;
    struct hostent* host; //#include <netdb.h>
    int on = 1;

    if ((host = gethostbyname(argv[1])) == NULL) {
        perror("can not understand the host name");
        exit(1);
    }

    hostname = argv[1];
    memset(&dest, 0, sizeof(dest));
    dest.sin_family = PF_INET;
    dest.sin_port = ntohs(0);
    dest.sin_addr = *(struct in_addr *)host->h_addr_list[0];//h_addr_list is char** type

    setuid(getuid());//grant user root authority
    if ((sockfd = socket(PF_INET, SOCK_RAW, IPPROTO_ICMP)) < 0) {
        perror("raw socket created error");
        exit(1);
    }

    pid = getpid();
    printf("Pid: %d\n", pid);
    set_sighandler();
    printf("Ping %s(%s): %d bytes data in ICMP packets.\n", argv[1], inet_ntoa(dest.sin_addr), datalen);

    if ((setitimer(ITIMER_REAL, &val_alarm, NULL)) == -1)
        bail("setitimer falied");

    recv_reply();

    return 0;

}

原文地址:https://www.cnblogs.com/lunar-ubuntu/p/12589362.html

时间: 2024-10-05 03:09:46

Linux下实现ping程序的相关文章

两种在linux下创建应用程序快捷方式的方法

两种在linux下创建应用程序快捷方式的方法: A. 在桌面上创建快捷方式 B. 在应用程序菜单中添加快捷方式 在桌面上创建快捷方式 这是最简单的一种方法,在桌面上单击鼠标右键,会有一个“创建启动器”栏.这里我以为mplayer创建快捷方式为例说明: 名称-mplayer(或者你喜欢的任何名称,这个名称会出现在快捷图标的 下方) 命令-/usr/bin/gmplayer(这个是mplayer的gui应用程序的执行文件,跟 安装路径相关,可以通过which gmplayer找到) 图标-一般应用程

Linux下C/C++程序调试基础(GCC,G++,GDB,CGDB,DDD)

在写程序的时候,经常会遇到一些问题,比如某些变量计算结果不是我们预期的那样,这时我们需要对程序进行调试.本文主要介绍调试C/C++在Linux操作系统下主要的调试工具. 在Linux下写程序,C/C++主要的编译器有GCC/G++,ICC等,像我等穷码农,最喜欢GCC了,很大原因是他免费!所以,我们以GCC/G++为例介绍主要的调试工具. 分以下几个内容介绍: 1.调试之前的工作 2.选择调试工具 3.调试步骤 点我,请帮我投一票! 调试之前的工作 编译器在编译阶段需要产生可供调试的代码,才能被

Linux下禁止ping最简单的方法

LINUX下禁止ping命令的使用 以root进入Linux系统,然后编辑文件icmp_echo_ignore_allvi /proc/sys/net/ipv4/icmp_echo_ignore_all将其值改为1后为禁止PING将其值改为0后为解除禁止PING 直接修改会提示错误: WARNING: The file has been changed since reading it!!!Do you really want to write to it (y/n)?y"icmp_echo_i

【linux】linux下能ping通ip 但是不能ping通域名

经过一翻查找后解决了,原因和方法如下: [[email protected]~]# grep host /etc/nsswitch.conf#hosts: db files nisplus nis dnshosts:            files主机只找文件不走dns,将其该成hosts:      files   dns   即可! [linux]linux下能ping通ip 但是不能ping通域名

Linux下执行Java程序

在linux下编译java程序,执行javac编译生成class文件时,在centos7终端输入如,javac hello.java    会提示未找到指令,但用java -verison测试环境变量是没问题的 百度了好久,说的很复杂,重新再linux配置环境变量,输入 vi /etc/profile进入,添加以下代码: export JAVA_HOME=/usr/local/jdk1.8.0_144 export PATH=$JAVA_HOME/bin:$PATH export CLASSPA

如何使用加多宝(jdb)在linux下调试Java程序

毕业时写了一段时间的C,那时候调试使用gdb,后来转了java,当时就想java程序怎么调试,找了一下,果然,那就是jdk自带的jdb windows里是这样的 Linux下是这样的 一般我在linux下来调试Java程序 好,那么,问题来了,这玩意怎么用?有好几种玩法 第一种玩法:以经典的HelloWorld为例,先写一个Java程序,如下: 我们把编译好的类上传到linux下,如下所示 务必强调一下:类所在的包名的层次结构也要在linux下体现出来 下面我们来玩起来,在linux下操作如下

Linux下C/C++程序开发管理(makefile)

一.引言          从我们刚开始编写一个简单的C/C++ "Hello,World!",到将其编译.运行处结果—这部分工作IDE(集成开发环境)帮我们做了,包括语法错误检查,编译,调试,执行二进制程序.大部分时间我们只关注程序代码本身的编写,如何在Linux下对C/C++源代码的      进行有效管理,包括编译.链接.调试,make工具可以帮助我们完成这部分的工作. 二.从“Hello,World”说起 1.执行单个源文件  一个编写好的C或C++代码源程序需要通过编译.链接

【linux 禁止ping设置】Linux下禁止ping最简单的方法

ping是一个通信协议,是ip协议的一部分,tcp/ip 协议的一部分.利用它可以检查网络是否能够连通,用好它可以很好地帮助我们分析判定网络故障.应用格式为:Ping IP地址.但服务启用ping有时候会造成很多麻烦. 因此有时候根据需要(如防止攻击),Linux服务器管理员可限制服务器禁止其它用户Ping.同时又保证Linux服务器又可以Ping其它服务器. 下面介绍linux下最简单的禁ping方法 首先登陆服务器终端之间执行:echo 1 > /proc/sys/net/ipv4/icmp

Linux下Qt应用程序的发布(使用LDD命令查看所有依赖的库文件)

最近一直在学习Qt,用Qt写了一个程序,但是不知道怎么发布,网上说的都是在windows下怎么发布Qt应用程序,但是,在windows下Qt应用程序依赖的库文件与linux下的名字不同.于是,我就想到Linux下有没有这么一个命令,能够找到一个可执行文件运行时所依赖的库文件,百度一下,还真的有ldd命令. ldd的作用是打印可执行文件依赖的共享库文件,它是glibc的一部分: [email protected]:~# ldd --helpUsage: ldd [OPTION]... FILE..