2.2. Host names and addresses

The previous section described how to look up the port number of a service using the getservbyname() API. This section complements that section by desribing the procedure for looking up an IP address using the gethostbyname() procedure. This section will also describe the system routines for converting from "dotted decimal" IP address format to the 32-bit address format, and back again.

To start with, we'll explain the 'inet_addr' API. It's the system API to convert from dotted decimal formatted IP address to a 32-bit network address. This API is explained in IBM's manuals at the following link: http://publib.boulder.ibm.com/pubs/html/as400/v4r5/ic2924/info/apis/inaddr.htm

IBM tells us that the prototype for the inet_addr procedure looks like this:

         unsigned long inet_addr(char *address_string)
     

This means that the procedure is named 'inet_addr', and it accepts one parameter, called 'address_string', which is a pointer to a character string. It returns an unsigned long integer, which in RPG is expressed as '10U 0'.

This is pretty straight forward in RPG. We simply prototype it like this:

         D inet_addr       PR            10U 0 ExtProc('inet_addr')
         D  address_str                    *   value options(*string)
     

Reading further down the IBM page on this procedure, you'll see that it returns the 32-bit network address when successful. And returns a -1 when it's not successful.

Now you should be confused! If it returns an 'unsigned' integer, how can it possibly return a negative number? By definition, an unsigned integer CANNOT be a negative number. In C, when the return value is compared against -1, the computer actually generates a 32-bit integer with the value of -1, and then compares, bit for bit, whether they match, even though one is signed and one isn't, as long as they have the same bit values, it thinks they're the same.

So the question is, what 'unsigned' value has the same binary value as the signed integer value of -1? Well, if you're familiar with the twos complement format, you'll know in order to make a positive number negative, you take it's binary complement, and add 1. Therefore, x'00000001' is a positive 1, and it's binary complement would be x'FFFFFFFE'. If we add 1 to that, we get x'FFFFFFFF'. IF we convert that to decimal, we get the value 4294967295.

Do you still think this is straight-forward? :) To simplify things somewhat, we'll define a named-constant called 'INADDR_NONE' (which, though they dont tell us that, is the name of the same constant in the C header file) When we check for errors, we'll simply check against that constant.

         D INADDR_NONE     C                   CONST(4294967295)
     

Once we have all the definitions straightened out, this is quite easy to call in RPG:

         c                   eval      IP = inet_addr('192.168.66.21')
         c                   if        IP = INADDR_NONE
         c*  conversion failed
         c                   endif
     

To convert an IP address from 32-bit network format back to dotted decimal uses a similar API called 'inet_ntoa'. The IBM manual for this is found at this link: http://publib.boulder.ibm.com/pubs/html/as400/v4r5/ic2924/info/apis/inntoa.htm

IBM tells us that the prototype for inet_ntoa looks like this:

         char *inet_ntoa(struct in_addr internet_address)
     

The procedure is called inet_ntoa. It accepts one parameter, which is a 'in_addr' structure, passed by value. It returns a pointer to a character string. That's nice. But what the heck is an 'in_addr' structure?

You'll note that the return value doesn't even bother to explain this structure. If you have the "system openness includes" installed on your system, you'll be able find the definition for in_addr in the file QSYSINC/NETINET in member IN. It looks like this:

       struct in_addr {
            u_long  s_addr;
        };
     

What this means is that the structure contains nothing but a single, unsigned, 32-bit integer. That's a bit confusing to try to figure out from IBM's documentation, but it's easy to code. The prototype for inet_ntoa in RPG will look like this:

         D inet_ntoa       PR              *   ExtProc('inet_ntoa')
         D  internet_addr                10U 0 value
     

Now, when you call inet_ntoa, it will either return NULL (which in RPG is called *NULL) if there's an error, or it will return a pointer to a character string. Unfortunately, we don't know how long this character string that it returns is! In C, you'd find the length of this string by searching for the 'null' character at the end of the string.

Fortunately, the RPG compiler developers created a BIF for just that purpose. This BIF is called %str, and it accepts a pointer, and returns the string beginning with the pointer, and ending with the trailing null. So we can call inet_ntoa like this:

         c                   eval      ptr = inet_ntoa(IP)
         c                   if        ptr = *NULL
         c*  conversion failed
         c                   else
         c                   eval      dottedIP = %str(ptr)
         c                   endif
     

The final API in this chapter is the 'gethostbyname' API, which does a DNS lookup on a given host name, and returns a 'host entry structure' that contains the details about that host. The main piece of data that we're interested in from that structure is the host's IP address.

IBM's manual page for this procedure is found at this link: http://publib.boulder.ibm.com/pubs/html/as400/v4r5/ic2924/info/apis/ghostnm.htm

The prototype IBM lists for this API looks like this:

         struct hostent *gethostbyname(char *host_name)
     

Which means that the API is 'gethostbyname', it accepts one parameter which is a pointer to a character string containing the host name. It returns a pointer to a 'hostent' structure.

So, the RPG prototype for the gethostbyname API looks like this:

         D gethostbyname   PR              *   extproc('gethostbyname')
         D   host_name                     *   value options(*string)
     

Looking down at the "return value" of the API, it tells us that it either returns NULL (or, in RPG, *NULL) if there's an error, or it returns a pointer to a 'hostent' structure. The hostent structure is described as being in this format:

          struct hostent {
            char   *h_name;
            char   **h_aliases;
            int    h_addrtype;
            int    h_length;
            char   **h_addr_list;
          };
    
          #define h_addr  h_addr_list[0]
     

