Network Programming

Unit 1 · Chapter 3 · Book pp. 67–94

Sockets Introduction

Three of last year's eight questions came from this chapter alone. It is about one thing: how an address gets from something a person typed into something the kernel will accept — and how the length of that address travels alongside it.

Q1 [5] Value–result arguments with an example. Q2(a) [5] Identify the five conversion functions in the given figure. Q4(a)(i) [10] A C program that analyses the byte ordering of a system. All three are worked in the solved paper.

Socket address structures

Most socket functions need a pointer to a socket address structure. Each protocol suite defines its own, and they all begin with sockaddr_ and end with a suffix naming the suite.

A socket address structure is a small form. You fill in three things — which kind of address this is, which port, which machine — and hand the form to the kernel. The awkwardness of the API comes entirely from the fact that different kinds of address need different-sized forms, but every function has to accept all of them.

Click any field below to see what it is for. Switch structures with the buttons, and note the sizes: 16 bytes for IPv4, 28 for IPv6.

Why the generic structure exists

bind, connect and the rest were defined before ANSI C, so there was no void *. The problem: one function has to accept an IPv4 address, an IPv6 address or a Unix-domain address. The 4.2BSD solution was to define a generic socket address structure and declare every socket function to take a pointer to that.

§3.2 · p.71
struct sockaddr {
    uint8_t       sa_len;
    sa_family_t   sa_family;    /* address family: AF_xxx value */
    char          sa_data[14];  /* protocol-specific address */
};

The consequence you meet on every single call:

struct sockaddr_in  serv;

/* fill in serv{} */

connect(sockfd, (struct sockaddr *) &serv, sizeof(serv));

The book abbreviates that cast to SA, defined in unp.h:

#define SA struct sockaddr

connect(sockfd, (SA *) &serv, sizeof(serv));
The kernel does not really believe your struct sockaddr. It reads the sa_family field, works out what the structure really is, and casts it back. The generic structure is a shipping envelope, not a description of the contents.

The new generic structure

struct sockaddr is 16 bytes — too small for the 28-byte sockaddr_in6. So IPv6 brought struct sockaddr_storage, which differs from struct sockaddr in exactly two ways. Both are quotable:

§3.2 · p.73 (a) If any socket address structure the system supports has alignment requirements, sockaddr_storage provides the strictest alignment requirement.

(b) sockaddr_storage is large enough to contain any socket address structure the system supports.
Apart from ss_family and ss_len, the rest of sockaddr_storage is opaque. You must cast it, or copy out of it, to reach any field — you cannot reach into it directly. And its field names begin ss_, not sa_.

The sin_len field

Where it came from
4.3BSD Reno added it when newer protocols arrived, to simplify the handling of variable-length socket address structures.
Do you set it?
No. Even where it is present you need never set it and never examine it — unless you are dealing with routing sockets. It is used inside the kernel by the routines that handle socket address structures from various protocol families.
Who fills it in
On the way inbind, connect, sendto, sendmsg — a Berkeley-derived implementation routes everything through a function called sockargs, which copies the structure from the process and explicitly sets sin_len to the size passed as an argument. On the way outaccept, recvfrom, recvmsg, getpeername, getsockname — the kernel sets it before returning.
POSIX
POSIX requires only three members of sockaddr_in: sin_family, sin_addr and sin_port. Almost all implementations add sin_zero so that all socket address structures are at least 16 bytes.
sa_family_t can be any size. It is normally an 8-bit unsigned integer if the implementation supports the length field, and a 16-bit unsigned integer if it does not — because without sin_len there is a spare byte at offset 0 to absorb. The older datatypes u_char, u_short, u_int and u_long are all unsigned, and all obsolete.

Value-result arguments

Q1, 5 marks, no choice: "Explain value–result arguments with an example." This is the highest-probability single question in the paper — it appeared in Part A where there is no choice at all. Full model answer in the solved paper.

When a socket address structure is passed to any socket function it is always passed by reference — a pointer. The length of the structure is passed too. But how the length is passed depends on which direction the structure is travelling.

