IPv6协议小解-ICMPv6协议分析-python实现ICMPv6 请求报文(Echo Request Message)

IPv6 协议小解

IPV6 数据报组成

image

IPV6 数据报在基本首部(base header)的后面允许有零个或者多个拓展首部(extension header),再后面就是数据。图中的IPv6报头就是基本首部,其后被称为数据报的有效载荷(payload)或净负荷。

所有的拓展首部并不算是IPv6数据报的首部。

IPV6 数据报首部

image

  1. Version 版本 占4位. 它指明了协议的版本,对IPv6该字段是6
  2. Traffic Classes 通信量类 占8位。使得源节点和路由器能够识别IPv6数据报的优先级(有点类似Qos)
  3. Flow Label 流标号 占20位。 首先,流(flow)的概念是: 互联网络上从特定源点到特定终点(单播或多播)的一系列数据报,比如实时音频视频传输, 而这个“流”所经过的路径上的路由器都保证指明的服务质量。 而对于传统的电子邮件或非实时数据,将其置为0 即可。
  4. Payload Length 有效载荷长度 占16位。 它指明IPv6数据报除基本首部以外的字节数。 这个字段的最大值是64K (65 535字节)
  5. Next Header 下一个首部 占8位。它相当于IPv4的协议字段或可选字。 当IPv6数据报没有拓展首部时,下一个首部字段的作用和IPv4的协议字段一样,指明基本首部后面的数据应交付IP层上层的哪一个高层协议。(例如 6 代表TCP, 17代表UDP, 58代表ICMPv6)
  6. Hop Limit 跳数限制 占8位。用来防止数据报在网络中无限期存在。最大为255跳。当路由器在转发数据报时,要先把跳数限制字段中的值减1. 当该字段为0时,就把这个数据报丢弃。
  7. Source IP Address 源地址 占128位。 数据报发送端IP地址
  8. Destination IP Address 目的地址 占128位。 数据报接收端的IP地址。

IPv6地址

IPv6使用冒号十六进制记法(colon hexademical notation, 简写为colon hex) : 把每个16位的值用十六进制值标识,各值之间用冒号分隔。

特殊记录:

  1. 在十六进制中,可以把数字前面的0省略掉,0000 可以写成0
  2. 零压缩 : 冒号十六进制记法允许零压缩(Zero compression),即一连串连续的零可以为一对冒号取代。任意地址只能使用一次零压缩。
    > 比如 FF05:0:0:0:0:0:0:B3 可以简写为FF05::B3
    >
  3. 冒号十六进制记法可结合点分十进制法的后缀。这样在IPv4和IPv6的转换中特别有用。
  4. CIDR的斜线表示法仍然可用。

IPv6 地址分类

IPv4 向 IPv6 的过渡

  1. 双协议栈
  2. 隧道技术
    所谓的隧道技术(tunneling) 就是把IPv6数据报封装到IPv4中,要使双协议栈的主机知道IPv4数据报里面封装的数据是一个IPv6数据报,就必须把IPv4首部的协议字段的值设置为41.

ICMPv6协议分析及Python实现

ICMPv6 格式分析

image

定义ICMPv6时, IPv6报头Next Header 要设置为58, 十六进制为0x3a

import socket 

icmpv6 =  socket.getprotobyname("ipv6-icmp")
rawSocket = socket.socket(socket.AF_INET6, socket.SOCK_RAW, icmpv6)

Type 为8 bit (这里我只用来做 ping6 检测,因此使用128,code为0, 十六进制Type为 0x80)

icmpv6TYPE.png

RFC 4443 给出的Type 信息, 一个是差错报文,一个是信息报文。 安

iamge

Code 为8 bit
Code 通常与Type配合使用。
1. 差错报文(RFC2463)

Checksum 为16bit 。 下图是校验和计算的RFC 文档

Snipaste_2018-08-01_19-06-11.png

翻译过来就是:校验和是整个ICMPv6报文16位的补码求和, 从ICMPv6报文报文的Type域开始,在ICMPv6 前面添加一个伪IPv6头报文,就像在[IPv6,Section 8.1 ] 中说明的一样。在伪报文头中的Next Header的为58.

For computing the checksum, the check sum field is first set to zero.

为了计算检验和,checksum 的域初始化为 0 .

Echo Request Message 请求报文的报文结构
Snipaste_2018-08-02_22-07-27.png

ICMPv6 Fields:

Type = 128
Code = 0
Identifer = 0
SequenceNumber = 0
Data = "Zero or more octets of arbitrary data"

The TCP and UDP “pseudo-header” for IPv6