This means that the hostent data structure contains 5 different items, a pointer to a character string containing the 'correct' hostname, a pointer to an array of pointers, each element pointing to an 'alias' of the hostname, an integer containing the address type, an integer containing the length of each address, and a pointer to an array of pointers, each one pointing to a character string.

The '#define' statement tells us that whenever we refer to 'h_addr' in this structure, that it will actually return the first pointer in the h_addr_list array.

If you read on, under the "Return" value, it explains that both of the arrays pointed to here are 'NULL-terminated lists'. This means that if you loop thru the array of pointers, you'll know you've reached the end when you find a 'NULL' (or *NULL in RPG).

It further tells you that each element of the h_addr_list array actually points to a structure of type 'in_addr', rather than a character string, as the structure definition implies. Why does it do that? Because gethostbyname can be used with other protocols besides TCP/IP. In the case of those other protocols, it might point to something besides an 'in_addr' structure. In fact, we could use the 'h_addrtype' member of the structure to determine what type of address was returned, if we so desired. However, this document is only intended to work with TCP/IP addresses.

Note, also, that we already know from our experiences with inet_ntoa above that 'in_addr' structures are merely an unsigned integer in disguise. Therefore, we can define the 'hostent' structure as follows:

         D p_hostent       S               *
         D hostent         DS                  Based(p_hostent)
         D   h_name                        *
         D   h_aliases                     *
         D   h_addrtype                  10I 0
         D   h_length                    10I 0
         D   h_addr_list                   *
         D p_h_addr        S               *   Based(h_addr_list)
         D h_addr          S             10U 0 Based(p_h_addr)
     

So, if we want to refer to the first IP address listed under h_addr_list, we can now just refer to h_addr. If we need to deal with later addresses in the list (which is very unusual, in my experiences) we can do so like this:

         D addr_list       S               *   DIM(100) Based(h_addr_list)
         D p_oneaddr       S               *
         D oneaddr         S             10U 0 based(p_oneaddr)
    
         CL0N01Factor1+++++++Opcode&ExtFactor2+++++++Result++++++++Len++D+HiLo    
         C                   do        100           x
         C                   if        addr_list(X) = *NULL
         C                   leave
         C                   endif
         C                   eval      p_oneaddr = addr_list(X)
         C***  oneaddr now contains the IP address of this
         C***   position of the array, use it now...
         C                   enddo
     

As I stated, however, it's very unusual to need to work with anything but the first address in the list. Therefore, 'h_addr' is usually what we'll refer to when we want to get an IP address using gethostbyname.

And we'd normally call gethostbyname like this:

         c                   eval      p_hostent = gethostbyname('www.ibm.com')
         c                   if        p_hostent <> *NULL
         c                   eval      IP = h_addr
         c                   endif
     

Now that we know the basic syntax to call these APIs, lets put them together and write a program that uses them. When we write a client program for TCP/IP, usually the first thing we need to do is figure out the IP address to connect to. Generally, this is supplied by the user, and the user will either supply an IP address directly (in the dotted-decimal format) or he'll supply a hostname that needs to be looked up.

The way that we go about doing this is by first calling 'inet_addr' to see if the host is a valid dotted-decimal format IP address. If it is not, we'll actually call 'gethostbyname' to look the address up using DNS and/or the host table. In our example program, we'll then either print a 'host not found' error message, or we'll call 'inet_ntoa' to get a dotted-decimal format IP address that we can print to the screen.

So here's our example of looking up an IP address for a hostname:

    File: SOCKTUT/QRPGLESRC, Member: DNSLOOKUP
         H DFTACTGRP(*NO) ACTGRP(*NEW)
    
         D inet_addr       PR            10U 0 ExtProc('inet_addr')
         D  address_str                    *   value options(*string)
    
         D INADDR_NONE     C                   CONST(4294967295)
    
         D inet_ntoa       PR              *   ExtProc('inet_ntoa')
         D  internet_addr                10U 0 value
    
         D p_hostent       S               *
         D hostent         DS                  Based(p_hostent)
         D   h_name                        *
         D   h_aliases                     *
         D   h_addrtype                  10I 0
         D   h_length                    10I 0
         D   h_addr_list                   *
         D p_h_addr        S               *   Based(h_addr_list)
         D h_addr          S             10U 0 Based(p_h_addr)
    
         D gethostbyname   PR              *   extproc('gethostbyname')
         D   host_name                     *   value options(*string)
    
         D host            S             32A
         D IP              S             10U 0
         D Msg             S             50A
    
         c     *entry        plist
         c                   parm                    host
    
         c                   eval      IP = inet_addr(%trim(host))
    
         c                   if        IP = INADDR_NONE
         c                   eval      p_hostent = gethostbyname(%trim(host))
         c                   if        p_hostent <> *NULL
         c                   eval      IP = h_addr
         c                   endif
         c                   endif
    
         c                   if        IP = INADDR_NONE
         c                   eval      Msg = 'Host not found!'
         c                   else
         c                   eval      Msg = 'IP = ' + %str(inet_ntoa(IP))
         c                   endif
    
         c                   dsply                   Msg
    
         c                   eval      *inlr = *on
     

This program can be compiled with: CRTBNDRPG DNSLOOKUP SRCFILE(xxx/QRPGLESRC)

And you can call it like this: CALL DNSLOOKUP PARM('www.yahoo.com') or CALL DNSLOOKUP PARM('www.scottklement.com') or with a dotted-decimal address like CALL DNSLOOKUP PARM('192.168.66.21')

After all that explanation, the code itself doesn't seem so complicated, does it? :)