PLUSNative: Network Floating via Semaphores

Network Floating Licensing is where an application may be restricted to running on a specific network, and restricted to a certain number of concurrent seats (where a seat may be a user or running instance of your application). This topic guides you through some basics of getting started with using network floating via semaphore files on a Windows share on a local area network (LAN). This feature has some limitations, so it is very important to review the overview of network floating before using it.

Getting Started

As is characteristic of PLUSNative, the first step is to initialize an API context. Only one network semaphore may be opened at a time for each API context initialized. In other words, if you have a single application that needs to manage separate semaphores to limit concurrent use of individual modules/features separately, each one will require its own API context.

Important

Note that the network floating via semaphore files is only supported in Windows platforms, from Windows Vista/Server 2008 and later. See the overview of network floating for additional details.

Before you proceed, note that you will need to establish the following pieces of information to use semaphores for network floating:

Cleaning-up Orphaned Semaphores

When you attempt to open a session, the default behavior is to first get a list of files in the semaphore path, and see if any files are not present in the possible list of file names that could be present. If no files are absent/free, the default behavior is then to search for a file that may have been orphaned by attempting to obtain a lock on each semaphore file present. Since this second part of the process typically results in significant performance degradation (which worsens as the number of seats allowed increases), it is best to first try to delete all orphaned semaphore files before attempting to open one. To do this, you can call the SK_PLUS_NetworkSemaphoreCleanup function like:

C/C++
if (SK_ERROR_NONE != SK_PLUS_NetworkSemaphoreCleanup(context, SK_FLAGS_NETWORK_SEMAPHORE_NOCLEANUPTHREAD, "[semaphorePath]", "[semaphorePrefix]", 0))
{
//TODO: Optionally handle the error condition.
}

You'll notice that in the example above, we pass the SK_FLAGS_NETWORK_SEMAPHORE_NOCLEANUPTHREAD flag, which tells the function to only attempt to clean up the files once for now. After your semaphore has been opened successfully, you can use this same function to create a background thread that periodically attempts to delete all orphaned files to help maximize performance.

C/C++
if (SK_ERROR_NONE != SK_PLUS_NetworkSemaphoreCleanup(context, SK_FLAGS_NONE, "[semaphorePath]", "[semaphorePrefix]", [delay]))
{
//TODO: Optionally handle the error condition.
}

In the example above, [delay] is the number of seconds the background thread will wait between attempts to delete orphaned files. For example, this could be replaced with 600 to try to delete files every 10 minutes. While this functionality is available as a convenience, keep in mind that this will increase network traffic for each user that has this background thread running.  If you have concerns about this, consider increasing the delay to suit your needs and the needs of your customers.

Opening a Semaphore

Opening a semaphore is synonymous with attempting to allocate a seat, which is only allowed to occur if the seats are not already consumed by other users and/or application instances. A rough example of the contents of a function that opens a semaphore is shown below.

C/C++
if (SK_ERROR_NONE != SK_PLUS_NetworkSemaphoreOpen(context, SK_FLAGS_NONE, [seatsAllowed], "[seatsAllowedXPath]", "[semaphorePath]", "[semaphorePrefix]", NULL, NULL))
{
//TODO: Handle the error condition, and show the user an error message.
}

Of course, there are a few blanks for you to fill in for the example above to work (or even compile):

Make sure the semaphore remains open as long as the user is accessing the feature or application being licensed.

Verifying the Semaphore

Additionally, as long as your semaphore remains open, you may validate it periodically and/or any time important features are used. The example code excerpt shown below provides a rough illustration on how you can verify your application still has a valid lock on its semaphore.

C/C++
if (SK_ERROR_NONE != SK_PLUS_NetworkSemaphoreVerify(context, SK_FLAGS_NONE))
{
//TODO: Handle the error condition, and show the user an error message.
}

Additionally, you can get statistics on the number of seats being used for displaying the status of the license.  This can be done using the SK_PLUS_NetworkSemaphoreStatistics function, as depicted below.

C/C++
int seatsAllowed = 0, seatsAllocated = 0, seatsAvailable = 0;
if (SK_ERROR_NONE != SK_PLUS_NetworkSemaphoreStatistics(context, SK_FLAGS_NONE, "[semaphorePath]", "[semaphorePrefix]", &seatsAllowed, &seatsAllocated, &seatsAvailable))
{
//TODO: Handle the error condition, and show the user an error message.
}

Closing the Semaphore

