使用PowerShell通过不同网络(网卡)访问不同网站

前言

在学校生活时经常有一种需求:访问某些网站(如校内系统)时需要使用校园网,访问另一些网站(如Steam、Github等)校园网很慢,需要使用手机浏览器等其他网络加速。此时我就觉得如果能同时使用两个网络该多好。

本文就介绍如何用PowerShell1实现这个功能(需要Windows平台,版本XP及以上)。

原理

首先需要明确,同一张网卡是无法同时连接两个网络的(除非同时使用多个代理达到这种效果)。幸运的是,现在绝大多数电脑都同时拥有有线和无线两张网卡,所以可以实现这个效果。

其次,一般应用程序无法直接指定网卡上网(例如虚拟机软件除外),不过我们有代替的方法:设置路由表。

路由表的基本原理是:要求IP的某些网段,走指定的网关。而网关又和网络有密不可分的联系,从而实现隐式地指定网卡。

查看电脑信息

首先可以同时连上有线和无线网,方便查看

网络适配器

一台电脑上会有多个网络适配器,其中有物理网卡,也有虚拟网卡。我们要找出其中物理网卡里的有线和无线网卡:首先键入ipconfig,可以对此有一个大致印象:

十分明显地看到,有好多适配器。但比如说名字里带virtual的,或者vEthernet这种显然是虚拟网卡。剩下很快就能发现有线和无线网络适配器分别是:以太网和WLAN(不同电脑可能名字不一样,连接状态也不尽相同)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
PS C:\Users\poker> ipconfig

Windows IP 配置


以太网适配器 以太网:

连接特定的 DNS 后缀 . . . . . . . : hgd
本地链接 IPv6 地址. . . . . . . . : XXXX::XXXX:XXXX:XXXX:XXXX%XX
IPv4 地址 . . . . . . . . . . . . : XXX.XXX.XXX.XXX
子网掩码 . . . . . . . . . . . . : XXX.XXX.XXX.XXX
默认网关. . . . . . . . . . . . . : XXX.XXX.XXX.XXX

无线局域网适配器 本地连接* 5:

媒体状态 . . . . . . . . . . . . : 媒体已断开连接
连接特定的 DNS 后缀 . . . . . . . :

无线局域网适配器 本地连接* 6:

媒体状态 . . . . . . . . . . . . : 媒体已断开连接
连接特定的 DNS 后缀 . . . . . . . :

无线局域网适配器 WLAN:

连接特定的 DNS 后缀 . . . . . . . :
本地链接 IPv6 地址. . . . . . . . : XXXX::XXXX:XXXX:XXXX:XXXX%XX
IPv4 地址 . . . . . . . . . . . . : XXX.XXX.XXX.XXX
子网掩码 . . . . . . . . . . . . : XXX.XXX.XXX.XXX
默认网关. . . . . . . . . . . . . : XXX.XXX.XXX.XXX

以太网适配器 蓝牙网络连接 3:

媒体状态 . . . . . . . . . . . . : 媒体已断开连接
连接特定的 DNS 后缀 . . . . . . . :

接下来使用Get-NetIPConfiguration指令,就可以看到物理网卡的信息了:

其中我们要注意Gateway的项,这就是网关。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
PS C:\Users\poker> Get-NetIPConfiguration

InterfaceAlias : 以太网
InterfaceIndex : 18
InterfaceDescription : Realtek PCIe GbE Family Controller
NetProfile.Name : XXXXXXXXXXXXXXX
IPv4Address : XXX.XXX.XXX.XXX
IPv6DefaultGateway :
IPv4DefaultGateway : XXX.XXX.XXX.XXX
DNSServer : XXX.XXX.XXX.XXX
XXX.XXX.XXX.XXX
XXX.XXX.XXX.XXX
XXX.XXX.XXX.XXX
XXX.XXX.XXX.XXX
XXX.XXX.XXX.XXX

InterfaceAlias : WLAN
InterfaceIndex : 14
InterfaceDescription : Intel(R) Wi-Fi 6 AX200 160MHz
NetProfile.Name : XXXXXXXXXXXXXXX
IPv4Address : XXX.XXX.XXX.XXX
IPv6DefaultGateway :
IPv4DefaultGateway : XXX.XXX.XXX.XXX
DNSServer : XXX.XXX.XXX.XXX

