CS110: Principles of Computer Systems
Autumn 2021
Jerry Cain
PDF

Lecture 22: Network System Calls, Library Functions
- The three data structures presented below are in place to model IP address/port pairs:
struct sockaddr { // generic socket
unsigned short sa_family; // protocol family for socket
char sa_data[14];
// address data (and defines full size to be 16 bytes)
};The sockaddr_in is used to model IPv4 address/port pairs.
- The
sin_familyfield should always be initialized to beAF_INET, which is a constant used when IPv4 addresses are being used. If it feels redundant that a record dedicated to IPv4 needs to store a constant saying everything is IPv4, then stay tuned. - The
sin_portfield stores a port number in network byte (i.e. big endian) order. - The
sin_addrfield stores an IPv4 address as a packed, big endianint, as you saw withgethostbynameand thestructhostent. - The
sin_zerofield is generally ignored (though it's often set to store all zeroes). It exists to pad the record up to 16 bytes.
struct sockaddr_in { // IPv4 socket address record
unsigned short sin_family;
unsigned short sin_port;
struct in_addr sin_addr;
unsigned char sin_zero[8];
};struct sockaddr_in6 { // IPv6 socket address record
unsigned short sin6_family;
unsigned short sin6_port;
unsigned int sin6_flowinfo;;
struct in6_addr sin6_addr;
unsigned int sin6_scope_id;
};Lecture 22: Network System Calls, Library Functions
The sockaddr_in6 is used to model IPv6 address/port pairs.
- The
sin6_familyfield should always be set toAF_INET6. As with thesin_familyfield,sin6_familyfield occupies the first two bytes of surrounding record. - The
sin6_portfield holds a two-byte, network-byte-ordered port number, just like sin_port does. - A
structin6_addris also wedged in there to manage a 128-bit IPv6 address. -
sin6_flowinfoandsin6_scope_idare beyond the scope of what we need, so we'll ignore them.
- The three data structures presented below are in place to model IP address/port pairs:
struct sockaddr { // generic socket
unsigned short sa_family; // protocol family for socket
char sa_data[14];
// address data (and defines full size to be 16 bytes)
};struct sockaddr_in { // IPv4 socket address record
unsigned short sin_family;
unsigned short sin_port;
struct in_addr sin_addr;
unsigned char sin_zero[8];
};struct sockaddr_in6 { // IPv6 socket address record
unsigned short sin6_family;
unsigned short sin6_port;
unsigned int sin6_flowinfo;;
struct in6_addr sin6_addr;
unsigned int sin6_scope_id;
};Lecture 22: Network System Calls, Library Functions
The struct sockaddr is the best C can do to emulate an abstract base class.
- You rarely if ever declare variables of type
structsockaddr, but many system calls will accept parameters of typestructsockaddr *. - Rather than define a set of network system calls for IPv4 addresses and a second set of system calls for IPv6 addresses, Linux defines one set for both.
- If a system call accepts a parameter of type
structsockaddr*, it really accepts the address of either astructsockaddr_inor astructsockaddr_in6. The system call relies on the value within the first two bytes—thesa_familyfield—to determine what the true record type is.
- The three data structures presented below are in place to model IP address/port pairs:
struct sockaddr { // generic socket
unsigned short sa_family; // protocol family for socket
char sa_data[14];
// address data (and defines full size to be 16 bytes)
};struct sockaddr_in { // IPv4 socket address record
unsigned short sin_family;
unsigned short sin_port;
struct in_addr sin_addr;
unsigned char sin_zero[8];
};struct sockaddr_in6 { // IPv6 socket address record
unsigned short sin6_family;
unsigned short sin6_port;
unsigned int sin6_flowinfo;;
struct in6_addr sin6_addr;
unsigned int sin6_scope_id;
};Lecture 22: Network System Calls, Library Functions
At this point, we know most of the directives needed to implement and understand how to implement createClientSocket and createServerSocket.
-
createClientSocketis the easier of the two, so we'll implement that one first. (For simplicity, we'll confine ourselves to an IPv4 world.) - Fundamentally, createClientSocket needs to:
- Confirm the host of interest is really on the net by checking to see if it has an IP address.
gethostbynamedoes this for us. - Allocate a new descriptor and configure it to be a socket descriptor. We'll rely on the
socketsystem call to do this. - Construct an instance of a
struct sockaddr_inthat packages the host and port number we're interested in connecting to. - Associate the freshly allocated socket descriptor with the host/port pair. We'll rely on an aptly named system call called
connectto do this. - Return the fully configured client socket.
- Confirm the host of interest is really on the net by checking to see if it has an IP address.
- The full implementation of createClientSocket is on the next slide (and right here).
Lecture 15: Network System Calls, Library Functions
Here is the full implementation of
createClientSocket:int createClientSocket(const string& host, unsigned short port) {
struct hostent *he = gethostbyname(host.c_str());
if (he == NULL) return -1;
int s = socket(AF_INET, SOCK_STREAM, 0);
if (s < 0) return -1;
struct sockaddr_in address;
memset(&address, 0, sizeof(address));
address.sin_family = AF_INET;
address.sin_port = htons(port);
// h_addr is #define for h_addr_list[0]
address.sin_addr = *((struct in_addr *)he->h_addr);
if (connect(s, (struct sockaddr *) &address, sizeof(address)) == 0) return s;
close(s);
return -1;
}Lecture 15: Network System Calls, Library Functions
Here are key details about my createClientSocket implementation worth calling out:
- We call
gethostbynamefirst before we callsocket, because we want to confirm the host has a registered IP address—which means it's reachable—before we allocate any system resources. - Recall that
gethostbynameis intrinsically IPv4. If we wanted to involve IPv6 addresses instead, we would need to usegethostbyname2. - The call to
socketfinds, claims, and returns an unused descriptor.AF_INETconfigures it to be compatible with an IPv4 address, andSOCK_STREAMconfigures it to provide reliable data transport, which basically means the socket will reorder data packets and requests missing or garbled data packets to be resent so as to give the impression that data that is received in the order it's sent.- The first argument could have been
AF_INET6had we decided to use IPv6 addresses instead. (Other arguments are possible, but they're less common.) - The second argument could have been
SOCK_DGRAMhad we preferred to collect data packets in the order they just happen to arrive and manage missing and garbled data packets ourselves. (Other arguments are possible, though they're less common.)
- The first argument could have been
Lecture 15: Network System Calls, Library Functions
Here are a few more details:
-
addressis declared to be of typestruct sockaddr_in, since that's the data type specifically set up to model IPv4 addresses. Had we been dealing with IPv6 addresses, we'd have declared astruct sockaddr_in6instead.- It's important to embed
AF_INETwithinsin_family, since those two bytes are examined by system calls to determine the type of socket address structure. - The
sin_portfield is, not surprisingly, designed to hold the port of interest.htons—that's an abbreviation forhost-to-network-short—is there to ensure the port is stored in network byte order (which is big endian order). On big endian machines,htonsis implemented to return the provided short without modification. On little endian machines (like themyths),htonsreturns a figure constructed by exchanging the two bytes of the incomingshort. In addition tohtons, Linux also providedhtonlfor four-bytelongs, and it also providesntohsandntohlto restore host byte order from network byte ordered figures.
- It's important to embed
- The call to
connectassociates the descriptorswith the host/IP address pair modeled by the suppliedstruct sockaddr_in *. The second argument is downcast to astruct sockaddr *, sinceconnectmust accept a pointer to any type within the entirestruct sockaddrfamily, not juststruct sockaddr_ins.connectwill return -1 (witherrnoset toECONNREFUSED) if the server of interest isn't running.
Lecture 15: Network System Calls, Library Functions
Here is the full implementation of createServerSocket (and online right here):
int createServerSocket(unsigned short port, int backlog) {
int s = socket(AF_INET, SOCK_STREAM, 0);
if (s < 0) return -1;
struct sockaddr_in address;
memset(&address, 0, sizeof(address));
address.sin_family = AF_INET;
address.sin_addr.s_addr = htonl(INADDR_ANY);
address.sin_port = htons(port);
if (bind(s, (struct sockaddr *)&address, sizeof(address)) == 0 &&
listen(s, backlog) == 0) return s;
close(s);
return -1;
}Lecture 15: Network System Calls, Library Functions
Here are key details about my implementation of createServerSocket:
- The call to
socketis precisely the same here as it was increateClientSocket. It allocates a descriptor and configures it to be a socket descriptor within theAF_INETnamespace. - The address of type
struct sockaddr_inhere is configured in much the same way it was increateClientSocket, except that thesin_addr.s_addrfield should be set to a local IP address, not a remote one. The constantINADDR_ANYis used to state that address should represent all local addresses. - The
bindcall simply assigns the set of local IP addresses represented byaddressto the provided sockets. Because we embeddedINADDR_ANYwithinaddress,bindassociates the supplied socket with all local IP addresses. That means oncecreateServerSockethas done its job, clients can connect to any of the machine's IP addresses via the specified port. - The
listencall is what converts the socket to be one that's willing to accept connections viaaccept. The second argument is a queue size limit, which states how many pending connection requests can accumulate and wait their turn to beaccepted. If the number of outstanding requests is at the limit, additional requests are simply refused.
Lecture 22: Networks and More System Calls
By Jerry Cain
Lecture 22: Networks and More System Calls
- 1,385