Switch direction below and step through. Watch what happens to len.

Direction 1 — process to kernel

§3.3 · p.74 Three functions pass a socket address structure from the process to the kernel: bind, connect, sendto.
struct sockaddr_in  serv;

/* fill in serv{} */

connect(sockfd, (SA *) &serv, sizeof(serv));
                                  ^^^^^^^^^^^^^ an integer, by value

Since the kernel is passed both the pointer and the size of what the pointer points to, it knows exactly how much data to copy from the process into the kernel. Nothing comes back. The size is a plain value argument.

Direction 2 — kernel to process

§3.3 · p.75 Four functions pass a socket address structure from the kernel to the process: accept, recvfrom, getsockname, getpeername.
struct sockaddr_un  cli;     /* Unix domain */
socklen_t  len;

len = sizeof(cli);            /* len is a value */

getpeername(unixfd, (SA *) &cli, &len);
                                 ^^^^ a POINTER to an integer

/* len may have changed */
§3.3 · p.75 The reason that the size changes from an integer to be a pointer to an integer is because the size is both a value when the function is called (it tells the kernel the size of the structure so that the kernel does not write past the end of the structure when filling it in) and a result when the function returns (it tells the process how much information the kernel actually stored in the structure). This type of argument is called a value-result argument.
When you call accept you do not know who is about to connect. It could be an IPv4 peer needing 16 bytes or an IPv6 peer needing 28. So you offer a buffer and say how big it is — that stops the kernel overrunning you. The kernel writes whatever it needs and then tells you how much it wrote — which is how you find out what kind of peer it was. One variable does both jobs because the two jobs happen at different moments: one on the way in, one on the way out.
Handing someone an envelope and saying "this holds at most 20 pages". They put 6 pages in and hand it back with "6" written on the flap. Your "20" was a limit going out; their "6" is a fact coming back. Same flap, two different pieces of information, and you needed both.

Fixed-length versus variable-length

StructureValue the kernel returns in len
sockaddr_in — fixed lengthAlways 16
sockaddr_in6 — fixed lengthAlways 28
sockaddr_un — variable lengthCan be less than the maximum size of the structure
The length of a returned socket address structure is the most common value-result argument in network programming, but not the only one — the same pattern turns up with the middle three arguments to select, with the length argument to getsockopt, and with the msg_namelen and msg_controllen members of msghdr used by recvmsg. §3.3 · p.76
In a five-mark answer you must show both directions, name 3 + 4 = 7 functions, and show the difference in the call — sizeof(serv) versus &len. Naming only accept and saying "the length changes" is a two-mark answer.
Why is the datatype socklen_t and not int?

Portability of the ABI. POSIX recommends socklen_t be defined as uint32_t — a definitely-32-bit type — so that the size of the argument does not change when the same source is compiled for a 64-bit model where int or long might differ.

Same reasoning as the fixed-width types elsewhere in the API: uint16_t for in_port_t, uint32_t for in_addr_t.

Byte ordering functions

A 16-bit integer is two bytes. There are two ways to put those two bytes in memory, and there is no standard between them — you will meet systems that use each.

Little-endian byte order
The low-order byte is at the starting address.
Big-endian byte order
The high-order byte is at the starting address.
Host byte order
Whichever of the two a given system uses. The native way that CPU orders multi-byte data.
Network byte order
The standardised order for data on the wire: big-endian, always, regardless of the hosts involved.
The names come from Gulliver's Travels — the two nations that went to war over which end of a boiled egg to open. They tell you which end of the multi-byte value, the little end or the big end, is stored at the starting address. There is no technical reason to prefer either. That is precisely why it needed standardising for the network, and why both survive on hosts.

Change the value and the width below, and watch the same number take two different shapes in memory.

The four conversion functions

§3.4 · p.78
#include <netinet/in.h>

uint16_t  htons(uint16_t  host16bitvalue);
uint32_t  htonl(uint32_t  host32bitvalue);
                              Both return: value in network byte order

