VPN 环境中的 Teams 事件的特殊注意事项

注意

本文属于解决远程用户 Microsoft 365 优化问题的系列文章。

Microsoft Teams 实时事件与会者流量 (这包括 Teams 制作的实时活动的与会者,以及通过 Teams 或 Viva Engage) 和 Microsoft Teams 市政厅与会者流量当前在服务的 URL/IP 列表中分类为“默认”与“优化”的参与者流量。 这些终结点被归类为 默认 终结点,因为它们托管在 CDN 上,其他服务可能也使用。 客户通常倾向于代理此类流量,并应用通常在此类终结点上执行的任何安全元素。

许多客户要求提供直接从本地 Internet 连接来连接 Teams 活动的与会者所需的 URL/IP 数据,而不是通过 VPN 基础结构路由大量且对延迟敏感的流量。 通常,如果没有专用命名空间和终结点的准确 IP 信息(分类为 默认的 Microsoft 365 终结点)则无法执行此操作。

使用以下步骤识别和启用来自使用强制隧道 VPN 的客户端的 Teams 事件的与会者流量的直接连接。 此解决方案旨在为客户提供一个选项,避免通过 VPN 路由与会者流量,同时由于在家办公方案而导致网络流量较高。 如果可能,我们建议通过检查代理访问服务。

注意

使用此解决方案时,可能会有一些服务元素无法解析为提供的 IP 地址,因此会遍历 VPN,但大量高容量流量(如流式处理数据)应该如此。 在实时事件/Stream范围之外,可能会有其他元素被此卸载捕获,但这些元素应受到限制,因为它们必须同时满足 FQDN IP 匹配,然后才能直接执行。

重要

建议权衡发送更多绕过 VPN 的流量的风险,以增加 Teams 事件的性能。

若要为 Teams 事件实现强制隧道异常,应应用以下步骤:

1.配置外部 DNS 解析

客户端需要可用的外部递归 DNS 解析,以便将以下主机名解析为 IP 地址。

对于 商业 云:

  • *.media.azure.net
  • *.bmc.cdn.office.net
  • *.ml.cdn.office.net

*.media.azure.net*.bmc.cdn.office.net 用于 Teams 生成的实时事件 (快速入门事件,以及从 Teams 客户端) 计划的 RTMP-In 支持事件。

*.media.azure.net*.bmc.cdn.office.net*.ml.cdn.office.net 用于 Teams 市政厅活动。

注意

其中一些终结点与 Teams 事件之外的其他元素共享。 建议不要仅使用这些命名空间来配置 VPN 卸载,即使 VPN 解决方案在技术上可行, (例如,如果它适用于命名空间而不是 IP) 。

对于 政府 (GCC、GCC High、DoD)

  • *.cdn.ml.gcc.teams.microsoft.com
  • *.cdn.ml.gov.teams.microsoft.us
  • *.cdn.ml.dod.teams.microsoft.us

*.cdn.ml.gcc.teams.microsoft.com 用于 Microsoft 365 美国政府社区云 (GCC) 中的 Teams 市政厅活动。

*.cdn.ml.gov.teams.microsoft.us 用于Microsoft 365 美国政府 GCC High Cloud (GCC High) 中的 Teams 市政厅活动。

*.cdn.ml.dod.teams.microsoft.us 用于Microsoft 365 美国政府 DoD 云 (DoD) 中的 Teams 市政厅活动。

VPN 配置中不需要 FQDN,它们仅用于在 PAC 文件中与 IP 结合使用,以直接发送相关流量。

2. 根据需要 (实现 PAC 文件更改)

对于在 VPN 上利用 PAC 文件通过代理路由流量的组织,通常使用 FQDN 来实现这一点。 但是,对于 Teams 事件,提供的主机名包含通配符,这些通配符解析为内容分发网络 (CDN) 不专门用于 Teams 事件流量的 IP 地址。 因此,如果仅根据 DNS 通配符匹配直接发送请求,则发往这些终结点的流量将被阻止,因为本文后面的 步骤 3 中没有通过直接路径进行路由。

