美文网首页
ping与ping6

ping与ping6

作者: running_sheep | 来源:发表于2019-06-17 09:38 被阅读0次

    最近在做一个ping的功能,用python实现,要分别实现ipv4和ipv6两种栈。虽然也是用开源的ping包,并且只有不到300行,但ipv6改的时候确实感觉无从下手;所以老大改完之后,就仔细研究了一下这个包,发现囊括的知识点确实不少。为了避免自己以后忘记,这里记录下来。

    ICMP协议

    实现ping主要通过ICMP协议,因为IP协议不是一个可靠的协议,不保证数据被成功送达。所以需要使用ICMP网络控制报文协议。因为它传递差错报文和其他重要信息,所以通常供IP层或TCP/UP层使用,被人为是IP层一个重要组成部分。

    ICMPv4和ICMPv6消息的前4个字节(也就是前32位)是相同的。数据链路层所能发送的最大数据帧MTU为1500字节,ICMP协议在实际传输中数据包为20字节IP首部+8字节ICMP首部+1472字节(数据大小)。

    ping原理

    ping程序的目的是测试另一台主机是否可达。该程序发送一份ICMP回显请求报文给远程主机,并等待ICMP回显应答。如果源主机在一定时间内收到应答,则认为主机可达。ping的原理是用类型码为0的ICMP发请求,收到请求的主机则用类型码为8的ICMP回应。通过计算ICMP应答报文数量和接收与发送报文之间的时间差,判断当前网络状态。ping命令在发送报文的时候,将当前时间值存储在ICMP报文中发出,当应答报文返回时,使用当前值减去存放在ICMP报文数据中存放发送请求的时间值来计算往返时间。Unix系统在实现ping程序时是把ICMP报文中的标识符字段置成发送进程的 ID号。这样 即使在同一台主机上同时运行了多个 ping程序实例,ping程序也可以识别出返回的信息。

    ping包分析

    my_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, 1)

    //ipv6:my_socket = socket.socket(socket.AF_INET6, socket.SOCK_RAW, 58)

    my_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BINDTODEVICE, src_nic)

    my_id = os.getpid() & 0xFFFF

    send_one_ping(my_socket, dest_addr, my_id, psize)

    delay = receive_one_ping(my_socket, my_id, timeout)

    1. socket(family,type[,protocal]) 使用给定的地址族、套接字类型、协议编号(默认为0)来创建套接字。

    socket类型

    socket.AF_INET:服务器之间网络通信

    socket.SOCK_RAW:原始套接字,普通的套接字无法处理ICMP、IGMP等网络报文,而SOCK_RAW可以;其次,SOCK_RAW也可以处理特殊的IPv4报文

    1这里是协议编号,ICMP协议编号为1,ICMPv6的协议编号为58

    这是winsock2.h里的定义。

    define IPPROTO_ICMP    1

    define IPPROTO_ICMPV6    58   /* ICMPv6 */

    2. 默认的socket选项不够时,用setsocket来调整。socket.setsockopt(level, optname, value)

    level:选项定义的层次--SOL_SOCKET、IPPROTO_TCP、IPPROTO_IP、IPPROTO_IPV6

    optname:需要设置的选项

    value:选项值

    SOL_SOCKET:正在使用socket选项

    当level设定为SOL_SOCKET,会有一些常见选项。SO_BINDTODEVICE:可以使socket只在某个特殊的网络接口有效。这时,value就是设备名称,或者为空字符串返回默认值。

    3.send_one_ping(my_socket, dest_addr, my_id, psize)

    my_id:Unix系统在实现ping程序时是把ICMP报文中的标识符字段置成发送进程的 ID号。这里和0xFFFF做了&运算,表示取字符低16位

    在send_one_ping中要完成构造包的工作。

    这一步用到了struct模块。当我们需要用python处理二进制数据,如存储文件,socket操作时,可以使用struct模块,来处理C语言中的结构体。

    pack(fmt,v1,v2,v3,...)//按照给定格式fmt,把数据封装成字符串(实际上是类似于C结构体字节流)

    format          C type            python type        standard size        notes

    b                signed char          integer                    1                    (3)

    B            unsigned char          integer                    1                    (3)

    h                    short                 integer                    1                    (3)

    H             unsigned short        integer                    1                    (3)

    对齐方式:!表示我们要使用网络字节顺序解析,因为数据是从网络中接收到的。(字节对齐,通常是以4个字节为单位的32位系统。故struct根据本地机器字节顺序转换,可以用格式中第一个字符来改变对齐方式。)

    这里要提到signed char和unsigned char的区别,作为字符使用,都是存字符的ASCII码,作为整数使用,两种类型取值范围不通,unsigned char取0~255,signed char可取值为-128至127。在ipv4中ICMP_ECHO_REQUEST赋值为8,所以struct.pack时用b转换,在ipv6中,ICMP_ECHO_REQUEST赋值为128,所以用B来转换。

        # Remove header size from packet size

        psize = psize - 8

        # Header is type (8), code (8), checksum (16), id (16), sequence (16)

        my_checksum = 0

        # Make a dummy heder with a 0 checksum.

        header = struct.pack("bbHHh", ICMP_ECHO_REQUEST, 0, my_checksum, id, 1)

        //ipv6:header = struct.pack("!BbHHh", ICMP_ECHO_REQUEST, 0, my_checksum, id, 1)

        bytes = struct.calcsize("d")

        data = (psize - bytes) * "Q"

        data = struct.pack("d", time.time()) + data

        # Calculate the checksum on the data and the dummy header.

        my_checksum = checksum(header + data)

        # Now that we have the right checksum, we put that in. It's just easier

        # to make up a new header than to stuff it into the dummy.

        header = struct.pack("bbHHh", ICMP_ECHO_REQUEST, 0, socket.htons(my_checksum), id, 1)

       //ipv6:header = struct.pack( "!BbHHh", ICMP_ECHO_REQUEST, 0, socket.htons(my_checksum), id, 1 )

        packet = header + data

        my_socket.sendto(packet, (dest_addr, 1))  # Don't know about the 1

        //发送UDP数据,将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。

        //ipv6:my_socket.sendto(packet, (dest_addr, 58, 0, 0))

    4.校验和算法

    IP包发送端,首先将校验和字段设置为0,然后将IP数据包头按16比特划分单元,若包头长度非16倍数,则用0补齐,然后对各个单元采用反码加法运算(即高位溢出加到低位),将得到的和的反码填入校验和字段发送。

    比如:

    一个数据包:45 00 00 29 44 F1 40 00 80 06 61 8D C0 A6 01 AE 4A 7D 47 7D

    参考

    https://www.cnblogs.com/JetpropelledSnake/p/9177770.html

    https://www.xuebuyuan.com/3254723.html

    相关文章

      网友评论

          本文标题:ping与ping6

          本文链接:https://www.haomeiwen.com/subject/dfhlfctx.html