203 lines
6.7 KiB
PowerShell
203 lines
6.7 KiB
PowerShell
#Requires -RunAsAdministrator
|
||
[CmdletBinding()]
|
||
param(
|
||
[switch]$Stop,
|
||
[string]$Socks = "127.0.0.1:59999",
|
||
[string[]]$Direct = @("117.143.9.6"),
|
||
[string]$Dns = "127.0.0.1",
|
||
[switch]$EnableDoH # 可选:为 $Dns 启用 DoH(Win11+)
|
||
)
|
||
|
||
# --- 全局设置 ---
|
||
$ErrorActionPreference = "Stop"
|
||
$TunIP = "192.168.123.1"
|
||
$TunMask = "255.255.255.0"
|
||
$State = Join-Path $PSScriptRoot ".wintun-state.json"
|
||
|
||
# --- 常量:防火墙规则名 ---
|
||
$FwNames = @(
|
||
"Block NonTUN DNS TCPv4",
|
||
"Block NonTUN DNS UDPv4",
|
||
"Block NonTUN DNS TCPv6",
|
||
"Block NonTUN DNS UDPv6"
|
||
)
|
||
|
||
function Get-Uplink {
|
||
<#
|
||
选一个物理上行(默认路由优先,排除虚拟网卡)
|
||
Returns: [pscustomobject] @{ Alias; Index; Gateway }
|
||
#>
|
||
$bad = "wintun|wireguard|tap|vEthernet|Hyper-V|VirtualBox|VMware|Npcap|Loopback|Docker|Tailscale|ZeroTier|Hamachi"
|
||
$routes = Get-NetRoute -DestinationPrefix "0.0.0.0/0" -AddressFamily IPv4 |
|
||
Sort-Object RouteMetric, InterfaceMetric
|
||
foreach ($r in $routes) {
|
||
$if = Get-NetIPInterface -InterfaceIndex $r.InterfaceIndex -ErrorAction SilentlyContinue |
|
||
Where-Object { $_.AddressFamily -eq 2 } # 2 means IPv4
|
||
if ($null -ne $if -and ($if.InterfaceAlias -notmatch $bad)) {
|
||
return [pscustomobject]@{ Alias = $if.InterfaceAlias; Index = $if.InterfaceIndex; Gateway = $r.NextHop }
|
||
}
|
||
}
|
||
throw "No physical uplink found."
|
||
}
|
||
|
||
function Remove-DnsEnforcement {
|
||
<#
|
||
清理:NRPT + 防火墙规则
|
||
#>
|
||
try {
|
||
Get-DnsClientNrptRule -ErrorAction SilentlyContinue |
|
||
Where-Object { $_.Namespace -eq "." } |
|
||
Remove-DnsClientNrptRule -ErrorAction SilentlyContinue -Force | Out-Null
|
||
} catch {}
|
||
|
||
foreach ($n in $FwNames) {
|
||
Get-NetFirewallRule -DisplayName $n -ErrorAction SilentlyContinue | Remove-NetFirewallRule -ErrorAction SilentlyContinue
|
||
}
|
||
}
|
||
|
||
function Apply-DnsEnforcement {
|
||
param(
|
||
[Parameter(Mandatory=$true)][string[]]$DnsServers,
|
||
[Parameter(Mandatory=$true)][int]$TunIfIndex
|
||
)
|
||
<#
|
||
强制 DNS:
|
||
1) NRPT "." 指向 $DnsServers
|
||
2) (已禁用) 封非 TUN 的 53 (TCP/UDP, v4/v6)
|
||
#>
|
||
# 清理旧 NRPT
|
||
Remove-DnsEnforcement
|
||
|
||
# NRPT:所有域名都走指定 DNS
|
||
Add-DnsClientNrptRule -Namespace "." -NameServers $DnsServers -Comment "Force all DNS via TUN" | Out-Null
|
||
|
||
# --- 防火墙规则部分已移除,以解决兼容性问题 ---
|
||
|
||
}
|
||
|
||
function Enable-DoHIfRequested {
|
||
param(
|
||
[Parameter(Mandatory=$true)][string]$Server
|
||
)
|
||
<#
|
||
为指定 IPv4 DNS 启用 DoH(Win11+)。失败时静默跳过。
|
||
#>
|
||
if (-not $EnableDoH) { return }
|
||
if ($Server -match '^\d{1,3}(\.\d{1-3}){3}$') {
|
||
try {
|
||
# Cloudflare 模板示例;如换 DNS,请替换模板
|
||
netsh dns add encryption server=$Server dohtemplate=https://cloudflare-dns.com/dns-query autoupgrade=yes udpfallback=no | Out-Null
|
||
} catch {}
|
||
}
|
||
}
|
||
|
||
function Start-Tun {
|
||
<#
|
||
启动 tun2socks,创建 TUN,设置地址与 DNS 强制策略,添加默认路由与直连例外
|
||
#>
|
||
$t2s = Join-Path $PSScriptRoot "tun2socks.exe"
|
||
$dll = Join-Path $PSScriptRoot "wintun.dll"
|
||
if (-not (Test-Path $t2s)) { throw "Missing tun2socks.exe" }
|
||
if (-not (Test-Path $dll)) { throw "Missing wintun.dll (same folder as script)" }
|
||
|
||
# 先清掉历史 tun2socks
|
||
Get-Process tun2socks -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue
|
||
|
||
$uplink = Get-Uplink
|
||
Write-Host ("Uplink: {0} Gateway: {1}" -f $uplink.Alias, $uplink.Gateway)
|
||
|
||
$before = (Get-NetIPInterface | Where-Object { $_.AddressFamily -eq 2 }).InterfaceIndex
|
||
|
||
# 启动 tun2socks
|
||
Start-Process -FilePath $t2s -ArgumentList @(
|
||
"-device","wintun",
|
||
"-proxy",("socks5://{0}" -f $Socks),
|
||
"-interface",$uplink.Alias
|
||
) -WindowStyle Hidden | Out-Null
|
||
|
||
# 识别新出现的 TUN 接口
|
||
$tun = $null
|
||
for ($i=0; $i -lt 60; $i++) {
|
||
Start-Sleep -Milliseconds 200
|
||
$after = Get-NetIPInterface | Where-Object { $_.AddressFamily -eq 2 }
|
||
$new = $after | Where-Object { $before -notcontains $_.InterfaceIndex }
|
||
if ($new) { $tun = $new | Sort-Object InterfaceMetric | Select-Object -First 1; break }
|
||
}
|
||
if (-not $tun) {
|
||
$tun = Get-NetIPInterface | Where-Object { $_.AddressFamily -eq 2 } |
|
||
Where-Object { $_.InterfaceAlias -match "wintun|tun|wireguard" } |
|
||
Sort-Object InterfaceMetric | Select-Object -First 1
|
||
}
|
||
if (-not $tun) { throw "Wintun interface not found." }
|
||
|
||
# 配置 TUN 地址
|
||
netsh interface ipv4 set address name="$($tun.InterfaceAlias)" source=static addr=$TunIP mask=$TunMask
|
||
|
||
# 设 TUN 的 DNS (使用 netsh 以增强兼容性)
|
||
$dnsServers = @($Dns)
|
||
netsh interface ipv4 set dnsservers name="$($tun.InterfaceAlias)" static $($dnsServers[0]) primary
|
||
|
||
# 可选启用 DoH
|
||
Enable-DoHIfRequested -Server $Dns
|
||
|
||
# 强制:NRPT + 防火墙
|
||
Apply-DnsEnforcement -DnsServers $dnsServers -TunIfIndex $tun.InterfaceIndex
|
||
|
||
# 把默认路由指向 TUN(所有流量含 DNS 都从隧道走)
|
||
route -p add 0.0.0.0 mask 0.0.0.0 $TunIP metric 1 if $($tun.InterfaceIndex) | Out-Null
|
||
|
||
# 直连例外(这些目标直接走物理上行)
|
||
foreach ($ip in $Direct) {
|
||
route -p add $ip mask 255.255.255.255 $($uplink.Gateway) if $($uplink.Index) metric 5 | Out-Null
|
||
}
|
||
|
||
# 降低 TUN 接口度量 (已注释掉,以解决兼容性问题)
|
||
# try { Set-NetIPInterface -InterfaceIndex $tun.InterfaceIndex -InterfaceMetric 5 -ErrorAction SilentlyContinue } catch {}
|
||
|
||
# 记录状态
|
||
@{
|
||
TunAlias = $tun.InterfaceAlias
|
||
TunIndex = $tun.InterfaceIndex
|
||
TunIP = $TunIP
|
||
Direct = $Direct
|
||
Dns = $dnsServers
|
||
} | ConvertTo-Json | Set-Content -Path $State -Encoding UTF8
|
||
|
||
Write-Host ("TUN ready: {0} -> SOCKS {1}; DNS {2}; direct: {3}" -f $tun.InterfaceAlias, $Socks, ($dnsServers -join ","), ($Direct -join ", ")) -ForegroundColor Green
|
||
}
|
||
|
||
function Stop-Tun {
|
||
<#
|
||
清理默认路由、直连路由、NRPT、防火墙、进程与状态
|
||
#>
|
||
$tunIPLocal = $TunIP
|
||
$directLocal = $Direct
|
||
if (Test-Path $State) {
|
||
try {
|
||
$s = Get-Content $State | ConvertFrom-Json
|
||
if ($s.TunIP) { $tunIPLocal = $s.TunIP }
|
||
if ($s.Direct) { $directLocal = $s.Direct }
|
||
} catch {}
|
||
}
|
||
|
||
# 路由清理
|
||
route delete 0.0.0.0 mask 0.0.0.0 $tunIPLocal | Out-Null
|
||
foreach ($ip in $directLocal) { route delete $ip | Out-Null }
|
||
|
||
# 清理 DNS 强制策略
|
||
Remove-DnsEnforcement
|
||
|
||
# 结束进程与状态文件
|
||
Get-Process tun2socks -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue
|
||
Remove-Item $State -ErrorAction SilentlyContinue | Out-Null
|
||
|
||
Write-Host "Stopped and cleaned routes & DNS policies." -ForegroundColor Yellow
|
||
}
|
||
|
||
# --- 入口 ---
|
||
try {
|
||
if ($Stop) { Stop-Tun } else { Start-Tun }
|
||
} catch {
|
||
Write-Host ("Error: {0}" -f $_.Exception.Message) -ForegroundColor Red
|
||
exit 1
|
||
} |