为了解决此问题,我们可以提供以下 IP,并将其与示例 PAC 文件中的主机名结合使用,如 步骤 1 中所述。 PAC 文件检查 URL 是否与用于 Teams 事件的 URL 匹配,如果匹配,它还检查从 DNS 查找返回的 IP 是否与为服务提供的 IP 匹配。 如果 两者都 匹配,则流量将直接路由。 如果 FQDN/IP) (任一元素不匹配,则将流量发送到代理。 因此,配置可确保解析为 IP 和已定义命名空间范围之外的 IP 的任何内容都像平常一样通过 VPN 遍历代理。

收集 CDN 终结点的当前列表

对于商业云,Teams 事件使用多个 CDN 提供商流式传输到客户,以提供最佳覆盖范围、质量和复原能力。 目前,使用来自 Microsoft 的 Azure CDN 和来自 Verizon 的 Azure CDN。 随着时间的推移,这种情况可能会因区域可用性等情况而更改。 本文旨在使你能够及时了解 IP 范围的最新动态。 对于 Microsoft 365 美国政府云 (GCC、GCC High 和 DoD) 仅使用来自 Microsoft 的 Azure CDN。

对于 商业 云:

  • 对于来自 Microsoft 的 Azure CDN,可以从 “从官方Microsoft下载中心下载 Azure IP 范围和服务标记 – 公有云 ”中下载列表,需要专门查找 JSON 中的服务标记 AzureFrontdoor.Frontend ; addressPrefixes 将显示 IPv4/IPv6 子网。 随着时间的推移,IP 可能会更改,但服务标记列表在投入使用之前始终会更新。

  • 对于 Verizon (Edgecast) 的 Azure CDN,可以使用 Edge Nodes 找到详尽列表 - 列表 (选择“) 试用 ”,需要专门查找 “Premium_Verizon ”部分。 请注意,此 API 显示 (源和 Anycast) 的所有 Edgecast IP。 目前,API 没有用于区分源和 Anycast 的机制。

对于 政府 (GCC、GCC High 和 DoD)

以下脚本可以生成一个 PAC 文件,该文件将包含 Teams 事件与会者流量的命名空间和 IP 列表。 -Instance 参数确定指定的环境 - 支持的值为 [Worldwide、USGov、USGovGCCHigh 和 UsGovDoD]。 (可选)该脚本还可以使用 -Type 参数包括 Optimize 和 Allow 域。

商业云的 PAC 文件生成示例

下面是如何为商业云生成 PAC 文件的示例:

  1. 将脚本以 Get-EventsPacFile.ps1的形式保存到本地硬盘。

  2. 转到 Verizon URL 并下载生成的 JSON (复制粘贴到名为 cdnedgenodes.json)

  3. 将文件放入与脚本相同的文件夹中。

  4. 在 PowerShell 窗口中运行以下命令。 如果只希望优化名称 (而不优化和允许) 将 -Type 参数更改为 Optimize。

    .\Get-EventsPacFile.ps1 -Instance Worldwide -CdnEdgeNodesFilePath .\cdnedgenodes.json -Type OptimizeAndAllow -FilePath .\Commercial.pac
    
  5. Commercial.pac 文件将包含 Teams 事件与会者流量 (IPv4/IPv6) 的所有命名空间和 IP。

Microsoft 365 美国政府社区云 (GCC) 的示例 PAC 文件生成

下面是如何为 GCC 环境生成 PAC 文件的示例:

  1. 将脚本以 Get-EventsPacFile.ps1的形式保存到本地硬盘。

  2. 在 PowerShell 窗口中运行以下命令。 如果只希望优化名称 (而不优化和允许) 将 -Type 参数更改为 Optimize。

    .\Get-EventsPacFile.ps1 -Instance UsGov -Type OptimizeAndAllow -FilePath .\USGov.pac
    
  3. USGov.pac 文件将包含特定于 GCC 云的所有命名空间和 IP (IPv4/IPv6) Teams 市政厅与会者流量。

Get-EventsPacFile.ps1
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

<#PSScriptInfo

.VERSION 1.0.6

.AUTHOR Microsoft Corporation

.GUID 7f692977-e76c-4582-97d5-9989850a2529

.COMPANYNAME Microsoft

.COPYRIGHT
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the MIT License.

.TAGS PAC Microsoft Microsoft365 365

.LICENSEURI

.PROJECTURI http://aka.ms/ipurlws

.ICONURI

.EXTERNALMODULEDEPENDENCIES

.REQUIREDSCRIPTS

.EXTERNALSCRIPTDEPENDENCIES

.RELEASENOTES

#>

<#

.SYNOPSIS

Create a PAC file for Microsoft 365 prioritized connectivity for Teams Events (Live Events, Town hall)

.DESCRIPTION

This script will access updated information to create a PAC file to prioritize Microsoft 365 Urls for
better access to the service. This script will allow you to create different types of files depending
on how traffic needs to be prioritized.

.PARAMETER Instance

The service instance inside Microsoft 365. The default is Worldwide. To specify GCC use the USGov value.

.PARAMETER ClientRequestId

The client request id to connect to the web service to query up to date Urls.

.PARAMETER DirectProxySettings

The direct proxy settings for priority traffic.

.PARAMETER DefaultProxySettings

The default proxy settings for non priority traffic.

.PARAMETER Type

The type of prioritization to give. Valid values are Optimize and OptimizeAndAllow, which are 2 different modes of operation.
These values align to the categories defined in our Principles of Network Connectivity at https://aka.ms/pnc

.PARAMETER Lowercase

Flag this to include lowercase transformation into the PAC file for the host name matching.

.PARAMETER TenantName

The tenant name to replace wildcard Urls in the webservice.

.PARAMETER ServiceAreas

The service areas to filter endpoints by in the webservice.

.PARAMETER FilePath

The file to print the content to.

.EXAMPLE

Get-EventsPacFile.ps1 -Instance Worldwide -CdnEdgeNodesFilePath .\cdnedgenodes.json -Type OptimizeAndAllow -FilePath .\Commercial.pac 

.EXAMPLE

Get-EventsPacFile.ps1 -Instance USGov -FilePath .\USGov.pac -Type OptimizeAndAllow


#>

#Requires -Version 2

[CmdletBinding(SupportsShouldProcess = $True)]
Param (
    [Parameter()]
    [ValidateSet('Worldwide', 'Germany', 'China', 'USGovDoD', 'USGovGCCHigh', 'USGov')]
    [String] $Instance = "Worldwide",

    [Parameter()]
    [ValidateNotNullOrEmpty()]
    [guid] $ClientRequestId = [Guid]::NewGuid(),

    [Parameter()]
    [ValidateNotNullOrEmpty()]
    [String] $DirectProxySettings = 'DIRECT',

    [Parameter()]
    [ValidateNotNullOrEmpty()]
    [String] $DefaultProxySettings = 'PROXY 10.10.10.10:8080',

    [Parameter()]
    [ValidateSet('OptimizeOnly','OptimizeAndAllow')]
    [string]
    $Type = 'OptimizeOnly',

    [Parameter()]
    [switch] $Lowercase,

    [Parameter()]
    [ValidateNotNullOrEmpty()]
    [string] $TenantName,

    [Parameter()]
    [ValidateSet('Exchange', 'SharePoint', 'Common', 'Skype')]
    [string[]] $ServiceAreas,

    [Parameter()]
    [ValidateNotNullOrEmpty()]
    [string] $FilePath,

    [Parameter()]
    [ValidateNotNullOrEmpty()]
    [string] $CdnEdgeNodesFilePath
)

##################################################################################################################
### Global constants
##################################################################################################################

$baseServiceUrl = if ($Instance -eq 'USGov') {
    "https://endpoints.office.com/endpoints/Worldwide/?ClientRequestId=$ClientRequestId"
} else {
    "https://endpoints.office.com/endpoints/$Instance/?ClientRequestId=$ClientRequestId"
}
$directProxyVarName = "direct"
$defaultProxyVarName = "proxyServer"

##################################################################################################################
### Functions to create PAC files
##################################################################################################################

function Get-PacString {
    param(
        [Parameter(ValueFromPipelineByPropertyName)]
        [string[]]
        $NonDirectOverrideFqdns,

        [Parameter(ValueFromPipelineByPropertyName)]
        [string[]]
        $DirectFqdns
    )

    $PACSb = New-Object 'System.Text.StringBuilder'
    $null = & {
        $PACSb.AppendLine('// This PAC file will provide proxy config to Microsoft 365 services')
        $PACSb.AppendLine('// using data from the public web service for all endpoints')
        $PACSb.AppendLine('function FindProxyForURL(url, host)')
        $PACSb.AppendLine('{')
        $PACSb.Append('    var ').Append($directProxyVarName).Append(' = "').Append($DirectProxySettings).AppendLine('";')
        $PACSb.Append('    var ').Append($defaultProxyVarName).Append(' = "').Append($DefaultProxySettings).AppendLine('";')
        if ($Lowercase) {
            $PACSb.AppendLine('    host = host.toLowerCase();')
        }
        $first = $true
        foreach ($fqdn in $NonDirectOverrideFqdns) {
            if ($first) {
                $PACSb.AppendLine()
                $PACSb.AppendLine('    // Force proxy for subdomains of bypassed hosts')
                $PACSb.AppendLine()
                $PACSb.Append('    if(')
            }
            else {
                $PACSb.AppendLine().Append('            || ')
            }
            $first = $false
            $PACSb.Append('shExpMatch(host, "').Append($fqdn).Append('")')
        }
        if (!$first) {
            $PACSb.AppendLine(')')
            $PACSb.AppendLine('    {')
            $PACSb.Append('        return ').Append($directProxyVarName).AppendLine(';')
            $PACSb.AppendLine('    }')
        }

        $first = $true
        foreach ($fqdn in $DirectFqdns) {
            if ($first) {
                $PACSb.AppendLine()
                $PACSb.AppendLine('    // Bypassed hosts')
                $PACSb.AppendLine()
                $PACSb.Append('    if(')
            }
            else {
                $PACSb.AppendLine().Append('            || ')
            }
            $first = $false
            $PACSb.Append('shExpMatch(host, "').Append($fqdn).Append('")')
        }
        if (!$first) {
            $PACSb.AppendLine(')')
            $PACSb.AppendLine('    {')
            $PACSb.Append('        return ').Append($directProxyVarName).AppendLine(';')
            $PACSb.AppendLine('    }')
        }

        if (!$ServiceAreas -or $ServiceAreas.Contains('Skype')) {
            $EventsConfig = Get-TeamsEventsConfiguration
            if ($EventsConfig.EventsAddressRanges.Count -gt 0) {
                $EventsBlock = $EventsConfig | Get-TLEPacConfiguration
                $PACSb.AppendLine()
                $PACSb.AppendLine($EventsBlock)
            }
        }

        $PACSb.Append('    return ').Append($defaultProxyVarName).AppendLine(';').Append('}')
    }

    return $PACSb.ToString()
}

##################################################################################################################
### Functions to get and filter endpoints
##################################################################################################################
function Get-TeamsEventsConfiguration {
    param()
    $IncludedHosts = switch ($Instance) {
        'USGov' {
            @('*.cdn.ml.gcc.teams.microsoft.com')
            break
        }
        'USGovDoD' {
            @('*.cdn.ml.dod.teams.microsoft.us')
            break
        }
        'USGovGCCHigh' {
            @('*.cdn.ml.gov.teams.microsoft.us')
            break
        }
        default {
            @('*.bmc.cdn.office.net', '*.ml.cdn.office.net', '*.media.azure.net')
            break
        }
    }
    $IncludedAddressRanges = & {
        if (!$Instance.StartsWith('USGov') -and ![string]::IsNullOrEmpty($CdnEdgeNodesFilePath) -and (Test-Path -Path $CdnEdgeNodesFilePath)) {
            Get-Content -Path $CdnEdgeNodesFilePath -Raw -ErrorAction SilentlyContinue | ConvertFrom-Json | Select-Object -ExpandProperty value |
                Where-Object { $_.name -eq 'Premium_Verizon' } | Select-Object -First 1 -ExpandProperty properties |
                Select-Object -ExpandProperty ipAddressGroups |
                ForEach-Object {
                    $_.ipv4Addresses
                    $_.ipv6Addresses
                } |
                Where-Object { $_.BaseIpAddress } |
                ForEach-Object { $_.BaseIpAddress + '/' + $_.prefixLength }
        }

        $ServiceTagsDownloadId = '56519'
        if ($Instance.StartsWith('USGov')) {
            $ServiceTagsDownloadId = '57063'
        }
        $AzureIPsUrl = Invoke-WebRequest -Uri "https://www.microsoft.com/en-us/download/confirmation.aspx?id=$ServiceTagsDownloadId" -UseBasicParsing -ErrorAction SilentlyContinue |
            Select-Object -ExpandProperty Links | Select-Object -ExpandProperty href |
            Where-Object { $_.EndsWith('.json') -and $_ -match 'ServiceTags' } | Select-Object -First 1
        if ($AzureIPsUrl) {
            Invoke-RestMethod -Uri $AzureIPsUrl -ErrorAction SilentlyContinue | Select-Object -ExpandProperty values |
                Where-Object { $_.name -eq 'AzureFrontDoor.Frontend' } | Select-Object -First 1 -ExpandProperty properties |
                Select-Object -ExpandProperty addressPrefixes
        }
    }
    [PSCustomObject]@{
        EventsHostNames = $IncludedHosts
        EventsAddressRanges = $IncludedAddressRanges
    }
}

function Get-TLEPacConfiguration {
    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipelineByPropertyName)]
        [string[]]
        $EventsHostNames,

        [Parameter(ValueFromPipelineByPropertyName)]
        [string[]]
        $EventsAddressRanges
    )
    if ($EventsAddressRanges.Count -eq 0) {
        return ''
    }
    $TLESb = New-Object 'System.Text.StringBuilder'
    $Spaces = '    '
    $null = $TLESb.Append($Spaces).AppendLine('// Bypass Teams Events attendee traffic')
    $first = $true
    $null = foreach ($hostName in $EventsHostNames) {
        if ($first) {
            $TLESb.AppendLine().Append($Spaces).Append('if(')
        }
        else {
            $TLESb.AppendLine().Append($Spaces).Append('    || ')
        }
        $first = $false
        $TLESb.Append('shExpMatch(host, "').Append($hostName).Append('")')
    }
    $null = $TLESb.AppendLine(')').Append($Spaces).AppendLine('{')
    $Spaces = $Spaces + $Spaces
    $null = $TLESb.Append($Spaces).AppendLine('var resolved_ip = dnsResolveEx(host);')

    $first = $true
    $null = foreach ($addressRange in $EventsAddressRanges) {
        if ($first) {
            $TLESb.AppendLine().Append($Spaces).Append('if(')
        } else {
            $TLESb.AppendLine().Append($Spaces).Append('    || ')
        }
        $first = $false
        $TLESb.Append('isInNetEx(resolved_ip, "').Append($addressRange).Append('")')
    }
    if (!$first) {
        $null = $TLESb.AppendLine(')').
            Append($Spaces).AppendLine('{').
            Append($Spaces).Append('    return ').Append($directProxyVarName).AppendLine(';').
            Append($Spaces).AppendLine('}')
    }
    else {
        $null = $TLESb.Append($Spaces).AppendLine('// no addresses found for service via script')
    }
    return $TLESb.AppendLine('    }').ToString()
}

