< Intro To Registration & Name Resolution | RnR Main | Service Registration Program Example >
What do we have in this chapter 8 part 2?
|
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 your service's characteristics. It is important to distinguish between a service class and the actual service. 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 it belongs to. Once this occurs, a client can perform a query to find out where your service instance is running and therefore can attempt communication.
Installing a Service Class
Before you register an instance of a service, you need to define the service class that your service will belong to. A service class defines what name spaces that 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 of 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 basically 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. In addition, several macros exist that accept a port number as a parameter and return the corresponding service's GUID. 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 8-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 8-2 Common Service ID Macros
|
|
Macro |
Description |
SVCID_TCP(Port) |
Generates a GUID from a TCP port number |
SVCID_DNS(RecordType) |
Generates a GUID from a DNS record type |
SVCID_UDP(Port) |
Generates a GUID from a UDP port number |
SVCID_NETWARE(SapId) |
Generates a GUID from an 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 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 8-3 lists the various attributes available. Every attribute listed has a value type of REG_DWORD.
Table 8-3 Service Types
|
|||
String Value |
Constant Define |
Name Space |
Description |
“SapId” |
SERVICE_TYPE_VALUE_ SAPID |
NS_SAP |
SAP identifier |
“Connection- Oriented” |
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_DNS NS_NTDS |
UDP port |
The dwNameSpace is the name space this attribute applies to. Table 8-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 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;
WSAInstallServiceClass(&sci);
The first noticeable thing this example illustrates 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. In addition, 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 part 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 because 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 that this service type applies to, as well as the type and size of the value. Table 8-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, then 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.
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 8-4 describes the three valid flags.
Table 8-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. |
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. In addition, at some later point you can deregister a single instance of the service by using the RNRSERVICE_DELETE service flag. Table 8-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 8-5 WSASetService() Flag Combinations
|
||
RNRSERVICE_REGISTER Flags |
Meaning |
|
|
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 Flags |
Meaning |
|
|
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 Flags |
Meaning |
|
|
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 that this service instance belongs to. 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 that value only; otherwise, use NS_ALL. It is possible to reference a custom name space provider. 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 address where 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;
In addition, 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, and 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 or 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 8-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.
Table 8-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 |
< Intro To Registration & Name Resolution | RnR Main | Service Registration Program Example >