Socket Coding, Part Two

Introduction

 Hey again. In an effort to actually complete this series of articles on socket coding, I started the follow up article as soon as possible. I assume that at this point you've read socket coding part one, and/or already know the basics. Without furhter ado, let's continue on our journey into the api.

Who the hell are you?!

One important thing that wasn't covered last time is hostname/dotted ip string translation. This is basically converting your standard hostnames, for example, www.k-rad_leet.org into 142.325.23.666, or whatever. The problem that stands is that when we are filling in the sin_addr field of our socket descriptor, the information is copied differently depending upon the format. Here we'll look at one way of converting hostname/ips into useable format:

void xlate_host( struct sockaddr_in *sa, char *hostname, unsigned int port )
{
   struct hostent *hp;
   unsigned long ip;

   hp = gethostbyname( hostname );
   if( hp == NULL )
   {
      ip = inet_addr( hostname );
      memcpy( (char*)&sa->sin_addr,&ip,4 );
   }
   else memcpy( (char*)&sa->sin_addr,hp->h_addr,hp->h_length );

   sa->sin_family = AF_INET;
   sa->sin_port = htons( port );
}

Here we first call gethostbyname which returns a pointer to a hostent structure containing the information we want in the h_addr field. If the function returns NULL then we know that it failed, so we can assume that the address that was passed wasn't a hostname, e.g. www.oober.org, and is a dotted string ip so we pass the string to the inet_addr() function which returns the ip packed into a long int. Once the host has been converted, it is copied into the socket descriptor.

Hello? Hello?!?

 It's about time we looked at handling incoming connections, which is essentially the basis of any daemon you can imagine. We'll be developing one of the simpler daemons, to handle the ident protocol. For those who aren't sure what this is, when using a *nix box you'll find that when you connect to somewhere you'll be user@yourip, where user is your ident. The majority of IRC clients have an ident daemon, we'll be writing a (much) smaller one.

Before we can begin we have to look at receiving incoming connections. This is achieved in much the same way as with connect() with a few important differences.

There are four steps to creating an open incoming socket:

Once this connection has been made the same rules apply for send() and recv() as for sockets initiating contact.

In a few minutes we'll be looking at the code to the ident daemon, but there's little point showing you the code without explaining the basics of the protocol. Let's delve.

RFC1413 - The Identification Protocol

To cut a long RFC short, here's basically what the specification demands happen:

k, that doesn't sound too hard. The thing to note is that this daemon isn't fully featured, it's merely for demostrative purposes, but it will work on 99.9% of ftp daemons, irc servers etc. If you'd like to expand it, simply add functions to handle the different response types from the RFC

Ident daemon source

To continue...

It would be easy to assume that now we know enough about sockets to write any app we'd like, which is true to a point. Let's look at the problem we'd encounter with an example. The majority of people have used a telnet program, as we type characters they're sent to the remote host and then echoed back to us, but at the same time any characters sent to us from the remote host are echoed on the screen.. herein lies our problem.

The recv() function when used with blocking sockets will wait for data to arrive at the socket until the call times out, this is usually a fairly long amount of time for our program to be sitting completely idle if no data arrives. We need a way for the socket to notify us when there is data to be read so that we can then read it from the socket, solving this problem. Thankfully help arrives in the form of the select() function.

Press '1' now for red hot pron action.

The select() function allows us to poll any amount of sockets for either read, write, or error status. To do this we need a set of sockets, thankfully there are a bunch of macros defined to make this nice an' easy:

FD_ZERO( *set )          This clears the set completely, usually used for initialisation.

FD_SET( socket,*set )    Adds socket socket to the set.

FD_ISSET( socket,*set )  Is nonzero if socket has been added to the set via FD_SET

FD_CLR( socket,*set )    Removes socket socket from the set.

Basically we add the sockets we wish to receive notification of to the set, and then pass this as a parameter to select():

// assume at this point we have a connected socket called 'sock'

fd_set s_set;

FD_ZERO( &s_set );
FD_SET( sock,&s_set );

select( 0,&s_set,NULL,NULL,NULL );

The first parameter is ignored, it is simply there for compatibility (allegedly). The second. third, and fourth params are read notification, write notification and error notification. The final parameter is an optional timeout value. Passing NULL indicates we want to wait forever (much like we would have to with recv())

The timeout structure is of type struct timeval and consists of two fields, tv_usec and tv_sec being time in milliseconds and in seconds respectively, for example to wait for approx. 1.5 seconds for a write event to occur:

struct timeval tv;
fd_set s_set;

FD_ZERO( &s_set );
FD_SET( sock,&set );
tv.tv_sec = 1;
tv.tv_usec = 500;
select( 0,NULL,&s_set,NULL,&tv );

Now, if select returns a nonzero result, then we have an event, otherwise a timeout is indicated. It is important to note that after calling select() we must use FD_SET again to reset the status of the set before the next call to select(). The mainloop of a typical program might look something like this:

while( !endofprogram )
{
   Set notification events via FD_SET
   if( select() returns a nonzero value )
      recv() the information
   The rest of the program loop
}

Obviously we should set the timeout value of tv to some small value, maybe 500 milliseconds so that we don't miss any information.

All of the stuff explained above can be seen in the included program. As this prog stands it isn't very useful, telnet to localhost on port 6969 and anything you type will be displayed by the program and echoed back to the telnet client.

Welcome to the closing...

Well, I think that just about sums up the main functions that you'd need in writing a winsock app. It may have been noticed that I've avoided all of the winsock specific message based service calls, a message based windows approach will be covered in the next part of this series. With the information in these docs you will be able to write not only windows progs, but all of the theory is 100% compatible with berkeley (ie, *nix) sockets and let's face it, linux is much more fun to code sockets apps for. ;-]

So next time we'll tackle the gunf you need to make a nice leet windows specific application. Including all of those WSA* functions you might have seen in the winsock api ref. Welp, hope you enjoyed and maybe learnt something without too much confusion. Laters...

NRoC [29/07/1999 15:16]