IP Fragmentation & MTU How IP Splits Large Packets and Why It Matters

 

IP Fragmentation & MTU
Chapter 58 โ€” Part 3: How IP Splits Large Packets and Why It Matters
๐Ÿ“š Theory
๐Ÿ’ป Code Examples
โ“ Interview Q&A

Your application might want to send a 50KB message in one go. But the physical network cannot carry arbitrary-sized packets โ€” it has a size limit called the MTU. When an IP datagram is too big, IP fragments it into smaller pieces. This page explains how that works, why it can be a problem, and how modern TCP avoids it.

Key Concepts
MTU Path MTU IP Fragmentation Fragment Offset Reassembly Path MTU Discovery Jumbogram DF Bit ICMP

๐Ÿ“ˆ What is MTU (Maximum Transmission Unit)?

The MTU is the maximum size (in bytes) of a single data frame that a data link layer can carry. Think of it as the size of a truck. You can only load as much as the truck can carry in one trip.

MTU on Common Network Types
Ethernet (LAN)
1,500 bytes
WiFi (802.11)
2,304 bytes
PPPoE (DSL)
1,492 bytes
Loopback (lo)
65,536 bytes
Max IPv4 datagram
65,535 bytes

The Ethernet MTU (1,500 bytes) is by far the most important one in practice because most internet traffic passes through Ethernet at some point. This means IP datagrams larger than 1,500 bytes will need to be fragmented.

The Path MTU is the minimum MTU across all network links between source and destination. A packet traveling from your laptop through WiFi (2304 MTU) then through Ethernet (1500 MTU) has a Path MTU of 1500 โ€” the smallest link in the chain.

โœ‚๏ธ How IP Fragmentation Works

When an IP datagram is larger than the MTU, the IP layer at the sender (or at a router) splits it into smaller fragments. Each fragment is itself a valid IP datagram with its own IP header. The fragments are reassembled only at the final destination โ€” not at intermediate routers.

Original IP Datagram (4000 bytes, larger than 1500 MTU)
IP Header
(20 bytes)
Data: 3980 bytes (too big for 1500 MTU)
โ†“ IP Fragmentation (MTU = 1500 bytes)

Fragment 1
IP Header
(offset=0,
MF=1)
Data bytes 0โ€“1479 (1480 bytes of payload)
Total: 1500 bytes โœ“

Fragment 2
IP Header
(offset=185,
MF=1)
Data bytes 1480โ€“2959 (1480 bytes of payload)
Total: 1500 bytes โœ“

Fragment 3 (last)
IP Header
(offset=370,
MF=0)
Data bytes 2960โ€“3979 (1020 bytes of payload)
Total: 1040 bytes โœ“
โ†“ All fragments arrive at destination โ†“ Reassembled back into original 4000-byte datagram

Key fields in the IP header used for fragmentation:

  • Fragment Offset: tells the receiver where in the original datagram this fragment belongs (measured in 8-byte units)
  • More Fragments (MF) flag: set to 1 on all fragments except the last one
  • Identification field: all fragments of the same original datagram share the same ID, so the receiver knows which fragments belong together

โš ๏ธ Why Fragmentation is Generally Undesirable

IP fragmentation happens transparently โ€” the application and TCP/UDP do not even know it happened. But it has a serious problem:

๐Ÿšจ The Fatal Flaw of Fragmentation

IP does not retransmit. A datagram can be reassembled at the destination only if ALL fragments arrive.

If even one fragment is lost in transit, the entire original datagram is useless and must be discarded. There is no way to request just the missing fragment.

The consequences depend on which transport protocol is used:

With UDP (no retransmission)

If any fragment is lost, the entire UDP datagram is dropped. The application never receives it. UDP has no way to recover. This leads to significant data loss rates on networks prone to packet loss.

With TCP (has retransmission)

TCP detects the loss (via timeout or duplicate ACK) and retransmits the data. But fragmentation still causes degraded throughput because the entire TCP segment must be retransmitted, not just the lost fragment.

๐Ÿ”Ž Path MTU Discovery โ€” How TCP Avoids Fragmentation

Modern TCP implementations use Path MTU Discovery (PMTUD) to figure out the Path MTU before sending large segments, so that IP is never asked to fragment.

Step 1: TCP sends a large segment with DF (Don’t Fragment) bit set in IP header
The DF bit tells routers: “Do not fragment this datagram. If it is too big, drop it and tell the sender.”
Step 2: A router on the path finds it too big for its MTU
The router drops the datagram and sends back an ICMP “Fragmentation Needed” message to the sender, including the MTU of the link that was too small.
Step 3: TCP reduces its segment size
TCP receives the ICMP message, learns the bottleneck MTU, and reduces its Maximum Segment Size (MSS) accordingly. Future segments are small enough to pass through without fragmentation.

