I'm programming in python and i have a problem, indeed when i throw my script it end some seconds after when he detect an IP6 packet. Apparently i have to filter packets and take only IP4 packet to avoid this problem and i would like to know how can i use it with the library dpkt if possible as i started.
I tried something but i'm a beginner and it don't work as you can see in this line:
#Select Ipv4 packets because of problem with the .p in Ipv6
if ip.p == dpkt.ip6:
return`
The error encountered say: "AttributeError: 'IP6' object has no attribute 'p'". This is the traceback:
This is my code if you want to have a look :)
Thanks for your time :)
import pcapy
import dpkt
from threading import Thread
import re
import binascii
liste=[]
listip=[]
piece_request_handshake = re.compile('13426974546f7272656e742070726f746f636f6c(?P<reserved>\w{8})(?P<info_hash>\w{20})(?P<peer_id>\w{20})')
piece_request_tcpclose = re.compile('(?P<start>\w{12})5011')
class PieceRequestSniffer(Thread):
def __init__(self, dev='eth0'):
Thread.__init__(self)
self.expr = 'udp or tcp'
self.maxlen = 65535 # max size of packet to capture
self.promiscuous = 1 # promiscuous mode?
self.read_timeout = 100 # in milliseconds
self.max_pkts = -1 # number of packets to capture; -1 => no limit
self.active = True
self.p = pcapy.open_live(dev, self.maxlen, self.promiscuous, self.read_timeout)
self.p.setfilter(self.expr)
#staticmethod
def cb(hdr, data):
eth = dpkt.ethernet.Ethernet(str(data))
ip = eth.data
#Select only TCP protocols
if ip.p == dpkt.ip.IP_PROTO_TCP:
tcp = ip.data
#Select Ipv4 packets because of problem with the .p in Ipv6
if ip.p == dpkt.ip6:
return
else:
try:
#Return hexadecimal representation
hex_data = binascii.hexlify(tcp.data)
except:
return
handshake = piece_request_handshake.findall(hex_data)
if handshake:
print "-----------handsheck filtered-------------"
liste.append(handshake)
print "\n"
#for element in zip(liste,"123456789abcdefghijklmnopqrstuvwxyz"):
# print(element)
def stop(self):
self.active = False
def run(self):
while self.active:
self.p.dispatch(0, PieceRequestSniffer.cb)
sniffer = PieceRequestSniffer()
sniffer.start()
Finally i found the good way to do it, the line is not:
if ip.p == dpkt.ip6:
return
But:
if eth.type == dpkt.ethernet.ETH_TYPE_IP6:
return
Related
I'm following Black Hat Python (2ed.), in which I'm writing a network scanning tool. The tool is in theory supposed to send UDP packets out to a given subnet, and if a host is up on that subnet, the response packet is decoded, found to contain the message in the original datagram, and used to indicate the host is up. This seems to generally be working well to capture packets; I can go to a website, or ping another host, and the tool reliably provides the correct source and destination addresses for those cases.
Here is the meat of the code (I have not included the class creation, or the passing of the host argument for brevity, but the host is 192.168.10.85).
class IP:
"""layer 3 (IP) packet header decoder"""
def __init__(self, buff=None):
header = struct.unpack('<BBHHHBBH4s4s', buff)
self.ver = header[0] >> 4
self.ihl = header[0] & 0xF
self.tos = header[1]
self.len = header[2]
self.id = header[3]
self.offset = header[4]
self.ttl = header[5]
self.protocol_num = header[6]
self.sum = header[7]
self.src = header[8]
self.dst = header[9]
# make IP addrs human readable
self.src_address = ipaddress.ip_address(self.src)
self.dst_address = ipaddress.ip_address(self.dst)
# the protocol_num is actually a code for the protocol name
self.protocol_name = {1: 'ICMP', 6: 'TCP', 17: 'UDP'}
# try to provide the human version of the protocol, otherwise just give the code
try:
self.protocol = self.protocol_name[self.protocol_num]
except KeyError as error:
self.protocol = self.protocol_num
print(f'Protocol is unrecognized, try googling "IP protocol {self.protocol_num}"')
class ICMP:
"""layer 4 (ICMP) packet header decoder"""
def __init__(self, buff):
header = struct.unpack('<BBHHH', buff)
self.type = header[0]
self.code = header[1]
self.checksum = header[2]
self.ident = header[3]
self.seq_num = header[4]
def udp_sender():
# blasts udp packets into the network to solicit responses
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sender:
for ip in ipaddress.ip_network(SUBNET).hosts():
# time.sleep(1)
print(f'sending a test message to {ip}')
# send our test message out to port 65212 on the destination
sender.sendto(bytes(MESSAGE, 'utf8'), (str(ip), 65212))
class Scanner:
def __init__(self, host):
self.host = host
# create raw socket, bind to public interface
# if windows:
if os.name == 'nt':
socket_protocol = socket.IPPROTO_IP
# if linux/mac:
else:
socket_protocol = socket.IPPROTO_ICMP
self.socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket_protocol)
self.socket.bind((host, 0))
# socket options, include header
self.socket.setsockopt(socket_protocol, socket.IP_HDRINCL, 1)
# enable promiscuous mode for windows
if os.name == 'nt':
self.socket.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON)
def sniff(self):
# set of all hosts that are up (respond to our ICMP message)
hosts_up = {f'{str(self.host)} *'}
try:
while True:
# read a packet, and parse the IP header
raw_buffer = self.socket.recvfrom(65535)[0]
# create IP header from the first 20 bytes
ip_header = IP(raw_buffer[0:20])
# if the protocol is ICMP, do some additional things
# print(f'src={ip_header.src_address}, dst={ip_header.dst_address}, prot_name={ip_header.protocol}')
if ip_header.protocol == 'ICMP':
# calculate where the ICMP packet starts
offset = ip_header.ihl * 4
buf = raw_buffer[offset:offset + 8]
# create ICMP structure
icmp_header = ICMP(buf)
print(f'type: {icmp_header.type}, code: {icmp_header.code}')
print(f'src={ip_header.src_address}, dst={ip_header.dst_address}, prot_name={ip_header.protocol}')
if icmp_header.type == 3 and icmp_header.code == 3:
print(f'type: {icmp_header.type}, code: {icmp_header.code}')
print(f'src={ip_header.src_address}, dst={ip_header.dst_address}, prot_name={ip_header.protocol}')
if ipaddress.ip_address(ip_header.src_address) in ipaddress.IPv4Network(SUBNET):
# make sure the packet has our test message
if raw_buffer[len(raw_buffer) - len(MESSAGE):] == bytes(MESSAGE, 'utf8'):
tgt = str(ip_header.src_address)
if tgt != self.host and tgt not in hosts_up:
hosts_up.add(str(ip_header.src_address))
print(f'Host Up: {tgt}')
However, when receiving the ICMP responses as a result of my datagram, the tool reports that the source and destination addresses are the same (my host, 192.168.10.85). Furthermore, while I should be receiving responses with Type 3 and Code 3 (destination unreachable, and port unreachable), but I am receiving (in my program) Type 3 and Code 1.
Here is an example of the output when I issue a ping command while the scanner is running, which seems correct:
src=192.168.10.85, dst=192.168.10.200, prot_name=ICMP type: 0, code: 0 src=192.168.10.200, dst=192.168.10.85, prot_name=ICMP type: 8, code: 0
Here is an example of the output to what I am assuming is the UDP packet response, which seems incorrect):
src=192.168.10.85, dst=192.168.10.85, prot_name=ICMP type: 3, code: 1
If I open wireshark while I'm running my code, I can correctly see the ICMP Type 3/Code 3 responses, so I know they are going through, here is a screen grab of one host on the target subnet as an example:
Why is my scanner not seeing these responses that are in wireshark?
I've tried running wireshark alongside my program, to see if the packets are being correctly decoded, and that the message in the UDP packet is properly in place. All signs indicate that the packets are going out to the hosts I'm trying to detect, and the correct responses are coming back, but my scanner refuses to find them.
I have 2 programs comunicating with each other via ethernet. Sending one is using scapy to encode port, ip and payload before sending it as ethernet frame. My problem is that in payload im sending counter and when reciving that it's sometimes changed to symbol.
\x00\x00\x00\x00\x00\x00\x00\x07
\x00\x00\x00\x00\x00\x00\x00\x08 is fine but next
\x00\x00\x00\x00\x00\x00\x00\t
\x00\x00\x00\x00\x00\x00\x00\n
\x00\x00\x00\x00\x00\x00\x00\x0b its fine again
later they are changed to next asci symbols
My question is how to stop converting bytes to asci?
sender.py
import socket
from scapy.all import *
PADDING_VALUE = b'\xd1'
ETH_P_ALL = 3
DST_IP = "127.0.0.12"
IFACE = "lo"
SRC_IP = "127.0.0.11"
class FpgaMockup:
def __init__(self, setup_iface):
self.setup_sock = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(ETH_P_ALL))
self.setup_sock.bind((setup_iface, 0))
self.padding = 16
def send(self, pkt):
self.setup_sock.send(pkt)
if __name__ == "__main__":
testing_fpga = FpgaMockup(IFACE)
for i in range(100):
packet = IP(dst=DST_IP, src=SRC_IP)/UDP(sport=12666, dport=12666)/Raw(load=int(i).to_bytes(8, "big")+PADDING_VALUE*testing_fpga.padding)
pkt = Ether(packet)
testing_fpga.send(raw(pkt))
print("Finished sending.")
reciever.py
import socket
ETH_P_ALL = 3
s = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(ETH_P_ALL))
s.bind(("lo", 0))
while(True):
pkt = s.recv(4096)
print(pkt)
These are not "changed". \x09 is exactly the same as \t, \x0a is the same as \n. These are just printed differently but nevertheless are the same:
>>> print(b'\x08\x09\x0a\x0b')
b'\x08\t\n\x0b'
>>> b'\x08\x09\x0a\x0b' == b'\x08\t\n\x0b'
True
For more information see the documentation to the syntax of String and Bytes literals.
If you don't want to have this conversation simply enforce writing as a hexadecimal sequence instead of characters:
>>> b'\x08\t\n\x0b'.hex()
'08090a0b'
I have a problem with my script. For a test i want to print an src and dst ip address but the characters displayed are special and after many researches i still don't understand why ...
I'm sure this is a simply problem but i didnt get it ...
This is the output:
And this is my script:
import pcapy
import dpkt
from threading import Thread
import re
import binascii
liste=[]
listip=[]
piece_request_handshake = re.compile('13426974546f7272656e742070726f746f636f6c(?P<reserved>\w{8})(?P<info_hash>\w{20})(?P<peer_id>\w{20})')
piece_request_tcpclose = re.compile('(?P<start>\w{12})5011')
class PieceRequestSniffer(Thread):
def __init__(self, dev='eth0'):
Thread.__init__(self)
self.expr = 'udp or tcp'
self.maxlen = 65535 # max size of packet to capture
self.promiscuous = 1 # promiscuous mode?
self.read_timeout = 100 # in milliseconds
self.max_pkts = -1 # number of packets to capture; -1 => no limit
self.active = True
self.p = pcapy.open_live(dev, self.maxlen, self.promiscuous, self.read_timeout)
self.p.setfilter(self.expr)
#staticmethod
def cb(hdr, data):
eth = dpkt.ethernet.Ethernet(str(data))
ip = eth.data
#Select Ipv4 packets because of problem with the .p in Ipv6
if eth.type == dpkt.ethernet.ETH_TYPE_IP6:
return
else:
#Select only TCP protocols
if ip.p == dpkt.ip.IP_PROTO_TCP:
tcp = ip.data
try:
#Return hexadecimal representation
hex_data = binascii.hexlify(tcp.data)
except:
return
fin_flag = ( tcp.flags & dpkt.tcp.TH_FIN ) != 0
if fin_flag:
print " -------------------FIN filtered-------------------"
src_ip = ip.src
dst_ip = ip.dst
#listip.append(theip)
print "\n"
print "src_ip %s %s dst_ip %s" % (src_ip,"\n", dst_ip)
#for element in zip(str(listip),str(thedata)):
#print(element)
def stop(self):
#logging.info('Piece Request Sniffer stopped...')
self.active = False
def run(self):
while self.active:
self.p.dispatch(0, PieceRequestSniffer.cb)
sniffer = PieceRequestSniffer()
sniffer.start()
First, import socket, then use:
src_ip = socket.inet_ntoa(ip.src)
dst_ip = socket.inet_ntoa(ip.dst)
This question already has answers here:
Importing installed package from script with the same name raises "AttributeError: module has no attribute" or an ImportError or NameError
(2 answers)
Closed 7 months ago.
I am trying to run this simple Python WebSocket, with a couple very minor changes. I am running Python 2.4.3 because I cannot use an newer version, but I'm not sure how much that matters.
Here is the error I'm getting:
Traceback (most recent call last):
File "socket.py", line 258, in ?
server = WebSocketServer("localhost", 8000, WebSocket)
File "socket.py", line 205, in __init__
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
AttributeError: 'module' object has no attribute 'AF_INET'
And here is my code:
import time
import struct
import socket
import base64
import sys
from select import select
import re
import logging
from threading import Thread
import signal
# Simple WebSocket server implementation. Handshakes with the client then echos back everything
# that is received. Has no dependencies (doesn't require Twisted etc) and works with the RFC6455
# version of WebSockets. Tested with FireFox 16, though should work with the latest versions of
# IE, Chrome etc.
#
# rich20b#gmail.com
# Adapted from https://gist.github.com/512987 with various functions stolen from other sites, see
# below for full details.
# Constants
MAGICGUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
TEXT = 0x01
BINARY = 0x02
# WebSocket implementation
class WebSocket(object):
handshake = (
"HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
"Upgrade: WebSocket\r\n"
"Connection: Upgrade\r\n"
"Sec-WebSocket-Accept: %(acceptstring)s\r\n"
"Server: TestTest\r\n"
"Access-Control-Allow-Origin: http://localhost\r\n"
"Access-Control-Allow-Credentials: true\r\n"
"\r\n"
)
# Constructor
def __init__(self, client, server):
self.client = client
self.server = server
self.handshaken = False
self.header = ""
self.data = ""
# Serve this client
def feed(self, data):
# If we haven't handshaken yet
if not self.handshaken:
logging.debug("No handshake yet")
self.header += data
if self.header.find('\r\n\r\n') != -1:
parts = self.header.split('\r\n\r\n', 1)
self.header = parts[0]
if self.dohandshake(self.header, parts[1]):
logging.info("Handshake successful")
self.handshaken = True
# We have handshaken
else:
logging.debug("Handshake is complete")
# Decode the data that we received according to section 5 of RFC6455
recv = self.decodeCharArray(data)
# Send our reply
self.sendMessage(''.join(recv).strip());
# Stolen from http://www.cs.rpi.edu/~goldsd/docs/spring2012-csci4220/websocket-py.txt
def sendMessage(self, s):
"""
Encode and send a WebSocket message
"""
# Empty message to start with
message = ""
# always send an entire message as one frame (fin)
b1 = 0x80
# in Python 2, strs are bytes and unicodes are strings
if type(s) == unicode:
b1 |= TEXT
payload = s.encode("UTF8")
elif type(s) == str:
b1 |= TEXT
payload = s
# Append 'FIN' flag to the message
message += chr(b1)
# never mask frames from the server to the client
b2 = 0
# How long is our payload?
length = len(payload)
if length < 126:
b2 |= length
message += chr(b2)
elif length < (2 ** 16) - 1:
b2 |= 126
message += chr(b2)
l = struct.pack(">H", length)
message += l
else:
l = struct.pack(">Q", length)
b2 |= 127
message += chr(b2)
message += l
# Append payload to message
message += payload
# Send to the client
self.client.send(str(message))
# Stolen from http://stackoverflow.com/questions/8125507/how-can-i-send-and-receive-websocket-messages-on-the-server-side
def decodeCharArray(self, stringStreamIn):
# Turn string values into opererable numeric byte values
byteArray = [ord(character) for character in stringStreamIn]
datalength = byteArray[1] & 127
indexFirstMask = 2
if datalength == 126:
indexFirstMask = 4
elif datalength == 127:
indexFirstMask = 10
# Extract masks
masks = [m for m in byteArray[indexFirstMask : indexFirstMask+4]]
indexFirstDataByte = indexFirstMask + 4
# List of decoded characters
decodedChars = []
i = indexFirstDataByte
j = 0
# Loop through each byte that was received
while i < len(byteArray):
# Unmask this byte and add to the decoded buffer
decodedChars.append( chr(byteArray[i] ^ masks[j % 4]) )
i += 1
j += 1
# Return the decoded string
return decodedChars
# Handshake with this client
def dohandshake(self, header, key=None):
logging.debug("Begin handshake: %s" % header)
# Get the handshake template
handshake = self.handshake
# Step through each header
for line in header.split('\r\n')[1:]:
name, value = line.split(': ', 1)
# If this is the key
if name.lower() == "sec-websocket-key":
# Append the standard GUID and get digest
combined = value + MAGICGUID
response = base64.b64encode(combined.digest())
# Replace the placeholder in the handshake response
handshake = handshake % { 'acceptstring' : response }
logging.debug("Sending handshake %s" % handshake)
self.client.send(handshake)
return True
def onmessage(self, data):
#logging.info("Got message: %s" % data)
self.send(data)
def send(self, data):
logging.info("Sent message: %s" % data)
self.client.send("\x00%s\xff" % data)
def close(self):
self.client.close()
# WebSocket server implementation
class WebSocketServer(object):
# Constructor
def __init__(self, bind, port, cls):
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.socket.bind((bind, port))
self.bind = bind
self.port = port
self.cls = cls
self.connections = {}
self.listeners = [self.socket]
# Listen for requests
def listen(self, backlog=5):
self.socket.listen(backlog)
logging.info("Listening on %s" % self.port)
# Keep serving requests
self.running = True
while self.running:
# Find clients that need servicing
rList, wList, xList = select(self.listeners, [], self.listeners, 1)
for ready in rList:
if ready == self.socket:
logging.debug("New client connection")
client, address = self.socket.accept()
fileno = client.fileno()
self.listeners.append(fileno)
self.connections[fileno] = self.cls(client, self)
else:
logging.debug("Client ready for reading %s" % ready)
client = self.connections[ready].client
data = client.recv(4096)
fileno = client.fileno()
if data:
self.connections[fileno].feed(data)
else:
logging.debug("Closing client %s" % ready)
self.connections[fileno].close()
del self.connections[fileno]
self.listeners.remove(ready)
# Step though and delete broken connections
for failed in xList:
if failed == self.socket:
logging.error("Socket broke")
for fileno, conn in self.connections:
conn.close()
self.running = False
# Entry point
if __name__ == "__main__":
logging.basicConfig(level=logging.DEBUG, format="%(asctime)s - %(levelname)s - %(message)s")
server = WebSocketServer("localhost", 8000, WebSocket)
server_thread = Thread(target=server.listen, args=[5])
server_thread.start()
# Add SIGINT handler for killing the threads
def signal_handler(signal, frame):
logging.info("Caught Ctrl+C, shutting down...")
server.running = False
sys.exit()
signal.signal(signal.SIGINT, signal_handler)
while True:
time.sleep(100)
It appears that you've named your own file socket.py, so when you import socket, you're not getting the system library (it's just re-importing the file you're currently in - which has no AF_INET symbol). Try renaming your file something like mysocket.py.
Even after changing the file name, if you are running the python from the terminal.
(you may get the same error)
Kindly
rm -rf socket.pyc
(previously compiled bytecode)
I had the same problem, I was literally stuck here for hours, tried re installing it a million times, but found the solution.
1) Make sure the file name is not socket.py,
2) Change the directory, it will not work in the home directory due to some permission issues.
If you have by anychance saved the file as socket.py, do not copy the same file or rename it to something else, the problem will persist.
What I advice you to do is, open a new folder in a different directory, write a simple socket code which involved AF_INET. Try to run it. It should work.
Issue can be that you have a file or Cache name socket.py or socket.pyc
rm -rf socket.py
rm -rf socket.pyc
Hopefully this will resolve your import issue. Gud Luck
enter the current working directory
and remove the files named 'socket.py' and 'socket.pyc'
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?
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!'
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))