Improve OpenVPN & OpenConnect interoperability

This commit is contained in:
世界
2026-08-30 17:41:44 +08:00
parent b2f1f630d9
commit b9c7d52582
47 changed files with 3915 additions and 440 deletions
+1 -1
View File
@@ -104,7 +104,7 @@ func (d *baseDevice) SetPacketWriter(writer PacketWriter) {
func (d *baseDevice) writeOutbound(packetBuffers []*buf.Buffer) error {
if d.packetWriter == nil {
buf.ReleaseMulti(packetBuffers)
return E.New("missing OpenConnect packet writer")
return E.New("missing packet writer")
}
return d.packetWriter(packetBuffers)
}
+2 -2
View File
@@ -5,9 +5,9 @@ package openconnect
import E "github.com/sagernet/sing/common/exceptions"
func newStackDevice(options DeviceOptions) (Device, error) {
return nil, E.New("OpenConnect system:false requires the with_gvisor build tag")
return nil, E.New("system:false requires the with_gvisor build tag")
}
func newSystemStackDevice(options DeviceOptions) (Device, error) {
return nil, E.New("OpenConnect system stack requires the with_gvisor build tag")
return nil, E.New("system stack requires the with_gvisor build tag")
}
+17 -29
View File
@@ -4,7 +4,6 @@ import (
"context"
"net"
"net/netip"
"runtime"
"slices"
"sync"
"syscall"
@@ -14,7 +13,6 @@ import (
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing-tun/gtcpip/header"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
@@ -96,30 +94,21 @@ func (d *systemDevice) buildTunOptions() tun.Options {
d.inet4Address = inet4Address
d.inet6Address = inet6Address
inet4Addresses, inet6Addresses := splitPrefixes(d.options.Configuration.Addresses)
inet4Routes, inet6Routes := splitPrefixes(common.Map(d.options.Configuration.Routes, func(route Route) netip.Prefix { return route.Prefix }))
inet4ExcludedRoutes, inet6ExcludedRoutes := splitPrefixes(common.Map(d.options.Configuration.ExcludedRoutes, func(route Route) netip.Prefix { return route.Prefix }))
networkManager := service.FromContext[adapter.NetworkManager](d.options.Context)
tunOptions := tun.Options{
Name: d.options.Name,
Inet4Address: inet4Addresses,
Inet6Address: inet6Addresses,
MTU: d.options.MTU,
GSO: true,
InterfaceScope: true,
DNSAddress: d.options.Configuration.DNS,
Inet4RouteAddress: inet4Routes,
Inet6RouteAddress: inet6Routes,
Inet4RouteExcludeAddress: inet4ExcludedRoutes,
Inet6RouteExcludeAddress: inet6ExcludedRoutes,
InterfaceMonitor: nil,
InterfaceFinder: nil,
Logger: d.options.Logger,
IPRoute2TableIndex: tun.DefaultIPRoute2TableIndex,
IPRoute2RuleIndex: tun.DefaultIPRoute2RuleIndex,
EXP_DisableDNSHijack: true,
}
if runtime.GOOS == "darwin" {
tunOptions.AutoRoute = true
Name: d.options.Name,
Inet4Address: inet4Addresses,
Inet6Address: inet6Addresses,
MTU: d.options.MTU,
GSO: true,
InterfaceScope: true,
DNSMode: tun.DNSModeDisabled,
InterfaceMonitor: nil,
InterfaceFinder: nil,
Logger: d.options.Logger,
IPRoute2TableIndex: tun.DefaultIPRoute2TableIndex,
IPRoute2RuleIndex: tun.DefaultIPRoute2RuleIndex,
EXP_DisableDNSHijack: true,
}
if networkManager != nil {
tunOptions.InterfaceMonitor = networkManager.InterfaceMonitor()
@@ -252,13 +241,12 @@ func (d *systemDevice) UpdateConfiguration(configuration Configuration) error {
return nil
}
if !slices.Equal(previousConfiguration.Addresses, configuration.Addresses) ||
previousMTU != updatedMTU ||
!slices.Equal(previousConfiguration.DNS, configuration.DNS) {
previousMTU != updatedMTU {
d.device.Close()
d.device = nil
return d.startLocked()
}
return d.device.UpdateRouteOptions(d.buildTunOptions())
return nil
}
func (d *systemDevice) WriteInboundBuffers(packetBuffers []*buf.Buffer) error {
@@ -270,7 +258,7 @@ func (d *systemDevice) writeBuffers(packetBuffers []*buf.Buffer) error {
tunInterface := d.device
d.stateAccess.RUnlock()
if tunInterface == nil {
return E.New("OpenConnect system device is not ready")
return E.New("system device is not ready")
}
linuxTUN, isLinuxTUN := tunInterface.(tun.LinuxTUN)
if isLinuxTUN {
@@ -313,7 +301,7 @@ func (d *systemDevice) writePacket(packet []byte) error {
tunInterface := d.device
d.stateAccess.RUnlock()
if tunInterface == nil {
return E.New("OpenConnect system device is not ready")
return E.New("system device is not ready")
}
if tun.PacketOffset == 0 {
_, err := tunInterface.Write(packet)
+21 -37
View File
@@ -3,7 +3,6 @@ package openvpn
import (
"context"
"net/netip"
"slices"
"sync/atomic"
"time"
@@ -54,13 +53,17 @@ type DeviceOptions struct {
}
type Configuration struct {
MTU uint32
Address []netip.Prefix
Routes []Route
DNS []netip.Addr
Topology string
Interface string
BlockIPv6 bool
MTU uint32
Address []netip.Prefix
Routes []Route
ExcludedRoutes []Route
DNS []netip.Addr
DNSServers []DNSServer
SearchDomains []string
DNSRoutes []string
Topology string
Interface string
BlockIPv6 bool
}
type Route struct {
@@ -69,6 +72,15 @@ type Route struct {
Metric int
}
type DNSServer struct {
Priority int
Addresses []netip.AddrPort
ResolveDomains []string
DNSSEC string
Transport string
SNI string
}
func NewDevice(options DeviceOptions) (Device, error) {
if !options.System {
return newStackDevice(options)
@@ -91,7 +103,7 @@ func (d *baseDevice) SetPacketWriter(writer PacketWriter) {
func (d *baseDevice) writeOutbound(packetBuffers []*buf.Buffer) error {
if d.packetWriter == nil {
buf.ReleaseMulti(packetBuffers)
return E.New("missing OpenVPN packet writer")
return E.New("missing packet writer")
}
return d.packetWriter(packetBuffers)
}
@@ -192,34 +204,6 @@ func splitPrefixes(prefixes []netip.Prefix) ([]netip.Prefix, []netip.Prefix) {
return inet4Prefixes, inet6Prefixes
}
func splitRoutes(routes []Route) ([]netip.Prefix, []netip.Prefix) {
var inet4Prefixes []netip.Prefix
var inet6Prefixes []netip.Prefix
for _, route := range routes {
if route.Prefix.Addr().Is4() {
inet4Prefixes = append(inet4Prefixes, route.Prefix)
} else {
inet6Prefixes = append(inet6Prefixes, route.Prefix)
}
}
return inet4Prefixes, inet6Prefixes
}
func routesWithBlockIPv6(configuration Configuration) []Route {
routes := configuration.Routes
if !configuration.BlockIPv6 {
return routes
}
inet6DefaultRoute := netip.PrefixFrom(netip.IPv6Unspecified(), 0)
for _, route := range routes {
if route.Prefix == inet6DefaultRoute {
return routes
}
}
routes = append(slices.Clone(routes), Route{Prefix: inet6DefaultRoute})
return routes
}
func hasRouteOptions(routes []Route) bool {
for _, route := range routes {
if route.Gateway.IsValid() || route.Metric != 0 {
+3 -3
View File
@@ -102,7 +102,7 @@ func (d *stackDevice) UpdateConfiguration(configuration Configuration) error {
d.stateAccess.Lock()
defer d.stateAccess.Unlock()
if d.logRouteOptions && hasRouteOptions(configuration.Routes) {
d.options.Logger.Debug("OpenVPN route gateway and metric options are not representable by the gVisor stack device; routes are installed by prefix")
d.options.Logger.Debug("route gateway and metric options are not representable by the gVisor stack device; routes are installed by prefix")
d.logRouteOptions = false
}
if configuration.MTU != 0 {
@@ -184,7 +184,7 @@ func (d *stackDevice) writeBuffers(packetBuffers []*buf.Buffer) error {
func (d *stackDevice) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
if destination.IsIPv6() && d.blockIPv6Enabled() {
return nil, E.New("IPv6 blocked by pushed OpenVPN block-ipv6")
return nil, E.New("IPv6 blocked by pushed block-ipv6")
}
inet4Address, inet6Address := d.PortAddresses()
address := tcpip.FullAddress{
@@ -221,7 +221,7 @@ func (d *stackDevice) DialContext(ctx context.Context, network string, destinati
func (d *stackDevice) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
if destination.IsIPv6() && d.blockIPv6Enabled() {
return nil, E.New("IPv6 blocked by pushed OpenVPN block-ipv6")
return nil, E.New("IPv6 blocked by pushed block-ipv6")
}
inet4Address, inet6Address := d.PortAddresses()
bind := tcpip.FullAddress{
+2 -2
View File
@@ -5,9 +5,9 @@ package openvpn
import E "github.com/sagernet/sing/common/exceptions"
func newStackDevice(options DeviceOptions) (Device, error) {
return nil, E.New("OpenVPN system:false requires the with_gvisor build tag")
return nil, E.New("system:false requires the with_gvisor build tag")
}
func newSystemStackDevice(options DeviceOptions) (Device, error) {
return nil, E.New("OpenVPN system stack requires the with_gvisor build tag")
return nil, E.New("system stack requires the with_gvisor build tag")
}
+16 -79
View File
@@ -4,7 +4,6 @@ import (
"context"
"net"
"net/netip"
"runtime"
"slices"
"sync"
"syscall"
@@ -30,14 +29,13 @@ const (
type systemDevice struct {
baseDevice
stateAccess sync.RWMutex
options DeviceOptions
dialer N.Dialer
device tun.Tun
inet4Address netip.Addr
inet6Address netip.Addr
logRouteOptions bool
closed bool
stateAccess sync.RWMutex
options DeviceOptions
dialer N.Dialer
device tun.Tun
inet4Address netip.Addr
inet6Address netip.Addr
closed bool
}
func newSystemDevice(options DeviceOptions) (*systemDevice, error) {
@@ -55,11 +53,10 @@ func newSystemDevice(options DeviceOptions) (*systemDevice, error) {
}
inet4Address, inet6Address := firstAddresses(options.Configuration.Address)
return &systemDevice{
options: options,
dialer: interfaceDialer,
inet4Address: inet4Address,
inet6Address: inet6Address,
logRouteOptions: true,
options: options,
dialer: interfaceDialer,
inet4Address: inet4Address,
inet6Address: inet6Address,
}, nil
}
@@ -97,13 +94,6 @@ func (d *systemDevice) buildTunOptions() tun.Options {
d.inet4Address = inet4Address
d.inet6Address = inet6Address
inet4Addresses, inet6Addresses := splitPrefixes(d.options.Configuration.Address)
if d.options.Configuration.BlockIPv6 && len(inet6Addresses) == 0 {
inet6Addresses = append(inet6Addresses, netip.MustParsePrefix("fddd:1194:1194:1194::2/64"))
}
routes := routesWithBlockIPv6(d.options.Configuration)
inet4Routes, inet6Routes := splitRoutes(routes)
inet4Gateway, _ := systemRouteGateway(routes, true)
inet6Gateway, _ := systemRouteGateway(routes, false)
networkManager := service.FromContext[adapter.NetworkManager](d.options.Context)
tunOptions := tun.Options{
Name: d.options.Name,
@@ -112,11 +102,7 @@ func (d *systemDevice) buildTunOptions() tun.Options {
MTU: d.options.MTU,
GSO: true,
InterfaceScope: true,
DNSAddress: d.options.Configuration.DNS,
Inet4Gateway: inet4Gateway,
Inet6Gateway: inet6Gateway,
Inet4RouteAddress: inet4Routes,
Inet6RouteAddress: inet6Routes,
DNSMode: tun.DNSModeDisabled,
InterfaceMonitor: nil,
InterfaceFinder: nil,
Logger: d.options.Logger,
@@ -124,9 +110,6 @@ func (d *systemDevice) buildTunOptions() tun.Options {
IPRoute2RuleIndex: tun.DefaultIPRoute2RuleIndex,
EXP_DisableDNSHijack: true,
}
if runtime.GOOS == "darwin" {
tunOptions.AutoRoute = true
}
if networkManager != nil {
tunOptions.InterfaceMonitor = networkManager.InterfaceMonitor()
tunOptions.InterfaceFinder = networkManager.InterfaceFinder()
@@ -134,43 +117,6 @@ func (d *systemDevice) buildTunOptions() tun.Options {
return tunOptions
}
func systemRouteGateway(routes []Route, ipv4 bool) (netip.Addr, bool) {
var gateway netip.Addr
var hasGateway bool
var hasMissingGateway bool
var gatewayUnrepresentable bool
var metricUnrepresentable bool
for _, route := range routes {
if route.Prefix.Addr().Is4() != ipv4 {
continue
}
if route.Metric != 0 {
metricUnrepresentable = true
}
if !route.Gateway.IsValid() {
hasMissingGateway = true
continue
}
if route.Gateway.Is4() != ipv4 {
gatewayUnrepresentable = true
continue
}
if !hasGateway {
gateway = route.Gateway
hasGateway = true
} else if gateway != route.Gateway {
gatewayUnrepresentable = true
}
}
if hasGateway && hasMissingGateway {
gatewayUnrepresentable = true
}
if gatewayUnrepresentable {
gateway = netip.Addr{}
}
return gateway, gatewayUnrepresentable || metricUnrepresentable
}
func (d *systemDevice) readLoop(tunInterface tun.Tun, mtu int) {
linuxTUN, isLinuxTUN := tunInterface.(tun.LinuxTUN)
if isLinuxTUN && linuxTUN.BatchSize() > 1 {
@@ -295,13 +241,6 @@ func (d *systemDevice) readLoopDarwin(tunInterface tun.DarwinTUN) {
func (d *systemDevice) UpdateConfiguration(configuration Configuration) error {
d.stateAccess.Lock()
defer d.stateAccess.Unlock()
routes := routesWithBlockIPv6(configuration)
_, hasUnrepresentableInet4RouteOptions := systemRouteGateway(routes, true)
_, hasUnrepresentableInet6RouteOptions := systemRouteGateway(routes, false)
if d.logRouteOptions && (hasUnrepresentableInet4RouteOptions || hasUnrepresentableInet6RouteOptions) {
d.options.Logger.Debug("some OpenVPN route gateway or metric options are not representable by the system device; routes are installed by prefix")
d.logRouteOptions = false
}
previousConfiguration := d.options.Configuration
previousMTU := d.options.MTU
updatedMTU := d.options.MTU
@@ -317,14 +256,12 @@ func (d *systemDevice) UpdateConfiguration(configuration Configuration) error {
return nil
}
if !slices.Equal(previousConfiguration.Address, configuration.Address) ||
previousMTU != updatedMTU ||
!slices.Equal(previousConfiguration.DNS, configuration.DNS) ||
previousConfiguration.BlockIPv6 != configuration.BlockIPv6 {
previousMTU != updatedMTU {
d.device.Close()
d.device = nil
return d.startLocked()
}
return d.device.UpdateRouteOptions(d.buildTunOptions())
return nil
}
func (d *systemDevice) blockIPv6Enabled() bool {
@@ -342,7 +279,7 @@ func (d *systemDevice) writeBuffers(packetBuffers []*buf.Buffer) error {
tunInterface := d.device
d.stateAccess.RUnlock()
if tunInterface == nil {
return E.New("OpenVPN system device is not ready")
return E.New("system device is not ready")
}
linuxTUN, isLinuxTUN := tunInterface.(tun.LinuxTUN)
if isLinuxTUN {
@@ -385,7 +322,7 @@ func (d *systemDevice) writePacket(packet []byte) error {
tunInterface := d.device
d.stateAccess.RUnlock()
if tunInterface == nil {
return E.New("OpenVPN system device is not ready")
return E.New("system device is not ready")
}
if tun.PacketOffset == 0 {
_, err := tunInterface.Write(packet)