美文网首页
Python网络编程4--实现IP分片与网络路径MTU探测

Python网络编程4--实现IP分片与网络路径MTU探测

作者: 净坛使者_猪悟能 | 来源:发表于2021-08-09 12:50 被阅读0次

一、IP分片原理

1.1 基本原理

  • IP包长计算
      IP 包全长由头部中的 total length 字段决定,该字段共 16 位,因此一个 IP 包最大可达 2^16-1 ,即 65535 字节。除去头部 20字节(无Options字段),IP 包最多可承载65535-20,即 65515 字节的数据。
  • MTU计算
      MTU是最大传输单元( Maximum Transmission Unit)的缩写,指一个接口无需分片所能发送的数据包的最大字节数。
      MTU范围在46 ~ 1500字节,默认一般都是1500(7字节前导码+1字节帧开始定界符+6字节的目的MAC+6字节的源MAC+2字节的帧类型+1500字节IP头及数据+4字节的 FCS = 1526字节。抓包软件抓到的是去掉前导码、帧开始定界符、FCS之外的数据,其最大值是 6+6+2+1500=1514)
  • 产生IP分片的原因
      IP分片发生在IP层,不仅源端主机会进行分片,中间的路由器也有可能分片,因为不同的网络的MTU是不一样的,如果传输路径上的某个网络的MTU比源端网络的MTU要小,路由器就可能对IP数据报再次进行分片。而分片数据的重组只会发生在目的端的IP层。

1.2 与分片相关的IP头部字段

IP头部
  • 标识符( identification ),IP 包的 ID ,全局自增,短时间内不会重复,可唯一标识一个 IP 包;

  • 标志位( flags ),包括两个用于控制和识别分片的标志位;

    • DF 标志位禁止中间路由对该包进行分片;
    • MF 标志位表明该包之后还有其他分片;
  • 偏移量( fragment offset ),表示一个分片相对于原始 IP 包开头的偏移量,以8字节为单位;

  • 分片大小计算
      假设主机①出口 MTU 是 1500 ,它准备发一个长度为 4000 字节的 IP包给主机②。这个包必须分片

    分片
      如上图,原包长达 4000 字节,其中头部 20 字节,数据部分为 3980 字节。分片包最大长度为 1500 ,除去头部的 20 字节,数据部分只剩 1480 。这意味着,原包 3980 字节至少需要分为 3 片。
      由于偏移量字段以 8 字节为单位,因此每个分片的数据长度必须为 8 的倍数,最后一片除外。由于 1480 刚好可以被 8 整除,因此分片数据长度可以选择 1480 。
  • 片偏移计算
      第一个分片,包含原包前 1480 字节数据,因此偏移量 offset=0(数据的首个字节离原始IP头部距离为0) ;而 MF=1 表示后面还有其他分片。第二个分片,包含原包紧接着的 1480 字节数据,偏移量offset=(1480+1480)/8=185 ;同样 MF=1 表示后面还有其他分片。最后一个分片,包含原包最后 1020 字节数据,偏移量(1480*2)/8=370 ;而 MF=0 表示它是最后一片了。

  • 分片重组
      分片到达目标主机后,系统根据包头中的源地址、目的地址、标识符、偏移量等字段,将它们重组合成原包。
      实际上,系统会分配一块内存作为重组分片的缓冲区。一个分片包首个分片达到后,系统将其移入到该缓冲区,等待其他分片达到:

    分片重组
    后续分片达到后,系统先根据源地址、目的地址和标识符确定它属于哪个包;再根据偏移量确定它属于原包的哪个部分;最后将分片数据拼接到原包中。

二、实现IP分片

2.1实验拓扑

实验拓扑如下,linux向R2发送IP分片,并在R2接口上抓包。


IP分片拓扑

2.2 Python手动制作分片

  • Python脚本
#!/usr/bin/python3.4
# -*- coding=utf-8 -*-
from kamene.all import *

#严重注意ICMP的校验和是整ICMP头部和数据部分一起计算的!!!
#flags="MF"或flags=1表示后面有分片,flags=0表示为最后一个分片,flags="DF"或flag=2表示不分片
#frag一位表示8字节,因此frag数值乘以8,才是真正的偏移量字节数!第一个分片需加上ICMP首部8字节。
send(IP(flags="MF",frag=0,id=1,dst='172.16.10.2')/ICMP(chksum=0x7932)/b'Farst Hello Word!!!!!!!!')
send(IP(flags=1,frag=4,id=1,proto=1,dst='172.16.10.2')/(b'second Hello Word!!!!!!!'))
send(IP(flags=0,frag=7,id=1,proto=1,dst='172.16.10.2')/(b'third Hello Word!!'))
  • 执行效果
    执行效果如下:


    分片

    抓包结果如下,发送三个数据包,前两包MF位置1.


    分片1
    分片2
    分片3

