【问题标题】:How to find mtu value of network through code(in python)?如何通过代码(在python中)找到网络的mtu值?
【发布时间】:2021-08-14 00:09:37
【问题描述】:

我必须编写一个代码,我需要在 python 中使用 udp 协议发送数据。我需要将数据包大小设置为网络的 MTU 值。有什么方法可以让我在 python 中编写一些代码来决定网络的 MTU 值吗?

【问题讨论】:

    标签: python sockets mtu


    【解决方案1】:

    这个答案取自 http://books.google.co.il/books?id=9HGUc8AO2xQC&pg=PA31&lpg=PA31&dq#v=onepage&q&f=false (第 31 页)

    s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
    hostName = #ip here
    Port = 9999
    s.connect((hostName, Port))
    s.setsockopt(socket.IPPROTO_IP, IN.IP_MTU_DISCOVER, IN.IP_PMTUDISC_DO)
    try:
        s.send('#' * 1473)
    except socket.error:
        print 'The message did not make it'
        option = getattr(IN, 'IP_MTU', 14)
        print 'MTU:', s.getsockopt(socket.IPPROTO_IP, option)
    else:
        print 'The big message was sent! Your network supports really big packets!'
    

    【讨论】:

    • 是的,确实有效,但是在udp协议中使用connect是否正确?
    • @RahulKatare:是的,它是:“要获得路径 MTU 的初始估计,请使用 connect(2) 将数据报套接字连接到目标地址,并通过调用 getsockopt(2) 来检索 MTU IP_MTU 选项。”。
    • 什么是INimport IN 不起作用 - 谷歌搜索 IN 并不容易。
    • Ah - IN 定义了特定于平台的网络常量。它已从 Python 3.6 python.readthedocs.io/en/latest/whatsnew/… 中删除
    【解决方案2】:

    有一个 github-gist 提供此功能:

    import re
    import socket
    import struct
    import logging
    import subprocess
    from fcntl import ioctl
    
    SIOCGIFMTU = 0x8921
    SIOCSIFMTU = 0x8922
    
    log = logging.getLogger(__name__)
    
    def get_mtu_for_address(ip):
        routeinfo = subprocess.check_output(['ip', 'route', 'get', ip])
        dev = re.search('.*dev (\w+) .*', routeinfo).groups()[0]
        mtuinfo = subprocess.check_output(['ip', 'link', 'show', dev])
        mtu = re.search('.*mtu ([0-9]+) .*', mtuinfo).groups()[0]
        return int(mtu)
    
    class Iface:
        def __init__(self, ifname):
            self.ifname = ifname
    
        def get_mtu(self):
            '''Use socket ioctl call to get MTU size'''
            s = socket.socket(type=socket.SOCK_DGRAM)
            ifr = self.ifname + '\x00'*(32-len(self.ifname))
            try:
                ifs = ioctl(s, SIOCGIFMTU, ifr)
                mtu = struct.unpack('<H',ifs[16:18])[0]
            except Exception, s:
                log.critical('socket ioctl call failed: {0}'.format(s))
                raise
    
            log.debug('get_mtu: mtu of {0} = {1}'.format(self.ifname, mtu))
            self.mtu = mtu
            return mtu
    
        def set_mtu(self, mtu):
            '''Use socket ioctl call to set MTU size'''
            s = socket.socket(type=socket.SOCK_DGRAM)
            ifr = struct.pack('<16sH', self.ifname, mtu) + '\x00'*14
            try:
                ifs = ioctl(s, SIOCSIFMTU, ifr)
                self.mtu = struct.unpack('<H',ifs[16:18])[0]
            except Exception, s:
                log.critical('socket ioctl call failed: {0}'.format(s))
                raise
    
            log.debug('set_mtu: mtu of {0} = {1}'.format(self.ifname, self.mtu))
    
            return self.mtu
    
    
    if __name__ == "__main__":
        import sys
        logging.basicConfig()
    
        mtu = None
        if len(sys.argv) > 2:
            dev,mtu = sys.argv[1:]
        elif len(sys.argv) > 1:
            dev = sys.argv[1]
        else:
            dev = 'eth0'
    
        iface = Iface(dev)
        if mtu is not None:
            iface.set_mtu(int(mtu))
    
        print dev,'mtu =',iface.get_mtu()
    

    来源:https://gist.github.com/nzjrs/8934855

    【讨论】:

    • 在 Python 3.7 中对我不起作用。 ioctl 调用 get_mtu 引发:OSError: [Errno 6] Device not configured
    【解决方案3】:

    接受的答案在 Python 3.7 中对我不起作用。我得到:OSError: [Errno 6] Device not configured

    但是,psutil 现在已经内置了。

    import psutil
    print(psutil.net_if_stats())
    

    结果:

    {
      'lo0': snicstats(isup=True, duplex=<NicDuplex.NIC_DUPLEX_UNKNOWN: 0>, speed=0, mtu=16384), 
      'en0': snicstats(isup=True, duplex=<NicDuplex.NIC_DUPLEX_UNKNOWN: 0>, speed=0, mtu=1500), 
      ...
    }
    

    【讨论】:

    • 这将为您提供网络接口的 MTU。不是整个数据包路由路径。
    • @AminPial 正确。之后我调用ping,设置包大小(减去TCP头大小)一路检查路径就可以处理了。
    • 是的,对。使用 DF (Don't Fragment) 标志 ping 并检查最小 MTU。无论如何,你会如何通过 ping 来考虑 min MSS(MTU-IP-Transport)?蛮力?还是二分查找?还是其他方式?
    • @AminPial 我在某处读到它是 28 个字节,然后通过手动二进制搜索对其进行了一次测试。我使用-c 1 -M do -s &lt;mtu - 28&gt;。你提到了 DF 标志。怎么设置?
    • 我已经在 windows 中测试过(linux 或 mac 的 ping 可能有不同的标志)。你可以在下面阅读我的回答。 -f -l {size} -f 用于 DF 标志。
    【解决方案4】:

    您可以简单地使用 DF (Don't Fragment) 标志对 ping 进行二进制搜索。这是通过上述技术查找 MTU 的工作编码。它为您提供了“完整数据包路由路径的最小 MTU,也就是您可以发送的最大有效负载”。

    仅在 Windows 上测试(不适用于 Linux/Mac,因为不同操作系统中的 ping 标志不同)

    
    # tested on Windows 10 Home and python 3.6 [at Great Istanbul, Turkey]
    import subprocess
    from time import perf_counter
    
    
    class FindMinMtu:
        """
            - Find Minimum "Maximum Transmission Unit" of a packet routing path via Binary Search
    
            - Suppose you want to find how much data you can send in each packet
              from London to Turkey?
            - Now we need to remember MTU and MSS (Max. Segment size) isn't not the same.
              MSS is the actual data (not headers) you can send. A typical formula for MSS is
              MSS = MTU - (IP header_size  + TCP/UDP/Any Transport Layer Protocol header_size)
              whereas MTU = Everything in packet - Ethernet headers
              MTU typical refers to Ethernet MTU, AKA how much payload can an ethernet cable push through next hop.
        """
    
        def __init__(self, url: str):
            self.url = url
    
            self._low_mtu = 500
            # typically ethernet cables can carry 1500 bytes (but Jumbo fiber can carry upto 9K bytes AFAIK)
            # so increase it as per your requirements
            self._high_mtu = 1500
            self._last_accepted = self._low_mtu
    
        @staticmethod
        def yield_console_output(command):
            p = subprocess.Popen(command,
                                 stdout=subprocess.PIPE,
                                 stderr=subprocess.STDOUT)
            return iter(p.stdout.readline, b'')
    
        def does_accept_mtu_size(self, size) -> bool:
            command = 'ping {domain_name} -t -f -l {size}'.format(domain_name=self.url,
                                                               size=size).split()
            for line in self.yield_console_output(command):
                line = line.decode(encoding='utf-8')
                if line.startswith('Packet') and 'DF' in line:
                    return False
                elif line.startswith('Reply'):
                    return True
    
        def find_min_mtu(self):
            while self._low_mtu <= self._high_mtu:
                if not (self.does_accept_mtu_size(self._low_mtu), self.does_accept_mtu_size(self._high_mtu)):
                    return self._last_accepted
                else:
                    middle = (self._high_mtu + self._low_mtu) // 2
                    print("Low: {} High: {} Middle: {}".format(self._low_mtu, self._high_mtu, middle))
                    if self.does_accept_mtu_size(middle):
                        self._last_accepted = middle
                        self._low_mtu = middle + 1
                    else:
                        self._high_mtu = middle - 1
            return self._last_accepted
    
    
    if __name__ == '__main__':
        start = perf_counter()
        # please provide protocol less domain name (without http://, https:// and also without www or any subdomain)
        # provide the naked url (without www/subdomain)
        f = FindMinMtu("libwired.com")
        print("\nMTU: {} bytes (Found in {} seconds)".format(f.find_min_mtu(), perf_counter() - start))
    

    【讨论】:

      猜你喜欢
      • 2011-09-11
      • 1970-01-01
      • 1970-01-01
      • 2014-05-16
      • 2018-09-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-02-02
      相关资源
      最近更新 更多