UDP has no PMTUD mechanism. UDP-based applications must either keep their messages small (under 512 bytes is a common safe choice) or implement their own fragmentation handling at the application level.

๐Ÿ“Œ IPv6 and Fragmentation: IPv6 routers do NOT fragment โ€” they only perform Path MTU Discovery. Fragmentation in IPv6 can only be done by the source host (using a special Fragment Extension Header). This is another reason IPv6 is more efficient at the router level.

๐Ÿ’ป Code: Discovering Path MTU with IP_MTU_DISCOVER

Linux lets a socket enable Path MTU Discovery explicitly using IP_MTU_DISCOVER socket option. After connecting, you can query the discovered Path MTU with IP_MTU.

/* path_mtu_discover.c
 * Enables Path MTU Discovery on a TCP socket and
 * reads the discovered Path MTU after connecting.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>

int main(int argc, char *argv[])
{
    if (argc != 3) {
        fprintf(stderr, "Usage: %s <ip> <port>\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1) { perror("socket"); exit(EXIT_FAILURE); }

    /*
     * IP_MTU_DISCOVER controls PMTUD behavior:
     *   IP_PMTUDISC_DO   = always set DF bit, perform PMTUD
     *   IP_PMTUDISC_DONT = never set DF bit, allow fragmentation
     *   IP_PMTUDISC_WANT = set DF bit only if PMTUD is globally enabled
     */
    int pmtud = IP_PMTUDISC_DO;
    if (setsockopt(sockfd, IPPROTO_IP, IP_MTU_DISCOVER,
                   &pmtud, sizeof(pmtud)) == -1) {
        perror("setsockopt IP_MTU_DISCOVER");
        close(sockfd);
        exit(EXIT_FAILURE);
    }
    printf("Path MTU Discovery enabled (DF bit will be set)\n");

    /* Connect to remote host */
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port   = htons(atoi(argv[2]));
    if (inet_pton(AF_INET, argv[1], &addr.sin_addr) != 1) {
        fprintf(stderr, "Invalid IP address\n");
        exit(EXIT_FAILURE);
    }

    if (connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
        perror("connect");
        close(sockfd);
        exit(EXIT_FAILURE);
    }
    printf("Connected to %s:%s\n", argv[1], argv[2]);

    /*
     * After connecting, query the kernel for the discovered Path MTU.
     * This is the maximum segment size we can use without triggering
     * IP fragmentation on this path.
     */
    int path_mtu;
    socklen_t optlen = sizeof(path_mtu);
    if (getsockopt(sockfd, IPPROTO_IP, IP_MTU, &path_mtu, &optlen) == -1) {
        perror("getsockopt IP_MTU");
    } else {
        printf("Discovered Path MTU: %d bytes\n", path_mtu);
        printf("Max safe payload per TCP segment: %d bytes\n",
               path_mtu - 40); /* 20 IP header + 20 TCP header */
    }

    close(sockfd);
    return 0;
}
/* Compile and run:
   gcc -o path_mtu path_mtu_discover.c
   ./path_mtu 93.184.216.34 80   (example.com port 80)

   Expected output (Ethernet path):
   Path MTU Discovery enabled (DF bit will be set)
   Connected to 93.184.216.34:80
   Discovered Path MTU: 1500 bytes
   Max safe payload per TCP segment: 1460 bytes
*/

For UDP, the best practice to avoid fragmentation is to keep datagrams small. The safe limit for UDP payload is typically 512 bytes (DNS uses this). If you need larger messages, consider implementing application-level segmentation.

/* udp_size_check.c
 * Demonstrates checking if a UDP payload exceeds safe MTU limits.
 * UDP has no PMTUD, so the application must be careful.
 */
#include <stdio.h>
#include <string.h>

#define ETHERNET_MTU        1500
#define IP_HEADER_SIZE      20
#define UDP_HEADER_SIZE     8
#define MAX_SAFE_UDP_PAYLOAD (ETHERNET_MTU - IP_HEADER_SIZE - UDP_HEADER_SIZE)
/* MAX_SAFE_UDP_PAYLOAD = 1500 - 20 - 8 = 1472 bytes */

#define CONSERVATIVE_UDP_LIMIT  512   /* used by DNS, avoids fragmentation risk */

