基于LINUX实现ping发送与接收

news2024/9/9 7:45:25

作用

Linux ping 命令用于检测主机:执行 ping 会使用 ICMP 传输协议,发出要求回应的信息,若远端主机的网络功能没有问题,就会回应该信息,因而得知该主机运作正常。

基础使用

  1. #ping 192.168.1.1//ping 主机ip
  2. #ping -c 2 www.runoob.com//收到两次包后,自动退出
  3. #ping -i 3 -s 1024 -t 255 g.cn //-i 3 发送周期为 3秒 -s 设置发送包的大小 -t 设置TTL值为 255

说明:
因为本文是偏linux ping编程,对于ping的用法介绍的很简单,关于详细的ping的介绍与使用,还请自行补充学习。

正文开始

  1. 先注册一个信号(虽然对于本文没什么关系,但是想增加下信号量的练习):
#include <signal.h>
typedef void (*sighandler)(int); 
/*注册一个signal*/
void handler_func(int signumber)
{
     printf("[%s][%d]------ signumber:%d--------\n",__func__,__LINE__,signumber);
}
signal(SIGINT,handler_func);
  1. ICMP协议理解:要进行PING的开发,我们首先需要知道PING的实现是基于ICMP协议来开发的。要进行PING的开发,我们首先需要知道PING的实现是基于ICMP协议来开发的。

报文格式
在这里插入图片描述
图形格式
在这里插入图片描述
对比着看,ICMP报文的种类有两种,即ICMP差错报告报文和ICMP询问报文。PING程序使用的ICMP报文种类为ICMP询问报文。注意一下上面说到的ICMP报文格式中的“类型”字段,我们在组包的时候可以向该字段填写不同的值来标定该ICMP报文的类型。下面列出的是几种常用的ICMP报文类型:
在这里插入图片描述
每个字段的字节大小如下:
在这里插入图片描述
3. ICMP包的组装

 void combin_Internet_contrl_message_protocol(struct icmp* message,int seq,int lenth)
{
      int i = 0;
    message->icmp_type = ICMP_ECHO;
    message->icmp_code = 0;
    message->icmp_cksum = 0;
    message->icmp_seq = seq;
    message->icmp_id = pid & 0xffff;

    message->icmp_cksum = check_sum((unsigned short*)message, lenth);
    printf("-----icmp_type:%d-------\n",message->icmp_type);
    printf("-----icmp_code:%d-------\n",message->icmp_code);
    printf("-----check_sum:%#X-------\n",message->icmp_cksum);
    printf("-----icmp_seq:%d-------\n",message->icmp_seq);
    printf("-----icmp_id:%d-------\n",message->icmp_id);

    printf("-----icmp_data[1]:%d-------\n",message->icmp_data[1]);
    printf("-----icmp_data[2]:%d-------\n",message->icmp_data[2]);
    printf("-----icmp_data[3]:%d-------\n",message->icmp_data[3]);
    printf("-----icmp_data[4]:%d-------\n",message->icmp_data[4]);
    printf("-----icmp_data[63]:%d-------\n",message->icmp_data[63]);
}

icmp_cksum 必须先填写为0再执行校验和算法计算,否则ping时对方主机会因为校验和计算错误而丢弃请求包,导致ping的失败。
在代码中我们看到了校验和check_sum,算法的基本思路:

采用的都是将数据流视为16位整数流进行重复叠加计算。为了计算检验和,首先把检验和字段置为0。然后,对有效数据范围内中每个16位进行二进制反码求和,结果存在检验和字段中,如果数据长度为奇数则补一字节0。当收到数据后,同样对有效数据范围中每个16位数进行二进制反码的求和。由于接收方在计算过程中包含了发送方存在首部中的检验和,因此,如果首部在传输过程中没有发生任何差错,那么接收方计算的结果应该为全0或全1(具体看实现了,本质一样)如果结果不是全0或全1,那么表示数据错误。

具体算法如下:

