程式扎記: [ NP in MS ] Internet Protocol (Part3 : Address and Name Resolution )

標籤

2011年4月24日 星期日

[ NP in MS ] Internet Protocol (Part3 : Address and Name Resolution )

Preface : 
In this section, we'll cover how to assign both literal string addresses and resolve names to the address specific structures for both IP protocols. First, we will cover the new name resolution APIs: getaddrinfo and getnameinfo. These APIs have replaced the IPv4 specific routines. Then we'll cover the generic Winsock APIs for converting between string literal addresses and socket address structure. These APIs are WSAAddressToString andWSAStringToAddress. Note that these functions perform only address conversion and assignment, not name resolution. 
Next, the IPv4 specific legacy routines will be described. We include the legacy API descriptions in case legacy code needs to be maintained, but any new projects should use the newer API functions. By using the newer functions it will be trivial to write an application that can seamlessly operate over both IPv4 and IPv6, which is the topic of the last section in this chapter. 
Finally, note that all the name resolution functions covered in this chapter deal only with resolving names and not registering a name with an address. This is accomplished by the Winsock Registration and Name Resolution (RNR) APIs discussed in Chapter 8. 

Name Resolution Routines : 
Along with IPv6, several new name resolution functions were introduced that could handle both IPv4 and IPv6 addresses. The legacy functions likegethostbyname and inet_addr work with IPv4 addresses only. The replacement functions are named getnameinfo and getaddrinfo. 
These new name resolution routines are defined in WS2TCPIP.H. Also, note that although these functions are new for Windows XP, they can be made available to work on all Winsock 2 enabled platforms. This is done by including the header file WSPIAPI.H before including WS2TCPIP.H. The compiled binary will then run on all Winsock 2–enabled platforms, such as Windows 95, Windows 98, Windows Me, Windows NT 4.0, and Windows 2000. The getaddrinfofunction provides protocol-independent name resolution. The function prototype is : 

  1. int getaddrinfo(  
  2.         const char FAR *nodename,  
  3.         const char FAR *servname,  
  4.         const struct addrinfo FAR *hints,  
  5.         struct addrinfo FAR *FAR *res  
  6. );  
The nodename parameter specifies the NULL-terminated host name or literal address. The servname is a NULL-terminated string containing the port number or a service name such as “ftp” or “telnet.” The third parameter, hints, is a structure that can pass one or more options that affect how the name resolution is performed. Finally, the res parameter returns a linked list of addrinfo structure containing the addresses the string name was resolved to. If the operation succeeds, zero is returned; otherwise the Winsock error code is returned. The addrinfo structure is defined as : 
  1. struct addrinfo {  
  2.         int     ai_flags;  
  3.         int     ai_family;  
  4.         int     ai_socktype;  
  5.         int     ai_protocol;  
  6.         size_t      ai_addrlen;  
  7.         char        *ai_canonname;  
  8.         struct sockaddr *ai_addr;  
  9.         struct addrinfo *ai_next;  
  10. };  
When passing hints into the API, the structure should be zeroed out beforehand, and the first four fields are relevant : 
ai_flags indicates one of three values: AI_PASSIVE, AI_CANONNAME, or AI_NUMERICHOST. AI_CANONNAME indicates that nodename is a computer name like www.microsoft.com and AI_NUMERICHOST indicates that it is a literal string address such as “10.10.10.1”. AI_PASSIVE will be discussed later.

ai_family can indicate AF_INET, AF_INET6, or AF_UNSPEC. If you wish to resolve to a specific address, type the supply AF_INET or AF_INET6. Otherwise, if AF_UNSPEC is given, then the addresses returned could be either IPv4 or IPv6 or both.

ai_socktype specifies the desired socket type, such as SOCK_DGRAM, SOCK_STREAM. This field is used when servname contains the name of a service. That is, some services have different port numbers depending on whether UDP or TCP is used.

ai_protocol specifies the desired protocol, such as IPPROTO_TCP. Again, this field is useful when servname indicates a service.