function Get-Endpoints {
    $url = $baseServiceUrl
    if ($TenantName) {
        $url += "&TenantName=$TenantName"
    }
    if ($ServiceAreas) {
        $url += "&ServiceAreas=" + ($ServiceAreas -Join ",")
    }
    return Invoke-RestMethod -Uri $url
}

function Get-MapVarUrls {
    Write-Verbose "Retrieving all endpoints for instance $Instance from web service."
    $Endpoints = Get-Endpoints

    $Include = if ($Type -eq 'OptimizeOnly') { @('Optimize') } else { @('Optimize', 'Allow') }

    $directUrls = $endpoints |
        Where-Object { $_.category -in $Include } |
        Where-Object { $_.urls } |
        ForEach-Object { $_.urls } |
        Sort-Object -Unique

    $MatchList = [Collections.Generic.Dictionary[string,Regex]]@{}
    $directUrls |
        Where-Object { $_.Contains('*') -or $_.Contains('?') } |
        ForEach-Object { $MatchList[$_] = [Regex]::new('^{0}$' -f $_.Replace('.','\.').Replace('*','.*').Replace('?','.?'),[Text.RegularExpressions.RegexOptions]::IgnoreCase) }

    $nonDirectPriorityUrls = $endpoints |
        Where-Object { $_.category -notin $Include } |
        Where-Object { $_.urls } |
        ForEach-Object { $_.urls } |
        Sort-Object -Unique |
        Where-Object { [Linq.Enumerable]::Any($MatchList,[Func[System.Collections.Generic.KeyValuePair[string,Regex],bool]]{$args[0].Key -ne $_ -and $args[0].Value.IsMatch($_)}) }

    return [PSCustomObject]@{
        NonDirectOverrideFqdns = $nonDirectPriorityUrls
        DirectFqdns = $directUrls
    }
}