int check_sum(unsigned short *buf, int sz)
{
    int nleft = sz;
    int sum = 0;
    unsigned short *w = buf;
    unsigned short ans = 0;

    while (nleft > 1) {
        sum += *w++;
        nleft -= 2;
    }

    if (nleft == 1) {
        *(unsigned char *) (&ans) = *(unsigned char *) w;
        sum += ans;
    }

    sum = (sum >> 16) + (sum & 0xFFFF);
    sum += (sum >> 16);
    ans = ~sum;
    
    return (ans);
}
  1. ICMP包的解包:知道怎么封装包,那解包就也不难了,注意的是,收到一个ICMP包,我们不要就认为这个包就是我们发出去的ICMP回送回答包,我们需要加一层代码来判断该ICMP报文的id和seq字段是否符合我们发送的ICMP报文的设置,来验证ICMP回复包的正确性。
  int analyse_Internet_contrl_message_protocol(char* buf,int len)
{
    int iphdr_len;
    struct ip* ip_hdr = (struct ip *)buf;
    iphdr_len = ip_hdr->ip_hl*4;
    struct icmp* icmp = (struct icmp*)(buf+iphdr_len);
    len-=iphdr_len;  //icmp包长度
    /*判断date长度是否为0,因为还有八字节的header*/
    if(len < 8)  
    {
        printf("---- data len is null\n----");
        return -1;
    }

    if(icmp->icmp_type !=  ICMP_ECHOREPLY)
    {
        printf("--------------回复的不是reply-----------\n");
        return -1;
    }
    /*判断包的序列号是否合理*/
    if((icmp->icmp_seq < 0))
    {
        printf("------icmp packet seq is out of range!----------\n");
        return -1;
    }

    /*判断该包是ICMP回送回答包且该包是我们发出去的*/
    if(icmp->icmp_id == (pid & 0xffff))
    {
          printf("-----------getpid:%d---------\n",pid);
    }
    else
    {
        return -1;
    }

    return 1;
}
  1. 说明,因为代码中添加了信号量,所以ctrl+c不可以终止程序运行,需要用ctrl+\来终止

整体代码

#include <netdb.h>
#include<netinet/in.h>
#include <netinet/ip.h>
#include <signal.h>
#include <sys/time.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <arpa/inet.h>
#include <netinet/ether.h>
#include <netpacket/packet.h>
#include <netdb.h>
#include <errno.h>
#include <netinet/ip_icmp.h>
#include <unistd.h>
#include <signal.h>

typedef void (*sighandler)(int); 

    int fd = 0;
    int pid;
    int size = 1024*128;
    struct protoent * ret;   
    struct sockaddr_in des;
    unsigned int inaddr; 
    struct icmp * send_buf;
    char recv_buf[512];
    unsigned int SEQ = 0 ;

int check_sum(unsigned short *buf, int sz)
{
    int nleft = sz;
    int sum = 0;
    unsigned short *w = buf;
    unsigned short ans = 0;

    while (nleft > 1) {
        sum += *w++;
        nleft -= 2;
    }

    if (nleft == 1) {
        *(unsigned char *) (&ans) = *(unsigned char *) w;
        sum += ans;
    }

    sum = (sum >> 16) + (sum & 0xFFFF);
    sum += (sum >> 16);
    ans = ~sum;
    
    return (ans);
}

void combin_Internet_contrl_message_protocol(struct icmp* message,int seq,int lenth)
{
      int i = 0;
    message->icmp_type = ICMP_ECHO;
    message->icmp_code = 0;
    message->icmp_cksum = 0;
    message->icmp_seq = seq;
    message->icmp_id = pid & 0xffff;

    message->icmp_cksum = check_sum((unsigned short*)message, lenth);
    printf("-----icmp_type:%d-------\n",message->icmp_type);
    printf("-----icmp_code:%d-------\n",message->icmp_code);
    printf("-----check_sum:%#X-------\n",message->icmp_cksum);
    printf("-----icmp_seq:%d-------\n",message->icmp_seq);
    printf("-----icmp_id:%d-------\n",message->icmp_id);

    printf("-----icmp_data[1]:%d-------\n",message->icmp_data[1]);
    printf("-----icmp_data[2]:%d-------\n",message->icmp_data[2]);
    printf("-----icmp_data[3]:%d-------\n",message->icmp_data[3]);
    printf("-----icmp_data[4]:%d-------\n",message->icmp_data[4]);
    printf("-----icmp_data[63]:%d-------\n",message->icmp_data[63]);
}

int analyse_Internet_contrl_message_protocol(char* buf,int len)
{
    int iphdr_len;
    struct ip* ip_hdr = (struct ip *)buf;
    iphdr_len = ip_hdr->ip_hl*4;
    struct icmp* icmp = (struct icmp*)(buf+iphdr_len);
    len-=iphdr_len;  //icmp包长度
    /*判断date长度是否为0,因为还有八字节的header*/
    if(len < 8)  
    {
        printf("---- data len is null\n----");
        return -1;
    }

    if(icmp->icmp_type !=  ICMP_ECHOREPLY)
    {
        printf("--------------回复的不是reply-----------\n");
        return -1;
    }
    /*判断包的序列号是否合理*/
    if((icmp->icmp_seq < 0))
    {
        printf("------icmp packet seq is out of range!----------\n");
        return -1;
    }

    /*判断该包是ICMP回送回答包且该包是我们发出去的*/
    if(icmp->icmp_id == (pid & 0xffff))
    {
          printf("-----------getpid:%d---------\n",pid);
    }
    else
    {
        return -1;
    }

    return 1;
}

