Querying a Service

Now that you know how to register a service within a name space, you'll take a look at how a client can query the name space for a given service so that the client can obtain information about the service for communication purposes. Name resolution is quite a bit simpler than service registration, even though name resolution uses three functions for querying: WSALookupServiceBegin, WSALookupServiceNext, and WSALookupServiceEnd.

The first step when performing a query is to call WSALookupServiceBegin, which initiates the query by setting up the constraints within which the query will act. The function prototype is as follows:

 INT WSALookupServiceBegin ( LPWSAQUERYSET lpqsRestrictions, DWORD dwControlFlags, LPHANDLE lphLookup ); 

The first parameter is a WSAQUERYSET structure that places constraints on the query, such as limiting the name spaces to query. The second parameter, dwControlFlags, determines the depth of the search. Table 10-7 contains the various possible flags and their meanings. These flags affect how the query behaves as well as which data is returned from the query. The last parameter is of type HANDLE and is initialized upon function return. The return value is 0 on success; otherwise, SOCKET_ERROR is returned. If one or more parameters are invalid, WSAGetLastError returns WSAEINVAL. If the name is found in the name space but no data matches the given restrictions, the error is WSANO_DATA. If the given service does not exist, WSASERVICE_NOT_FOUND is the error.

Table 10-7. Control flags

Flag Meaning
LUP_DEEP In hierarchical name spaces, query deep as opposed to the first level.
LUP_CONTAINERS Retrieve container objects only. This flag pertains to hierarchical name spaces only.
LUP_NOCONTAINERS Do not return any containers. This flag pertains to hierarchical name spaces only.
LUP_FLUSHCACHE Ignore cached information, and query the name space directly. Note that not all name providers cache queries.
LUP_FLUSHPREVIOUS Instruct the name provider to discard the information set previously returned. This flag is typically used after WSALookupServiceNext returns WSA_NOT_ENOUGH_MEMORY. The information that was too big for the supplied buffer is discarded, and the next information set is to be retrieved.
LUP_NEAREST Retrieve the results in order of distance. Note that it is up to the name provider to calculate this distance metric, as there is no specific provision for this information when a service is registered. Name providers are not required to support this concept.
LUP_RES_SERVICE Specifies that the local address be returned in the CSADDR_INFO structure.
LUP_RETURN_ADDR Retrieve the addresses as lpcsaBuffer.
LUP_RETURN_ALIASES Retrieve only alias information. Each alias will be returned in successive calls to WSALookupServiceNext and will have the RESULT_IS_ALIAS flag set.
LUP_RETURN_ALL Retrieve all available information.
LUP_RETURN_BLOB Retrieve the private data as lpBlob.
LUP_RETURN_COMMENT Retrieve the comment as lpszComment.
LUP_RETURN_NAME Retrieve the name as lpszServiceInstanceName.
LUP_RETURN_TYPE Retrieve the type as lpServiceClassId.
LUP_RETURN_VERSION Retrieve the version as lpVersion.

When you make a call to WSALookupServiceBegin, a handle for the query is returned that you pass to WSALookupServiceNext, which returns the data to you. The function is defined as

 INT WSALookupServiceNext ( HANDLE hLookup, DWORD dwControlFlags, LPDWORD lpdwBufferLength, LPWSAQUERYSET lpqsResults ); 

The handle hLookup is returned from WSALookupServiceBegin. The dwControlFlags parameter has the same meaning as in WSALookupServiceBegin except that only LUP_FLUSHPREVIOUS is supported. The parameter lpdwBufferLength is the length of the buffer passed as lpqsResults. Because the WSAQUERYSET structure could contain binary large object (BLOB) data, it is often required that you pass a buffer larger than the structure itself. If the buffer size is insufficient for the data to be returned, the function call fails with WSA_NOT_ENOUGH_MEMORY.