InterfaceAlias : 蓝牙网络连接 3
InterfaceIndex : 10
InterfaceDescription : Bluetooth Device (Personal Area Network) #3
NetAdapter.Status : Disconnected

其他相关指令:netsh interface show interfaceGet-NetAdapter,感兴趣可以看看。

路由

使用Get-NetRoute指令(或route print)可以查看路由表:

表中每一条代表一个路由,注意他所有的属性:

  • ifIndex (interface index)(接口序号):即和上图同名的属性。

  • DestinationPrefix(目标前缀):即指定的网段,属于这个网段的会找对应的网关(其中前面四段句点分开的是IP,斜杠后的是子网掩码从左往右数1的位数)。

  • NextHop(下一跳):即指定的网关。

  • 两个Metric(跃点):大致表示这个路由的优先级,数字越小越优先。

  • PolicyStore(存储策略):表示这是临时路由(ActiveStore)还是永久路由(PersistentStore),其中临时路由会在重启后删除重设。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
PS C:\Users\poker> Get-NetRoute

ifIndex DestinationPrefix NextHop RouteMetric ifMetric PolicyStore
------- ----------------- ------- ----------- -------- -----------
10 255.255.255.255/32 0.0.0.0 256 65 ActiveStore
13 255.255.255.255/32 0.0.0.0 256 25 ActiveStore
11 255.255.255.255/32 0.0.0.0 256 25 ActiveStore
18 255.255.255.255/32 0.0.0.0 256 25 ActiveStore
14 255.255.255.255/32 0.0.0.0 256 50 ActiveStore
1 255.255.255.255/32 0.0.0.0 256 75 ActiveStore
10 224.0.0.0/4 0.0.0.0 256 65 ActiveStore
13 224.0.0.0/4 0.0.0.0 256 25 ActiveStore
11 224.0.0.0/4 0.0.0.0 256 25 ActiveStore
18 224.0.0.0/4 0.0.0.0 256 25 ActiveStore
14 224.0.0.0/4 0.0.0.0 256 50 ActiveStore
1 224.0.0.0/4 0.0.0.0 256 75 ActiveStore
14 192.168.1.255/32 0.0.0.0 256 50 ActiveStore
14 192.168.1.226/32 0.0.0.0 256 50 ActiveStore
14 192.168.1.0/24 0.0.0.0 256 50 ActiveStore
18 172.19.73.255/32 0.0.0.0 256 25 ActiveStore
18 172.19.73.29/32 0.0.0.0 256 25 ActiveStore
18 172.19.73.0/24 0.0.0.0 256 25 ActiveStore
1 127.255.255.255/32 0.0.0.0 256 75 ActiveStore
1 127.0.0.1/32 0.0.0.0 256 75 ActiveStore
1 127.0.0.0/8 0.0.0.0 256 75 ActiveStore
14 0.0.0.0/0 XXX.XXX.XXX.XXX 0 50 ActiveStore
18 0.0.0.0/0 XXX.XXX.XXX.XXX 0 25 ActiveStore
10 ff00::/8 :: 256 65 ActiveStore
13 ff00::/8 :: 256 25 ActiveStore
11 ff00::/8 :: 256 25 ActiveStore
18 ff00::/8 :: 256 25 ActiveStore
14 ff00::/8 :: 256 50 ActiveStore
1 ff00::/8 :: 256 75 ActiveStore
13 fe80::ddf5:310e:d09b:1326/128 :: 256 25 ActiveStore
10 fe80::8b75:5968:ee8a:42d6/128 :: 256 65 ActiveStore
18 fe80::5c8c:97bf:72c:71e9/128 :: 256 25 ActiveStore
11 fe80::4fbb:4f7c:2d3c:426a/128 :: 256 25 ActiveStore
14 fe80::3c65:d5a8:6d1:95cd/128 :: 256 50 ActiveStore
10 fe80::/64 :: 256 65 ActiveStore
13 fe80::/64 :: 256 25 ActiveStore
11 fe80::/64 :: 256 25 ActiveStore
18 fe80::/64 :: 256 25 ActiveStore
14 fe80::/64 :: 256 50 ActiveStore
1 ::1/128 :: 256 75 ActiveStore
18 ::/0 XXXX::XXXX:XXXX:XXXX:XXXX 256 25 ActiveStore

