Part 4: UDP User Datagram Protocol — Connectionless, Unreliable & Fast

 

Chapter 58 – Part 4: UDP
User Datagram Protocol — Connectionless, Unreliable & Fast
Topic
UDP Protocol
Level
Intermediate
Part
4 of 5

What is UDP?

UDP (User Datagram Protocol) is the simpler of the two main transport protocols in TCP/IP. It sits on top of IP and adds only two things: port numbers (to identify which application gets the data) and a checksum (to detect errors in transit). Everything else that TCP provides — connection setup, reliability, ordering, flow control — UDP does not provide.

Like IP, UDP is connectionless: each datagram is sent independently, with no prior handshake and no guarantee of delivery, ordering, or duplicate avoidance. Despite this, UDP is preferred for many applications because of its low overhead and speed.

Key Terms:

UDP SOCK_DGRAM Connectionless Unreliable Datagram Checksum MTU IP Fragmentation 576 bytes sendto() recvfrom()

1. What UDP Adds Over IP

IP already handles routing packets across networks. UDP adds just two features on top of IP:

IP Layer
✅ Routing across networks
✅ Best-effort delivery
❌ No port numbers
❌ No error detection
+ UDP Adds
✅ Port numbers
✅ Checksum (error detection)
❌ No connection setup
❌ No delivery guarantee
= UDP / IP
Fast, low-overhead
Good for: DNS, games,
video streaming, VoIP

Because UDP does so little, it is very fast and has very low latency. There is no connection to set up, no acknowledgments to wait for, and no retransmission of lost data. If reliability is needed, it must be implemented in the application layer.

2. UDP Header Structure

The UDP header is only 8 bytes long (very small compared to TCP’s 20+ byte header):

Source Port
(16 bits)
Destination Port
(16 bits)
Length
(16 bits — header + data)
Checksum
(16 bits)
Data (payload) — variable length
Total UDP header = 8 bytes. Minimum UDP payload = 0 bytes.

The checksum covers the UDP header and data. It detects corruption during transit. However, checksums are not infallible — busy internet servers see roughly one undetected transmission error every few days. Applications needing stronger guarantees should use SSL/TLS or implement their own error control.

3. IP Fragmentation & UDP — Choosing the Right Datagram Size

Every network link has a Maximum Transmission Unit (MTU) — the largest packet it can carry without splitting. Common Ethernet MTU is 1500 bytes. If a UDP datagram (including IP header) exceeds the MTU, the IP layer fragments it into multiple smaller packets.

Large UDP Datagram
3000 bytes total
(exceeds MTU of 1500)
IP splits into 2 fragments:
Fragment 1: 1500 bytes
Fragment 2: 1500+ bytes
If any fragment lost → whole datagram lost

Unlike TCP, UDP does not have mechanisms to avoid fragmentation. The problem with fragmentation is that if even one fragment is lost, the entire original datagram is lost — and UDP does not retransmit.

Safe UDP Datagram Size

To be safe and avoid IP fragmentation, UDP-based applications keep datagrams under the IPv4 minimum reassembly buffer size of 576 bytes:

IP Header
20 bytes
UDP Header
8 bytes
UDP Payload (your data)
548 bytes available
(Many apps use only 512 bytes to be safe)
Total: 576 bytes = IPv4 minimum reassembly buffer (guaranteed to work across all routers)

In practice, many UDP-based applications (like DNS) use a still more conservative limit of 512 bytes for the data portion to ensure no fragmentation occurs on any path.

4. Coding Example – UDP Echo Server & Client

UDP uses SOCK_DGRAM sockets with sendto() and recvfrom() instead of connect()/send()/recv():

UDP Echo Server

/* udp_echo_server.c */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define PORT    9000
#define BUFSIZE  512  /* Stay well under 576-byte safety limit */

int main() {
    int sockfd;
    char buf[BUFSIZE];
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_len = sizeof(client_addr);
    ssize_t n;

    /* SOCK_DGRAM = UDP socket */
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd == -1) { perror("socket"); return 1; }

    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family      = AF_INET;
    server_addr.sin_port        = htons(PORT);
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

    if (bind(sockfd, (struct sockaddr *)&server_addr,
             sizeof(server_addr)) == -1) {
        perror("bind"); return 1;
    }

    printf("UDP echo server ready on port %d\n", PORT);

    for (;;) {
        /*
         * recvfrom() blocks until a datagram arrives.
         * client_addr is filled with the sender's address.
         * No connection is needed — each datagram is independent.
         */
        n = recvfrom(sockfd, buf, BUFSIZE, 0,
                     (struct sockaddr *)&client_addr, &client_len);
        if (n == -1) { perror("recvfrom"); continue; }

        printf("Received %zd bytes from %s:%d\n",
               n,
               inet_ntoa(client_addr.sin_addr),
               ntohs(client_addr.sin_port));

        /* Echo the datagram back to the sender */
        sendto(sockfd, buf, n, 0,
               (struct sockaddr *)&client_addr, client_len);
    }

    close(sockfd);
    return 0;
}

