From d46944f32bfef1e362edb068e10073da9beab509 Mon Sep 17 00:00:00 2001 From: wwqgtxx Date: Tue, 4 Aug 2026 23:07:46 +0800 Subject: [PATCH] feat: add `ip-stack` option for openvpn outbound --- adapter/outbound/openvpn.go | 25 +++++++++++++++---------- docs/config.yaml | 3 +++ 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/adapter/outbound/openvpn.go b/adapter/outbound/openvpn.go index 54cb003a..65c27986 100644 --- a/adapter/outbound/openvpn.go +++ b/adapter/outbound/openvpn.go @@ -18,8 +18,6 @@ import ( "github.com/metacubex/mihomo/log" ovpn "github.com/metacubex/mihomo/transport/openvpn" - wireguard "github.com/metacubex/sing-wireguard" - M "github.com/metacubex/sing/common/metadata" "golang.org/x/sync/semaphore" ) @@ -28,7 +26,7 @@ type OpenVPN struct { option *OpenVPNOption config *ovpn.ClientConfig - tunDevice wireguard.Device + tunDevice ipStack client *ovpn.Client resolver resolver.Resolver dns []dns.NameServer @@ -67,6 +65,8 @@ type OpenVPNOption struct { MTU int `proxy:"mtu,omitempty"` UDP bool `proxy:"udp,omitempty"` + IPStack IPStackOption `proxy:"ip-stack,omitempty"` + RemoteDnsResolve bool `proxy:"remote-dns-resolve,omitempty"` Dns []string `proxy:"dns,omitempty"` } @@ -75,6 +75,10 @@ func NewOpenVPN(option OpenVPNOption) (*OpenVPN, error) { if option.HandshakeTimeout < 0 { return nil, errors.New("openvpn handshake timeout must be non-negative") } + option.IPStack.normalize() + if err := option.IPStack.validate(); err != nil { + return nil, err + } cfg := &ovpn.ClientConfig{ RemoteHost: option.Server, RemotePort: uint16(option.Port), @@ -143,10 +147,10 @@ func (o *OpenVPN) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Co } options := o.DialOptions() options = append(options, dialer.WithResolver(r)) - options = append(options, dialer.WithNetDialer(wgNetDialer{tunDevice: tunDevice})) + options = append(options, dialer.WithNetDialer(ipStackNetDialer{stack: tunDevice})) conn, err = dialer.NewDialer(options...).DialContext(ctx, "tcp", metadata.RemoteAddress()) } else { - conn, err = tunDevice.DialContext(ctx, "tcp", M.SocksaddrFrom(metadata.DstIP, metadata.DstPort).Unwrap()) + conn, err = tunDevice.DialTCP(ctx, "tcp", netip.AddrPort{}, metadata.AddrPort()) } if err != nil { return nil, err @@ -166,7 +170,8 @@ func (o *OpenVPN) ListenPacketContext(ctx context.Context, metadata *C.Metadata) if err = o.resolveUDP(ctx, metadata, r); err != nil { return nil, err } - pc, err = tunDevice.ListenPacket(ctx, M.SocksaddrFrom(metadata.DstIP, metadata.DstPort).Unwrap()) + // The ipStack contract guarantees that a generic UDP wildcard supports both address families. + pc, err = tunDevice.ListenUDP(ctx, "udp", netip.AddrPort{}) if err != nil { return nil, err } @@ -229,7 +234,7 @@ func (o *OpenVPN) Close() error { return nil } -func (o *OpenVPN) run(ctx context.Context) (wireguard.Device, resolver.Resolver, error) { +func (o *OpenVPN) run(ctx context.Context) (ipStack, resolver.Resolver, error) { runCtx, cancel := context.WithCancel(ctx) stop := contextutils.AfterFunc(o.runCtx, cancel) defer func() { @@ -259,7 +264,7 @@ func (o *OpenVPN) run(ctx context.Context) (wireguard.Device, resolver.Resolver, if o.option.HandshakeTimeout > 0 { type runResult struct { - tunDevice wireguard.Device + tunDevice ipStack resolver resolver.Resolver err error } @@ -288,7 +293,7 @@ func (o *OpenVPN) run(ctx context.Context) (wireguard.Device, resolver.Resolver, return o.startLocked(runCtx) } -func (o *OpenVPN) startLocked(handshakeCtx context.Context) (wireguard.Device, resolver.Resolver, error) { +func (o *OpenVPN) startLocked(handshakeCtx context.Context) (ipStack, resolver.Resolver, error) { packetIO, err := o.openPacketIO(handshakeCtx) if err != nil { return nil, nil, fmt.Errorf("connect OpenVPN server: %w", err) @@ -309,7 +314,7 @@ func (o *OpenVPN) startLocked(handshakeCtx context.Context) (wireguard.Device, r if mtu == 0 { mtu = 1500 } - tunDevice, err := wireguard.NewStackDevice(push.Prefixes, uint32(mtu)) + tunDevice, err := newIPStack(o.option.IPStack, push.Prefixes, uint32(mtu)) if err != nil { _ = client.Close() return nil, nil, fmt.Errorf("create OpenVPN stack device: %w", err) diff --git a/docs/config.yaml b/docs/config.yaml index c1000f07..b23a35f2 100644 --- a/docs/config.yaml +++ b/docs/config.yaml @@ -1446,6 +1446,9 @@ proxies: # socks5 # handshake-timeout: 30 # 单位为秒;配置后握手时不受外层连接超时影响;默认值为 0,表示仅使用外层连接超时 # mtu: 1500 udp: true + # ip-stack: + # mode: auto # options: auto, gvisor, mips; auto uses gVisor when compiled in and mihomo IP stack (MIPS) otherwise + # congestion-controller: cubic # TCP congestion controller: cubic, reno, or bbr; ignored by gVisor # 一个出站代理的标识。当值不为空时,将使用指定的 proxy 发出连接 # dialer-proxy: "ss1" # remote-dns-resolve: true # 强制 dns 远程解析,默认值为 false