注意NextHop不为空的路由(一般Metric也最小),这就是有线和无线网默认的路由,我们新建路由时为避免冲突,要先删除这个(不用担心失误,重启后会恢复)

这些路由前面网段都是0.0.0.0/0(或::/0),表示全网段都可以使用这个路由。

Metric可以发现,绝大多数电脑同时连有线和无线网时,都是有线网的Metric比较小,这也是为什么路由总是会优先选择有线网连接。Windows默认开启了AutomaticMetric,会按照带宽网速自动设定Metric2,有需要可以去设置关闭

设备地址

输入指令Get-CimInstance Win32_NetworkAdapterConfiguration | Select-Object Description, SettingID,可以看到网卡以及对应的Guid

我们将来可以通过这个Guid,从注册表HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Network\{4D36E972-E325-11CE-BFC1-08002BE10318}\{对应设备Guid}\ConnectionPnPInstanceId项判断是不是物理网卡(值以PCI开头是物理网卡,BTH开头是蓝牙网卡,其他是虚拟网卡)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
PS C:\Users\poker> Get-CimInstance Win32_NetworkAdapterConfiguration | Select-Object Description, SettingID

Description SettingID
----------- ---------
Microsoft Kernel Debug Network Adapter {2A714150-362D-497E-AC8B-A9F963B02478}
Realtek PCIe GbE Family Controller {XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}
Intel(R) Wi-Fi 6 AX200 160MHz {XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}
Microsoft Wi-Fi Direct Virtual Adapter {33A4093B-99C7-478F-AF06-FD0A698B1FF5}
Microsoft Wi-Fi Direct Virtual Adapter {F1F71D7B-D35B-4C65-8A91-DC94EE7C0A2E}
WAN Miniport (SSTP) {FA29F161-184C-4773-898D-A104510EE7F5}
WAN Miniport (IKEv2) {9859D6F0-2451-47BB-8748-7B1C5C542200}
WAN Miniport (L2TP) {EF4DCC46-BE1D-4FC7-8CFB-CF00F37AB9C8}
WAN Miniport (PPTP) {5E553B27-3FA5-445B-8A15-8249DF31AF3F}
WAN Miniport (PPPOE) {91899BE4-723E-43A6-8006-6BB2AFA4FC32}
WAN Miniport (IP) {CD5DD751-23AA-44F2-940F-4B565365892D}
WAN Miniport (IPv6) {DC0056A3-472D-4178-901C-131B8D8261E9}
WAN Miniport (Network Monitor) {218400DD-182F-4BC4-A266-EBDB68EDD6DF}
Bluetooth Device (Personal Area Network) {2CA478BD-DADE-48B3-949B-66A882E18A72}

实现思路

接下来就是实现环节了,虽然指定网关听起来很容易。但如果切换网络之后,不仅网关可能会变,而且接口别名(InterfaceAlias)也会变,到时候再调整比较麻烦。

如果可以指定网卡,并指定网段就很方便了。实际上我们确实可以这样做,因为Get-NetIPConfiguration(或Get-NetAdapter)可以看出来:接口描述(InterfaceDescription)、接口别名(InterfaceAlias)和网关(Gateway)几乎是一一对应的关系,所以我们可以通过PowerShell(或C/C++)从接口描述查找网关,并设置路由,就可以达成目的。

至于网段分配,一般校园网都是连续的网段,比如说172.18.XXX.XXX都是校园网IP,那么就设置目标前缀(DestinationPrefix)为172.18.0.0/16,表示子网掩码是255.255.0.0,即前16位是固定的,后面可以变化。将这段路由优先级设最高,然后再设置剩下的路由(直接用0.0.0.0/0)全部走另一个网络(优先级第二高)。

