Registering a Service

The next step is to find out how to set up your own service and make it available and known to other machines on the network. This is known as registering an instance of your service with the name space provider so that it can either be advertised or queried by clients that want to communicate with it. Registering a service is actually a two-step process. The first step is to install a service class that describes the characteristics of your service.

It is important to distinguish between a service class and the actual service itself. For example, the service class describes which name spaces your service is to be registered with as well as certain characteristics about the service, such as whether it is connection-oriented or connectionless. The service class in no way describes how a client can establish a connection. Once the service class is registered, you register an actual instance of your service that references the correct service class to which it belongs. Once this occurs, a client can perform a query to find out where your service instance is running and therefore can attempt a communication.

Installing a Service Class

Before you register an instance of a service, you need to define the service class to which your service will belong. A service class defines what name spaces a service belonging to this class is registered with. The Winsock function that registers a service class is WSAInstallServiceClass, which is defined as

 INT WSAInstallServiceClass (LPWSASERVICECLASSINFO lpServiceClassInfo); 

The single parameter lpServiceClassInfo points to a WSASERVICECLASSINFO structure that defines the attributes of this class. The structure is defined as

 typedef struct _WSAServiceClassInfo { LPGUID lpServiceClassId; LPTSTR lpszServiceClassName; DWORD dwCount; LPWSANSCLASSINFO lpClassInfos; } WSASERVICECLASSINFO, *PWSASERVICECLASSINFO, LPWSASERVICECLASSINFO; 

The first field is a GUID that uniquely identifies this particular service class. There are a couple ways to generate a GUID to use here. One way is to use the utility Uuidgen.exe and create a GUID for this service class. The problem with this method is that if you need to refer back to this GUID, you pretty much have to hardcode its value into a header file somewhere. This is where the second solution is useful. Within the header file Svcguid.h, several macros generate a GUID based on a simple attribute. For example, if you install a service class for SAP that will be used to advertise your IPX application, you can use the SVCID_NETWARE macro. The only parameter is the SAP ID number you assign to your "class" of applications. A number of SAP IDs are predefined in NetWare, such as 0x4 for file servers and 0x7 for a print server. Using this method, all you need is the easy-to-remember SAP ID to generate the GUID for the corresponding service class. Additionally, several macros exist that accept a port number as a parameter and return the GUID of the corresponding service. Take a look at the header file Svcguid.h, which contains other useful macros for the reverse operation—extracting the service port number from a GUID. Table 10-2 lists the most commonly used macros for generating GUIDs from simple protocol attributes such as port numbers or SAP IDs. The header file also contains constants for well-known port numbers for services such as FTP and Telnet.

Table 10-2. Common service ID macros

Macro Description
SVCID_TCP(Port) Generates a GUID from TCP port number
SVCID_DNS(RecordType) Generates a GUID from a DNS record type
SVCID_UDP(Port) Generates a GUID from UDP port number
SVCID_NETWARE(SapId) Generates a GUID from SAP ID number

The second field of the WSASERVICECLASSINFO structure, lpszServiceClassName, is simply a string name for this particular service class. The last two fields are related. The dwCount field refers to the number of WSANSCLASSINFO structures passed in the lpClassInfos field. These structures define the name spaces and protocol characteristics that apply to the actual services that register under this service class. The structure is defined as

 typedef struct _WSANSClassInfo { LPSTR lpszName; DWORD dwNameSpace; DWORD dwValueType; DWORD dwValueSize; LPVOID lpValue; }WSANSCLASSINFO, *PWSANSCLASSINFO, *LPWSANSCLASSINFO; 

The lpszName field defines the attribute that the service class possesses. Table 10-3 lists the various attributes available. Every attribute listed has a value type of REG_DWORD.

Table 10-3. Service types

String Value Constant Define Name Space Description
"SapId" SERVICE_TYPE_VALUE_SAPID NS_SAP SAP ID
"ConnectionOriented" SERVICE_TYPE_VALUE_CONN Any Indicates whether service is connection-oriented or connectionless
"TcpPort" SERVICE_TYPE_VALUE_TCPPORT NS_DNS
NS_NTDS
TCP port
"UdpPort" SERVICE_TYPE_VALUE_UDPPORT NS_NTDS
NS_DNS
UDP port

