当前位置:   article > 正文

基于python的ping命令分析与实现_pythonping

pythonping

摘要

在网络日益发达的今天,网络变得越来越重要,而ping命令是我们使用的比较多的用于测试网络连通性的命令,ping用于确定本地主机是否能与另一台主机成功交换(发送与接收)数据包,再根据返回的信息,就可以推断TCP/IP参数是否设置正确,以及运行是否正常、网络是否通畅等。因此,用好ping命令就要先理解ping命令的含义,掌握网络协议的基本实现方法。

关键词:icmp,python,ping,wireshark 流量分析,网络编程

1 ping命令原理分析

1.1 ping命令结果

系统环境:Windows10

DOS的ping命令结果:

在这里插入图片描述

图1.ping命令结果

1.2 ping输出分析

输出一:

将ping目标主机进行DNS解析,获取对应的ip地址,并向该主机发送32字节大小的数据包。

输出二:

来自目标主机的响应信息:

  • 字节:响应的数据包大小是32字节。
  • 时间:请求往返耗时。
  • TTL:IP数据报的ttl设置。

输出三:

ping响应概览:

  • 一共发送了4个ping请求,收到4个ping响应,丢包率是0%。
  • 最短、最长和平均的往返时间:往返时间。

1.3 wireshark 抓包分析

源地址:10.77.170.136

目标地址:14.215.177.38 (www.baidu.com)

在wireshark 过滤器中设置好筛选条件,发现抓取到了4组请求与响应。

在这里插入图片描述

图2.wireshark抓取的请求与响应

发现ping命令主要基于ICMP(Internet Control Message Protocol)Internet控制报文协议实现,它包含了两部分:ping命令发送一个ICMP回显请求报文给目标主机,目标主机接收到之后, 会返回一个ICMP回显响应报文。

查看第一个ICMP回显请求报文,Type(类型)为8,Code(代码)为0,Identifier(标识符)为发送进程的id,Data(数据包)为32字节数据。

在这里插入图片描述

图3.ICMP回显请求报文

查看第一个ICMP回显响应报文,Type(类型)为0,Code(代码)为0,Identifier(标识符)与第一个ICMP回显请求报文一致。Response time(响应时间)为5.893ms。

在这里插入图片描述

图4.ICMP回显响应报文

1.4 ICMP协议分析

ICMP协议不是高层协议,而是IP层的协议,因为ICMP报文装在IP数据报中,作为其中的数据部分。ICMP报文作为IP层数据包的数据,加上数据报的首部,组成IP数据报发送出去。

ICMP报文格式:

在这里插入图片描述

图5.ICMP数据包
  • 类型:1个字节。8表示回显请求报文,0表示回显响应报文。
  • 代码:1个字节。回显请求报文、回显响应报文 时均为0。
  • 校验和:2个字节。
  • 标识符:2个字节。发送ICMP报文的客户端进程的id,服务端会回传给客户端。因为同一个客户端可能同时运行多个ping程序,这样客户端收到回西显报文,可以知道是响应给哪个客户端进程的。
  • 序列号:2个字节。从0开始,客户端每次发送新的回显请求时+1。服务端原样会传。

1.5 ping命令流程图

在这里插入图片描述

图6.ping命令流程图

2 ping命令实现

2.1 实现准备

2.1.1 实验环境

操作系统:Windows10

开发环境:python,pycharm

开发所用库:socket、select、struct、os、time

2.1.2 实现思路

根据ping命令流程,编写5个模块:

  • 连接connect模块:使用socket发送ICMP数据报。
  • 请求request模块:构造ICMP数据报,并使用struct将其打包成字节流数据。
  • 响应reply模块:接收响应数据报,使用select接收并监控通信,提取报文中的内容。
  • 校验和checksum模块:对ICMP数据报进行二进制反码求和。
  • ping模块:将请求模块与响应模块组合,并输出对应的信息。

2.2 连接connect函数

使用socket模块来创建套接字,套接字类型选择基于网络的原始套接字,普通的套接字无法处理ICMP、IGMP等网络报文,而SOCK_RAW可以。

def connect(addr, icmp_packet):
    # 实例化一个socket对象,ipv4,原始套接字,分配协议端口
    rawsocket = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.getprotobyname("icmp"))
    # 发送数据到网络
    rawsocket.sendto(icmp_packet, (addr, 800))
    # 返回数据
    return rawsocket, addr
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

2.3 请求request函数

根据ICMP数据报的特点,在本地构造对应的数据包,并使用struct模块将其打包成计算机可识别的二进制数据流。struct模块类似于C语言中的结构体。

def request(seq):
    type = 8
    code = 0
    check_sum = 0
    ID = os.getpid() & 0xffff
    #data = time.time()
    data = b'abcdefghijklmnopqrstuvwabcdefghi'
    #  把字节打包成二进制数据,其中>(!)代表是是大端模式,B代表的是一个字节的无符号整数,H代表的是两个字节的无符号整数,d代表的是double
    icmp_packet = struct.pack('>BBHHH32s', type, code, check_sum, ID, seq, data)
    check_sum = checksum(icmp_packet)  # 获取校验和
    #  把校验和传入,再次打包
    icmp_packet = struct.pack('>BBHHH32s', type, code, check_sum, ID, seq, data)
    return icmp_packet
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

2.4 响应reply函数

使用select模块监听rawsocket套接字,并接收来自rawsocket的数据报。使用struct模块进行解包,根据IP数据报和ICMP数据报的结构,截取所需要的内容。

