Sockets: Internet Domains IPv6 UDP Client

 

Chapter 59 โ€“ Sockets: Internet Domains
Part 1 of 3 ย |ย  IPv6 UDP Client (Listing 59-4)
๐Ÿ“ก Topic
IPv6 Sockets
๐Ÿ”ง Type
SOCK_DGRAM
๐Ÿ“˜ Source
TLPI Ch.59

What is this file about?

This tutorial explains how to write an IPv6 UDP client in C that sends messages to a server and reads back the response. The code comes from TLPI Listing 59-4 (sockets/i6d_ucase_cl.c). We break it down step by step so you understand every line.

Key Terms

AF_INET6 SOCK_DGRAM sockaddr_in6 inet_pton() htons() sendto() recvfrom() sin6_family sin6_port sin6_addr

Background: IPv4 vs IPv6 Sockets

IPv4 sockets use AF_INET and the sockaddr_in structure. IPv6 sockets use AF_INET6 and the sockaddr_in6 structure. The programming model is almost identical โ€” you just change the address family and structure.

UDP (SOCK_DGRAM) is a connectionless protocol. There is no connect()/accept() handshake. The client just creates a socket, fills in the server address, and calls sendto().

Property IPv4 (AF_INET) IPv6 (AF_INET6)
Address family constant AF_INET AF_INET6
Socket address struct struct sockaddr_in struct sockaddr_in6
Address size (bytes) 4 bytes (32-bit) 16 bytes (128-bit)
Address field sin_addr sin6_addr
Port field sin_port sin6_port
Text address to binary inet_pton(AF_INET, ...) inet_pton(AF_INET6, ...)

The sockaddr_in6 Structure

When you work with IPv6 sockets, you use struct sockaddr_in6 to hold the server’s address. Here is what it looks like:

struct sockaddr_in6 {
    sa_family_t  sin6_family;   /* AF_INET6 โ€” address family */
    in_port_t    sin6_port;     /* Port number (network byte order) */
    uint32_t     sin6_flowinfo; /* IPv6 flow info (usually 0) */
    struct in6_addr sin6_addr;  /* 128-bit IPv6 address */
    uint32_t     sin6_scope_id; /* Scope ID (usually 0) */
};

You always zero the whole structure first with memset(), then fill in only the fields you need. This avoids garbage values in the unused fields.

Full Code โ€“ IPv6 UDP Client

Below is the complete client from Listing 59-4 with explanations after each section.

#include "i6d_ucase.h"   /* defines PORT_NUM, BUF_SIZE, helper functions */

int
main(int argc, char *argv[])
{
    struct sockaddr_in6 svaddr;  /* server's address structure */
    int    sfd, j;
    size_t  msgLen;
    ssize_t numBytes;
    char    resp[BUF_SIZE];

    /* ---- Step 1: check arguments ---- */
    if (argc < 3 || strcmp(argv[1], "--help") == 0)
        usageErr("%s host-address msg...\n", argv[0]);
    /*
     * argv[1] = IPv6 address of server  e.g.  ::1
     * argv[2], argv[3], ... = messages to send
     */

    /* ---- Step 2: create a UDP socket ---- */
    sfd = socket(AF_INET6, SOCK_DGRAM, 0);
    if (sfd == -1)
        errExit("socket");
    /*
     * AF_INET6   = IPv6 address family
     * SOCK_DGRAM = UDP (connectionless, unreliable datagrams)
     * 0          = let kernel choose protocol (UDP)
     */

    /* ---- Step 3: fill in server address ---- */
    memset(&svaddr, 0, sizeof(struct sockaddr_in6)); /* zero first */
    svaddr.sin6_family = AF_INET6;
    svaddr.sin6_port   = htons(PORT_NUM);  /* convert to network byte order */
    if (inet_pton(AF_INET6, argv[1], &svaddr.sin6_addr) <= 0)
        fatal("inet_pton failed for address '%s'", argv[1]);
    /*
     * inet_pton() = "presentation to network"
     * converts a text IPv6 address like "::1" into 16 binary bytes
     * stored in sin6_addr.
     * Returns > 0 on success, 0 if string is not valid, -1 on error.
     */

    /* ---- Step 4: send each message and print reply ---- */
    for (j = 2; j < argc; j++) {
        msgLen = strlen(argv[j]);

        /* send the message to server */
        if (sendto(sfd, argv[j], msgLen, 0,
                   (struct sockaddr *) &svaddr,
                   sizeof(struct sockaddr_in6)) != msgLen)
            fatal("sendto");

        /* receive the reply */
        numBytes = recvfrom(sfd, resp, BUF_SIZE, 0, NULL, NULL);
        if (numBytes == -1)
            errExit("recvfrom");

        printf("Response %d: %.*s\n", j - 1, (int) numBytes, resp);
        /*
         * %.*s = print exactly numBytes characters from resp
         * (resp may not be null-terminated, so we use this form)
         */
    }

    exit(EXIT_SUCCESS);
}

Key Functions Explained

