I am working on an application where I need to send large data in multiple UDP packets to a client, how can I determine programmatically the MTU for my UDP socket?
I need to be able to do this on both windows and linux.
I am working on an application where I need to send large data in multiple UDP packets to a client, how can I determine programmatically the MTU for my UDP socket?
I need to be able to do this on both windows and linux.
Here are my two functions from my eventdispatcher library (look at the udp_base.cpp for more details).
The first one returns the actual MTU size. Often 1500 for inter-computer communication. It can be much larger on locahost (about 64K).
The problem of the MTU size is that it includes data other than yours. In other words, the size available to you is not the MTU. Instead you have to find out what the MSS is. That's the second function below.
So far so good, it works under Linux, I would imagine that MS-Windows may have similar calls, just a different API.
/** \brief Retrieve the size of the MTU on that connection.
*
* Linux offers a ioctl() function to retrieve the MTU's size. This
* function uses that and returns the result. If the call fails,
* then the function returns -1.
*
* The function returns the MTU's size of the socket on this side.
* If you want to communicate effectively with another system, you
* want to also ask about the MTU on the other side of the socket.
*
* \note
* MTU stands for Maximum Transmission Unit.
*
* \note
* PMTUD stands for Path Maximum Transmission Unit Discovery.
*
* \note
* PLPMTU stands for Packetization Layer Path Maximum Transmission Unit
* Discovery.
*
* \todo
* We need to support the possibly dynamically changing MTU size
* that the Internet may generate (or even a LAN if you let people
* tweak their MTU "randomly".) This is done by preventing
* defragmentation (see IP_NODEFRAG in `man 7 ip`) and also by
* asking for MTU size discovery (IP_MTU_DISCOVER). The size
* discovery changes over time as devices on the MTU path (the
* route taken by the packets) changes over time. The idea is
* to find the smallest MTU size of the MTU path and use that
* to send packets of that size at the most. Note that packets
* are otherwise automatically broken in smaller chunks and
* rebuilt on the other side, but that is not efficient if you
* expect to lose quite a few packets. The limit for chunked
* packets is a little under 64Kb.
*
* \note
* errno is either EBADF or set by ioctl().
*
* \sa
* See `man 7 netdevice`
*
* \return -1 if the MTU could not be retrieved, the MTU's size otherwise.
*/
int udp_base::get_mtu_size() const
{
if(f_socket != nullptr
&& f_mtu_size == 0)
{
addr::addr a;
switch(f_addrinfo->ai_family)
{
case AF_INET:
a.set_ipv4(*reinterpret_cast<struct sockaddr_in *>(f_addrinfo->ai_addr));
break;
case AF_INET6:
a.set_ipv6(*reinterpret_cast<struct sockaddr_in6 *>(f_addrinfo->ai_addr));
break;
default:
f_mtu_size = -1;
errno = EBADF;
break;
}
if(f_mtu_size == 0)
{
std::string iface_name;
addr::iface::pointer_t i(find_addr_interface(a));
if(i != nullptr)
{
iface_name = i->get_name();
}
if(iface_name.empty())
{
f_mtu_size = -1;
errno = EBADF;
}
else
{
ifreq ifr;
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, iface_name.c_str(), sizeof(ifr.ifr_name));
if(ioctl(f_socket.get(), SIOCGIFMTU, &ifr) == 0)
{
f_mtu_size = ifr.ifr_mtu;
}
else
{
f_mtu_size = -1;
// errno -- defined by ioctl()
}
}
}
}
return f_mtu_size;
}
/** \brief Determine the size of the data buffer we can use.
*
* This function gets the MTU of the connection (i.e. not the PMTUD
* or PLPMTUD yet...) and subtract the space necessary for the IP and
* UDP headers. This is called the Maximum Segment Size (MSS).
*
* \todo
* If the IP address (in f_addr) is an IPv6, then we need to switch to
* the corresponding IPv6 subtractions.
*
* \todo
* Look into the the IP options because some options add to the size
* of the IP header. It's incredible that we have to take care of that
* on our end!
*
* \todo
* For congetion control, read more as described on ietf.org:
* https://tools.ietf.org/html/rfc8085
*
* \todo
* The sizes that will always work (as long as all the components of the
* path are working as per the UDP RFC) are (1) for IPv4, 576 bytes, and
* (2) for IPv6, 1280 bytes. This size is called EMTU_S which stands for
* "Effective Maximum Transmission Unit for Sending."
*
* \return The size of the MMU, which is the MTU minus IP and UDP headers.
*/
int udp_base::get_mss_size() const
{
// where these structures are defined
//
// ether_header -- /usr/include/net/ethernet.h
// iphdr -- /usr/include/netinet/ip.h
// udphdr -- /usr/include/netinet/udp.h
//
int const mtu(get_mtu_size()
//- sizeof(ether_header) // WARNING: this is for IPv4 only -- this is "transparent" to the MTU (i.e. it wraps the 1,500 bytes)
//- ETHER_CRC_LEN // this is the CRC for the ethernet which appears at the end of the packet
- sizeof(iphdr) // WARNING: this is for IPv4 only
//- ... // the IP protocol accepts options!
- sizeof(udphdr)
);
return mtu <= 0 ? -1 : mtu;
}
You could set the "Don't Fragment" Flag in the IP Header. You then may get an icmp response about needed Fragmentation.
The most common way to discover the best MTU is with the command "ping"
ping -M do -s MTU_N www.google.com # Linux
ping www.google.com -f -l MTU_N # Windows
Where MTU_N is the MTU you wanna test. That way you could iterate MTU_N from 500 to 1900 until you get the best possible speed.
But the best MTU will depend of several things:
So you will need to re-test every time any of this factors change.
Here are my two functions from my eventdispatcher library (look at the udp_base.cpp for more details).
The first one returns the actual MTU size. Often 1500 for inter-computer communication. It can be much larger on locahost (about 64K).
The problem of the MTU size is that it includes data other than yours. In other words, the size available to you is not the MTU. Instead you have to find out what the MSS is. That's the second function below.
So far so good, it works under Linux, I would imagine that MS-Windows may have similar calls, just a different API.
/** \brief Retrieve the size of the MTU on that connection.
*
* Linux offers a ioctl() function to retrieve the MTU's size. This
* function uses that and returns the result. If the call fails,
* then the function returns -1.
*
* The function returns the MTU's size of the socket on this side.
* If you want to communicate effectively with another system, you
* want to also ask about the MTU on the other side of the socket.
*
* \note
* MTU stands for Maximum Transmission Unit.
*
* \note
* PMTUD stands for Path Maximum Transmission Unit Discovery.
*
* \note
* PLPMTU stands for Packetization Layer Path Maximum Transmission Unit
* Discovery.
*
* \todo
* We need to support the possibly dynamically changing MTU size
* that the Internet may generate (or even a LAN if you let people
* tweak their MTU "randomly".) This is done by preventing
* defragmentation (see IP_NODEFRAG in `man 7 ip`) and also by
* asking for MTU size discovery (IP_MTU_DISCOVER). The size
* discovery changes over time as devices on the MTU path (the
* route taken by the packets) changes over time. The idea is
* to find the smallest MTU size of the MTU path and use that
* to send packets of that size at the most. Note that packets
* are otherwise automatically broken in smaller chunks and
* rebuilt on the other side, but that is not efficient if you
* expect to lose quite a few packets. The limit for chunked
* packets is a little under 64Kb.
*
* \note
* errno is either EBADF or set by ioctl().
*
* \sa
* See `man 7 netdevice`
*
* \return -1 if the MTU could not be retrieved, the MTU's size otherwise.
*/
int udp_base::get_mtu_size() const
{
if(f_socket != nullptr
&& f_mtu_size == 0)
{
addr::addr a;
switch(f_addrinfo->ai_family)
{
case AF_INET:
a.set_ipv4(*reinterpret_cast<struct sockaddr_in *>(f_addrinfo->ai_addr));
break;
case AF_INET6:
a.set_ipv6(*reinterpret_cast<struct sockaddr_in6 *>(f_addrinfo->ai_addr));
break;
default:
f_mtu_size = -1;
errno = EBADF;
break;
}
if(f_mtu_size == 0)
{
std::string iface_name;
addr::iface::pointer_t i(find_addr_interface(a));
if(i != nullptr)
{
iface_name = i->get_name();
}
if(iface_name.empty())
{
f_mtu_size = -1;
errno = EBADF;
}
else
{
ifreq ifr;
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, iface_name.c_str(), sizeof(ifr.ifr_name));
if(ioctl(f_socket.get(), SIOCGIFMTU, &ifr) == 0)
{
f_mtu_size = ifr.ifr_mtu;
}
else
{
f_mtu_size = -1;
// errno -- defined by ioctl()
}
}
}
}
return f_mtu_size;
}
/** \brief Determine the size of the data buffer we can use.
*
* This function gets the MTU of the connection (i.e. not the PMTUD
* or PLPMTUD yet...) and subtract the space necessary for the IP and
* UDP headers. This is called the Maximum Segment Size (MSS).
*
* \todo
* If the IP address (in f_addr) is an IPv6, then we need to switch to
* the corresponding IPv6 subtractions.
*
* \todo
* Look into the the IP options because some options add to the size
* of the IP header. It's incredible that we have to take care of that
* on our end!
*
* \todo
* For congetion control, read more as described on ietf.org:
* https://tools.ietf.org/html/rfc8085
*
* \todo
* The sizes that will always work (as long as all the components of the
* path are working as per the UDP RFC) are (1) for IPv4, 576 bytes, and
* (2) for IPv6, 1280 bytes. This size is called EMTU_S which stands for
* "Effective Maximum Transmission Unit for Sending."
*
* \return The size of the MMU, which is the MTU minus IP and UDP headers.
*/
int udp_base::get_mss_size() const
{
// where these structures are defined
//
// ether_header -- /usr/include/net/ethernet.h
// iphdr -- /usr/include/netinet/ip.h
// udphdr -- /usr/include/netinet/udp.h
//
int const mtu(get_mtu_size()
//- sizeof(ether_header) // WARNING: this is for IPv4 only -- this is "transparent" to the MTU (i.e. it wraps the 1,500 bytes)
//- ETHER_CRC_LEN // this is the CRC for the ethernet which appears at the end of the packet
- sizeof(iphdr) // WARNING: this is for IPv4 only
//- ... // the IP protocol accepts options!
- sizeof(udphdr)
);
return mtu <= 0 ? -1 : mtu;
}
© 2022 - 2024 — McMap. All rights reserved.