If no hints are passed into getaddrinfo, the function behaves as if a zeroed hints structure was provided with an ai_family of AF_UNSPEC. 
If the function succeeds, then the resolved addresses are returned via res. If the name resolved to more than one address, then the result is a linked list chained by the ai_next field. Every address resolved from the name is indicated in ai_addr with the length of that socket address structure given in ai_addrlen. These two fields may be passed directly into bind, connect, sendto, etc. 
The following code sample illustrates how to resolve a hostname along with the port number before making a TCP connection to the server : 
  1. SOCKET              s;  
  2. struct addrinfo     hints,  
  3.                 *result;  
  4. int             rc;  
  5.   
  6. memset(&hints, 0, sizeof(hints));  
  7. hints.ai_flags = AI_CANONNAME;  
  8. hints.ai_family = AF_UNSPEC;  
  9. hints.ai_socktype = SOCK_STREAM;  
  10. hints.ai_protocol = IPPROTO_TCP;  
  11. rc = getaddrinfo("foobar""5001", &hints, &result);  
  12. if (rc != 0) {  
  13.         // unable to resolve the name  
  14. }  
  15. s = socket(result->ai_family, result->ai_socktype,   
  16. result->ai_protocol);  
  17. if (s == INVALID_SOCKET) {  
  18.         // socket API failed  
  19. }  
  20. rc = connect(s, result->ai_addr, result->ai_addrlen);  
  21. if (rc == SOCKET_ERROR) {  
  22.         // connect API failed  
  23. }  
  24. freeaddrinfo(result);  
In this example, the application is resolving the hostname “foobar” and wants to establish a TCP connection to a service on port 5001. You'll also notice that this code doesn't care if the name resolved to an IPv4 or an IPv6 address. It is possible that “foobar” has both IPv4 and IPv6 addresses registered, in which case result will contain additional addrinfo structures linked by the ai_next field. If an application wanted only IPv4 addresses registered to “foobar,” thehints.ai_family should be set to AF_INET. Finally, note that the information returned via res is dynamically allocated and needs to be freed by calling thefreeaddrinfo API once the application is finished using the information. 
Another common action that applications perform is assigning a literal string address such as “172.17.7.1” or “fe80::1234” into a socket address structure of the appropriate type. The getaddrinfo function does this by setting the AI_NUMERICHOST flag within the hints. The following code illustrates this : 
  1. struct addrinfo     hints,  
  2.                     *result;  
  3. int             rc;  
  4.   
  5. memset(&hints, 0, sizeof(hints));  
  6. hints.ai_flags = AI_NUMERICHOST;  
  7. hints.ai_family = AI_UNSPEC;  
  8. hints.ai_socktype = SOCK_STREAM;  
  9. hints.ai_protocol = IPPROTO_TCP;  
  10. rc = getaddrinfo("172.17.7.1""5001", &hints, &result);  
  11. if (rc != 0) {  
  12.         // invalid literal address  
  13. }  
  14. // Use the result  
  15. freeaddrinfo(result);  
The literal address “172.17.7.1” will be converted to the necessary socket address structure and returned via result. Because AF_UNSPEC is passed, the API will determine the correct socket address structure (SOCKADDR_IN or SOCKADDR_IN6) required and convert the address accordingly. As before, the port field of the resulting socket address structure will be initialized to 5001. 
Note that if no flags are passed as part of the hints and a literal string address is resolved, the returned structure addrinfo containing the converted address will have the AI_NUMERICHOST flags set. Likewise, if a hostname is resolved but no hints are passed, the returned structure addrinfo flag will containAI_CANONNAME. 
The last flag that can be used with getaddrinfo is AI_PASSIVE, which is used to obtain an address that can be passed to the bind function. For IPv4, this would be INADDR_ANY (0.0.0.0) and for IPv6 it would be IN6ADDR_ANY (::). To obtain the bind address, the hints should indicate which address family the passive address is to be obtained for (via ai_family), nodename should be NULL, and servname should be non-NULL—indicating the port number the application will bind to (which can be “0”). If AF_UNSPEC is passed in the hints, then two addrinfo structures will be returned, one with the IPv4 bind address and the other with the IPv6 bind address. 
The AI_PASSIVE flag is useful after resolving a hostname via getaddrinfo. Once the resolved address is returned, the original result's ai_family can be used in another call to getaddrinfo to obtain the appropriate bind address for that address family. This prevents applications from having to touch the internal socket address structure's fields and also removes the need for two separate code paths for binding the socket depending on which address family the address was resolved to. 
The other new name resolution API is getnameinfo, which performs the reverse of the getaddrinfo function. It takes a socket address structure already initialized and returns the host and service name corresponding to the address/port information. The getnameinfo function is prototyped as the following : 
  1. int getnameinfo(  
  2.         const struct sockaddr FAR *sa,  
  3.         socklen_t salen,  
  4.         char FAR *host,  
  5.         DWORD hostlen,  
  6.         char FAR *serv,  
  7.         DWORD servlen,  
  8.         int flags  
  9. );  