void handler_func(int signumber)
{
     printf("[%s][%d]------ signumber:%d--------\n",__func__,__LINE__,signumber);
}

int main(void)
{ 
    struct timeval tv;
    tv.tv_usec = 0;  /*设置select函数的超时时间为200us*/
    tv.tv_sec = 3;
    struct protoent *proto;
    int rev_size1 = 0 ;
    int ret;
    fd_set read_fd;
     
    /*注册一个signal*/
    signal(SIGINT,handler_func);
    proto = getprotobyname("icmp"); 
    int cnt= 0 ;
    if(proto == NULL)
    {
        printf("[%s][%d]-----------getprotobyname get is fail----\n",__func__,__LINE__);
        return;
    }
    printf("-------------proto->p_proto:%d------\n",proto->p_proto);

    /*创建socket */
    fd = socket(AF_INET,SOCK_RAW,proto->p_proto);
    if(fd < 0)
    {
        printf("[%s][%d]-----------fd is:%d,socket create is fail----\n",__func__,__LINE__,fd);
        int eno = errno;
        if(eno == 1)
        {
            /*必须使用sudo才能建立Raw socket*/
            printf("Operation not permitted!\n");
        }
        else
        {
            /*除了权限之外的错误打印*/
            printf("Cannot create socket! Error %d", eno);
        }
        return;
    }

    /*增大接收缓冲区至128K*/
    printf("-----------setsockopt---------\n");
    setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size)); 

    /*清空网络结构体*/
    bzero(&des,sizeof(des));

    /*网络结构体赋值ipv4*/
    des.sin_family = AF_INET;

    /*十进制的数转化为二进制的数,将字符串形式的IP地址转换为按网络字节顺序的整型值*/
    inaddr = inet_addr("192.168.3.1");
    printf("-----------------inet_addr结束------------\n");

    /*将需要ping的ip地址赋值到des网络结构体*/
    memcpy((char*)&des.sin_addr, &inaddr, sizeof(inaddr));
    
    /*设置监听socket*/
    FD_ZERO(&read_fd);
    FD_SET(fd, &read_fd);

    /*获取当前pid*/
    pid = getpid()&0xffff;
    printf("-----------getpid:%d---------\n",pid);
    
    /*申请内存,并赋空*/
    send_buf = malloc(64);
    if(send_buf == NULL)
    {
        free(send_buf);
        goto fail;
    }
    bzero(send_buf,sizeof(send_buf));

    while(1)
    {
    /*组包*/
    combin_Internet_contrl_message_protocol(send_buf,cnt,64);
    printf("[%s][%d]-----------------组包结束------------\n",__func__,__LINE__);

    /*sendto 函数*/
    /*或者使用ret = sendto(fd, send_buf, 64, 0, (struct sockaddr*)&des, sizeof(struct sockaddr_in));*/
    ret = sendto(fd,send_buf,64,0,(struct sockaddr *)&des,sizeof(des));
    if(ret < 0 )
    {
        printf("------------send fail ---------\n");

    }
    /*发包数++*/
    cnt++;
    /*睡眠1s*/
    sleep(1);
    
    /*接受buff赋空*/
    memset(recv_buf, 0 ,sizeof(recv_buf));
    
    /*监视socket*/
    printf("---------------正常进入----------------\n");
    ret = select(fd+1, &read_fd, NULL, NULL, &tv);
    
    switch(ret)
    {
        case 0://超时
            printf("------------超时-----------\n");
        break; 
        case -1://error
            printf("------------错误-----------\n");
        break; 
        default:
        
                    rev_size1 = recv(fd, recv_buf, sizeof(recv_buf), 0);
                    if(rev_size1 < 0)
                    {
                        printf("----------recv data fail!------------\n");
                        continue;//中止本次循环,重新判断循环条件,开始下一次循环
                    }
                    /*对接收的包进行解封*/
                    ret = analyse_Internet_contrl_message_protocol(recv_buf, size); 
                    /*不是属于自己的icmp包,丢弃不处理*/
                    if(ret == -1)  
                    {
                        continue;
                    }
                    printf("--------------ping success ----------\n");
    break;
    }
    }
    fail:
    close(fd);
    return 0;
}