2.2.1 ICMP校验和计算

  ICMP包校验和是连通头部信息加数据本身一起进行校验(ip包只需要校验头部信息)而Scapy自动添加ICMP校验和时只会计算第一分片的数据,当三个分片到达目标主机进行重组后校验不通过,将重组后的数据包丢弃;因此在手动设置IP分片时,需要手动将校验和添加入ICMP首部中。
由于手动计算校验和过程较复杂,可通过wireshark抓包,可以获取到正确的校验和。


ICMP校验和

2.3 Scapy自动分片

  • python脚本
#!/usr/bin/python3.4
# -*- coding=utf-8 -*-
from kamene.all import *

##############################自动制造Fragment################################
frags = fragment(IP(dst='172.16.10.2')/ICMP()/(b"Hello Word"*300))
#产生每一个分片,可以对分片就行修改!!!!
send(frags)

#正常发包,系统会自动进行分片处理!!!!
#send(IP(dst='172.16.10.2')/ICMP()/(b"Hello Word"*300))

抓包结果如下,由于单个数据包长度超过MTU,系统自动将ICMP request包分片发送,同样的ICMP reply系统也进行了分片。


自动分片

三、网络路径MTU探测

3.1 基本原理

  当主机发送分组的长度超过MTU又不可以分片(IP flags位DF置1),则这个分组丢弃,并用ICMP差错报文向主机报告。

3.2 具体实现

  • 实验拓扑
    实验拓扑如下,在路由器出接口设置不同的MTU值。


    MTU路径
  • Python脚本
#!/usr/bin/python3.4
# -*- coding=utf-8 -*-

from kamene.all import *
import time
import re

def ping_df(dst,mtu):
    pyload = b'v'*(int(mtu) - 28) #28为20字节IP头部和8字节ICMP头部的长度

    ping_one_reply = sr1(IP(dst=dst,flags='DF')/ICMP()/pyload, timeout = 1, verbose=False)
    #发送DF位的数据包
    try:
        if ping_one_reply.getlayer(ICMP).type == 3 and ping_one_reply.getlayer(ICMP).code == 4:
            #如果返回ICMP不可达信息,就返回1和当前的mtu
            MTU=ping_one_reply.getlayer(ICMP).unused#获取经过设备出接口的MTU值
            print("中间设备出接口MTU值为:", MTU)
            return 1
        elif ping_one_reply.getlayer(ICMP).type == 0 and ping_one_reply.getlayer(ICMP).code == 0:
            #如果返回ICMP echo reply,就返回2和当前的mtu
            return 2, mtu
    except Exception as e:
        if re.match('.*NoneType.*',str(e)):
            return None #如果测试失败,就返回None
def discover_path_mtu(dst):
    mtu = 1500 #mtu从1500开始向下减
    while True:
        Result = ping_df(dst,mtu)
        if Result == None: #如果测试失败就打印信息,并且跳出循环
            print('目标: ' + dst + '不可达!')
            break
        elif Result[0] == 2: #如果PING测试成功,就打印信息,并且跳出循环
            print('目标: ' + dst + '的Path MTU为: ' + str(Result[1]))
            break
        elif Result[0] == 1: #如果得到不可达信息,就较少MTU,打印消息,并且继续循环  
            mtu = mtu - 50
        time.sleep(1)

if __name__ == '__main__':
    dest=input("目标IP>>>")
    discover_path_mtu(dest)
  • 执行效果
    执行结果如下,返回经过的中间设备出接口的MTU值,如此则可得知路径中最小的MTU值。


    MTU探测

    抓包结果如下:
    request包中Flags DF置1,不允许分片。


    request
    由于分组长度超过了该设备出接口的MTU值,故返回一个ICMP差错报文告知分组长度超过MTU,并将下一跳的MTU值返回。
    ICMP 不允许分片

参考:(https://fasionchan.com/network/ip/fragmentation/)

相关文章

网友评论

      本文标题:Python网络编程4--实现IP分片与网络路径MTU探测

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