The parameters are fairly self-explanatory. sa is the socket address structure on which the name information will be obtained for and salen is the size of that structure. host is the character buffer to receive the host's name. By default, the fully qualified domain name (FQDN) will be returned. hostlen simply indicates the size of the host buffer. serv is the character buffer to receive the service/port information and servlen is the length of that buffer. Finally, the flags parameter indicates how the socket address should be resolved. The possible flag values are the following : 
* NI_NOFQDN indicates that only the relative distinguished name (RDN) returned. For example, with this flag set the host named “mist.microsoft.com” would return only “mist”.

* NI_NUMERICHOST indicates to return the string representation of the address and not the hostname.

* NI_NAMEREQD indicates if the address cannot be resolved to a FQDN to return an error.

* NI_NUMERICSERV indicates to return the port information as a string instead of resolving to a well-known service name, such as “ftp.” Note that if the serv buffer is supplied with this flag absent and the port number cannot be resolved to a well-known service, genameinfo will fail with errorWSANO_DATA (11004).

* NI_DGRAM is used to differentiate datagram services from stream services. This is necessary for those few services that define different port numbers for UDP and TCP.

There is a file named RESOLVER.CPP that performs name resolution on the companion CD. A hostname or address is passed to the application along with service or port information and is resolved via getaddrinfo. Then for every resolved address returned, getnameinfo is called to obtain either the machine name back or the string literal address. 

Simple Address Conversion : 
When an application needs only to convert between string literal addresses and socket address structures, the WSAStringToAddress andWSAAddressToString APIs are available. WSAStringToAddress is not as "smart" as getaddrinfo because you must specify the address family that the string address belongs to. The API is the following : 
  1. INT WSAStringToAddress(  
  2.         LPTSTR AddressString,  
  3.         INT AddressFamily,  
  4.         LPWSAPROTOCOL_INFO lpProtocolInfo,  
  5.         LPSOCKADDR lpAddress,  
  6.         LPINT lpAddressLength  
  7. );  
The first parameter is the string to convert and the second indicates the address family the string belongs to (such as AF_INETAF_INET6, or AF_IPX). The third parameter, lpProtocolInfo, is an optional pointer to the WSAPROTOCOL_INFO structure that defines the protocol provider to use when performing the conversion. If there are multiple providers implementing a protocol, this parameter can be used to specify an explicit provider. The fourth parameter is the appropriate socket address structure to which the string address will be converted and assigned into. 
Note that this API will convert string addresses that contain port numbers. For example, the IPv4 string notation allows a colon followed by the port number at the end of the address. For example, “157.54.126.42:1200” indicates the IPv4 address using port 1200. In IPv6, the IPv6 address string must be enclosed in square brackets after which the colon and port notation may be used. For example, [fe80::250:8bff:fea0:92ed%5]:80 indicates a link-local address with its scope ID followed by port 80. Note that only port numbers will be resolved and not service names (such as “ftp”). For both these examples, if these strings were converted with WSAStringToAddress, then the returned socket address structure will be initialized with the appropriate binary IP address, port number, and address family. For IPv6, the scope ID field will also be initialized if the string address contains “%scope_ID” after the address portion. 
The WSAAddressToString provides a mapping from a socket address structure to a string representation of that address. The prototype is : 
  1. INT WSAAddressToString(  
  2.         LPSOCKADDR lpsaAddress,  
  3.         DWORD dwAddressLength,  
  4.         LPWSAPROTOCOL_INFO lpProtocolInfo,  
  5.         LPTSTR lpszAddressString,  
  6.         LPDWORD lpdwAddressStringLength  
  7. );  
This function takes a SOCKADDR structure and formats the binary address to a string indicated by the buffer lpszAddressString. Again, if there is more than one transport provider for a given protocol, a specific one may be selected by passing its WSAPROTOCOL_INFO structure as lpProtocolInfo. Note that the address family is a field of the SOCKADDR structure passed as lpsaAddress. 