##################################################################################################################
### Main script
##################################################################################################################

$content = Get-MapVarUrls | Get-PacString

if ($FilePath) {
    $content | Out-File -FilePath $FilePath -Encoding ascii
}
else {
    $content
}

该脚本将根据 AzureFrontDoor.Frontend实例参数值和密钥自动分析相应的 Azure CDN 列表,因此无需手动获取该列表。

同样,我们不建议仅使用 FQDN 执行 VPN 卸载; 利用函数 中的 FQDN 和 IP 地址有助于将此卸载的使用范围限定为一组有限的终结点,包括 Teams 事件。 函数的结构方式将导致对 FQDN 执行 DNS 查找,该查找与客户端直接列出的 FQDN 匹配,即剩余命名空间的 DNS 解析保持不变。

3.在 VPN 上配置路由以启用直接出口

最后一步是为将 当前 CDN 终结点列表收集 到 VPN 配置中所述的 Teams 事件 IP 添加直接路由,以确保流量不会通过强制隧道发送到 VPN。 有关如何为 Microsoft 365 优化终结点执行此操作的详细信息,请参阅 Microsoft 365 实现 VPN 拆分隧道的实现 VPN 拆分隧道部分。 对于本文档中列出的 Teams 事件 IP,此过程完全相同。

注意