Once you have initiated the query with WSALookupServiceBegin, call WSALookupServiceNext until the error WSA_E_NO_MORE (10110) is generated. One note of caution: in earlier implementations of Winsock, the error code for no more data is WSAENOMORE (10102), so robust code should check for both error codes. Once all the data has been returned or you have finished querying, call WSALookupServiceEnd with the HANDLE variable used in the queries. The function is

 INT WSALookupServiceEnd ( HANDLE hLookup ); 

Forming a Query

Let's look at how you can query the service you registered in the previous section. The first thing to do is set up a WSAQUERYSET structure that defines the query. Look at the following code:

 WSAQUERYSET qs; GUID guid = SVCID_NETWARE(200); AFPROTOCOLS afp[2] = {{AF_IPX, NSPROTO_IPX}, {AF_INET, IPPROTO_UDP}}; HANDLE hLookup; int ret; memset(&qs, 0, sizeof(qs)); qs.dwSize = sizeof (WSAQUERYSET); qs.lpszServiceInstanceName = "Widget Server"; qs.lpServiceClassId = &guid; qs.dwNameSpace = NS_ALL; qs.dwNumberOfProtocols = 2; qs.lpafpProtocols = afp; ret = WSALookupServiceBegin(&qs, LUP_RETURN_ADDR | LUP_RETURN_NAME, &hLookup); if (ret == SOCKET_ERROR) // Error 

Remember that all service lookups are based on the service class GUID that the service you are searching for is based on. The variable guid is set to the service class ID of our server. You first initialize qs to 0 and set the dwSize field to the size of the structure. The next step is to give the name of the service you are searching for. The service name can be the exact name of the server, or you can specify a wildcard (*) that will return all services of the given service class GUID. Next you tell the query to search all name spaces by using the NS_ALL constant. Last you set up the protocols that our client is capable of connecting with, which are IPX and UDP/IP. This is done by using an array of two AFPROTOCOLS structures.

Now you are ready to begin the query, so you must call WSALookupServiceBegin. The first parameter is our WSAQUERYSET structure, while the next parameters are flags defining which data should be returned if a matching service is found. Here you specify that you want addressing information and the service name by logically ORing the two flags LUP_RETURN_ADDR and LUP_RETURN_NAME. The flag LUP_RETURN_NAME is necessary only if you're specifying the wildcard (*) for the service name; otherwise, you already know the name of the service. The last parameter is a HANDLE variable that identifies this particular query. It will be initialized upon successful return.

Once the query is successfully opened, you call WSALookupServiceNext until WSA_E_NO_MORE is returned. Each successful call returns information about a service that matches our criteria. Here is what the code looks like for this step:

 char buff[sizeof(WSAQUERYSET) + 2000]; DWORD dwLength, dwErr; WSAQUERYSET *pqs = NULL; SOCKADDR *addr; int i; pqs = (WSAQUERYSET *)buff; dwLength = sizeof(WSAQUERYSET) + 2000; while (1) { ret = WSALookupServiceNext(hLookup, 0, &dwLength, pqs); if (ret == SOCKET_ERROR) { if ((dwErr = WSAGetLastError()) == WSAEFAULT) { printf("Buffer too small; required size is: %d\n", dwLength); break; } else if ((dwErr == WSA_E_NO_MORE) || (dwErr = WSAENOMORE)) break; else { printf("Failed with error: %d\n", dwErr); break; } } for (i = 0; i < pqs->dwNumberOfCsAddrs; i++) { addr = (SOCKADDR *)pqs->lpcsaBuffer[i].RemoteAddr.lpSockaddr; if (addr->sa_family == AF_INET) { SOCKADDR_IN *ipaddr = (SOCKADDR_IN *)addr; printf("IP address:port = %s:%d\n", inet_ntoa(addr->sin_addr), addr->sin_port); } else if (addr->sa_family == AF_IPX) { SOCKADDR_IPX *ipxaddr = (SOCKADDR_IPX *)addr; printf("%02X%02X%02X%02X.%02X%02X%02X%02X%02X%02X:%04X", (unsigned char)ipxaddr->sa_netnum[0], (unsigned char)ipxaddr->sa_netnum[1], (unsigned char)ipxaddr->sa_netnum[2], (unsigned char)ipxaddr->sa_netnum[3], (unsigned char)ipxaddr->sa_nodenum[0], (unsigned char)ipxaddr->sa_nodenum[1], (unsigned char)ipxaddr->sa_nodenum[2], (unsigned char)ipxaddr->sa_nodenum[3], (unsigned char)ipxaddr->sa_nodenum[4], (unsigned char)ipxaddr->sa_nodenum[5], ntohs(ipxaddr->sa_socket)); } } } WSALookupServiceEnd(hLookup); 