编译与运行

代码可以直接gcc进行编译:

 gcc ping.c -o ping.o

执行:因为存在权限问题,所以要root执行

sudo ./ping.o

运行结果
在这里插入图片描述
抓包截图:成功抓取发送与接收
在这里插入图片描述

欢迎大家一起探讨交流,不吝指教hh

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/433073.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【项目】视频列表滑动,自动播放

自动播放 期望效果&#xff0c;当滑动列表结束后&#xff0c;屏幕中间的视频自动播放HTML页面data变量实践操作&#xff01;重点来了&#xff01;滚动获得的数据实现效果源码&#xff08;粘贴即可运行&#xff09; 期望效果&#xff0c;当滑动列表结束后&#xff0c;屏幕中间的…

C. Anna, Svyatoslav and Maps(floyd + 思维)

Problem - C - Codeforces 给你一个有n个顶点的无权图&#xff0c;以及由m个顶点的序列p1,p2,...,pm给出的路径&#xff08;该路径不一定简单&#xff09;&#xff1b;对于每个1≤i<m&#xff0c;有一个弧从pi到pi1。 如果v是p的子序列&#xff0c;v1p1&#xff0c;vkpm&a…

重学Java设计模式-行为型模式-命令模式

重学Java设计模式-行为型模式-命令模式 内容摘自&#xff1a;https://bugstack.cn/md/develop/design-pattern/2020-06-21-重学 Java 设计模式《实战命令模式》.html#重学-java-设计模式-实战命令模式「模拟高档餐厅八大菜系-小二点单厨师烹饪场景」 命令模式介绍 图片来自&a…

后端查询到数据,前端显示该数据为null

问题展示&#xff1a; 数据库可视化界面。我们要展示record属性里面的值。 前端form表单&#xff1a; 后端属性&#xff1a; 后端sql语句&#xff1a; 接下来我们查询订单详情&#xff0c;ID8的订单。 后端控制台&#xff1a; 我们明显的看到&#xff0c;record这个属…

CSS选择器进阶1.2

一&#xff0c;复合选择器 1.1后代选择器&#xff1a;Space 作用&#xff1a;根据HTML标签的嵌套关系&#xff0c;选择父元素后代中满足条件的元素。 选择器语法&#xff08;选择器1为父选择器&#xff0c;选择器2为后代选择器&#xff09;&#xff1a; 选择器1 选择器2{CSS…

【HTML5】HTML5 语义化标签 ( HTML5 简介 | 新增特性 | 语义化标签及代码示例 )

文章目录 一、HTML5 简介二、HTML5 语义化标签三、HTML5 语义化标签代码示例 一、HTML5 简介 HTML5 指的是 对 HTML 语言的第五次重大修改 , 新增了新的元素 / 属性 / 行为 ; HTML5 新增的特性 : 语义特性本地存储特性设备兼容特性连接特性网页多媒体特性三维特性图形及特效特…

故障重现, JAVA进程内存不够时突然挂掉模拟

背景&#xff0c;服务器上的一个JAVA服务进程突然挂掉&#xff0c;查看产生了崩溃日志&#xff0c;如下&#xff1a; # Set larger code cache with -XX:ReservedCodeCacheSize # This output file may be truncated or incomplete. # # Out of Memory Error (os_linux.cpp:26…

什么是跳表?

文章目录文章目的注意事项1.什么是跳表-skiplist2.skiplist的效率如何保证&#xff1f;2.1 一个节点的平均层数3. skiplist的实现文章目的 让你知道什么是跳表,梳理跳表跳表的设计思路及实现 注意事项 下面有数学公式,需要数学功底,只要弄清楚用来干嘛就行有兴趣的人可以了解…

FVCOM模型数值模拟流域、海洋水动力、水环境,解决水交换及污染物扩散问题、溢油及物质输运问题

目录 FVCOM流域、海洋水环境数值模拟方法及实践技术应用 第一章、FVCOM水动力相关理论 第二章、Linux系统下FVCOM运行环境搭建 第三章、FVCOM三维水动力数值模拟前处理 第四章、FVCOM三维水动力数值模拟 第五章、FVCOM三维水动力计算结果可视化及率定方法 第六章、FVCOM…

算法的空间复杂度

