解析ping原理及实现

PING

一、概述

ping属于一个通信协议,是TCP/IP协议的一部分。利用“ping”命令可以检查网络是否通畅或者网络连接速度,很好地分析和判定网络故障。

Ping发送一个ICMP(Internet Control Messages Protocol),即因特网信报控制协议;接收端回声消息给目的地并报告是否收到所希望的ICMPecho (ICMP回声应答)。它的原理是:利用网络上机器IP地址的唯一性,给目标IP地址发送一个数据包,通过对方回复的数据包来确定两台网络机器是否连接相通,时延是多少。

ping是端对端连通,通常用来作为可用性的检查,但是某些病毒木马会强行远程执行大量ping命令抢占你的网络资源,导致系统变慢,网速变慢。严禁ping入侵作为大多数防火墙的一个基本功能提供给用户进行选择。通常的情况下你如果不用作服务器或者进行网络测试,可以放心的选中它,保护你的电脑。


二、ICMP概述

定位

ping属于哪一层?ping命令使用的tcp报文还是udp报文呢?

首先,答案是:PING是应用层直接使用网络层ICMP的一个例子,它没有通过运输层的TCP或UDP。

PING命令是我们能够直接使用的命令,像HTTP、FTP,属于应用层。ping命令底层使用的是ICMP,ICMP报文封装在ip包里,所以ICMP属于网络层协议。

分层协议
补充IGMP(是G)

IGMP即Internet工作组管理协议(Internet Group Management Protocol),IGMP主要用来解决网络上广播时占用带宽的问题。

当网络上的信息要传输给所有工作站时,就发出广播(broadcast)信息(即IP地址主机标识位全为1),交换机会将广播信息不经过滤地发给所有工作站;但当这些信息只需传输给某一部分工作站时,通常采用组播(multicast,也称多点广播)的方式,这就要求交换机支持IGMP。支持IGMP的交换机会识别组播信息并将其转发至相应的组,从而使不需要这些信息的工作站的网络带宽不被浪费。IGMP对于提高多媒体传输时的网络性能尤为重要。

IP和ICMP报文的关系

报文内的具体字段可以暂时抛开,可以看到ICMP报文是内嵌在IP报文内的,也就是说IP报文里包含着ICMP报文,ICMP报文是IP报文的一部分。

IP和ICMP报文

三、ICMP实现

由于PING命令是使用ICMP实现的,那么ICMP是如何实现的对了解PING是至关重要的。

1. ICMP报文类型

ICMP类型分为两大类:

  1. 差错报告
  2. 询问报告

ICMP通过一个整数数字来表示不同的报文类型,双方通过该类型值来识别报文的目的,并作出不同的反应。

种类 类型值 ICMP报文的类型
差错报告 3 终点不可达
差错报告 11 时间超过
差错报告 12 参数问题
差错报告 5 改变路由
询问报告 8/0 回送请求/回答
询问报告 13/14 时间戳请求/回答

对于PING命令,我们需要的到达的效果是检查是否联通。那么只需要我们的请求方带上数字标识8(回送请求),如果对方回送的数值是0,那么证明两者是联通的。

2. Checksum 检验和

校验和是一个从数据包计算出来的值来检查其完整性。通过完整性,我们可以检查收到的数据是否没有错误。由于最底层的网络通信是通过电路传输的,期间包含很多不定因素,可能导致部分数据丢失、改变。所以网络协议大都需要通过某种方法来确保收到的数据是正确的。检验和是常用的一种方式。

ICMP协议中使用了检验和来保证接收到正确的数据。在源端,计算校验和并将其作为字段设置在报文中。在目标端,再次计算校验和,并用报文中现有的校验和值进行交叉检查,看看数据包是否正常。

如何计算

icmp检验和的计算与IP检验和类似,不同的是,icmp需要通过其报文信息与数据主体一起校验,IP只需校验头部信息。