仅 IP (不) 收集 CDN 终结点的当前列表 的 FQDN 应用于 VPN 配置。

常见问题

这是否会将我的所有流量直接发送到服务?

否,这将为 Teams 事件与会者直接发送对延迟敏感的流式处理流量,如果任何其他流量未解析为已发布的 IP,它们将继续使用 VPN 隧道。

是否需要使用 IPv6 地址?

否,仅在需要时连接才能为 IPv4。

为什么这些 IP 未在 Microsoft 365 URL/IP 服务中发布?

Microsoft对服务中信息的格式和类型进行严格控制,以确保客户能够可靠地使用该信息来实现基于终结点类别的安全和最佳路由。

“默认终结点”类别没有提供 IP 信息的原因有很多, (默认终结点可能不受Microsoft控制、更改过于频繁,或者可能与其他元素共享) 块中。 因此,默认终结点设计为通过 FQDN 发送到检查代理,就像普通 Web 流量一样。

在这种情况下,上述终结点是可由非Microsoft受控制元素(Teams 事件)使用的 CDN,因此直接发送流量也意味着解析为这些 IP 的任何其他内容也将直接从客户端发送。 由于当前全球危机的独特性,以及为了满足客户的短期需求,Microsoft提供了上述信息供客户根据需要使用。