The dwNameSpace is the name space for which this attribute applies. Table 10-3 also lists the name spaces to which the various service types usually apply. The last three fields, dwValueType, dwValueSize, and lpValue all describe the actual value associated with the service type. The dwValueType field signifies the type of data associated with this entry and therefore can be one of the registry type values. For example, if the value is a DWORD, the value type is REG_DWORD. The next field, dwValueSize, is simply the size of the data passed as lpValue, which is a pointer to the data.

The following code example illustrates how to install a service class named "Widget Server Class."

 WSASERVICECLASSINFO sci; WSANSCLASSINFO aNameSpaceClassInfo[4]; DWORD dwSapId = 200, dwUdpPort = 5150, dwZero = 0; int ret; memset(&sci, 0, sizeof(sci)); SET_NETWARE_SVCID(&sci.lpServiceClassId, dwSapId); sci.lpszServiceClassName = (LPSTR)"Widget Server Class"; sci.dwCount = 4; sci.lpClassInfos = aNameSpaceClassInfo; memset(aNameSpaceClassInfo, 0, sizeof(WSANSCLASSINFO) * 4); // NTDS name space setup aNameSpaceClassInfo[0].lpszName = SERVICE_TYPE_VALUE_CONN; aNameSpaceClassInfo[0].dwNameSpace = NS_NTDS; aNameSpaceClassInfo[0].dwValueType = REG_DWORD; aNameSpaceClassInfo[0].dwValueSize = sizeof(DWORD); aNameSpaceClassInfo[0].lpValue = &dwZero; aNameSpaceClassInfo[1].lpszName = SERVICE_TYPE_VALUE_UDPPORT; aNameSpaceClassInfo[1].dwNameSpace = NS_NTDS; aNameSpaceClassInfo[1].dwValueType = REG_DWORD; aNameSpaceClassInfo[1].dwValueSize = sizeof(DWORD); aNameSpaceClassInfo[1].lpValue = &dwUdpPort; // SAP name space setup aNameSpaceClassInfo[2].lpszName = SERVICE_TYPE_VALUE_CONN; aNameSpaceClassInfo[2].dwNameSpace = NS_SAP; aNameSpaceClassInfo[2].dwValueType = REG_DWORD; aNameSpaceClassInfo[2].dwValueSize = sizeof(DWORD); aNameSpaceClassInfo[2].lpValue = &dwZero; aNameSpaceClassInfo[3].lpszName = SERVICE_TYPE_VALUE_SAPID; aNameSpaceClassInfo[3].dwNameSpace = NS_SAP; aNameSpaceClassInfo[3].dwValueType = REG_DWORD; aNameSpaceClassInfo[3].dwValueSize = sizeof(DWORD); aNameSpaceClassInfo[3].lpValue = &dwSapId; ret = WSAInstallServiceClass(&sci); if (ret == SOCKET_ERROR) { printf("WSAInstallServiceClass() failed %d\n", WSAGetLastError()); } 

The first noticeable thing this example does is to pick a GUID that this class will be registered under. The services you are designing all belong to the class "Widget Server Class", and this service class describes the general attributes belonging to an instance of the service. In this example, we chose to register this class with the NetWare SAP ID of 200. This is only for convenience. We could have picked an arbitrary GUID or even the GUID based on the UDP port number. Additionally, the service can use the UDP protocol, in which case the clients are listening on port 5150.

The next step of note is setting the dwCount field of the WSASERVICECLASSINFO to 4. In this example, you will register this service class with both the SAP name space (NS_SAP) and the Windows NT domain space (NS_NTDS). The odd thing you'll notice is that we use four WSANSCLASSINFO structures, even though we are registering the service class with only two name spaces. This is because we define two attributes for each name space and each attribute requires a separate WSANSCLASSINFO structure. For each name space, we define whether the service will be connection-oriented. In this example, the name space is connectionless, as we set the value for SERVICE_TYPE_VALUE_CONN to be a Boolean 0. For the Windows NT domain space, we also set the UDP port number this service normally runs under by using the service type SERVICE_TYPE_VALUE_UDPPORT. For the SAP name space, we set the SAP ID of our service with service type SERVICE_TYPE_VALUE_SAPID.

For every WSANSCLASSINFO entry, you must set the name space identifier to which this service type applies, as well as the type and size of the value. Table 10-3 contains the types required for the service types, which all turn out to be DWORD in the example. The last step is simply to call WSAInstallServiceClass and pass the WSASERVICECLASSINFO structure as the parameter. If WSAInstallServiceClass is successful, the function returns 0; otherwise, it returns SOCKET_ERROR. If WSASERVICECLASSINFO is invalid or improperly formed, WSAGetLastError returns WSAEINVAL. If the service class already exists, WSAGetLastError returns WSAEALREADY. In this case, a service class can be removed by calling WSARemoveServiceClass, which is declared as

 INT WSARemoveServiceClass( LPGUID lpServiceClassId ); 

This function's only parameter is a pointer to the GUID that defines the given service class.

Service Registration

Once you have a service class installed that describes the general attributes of your service, you can register an instance of your service so that it is available for lookup by other clients on remote machines. The Winsock function to register an instance of a service is WSASetService.

 INT WSASetService ( LPWSAQUERYSET lpqsRegInfo, WSAESETSERVICEOP essOperation, DWORD dwControlFlags ); 

The first parameter, lpqsRegInfo, is a pointer to a WSAQUERYSET structure that defines the particular service. We'll discuss what goes in this structure shortly. The essOperation parameter specifies the action to take place, such as registration or deregistration. Table 10-4 describes the three valid flags.

The third parameter, dwControlFlags, is either 0 or the flag SERVICE_MULTIPLE. This flag is used if multiple addresses will be registered under the given service instance. For example, say you have a service that you want to run on five machines. The WSAQUERYSET structure passed into WSASetService would reference five CSADDR_INFO structures, each describing the location of one instance of the service. This requires the SERVICE_MULTIPLE flag to be set. Additionally, at some later point you can deregister a single instance of the service by using the RNRSERVICE_DELETE service flag. Table 10-5 gives the possible combinations of the operation and control flags and describes the result of the command, depending on whether the service already exists.

Table 10-4. Set service flags

Operation Flag Meaning
RNRSERVICE_REGISTER Register the service. For dynamic name providers, this means to begin actively advertising the service. For persistent name providers, this means updating the database. For static name providers, this does nothing.
RNRSERVICE_DEREGISTER Remove the entire service from the registry. For dynamic name providers, this means to stop advertising the service. For persistent name providers, this means removing the service from the database. For static name providers, this does nothing.
RNRSERVICE_DELETE Remove only the given instance of the service from the name space. A service might be registered that contains multiple instances (using the SERVICE_MULTIPLE flag upon registration), and this command removes only the given instance of the service (as defined by a CSADDR_INFO structure). Again, this applies only to dynamic and persistent name providers.

Table 10-5. WSASetService flag combinations

RNRSERVICE_REGISTER
FlagsMeaning
  If the Service Already Exists If the Service Does Not Exist
none Overwrite the existing service instance. Add a new service entry on the given address.
SERVICE_MULTIPLE Update the service instance by adding the new addresses. Add a new service entry on the given addresses.
RNRSERVICE_DEREGISTER
FlagsMeaning
  If the Service Already Exists If the Service Does Not Exist
none Remove all instances of the service, but do not remove the service. (Basically, WSAQUERYSET remains, but the number of CSADDR_INFO structures is 0.) This is an error, and WSASERVICE_NOT_FOUND is returned.
SERVICE_MULTIPLE Update the service by removing the given addresses. The service remains registered, even if no addresses remain. This is an error, and WSASERVICE_NOT_FOUND is returned.
RNRSERVICE_DELETE
FlagsMeaning
  If the Service Already Exists If the Service Does Not Exist
none The service is removed completely from the name space. This is an error, and WSASERVICE_NOT_FOUND is returned.
SERVICE_MULTIPLE Update the service by removing the given addresses. If no addresses remain, the service is completely removed from the name space. This is an error, and WSASERVICE_NOT_FOUND is returned.

