Registering a Service

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 WSA- InstallServiceClass 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.

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 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 regist-ered 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. (Writing your own name space is discussed in Chapter 12.) 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

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, there are a few features to be aware of before you begin. First, the Windows NT domain space requires Windows 2000 or Windows XP because it is based on the Active Directory directory service. This also means that the Windows 2000 or Windows XP workstation on which you hope to register and/or look up services must have a machine account in that domain in order to access Active Directory. The other feature to note is that the Windows NT domain space is capable of registering socket addresses from any protocol family. Your IP and IPX services can all be registered in the same name space. Also, there is a dynamic way of adding and removing IP-based services. The following code example illustrates the basic steps required to register an instance of a service. For simplicity, no error checking is performed.

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;      WSASetService(&qs, RNRSERVICE_REGISTER, 0L);

The example illustrates how to set an instance of a service so that a client of that service can find out the address that it needs 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 that we are interested in. Because 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 that clients need to connect to. 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, and 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 4, 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 get 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 because 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 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. With IP, getsockname is not sufficient; you must obtain all of the local IP interfaces. There are a number of methods for getting 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 and Windows XP, the ioctl SIO_ADDRESS_LIST_QUERY is available. Finally, the IP helper functions discussed in Chapter 16 can be used as well. Simple TCP/IP name resolution and gethostbyname are presented in Chapter 3, and ioctl commands are found in Chapter 7.

note

There is a full-fledged example that addresses multihomed machines in a file named RNRCS.CPP on the companion CD.



Network Programming for Microsoft Windows
Network Programming for Microsoft Windows (Microsoft Professional Series)
ISBN: 0735605602
EAN: 2147483647
Year: 2001
Pages: 172
Authors: Anthony Jones

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