Files

143 lines
4.9 KiB
Go

package main
import (
"context"
"errors"
"net"
"net/netip"
"strings"
"time"
)
func resolveEndpoint(raw string) (netip.Addr, string, error) {
host, port, err := net.SplitHostPort(raw)
if err != nil || host == "" || port == "" {
return netip.Addr{}, "", errors.New("VPN 公网端点格式无效")
}
if ip, err := netip.ParseAddr(host); err == nil {
if !ip.Is4() || !ip.IsGlobalUnicast() {
return netip.Addr{}, "", errors.New("VPN 公网端点需要有效 IPv4 地址")
}
return ip, port, nil
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
addresses, err := net.DefaultResolver.LookupNetIP(ctx, "ip4", host)
if err != nil {
return netip.Addr{}, "", errors.New("无法在连接前解析 VPN 公网端点")
}
for _, address := range addresses {
if address.Is4() && address.IsGlobalUnicast() {
return address, port, nil
}
}
return netip.Addr{}, "", errors.New("VPN 公网端点没有可用 IPv4 地址")
}
// networkPlan contains only public addressing information. The privileged
// network adapter still needs to discover the physical gateway and persist a
// rollback journal before applying any of these routes.
type networkPlan struct {
Interface string `json:"interface"`
ClientAddress netip.Prefix `json:"client_address"`
Routes []netip.Prefix `json:"routes"`
Endpoint netip.Addr `json:"endpoint"`
EndpointBypass bool `json:"endpoint_bypass"`
PhysicalExit *physicalRoute `json:"physical_exit,omitempty"`
}
type physicalRoute struct {
Interface string `json:"interface"`
Gateway *netip.Addr `json:"gateway,omitempty"`
}
// macOS route -n get output is only an observation, never an executable
// command. Reject another VPN's utun as the physical endpoint exit.
func parsePhysicalRoute(output string) (physicalRoute, error) {
var exit physicalRoute
for _, line := range strings.Split(output, "\n") {
key, value, ok := strings.Cut(strings.TrimSpace(line), ":")
if !ok {
continue
}
value = strings.TrimSpace(value)
switch key {
case "interface":
exit.Interface = value
case "gateway":
gateway, err := netip.ParseAddr(value)
if err != nil || !gateway.Is4() || gateway.IsUnspecified() || gateway.IsLoopback() {
return physicalRoute{}, errors.New("物理网关地址无效")
}
exit.Gateway = &gateway
}
}
if len(exit.Interface) < 2 || len(exit.Interface) > 16 || strings.HasPrefix(exit.Interface, "utun") || exit.Interface == "lo0" {
return physicalRoute{}, errors.New("VPN 网关当前未经过物理网络出口")
}
for _, char := range exit.Interface {
if !((char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z') || (char >= '0' && char <= '9')) {
return physicalRoute{}, errors.New("物理出口接口名称无效")
}
}
return exit, nil
}
func planNativeRoutes(r *record, interfaceName string, endpoint netip.Addr) (networkPlan, error) {
if !strings.HasPrefix(interfaceName, "utun") || len(interfaceName) < 5 {
return networkPlan{}, errors.New("本机接口名称无效")
}
for _, digit := range interfaceName[4:] {
if digit < '0' || digit > '9' {
return networkPlan{}, errors.New("本机接口名称无效")
}
}
address, err := netip.ParsePrefix(r.Config.ClientAddress)
if err != nil || !address.Addr().Is4() || address.Addr().IsUnspecified() {
return networkPlan{}, errors.New("本机 IPv4 地址无效")
}
if !endpoint.IsValid() || !endpoint.Is4() || !endpoint.IsGlobalUnicast() {
return networkPlan{}, errors.New("VPN 公网端点必须解析为可达的 IPv4 地址")
}
plan := networkPlan{Interface: interfaceName, ClientAddress: address, Endpoint: endpoint, Routes: []netip.Prefix{}}
seen := make(map[netip.Prefix]bool)
for _, part := range strings.Split(r.Config.AllowedIPs, ",") {
prefix, err := netip.ParsePrefix(strings.TrimSpace(part))
if err != nil {
return networkPlan{}, errors.New("WireGuard 授权路由无效")
}
if !prefix.Addr().Is4() {
return networkPlan{}, errors.New("IPv6 系统路由尚未实现,请在专线中仅授权 IPv4 路由")
}
prefix = prefix.Masked()
if prefix.Bits() == 0 {
// These /1 routes override the physical default while retaining it
// for explicit endpoint /32 bypass. The helper must install the bypass.
for _, half := range []string{"0.0.0.0/1", "128.0.0.0/1"} {
p := netip.MustParsePrefix(half)
if !seen[p] {
plan.Routes = append(plan.Routes, p)
seen[p] = true
}
}
plan.EndpointBypass = true
continue
}
if !seen[prefix] {
plan.Routes = append(plan.Routes, prefix)
seen[prefix] = true
}
if prefix.Contains(endpoint) {
plan.EndpointBypass = true
}
}
if plan.EndpointBypass {
// A physical /32 must be installed before any matching VPN route.
// Otherwise a full-tunnel VPN can attempt to reach itself through TUN.
if endpoint == address.Addr() {
return networkPlan{}, errors.New("VPN 端点与本机地址冲突")
}
}
return plan, nil
}