IPv6 Sockets
SOCK_DGRAM
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
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, ...) |
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.
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);
}
| 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 |
| 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 |
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
| 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 |
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)