Function Purpose Returns
socket(AF_INET6, SOCK_DGRAM, 0) Creates an IPv6 UDP socket socket file descriptor (int), -1 on error
htons(PORT_NUM) Host-to-Network Short โ€” converts port to big-endian uint16_t in network byte order
inet_pton(AF_INET6, str, &addr) Converts text IPv6 address to binary 1 on success, 0 invalid, -1 error
sendto(sfd, buf, len, flags, addr, addrlen) Sends a datagram to the specified address bytes sent, -1 on error
recvfrom(sfd, buf, len, flags, NULL, NULL) Receives a datagram (ignoring sender address here) bytes received, -1 on error

How Data Flows: Client โ†’ Server โ†’ Client

Client Process
argv[j] = message
โ”€โ”€sendto()โ”€โ”€โ–ถ UDP / IPv6 Network
Datagram packet
โ”€โ”€โ–ถ Server Process
recvfrom() โ†’ UPPERCASE
recvfrom()
print response
โ—€โ”€โ”€ UDP Response
Uppercase reply datagram
โ—€โ”€โ”€sendto()โ”€โ”€ Server sends back
uppercase message

Minimal Working Example (Self-contained)

The example below is a simplified IPv6 UDP client that you can actually compile and run (requires a listening server at ::1 port 50000):

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define PORT  50000
#define BUF   256

int main(void)
{
    int sfd;
    struct sockaddr_in6 svaddr;
    char msg[] = "hello world";
    char resp[BUF];
    ssize_t n;

    /* 1. Create IPv6 UDP socket */
    sfd = socket(AF_INET6, SOCK_DGRAM, 0);
    if (sfd == -1) { perror("socket"); exit(1); }

    /* 2. Fill in server address (loopback ::1) */
    memset(&svaddr, 0, sizeof(svaddr));
    svaddr.sin6_family = AF_INET6;
    svaddr.sin6_port   = htons(PORT);
    if (inet_pton(AF_INET6, "::1", &svaddr.sin6_addr) <= 0) {
        fprintf(stderr, "inet_pton failed\n");
        exit(1);
    }

    /* 3. Send the message */
    if (sendto(sfd, msg, strlen(msg), 0,
               (struct sockaddr *)&svaddr, sizeof(svaddr)) < 0) {
        perror("sendto"); exit(1);
    }
    printf("Sent: %s\n", msg);

    /* 4. Wait for a reply */
    n = recvfrom(sfd, resp, BUF - 1, 0, NULL, NULL);
    if (n < 0) { perror("recvfrom"); exit(1); }
    resp[n] = '\0';
    printf("Response: %s\n", resp);

    close(sfd);
    return 0;
}

Compile:

gcc -o udp6_client udp6_client.c
./udp6_client

Common Mistakes to Avoid
Mistake What Goes Wrong Fix
Forgetting memset() on the address structure Garbage values in unused fields may cause sendto() to fail Always memset(&svaddr, 0, sizeof(svaddr)) first
Not calling htons() on the port Port is stored in wrong byte order, server never matches svaddr.sin6_port = htons(PORT_NUM);
Using AF_INET with sockaddr_in6 Address family mismatch, connection fails Use AF_INET6 everywhere consistently
Passing wrong size to sendto() Truncated or oversized datagram Use sizeof(struct sockaddr_in6)
Null-terminating resp without checking numBytes Buffer overflow if response fills BUF_SIZE Use %.*s format with numBytes, or resp[n]='\0' carefully

Interview Questions & Answers

Q1. What is the difference between SOCK_DGRAM and SOCK_STREAM?

SOCK_STREAM uses TCP โ€” it is connection-oriented, reliable, and preserves message boundaries. SOCK_DGRAM uses UDP โ€” it is connectionless, unreliable (no retransmission), and each sendto() call sends exactly one datagram.

Q2. Why do we call htons() when setting the port number?

Network protocols use big-endian (network byte order) for multi-byte integers. x86 CPUs use little-endian. htons() (host-to-network short) converts the 16-bit port from host byte order to network byte order so both sides agree on the value.

Q3. What does inet_pton() do and what does “pton” stand for?

inet_pton() means presentation to network. It converts a human-readable IP address string (like "::1" for IPv6 loopback or "192.168.1.1" for IPv4) into its binary form suitable for storing in a socket address structure.

Q4. In the UDP client, why is there no connect() call?

UDP is connectionless. You do not need to establish a connection before sending. sendto() takes the destination address directly as a parameter each time you send. (You can optionally call connect() on a UDP socket to set a default destination, but it is not required.)

Q5. What is the last two arguments of recvfrom() set to NULL here?

The last two arguments of recvfrom() are a pointer to a sender address structure and a pointer to its length. When set to NULL, we simply do not care about who sent the reply. The client already knows it sent to the server, so it trusts any incoming datagram as the response.

Q6. Why is %.*s used in printf instead of %s?

The buffer resp received from recvfrom() is not null-terminated โ€” it is raw bytes. Using %.*s with (int)numBytes tells printf to print exactly that many characters without relying on a null terminator, preventing undefined behavior.

Q7. What happens if the server is not running when the client calls sendto()?

With UDP, sendto() itself will succeed โ€” the datagram is simply sent into the network and dropped if nobody is listening. The client will then block forever on recvfrom() waiting for a reply that never arrives. In production code you should set a timeout using setsockopt() with SO_RCVTIMEO.

Continue Learning

Next: DNS โ€“ Domain Name System (Part 2 of 3)

Part 2 โ†’ DNS Part 3 โ†’ /etc/services

Leave a Reply

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