Microsoft正在努力重新配置 Teams 事件终结点,以允许它们在未来包含在“允许/优化”终结点类别中。

是否只需允许访问这些 IP?

否,访问相应环境的所有 必需 标记终结点对于服务运行至关重要。

此建议将涵盖哪些方案?

  1. Teams 应用中生成的实时事件
  2. 外部设备 (编码器) 生成的事件
  3. Teams 大会堂

此建议是否涵盖演示者流量?

它不是;上述建议纯粹是给那些参加活动的人。 从 Teams 内部演示将看到演示者的流量流向 URL/IP 服务第 11 行中列出的优化标记的 UDP 终结点,以及针对 Microsoft 365实现 VPN 拆分隧道中概述的详细 VPN 卸载建议。

概述:Microsoft 365 VPN 拆分隧道

实现 Microsoft 365 VPN 拆分隧道

Microsoft 365 的常见 VPN 拆分隧道方案

保护用于 VPN 拆分隧道的 Teams 媒体流量

面向中国用户的Microsoft 365 性能优化

Microsoft 365 网络连接原则

评估 Microsoft 365 网络连接

Microsoft 365 网络和性能优化

安全专业人员和 IT 人员在当前独特的远程工作场景中实现新式安全控制的替代方法(Microsoft 安全团队博客)

增强 Microsoft 的 VPN 性能:使用 Windows 10 VPN 配置文件以允许自动打开连接

运行 VPN:Microsoft 如何让远程工作人员互联

Microsoft 全局网络