Quick Start
Introduction
VpnSDK is a reactive/observable library that allows developers to easily implement VPN functionality in to their application. In this quick start we will build a basic console application that authenticates with the WLVPN API, finds a VPN server to connect to and connect to it using the IKEv2 protocol.
Getting Started
Create a Project and Install the NuGet package
Begin by creating a .NET Framework console application the standard way in Visual Studio. After your project has been created, add the NuGet package "VpnSDK.Whitelabel" from the WLVPN MyGet feed.
Our basic console VPN application
To get you up and running, shown below is the full source code for a console application that implements the functionality described in the introduction. While it might seem intimidating at first, the following sections will dissect this and explain the steps required to get up and running and what each line of code means. If you're familiar with reactive-style development, the following code may only be what you need to get started on your own application. If you're not familiar, continue reading.
Code example: The full console application.
using DynamicData;
using System;
using System.IO;
using System.Linq;
using VpnSDK.Public;
using VpnSDK.Public.Enums;
using VpnSDK.Public.Interfaces;
namespace VpnSdkExample
{
internal class Program
{
private static void Main(string[] args)
{
ISDK sdkManager = new SDKBuilder()
.SetApiKey("EXAMPLE_API_KEY")
.SetApplicationName("VPN SDK Example")
.SetAuthenticationToken("AUTHENTICATION_TOKEN")
.SetRasConfiguration(new RasConfiguration()
{
RasDeviceDescription = "ExampleVPN"
})
.Create();
sdkManager.Login("demo", "demo").Subscribe(status =>
{
Console.WriteLine($"Authentication status: {status}");
if (status == AuthenticationStatus.Authenticated)
{
Console.WriteLine("Authenticated successfully!");
PostLogin(sdkManager);
}
}, exception => { Console.WriteLine($"Unable to authenticate. Reason: {exception.Message}"); });
Console.ReadLine();
}
private static void PostLogin(ISDK sdkManager)
{
// We're now logged in.
// First we'll subscribe to the WhenLocationListChanged observable.
// This allows the SDK to know that it needs to start watching for location changes.
sdkManager.WhenLocationListChanged.Subscribe();
// Connect to the RegionList and turn it in to an observable list.
var observableList = sdkManager.RegionsList.Connect().AsObservableList();
// Observe our list for changes.
// For now, we just listen until the first region appears then unsubscribe.
observableList.CountChanged.Subscribe(regionsAvailable =>
{
Console.WriteLine($"Amount of available regions updated to: {regionsAvailable}");
if (regionsAvailable >= 2)
{
// Get the first region, use OfType<IRegion> if you wish to not get the IBestAvailable (this means the best region for the current user) location.
IRegion region = observableList.Items.OfType<IRegion>().First();
Console.WriteLine($"Found region! {region.City}, {region.Country}.");
// Dispose the list since for our example, we don't want to be informed of anymore changes.
observableList.Dispose();
ConnectToVPN(sdkManager, region);
}
});
}
private static void ConnectToVPN(ISDK sdkManager, IRegion region)
{
Console.WriteLine("Starting VPN connection.");
// Create an IConnectionConfiguration that describes how you want to connect to the ILocation.
// In this example, we're just using IKEv2.
IConnectionConfiguration vpnConfiguration = new RasConnectionConfigurationBuilder().SetConnectionType(NetworkConnectionType.IKEv2).Build();
sdkManager.Connect(region, vpnConfiguration).Subscribe(vpnStatus =>
{
Console.WriteLine($"VPN status: {vpnStatus}");
if (vpnStatus == ConnectionStatus.Connected)
{
Console.WriteLine("Connection successful!");
// Now disconnect and listen for the disconnect changes.
sdkManager.Disconnect().Subscribe(status =>
{
Console.WriteLine($"Disconnect status: {status}");
if (status == ConnectionStatus.Disconnected)
{
Console.WriteLine("Disconnected from the VPN.");
Console.WriteLine("Press enter to quit.");
}
});
}
},
exception =>
{
Console.WriteLine("The VPN connection encountered an error.");
Console.WriteLine($"Error: {exception.Message}");
});
}
}
}
As mentioned before, this might be a lot to take in. We'll now dissect this and explain step by step what is going on and how to write this for yourself.
Configuring the SDK Manager
Everything in the VpnSDK library is provided through an ISDK
object that provides observable methods for both WLVPN API operations and VPN connectivity.
The ISDK
can be thought of as your single source of truth. It is the base object that will handle everything, and be your main source of data. You create one instance and use that throughout your entire application lifetime. Before we get in to using the ISDK
object (also referred as the SDK manager), we need to actually construct it first.
To construct an ISDK
object, we provide a SDKBuilder
that uses the standard builder pattern to configure and construct the ISDK
object.
Let's start by doing the bare minimum.
Code example: Creating an ISDK
object using the configuration builder.
ISDK sdkManager = new SDKBuilder().Create();
As you'll see quickly, upon execution this will throw an exception informing you that you did not configure the required parameters. The method called at the end of this chain .Create()
is always the last one to be called and before it, should be the configuration of your SDK object.
Let's start by configuring it with an API key (this is provided by your WLVPN account manager).
Code example: Creating an ISDK
object using the configuration builder and configuring an API key.
ISDK sdkManager = new SDKBuilder().SetApiKey("ExampleAPIKeyHere").Create();
Unfortunately again, you will see this throws an exception about there still being missing parameters. We'll expand this further by configuring the authentication token (provided by your account manager), setting the application name.
Code example: Creating an ISDK
object using the configuration builder and configuring the mandatory parameters.
ISDK sdkManager = new SDKBuilder()
.SetApiKey("ExampleAPIKeyHere")
.SetAuthenticationToken("@authToken")
.SetApplicationName("VpnSDK Console Example")
.Create();
For a final time, you're going to an exception on create but notice this time, the exception is telling you that you have not added any VPN configuration settings. As our example application only intends to use IKEv2, we need to use the method .SetRasConfiguration()
. RAS (Remote Access Services) refers to the Windows provided VPN API that handles IKEv2, SSTP and L2TP connections. To use those, it requires what is referred to as a "Device Description" which is just Microsoft phrasing for the name of the connection. You generally want to refer to your connection name as a single word that reflects your brand name. Knowing this now, let's configure the ability to use IKEv2 with our SDK manager and finally have an instance of ISDK
that we can use.
Code example: Creating an ISDK
object using the configuration builder and configuring the mandatory parameters as well as the RAS configuration.
ISDK sdkManager = new SDKBuilder()
.SetApiKey("ExampleAPIKeyHere")
.SetAuthenticationToken("@authToken")
.SetApplicationName("VpnSDK Console Example")
.SetRasConfiguration(new RasConfiguration()
{
RasDeviceDescription = "ExampleVPN"
})
.Create();
We now have a working ISDK
object we can use and move forward with. Next, we'll go in to how to authenticate a user with the WLVPN API.
Logging a user in
Now that the ISDK
object has been created, we can start using the reactive methods provided to login to the API. Reactive methods are asynchronous functions that expect a delegate/callback to handle when something has updated or when an error has occurred. The method used to begin a login attempt is .Login("username","password");
.
Code example: Using the login method and subscribing to the result to monitor whether the user was able to log in or not.
sdkManager.Login("demo", "user").Subscribe(status =>
{
Console.WriteLine($"Authentication status: {status}");
if (status == AuthenticationStatus.Authenticated)
{
Console.WriteLine("Authenticated successfully!");
}
}, exception =>
{
Console.WriteLine($"Unable to authenticate. Reason: {exception.Message}");
});
As you can see, it's fairly simple, we have one delegate that prints to the console if the authentication status has updated, checks if the status is now AuthenticationStatus.Authenticated
and prints a congratulatory message. The second delegate checks if an exception/error was thrown during authentication (e.g. if the username or password was wrong) and prints out a message informing so that includes the message returned by the API. These messages are considered user friendly so in your application if you wish to present the reason it failed to the user, do not be afraid to use exception.Message
.
You may have noticed something wrong though, your application exists instantly before the login can complete. At the end of your private static void Main(string[] args)
method, add a a Console.ReadLine()
so your console application only closes when you press Enter
.
Now with that out of the way, we can see in our console that the authentication occurs and we're told whether it connected or if it failed.
The SDK manager is now in a state where all the other methods on it are able to actually do something as API connectivity has been configured and we have a user authenticated.
To help us keep all our code related to what to do post-login, create the following function under Main()
. We make sure to pass a copy of our SDK manager, you could keep this as a global static object if you wished, in a real-world application you'd most likely have this set up in an IoC container (e.g. SimpleInjector).
private static void PostLogin(ISDK sdkManager)
{
}
Finally, after the line Console.WriteLine("Authenticated successfully!")
add a call to PostLogin()
like so: PostLogin(sdkManager);
We now have a PostLogin(ISDK sdkManager)
method that will only be called when the SDK manager has authenticated.
All future logic that needs a connected SDK manager is placed within the PostLogin
method.
Retrieving Regions/Locations
Once logged in, we can now start retrieving all the available locations the end user can connect to. This is provided through a DynamicData SourceList
that updates itself automatically when there are changes on the API side. Unless you're very familiar with how the DynamicData library works, you will most likely want to just convert this to an ObservableList
. This functionality is built in and very easily done.
To keep things simple, we will do that and work from there. A caveat with this approach is you must have a subscription to WhenLocationListChanged
to inform the SDK manager that something is listening for location updates and to start updating the RegionsList
observable. It is suggested that you use WhenLocationListChanged
which can be seen used in the ConsumerVPN example but for this console application we will keep things simple.
Code example: Converting the RegionsList
in to a standard ObservableList
.
private static void PostLogin(ISDK sdkManager)
{
// First we'll subscribe and start listening for server changes.
// This allows the SDK to know that it needs to start watching for server changes.
sdkManager.WhenLocationListChanged.Subscribe();
// Connect to the RegionList and turn it in to an observable list.
var observableList = sdkManager.RegionsList.Connect().AsObservableList();
}
You may have noticed we needed to call Connect()
first. Since it's a live collection, we connect to it and it updates itself, rather than subscribing to an event stream.
We now have our good old fashioned observable list. In a GUI application you could use this to bind it to a UI element or you could apply filters after running Connect()
to restrict the end result observable list only having certain countries or locations. Since this is just a simple example, all we want to do is observe the list, find when it is populated and connect to the first region available.
The ObservableList
type provides another observable of it's own that allows us to subscribe to when the amount of items in the collection have changed. This property is called CountChanged
.
Code example: Subscribing to the observable list of regions and finding when there are more than 2 regions available.
observableList.CountChanged.Subscribe(regionsAvailable =>
{
Console.WriteLine($"Amount of available regions updated to: {regionsAvailable}");
if (regionsAvailable >= 2)
{
Console.WriteLine("There are more than two regions available!");
}
});
Now that we have a way of knowing that there are a good amount of regions available in the observable list, let's get the first region in the list.
Code example: Getting the first region from our observable list.
observableList.CountChanged.Subscribe(regionsAvailable =>
{
Console.WriteLine($"Amount of available regions updated to: {regionsAvailable}");
if (regionsAvailable >= 2)
{
// Get the first region, use OfType<IRegion> if you wish
// to not get an IBestAvailable object.IBestAvailable is a region
// that represents the best server for the user to connect to.
IRegion region = observableList.Items.OfType<IRegion>().First();
Console.WriteLine($"Found region! {region.City}, {region.Country}.");
}
});
The .OfType<IRegion>()
might throw you off at first. This is because the first object you get from the region list is an IBestLocation
object that acts like a location but when passed to the VPN connection methods, automatically gets converted in to what the current best region for the user to connect to (based off region load as well as distance).
If you run your console application now, you should see that you found a region. The first one that generally appears is Toronto, Canada but this doesn't matter. As you build your real application, you can use the Sort()
and Filter()
methods on the observable list to sort and represent the regions in your preferred way. One simple example might be filtering out any regions with the country code AU
if you do not wish for your users to be able to connect to Australia.
With an IRegion
object at your disposal, we now have everything we need to establish a VPN connection.
Before we go on to the next step, like in the previous section (Logging a user in), we're going to create a new static method in our application where we can keep our VPN connection logic in. Create the following static method:
private static void ConnectToVPN(ISDK sdkManager, IRegion region)
{
}
We also need to do two last things, before we call ConnectToVPN()
after getting our IRegion
object, we need to dispose the observable list. By doing so, this ensures no further subscription callbacks happen as disposing an observable stops any future delegate execution attempts. In a standard application you would tend to not dispose but since our application is very "Do X then Y then Z", we don't want to accidentally call ConnectToVPN()
twice.
Update your CountChanged
subscriber to the following to both dispose and call the ConnectToVPN()
method.
Code example: Final implementation of the CountChanged subscriber for our observable region list.
observableList.CountChanged.Subscribe(regionsAvailable =>
{
Console.WriteLine($"Amount of available regions updated to: {regionsAvailable}");
if (regionsAvailable >= 2)
{
// Get the first region, use OfType<IRegion> if you wish
// to not get an IBestAvailable object.IBestAvailable is a region
// that represents the best server for the user to connect to.
IRegion location = observableList.Items.OfType<IRegion>().First();
// Dispose the list which will in turn cancel the subscription.
observableList.Dispose();
Console.WriteLine($"Found region! {location.City}, {location.Country}.");
StartConnection(location);
}
});
Connecting to a Region
Now that you have an IRegion
object that you wish to connect to, we will use the .Connect(region, configuration)
method on the SDK manager. This returns an observable VPN connection that allows you to observe the connection status as well as any errors that occur.
You may have noticed that Connect requires a configuration parameter, per-connection you must create this object based on what type of VPN protocol you want to use. In this quick-start since we are planning to use IKEv2, we will use the RasConnectionBuilder
class (remember RAS from constructing the ISDK
object?) to create our configuration. Refer to the following code example as a way to create a simple IKEv2 configuration.
IConnectionConfiguration vpnConfiguration = new RasConnectionConfigurationBuilder().SetConnectionType(NetworkConnectionType.IKEv2).Build();
With both our IRegion
and IConnnectionConfiguration
objects prepared, we can connect. Just like the login method, you subscribe to the result of Connect()
and provide a delegate to handle updates of the VPN connection and a delegate to handle any errors.
For our console application we will use the following as a simple example that writes to the console the VPN connection status and checks if it was successful, if so, print a special message and then disconnects. If there was an error connecting or the connection fails while it's connected, we will write that to the console as well.
Since we don't want to create an application that creates a VPN application and then it stays connected forever for our example, we're going to subscribe to the Connect()
method and when the status is ConnectionStatus.Connected
we will run a Disconnect()
in there and subscribe to that. You can observe new observables in the observation of your current observables. It can get pretty crazy.
While it might be intimidating at first, this is no different than how we use .Login()
earlier.
Code example: Connecting to an IKEv2 VPN with an IConnectionConfiguration
object and an IRegion
followed by a disconnect if the connection process had no issues.
sdkManager.Connect(region, vpnConfiguration).Subscribe(vpnStatus =>
{
Console.WriteLine($"VPN status: {vpnStatus}");
if (vpnStatus == ConnectionStatus.Connected)
{
Console.WriteLine("Connection successful!");
// Now disconnect and listen for the disconnect changes.
sdkManager.Disconnect().Subscribe(status =>
{
Console.WriteLine($"Disconnect status: {status}");
if (status == ConnectionStatus.Disconnected)
{
Console.WriteLine("Disconnected from the VPN.");
Console.WriteLine("Press enter to quit.");
}
});
}
},
exception =>
{
Console.WriteLine("The VPN connection encountered an error.");
Console.WriteLine($"Error: {exception.Message}");
});
We now have a complete console application that authenticates, finds a region to connect to, establishes a VPN connection and disconnects and all within the restrictions that developing for a console application provides without issue.
Conclusion
As the guide has shown, the abstractions provided to you with VpnSDK makes everything about integrating WLSDK API and VPN functionality simple. While the quick start guide doesn't cover all the features provided by VpnSDK (they are covered in other articles), you should now have a launching point on how to get started with your own application. Provided with your WLVPN VpnSDK package are two example WPF applications built as a reference guide on how we at WLVPN use the VpnSDK in our own applications.