另外还可以结合Proxifier等软件管理,如用Proxifier强大的代理筛选功能将校园网所有IP或程序都代理到某一台学校的服务器上,这样路由规则就可以只要指定一个IP了(如172.18.6.57/32)。

脚本代码

需要使用PowerShell7及以上运行。

在使用的时候,只需要按照需要修改# Main部分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# NetAdapterInfo
class NetAdapterInfo {
[guid] $SettingID
[string] $Description
[string] $DefaultIPGateway

NetAdapterInfo (
[guid] $settingID,
[string] $description,
[string] $defaultIPGateway
) {
$this.SettingID = $settingID
$this.Description = $description
$this.DefaultIPGateway = $defaultIPGateway
}
}

# Functions
function Test-PhysicalNic {
param (
[Parameter(
Mandatory,
ValueFromPipeline
)]
[NetAdapterInfo]
$Info
)

$str = Get-ItemProperty `
-Path "HKLM:\SYSTEM\CurrentControlSet\Control\Network\{4D36E972-E325-11CE-BFC1-08002BE10318}\{$($Info.SettingID)}\Connection" `
-Name PnPInstanceId
$str.PnPInstanceId.StartsWith("PCI")
}

function Get-PhysicalNics {
Get-CimInstance `
-Class Win32_NetworkAdapterConfiguration `
| ForEach-Object {
[NetAdapterInfo]::new(
$_.SettingID,
$_.Description,
$_.DefaultIPGateway
)
} `
| Where-Object { Test-PhysicalNic $_ }
}

function Remove-AdapterRoute {
param (
[string]
$DestinationPrefix = "0.0.0.0/0"
)
Remove-NetRoute $DestinationPrefix
}

function Set-AdapterRoute {
param (
[NetAdapterInfo[]]
$List,
[string]
$AdapterDescription,
[string]
$DestinationPrefix = "0.0.0.0/0",
[UInt16]
$RouteMetric = 0,
[bool]
$PersistentStore = $false
)
$Private:NextHop = ($List | Where-Object Description -EQ $AdapterDescription)[0].DefaultIPGateway
New-NetRoute `
$DestinationPrefix `
-InterfaceAlias $AdapterDescription `
-NextHop $Private:NextHop `
-PolicyStore ($PersistentStore ? "PersistentStore" : "ActiveStore")
}

function Set-AdapterRoute {
param (
[NetAdapterInfo[]]
$List,
[string]
$InterfaceDescription,
[string]
$DestinationPrefix = "0.0.0.0/0",
[string]
$IsIpv4 = $true,
[UInt16]
$RouteMetric = 0,
[bool]
$PersistentStore = $false
)
$InterfaceAlias = (Get-NetAdapter | Where-Object Name -EQ $InterfaceDescription)[0].Name
$gateways = ($List | Where-Object Description -EQ $InterfaceAlias)[0].DefaultIPGateway
if ($gateways -EQ "") {
return
}
foreach ($gateway in $gateways.Split(' ')) {
if (($IsIpv4 -and ([System.Net.IPAddress]::Parse($gateway).AddressFamily -EQ [System.Net.Sockets.AddressFamily]::InterNetwork)) -or `
(!$IsIpv4 -and ([System.Net.IPAddress]::Parse($gateway).AddressFamily -EQ [System.Net.Sockets.AddressFamily]::InterNetworkV6))) {
New-NetRoute `
$DestinationPrefix `
-InterfaceAlias $InterfaceAlias `
-NextHop $gateway `
-PolicyStore ($PersistentStore ? "PersistentStore" : "ActiveStore") `
-RouteMetric $RouteMetric
return
}
}
}

# Main
$info = Get-PhysicalNics

Remove-AdapterRoute

Remove-AdapterRoute ::/0

Set-AdapterRoute $info "Realtek PCIe GbE Family Controller" 172.18.6.57/32

Set-AdapterRoute $info "Intel(R) Wi-Fi 6 AX200 160MHz" 0.0.0.0/0

我的代码仓库3里包含完整PowerShell脚本和部分C++方法。


  1. NetTcpIp↩︎

  2. Automatic Metric↩︎

  3. RouteModifier↩︎