When your user finishes using the application or feature being licensed, you should close the semaphore to make the seat available to other users. A rough example code excerpt showing how to close a semaphore is shown below.

C/C++
if (SK_ERROR_NONE != SK_PLUS_NetworkSemaphoreDispose(context, SK_FLAGS_NONE))
{
//TODO: Optionally handle the error condition.
}

The semaphore is also closed when disposing of the context used when opening it (when calling SK_ApiContextDispose). Note that, even if you give the user an option to close the semaphore, you should consider automatically closing it when the relevant application or feature is being closed to help ensure seats become available to other users as appropriate.

System Identification

System identifiers are what allow you to bind a license to a particular "system." Read more about adding basic copy protection for an overview on how this works and is implemented. In most cases, the system being licensed is generally a single device. However, in the case of network floating licensing, the "system" that is authorized to use the license is typically a network of devices. Naturally, this difference means system identification is usually handled differently when using network floating licensing via semaphores.

Binding the license to the share

To begin, PLUSNative includes a SK_SYSTEM_IDENTIFIER_ALGORITHM_NETWORK_NAME system identifier algorithm, which is designed to generate an identifier using a given server name and share name in a UNC path (e.g. \\SERVER1\\SHARE1). This algorithm can also accept a mapped network drive configured to use a UNC path and to reconnect at login.

Important

While this algorithm is import to use to prevent casual copying of licenses from one file share to another, this by itself is not enough to make things completely tamper-proof. This is because of how it is technically possible to create another server with the same name and share name on a different segment or network, and how it is possible to copy sub-directories on a share. Furthermore, these concerns are exacerbated by the ubiquity of virtualization technologies. As such, we strongly recommend using SK_SYSTEM_IDENTIFIER_ALGORITHM_NETWORK_NAME with additional system identifier algorithms to strengthen copy protection in your applications. We also recommend you consider using Cloud-Controlled Network Floating Licensing instead, if possible.

Binding the license to the server's volume

A second system identifier algorithm PLUSNative includes is SK_SYSTEM_IDENTIFIER_ALGORITHM_HARD_DISK_VOLUME_SERIAL, which is designed to get the serial number (typically determined when a drive is formatted) of a volume/drive. While this is most often used for activating individual devices, it is also possible to use this to generate an identifier for a remote drive's volume serial. Below is some example code showing how you can implement this in PLUSNative:

C++
const char *volume = "R:\\";
char formatedVolumeSerialNumber[12] = { 0 };
DWORD volumeSerialNumber = 0;

GetVolumeInformation(volume, NULL, 0, &volumeSerialNumber, NULL, NULL, NULL, 0)
if (0 == volumeSerialNumber)
{
/*TODO: Add your own error handling here!*/
}
else
{
sprintf_s(formatedVolumeSerialNumber, sizeof(formatedVolumeSerialNumber), "%u", volumeSerialNumber);
/*TODO: Add your error handling below for when SK_PLUS_SystemIdentifierAddCurrentIdentifier does not return SK_ERROR_NONE.*/
SK_PLUS_SystemIdentifierAddCurrentIdentifier(context, SK_FLAGS_NONE, "HardDiskVolumeSerialIdentifier1", "HardDiskVolumeSerialIdentifier", formatedVolumeSerialNumber);
}

Even though this algorithm further strengthens your application's copy protection, it too is not completely tamper-proof. The volume serial number is typically determined when a drive is formatted. It is possible to duplicate this when using a system imaging tool (such as Norton Ghost) to restore a drive image backup to a new drive, copying a virtual machine to another host, or just using a third-party tool to alter a volume's serial. While these approaches do require some technical expertise, it is not outside of the capabilities of the typical IT staff who would be responsible for installing and configuring your application for a network. Consequently, we again strongly advise you to consider using additional algorithms, such as binding to application-specific data, to further strengthen copy protection for your applications.

Binding to application-specific data

In many cases, it is possible to bind a license to some information that is unique to the customer to which the license was issued. For example, if your software already has to ask the user for some sort of business license number, your software could implement a custom identifier that leverages that information. While this does not necessarily prevent abuse from within a given organization, it is a good way to prevent abuse from spreading outside the organization.

Binding to an absolute path

As noted in the "Binding the license to the share", binding the license to a server name and share is not enough to prevent abuse. Sometimes it is necessary to take additional measures to prevent abuse of a license from within an organization, which is not always practical to achieve by binding to application-specific data (as noted in the section immediately above). To prevent abuse on an organizations server, you can do the following:

Review the Samples

The sample applications include much more functionality, including: