Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Hosted-services / Azure

Azure Local Network Gateway Dynamic IP Update

5.00/5 (2 votes)
9 Apr 2017CPOL5 min read 16.8K  
Seamsless S2S VPN without static IP and expensive VPN devices.

Introduction

Site-to-Site connections, usually referred as S2S, can be used for cross-premises and hybrid configurations. S2S VPN gateway connection is a connection over IPsec/IKE VPN tunnel that we can establish between Azure resources and a home/enterprise network. This type of connection though requires a couple of things that are not always available in a home lab :

  1. An Azure Subscription - go grab one, that's the easy part plus there are some nice premiun signup offers.
  2. A VPN device in on-premises that belongs to the list of compatible VPN devices
  3. And finally this VPN device is been assigned with a public static IP which is not located behind a NAT.

Well, VPN device and static IP requirements raise the bar a bit. Nobody has a VPN device at home or even at enterpise level employing such a device for experimental or development reasons would be an overkill from every perspective. Thankfully there is a cheap and easy alternative : setting up a RRAS (Routing and Remote Access Service) Server. Purpose of the article is not presenting how to establish a S2S connection or a RRAS server. There are pretty awesome articles out there for the former and the latter themas (Google is your friend here ;) )

Actually the article is focusing on the "static IP requirement". It is rather seldom, someone having a static IP at home. Most of us have a broadband connection with a dynamic IP address which frequently changes.  One of the core elements of the S2S VPN connection is the so called Local Network Gateway which is that component that resides in the cloud virtual network and points to the on-premises location. We need to specify two parameters to the Local Network Gateway :

  1. The gateway IP address, which should be the IP of your VPN device - that cannot be located behind NAT (imporant detail I'll never get tired to highlight)
  2. The Address Prefix which is the on-premises address space that Azure will route traffic to.

 

So what do we actually need is a way to automatically synchronize our Local Network Gateway's IP address with the one that our ISP assignes to our broadband router.

Background

When need to manage Virtual Machines, Virtual Networks, Web or Mobile Apps, or Storage Accounts via native .NET code, Management Libraries for .NET come to the rescue. Azure has two different models for deploying, creating and managing resources. The so-called Classic and Resource Manager. In this article we are going to focus exclusively on the Resource Manager module as Classic is slowly deprecated and on the Microsoft.Azure.Management.Network namespace of the Management Libraries.

We are going to develop a simple console application, that wil periodically run in the background as a Windows Scheduled Task, which will identify our current IP address and will subsequently silently update the Local Network Gateway's IP address maintaining like that an almost seamless VPN connection among cross-premises.

Getting things done

Foremost, we must register a new application in our Azure AD. It has to be a Native App, given that we are talking for a console application here. The redirect URI doesn't really matter but fill in at least something valid and meaningful.

 

Grant Windows Azure Service Management API permissions to your app :

 

and create a Key, we are going to need it later when requesting an authentication token for our application :

 

 

In order to be able to work with Resource Manager model (ARM) we can do it either by consuming the exposed REST API endpoints or just utilize the .NET wrappers provided from Microsoft instead. Hence, in a new console application add the following nuget packages :

  1. Microsoft.Azure.Management.ResourceManager
  2. Microsoft.Azure.Management.Network
  3. Microsoft.IdentityModel.Clients.ActiveDirectory

These are enough to get us started with getting authentication tokens and working with ARM and its network resources counterpart.

Then we have to start configuring our application. Open the app.config and add the following entries under appsettings block :

C++
key="login" value="https://login.windows.net/{0}">
key="aadInstance" value="https://login.microsoftonline.com/{0}">
key="tenantId" value="YOUR AZURE AD TENANT ID">
key="apiEndpoint" value="https://management.azure.com/">
key="clientId" value="THE APP-ID YOUR REGISTERED IN AZURE AD">
key="clientSecretKeyPreviewPortal" value="THE APP SECRET KEY">
key="redirectUri" value="http://iprefresh">
key="subscriptionId" value="YOUR SUBSCRIPTION ID">
key="resourceGroupName" value="rgHybridDevelopmentLab">
key="localNetworkGatewayName" value="localgw-hyde-01">

Our next concern will be to provide an authentication method that eventually could provide us with an access token that we will use to our future requests against the ARM API. We can do it with the following method :

private static async Task<AuthenticationResult> GetAccessTokenAsync()
{
    var authContextUrl = String.Format(login, tenantId);

    var cc = new ClientCredential(clientId, clientSecretKeyPreviewPortal);
    var context = new AuthenticationContext(authContextUrl);
    var token = await context.AcquireTokenAsync(apiEndPoint, cc);

    if (token == null)
    {
        throw new InvalidOperationException("Could not get the Access Token, please check the clientId and clientSecretKey values.");
    }

    return token;
}

and in every subsequent request we can extract the access token from the AuthenticationResult object like this :

var token = await GetAccessTokenAsync();
var credentials = new TokenCredentials(token.AccessToken);

Next thing we need to do is to construct a method from which we could identify our current public IP. Careful here, we are talking for our public IP assigned from our ISP and not a local area network IP, perhaps automatically assigned to our host from the DHCP server of our router. That is pretty easy to be done by calling special endpoints of various dynamic DNS services in the web which will be glad to help us for free :

private static string GetCurrentIPAddress()
{
    string ipAddress = String.Empty;

    try
    {
        string url = "http://checkip.dyndns.org";
        System.Net.WebRequest req = System.Net.WebRequest.Create(url);
        System.Net.WebResponse resp = req.GetResponse();
        System.IO.StreamReader sr = new System.IO.StreamReader(resp.GetResponseStream());
        string response = sr.ReadToEnd().Trim();
        string[] a = response.Split(':');
        string a2 = a[1].Substring(1);
        string[] a3 = a2.Split('<');

        ipAddress = a3[0];
    }
    catch (Exception checkIpException)
    {
        ipAddress = checkIpException.Message;
    }

    return ipAddress;
}

Not exactly there yet. One last but most important step left. We got our IP address and now we have to update our Local Network Gateway with the new value :

private static async Task SetCurrentIPAddress(IPAddress ipaddr)
{
      if (ipaddr.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
      {
            try
            {
                Console.WriteLine("Getting Local Network Gateway...");

                var token = await GetAccessTokenAsync();
                var credentials = new TokenCredentials(token.AccessToken);

                NetworkManagementClient vnetMgmtClient = new NetworkManagementClient(credentials);
                vnetMgmtClient.SubscriptionId = subscriptionId;
                var localGwOperations = vnetMgmtClient.LocalNetworkGateways;

                var localGws = LocalNetworkGatewaysOperationsExtensions.List(localGwOperations, resourceGroupName);

                if (localGws != null)
                {
                    var localGw = localGws.Where(lgw => lgw.Name == localNetworkGatewayName).SingleOrDefault();
                    string ipAddress = ipaddr.ToString();

                    if (localGw.GatewayIpAddress != ipAddress)
                    {
                        localGw.GatewayIpAddress = ipAddress;
                        var updatedLocalGw = LocalNetworkGatewaysOperationsExtensions.CreateOrUpdate(localGwOperations, resourceGroupName, localGw.Name, localGw);

                        Console.WriteLine("Updated Local Network Gateway. New IP Address : " + ipAddress);
                    }

                    Console.WriteLine("No changes applied to Local Network Gateway IP, all in sync.");
                }
            }
            catch (Exception setIpAddressException)
            {
                Console.WriteLine(setIpAddressException.Message);
                Console.WriteLine("Failed to update Local Network Gateway...");
            }

        }
}

What this snippet does in a nutshell, is querying a predefined Resource Group and gets a list of all the available Local Network Gateways that might exist in it. Then finds that one with the matching name we provided in our appsettings. In case the new IP address is different than the existing one, then updates the resource with the new value.

Last but not least, updating will fail if our application has not sufficient priviledges to do so. So get back in the Azure Preview Portal and assign our application as Owner in the Subscripton level (this is only for demonstration purposes, in real life you should always follow the principle of minimum priviledges and define it in the appropriate level of your resources hierarchy :

 

Using the code

If you have reached that far reading, I am assuming you have a RRAS server in place and you managed to get S2S-connected with a virtual network in Azure. Create a schedule task, preferrably on the RRAS server, that will  periodically - I chose 5 minutes interval - obtain the new IP and update the Local Network Gateway, keeping that way always your VPN up and running.

Then visit your Connection resource in Azure Preview Portal and notice that your connection status is always Connected and the Local Network Gateway IP Address is always automatically refresh its value.

The code presented is just for demonstration purposes and not intended for production. There is a lot of space for improvement, especially in the context of performance.

History

-

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)