因为通常在IP报头之后的数据(如ICMP,TCP等)具有自己的校验和

就算法而言,imcp校验和是:imcp报文中所有16位字的补码总和的16位补码。具体操作如:

  1. 将校验和字段置为0。
  2. 将每两个字节(16位)相加(二进制求和)直到最后得出结果,若出现最后还剩一个字节继续与前面结果相加。
  3. (溢出)将高16位与低16位相加,直到高16位为0为止。
  4. 将最后的结果(二进制)取反。

关于二进制求和:

# 例:
# 1. 不溢出时

4500  - > 0100 0101 0000 0000
003c  - > 0000 0000 0011 1100

453C  - > 0100 0101 0011 1100  # 结果

# 2. 溢出时 高16位与低16位相加
E188  - > 1110 0001 1000 1000 
AC10  - > 1010 1100 0001 0000

18D98->1 1000 1101 1001 1000
8D99  - >  1000 1101 1001 1001   # 结果


# 最后结果取反
0A0C  - > 0000101000001100  # 最后一次累加
4E19  - > 0100111000011001  # 取反得最终结果
代码实现
  # 检验和
    def chesksum(data):
        n = len(data)
        m = n % 2
        sum = 0
        for i in range(0, n - m, 2):
            # 传入data以每两个字节(十六进制)通过ord转十进制,第一字节在低位,第二个字节在高位
            # ?????为什么第二个字节在高位
            sum += (data[i]) + ((data[i+1]) << 8)
            sum = (sum >> 16) + (sum & 0xffff)
        if m:
            sum += (data[-1])
            sum = (sum >> 16) + (sum & 0xffff)
    
        # 取反
        answer = ~sum & 0xffff
        #  主机字节序转网络字节序列
        answer = answer >> 8 | (answer << 8 & 0xff00)
        return answer
补充:小/大端序

不同CPU中,4字节整数在内存空间的存储方式是不同的。若不考虑这些就收发数据会发生问题,因为保存顺序的不同意味着对接收数据的解析顺序也不同。一般分为两种:

  • 大端序(Big Endian):高位字节存放到低位地址(高位字节在前)。
  • 小端序(Little Endian):高位字节存放到高位地址(低位字节在前)。
保存4字节 int 型数据 0x12345678

大端序

image

小端序

image

不同CPU保存和解析数据的方式不同(主流的Intel系列CPU为小端序),小端序系统和大端序系统通信时会发生数据解析错误。因此在发送数据前,要将数据转换为统一的格式——网络字节序(Network Byte Order)。网络字节序统一为大端序。

四、实现PING功能

简单流程

  1. 执行 ping 192.168.0.5
Ping命令会构建一个固定格式的ICMP请求数据包,
然后由ICMP协议将这个数据包连同地址“192.168.0.5”一起交给IP层协议
  1. IP层相关操作
IP层协议将以地址“192.168.0.5”作为目的地址,本机IP地址作为源地址,
加上一些其他的控制信息,构建一个IP数据包发往192.168.0.5。
  1. 目的主机相关操作
接收后检查该数据帧,将IP数据包从帧中提取出来,交给本机的IP层协议。
IP层检查后,将有用的信息提取后交给ICMP协议
ICMP协议后者处理后,马上构建一个ICMP应答包,发送给主机A
代码实现
# encoding:utf-8
import time
import struct
import socket
import select


# 检验和
def chesksum(data):
    n = len(data)
    m = n % 2
    sum = 0
    for i in range(0, n - m, 2):
        # 传入data以每两个字节(十六进制)通过ord转十进制,第一字节在低位,第二个字节在高位
        sum += (data[i]) + ((data[i+1]) << 8)
        sum = (sum >> 16) + (sum & 0xffff)
    if m:
        sum += (data[-1])
        sum = (sum >> 16) + (sum & 0xffff)

    # 取反
    answer = ~sum & 0xffff
    #  主机字节序转网络字节序列
    answer = answer >> 8 | (answer << 8 & 0xff00)
    return answer