The example code is straightforward, although a bit simplified. Calling WSALookupServiceNext requires only a valid handle to a query, the length of the return buffer, and the return buffer itself. You don't need to specify any control flags, as the only valid flag for this function is LUP_FLUSHPREVIOUS. If our supplied buffer is too small and this flag is set, the results from this call are discarded. However, in this example we don't use LUP_FLUSHPREVIOUS, and if our buffer is too small, the WSAEFAULT error is generated. If this occurs, lpdwBufferLength is set to the required size. Our example uses a fixed-size buffer equal to the size of the WSAQUERYSET structure plus 2000 bytes. Since all you are asking for are service names and addresses, this should be a sufficient size. Of course, in production code your applications should be prepared to handle the WSAEFAULT error.

Once you successfully call WSALookupServiceNext, the buffer is filled with a WSAQUERYSET structure containing the results. In our query, you asked for names and addresses; the fields of WSAQUERYSET that are of most interest are lpszServiceInstanceName and lpcsaBuffer. The former contains the name of the service, while the latter is an array of CSADDR_INFO structures that contains addressing information for the service. The parameter dwNumberOfCsAddrs tells us exactly how many addresses have been returned. In the example code, all we do is simply print out the addresses. Check only for IPX and IP addresses to print because those are the only address families you requested when you opened the query.

If our query used a wildcard (*) for the service name, each call to WSALookupServiceNext would return a particular instance of that service running somewhere on the network—provided, of course, that multiple instances are actually registered and running. Once all instances of the service have been returned, the error WSA_E_NO_MORE is generated, and you break out of the loop. The last thing you do is call WSALookupServiceEnd on the query handle. This releases any resources allocated for the query.

Querying DNS

Earlier we mentioned that the DNS namespace is static, which means you cannot dynamically register your service; however, you can still use the Winsock name resolution functions to perform a DNS query. Performing a DNS query is actually a bit more complicated than performing a normal query for a service that you have registered because the DNS name space provider returns the query information in the form of a BLOB. Why does it do this? Remember from the Chapter 6 discussion of gethostbyname that a name lookup returns a HOSTENT structure that contains not only IP addresses but also aliases. That information doesn't quite fit into the fields of the WSAQUERYSET structure.

The tricky thing about BLOB data is that the format of the data is not well documented, which makes directly querying DNS challenging. First let's take a look at how to open the query. The file Dnsquery.c on the companion CD contains the entire example code for querying DNS directly; however, we'll take a look at it piece by piece. 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; HANDLE hQuery; 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.lpafProtocols = 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 = pqs->lpBlob->pBlobData; 

Because a DNS name space provider returns the host information in the form of a BLOB, you need to supply a sufficiently large buffer. That is why you 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, all 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 returned by the query. From Chapter 6, you know 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 actually lies. The offsets are from the start of the BLOB data. This requires you to fix up the pointers to reference the absolute memory location before you can actually access the data. Figure 10-2 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. In order 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. Additionally, 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 10-2, 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.

click to view at full size.

Figure 10-2. 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.



Network Programming for Microsoft Windows
Linux Server Hacks, Volume Two: Tips & Tools for Connecting, Monitoring, and Troubleshooting
ISBN: 735615799
EAN: 2147483647
Year: 1998
Pages: 159

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net