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

VPNScripter:Windows VPN 连接的脚本工具

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.84/5 (15投票s)

2016年12月2日

MIT

8分钟阅读

viewsIcon

63745

downloadIcon

1590

根据 XML 配置文件在 Windows 中快速创建 VPN 连接

引言

这是一个通用的 PowerShell 脚本,它会根据 XML 配置文件自动创建一组 VPN 连接。如果您想快速在 Windows 中配置 VPN,而无需使用 VPN 提供商开发的任何自定义 VPN 软件,这将非常有用。

背景

VPN 历来是企业从互联网连接到公司私有网络的一种方式。如今,它们已转向商业化,主要用于隐私问题,将所有客户的互联网流量路由到分布在全球的中继服务器。VPN 连接有几种不同的协议,Windows 原生支持其中大多数,特别是

  • PPTP:最早的 VPN 协议之一,基于 PPP 协议的封装(通过 GRE 协议)和加密(RC4)[1]。PPTP 由微软开发,由于其加密算法薄弱且不安全,目前已被弃用。客户端身份验证通常通过用户名和密码进行,但也可以使用客户端证书。从网络角度来看,PPTP 使用 TCP 端口 1723,并要求路由器转发 GRE IP 协议。
  • L2TP/IPSEC:L2TP 是一种隧道协议,与 PPTP 一样用于封装 PPP 协议。由于 L2TP 不安全(没有任何加密),为了提供安全性,它通常与另一种协议 IPSEC 一起使用[2]L2TP 客户端身份验证需要用户名和密码,此外客户端和服务器通常还共享一个 PSK 以允许客户端建立 IPSEC 隧道,但客户端也可以通过证书向 IPSEC 服务器进行身份验证。从网络角度来看,L2TP 使用 UDP 端口 1701,而 IPSEC 使用 UDP 端口 4500 和 500(如果不在 NAT 后面,路由器还必须转发 ESP IP 协议)。它是“最庞大”的协议。
  • SSTP:由微软开发,与 PPTP 和 L2TP 一样,它基于 PPP 协议的封装,但使用 SSL/TLS 通道来保护所有流量(与使用 https 在安全互联网网站上浏览时使用的协议相同)。与 PPTP 一样,客户端身份验证通常通过用户名和密码进行,但也可以使用客户端证书。由于它只使用标准的 HTTPS 端口(TCP 443),因此它是绕过几乎所有防火墙限制最适合的协议,并且也可以与代理完美配合使用。
  • IKEv2:它是 IPSEC 的一个细微变种,其中客户端身份验证不仅通过 PSK 或证书进行,还可以使用标准的用户名-密码凭据。此外,IKEv2 对不断变化的网络连接具有更强的弹性,是移动用户在接入点之间切换时的不错选择。从网络角度来看,IKEv2 使用 UDP 端口 500 和 4500(如果不在 NAT 后面,路由器还必须转发 ESP IP 协议)。

Windows 还有一种**自动**协议,它本质上是通过按顺序尝试所有协议 IKEv2、SSTP、L2TP/IPSEC、PPTP 来建立 VPN 连接。有关更多信息,请参阅[3]

Using the Code

所有脚本工具的配置都在名为 vpnconfig.xml 的 XML 文件中(见下文)。该文件定义了一组 VPN 提供商(Provider 元素节点),并为每个提供商指定了一组服务器(Server 子元素节点),为每个服务器将在 Windows 中创建一个 VPN 连接。我们来检查一下这些节点:

  • Provider:此节点对应于一个 VPN 提供商。属性:
    • name:用于生成 Windows 中的 VPN 连接名称。
    • basedomain:用于生成 VPN 服务器的主机名(通常 VPN 提供商提供一组共享公共域名的服务器)。可以在服务器节点中覆盖。
    • l2tppsk:这是 L2TP/IPSEC 连接的预共享密钥(通常在所有提供商的服务器上都相同)。
    • user:这是访问 VPN 的用户名(通常在所有提供商的服务器上都相同)。
    • password:这是与用户名关联的密码(通常在所有提供商的服务器上都相同)。
    • proto:指定所有服务器使用的 VPN 协议。如果留空,则表示自动。
  • Server:此节点对应于 VPN 提供商的一个服务器。属性:
    • server:指定服务器的主机名。如果此属性不包含 "." 字符,则通过将此字段与提供商的基本域连接来获得主机名,否则主机名等于此字段。
    • proto:指定使用的 VPN 协议。它还会覆盖提供商节点中定义的任何协议。
    • user:这是访问 VPN 的用户名。它还会覆盖提供商节点中定义的任何用户。
    • password:这是与 username 关联的密码。它还会覆盖提供商节点中定义的任何密码。
