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?
Asked
Active
Viewed 1.1k times
4 Answers
9
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!'

user2084795
- 704
- 1
- 7
- 20

hillel_guy
- 646
- 6
- 17
-
Yes, It worked, but is it right to use connect in udp protocol? – Rahul Katare Dec 24 '12 at 04:21
-
@RahulKatare: Yes it is: "To get an initial estimate of the path MTU, connect a datagram socket to the destination address using connect(2) and retrieve the MTU by calling getsockopt(2) with the IP_MTU option.". – Matt Joiner Jun 19 '13 at 11:34
-
What is `IN`? `import IN` doesn't work - googling `IN` isn't easy. – Isaac Turner Nov 08 '17 at 23:24
-
Ah - `IN` defines platform specific network constants. It has been removed from Python 3.6 https://python.readthedocs.io/en/latest/whatsnew/3.6.html#api-and-feature-removals – Isaac Turner Nov 09 '17 at 00:09
1
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()

Humoyun Ahmad
- 2,875
- 4
- 28
- 46
-
Does not work for me in Python 3.7. The `ioctl` call in `get_mtu` raises: `OSError: [Errno 6] Device not configured` – Damon Maria Apr 14 '21 at 22:31
1
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),
...
}

Damon Maria
- 1,001
- 1
- 8
- 21
-
this gives you the MTU of your network interface. not the whole packet routing path. – Amin Pial Aug 06 '21 at 14:50
-
@AminPial Correct. After this I call to ping, setting the packet size (minus TCP header size) to check the path all the way can handle it. – Damon Maria Aug 12 '21 at 05:09
-
yap, right. ping with ```DF (Don't Fragment)``` flag and check the min MTU. Anyway, how would you approach to mind min MSS (MTU- IP- Transport) via ping? brute force? or a binary search? or any other way? – Amin Pial Aug 12 '21 at 15:24
-
@AminPial I read somewhere it was 28 bytes and then tested it once by manual binary search. I use `-c 1 -M do -s
`. You mention the DF flag. How can that be set? – Damon Maria Aug 13 '21 at 20:07 -
I haved tested in windows (linux or mac's ping might have different flags). You can read my answer below. ```-f -l {size}``` -f is for DF flag. – Amin Pial Aug 14 '21 at 06:15
0
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))

Amin Pial
- 383
- 6
- 12