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.
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.
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.
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));
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:
sockaddr_storage provides the
strictest alignment requirement.
(b)
sockaddr_storage is large enough to
contain any socket address structure the system supports.
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 in —
bind,connect,sendto,sendmsg— a Berkeley-derived implementation routes everything through a function calledsockargs, which copies the structure from the process and explicitly setssin_lento the size passed as an argument. On the way out —accept,recvfrom,recvmsg,getpeername,getsockname— the kernel sets it before returning. - POSIX
-
POSIX requires only three members of
sockaddr_in:sin_family,sin_addrandsin_port. Almost all implementations addsin_zeroso 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
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
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
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 */
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.
Fixed-length versus variable-length
| Structure | Value the kernel returns in len |
|---|---|
sockaddr_in — fixed length | Always 16 |
sockaddr_in6 — fixed length | Always 28 |
sockaddr_un — variable length | Can be less than the maximum size of the structure |
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
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.
Change the value and the width below, and watch the same number take two different shapes in memory.
The four conversion functions
#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
htons = "host to network, short".
long is 64 bits, but htonl still converts exactly 32.
| Field | Width | Function |
|---|---|---|
| sin_port | 16-bit | htons() |
| sin_addr.s_addr | 32-bit | htonl() |
| sin6_port | 16-bit | htons() |
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);
| Does | BSD | ANSI C | Returns |
|---|---|---|---|
| Set n bytes to zero | bzero(dest, n) | memset(dest, 0, n) | — |
| Move n bytes | bcopy(src, dest, n) | memcpy(dest, src, n) | — |
| Compare n bytes | bcmp(p1, p2, n) | memcmp(p1, p2, n) | 0 if equal |
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.
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
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
#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
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.
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
#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
pandn- 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_INETorAF_INET6. If the family is not supported, both functions return an error witherrnoset toEAFNOSUPPORT. len-
The size of the caller's buffer, so that
inet_ntopcannot overflow it. If it is too small the call fails witherrnoset toENOSPC. Two constants exist for it:INET_ADDRSTRLEN= 16 andINET6_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_ntophas 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_ntoptakes a pointer to a socket address structure, looks inside it, and formats a presentation string for whatever family it finds — including the port, as206.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.
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.
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.
| Function | Direction | IPv6? |
|---|---|---|
| inet_aton | presentation → numeric | No |
| inet_addr | presentation → numeric | No — and deprecated |
| inet_ntoa | numeric → presentation | No |
| inet_pton | presentation → numeric | Yes |
| inet_ntop | numeric → presentation | Yes |
The three inet_+a ones are the old IPv4-only group;
the pton/ntop pair replaced all of them and handles
both protocols.