Question

I have to write a code where I need to send data using udp protocol in python. I need to set the packet size to the MTU value of the network. Is there any way that I can decide the MTU value of the network writing some code in python?

Was it helpful?

Solution

This answer was taken from http://books.google.co.il/books?id=9HGUc8AO2xQC&pg=PA31&lpg=PA31&dq#v=onepage&q&f=false (page 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!'

OTHER TIPS

There is a github-gist providing this functionality:

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()

Source: https://gist.github.com/nzjrs/8934855

The accepted answer did not work for me in Python 3.7. I get: OSError: [Errno 6] Device not configured

But, psutil now has this built in.

import psutil
print(psutil.net_if_stats())

Results in:

{
  '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), 
  ...
}

You can simply do a binary search over ping with DF (Don't Fragment) flag. Here is a working coding to find MTU through the above-mentioned technique. It gives you `minimum MTU of the full packet routing path AKA the max payload you can send.

Tested only on Windows (won't work on Linux/Mac as ping flags are different in different OS)


# 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))
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top