def request_ping(data_type, data_code, data_checksum, data_ID, data_Sequence, payload_body):
    #  把字节打包成二进制数据
    imcp_packet = struct.pack('>BBHHH32s', data_type, data_code, data_checksum, data_ID, data_Sequence, payload_body)
    # 获取校验和
    icmp_chesksum = chesksum(imcp_packet)
    #  把校验和传入,再次打包
    imcp_packet = struct.pack('>BBHHH32s', data_type, data_code, icmp_chesksum, data_ID, data_Sequence, payload_body)
    return imcp_packet

# 初始化套接字,并发送
def raw_socket(dst_addr,imcp_packet):
    # 实例化一个socket对象,ipv4,原套接字(普通套接字无法处理ICMP等报文),分配协议端口
    rawsocket = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.getprotobyname("icmp"))
    # 记录当前请求时间
    send_request_ping_time = time.time()
    # 发送数据到网络
    rawsocket.sendto(imcp_packet, (dst_addr, 80))
    return send_request_ping_time, rawsocket


def reply_ping(send_request_ping_time, rawsocket, data_Sequence, timeout=2):
    while True:
        '''
        select函数,直到inputs中的套接字被触发(在此例中,套接字接收到客户端发来的握手信号,从而变得可读,满足select函数的“可读”条件),
        返回被触发的套接字(服务器套接字);                
        '''
        # 实例化select对象(非阻塞),可读,可写为空,异常为空,超时时间
        what_ready = select.select([rawsocket], [], [], timeout)
        # 等待时间
        # wait_for_time = (time.time() - started_select)
        wait_for_time = (time.time() - send_request_ping_time)
        # 没有返回可读的内容,判断超时
        if what_ready[0] == []:  # Timeout
            return -1
        # 记录接收时间
        time_received = time.time()
        # 设置接收的包的字节为1024
        received_packet, addr = rawsocket.recvfrom(1024)
        # 获取接收包的icmp头
        # print(icmpHeader)
        icmpHeader = received_packet[20:28]
        # 反转编码
        type, code, r_checksum, packet_id, sequence = struct.unpack(
            ">BBHHH", icmpHeader
        )

        if type == 0 and sequence == data_Sequence:
            return time_received - send_request_ping_time

        # 数据包的超时时间判断
        timeout = timeout - wait_for_time
        if timeout <= 0:
            return -1


def dealtime(dst_addr, sumtime, shorttime, longtime, accept, i, time):
    sumtime += time
    print(sumtime)
    if i == 4:
        print("{0}的Ping统计信息:".format(dst_addr))
        print("数据包:已发送={0},接收={1},丢失={2}({3}%丢失),\n往返行程的估计时间(以毫秒为单位):\n\t最短={4}ms,最长={5}ms,平均={6}ms".format(i+1,accept,i+1-accept,(i+1-accept)/(i+1)*100,shorttime,longtime,sumtime))


