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? :)