<!-- VpnScripter configuration file © 2016 Federico Di Marco

Provider defines the vpn providers you have. 
Each vpn provider has usually a set of servers to which you
can connect and the script creates a vpn connection 
for every server you specify for every provider listed.
The created connection will have:
- Name: Server attribute (till first .) + Provider name 
if proto is auto or empty, Server + Proto + Provider name if not null.        
- Protocol: the one specified in the server element or if null 
the one specified in the provider element or if null auto
- L2tp PSK, User, Password: the one specified in the server 
element or if null the one specified in the provider element 
(you don't have to repeat them for all servers)
- Server hostname: Server attribute + Provider base domain 
if server attributes does not contain any "." char
                  otherwise server attribute
-->

<Providers>
  <Provider name="TestVPN1" 
  basedomain="myvpn.com" user="user01" 
  password="hottie">   
    <Server server="sw" />   
    <!--Vpn will have sw.myvpn.com as host address and 
    SW TestVPN1 as name, automatic protocol-->
    <Server server="ro" proto="PPTP"/>   
    <!--Server proto overrides Provider specified protocol (auto in this case)-->
    <Server server="kick-vm.myvpnext.com" 
    proto="SSTP"/>  
    <!--Vpn will have kick-vm.myvpnext.com as host address 
    and KICK-VM TestVPN1 as name -->
    <Server server="sp" proto="IKEV2" 
    user="user15" password="beer" /> 
</Provider>
<Provider name="TestVPN2" basedomain="myvpn2.com" 
l2tppsk="12345" user="test001" password="master">
    <Server server="karate" proto="L2TP" 
    l2tppsk="314pi"/> 
    <Server server="kazu"  /> 
    <Server server="moon"  /> 
    <Server server="sun" /> 
</Provider>
</Providers>

在 zip 压缩包中,您将找到 3 个文件:

  • DotRas.dll:这是一个 .NET 类库,供 PowerShell 脚本用于创建 VPN 连接,它必须与脚本放在同一个文件夹中。
  • vpnconfig.xml:这是上述 XML 配置文件,需要用您的设置进行编辑。
  • VpnScripter.ps1:这是一个 PowerShell 脚本,可以从 PowerShell 启动或双击。它接受一个可选参数 ConfigFile,用于指定要使用的 XML 配置文件名(默认为 vpnconfig.xml)。您可以指定 -Debug 标志以获得更详细的日志输出。

该脚本已在 Windows 10 上进行测试,但应能在任何至少拥有 PowerShell 3.0 的 Windows 版本上运行。

实现概述

基本上,所有代码都包含在一个名为 VpnScripter.ps1 的 PowerShell 文件中,该文件混合了 XSD、C# 和 PowerShell 代码。基本上,它:

  • 使用 PowerShell 脚本中定义的嵌入式 XSD 架构解析和验证 XML 配置文件 vpnconfig.xml
    该架构本身非常简单(例如,主要包含 xs:attribute 子句,指定配置文件的允许/必需属性),并定义了一个名为 NotEmptyTrimmedString 的简单类型,顾名思义,它通过正则表达式检查属性值是否为“非空且已修剪的字符串”(它用于必须“有意义”的服务器属性)。XSD 架构的验证通过 PowerShell 函数 ValidateLoadXml 执行,该函数使用标准的 .NET 函数执行验证(例如,C# new XmlDocument().Load(XmlReader.Create(<file>,<settings with schema file>)))。
    function ValidateLoadXml([string] $XmlFile, [string] $Schema) {
    
    	$verr={ 
    		Write-Error "Error: malformed XSD/XML Line: $($_.Exception.LineNumber) 
    		Offset: $($_.Exception.LinePosition) - $($_.Message)" 
    		throw [System.IO.InvalidDataException] 
    	}
    
    	try {
    		[System.Xml.XmlReaderSettings]$readsett=New-Object System.Xml.XmlReaderSettings
    		$readsett.Schemas.Add([System.Xml.Schema.XmlSchema]::Read
    		((New-Object System.IO.StringReader($Schema)),$verr))
    		$readsett.ValidationType=[System.Xml.ValidationType]::Schema
    		$readsett.add_ValidationEventHandler($verr)
    		$xmlconf = New-Object System.Xml.XmlDocument
    		$xmlconf.Load([System.Xml.XmlReader]::Create($XmlFile,$readsett))
    	}
    	catch [System.IO.InvalidDataException]  {
    		return $null
    	}
    	
    	return $xmlconf
    }	
  • 遍历 XML 配置文件中所有 Provider 节点的所有 Server 节点。
    • 根据上述规则构建在 Windows 中创建 VPN 条目所需的参数列表(如果 server 属性包含一个点 '.',则它是 VPN 端点的完整 DNS 名称;否则,它必须与 basedomain 属性连接等)。为了评估所需的用户名、密码、协议和 l2tppsk,脚本使用了一个联合运算符(在 SQL 查询和 C# 中很常见,运算符 ??),但不幸的是 PowerShell 中没有,因此实现了一个自定义的 PowerShell 函数,称为 Coalesce
      function Coalesce([string[]] $StringsToLookThrough, [switch]$EmptyStringAsNull) {
        if ($EmptyStringAsNull.IsPresent) {
          return ($StringsToLookThrough | Where-Object { $_ } | Select-Object -first 1)
        }
        else {
          return (($StringsToLookThrough -ne $null) | Select-Object -first 1)
        }  
      }	
    • 对参数执行其他检查,这些检查不如 XSD 那么直接。
      • 如果 proto 属性是 L2TP,则 l2tppsk 属性不能为空。
      • userpassword 属性不能为空。
    • 通过 Get-VpnConnection cmdlet 检查 VPN 条目是否已在 Windows 中存在,如果存在,则通过 Remove-VpnConnection cmdlet 删除它。
      			$exist=Get-VpnConnection -Name $vpnname -ErrorAction silentlycontinue
      			if ($exist -ne $null) {
      				Write-Host "Info: Removing VPN connection $vpnname"
      		
      				Remove-VpnConnection -Name $vpnname -Force	
      			}
    • 通过调用自定义的 C# 函数 Add 创建新的 VPN 条目。
      基本上,此函数设置 VPN 参数(协议通过一个名为 ConvertProto 的简单转换函数转换为 enum,对于 IKEv2 VPN,必须使用 EAP 身份验证等),并直接调用 DotRas.dll 函数将新的 VPN 条目添加到 Windows。
      		public static void Add(string path,string name,string server, 
      		string proto, string l2tppsk, string user, string password) {
      			RasPhoneBook PhoneBook=new RasPhoneBook();
      			PhoneBook.Open(path);
      			RasEntry VpnEntry = RasEntry.CreateVpnEntry
      			(name,server, ConvertProto(proto), 
      			RasDevice.Create(name, DotRas.RasDeviceType.Vpn), true);
      			VpnEntry.Options.UsePreSharedKey = true;
      			VpnEntry.Options.CacheCredentials = true;
      			VpnEntry.Options.ReconnectIfDropped = true;
      			if (VpnEntry.VpnStrategy==RasVpnStrategy.IkeV2Only) {
      				// 23 EAP-AKA
      				// 50 EAP-AKA'
      				// 18 EAP-SIM
      				// 21 EAP-TTLS
      				// 25 PEAP
      				// 26 EAP-MSCHAPV2
      				// 13 EAP-smart card or certificate
      				VpnEntry.Options.RequireEap = true;
      				VpnEntry.CustomAuthKey=26; // 26 means eap-mschapv2 username/password
      			}
      			else { 
      				VpnEntry.Options.RequireMSChap2 = true;				
      			}
      			//VpnEntry.Options.RequireWin95MSChap = false; // seems to be ignored, 
      			//chap is still checked in newly created vpn profile
      			//VpnEntry.Options.RequireMSChap = false;  // seems to be ignored, 
      			//chap is still checked in newly created vpn profile
      			//VpnEntry.Options.RequireChap = false; // seems to be ignored, 
      			//chap is still checked in newly created vpn profile
      			VpnEntry.EncryptionType = RasEncryptionType.RequireMax;
      			PhoneBook.Entries.Add(VpnEntry);
      			VpnEntry.UpdateCredentials(RasPreSharedKey.Client,l2tppsk);
      			VpnEntry.UpdateCredentials(new System.Net.NetworkCredential(user,password));
      		}		

如前所述,XSD 架构和 .NET 辅助代码都已嵌入 PowerShell 脚本中,形成一个几乎自包含(需要 DotRas.dll)的文件。事实上,PowerShell 脚本有一个很好的特性,允许在其中包含任何 .NET 代码,从而能够执行 C# 代码可以完成的所有操作。C# 代码的解析和编译方式很有趣:

  • 首先,“加载”自定义代码使用的任何 DLL(使用 Add-Type -Path <dll file>)或程序集(使用 Add-Type -AssemblyName <assembly>)。
  • 然后使用以下命令解析和编译您的自定义 C# 代码:
    Add-Type -ReferencedAssemblies <list of referenced assemblies> -TypeDefinition <string containing C# code> -Language CSharp
Add-Type -Path $psscriptroot\DotRas.dll
Add-Type -AssemblyName System.Xml
Add-Type -ReferencedAssemblies $psscriptroot\DotRas.dll,System.Xml 
         -TypeDefinition $Source -Language CSharp

关注点

使用外部库 DotRas.dll 是因为使用标准的 PowerShell 命令 Add-VpnConnection 似乎无法配置凭据(登录名和密码),如果您找到方法,请告知我。

存储库

您可以在 GitHub 上找到源代码存储库。如果您想为您的 VPN 提供商添加预定义的包含所有服务器的配置文件,或者改进代码,欢迎您。

历史

  • V1.0 (2016 年 12 月 2 日)
    • 首次发布
  • V1.1. (2016 年 12 月 7 日)
    • 在服务器节点中添加了对 l2tppskuserpassword 的覆盖。
    • 通过 XSD 添加了 XML 验证,并对 user/password/l2tppsk 参数进行了简单检查。
  • V1.2 (2017 年 3 月 27 日)
    • 添加了“实现概述”部分。

参考文献

  • [1] PPP 是一个历史悠久的点对点协议,用于通过调制解调器连接到 ISP。它允许在两个端点之间建立连接。
  • [2] IPSEC 是两个端点之间的加密隧道,基于一组规则(称为安全关联),这些规则建立隧道中使用的加密和完整性算法以及需要隧道化的网络数据包(例如,所有 TCP 数据包、寻址到特定端口的 TCP 数据包等)。基本上,它将 TCP/UDP 封装在一个新的加密 IP 数据包(ESP)中。用于加密和检查消息完整性的算法通过一种称为 IKE 的协商协议建立。由于客户端和服务器主要通过证书相互认证,IPSEC 通常用于局域网到局域网 VPN(例如,连接两个不同组织的网络),而不是客户端到局域网场景。但是,对于此类场景,可以通过 PSK(预共享密钥或共享密钥)进行身份验证,尽管这会降低安全性。
  • [3] Microsoft VPN 隧道协议
© . All rights reserved.