IPv6 协议小解
IPV6 数据报组成
IPV6 数据报在基本首部(base header)的后面允许有零个或者多个拓展首部(extension header),再后面就是数据。图中的IPv6报头就是基本首部,其后被称为数据报的有效载荷(payload)或净负荷。
所有的拓展首部并不算是IPv6数据报的首部。
IPV6 数据报首部
- Version 版本 占4位. 它指明了协议的版本,对IPv6该字段是6
- Traffic Classes 通信量类 占8位。使得源节点和路由器能够识别IPv6数据报的优先级(有点类似Qos)
- Flow Label 流标号 占20位。 首先,流(flow)的概念是: 互联网络上从特定源点到特定终点(单播或多播)的一系列数据报,比如实时音频视频传输, 而这个“流”所经过的路径上的路由器都保证指明的服务质量。 而对于传统的电子邮件或非实时数据,将其置为0 即可。
- Payload Length 有效载荷长度 占16位。 它指明IPv6数据报除基本首部以外的字节数。 这个字段的最大值是64K (65 535字节)
- Next Header 下一个首部 占8位。它相当于IPv4的协议字段或可选字。 当IPv6数据报没有拓展首部时,下一个首部字段的作用和IPv4的协议字段一样,指明基本首部后面的数据应交付IP层上层的哪一个高层协议。(例如 6 代表TCP, 17代表UDP, 58代表ICMPv6)
- Hop Limit 跳数限制 占8位。用来防止数据报在网络中无限期存在。最大为255跳。当路由器在转发数据报时,要先把跳数限制字段中的值减1. 当该字段为0时,就把这个数据报丢弃。
- Source IP Address 源地址 占128位。 数据报发送端IP地址
- Destination IP Address 目的地址 占128位。 数据报接收端的IP地址。
IPv6地址
IPv6使用冒号十六进制记法(colon hexademical notation, 简写为colon hex) : 把每个16位的值用十六进制值标识,各值之间用冒号分隔。
特殊记录:
- 在十六进制中,可以把数字前面的0省略掉,0000 可以写成0
- 零压缩 : 冒号十六进制记法允许零压缩(Zero compression),即一连串连续的零可以为一对冒号取代。任意地址只能使用一次零压缩。
> 比如 FF05:0:0:0:0:0:0:B3 可以简写为FF05::B3
> - 冒号十六进制记法可结合点分十进制法的后缀。这样在IPv4和IPv6的转换中特别有用。
- CIDR的斜线表示法仍然可用。
IPv6 地址分类
IPv4 向 IPv6 的过渡
- 双协议栈
- 隧道技术
所谓的隧道技术(tunneling) 就是把IPv6数据报封装到IPv4中,要使双协议栈的主机知道IPv4数据报里面封装的数据是一个IPv6数据报,就必须把IPv4首部的协议字段的值设置为41.
ICMPv6协议分析及Python实现
ICMPv6 格式分析
定义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)
RFC 4443 给出的Type 信息, 一个是差错报文,一个是信息报文。 安
Code 为8 bit
Code 通常与Type配合使用。
1. 差错报文(RFC2463)Checksum 为16bit 。 下图是校验和计算的RFC 文档
翻译过来就是:校验和是整个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 请求报文的报文结构
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
- 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)
暂无评论