Writing Protocol Independant Code
part 2
Two of the most commonly used functions in writing network
applications are gethostbyname() and gethostbyaddr(). Both of these
functions, however, are protocol dependant. For us to call gethostbyname()
we must first know if the call will request the host's A record (in the
case of IPv4) or the host's AAAA record (in the case of IPv6). We must
know this so we may decide to create an IPv4 address structure (sin_addr)
or an IPv6 address structure (sin_addr6) to store the return value.
Likewise, for us to cal gethostbyname() we must know which member of our
socket structure (i.e. sin_addr or sin_addr6) contains the binary address.
In part 2 of our ongoing tutorial we discuss the use of
getaddrinfo() as a protocol independant alternative. [Throughout this
tutorial we assume the reader is working in a posix-compliant UNIX
environment.]
Note: It would be possible for us to put #ifdefs throughout our
source code to determine if the system under which it is being
compiled run IPv4 or IPv6 and then change its behavior accordingly.
However, there are three problems with this approach:
1. It would substantially increase the amount of code for
any non-trivial application,
2. it would not prevent problems with binaries that were
compiled on a machine that supported one version of IP and
then ran on a machine that supported a different version,
3. It would not allow communication between an IPv4 server
and an IPv6 client, both running a dual stack. We stated
in part one of this tutorial that this problem must be
handled in the application layer.
The function getaddrinfo() was defined by posix.1g and provides a
protocol independant alternative to gethostbyname() by allowing the user
to specify the host's canonnical name, independant of IP details. Figure
2.1 shows the function's prototype.
===============================================================================
Figure 2.1
#include <netdb.h>
int getaddrinfo(const char *host, const char *port,
const struct addrinfo *filter, struct addrinfo **addrlist);
===============================================================================
-The host argument can be either a cononnical hostname, an IPv4
dotted-decimal, or an IPv6 hex string.
-The port argument can be either a service name (i.e. ftp, pop, etc.)
or a port number.
-The filter argument can be either either a NULL pointer or an
addrinfo structure.
-The addrlist argument is a pointer of type struct addrinfo. When
getaddrinfo() returns, this pointer will point to the head of a
linked-list of addrinfo structures.
-getaddrinfo() returns 0 on success.
We now examine the addrinfo structure and how it is used in
calling getaddrinfo(). Figure 2.2 shows the definition of the addrinfo
structure.
===============================================================================
Figure 2.2
#include <netdb.h>
struct addrinfo {
int ai_flags;
int ai_family;
int ai_socktype;
int ai_protocol;
size_t ai_addrlen;
struct sockaddr *ai_addr;
struct addrinfo *ai_next;
};
===============================================================================
The filter argument in the call to getaddrinfo() allows us to
narrow the range of addrinfo structures that can be returned through the
addrlist argument. If we, for instance, wanted to handle only IPv6
clients, we could set the ai_family member to equal AF_INET6. By doing
this, getaddrinfo() will only return addrinfo structures that contain IPv6
addresses and IPv4-mapped IPv6 addresses within their ai_addr member.
There are four members of the filter structure that we may set for
the call to getaddrinfo(). Those members are: ai_flags, ai_family,
ai_socktype, ai_protocol.
-The ai_flags member can be set to equal either AI_PASSIVE or
AI_CONONNAME. AI_PASSIVE indicates that the socket will be used as a
listening socket. The AI_CANONNAME macro requests that getaddrinfo()
return the host's canonnical name (e.g. blah.foo.gov).
-The ai_family member can be set to equal any of the AF_XXX
macros. Setting ai_family to equal AF_INET will return all addrinfo
structures that contain an IPv4 address in the ai_addr member. If the host
argument to getaddrinfo() is an IPv6 hex string and AF_INET is set, error
EAI_ADDRFAMILY is returned. Setting the ai_family member to equal AF_INET6
will return all structures containing IPv6 addresses in the ai_addr
member. Setting this also returns structures containing IPv4-mapped IPv6
addresses in the ai_addr member. Passing host to getaddrinfo() as a
dotted-decimal while AF_INET6 is set will generate the error
EAI_ADDRFAMILY. Setting ai_family to AF_UNSPEC tells getaddrinfo() not to
discard any structures based on the accompanying address.
-The ai_socktype mamber can be set to equal any of the SOCK_XXX
macros. Setting ai_socktype to equal SOCK_DGRAM tells getaddrinfo() that
the socket will be used for sending/recieving UDP datagrams. Likewise,
setting it to equal SOCK_STREAM tells the function that the socket will be
used for TCP connections.
-The ai_protocol member can be set to equal any of the IPPROTO_XXX
macros. However, we ignore this member; the use of the ai_family and
ai_socktype members eliminates any need to set ai_protocol.
Before showing an example of getaddrinfo()'s use, we must first
define two other functions. Figure 2.3 and 2.4 shows these function
prototypes.
===============================================================================
Figure 2.3
#include <netdb.h>
char *gai_strerror(int errnum);
===============================================================================
===============================================================================
Figure 2.4
#include <netdb.h>
void freeaddrinfo(struct addrinfo *ai);
===============================================================================
In figure 2.3 we show the prototype fo the gai_strerror()
function. getaddrinfo() does not set the errno variable, instead it
returns a non-zero integer to indicate an error. gai_strerror() translates
this value for us and returns a string detailing the error.
In figure 2.4 we show the prototype for the freeaddrinfo()
function. The argument ai is a pointer to the head of a linked-list of
addrinfo structures. We call this function after we are through with the
linked-list returned by getaddrinfo(). freeaddrinfo() traverses the
linked-list, calling free() for each structure.
Since calling getaddrinfo() takes a considerable amount of code we
define our own functions to handle it. Figure 2.5 shows our first
function, getaddrlist().
===============================================================================
Figure 2.5
#include "tutorial.h" /* defined at end of tutorial */
struct addrinfo *getaddrlist(const char *host, const char *port,
int flags, int family, int socktype) {
int errnum;
struct addrinfo filter, *addrlist;
bzero(&filter, sizeof(struct addrinfo));
filter.ai_flags = flags;
filter.ai_family = family; /* specify IPv4, v6, etc. */
filter.ai_socktype = socktype; /* specify UDP, TCP, etc. */
if (errnum = getaddrinfo(host, port, &filter, &addrlist)) {
fprintf(stderr, "getaddrinfo() failed. : %s\n",
gai_strerror(errnum));
exit(1);
} else return (addrlist);
}
===============================================================================
Our getaddrlist() function takes five arguments; a hostname or IP
address, service name or port number, flags (e.g. AI_PASSIVE), the family
(e.g. AF_INET6), and the socket type (e.g. SOCK_STREAM). It then returns
the linked-list of addrinfo structures returned by getaddrinfo() or calls
gai_strerror() to print the error and exits.
We can now call any of the following functions we define by
passing to them the linked-list returned by getaddrlist(). The next
function we define uses the linked-list to establish a TCP connection.
Figure 2.6 shows our function gai_connect().
===============================================================================
Figure 2.6
#include "tutorial.h" /* defined at end of tutorial */
int gai_connect(struct addinfo *ai) {
int sockfd;
if (ai == NULL)
return (-1);
if ( (sockfd = socket(ai->ai_family, ai->ai_socktype,
ai->ai_protocol)) < 0) {
sockfd = gai_connect(ai->ai_next);
return(sockfd);
}
if (connect(sockfd, ai->ai_addr, ai->ai_addrlen) == 0)
return (sockfd);
else {
if (close(sockfd) ==-1) {
perror("close() failed.");
exit(1);
}
sockfd = gai_connect(ai->ai_next);
return (sockfd);
}
}
===============================================================================
In figure 2.6 we define our gai_connect() function to traverse the
linked-list returned by getaddrlist(). The function tries to create a
socket and connect to the given host, returning the connected socket on
success. If it fails to do either, gai_connect() calls itself, passing the
next structure in the linked-list (ai->next) as its argument. This method
of a function calling itself is known as recursion. If gai_connect()
traverses the linked-list and is unable to establish a connection (ai ==
NULL), -1 will be returned through the recursive calls to gai_connect().
Figure 2.7 illustrates the steps that gai_connect() goes through to
connect a socket.
Note: We do not call freeaddrinfo() from inside the function, we
handle this elsewhere for added flexibility.
===============================================================================
Figure 2.7
_________________
| gai_connect() |
|_______________|
|
________|________ yes _______________
| ai == NULL |______| return (-1) |
|_______________| |_____________|
no |
________|_______ fail ___________________________________
| socket() |_______| sockfd=gai_connect(ai->ai_next) |
|______________| |_________________________________|
success | |
| _________|_________
| | return (sockfd) |
| |_________________|
|
_______|_______ fail ___________________________________
| connect() |_______| sockfd=gai_connect(ai->ai_next) |
|_____________| |_________________________________|
success | |
| _________|_________
| | return (sockfd) |
| |_________________|
|
_________|_________
| return (sockfd) |
|_________________|
===============================================================================
If ai equals NULL, gai_connect() returns -1 indicating a failure to
connect any sockets. If not, the function continues and tries to create a
socket with the current structure. If this fails, gai_connect() calls itself,
passing to it the next addrinfo structure in the linked list. If it
succeeds, gai_connect() continues and tries to connect the socket. If
connect() fails, gai_connect() calls itself and tries the next structure
in the linked list, otherwise it returns the connected socket. If
gai_connect() succeeds somewhere along the linked-list, the connected
socket is returned through all the recursive calls to gai_connect(). If
the function encounters NULL, the end of the list, -1 is returned through
all the recursive calls to gai_connect(), indicating a failure to connect
to the given host.
We now use these functions to creat a simple daytime client.
Figure 2.8 shows the source code for our client.
===============================================================================
Figure 2.8
#include "tutorial.h" /* defined at the end of our tutorial */
int main(int argc, char **argv) {
int sockfd;
char buff[MAXLINE];
struct addrinfo *ai;
if (argc < 2) {
fprintf(stderr, "USAGE: %s .\n", argv[0]);
exit(1);
}
ai = getaddrlist(argv[1], "daytime", AI_CANONNAME, AF_UNSPEC,
SOCK_STREAM);
if ( (sockfd = gai_connect(ai)) < 0) {
perror("gai_connect() failed."); /* print err from last
call to connect() */
exit(1);
}
freeaddrinfo(ai);
if (read(sockfd, buff, MAXLINE) < 0) { /* read time from server */
perror("read() failed.");
exit(1);
}
if (fputs(buff, stdout) == EOF) { /* print time to screen */
perror("fputs() failed.");
exit(1);
}
if (close(sockfd) == -1) { /* done with socket */
perror("close() failed.");
exit(1);
}
}
===============================================================================
We now step through the source code of our daytime client.
-We creat a socket descriptor, a buffer to hold the server's
reply, and a pointer to type struct addrinfo to refrence the linked-list
returned by getaddrlist().
-We then call getaddrlist(), passing to it the hostname or IP
address given at the command line, "daytime" as the service, AI_CANONNAME
so that the server's canonnical name is also returned, AF_UNSPEC to
recieve both IPv4 and IPv6 addresses and SOCK_STREAM to indicate that we
will be making a TCP connection. The return value is stored in the
pointer, ai.
-Next, we call gai_connect(), passing ai to it, which traverses
the linked-list and tries to establish a connection. gai_connect() will
either return -1 (fail) or a connected socket descriptor (success) which
is stored in sockfd.
-Lastly, we read a line of data from the server and print it to
the screen.
Note: We call read without checking for a short count. It is
possible that the socket's buffer in the kernel was full and
not all data being sent was returned by read(), in which case
we would need to call read again. However, handling this
possibility is out of the scope of this tutorial; here we are
concerned with _making_ a connection, not handeling one.
Compiling and running our program gives the following results:
[root@localhost code]# ./a.out localhost
Sat Oct 17 00:38:26 1998
[root@localhost code]#
We are now ready to write the server side of this application. For
this we define a new function to handle the creation of a listening socket
and binding an address. Figure 2.9 shows our gai_listen() function.
===============================================================================
Figure 2.9
#include "tutorial.h" /* defined at end of tutorial */
int gai_listen(struct addrinfo *ai) {
const int on = 1;
int listenfd;
if (ai == NULL)
return(-1);
if ( (listenfd = socket(ai->ai_family, ai->ai_socktype,
ai->ai_protocol)) < 0) {
listenfd = gai_listen(ai->ai_next);
return (listenfd);
}
if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on,
sizeof(on)) < 0) {
perror("setsockopt() failed.");
exit(1);
}
if (bind(listenfd, ai->ai_addr, ai->ai_addrlen) == 0) {
if (listen(listenfd, LISTENQ) < 0) {
perror("listen() failed.");
exit(1);
}
return (listenfd);
} else {
if (close(listenfd) == -1) {
perror("close() failed.");
exit(1);
}
listenfd = gai_listen(ai->ai_next);
return (listenfd);
}
}
===============================================================================
Our gai_listen() function uses the same method of recursion to
step through the linked-list returned by getaddrlist() as our
gai_connect() function does. Figure 2.10 illustrates the steps
gai_listen() goes through to bind a port.
===============================================================================
Figure 2.10
_________________
| gai_listen() |
|_______________|
|
________|________ yes _______________
| ai == NULL |______| return (-1) |
|_______________| |_____________|
no |
________|_______ fail ____________________________________
| socket() |_______| listenfd=gai_listen(ai->ai_next) |
|______________| |__________________________________|
success | |
| ___________|__________
| | return (listenkfd) |
| |____________________|
|
_______|_______ fail ____________________________________
| bind() |_______| listenfd=gai_listen(ai->ai_next) |
|_____________| |__________________________________|
success | |
| _________|___________
| | return (listenfd) |
| |___________________|
|
_________|___________
| return (listenfd) |
|___________________|
===============================================================================
gai_listen() takes the pointer passed to it and checks whether or
not it is NULL, indicating the end of the linked list. If NULL is
detected, -1 is returned. It then tries to creat a socket using the
socket() function. If it fails to do so, it calls itself, passing to it
the next stucture in the linked-list. If it succeeds in creating a socket
it continues and calls bind(). Our function then tries to bind the daytime
port (13) and the address within the current structure. That address is
the result of a previous call to getaddrlist(), specifying the local host
as the hostname argument. If the call to bind() succeeds, gai_listen()
calls listen() for the socket and then returns the listening socket, now
ready to accept connections. If it fails to bind the socket, the function
calls itself, passing to it the next structure in the linked list. If NULL
is encountered at anytime, -1 is returned back through the recursive calls
to gai_listen(). If our function is able to successfully bind any of the
structures along the linked-list, the listening socket is returned back
through all the recursive calls to itself.
We now write the server side of our example. Our daytime server
uses the gai_listen() function we just defined to give it protocol
independence. Figure 2.11 shows the source code for our daytime server.
===============================================================================
Figure 2.11
#include "tutorial.h" /* defined at the end of the tutorial */
int main(int argc, char **argv) {
int listenfd, sockfd;
time_t ticks;
char buff[MAXLINE];
struct addrinfo *ai;
if (argc < 2) {
fprintf(stderr, "USAGE: %s .\n", argv[0]);
exit(1);
}
ai = getaddrlist(argv[1], "daytime", AI_PASSIVE, AF_UNSPEC,
SOCK_STREAM);
if ( (listenfd = gai_listen(ai)) < 0) {
perror("gai_listen() failed."); /* print err from last
call to bind() */
exit(1);
}
while(1) {
if (sockfd = accept(listenfd, (struct sockaddr *)NULL,
NULL) < 0) {
perror("accept() failed.");
exit(1);
}
ticks = time(NULL);
snprintf(buff, MAXLINE, "%.24s\r\n", ctime(&ticks));
if (write(sockfd, buff, strlen(buff)) < 0) {
perror("write() failed.");
exit(1);
}
if (close(sockfd) == -1) {
perror("close() failed.");
exit(1);
}
}
}
===============================================================================
We now step through the source code of our daytime server.
-We first declare two socket descriptors; listenfd to wait for
incoming connections and sockfd to service those connections. We then
decalre our ticks variable to hold the return value of time(), a buffer to
hold the human readable string returned from ctime() and a pointer to type
struct addrinfo for calling getaddrlist() and gai_listen().
-The first function we call is getaddrlist(). The arguments we
pass to it are; the address specified at the command line that the user
wishes to bind, the service name ("daytime"), AI_PASSIVE to declare that
this will be a listening socket, AF_UNSPEC to request that getaddrinfo()
return structures containing both IPv4 and IPv6 addresses for the local
host, and finnally SOCK_STREAM to declare that the socket will be used for
TCP connections. We store the return value in our pointer ai.
-Next we call gai_listen(), passing to it our pointer. If it
successfully creats a socket and binds the specified address, a socket
descriptor is returned. If it fails, -1 is returned, we print the error
from the last call to bind, and exit.
-Our program now enters a loop, waiting for a connection and then
servicing the client.
-The server blocks into a call to accept(), waiting for a client
to connect. We pass the listening socket to accept but NULL for the other
two arguments. The second and third argument to accept() is a structure of
type sockaddr to hold the clients address and the strucutres length. We do
not need to know the address of the client for this example so we set the
final two arguments to NULL.
-After recieving a connection, the server calls time() and stores
the return value in our ticks variable. time() returns the number of
seconds since the Epoch (00:00:00 UTC, Jan. 1, 1970).
-We call ctime() to convert this value into a human-readable form
and store it in our buffer.
-We then write() the buffer to the client and close the connected
socket. The server returns to the top of the loop and blocks into another
call to accept().
Note: There are better ways to handle the client connections, e.g.
checking for ECONNABORTED if accept() returns an error in which
case we do not need to exit, or checking for a short count when
calling write(), but these techniques are beyond the scopr of this
tutorial.
If we did not use our gai_listen() function, we would of had to
creat a sockaddr_in or sockaddr_in6 structure and set the sin_family
member to either AF_INET or AF_INET6 before we could call socket() or
bind(). Choosing one or the other would have made our server dependant on
IPv4 or IPv6. Using #ifdefs to determine whether to use sockaddr_in or
sockaddr_in6 wherever we required a sockaddr structure would have been
possible. However, this approach would be terribly cumbersome for any
non-trivial amount of code.
We now run our server. We first kill the current daytime server
running on the system and start our own server in the background. We then
[root@localhost code]# ./dtserv 0.0.0.0 &
start the daytime client that we wrote earlier and connect to the server.
We get the following output.
[root@localhost code]# ./dtcli localhost
Sat Oct 17 10:00:42 1998
[root@localhost code]#
In part 2 of our tutorial on writing protocol independent code, we
have show the use of getaddrinfo() in creating TCP client/server
applications. In part 3, we will expand upon this and show how
getaddrinfo() can also be used to creat protocol independent UDP
applications.
-J. Kennedy
jkennedy@texas.net
==== tutorial.h =============================================================
#include <stdio.h> /* for fputs() */
#include <errno.h> /* for perror() */
#include <netdb.h> /* for addrinfo */
#include <sys/types.h> /* for socket() and connect() */
#include <sys/socket.h>
#include <unistd.h> /* for read() and close() */
#define MAXLINE 4096
#define LISTENQ 1024
struct addrinfo *getaddrlist(const char *host, const char *port, int flags,
int family, int socktype);
int gai_connect(struct addrinfo *ai);
int gai_listen(struct addrinfo *ai);
===============================================================================
Thanks to the #C crew, Avalonian and Weaver for answering my questions and
providing helpful documentaion, and to W. Richard Stevens for his
invaluable book, "Unix Network Programming, vol. 1", which helped me
solidify the concepts discussed here.
This page is Copyright © 1998 By
C Scene. All Rights Reserved