UDP Echo Client

/* udp_echo_client.c */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define SERVER_IP  "127.0.0.1"
#define PORT       9000
#define BUFSIZE    512

int main() {
    int sockfd;
    char send_buf[] = "Hello, UDP Server!";
    char recv_buf[BUFSIZE];
    struct sockaddr_in server_addr;
    socklen_t server_len = sizeof(server_addr);
    ssize_t n;

    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd == -1) { perror("socket"); return 1; }

    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port   = htons(PORT);
    inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr);

    /*
     * No connect() needed for UDP.
     * sendto() specifies the destination each time.
     * The OS auto-assigns an ephemeral source port.
     */
    n = sendto(sockfd, send_buf, strlen(send_buf), 0,
               (struct sockaddr *)&server_addr, server_len);
    printf("Sent %zd bytes\n", n);

    /*
     * recvfrom() waits for any reply.
     * CAUTION: No guarantee the reply is from our server —
     * application must validate sender's address if security matters.
     */
    n = recvfrom(sockfd, recv_buf, BUFSIZE - 1, 0,
                 (struct sockaddr *)&server_addr, &server_len);
    if (n > 0) {
        recv_buf[n] = '\0';
        printf("Echo received: %s\n", recv_buf);
    }

    close(sockfd);
    return 0;
}

Key differences from TCP: No listen(), no accept(), no connect() (though connect() can be used with UDP to set a default destination). Each sendto() is a self-contained datagram.

5. When to Prefer UDP Over TCP
Scenario Why UDP Works Better Real-World Examples
Low latency matters more than reliability No handshake delay; no retransmit wait Online games, VoIP, live video streaming
Small, frequent queries No connection overhead; fits in one datagram DNS lookups, SNMP monitoring
Broadcast or multicast needed UDP supports one-to-many; TCP is one-to-one mDNS, DHCP, network discovery
App handles its own reliability Custom retransmit logic more efficient than TCP QUIC protocol, game state sync

Interview Questions – UDP
Q1. What two features does UDP add on top of IP?UDP adds port numbers (so the OS can identify which application gets the datagram) and a checksum (so errors in the header and data can be detected). Everything else — connection setup, reliability, ordering, flow control, congestion control — is absent in UDP. This is why UDP is sometimes called “a thin wrapper around IP.”

Q2. What does “connectionless” mean in the context of UDP?Connectionless means that no handshake occurs before data is sent. There is no connect(), listen(), or accept() involved. Each datagram is sent independently, possibly taking different network paths. The sender doesn’t wait for the receiver to be ready, and does not know if the datagram arrived. This makes UDP faster but unreliable.

Q3. Why is IP fragmentation a bigger problem for UDP than for TCP?TCP contains built-in mechanisms (Path MTU Discovery) to determine the correct packet size and avoid fragmentation. UDP has no such mechanism. If a UDP datagram exceeds the MTU of any link along the path, IP fragments it. If any single fragment is lost, the entire datagram is lost — and UDP does not retransmit. The application must handle this or avoid fragmentation by keeping datagrams small.

Q4. What is the recommended maximum UDP payload size to avoid fragmentation and why?The conservative safe limit is 548 bytes of payload (576 bytes total IP packet minus 20 bytes IP header minus 8 bytes UDP header). This is based on the IPv4 minimum reassembly buffer size of 576 bytes, which every conformant IP implementation must support. Many applications use an even more conservative 512 bytes of data. Going over the path MTU causes fragmentation and increases the chance of datagram loss.

Q5. How do you send and receive UDP datagrams in C? What functions are used?For sending: sendto(sockfd, buf, len, flags, dest_addr, addrlen) — you specify the destination address on every call because there is no persistent connection. For receiving: recvfrom(sockfd, buf, len, flags, src_addr, addrlen) — it fills in the sender’s address so you can reply. Alternative: call connect() on a UDP socket to set a fixed peer, then use regular send()/recv().

Q6. Are UDP checksums reliable enough for critical data?No, not by themselves. UDP uses a 16-bit checksum which detects most bit errors, but studies show busy internet servers experience roughly one undetected transmission error every few days. For applications requiring strong data integrity (e.g., financial transactions, medical data), additional mechanisms like SSL/TLS or application-level error control (sequence numbers + hash verification) must be used.

Q7. Can you use connect() on a UDP socket? What does it do?Yes. Calling connect() on a UDP socket does not initiate a handshake (there is none in UDP). Instead, it records the remote address as the default destination. After connect(), you can use send()/recv() instead of sendto()/recvfrom(). It also causes the kernel to filter incoming datagrams — only those from the connected address are delivered. This is sometimes called a “connected UDP socket.”

Continue Learning

Next: TCP — reliable, connection-oriented, stream-based communication with endpoints and buffers.

Part 5: TCP → ← Part 3: Port Numbers

Leave a Reply

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