uint16_t  ntohs(uint16_t  net16bitvalue);
uint32_t  ntohl(uint32_t  net32bitvalue);
                              Both return: value in host byte order
Read the name straight through: h host, to, n network, s short. htons = "host to network, short".
The terms "short" and "long" are historical artefacts. Think of s as a 16-bit value — a TCP or UDP port number — and l as a 32-bit value — an IPv4 address. On a 64-bit LP64 system a C long is 64 bits, but htonl still converts exactly 32.
FieldWidthFunction
sin_port16-bithtons()
sin_addr.s_addr32-bithtonl()
sin6_port16-bithtons()
On a big-endian system the four functions are usually defined as null macros — they do nothing at all. Which is exactly why omitting them is a bug that hides: your code works on one machine and silently fails on another. Always call them, on every system.

Bit ordering in RFCs

An important convention in Internet standards, and a different thing from byte order. When an RFC draws a protocol header it shows the four bytes in the order in which they appear on the wire, with the leftmost bit the most significant. So reading an RFC diagram left-to-right, top-to-bottom gives you the exact order of bits on the cable.

Byte manipulation functions

Two groups of functions that operate on multi-byte fields without interpreting the data and without assuming it is a null-terminated C string. They are needed because an IP address can perfectly well contain a zero byte — 10.0.0.1 contains three — so the str functions from <string.h> would stop halfway through it.

Group 1 · b for byte

From 4.2BSD. Still provided by almost any system that supports the socket functions.

#include <strings.h>

void bzero(void *dest, size_t n);
void bcopy(const void *src,
           void *dest, size_t n);
int  bcmp(const void *p1,
          const void *p2, size_t n);

Group 2 · mem for memory

From the ANSI C standard. Provided with any system that has an ANSI C library.

#include <string.h>

void *memset(void *dest,
             int c, size_t n);
void *memcpy(void *dest,
             const void *src, size_t n);
int   memcmp(const void *p1,
             const void *p2, size_t n);
DoesBSDANSI CReturns
Set n bytes to zerobzero(dest, n)memset(dest, 0, n)
Move n bytesbcopy(src, dest, n)memcpy(dest, src, n)
Compare n bytesbcmp(p1, p2, n)memcmp(p1, p2, n)0 if equal
1. The argument order is reversed. bcopy takes source first; memcpy takes destination first. The book's mnemonic: for memcpy, remember that the two arguments are in the same left-to-right order as an assignment, dest = src.

2. memcmp and bcmp are not the same. bcmp only tells you equal or unequal. memcmp returns a value less than, equal to or greater than zero — so it also tells you which is larger, comparing byte by byte as unsigned characters.
The book uses bzero throughout rather than memset, because it takes two arguments instead of three and so is harder to get wrong. With memset it is genuinely easy to write memset(&serv, sizeof(serv), 0) — arguments swapped, zero bytes cleared, no warning.

Address conversion functions

Q2(a), 5 marks: the figure below was reproduced with the five function names blanked out, and you were asked to name them. Press quiz me on the figure to see the exam's version of it.

Two groups of functions convert between the two representations of an Internet address: the ASCII string a human prefers, and the network byte ordered binary value stored in a socket address structure.

The three IPv4-only functions

§3.6 · p.82
#include <arpa/inet.h>

int inet_aton(const char *strptr, struct in_addr *addrptr);
                         Returns: 1 if string was valid, 0 on error

in_addr_t inet_addr(const char *strptr);
       Returns: 32-bit binary network byte ordered IPv4 address;
                INADDR_NONE if error

char *inet_ntoa(struct in_addr inaddr);
       Returns: pointer to dotted-decimal string
It returns INADDR_NONE on error, which is typically 32 one-bits. But 32 one-bits is 255.255.255.255, a perfectly valid IPv4 broadcast address. The function therefore cannot distinguish that address from a failure. Any new code should use inet_aton instead — and note that inet_aton returns 1 for success and 0 for error, which is the opposite of the convention you might expect.
The pointer inet_ntoa returns is to a string held in static memory. That makes the function not re-entrant — the next call overwrites the previous result. Also notice it takes its argument as a structure, by value, not as a pointer. Passing structures by value is unusual in C and worth remarking on.