def reply(rawsocket, ping_time, timeout=1):
    while True:
        # 开始时间
        started_time = time.time()
        # 实例化select对象,可读rawsocket,可写为空,可执行为空,超时时间
        recv = select.select([rawsocket], [], [], timeout)
        # 等待时间
        wait_for_time = (time.time() - started_time)
        # 如果没有返回可写内容判断为超时
        if recv[0] == []:
            return -1, -1
        # 记录接收时间
        received_time = time.time()
        # 设置接收的包的字节为1024字节
        received_packet, addr = rawsocket.recvfrom(1024)
        # 判断是否超时
        timeout = timeout - wait_for_time
        if timeout <= 0:
            return -1, -1, -1
        #获取ip报文的TTL
        ttl = received_packet[8]
        # 获取icmp报文的头部
        icmpHeader = received_packet[20:28]
        #print(received_packet)
        type, code, checksum, packet_id, sequence = struct.unpack(">BBHHH", icmpHeader)
        if type == 0:
            return received_time - ping_time, sequence, ttl
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

2.5 校验和checksum函数

校验和的计算主要有如下步骤:

  1. 把校验和字段置为0
  2. 把需校验的数据看成以16位为单位的数字组成,将每两个字节(16位)相加(二进制求和)
  3. 将高16位与低16位相加,直到高16位为0为止。
  4. 返回二进制取反后的结果
def checksum(data):
    n = len(data)
    m = n % 2
    sum = 0
    for i in range(0, n - m, 2):
        # 传入data以每两个字节(十六进制),第一字节在低位,第二个字节在高位
        sum += (data[i]) + ((data[i + 1]) << 8)
    if m:
        sum += (data[-1])
    # 将高于16位与低16位相加
    while sum >> 16:
        sum = (sum >> 16) + (sum & 0xffff)
    # 如果还有高于16位,将继续与低16位相加
    answer = ~sum & 0xffff
    #  主机字节序转网络字节序列
    answer = answer >> 8 | (answer << 8 & 0xff00)
    return answer
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

2.6 ping函数

通过socket模块中的gethostbyname函数获取域名的ip地址,利用请求request函数返回icmp_packet数据包,并使用连接connect函数连接目标主机,同时利用响应reply函数接收ICMP回显响应报文,最后输出相关信息与统计信息。

def ping(host, count = 4, timeout = 2):
    addr = socket.gethostbyname(host)  # 获取ip地址
    print("正在 Ping {0} [{1}] 具有 32 字节的数据:".format(host, addr))
    lost = 0
    accept = 0
    sumtime = 0.0
    times = []  # 统计所有包的往返时间长度
    for i in range(count):
        i += 1
        icmp_packet = request(i)
        # print(icmp_packet)
        rawsocket, dst_addr = connect(addr, icmp_packet)
        time0, sequence, ttl = reply(rawsocket, time.time(), timeout)
        if time0 < 0:
            print("请求超时")
            lost += 1
            times.append(timeout * 1000)
        else:
            time0 = time0 * 1000
            print("来自 {0} 的回复: 字节=32 seq = {1} 时间={2:.2f}ms TTL={3}".format(dst_addr, sequence, time0, ttl))
            accept += 1
            sumtime += time0
            times.append(time0)
    # 统计
    print('{0} 的 Ping 统计信息:'.format(addr))
    print('\t数据包: 已发送 = {0},已接收 = {1},丢失 = {2} ({3}% 丢失),'
          .format(count, accept, lost, lost // (lost + accept) * 100,))
    print('往返行程的估计时间(以毫秒为单位):')
    print('\t最短 = {0:.2f}ms,最长 = {1:.2f}ms,平均 = {2:.2f}ms'
          .format(min(times),max(times), sum(times) // (lost + accept)))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

2.7 测试结果

如图,与预期结果相差不大,成功实现ping命令程序。
在这里插入图片描述

图7.实现的ping命令

在这里插入图片描述

图8.wireshark抓包结果

3 结束语

随着网络技术的不断进步,通过网络通信协议实现的功能越来越多,ping命令只是其中的一个简单的应用,通过socket的raw_socket类型,还可以自定义数据报进行更底层应用的开发。

此外,可以编写ping程序来进行dos攻击或ddos攻击。例如本文中的程序,将ping 的目标设置为拟进行攻击目标,使用循环来实现不间断地发送数据包,即完成了dos攻击。若使用了大量的肉鸡服完成攻击的话,这便是ddos攻击,破坏力极大。

参考文献

[1] 谢希仁. 《计算机网络》. 第7版. 电子工业出版社. 2017年出版

[2]Ping程序设计 : https://blog.csdn.net/zha_ojunchen/article/details/106225905

[3]python socket编程详细介绍 : https://blog.csdn.net/rebelqsp/article/details/22109925

[4]python之struct详解 : https://blog.csdn.net/qq_30638831/article/details/80421019

[5]Ping 本质( ICMP ) : https://www.jianshu.com/p/e1795962ad76

[6]IP、ICMP、UDP、TCP 校验和算法 : https://blog.csdn.net/weiweiliulu/article/details/20531367

[7]select — Waiting for I/O completion : https://docs.python.org/3/library/select.html#select.select

[8]IP Protocol Header Fundamentals Explained with Diagrams : https://www.thegeekstuff.com/2012/03/ip-protocol-header/

[9] How to Calculate IP Header Checksum : https://www.thegeekstuff.com/2012/05/ip-header-checksum/

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/盐析白兔/article/detail/620398
推荐阅读
相关标签
  

闽ICP备14008679号