Legacy Name Resolution Routines : 
This section covers the legacy name resolution and is included only for the sake of code maintenance, because new applications should be using getaddrinfoand getnameinfo. The other feature you will notice is that the two new API calls replace eight legacy functions. 
The function inet_addr converts a dotted IPv4 address to a 32-bit unsigned long integer quantity. The inet_addr function is defined as : 
  1. unsigned long inet_addr(  
  2.     const char FAR *cp   
  3. );  
The cp field is a null-terminated character string that accepts an IP address in dotted notation. Note that this function returns an IPv4 address as a 32-bit unsigned long integer in network-byte order, which can be assigned into the SOCKADDR_IN field sin_addr. Network-byte order is described in Chapter 1. 
The reverse of inet_addr is inet_ntoa, which takes an IPv4 network address and converts it to a string. This function is declared as : 
  1. char FAR *inet_ntoa(  
  2.         Struct in_addr in  
  3. );  
The following code sample demonstrates how to create a SOCKADDR_IN structure using the inet_addr and htons functions : 
  1. SOCKADDR_IN InternetAddr;  
  2. INT nPortId = 5150;  
  3.   
  4. InternetAddr.sin_family = AF_INET;  
  5.   
  6. // Convert the proposed dotted Internet address 136.149.3.29  
  7. // to a 4-byte integer, and assign it to sin_addr  
  8.   
  9. InternetAddr.sin_addr.s_addr = inet_addr("136.149.3.29");  
  10.   
  11. // The nPortId variable is stored in host-byte order. Convert  
  12. // nPortId to network-byte order, and assign it to sin_port.  
  13.   
  14. InternetAddr.sin_port = htons(nPortId);  
The Winsock functions gethostbyname, WSAAsyncGetHostByName, gethostbyaddr, and WSAAsyncGetHostByAddr retrieve host information corresponding to a host name or host address from a host database. The first two functions translate a hostname to its network IPv4 addresses and the second two do the reverse—map an IPv4 network address back to a hostname. These functions return a HOSTENT structure that is defined as : 
  1. struct hostent   
  2. {  
  3.     char FAR *       h_name;  
  4.     char FAR * FAR * h_aliases;  
  5.     short            h_addrtype;  
  6.     short            h_length;  
  7.     char FAR * FAR * h_addr_list;  
  8. };  
The h_name field is the official name of the host. If your network uses the DNS, it is the FQDN that causes the name server to return a reply. If your network uses a local “hosts” file, it is the first entry after the IP address. The h_aliases field is a null-terminated array of alternative names for the host. Theh_addrtype represents the address family being returned. The h_length field defines the length in bytes of each address in the h_addr_list field, which will be four bytes for IPv4 addresses. The h_addr_list field is a null-terminated array of IP addresses for the host. (A host can have more than one IP address assigned to it.) Each address in the array is returned in network-byte order. Normally, applications use the first address in the array. However, if more than one address is returned, applications should randomly choose an available address rather than always use the first address. The prototypes for these functions are : 
  1. struct hostent FAR * gethostbyname (  
  2.     const char FAR * name    
  3. );  
  4.   
  5. HANDLE WSAAsyncGetHostByName(   
  6.     HWND hWnd,  
  7.     unsigned int wMsg,  
  8.     const char FAR * name,  
  9.     char FAR * buf,  
  10.     int buflen  
  11. );  
  12.   
  13. struct HOSTENT FAR * gethostbyaddr(  
  14.     const char FAR * addr,  
  15.     int len,  
  16.     int type  
  17. );  
  18.   
  19. HANDLE WSAAsyncGetHostByAddr(  
  20.         HWND hWnd,  
  21.         unsigned int wMsg,  
  22.         const char FAR *addr,  
  23.         int len,  
  24.         int type,  
  25.         char FAR *buf,  
  26.         int buflen  
  27. );  
For the first two functions, the name parameter represents a friendly name of the host you are looking for, and the latter two functions take an IPv4 network address in the addr parameter. The length of the address is specified as len. Also, type indicates the address family of the network address passed, which would be AF_INET. All four functions return the results via a HOSTENT structure. For the two synchronous functions, the HOSTENT is a system-allocated buffer that the application should not rely on being static. The two asynchronous functions will copy the HOSTENT structure to the buffer indicated by the bufparameter. This buffer size should be equal to MAXGETHOSTSTRUCT. 
Finally, these and the rest of the asynchronous name and service resolution functions return a HANDLE identifying the operation issued. Upon completion, a window message indicated by wMsg is posted to the window given by hWnd. If at some point the application wishes to cancel the asynchronous request, theWSACancelAsyncRequest function is used. This function is declared as : 
  1. int WSACancelAsyncRequest(  
  2.         HANDLE hAsyncTaskHandle  
  3. );  