The two that handle both protocols

§3.7 · p.83
#include <arpa/inet.h>

int inet_pton(int family, const char *strptr, void *addrptr);
       Returns: 1 if OK, 0 if input not a valid presentation format,
                -1 on error

const char *inet_ntop(int family, const void *addrptr,
                      char *strptr, size_t len);
       Returns: pointer to result if OK, NULL on error
p and n
p stands for presentation — the text a human reads and types. n stands for numeric — the binary value that goes into a socket address structure.
family
Either AF_INET or AF_INET6. If the family is not supported, both functions return an error with errno set to EAFNOSUPPORT.
len
The size of the caller's buffer, so that inet_ntop cannot overflow it. If it is too small the call fails with errno set to ENOSPC. Two constants exist for it: INET_ADDRSTRLEN = 16 and INET6_ADDRSTRLEN = 46.
inet_pton has three possible returns, and two of them are failures: 0 means the string was not a valid address, −1 means something else went wrong. So the correct test is if (inet_pton(...) <= 0), not < 0. Writing < 0 silently accepts garbage input.

Migrating old code

Even on a system with no IPv6 support you can start using the newer functions.

replace this

foo.sin_addr.s_addr = inet_addr(cp);

with this

inet_pton(AF_INET, cp, &foo.sin_addr);

replace this

ptr = inet_ntoa(foo.sin_addr);

with this

char str[INET_ADDRSTRLEN];
ptr = inet_ntop(AF_INET, &foo.sin_addr,
                str, sizeof(str));

sock_ntop and the readn/writen family

Two sets of helpers the book defines for itself. They are not standard, but the examples use them constantly, so you need to know what they do.

sock_ntop
inet_ntop has a design problem: the caller must already know the address family and pass a pointer to the binary address, so the caller has to be protocol-dependent. sock_ntop takes a pointer to a socket address structure, looks inside it, and formats a presentation string for whatever family it finds — including the port, as 206.168.112.96:9877. That makes calling code protocol-independent.
readn, writen, readline
Read exactly n bytes, write exactly n bytes, and read one line. All three return the number of bytes transferred, or −1 on error.
§3.9 · p.88 Stream sockets exhibit a behaviour with read and write that differs from normal file I/O. A read or write on a stream socket might input or output fewer bytes than requested, but this is not an error condition. The reason is that buffer limits might be reached for the socket in the kernel. All that is required to transfer the remaining bytes is for the caller to invoke the function again.
Asking for 1,000 bytes and being handed 300 is not a failure — it is TCP telling you that is all it has right now. The fix is a loop. This is the same fact as "TCP is a byte stream with no record boundaries", met from the programmer's side rather than the protocol's.
A short count is always possible on a stream socket with read. With write it is normally seen only if the socket is non-blocking — but the book always calls writen anyway, in case an implementation returns one. Some versions of Unix also do this when writing more than 4,096 bytes to a pipe.
You call read(sockfd, buf, 100) and it returns 40. What do you do?

Call it again for the remaining 60, advancing the buffer pointer. It is not an error and there is nothing to report — the kernel simply had 40 bytes available at that moment.

This is exactly what readn does for you, which is why the book uses it in place of read anywhere a fixed number of bytes is expected. Treating a short count as an error, or as end of message, is one of the most common real bugs in network code.

Name the five address conversion functions, and say which handle IPv6.
FunctionDirectionIPv6?
inet_atonpresentation → numericNo
inet_addrpresentation → numericNo — and deprecated
inet_ntoanumeric → presentationNo
inet_ptonpresentation → numericYes
inet_ntopnumeric → presentationYes

The three inet_+a ones are the old IPv4-only group; the pton/ntop pair replaced all of them and handles both protocols.