def ping(host):
    # 统计最终 已发送、 接受、 丢失
    send, accept, lost = 0, 0, 0
    sumtime, shorttime, longtime, avgtime = 0, 1000, 0, 0
    # TODO icmp数据包的构建
    # 8回射请求 11超时 0回射应答
    data_type = 8
    data_code = 0
    # 检验和
    data_checksum = 0
    # ID
    data_ID = 0
    # 序号
    data_Sequence = 1
    # 可选的内容
    payload_body = b'abcdefghijklmnopqrstuvwabcdefghi' #data

    # 将主机名转ipv4地址格式,返回以ipv4地址格式的字符串,如果主机名称是ipv4地址,则它将保持不变
    dst_addr = socket.gethostbyname(host)
    print("正在 Ping {0} [{1}] 具有 32 字节的数据:".format(host, dst_addr))
    # 默认发送4次
    for i in range(0, 4):
        send = i + 1
        # 请求ping数据包的二进制转换
        icmp_packet = request_ping(data_type, data_code, data_checksum, data_ID, data_Sequence + i, payload_body)
        # 连接套接字,并将数据发送到套接字
        send_request_ping_time, rawsocket = raw_socket(dst_addr, icmp_packet)
        # 数据包传输时间
        times = reply_ping(send_request_ping_time, rawsocket, data_Sequence + i)
        if times > 0:
            print("来自 {0} 的回复: 字节=32 时间={1}ms".format(dst_addr, int(times*1000)))

            accept += 1
            return_time = int(times * 1000)
            sumtime += return_time
            if return_time > longtime:
                longtime = return_time
            if return_time < shorttime:
                shorttime = return_time
            time.sleep(0.7)
        else:
            lost += 1
            print("请求超时。")

        if send == 4:
            print("{0}的Ping统计信息:".format(dst_addr))
            print("\t数据包:已发送={0},接收={1},丢失={2}({3}%丢失),\n往返行程的估计时间(以毫秒为单位):\n\t最短={4}ms,最长={5}ms,平均={6}ms".format(
                i + 1, accept, i + 1 - accept, (i + 1 - accept) / (i + 1) * 100, shorttime, longtime, sumtime/send))


if __name__ == "__main__":
    i = input("请输入要ping的主机或域名\n")
    ping(i)
补充:SOCK_RAW

实际上,我们常用的网络编程都是在应用层的报文的收发操作,也就是大多数程序员接触到的流式套接字(SOCK_STREAM)和数据包式套接字(SOCK_DGRAM)。而这些数据包都是由系统提供的协议栈实现,用户只需要填充应用层报文即可,由系统完成底层报文头的填充并发送。

然而在某些情况下需要执行更底层的操作,比如修改报文头、避开系统协议栈等。这个时候就需要使用其他的方式来实现。

原始套接字(SOCK_RAW)是一种不同于SOCK_STREAM、SOCK_DGRAM的套接字,它实现于系统核心。首先来说,普通的套接字无法处理ICMP、IGMP等网络报文,而SOCK_RAW可以;其次,SOCK_RAW也可以处理特殊的IPv4报文;此外,利用原始套接字,可以通过IP_HDRINCL套接字选项由用户构造IP头。总体来说,SOCK_RAW可以处理普通的网络报文之外,还可以处理一些特殊协议报文以及操作IP层及其以上的数据。

引用自:

https://blog.csdn.net/zhj082/article/details/80531628

https://www.jianshu.com/p/17f16256008d

《计算机网络》第七版

https://blog.csdn.net/newnewman80/article/details/8000404
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,069评论 4 358
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,212评论 1 287
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 105,912评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,424评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,741评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,194评论 1 206
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,553评论 2 307
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,289评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,923评论 1 237
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,251评论 2 240
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,775评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,144评论 2 249
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,698评论 3 228
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,936评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,658评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,214评论 2 267
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,159评论 2 258

推荐阅读更多精彩内容

  • 个人认为,Goodboy1881先生的TCP /IP 协议详解学习博客系列博客是一部非常精彩的学习笔记,这虽然只是...
    贰零壹柒_fc10阅读 5,019评论 0 8
  • ping 命令 Ping是为了测试另一台主机是否可达,现在已经成为一种常用的网络状态检查工具。 常见的ping命令...
    iosmedia阅读 4,455评论 3 12
  • 本篇结构: ICMP IGMP 附 反思 接着上一篇TCP/IP--划分子网和构造超网,本章接着分享IP协议的两个...
    w1992wishes阅读 10,345评论 0 4
  • 一、ICMP 1.1、什么是ICMP ICMP的全称是Internet Control Message Proto...
    陳铁柱阅读 2,044评论 0 0
  • 最近在做一个ping的功能,用python实现,要分别实现ipv4和ipv6两种栈。虽然也是用开源的ping包,并...
    running_sheep阅读 5,334评论 0 1