1

How can I find the maximum length of a UDP payload in Python (Python 2), preferably platform-independent?

Specifically, I want to avoid [Errno 90] Message too long AKA errno.EMSGSIZE.

Background

What I am not asking

Demo Code

To see the error in action:

import socket

msg_len = 65537  # Not even possible!

ip_address = "127.0.0.1"
port = 5005
msg = "A" * msg_len

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.sendto(msg, (ip_address, port))
Community
  • 1
  • 1
jtpereyda
  • 6,987
  • 10
  • 51
  • 80
  • 1
    Related: http://stackoverflow.com/questions/25841/maximum-buffer-length-for-sendto – Robᵩ Oct 13 '16 at 22:58
  • I don't think there's a portable way to get this information. It's not a feature of the socket API. – Barmar Oct 13 '16 at 22:59

2 Answers2

4

Some of your premises need a minor correction.

As a result of the IP header containing a 16 bit field for length, the largest size an IPv4 message can be is 65535 bytes And that includes the IP header itself.

The IP packet itself has at least a 20 byte header. Hence 65535 - 20 == 65515 is the largest size of the payload of an IP message. The payload can be a UDP datagram.

The UDP datagram itself is typically an 8 byte header. Hence 65515 - 8 == 65507. So even if the UDP header could theoretically contain an amount higher than 65507 in its own length field, the IPv4 message can't contain it.

But if your system adds any more headers to the IP header (option fields via socket ioctls or whatever), then the limit of the UDP application payload will get reduced by a corresponding amount.

In practice, any IP message above the MTU size of your network adapter (~1500 bytes), will trigger the UDP packet to undergo IP fragmentation. So if your ethernet card has a 1500 byte message size, a a UDP datagram containing 65507 bytes of application data will get fragmented into approximately 43 separate ethernet frames. Each frame is a fragmented IP packet containing a subset of the UDP bytes, but with a seperate header. When all the IP fragments are received on the remote end, it logically gets delivered to the application as 65507 byte datagram. Fragmentation is transparent to applications.

I would suggest running your code with Wireshark and send to a real IP address out of the network. You can observe and study how IP fragmentation works.

selbie
  • 100,020
  • 15
  • 103
  • 173
  • 1
    "_Fragmentation is transparent to applications._" That is true, but fragmentation and reassembly take time. Also, many routers and most firewalls will only allow the first fragment through because fragmentation is used in attacks. If any one fragment of the whole is missing, the entire will be discarded. Fragmentation is really no longer practical. – Ron Maupin Oct 14 '16 at 02:28
  • Ron, I was about to write up similar details about the reliability issues of IP fragmentation and why it's not a good idea to overload too much data into the UDP field. It's understandable that a modern router will limit the number of fragments, but I would be really surprised if it limited it to just 1. (But hey, I learn something new every day). – selbie Oct 14 '16 at 02:35
  • I was more writing about the destination router or firewall. Most firewalls by default just no longer allow fragments. Business typically have the same thing configured on routers. I'm always amazed that people want to load and unreliable protocol with as much data as possible, knowing that losing a single packet will lose more data. – Ron Maupin Oct 14 '16 at 02:38
  • Thanks Ron. I'm actually curious to test this since I maintain [Stuntman](http://www.stunprotocol.org). But I have yet to implement the PADDING attribute support for [fragment handling detection](https://tools.ietf.org/html/rfc5780#section-3.5) because I never saw a need for it (and some security concerns for public servers). It would be interesting to test some of the various networks I'm on. – selbie Oct 14 '16 at 03:07
3

Well, there's always the try-it-and-see approach... I wouldn't call this elegant, but it is platform-independent:

import socket

def canSendUDPPacketOfSize(sock, packetSize):
   ip_address = "127.0.0.1"
   port = 5005
   try:
      msg = "A" * packetSize
      if (sock.sendto(msg, (ip_address, port)) == len(msg)):
         return True
   except:
      pass
   return False

def get_max_udp_packet_size_aux(sock, largestKnownGoodSize, smallestKnownBadSize):
   if ((largestKnownGoodSize+1) == smallestKnownBadSize):
      return largestKnownGoodSize
   else:
      newMidSize = int((largestKnownGoodSize+smallestKnownBadSize)/2)
      if (canSendUDPPacketOfSize(sock, newMidSize)):
         return get_max_udp_packet_size_aux(sock, newMidSize, smallestKnownBadSize)
      else:
         return get_max_udp_packet_size_aux(sock, largestKnownGoodSize, newMidSize)

def get_max_udp_packet_size():
   sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
   ret = get_max_udp_packet_size_aux(sock, 0, 65508)
   sock.close()
   return ret

print "Maximum UDP packet send size is", get_max_udp_packet_size()
Jeremy Friesner
  • 70,199
  • 15
  • 131
  • 234