From e1e43adc801be9b04fdc26017e6ad2a362b4f19f Mon Sep 17 00:00:00 2001 From: wangjia <809946525@qq.com> Date: Sun, 26 Jul 2026 07:17:09 +0800 Subject: [PATCH] =?UTF-8?q?fix(client-tun):=20macOS/iOS=20=E5=BA=94?= =?UTF-8?q?=E7=94=A8=20excludedRoutes,=E7=A7=81=E6=9C=89=20LAN=20=E7=9C=9F?= =?UTF-8?q?=E7=9B=B4=E8=BF=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 上一版加了服务端 route_exclude_address,但 macOS/iOS 路由由 Swift 的 NEPacketTunnelNetworkSettings 控制,而 openTun 只设了 includedRoutes、 从不设 excludedRoutes → libbox 解析出的排除网段被丢弃,strict_route 仍把 LAN(192.168/16、10/8)抓进隧道,VPN 开着连不上局域网/NAS/家里机器/内网 gitea。 修:openTun 里 ipv4/ipv6 各补 excludedRoutes = excludedRoutes4/6(options), 读 libbox 的 getInet4/6RouteExcludeAddress(sing-box config 的 route_exclude_address 经此暴露)。镜像已有的 includedRoutes4/6 写法。 生效:客户端重新构建发版后,私有 LAN 直连恢复 → CI runner 不再需要经 ali 的 中继隧道(relay),从 Mac 也能直连家里所有内网机器。 Android(VpnService.excludeRoute,API33+ 分版本)作后续。 Co-Authored-By: Claude Fable 5 --- .../ios/PacketTunnel/PacketTunnelProvider.swift | 15 +++++++++++++++ .../macos/PacketTunnel/PacketTunnelProvider.swift | 15 +++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/client/ios/PacketTunnel/PacketTunnelProvider.swift b/client/ios/PacketTunnel/PacketTunnelProvider.swift index 3de86f3..6f2c35f 100644 --- a/client/ios/PacketTunnel/PacketTunnelProvider.swift +++ b/client/ios/PacketTunnel/PacketTunnelProvider.swift @@ -206,6 +206,7 @@ final class PangolinPlatformInterface: NSObject, LibboxPlatformInterfaceProtocol let ipv4 = NEIPv4Settings(addresses: v4addrs.map(\.address), subnetMasks: v4addrs.map(\.mask)) ipv4.includedRoutes = includedRoutes4(options) + ipv4.excludedRoutes = excludedRoutes4(options) settings.ipv4Settings = ipv4 } let v6addrs = routePrefixes(options.getInet6Address()) @@ -213,6 +214,7 @@ final class PangolinPlatformInterface: NSObject, LibboxPlatformInterfaceProtocol let ipv6 = NEIPv6Settings(addresses: v6addrs.map(\.address), networkPrefixLengths: v6addrs.map { NSNumber(value: $0.prefix) }) ipv6.includedRoutes = includedRoutes6(options) + ipv6.excludedRoutes = excludedRoutes6(options) settings.ipv6Settings = ipv6 } if let dns = try? options.getDNSServerAddress(), !dns.value.isEmpty { @@ -311,6 +313,19 @@ final class PangolinPlatformInterface: NSObject, LibboxPlatformInterfaceProtocol networkPrefixLength: NSNumber(value: $0.prefix)) } } + // 排除路由:sing-box config 的 route_exclude_address(私有 LAN 网段)经 libbox + // 暴露在此。之前只设 includedRoutes、丢了 excludedRoutes → strict_route 把 LAN 也 + // 抓进隧道,VPN 开着连不上局域网/NAS/家里机器。设上 excludedRoutes 后 LAN 直连。 + private func excludedRoutes4(_ o: any LibboxTunOptionsProtocol) -> [NEIPv4Route] { + routePrefixes(o.getInet4RouteExcludeAddress()) + .map { NEIPv4Route(destinationAddress: $0.address, subnetMask: $0.mask) } + } + private func excludedRoutes6(_ o: any LibboxTunOptionsProtocol) -> [NEIPv6Route] { + routePrefixes(o.getInet6RouteExcludeAddress()) + .map { NEIPv6Route(destinationAddress: $0.address, + networkPrefixLength: NSNumber(value: $0.prefix)) } + } + // utun fd:getpeername 得 sockaddr_ctl,匹配 utun 控制 id(WireGuard 技法) private static func tunFd() -> Int32? { var ctlInfo = ctl_info() diff --git a/client/macos/PacketTunnel/PacketTunnelProvider.swift b/client/macos/PacketTunnel/PacketTunnelProvider.swift index 4ffb4c4..69d923a 100644 --- a/client/macos/PacketTunnel/PacketTunnelProvider.swift +++ b/client/macos/PacketTunnel/PacketTunnelProvider.swift @@ -201,6 +201,7 @@ final class PangolinPlatformInterface: NSObject, LibboxPlatformInterfaceProtocol let ipv4 = NEIPv4Settings(addresses: v4addrs.map(\.address), subnetMasks: v4addrs.map(\.mask)) ipv4.includedRoutes = includedRoutes4(options) + ipv4.excludedRoutes = excludedRoutes4(options) settings.ipv4Settings = ipv4 } let v6addrs = routePrefixes(options.getInet6Address()) @@ -208,6 +209,7 @@ final class PangolinPlatformInterface: NSObject, LibboxPlatformInterfaceProtocol let ipv6 = NEIPv6Settings(addresses: v6addrs.map(\.address), networkPrefixLengths: v6addrs.map { NSNumber(value: $0.prefix) }) ipv6.includedRoutes = includedRoutes6(options) + ipv6.excludedRoutes = excludedRoutes6(options) settings.ipv6Settings = ipv6 } if let dns = try? options.getDNSServerAddress(), !dns.value.isEmpty { @@ -301,6 +303,19 @@ final class PangolinPlatformInterface: NSObject, LibboxPlatformInterfaceProtocol networkPrefixLength: NSNumber(value: $0.prefix)) } } + // 排除路由:sing-box config 的 route_exclude_address(私有 LAN 网段)经 libbox + // 暴露在此。之前只设 includedRoutes、丢了 excludedRoutes → strict_route 把 LAN 也 + // 抓进隧道,VPN 开着连不上局域网/NAS/家里机器。设上 excludedRoutes 后 LAN 直连。 + private func excludedRoutes4(_ o: any LibboxTunOptionsProtocol) -> [NEIPv4Route] { + routePrefixes(o.getInet4RouteExcludeAddress()) + .map { NEIPv4Route(destinationAddress: $0.address, subnetMask: $0.mask) } + } + private func excludedRoutes6(_ o: any LibboxTunOptionsProtocol) -> [NEIPv6Route] { + routePrefixes(o.getInet6RouteExcludeAddress()) + .map { NEIPv6Route(destinationAddress: $0.address, + networkPrefixLength: NSNumber(value: $0.prefix)) } + } + // utun fd:getpeername 得 sockaddr_ctl,匹配 utun 控制 id(WireGuard 技法) private static func tunFd() -> Int32? { var ctlInfo = ctl_info()