65.9K
CodeProject 正在变化。 阅读更多。
Home

Azure 本地网络网关动态 IP 更新

starIconstarIconstarIconstarIconstarIcon

5.00/5 (2投票s)

2017 年 4 月 9 日

CPOL

5分钟阅读

viewsIcon

17462

无需静态 IP 和昂贵的 VPN 设备即可实现无缝 S2S VPN。

引言

站点到站点连接,通常称为 S2S,可用于跨 premises 和混合配置。S2S VPN 网关连接是一种 IPsec/IKE VPN 隧道连接,我们可以在 Azure 资源与家庭/企业网络之间建立。然而,这种类型的连接需要一些在家用实验室中并不总是可用的东西。

  1. 一个 Azure 订阅 - 去拿一个,这是容易的部分,还有一些不错的注册优惠。
  2. 本地网络中属于兼容 VPN 设备列表的 VPN 设备
  3. 最后,此 VPN 设备已分配了一个公共静态 IP,并且不在 NAT 之后。

嗯,VPN 设备和静态 IP 的要求提高了门槛。没有人有家用的 VPN 设备,甚至企业级别的 VPN 设备用于实验或开发目的,从各个角度来看都是一种过度。幸运的是,有一个便宜又简单的替代方案:设置 RRAS(路由和远程访问服务)服务器。本文的目的不是介绍如何建立 S2S 连接或 RRAS 服务器。对于前者和后者,有很棒的文章(在这里,谷歌是你的朋友 ;))。

实际上,本文的重点是“静态 IP 要求”。在家拥有静态 IP 的人非常少见。我们大多数人都有一个宽带连接,其动态 IP 地址会经常更改。S2S VPN 连接的核心元素之一是所谓的 **本地网络网关**,它是位于云虚拟网络中并指向本地位置的组件。我们需要为本地网络网关指定两个参数:

  1. 网关 **IP 地址**,它应该是你的 VPN 设备的 IP - 不能位于 NAT 之后(我永远不会厌倦强调的重要细节)。
  2. 地址前缀,即 Azure 将流量路由到的本地地址空间。

 

因此,我们真正需要的是一种方法,可以自动将我们的本地网络网关的 IP 地址与 ISP 分配给我们的宽带路由器的 IP 地址同步。

背景

当我们通过原生 .NET 代码管理虚拟机、虚拟网络、Web 或移动应用或存储帐户时,**.NET 管理库** 将派上用场。Azure 有两种不同的部署、创建和管理资源的模型。所谓的 Classic 和 Resource Manager。在本文中,我们将完全专注于 Resource Manager 模块,因为 Classic 正在缓慢弃用,以及管理库的 **Microsoft.Azure.Management.Network** 命名空间。

我们将开发一个简单的控制台应用程序,它将作为 Windows 计划任务在后台定期运行,该应用程序将识别我们当前的 IP 地址,然后静默更新本地网络网关的 IP 地址,从而在跨 premises 之间保持几乎无缝的 VPN 连接。

完成工作

首先,我们必须在 Azure AD 中注册一个新应用程序。它必须是 **本机应用**,因为我们在这里讨论的是一个控制台应用程序。重定向 URI 并不真正重要,但至少要填写一个有效且有意义的值。

 

授予 **Windows Azure 服务管理 API** 权限给你的应用。

 

并创建一个密钥,我们稍后在请求应用程序的身份验证令牌时会用到它。

 

 

为了能够使用 Resource Manager 模型 (ARM),我们可以通过消耗公开的 REST API 端点来实现,或者直接利用 Microsoft 提供的 .NET 包装器。因此,在一个新的控制台应用程序中添加以下 nuget 包:

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

这些足以让我们开始获取身份验证令牌并使用 ARM 及其网络资源对应项。

然后,我们必须开始配置我们的应用程序。打开 app.config,并在 appsettings 块下添加以下条目:

    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">

我们的下一个问题将是提供一种身份验证方法,该方法最终可以为我们提供我们将用于未来对 ARM API 的请求的访问令牌。我们可以通过以下方法做到这一点:

        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;
        }

并在每一次后续请求中,我们可以像这样从 AuthenticationResult 对象中提取访问令牌:

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

接下来,我们需要构建一个方法来识别我们当前的公共 IP。注意,我们这里讨论的是 ISP 分配给我们的公共 IP,而不是本地网络 IP,也许是路由器 DHCP 服务器自动分配给我们的主机。通过调用 Web 上各种动态 DNS 服务的特殊端点,这很容易做到,它们很乐意免费帮助我们。

        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;
        }

还没有完全达到目标。最后但最重要的一步。我们获得了 IP 地址,现在我们必须使用新值更新我们的本地网络网关。

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...");
            }

        }
}

这段代码的简要作用是查询预定义的资源组,并获取其中所有可用的本地网络网关的列表。然后找到与我们在 appsettings 中提供的名称匹配的那个。如果新 IP 地址与现有 IP 地址不同,则使用新值更新资源。

最后但同样重要的是,如果我们的应用程序没有足够的权限,更新将会失败。所以回到 Azure Preview Portal,将我们的应用程序分配为订阅级别的所有者(这仅用于演示目的,在实际生活中,您应该始终遵循最小特权原则,并在资源层次结构的适当级别定义它)。

 

使用代码

如果您读到这里,我假设您已经安装了 RRAS 服务器,并且设法使用 Azure 中的虚拟网络建立了 S2S 连接。创建一个计划任务,最好是在 RRAS 服务器上,它将定期 - 我选择了 5 分钟的间隔 - 获取新 IP 并更新本地网络网关,从而始终保持您的 VPN 正常运行。

然后访问 Azure Preview Portal 中的连接资源,并注意到您的连接状态始终是“已连接”,并且本地网络网关 IP 地址始终会自动刷新其值。

所提供的代码仅用于演示目的,不适用于生产环境。尤其是在性能方面,还有很大的改进空间。

历史

-

© . All rights reserved.