Now that you have an understanding of what WSASetService does, let's take a look at the WSAQUERYSET structure that needs to be filled out and passed into the function. This structure is defined as

 typedef struct _WSAQuerySetW { DWORD dwSize; LPTSTR lpszServiceInstanceName; LPGUID lpServiceClassId; LPWSAVERSION lpVersion; LPTSTR lpszComment; DWORD dwNameSpace; LPGUID lpNSProviderId; LPTSTR lpszContext; DWORD dwNumberOfProtocols; LPAFPROTOCOLS lpafpProtocols; LPTSTR lpszQueryString; DWORD dwNumberOfCsAddrs; LPCSADDR_INFO lpcsaBuffer; DWORD dwOutputFlags; LPBLOB lpBlob; } WSAQUERYSETW, *PWSAQUERYSETW, *LPWSAQUERYSETW; 

The dwSize field should be set to the size of the WSAQUERYSET structure. The lpszServiceInstanceName field contains a string identifier naming this instance of the server. The lpServiceClassId field is the GUID for the service class to which this service instance belongs. The lpVersion field is optional. You can use it to supply version information that could be useful when a client queries for a service. The lpszComment field is also optional. You can specify any kind of comment string here. The dwNameSpace field specifies the name spaces to register your service with. If you're using only a single name space, use only that value; otherwise, use NS_ALL. It is possible to reference a custom name space provider. (Writing your own name space is discussed in Chapter 14.) For a custom name space provider, the dwNameSpace field is set to 0 and lpNSProviderId specifies the GUID representing the custom provider. The lpszContext field specifies the starting point of the query in a hierarchical name space such as NDS.

The dwNumberOfProtocols and lpafpProtocols fields are optional parameters used to narrow the search to return only the supplied protocols. The dwNumberOfProtocols field references the number of AFPROTOCOLS structures contained in the lpafpProtocols array. The structure is defined as

 typedef struct _AFPROTOCOLS { INT iAddressFamily; INT iProtocol; } AFPROTOCOLS, *PAFPROTOCOLS, *LPAFPROTOCOLS; 

The first field, iAddressFamily, is the address family constant, such as AF_INET or AF_IPX. The second field, iProtocol, is the protocol from the given address family, such as IPPROTO_TCP or NSPROTO_IPX.

The next field in the WSAQUERYSET structure, lpszQueryString, is optional and used only by name spaces supporting enriched Structured Query Language (SQL) queries such as Whois++. This parameter is used to specify that string.

The next two fields are the most important when registering a service. The dwNumberOfCsAddrs field simply provides the number of CSADDR_INFO structures passed in lpcsaBuffer. The CSADDR_INFO structure defines the address family and the actual address at which the service is located. If multiple structures are present, multiple instances of the service are available. The structure is defined as

 typedef struct _CSADDR_INFO { SOCKET_ADDRESS LocalAddr; SOCKET_ADDRESS RemoteAddr; INT iSocketType; INT iProtocol; } CSADDR_INFO; typedef struct _SOCKET_ADDRESS { LPSOCKADDR lpSockaddr; INT iSockaddrLength; } SOCKET_ADDRESS, *PSOCKET_ADDRESS, FAR * LPSOCKET_ADDRESS; 

Additionally, the definition of SOCKET_ADDRESS is included. When registering a service, you can specify the local and remote addresses. The local address field (LocalAddr) is used to specify the address that an instance of this service should bind to, while the remote address field (RemoteAddr) is the address a client should use in a connect or a sendto call. The other two fields, iSocketType and iProtocol, specify the socket type (for example, SOCK_STREAM, SOCK_DGRAM) and the protocol family (for example, AF_INET, AF_IPX) for the given addresses.

The last two fields of the WSAQUERYSET structure are dwOutputFlags and lpBlob. These two fields are generally not needed for service registration; they are more useful when querying for a service instance (covered in the next section). Only the name space provider can return a BLOB structure. That is, when registering a service you cannot add your own BLOB structure to be returned in client queries.