TIM截图20180802223337.png

  • Source Address 和 Destination Address 为 128位IPv6地址
  • The Next Header 设置为58 ,占8位,identifies the upper-layer protocol
  • The Upper-Layer Packet Length (占32位, 其实我也不知道具体占多少位)是上层协议报文的长度,包括头部和数据部分

// pyload length
0x00, 0x00, 0x00, 0x18,

// next header
0x00, 0x00, 0x00, 0x3a

ICMPv6 报文的 Python 实现

# coding : utf-8
# ICMPv6.py

__author__ = "Aaron"
__time__="2018/8/2"

import socket
import struct
import time
import select

def checkSum(message):
    """
    校验
    """
    n = len(message)
    m = n % 2
    sum = 0 
    for i in range(0, n - m ,2):
        sum += (message[i]) + ((message[i+1]) << 8)#传入data以每两个字节(十六进制)通过ord转十进制,第一字节在低位,第二个字节在高位
    if m:
        sum += (message[-1])
    #将高于16位与低16位相加
    sum = (sum >> 16) + (sum & 0xffff)
    sum += (sum >> 16) #如果还有高于16位,将继续与低16位相加

    answer = ~sum & 0xffff
    #主机字节序转网络字节序列(参考小端序转大端序)
    answer = answer >> 8 | (answer << 8 & 0xff00)
    return answer 

def rawSocket(dst_addr, icmpv6_packet):
    sendSocket = socket.socket(socket.AF_INET6, socket.SOCK_RAW, socket.getprotobyname('ipv6-icmp'))
    send_time = time.time()
    # sendSocket.sendto(icmpv6_packet, (dst_addr, 1)) # 感谢网友liut指出此处的问题
    sendSocket.sendto(icmpv6_packet, socket.getaddrinfo(dst_addr, 1)[0][4]) 
    return send_time, sendSocket

def pseudo_header(s_addr, dst_addr, upper_packet_len, n_header = 58):
    zero = 0
    # 将冒号记法地址转换为对应的底层数据
    packed_s_addr = socket.inet_pton(socket.AF_INET6, s_addr)
    packed_dst_addr = socket.inet_pton(socket.AF_INET6, dst_addr)

    packet = packed_s_addr + packed_dst_addr + struct.pack("!2L", upper_packet_len,
     n_header)
    return packet

def ICMPv6(checksum = 0, SequenceNumber = 0):
    Type = 128
    Code = 0
    Identifier = 0
    Data = b"Zero or more octets of arbitrary data."
    Message = struct.pack('!2B3H{0}s'.format(len(Data)), Type, Code, checksum,
                                                        Identifier, SequenceNumber,Data )
    return Message

def echo_ping(send_time, sendsocket, data_sequence, timeout = 2):
    while True:
        started_select = time.time()
        what_ready = select.select([sendsocket], [], [], timeout)
        wait_for_time = (time.time() - started_select)
        if what_ready[0] == []: # timeout
            return -1
        time_received = time.time()
        recieved_packet, addr = sendsocket.recvfrom(1024)
        icmpHeader = recieved_packet[0:8]
        type, code, checksum, packet_id, SequenceNumber = struct.unpack(
                "!2B3H", icmpHeader
            )
        if type == 129 and SequenceNumber == data_sequence:
            return time_received - send_time
        if timeout <= 0:
            return -1

def ping(host):
    # dst_addr = socket.gethostbyaddr(host)[-1][0]
    dst_addr = host
    s_addr = socket.getaddrinfo(socket.gethostname(), 0)[0][-1][0]

    print("正在 Ping {0} [{1}] 具有 32 字节的数据:".format(host,dst_addr))

    for i in range(0, 4):
        icmpv6 = ICMPv6(SequenceNumber = i)
        ipv6_header = pseudo_header(s_addr, dst_addr, len(icmpv6))
        icmpv6_checksum = checkSum(ipv6_header + icmpv6)
        icmpv6_packet = ICMPv6(icmpv6_checksum, SequenceNumber = i)
        send_time, r_socket = rawSocket(dst_addr, icmpv6_packet)
        times = echo_ping(send_time, r_socket, i)
        if times >= 0:
            print("来自 {0} 的回复: 字节=32 时间={1}ms".format(dst_addr,int(times*1000)))
            time.sleep(0.7)
        else:
            print("请求超时")

if __name__=="__main__":
    host = "fe80::2ad3:8509:9243:ce27"
    ping(host)

运行结果检测

image

暂无评论

发表评论

您的电子邮件地址不会被公开,必填项已用*标注。

相关推荐

暂无相关文章!