void check_udp_payload_size(size_t payload_size)
{
    printf("Payload size: %zu bytes\n", payload_size);

    if (payload_size <= CONSERVATIVE_UDP_LIMIT) {
        printf("Status: SAFE โ€” below conservative 512-byte limit\n");
    } else if (payload_size <= MAX_SAFE_UDP_PAYLOAD) {
        printf("Status: OK for Ethernet โ€” but may fragment on smaller MTU paths\n");
    } else {
        printf("Status: WARNING โ€” exceeds Ethernet MTU, WILL cause IP fragmentation!\n");
        printf("Consider splitting into multiple UDP datagrams of <= %d bytes each\n",
               MAX_SAFE_UDP_PAYLOAD);
    }
    printf("\n");
}

int main(void)
{
    check_udp_payload_size(200);    /* DNS query โ€” safe */
    check_udp_payload_size(1000);   /* OK for Ethernet, risky on some paths */
    check_udp_payload_size(2000);   /* Will cause fragmentation */
    check_udp_payload_size(65000);  /* Definitely fragmented */
    return 0;
}

๐Ÿ“‹ IP Datagram Size Limits โ€” Summary Table
Parameter IPv4 IPv6
Max datagram size 65,535 bytes 65,575 bytes (40B header + 65,535B data)
Jumbogram support No Yes (optional extension)
Min reassembly buffer 576 bytes 1,500 bytes
Typical Ethernet MTU 1,500 bytes (both IPv4 and IPv6)
Who can fragment? Sender or routers Source host only (routers cannot fragment)
Typical TCP MSS 1,460 bytes (1,500 MTU โˆ’ 20 IP hdr โˆ’ 20 TCP hdr)

โ“ Interview Questions & Answers
Q1: What is MTU and what is Path MTU?
A: MTU (Maximum Transmission Unit) is the largest frame size a single data link can carry. Ethernet MTU is typically 1,500 bytes. Path MTU is the minimum MTU across all links on the entire path from source to destination. If any link in the path has a smaller MTU, that becomes the limiting factor.
Q2: What happens when an IP datagram is larger than the MTU?
A: IP fragments it into smaller pieces, each no larger than the MTU. Each fragment has its own IP header with a Fragment Offset field indicating where it belongs in the original datagram, a More Fragments (MF) flag, and a shared Identification value. The destination reassembles all fragments into the original datagram.
Q3: Why is IP fragmentation considered undesirable?
A: Because if even one fragment is lost, the entire original datagram is unusable. IP does not perform retransmission, so the loss is permanent at the IP level. With UDP (no retransmission), this means data loss. With TCP, the entire segment must be retransmitted, reducing throughput.
Q4: What is Path MTU Discovery and how does TCP use it?
A: PMTUD is a technique where the sender sets the DF (Don’t Fragment) bit in IP headers. If a router cannot forward the datagram because it is too large for its outgoing link MTU, the router drops the datagram and sends an ICMP “Fragmentation Needed” message back with the link’s MTU. TCP receives this, reduces its MSS, and retransmits. This way, TCP never asks IP to fragment.
Q5: Does UDP do Path MTU Discovery?
A: No. UDP has no built-in PMTUD mechanism. UDP-based applications must either keep messages small (traditionally below 512 bytes) or implement their own message splitting. QUIC (a modern UDP-based transport protocol) does implement PMTUD.
Q6: What is the DF (Don’t Fragment) bit and what happens when it is set?
A: The DF bit is a flag in the IPv4 header (bit 1 of the Flags field). When set, it instructs routers NOT to fragment this datagram. If a router receives a DF-flagged datagram that is too large for the outgoing link, it must drop the datagram and send an ICMP Type 3 Code 4 “Fragmentation Needed and DF Set” message back to the sender. PMTUD relies on this mechanism.
Q7: What is the typical TCP Maximum Segment Size (MSS) on an Ethernet network?
A: 1,460 bytes. This is calculated as: Ethernet MTU (1500) minus IP header (20 bytes) minus TCP header (20 bytes) = 1,460 bytes of application payload per TCP segment.
Q8: How does IPv6 handle fragmentation differently from IPv4?
A: In IPv6, routers are not allowed to fragment datagrams. Only the source host can fragment (using a Fragment Extension Header). IPv6 routers that receive a too-large datagram drop it and send an ICMPv6 “Packet Too Big” message back to the sender. This makes routers simpler and faster, and pushes fragmentation responsibility to the endpoints.

Leave a Reply

Your email address will not be published. Required fields are marked *