< Querying DNS | RnR Main | Network Location Awareness (NLA) >
What do we have in this chapter 8 part 6?
|
Another Example: HOSTENT, gethostbyname() and gethostbyaddr()
The following example demonstrates the use of the HOSTENT structure with the gethostbyname() function. Create a new empty Win32 console application project and add the project/solution name.
Add the following source code.
// Link to ws2_32.lib #include <winsock2.h> #include <ws2tcpip.h> #include <stdio.h> |
int main(int argc, char **argv)
{
// Declare and initialize variables
WSADATA wsaData;
DWORD dwError;
int i = 0;
struct hostent *remoteHost;
char *host_name;
struct in_addr addr;
char **pAlias;
// Validate the parameters
if (argc != 2)
{
printf("Usage: %s ipv4address\n", argv[0]);
printf(" or\n");
printf(" %s hostname\n", argv[0]);
printf(" to return the host\n");
printf(" %s 127.0.0.1\n", argv[0]);
printf(" to return the IP addresses for a host\n");
printf(" %s www.google.com\n", argv[0]);
return 1;
}
// Initialize Winsock
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
printf("WSAStartup() failed with error code %d\n", WSAGetLastError());
return 1;
}
else
printf("WSAStartup() should be fine!\n");
host_name = argv[1];
// If the user input is an alpha name for the host, use gethostbyname()
// If not, get host by addr (assume IPv4)
if (isalpha(host_name[0]))
{
// host address is a name
printf("Calling gethostbyname() with %s\n", host_name);
remoteHost = gethostbyname(host_name);
}
else
{
printf("Calling gethostbyaddr() with %s\n", host_name);
addr.s_addr = inet_addr(host_name);
if (addr.s_addr == INADDR_NONE)
{
printf("The IPv4 address entered must be a legal address!\n");
return 1;
}
else
remoteHost = gethostbyaddr((char *) &addr, 4, AF_INET);
}
if (remoteHost == NULL)
{
dwError = WSAGetLastError();
if (dwError != 0)
{
if (dwError == WSAHOST_NOT_FOUND)
{
printf("Host not found!\n");
return 1;
}
else if (dwError == WSANO_DATA)
{
printf("No data record found!\n");
return 1;
}
else
{
printf("Function failed with error code %ld\n", dwError);
return 1;
}
}
}
else
{
printf("Function returned:\n");
printf("\tOfficial name: %s\n", remoteHost->h_name);
for (pAlias = remoteHost->h_aliases; *pAlias != 0; pAlias++)
{
printf("\tAlternate name #%d: %s\n", ++i, *pAlias);
}
printf("\tAddress type: ");
switch (remoteHost->h_addrtype)
{
case AF_INET:
printf("AF_INET\n");
break;
case AF_INET6:
printf("AF_INET6\n");
break;
case AF_NETBIOS:
printf("AF_NETBIOS\n");
break;
default:
printf(" %d\n", remoteHost->h_addrtype);
break;
}
printf("\tAddress length: %d\n", remoteHost->h_length);
i = 0;
while (remoteHost->h_addr_list[i] != 0)
{
addr.s_addr = *(u_long *) remoteHost->h_addr_list[i++];
printf("\tIP Address #%d: %s\n", i, inet_ntoa(addr));
}
}
return 0;
}
Build and run the project.
-------------------------------------------------------
The following code illustrates initializing the DNS query:
WSAQUERYSET qs;
AFPROTOCOLS afp [2] = {{AF_INET, IPPROTO_UDP},{AF_INET, IPPROTO_TCP}};
GUID hostnameguid = SVCID_INET_HOSTADDRBYNAME;
DWORD dwLength = sizeof(WSAQUERYSET) + sizeof(HOSTENT) + 2048;
char buff[dwLength];
HANDLE hQuery;
int ret;
qs = (WSAQUERYSET *)buff;
memset(&qs, 0, sizeof(qs));
qs.dwSize = sizeof(WSAQUERYSET);
qs.lpszServiceInstanceName = argv[1];
qs.lpServiceClassId = &hostnameguid;
qs.dwNameSpace = NS_DNS;
qs.dwNumberOfProtocols = 2;
qs.lpafpProtocols = afp;
ret = WSALookupServiceBegin(&qs, LUP_RETURN_NAME │ LUP_RETURN_BLOB, &hQuery);
if (ret == SOCKET_ERROR)
// Error
Setting up the query is quite similar to our previous example. The most noticeable change is that we use the predefined GUID SVCID_INET_ HOSTADDRBYNAME. This is the GUID that identifies host name queries. The lpszServiceInstanceName is the host name that we want to resolve. Because we are resolving host names through DNS, we need to specify only NS_DNS for dwNameSpace. Finally, lpafProtocols is set to an array of two AFPROTOCOLS structures, which defines the TCP/IP and UDP/IP protocols as those that our query is interested in. Once you establish the query, you can call WSALookupServiceNext() to return data.
char buff[sizeof(WSAQUERYSET) + sizeof(HOSTENT) + 2048];
DWORD dwLength = sizeof(WSAQUERYSET) + sizeof(HOSTENT) + 2048;
WSAQUERYSET *pqs;
HOSTENT *hostent;
pqs = (WSAQUERYSET *)buff;
pqs->dwSize = sizeof(WSAQUERYSET);
ret = WSALookupServiceNext(hQuery, 0, &dwLength, pqs);
if (ret == SOCKET_ERROR)
// Error
WSALookupServiceEnd(hQuery);
hostent = (HOSTENT *) pqs->lpBlob->pBlobData;
Because a DNS name space provider returns the host information as a BLOB, you need to supply a sufficiently large buffer. Use a buffer equal in size to a WSAQUERYSET structure, plus a HOSTENT structure, plus 2048 bytes for good measure. Again, if this were insufficient, the call would fail with WSAEFAULT. In a DNS query, the host information is returned within the HOSTENT structure, even if a host name is associated with multiple IP addresses. That is why you don't need to call WSALookupServiceNext() multiple times.
Now comes the tricky part, decoding the BLOB structure that the query returns. The HOSTENT structure is defined as:
typedef struct hostent {
char FAR * h_name;
char FAR * FAR * h_aliases;
short h_addrtype;
short h_length;
char FAR * FAR * h_addr_list;
} HOSTENT;
When the HOSTENT structure is returned as the BLOB data, the pointers within the structure are actually offsets into memory where the data lies. The offsets are from the start of the BLOB data, requiring you to fix up the pointers to reference the absolute memory location before you can access the data. Figure 8-1 shows the HOSTENT structure and memory layout returned. The DNS query is performed on the host name riven, which has a single IP address and no aliases. Each field in the structure has the offset value. To correct this so the fields reference the right location, you need to add the offset value to the address of the head of the HOSTENT structure. This needs to be performed on the h_name, h_aliases, and h_addr_list fields. In addition, the h_aliases and h_addr_list fields are an array of pointers. Once you obtain the correct pointer to the array of pointers, each 32-bit field in the references location is made up of offsets. If you take a look at the h_addr_list field in Figure 8-1, you'll see that the initial offset is 16 bytes, which references the byte after the end of the HOSTENT structure. This is the array of pointers to the 4-byte IP address. However, the first pointer in the array is an offset of 28 bytes. To reference the correct location, take the address of the HOSTENT structure and add 28 bytes, which points to a 4-byte location with the data 0x9D36B9BA, which is the IP address 157.54.185.186. You then take the 4 bytes after the entry with the offset of 28 bytes, which is 0. This signifies the end of the array of pointers. If multiple IP addresses were associated with this host name, another offset would be present and you would fix the pointer exactly as in the first case. The same procedure is done to fix the h_aliases pointer and the array of pointers it references. In this example, there are no aliases for our host. The first entry in the array is 0, which indicates that you don't have to do any further work for that field. The last field is the h_name field, which is easy to correct; simply add the offset to the address of the HOSTENT structure and it points to the start of a null-terminated string.
Figure 8-1 HOSTENT BLOB format
The code needed to fix these offsets into real addresses is simple, although quite a bit of pointer arithmetic is involved. To fix the h_name field, a simple offset adjustment such as the following will do:
hostent->h_name = (PCHAR)((DWORD_PTR)hostent->h_name + (PCHAR)hostent);
To fix the array of pointers, as in the h_aliases and h_addr_list fields, requires a bit more code, but only to traverse the array and fix the references until a null entry is hit. The code looks like this:
PCHAR *addr;
if (hostent->h_aliases)
{
addr = hostent->h_aliases = (PCHAR)((DWORD_PTR)hostent->h_aliases + (PCHAR)hostent);
while (addr)
{
addr = (PCHAR)((DWORD_PTR)addr + (PCHAR *)hostent);
addr++;
}
}
The code simply steps through each array entry and adds the starting address of the HOSTENT structure to the given offset, which becomes the value for that entry. Of course, once you hit an array entry whose value is 0, you stop. The same process needs to be applied to the h_addr_list field as well. Once the offsets are fixed, you can use the HOSTENT structure as you normally would.
< Querying DNS | RnR Main | Network Location Awareness (NLA) >