Table 10-6 lists the fields of the WSAQUERYSET structure and identifies which are required or optional depending on whether a query or a registration is being performed.

Service registration example

In this section, we'll show you how to register your own service under both the SAP and NTDS name spaces. The Windows NT domain space is quite powerful, which is why we want to include it in our example. However, be aware of the following features. First the Windows NT domain space requires Windows 2000 since it is based on the Active Directory. This also means that the Windows 2000 workstation on which you hope to register and/or look up services must have a machine account in that domain in order to access the Active Directory. The other feature to note is that the Windows NT domain space is capable of registering socket addresses from any protocol family. This means that your IP and IPX services can all be registered in the same name space. It also means that there is a dynamic way of adding and removing IPbased services. Figure 10-1 illustrates the basic steps required to register an instance of a service. For the sake of simplicity, no error checking is performed.

Table 10-6. WSAQUERYSET fields

Field Query Registration
dwSize Required Required
lpszServiceInstanceName String or "*" required Required
lpServiceClassId Required Required
lpVersion Optional Optional
lpszComment Ignored Optional
dwNameSpace
lpNSProviderId
One of these two fields must be specified One of these two fields must be specified
lpszContext Optional Optional
dwNumberOfProtocols Zero or more Zero or more
lpafpProtocols Optional Optional
lpszQueryString Optional Ignored
dwNumberOfCsAddrs Ignored Required
lpcsaBuffer Ignored Required
dwOutputFlags Ignored Optional
lpBlob Ignored, can be returned by the query Ignored

Figure 10-1. The WSASetService example

 SOCKET socks[2]; WSAQUERYSET qs; CSADDR_INFO lpCSAddr[2]; SOCKADDR_IN sa_in; SOCKADDR_IPX sa_ipx; IPX_ADDRESS_DATA ipx_data; GUID guid = SVCID_NETWARE(200); int ret, cb; memset(&qs, 0, sizeof(WSAQUERYSET)); qs.dwSize = sizeof(WSAQUERYSET); qs.lpszServiceInstanceName = (LPSTR)"Widget Server"; qs.lpServiceClassId = &guid; qs.dwNameSpace = NS_ALL; qs.lpNSProviderId = NULL; qs.lpcsaBuffer = lpCSAddr; qs.lpBlob = NULL; // // Set the IP address of our service // memset(&sa_in, 0, sizeof(sa_in)); sa_in.sin_family = AF_INET; sa_in.sin_addr.s_addr = htonl(INADDR_ANY); sa_in.sin_port = 5150; socks[0] = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); ret = bind(socks[0], (SOCKADDR *)&sa_in, sizeof(sa_in)); cb = sizeof(sa_in); getsockname(socks[0], (SOCKADDR *)&sa_in, &cb); lpCSAddr[0].iSocketType = SOCK_DGRAM; lpCSAddr[0].iProtocol = IPPROTO_UDP; lpCSAddr[0].LocalAddr.lpSockaddr = (SOCKADDR *)&sa_in; lpCSAddr[0].LocalAddr.iSockaddrLength = sizeof(sa_in); lpCSAddr[0].RemoteAddr.lpSockaddr = (SOCKADDR *)&sa_in; lpCSAddr[0].RemoteAddr.iSockaddrLength = sizeof(sa_in); // // Set up the IPX address for our service // memset(sa_ipx.sa_netnum, 0, sizeof(sa_ipx.sa_netnum)); memset(sa_ipx.sa_nodenum, 0, sizeof(sa_ipx.sa_nodenum)); sa_ipx.sa_family = AF_IPX; sa_ipx.sa_socket = 0; socks[1] = socket(AF_IPX, SOCK_DGRAM, NSPROTO_IPX); ret = bind(socks[1], (SOCKADDR *)&sa_ipx, sizeof(sa_ipx)); cb = sizeof(IPX_ADDRESS_DATA); memset (&ipx_data, 0, cb); ipx_data.adapternum = 0; ret = getsockopt(socks[1], NSPROTO_IPX, IPX_ADDRESS, (char *)&ipx_data, &cb); cb = sizeof(SOCKADDR_IPX); getsockname(socks[1], (SOCKADDR *)sa_ipx, &cb); memcpy(sa_ipx.sa_netnum, ipx_data.netnum, sizeof(sa_ipx.sa_netnum)); memcpy(sa_ipx.sa_nodenum, ipx_data.nodenum, sizeof(sa_ipx.sa_nodenum)); lpCSAddr[1].iSocketType = SOCK_DGRAM; lpCSAddr[1].iProtocol = NSPROTO_IPX; lpCSAddr[1].LocalAddr.lpSockaddr = (struct sockaddr *)&sa_ipx; lpCSAddr[1].LocalAddr.iSockaddrLength = sizeof(sa_ipx); lpCSAddr[1].RemoteAddr.lpSockaddr = (struct sockaddr *)&sa_ipx; lpCSAddr[1].RemoteAddr.iSockaddrLength = sizeof(sa_ipx); qs.dwNumberOfCsAddrs = 2; ret = WSASetService(&qs, RNRSERVICE_REGISTER, 0L); 