空间复杂度也是一个数学表达式&#xff0c;是对一个算法在运行过程中临时占用存储空间大小的量度 空间复杂度不是程序占用了多少bytes的空间&#xff0c;因为这个也没太大意义 所以空间复杂度算的是变量的个数 空间复杂度计算规则基本跟实践复杂度类似&#xff0c;也使用大O渐进…

Ajax详解

1、什么是Ajax ajax 全名 async javascript and XML是前后台交互的能力也就是我们客户端给服务端发送消息的工具&#xff0c;以及接受响应的工具是一个 默认异步 执行机制的功能。 2、 AJAX 的优势 不需要插件的支持&#xff0c;原生 js 就可以使用用户体验好&#xff08;不…

学生成绩管理系统的设计与实现

一、系统需求分析 实现对一个有32个学生的班级&#xff0c;每个学生有7门课程&#xff0c;实现对他们的班级成绩进行添加、修改、删除、查找、统计输出等基本信息进行一系列的操作。每个学生包括如下信息&#xff1a;学号、姓名、7门课程名称。 二、系统功能模块设计 主要包含…

zookeeper 搭建 linux

jdk安装 1.从网盘里下载jkd 2.创建安装目录&#xff0c;然后将jdk包解压到目录中 mkdir jdktar -zxvf jdk-8u271-linux-x64.tar.gz -C /home/ubuntu/app/jdk/ 3.设置环境变量 修改 vi /etc/profile, 在 profile 文件中添加如下内容并保存&#xff1a; set java environment JAV…

OpenAI-ChatGPT最新官方接口《AI绘图》全网最详细中英文实用指南和教程,助你零基础快速轻松掌握全新技术(三)(附源码)

ChatGPT-AI绘图 Image generation Beta 图片生成前言IntroductionUsageGenerationsEdits 编辑 VariationsLanguage-specific tips 特定语言提示Python 语言Using in-memory image data 使用内存中的图像数据Operating on image data 操作图像数据Error handling Node.js 语言Us…

手把手教你使用Python调用 ChatGPT!支持http代理

手把手教你使用Python调用 ChatGPT&#xff01;支持http代理 作者&#xff1a;虚坏叔叔 博客&#xff1a;https://xuhss.com 早餐店不会开到晚上&#xff0c;想吃的人早就来了&#xff01;&#x1f604; 前段时间OpenAI 开放了两个新模型的api接口&#xff0c;专门为聊天而生的…

《JavaEE初阶》多线程基础

《JavaEE初阶》多线程基础 文章目录《JavaEE初阶》多线程基础前言:多线程的概念简单创建线程并运行:简述Thread中run方法与start方法的区别创建线程的几种方法:探讨串行执行与并行执行的执行时间多线程的使用场景:Thread类简单介绍:构造方法:获取线程的常见属性:线程的常用方法…

Nacos 客户端服务发现源码分析-篇六

Nacos 客户端服务发现源码分析-篇六 &#x1f550;Nacos 客户端服务注册源码分析-篇一 &#x1f551;Nacos 客户端服务注册源码分析-篇二 &#x1f552;Nacos 客户端服务注册源码分析-篇三 &#x1f553;Nacos 服务端服务注册源码分析-篇四 &#x1f554;Nacos 服务端健康…

ChatGPT神器免费使用,告别昂贵低效工具

大家好&#xff0c;今天我要向大家介绍一款免费的ChatGPT使用网址&#xff0c;它可以让你轻松地使用ChatGPT进行AI创作&#xff01;而且&#xff0c;这个网址还是免费的&#xff0c;不需要担心会有额外的费用。 ChatGPT是一种非常强大的AI技术&#xff0c;可以用于各种领域&…

《PyTorch 深度学习实践》第11讲 卷积神经网络(高级篇)

文章目录 1 Inception Module1.1 11卷积1.2 Inception模块结构1.3 完整代码 2 残差网络(Residual Network) 该专栏内容为对该视频的学习记录&#xff1a;【《PyTorch深度学习实践》完结合集】 专栏的全部代码、数据集和课件全放在个人GitHub了&#xff0c;欢迎自取。 1 Incept…

数据库实验 | 第3关:建立和调用存储函数

任务描述 本关任务&#xff1a; 销售数据库有顾客、销售单数据表 顾客gk数据表有会员号hyh、姓名name、性别sex、电话tel、部门dept字段 销售单xsd数据表有销售单号xsdh、会员号hyh、雇员号gyh、销售日期xsrq、应付款yfk、实际付款sjfk字段 任务要求 建立存储过程 gkjb(nf …