Keep in mind that the synchronous API calls will block until the query completes or times out, which could take several seconds. 
The next type of legacy resolution functions provide the capability to retrieve port numbers for well-known services and the reverse. The API functionsgetservbyname and WSAAsyncGetServByName take the name of a well-known service like “FTP” and return the port number that the service uses. The functions getservbyport and WSAAsyncGetServByPort perform the reverse operation by taking the port number and returning the service name that uses that port. These functions simply retrieve static information from a file named services. In Windows 95, Windows 98, and Windows Me, the services file is located under %WINDOWS%; in Windows NT, it is located under %WINDOWS%\System32\Drivers\Etc. 
These four functions return the service information in a SERVENT structure that is defined as : 
  1. struct servent {  
  2.         char FAR *     s_name;  
  3.         char FAR * FAR *s_aliases;  
  4.         short          s_port;  
  5.         char FAR *     s_proto  
  6. };  
The field s_name is the name of the service and s_aliases is a NULL terminated array of string pointers, each containing another name for the service. s_portis the port number used by the service and s_proto is the protocol used by the service, such as the strings “tcp” and “udp.” These functions are defined as follows : 
  1. struct servent FAR * getservbyname(  
  2.     const char FAR * name,  
  3.     const char FAR * proto  
  4. );  
  5.   
  6. HANDLE WSAAsyncGetServByName(  
  7.         HWND hWnd,  
  8.         unsigned int wMsg,  
  9.         const char FAR *name,  
  10.         const char FAR *proto,  
  11.         char FAR *buf,  
  12.         int buflen  
  13. );  
  14.   
  15. struct servent FAR *getservbyport(  
  16.         int port,  
  17.         const char FAR *proto  
  18. );  
  19.   
  20. HANDLE WSAAsyncGetServByPort(  
  21.         HWND hWnd,  
  22.         unsigned int wMsg,  
  23.         int port,  
  24.         const char FAR *proto,  
  25.         char FAR *buf,  
  26.         int buflen  
  27. );  
The name parameter represents the name of the service you are looking for. The proto parameter optionally points to a string that indicates the protocol that the service in name is registered under, such as “tcp” or “udp”. The second two functions simply take the port number to match to a service name. The synchronous API functions return a SERVENT structure, which is a system allocated buffer, and the asynchronous ones take an application supplied buffer, which should also be of the size MAXGETHOSTSTRUCT. 
The last set of legacy name resolution API functions convert between a protocol string name, such as “tcp”, and its protocol number (“tcp” would resolve toIPPROTO_TCP.). These functions are getprotobyname, WSAAsyncGetProtoByName, getprotobynumber, and WSAAsyncGetProtoByNumber. The first two convert from the string protocol to the protocol number and the latter two do the opposite—map the protocol number back to its string name. These functions return a PROTOENT structure defined as : 
  1. struct protoent {  
  2.         char FAR *     p_name;  
  3.         char FAR * FAR *p_aliases;  
  4.         short          p_proto;  
  5. };  
The first field, p_name, is the string name of the protocol, and p_aliases is a NULL terminated array of string pointers that contain other names the protocol is known by. Finally, p_proto is the protocol number (such as IPPROTO_UDP or IPPROTO_TCP). These function prototypes are : 
  1. struct protoent FAR *getprotbyname(  
  2.         const char FAR *name  
  3. );  
  4.   
  5. HANDLE WSAAsyncGetProtoByName(  
  6.         HWND hWnd,  
  7.         unsigned int wMsg,  
  8.         const char FAR *name,  
  9.         char FAR *buf,  
  10.         int buflen  
  11. );  
  12.   
  13. struct protoent FAR *getprotobynumber(  
  14.         int number  
  15. );  
  16.   
  17. HANDLE WSAAsyncGetProtoByNumber(  
  18.         HWND hWnd,  
  19.         unsigned int wMsg,  
  20.         int number,  
  21.         char FAR *buf,  
  22.         int buflen  
  23. );  
These functions behave the same way the legacy name resolution functions described earlier do in terms of synchronous and asynchronous functions.

沒有留言:

張貼留言

網誌存檔

關於我自己

我的相片
Where there is a will, there is a way!