The example code in Figure 10-1 illustrates how to set an instance of a service so that a client of that service can find out the address that it requires to communicate with the service. The first order of business is to initialize the WSAQUERYSET structure. We also need to give a name to the instance of our service. In this case, we simply call it "Widget Server." The other critical step is to use the same GUID we used to register our service class. Whenever you register an instance of a service, that service must belong to a service class. In this case, we use the "Widget Service Class" (defined in the previous section), whose GUID is SVCID_NETWARE(200). The next step is to set the name spaces in which we are interested. Since our service runs over both IPX and UDP, we specify NS_ALL. Because we're specifying a preexisting name space, lpNSProviderId must be set to NULL.

The next step is to set up the SOCKADDR structures within the CSADDR_INFO array that WSASetService passes as the lpcsaBuffer field of the WSAQUERYSET structure. You'll notice that in our example we actually create the sockets and bind them to a local address before we set up the SOCKADDR structure. This is because we need to find the exact local address to which clients need to connect. For example, when creating our UDP socket for the server, we bind to INADDR_ANY, which doesn't give us the actual IP address until we call getsockname. Using the information returned from getsockname, we can build a SOCKADDR_IN structure. Within the CSADDR_INFO structure, we set the socket type and the protocol. The other two fields are the local and remote address information. The local address is the address that a server should bind to, while the remote address is the address that a client should use to connect to the service.

After setting up the SOCKADDR_IN structure for our UDP-based server, we set up the IPX-based service. In Chapter 6, you saw that servers should bind to the internal network number by setting the network and node number to 0. Again, this doesn't give you the address that clients need, so call the socket option IPX_ADDRESS to obtain the actual address. In filling the CSADDR_INFO structure for IPX, use SOCK_DGRAM and NSPROTO_IPX for the socket type and the protocol, respectively. The last step is to set the dwNumberOfCsAddrs field in the WSAQUERYSET structure to 2, as there are two addresses—UDP and IPX—that clients can use to establish a connection. Finally, call WSASetService with our WSAQUERYSET structure, the RNRSERVICE_REGISTER flag, and no control flags. You do not specify the SERVICE_MULTIPLE control flag so that if you choose to deregister our service, all instances of the service (both the IPX and UDP addresses) will be deregistered.

There is one consideration that the above example does not take into account: multihomed machines. If you create a UDP-based server that binds to INADDR_ANY on a multihomed machine, the client can connect to the server on any of the available interfaces. In the case of IP, getsockname is not sufficient; you must obtain all local IP interfaces. There are a number of methods of obtaining this information, depending on the platform you are on. One method common to all platforms is calling gethostbyname to return a list of IP addresses for our name. Under Winsock 2, you can also call the ioctl command SIO_GET_INTERFACE_LIST. For Windows 2000, the ioctl SIO_ADDRESS_LIST_QUERY is available. Finally, the IP helper functions discussed in Appendix B can be used as well. Simple TCP/IP name resolution and gethostbyname are presented in Chapter 6, while ioctl commands are found in Chapter 9. In addition, Rnrcs.c (on the companion CD) is a full-fledged example that addresses multihomed machines.



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