mirror of
https://github.com/MetaCubeX/mihomo.git
synced 2026-04-22 16:17:16 +08:00
feat: support ipv6 dual stack fallback for masque/trusttunnel/xhttp h3 mode
This commit is contained in:
@@ -257,26 +257,11 @@ func (w *Masque) run(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
var udpAddr *net.UDPAddr
|
||||
udpAddr, err = resolveUDPAddr(ctx, "udp", w.addr, w.prefer)
|
||||
var quicConn *quic.Conn
|
||||
pc, quicConn, err = common.DialQuic(ctx, w.addr, w.DialOptions(), w.dialer, w.tlsConfig, w.quicConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pc, err = w.dialer.ListenPacket(ctx, "udp", "", udpAddr.AddrPort())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
transport := quic.Transport{Conn: pc}
|
||||
transport.SetCreatedConn(true) // auto close conn
|
||||
transport.SetSingleUse(true) // auto close transport
|
||||
quicConn, err := transport.Dial(ctx, udpAddr, w.tlsConfig, w.quicConfig)
|
||||
if err != nil {
|
||||
_ = pc.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
common.SetCongestionController(quicConn, w.option.CongestionController, w.option.CWND)
|
||||
|
||||
closer, ipConn, err = masque.ConnectTunnel(ctx, quicConn, w.uri)
|
||||
|
||||
@@ -3,7 +3,6 @@ package outbound
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
|
||||
N "github.com/metacubex/mihomo/common/net"
|
||||
@@ -103,14 +102,8 @@ func NewTrustTunnel(option TrustTunnelOption) (*TrustTunnel, error) {
|
||||
outbound.dialer = option.NewDialer(outbound.DialOptions())
|
||||
|
||||
tOption := trusttunnel.ClientOptions{
|
||||
Dialer: outbound.dialer,
|
||||
ResolvUDP: func(ctx context.Context, server string) (netip.AddrPort, error) {
|
||||
udpAddr, err := resolveUDPAddr(ctx, "udp", server, option.IPVersion)
|
||||
if err != nil {
|
||||
return netip.AddrPort{}, err
|
||||
}
|
||||
return udpAddr.AddrPort(), nil
|
||||
},
|
||||
Dialer: outbound.dialer,
|
||||
DialOptions: outbound.DialOptions,
|
||||
Server: addr,
|
||||
Username: option.UserName,
|
||||
Password: option.Password,
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
tlsC "github.com/metacubex/mihomo/component/tls"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/transport/gun"
|
||||
"github.com/metacubex/mihomo/transport/tuic/common"
|
||||
"github.com/metacubex/mihomo/transport/vless"
|
||||
"github.com/metacubex/mihomo/transport/vless/encryption"
|
||||
"github.com/metacubex/mihomo/transport/vmess"
|
||||
@@ -588,26 +589,11 @@ func NewVless(option VlessOption) (*Vless, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
udpAddr, err := resolveUDPAddr(ctx, "udp", v.addr, v.prefer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = v.echConfig.ClientHandle(ctx, tlsConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
packetConn, err := v.dialer.ListenPacket(ctx, "udp", "", udpAddr.AddrPort())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
transport := quic.Transport{Conn: packetConn}
|
||||
transport.SetCreatedConn(true) // auto close conn
|
||||
transport.SetSingleUse(true) // auto close transport
|
||||
quicConn, err := transport.DialEarly(ctx, udpAddr, tlsConfig, cfg)
|
||||
if err != nil {
|
||||
_ = packetConn.Close()
|
||||
return nil, err
|
||||
}
|
||||
_, quicConn, err := common.DialQuicEarly(ctx, v.addr, v.DialOptions(), v.dialer, tlsConfig, cfg)
|
||||
return quicConn, nil
|
||||
},
|
||||
v.option.ALPN,
|
||||
@@ -743,26 +729,11 @@ func NewVless(option VlessOption) (*Vless, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
udpAddr, err := resolveUDPAddr(ctx, "udp", downloadAddr, v.prefer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = downloadEchConfig.ClientHandle(ctx, tlsConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
packetConn, err := v.dialer.ListenPacket(ctx, "udp", "", udpAddr.AddrPort())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
transport := quic.Transport{Conn: packetConn}
|
||||
transport.SetCreatedConn(true) // auto close conn
|
||||
transport.SetSingleUse(true) // auto close transport
|
||||
quicConn, err := transport.DialEarly(ctx, udpAddr, tlsConfig, cfg)
|
||||
if err != nil {
|
||||
_ = packetConn.Close()
|
||||
return nil, err
|
||||
}
|
||||
_, quicConn, err := common.DialQuicEarly(ctx, downloadAddr, v.DialOptions(), v.dialer, tlsConfig, cfg)
|
||||
return quicConn, nil
|
||||
},
|
||||
downloadALPN,
|
||||
|
||||
@@ -24,6 +24,12 @@ type NetDialer interface {
|
||||
DialContext(ctx context.Context, network, address string) (net.Conn, error)
|
||||
}
|
||||
|
||||
type NetDialerFunc func(ctx context.Context, network, address string) (net.Conn, error)
|
||||
|
||||
func (f NetDialerFunc) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
return f(ctx, network, address)
|
||||
}
|
||||
|
||||
type option struct {
|
||||
interfaceName string
|
||||
fallbackBind bool
|
||||
@@ -115,6 +121,14 @@ func WithOption(o option) Option {
|
||||
}
|
||||
}
|
||||
|
||||
func WithOptions(options ...Option) Option {
|
||||
return func(opt *option) {
|
||||
for _, o := range options {
|
||||
o(opt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func IsZeroOptions(opts []Option) bool {
|
||||
return applyOptions(opts...) == option{}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
@@ -14,6 +13,7 @@ import (
|
||||
|
||||
"github.com/metacubex/mihomo/common/httputils"
|
||||
"github.com/metacubex/mihomo/common/once"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/transport/vmess"
|
||||
|
||||
@@ -22,11 +22,11 @@ import (
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
type ResolvUDPFunc func(ctx context.Context, server string) (netip.AddrPort, error)
|
||||
type DialOptionsFunc func() []dialer.Option
|
||||
|
||||
type ClientOptions struct {
|
||||
Dialer C.Dialer
|
||||
ResolvUDP ResolvUDPFunc
|
||||
DialOptions DialOptionsFunc // for quic
|
||||
Server string
|
||||
Username string
|
||||
Password string
|
||||
@@ -43,7 +43,7 @@ type ClientOptions struct {
|
||||
type Client struct {
|
||||
ctx context.Context
|
||||
dialer C.Dialer
|
||||
resolv ResolvUDPFunc
|
||||
dialOptions DialOptionsFunc
|
||||
server string
|
||||
auth string
|
||||
roundTripper http.RoundTripper
|
||||
@@ -55,11 +55,11 @@ type Client struct {
|
||||
|
||||
func NewClient(ctx context.Context, options ClientOptions) (client *Client, err error) {
|
||||
client = &Client{
|
||||
ctx: ctx,
|
||||
dialer: options.Dialer,
|
||||
resolv: options.ResolvUDP,
|
||||
server: options.Server,
|
||||
auth: buildAuth(options.Username, options.Password),
|
||||
ctx: ctx,
|
||||
dialer: options.Dialer,
|
||||
dialOptions: options.DialOptions,
|
||||
server: options.Server,
|
||||
auth: buildAuth(options.Username, options.Password),
|
||||
}
|
||||
if options.QUIC {
|
||||
if len(options.TLSConfig.NextProtos) == 0 {
|
||||
|
||||
@@ -30,26 +30,14 @@ func (c *Client) quicRoundTripper(tlsConfig *vmess.TLSConfig, congestionControlN
|
||||
Allow0RTT: false,
|
||||
},
|
||||
Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (*quic.Conn, error) {
|
||||
addrPort, err := c.resolv(ctx, c.server)
|
||||
err := tlsConfig.ECH.ClientHandle(ctx, tlsCfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = tlsConfig.ECH.ClientHandle(ctx, tlsCfg)
|
||||
_, quicConn, err := common.DialQuicEarly(ctx, addr, c.dialOptions(), c.dialer, tlsCfg, cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
packetConn, err := c.dialer.ListenPacket(ctx, "udp", "", addrPort)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
transport := quic.Transport{Conn: packetConn}
|
||||
transport.SetCreatedConn(true) // auto close conn
|
||||
transport.SetSingleUse(true) // auto close transport
|
||||
quicConn, err := transport.DialEarly(ctx, net.UDPAddrFromAddrPort(addrPort), tlsCfg, cfg)
|
||||
if err != nil {
|
||||
_ = packetConn.Close()
|
||||
return nil, err
|
||||
}
|
||||
common.SetCongestionController(quicConn, congestionControlName, cwnd)
|
||||
return quicConn, nil
|
||||
},
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
|
||||
"github.com/metacubex/quic-go"
|
||||
"github.com/metacubex/tls"
|
||||
)
|
||||
|
||||
// DialQuicEarly dials a new connection, attempting to use 0-RTT if possible.
|
||||
func DialQuicEarly(ctx context.Context, address string, opts []dialer.Option, dialer C.Dialer, tlsConf *tls.Config, conf *quic.Config) (net.PacketConn, *quic.Conn, error) {
|
||||
return dialQuic(ctx, address, opts, dialer, tlsConf, conf, true)
|
||||
}
|
||||
|
||||
// DialQuic dials a new connection to a remote host (not using 0-RTT).
|
||||
func DialQuic(ctx context.Context, address string, opts []dialer.Option, dialer C.Dialer, tlsConf *tls.Config, conf *quic.Config) (net.PacketConn, *quic.Conn, error) {
|
||||
return dialQuic(ctx, address, opts, dialer, tlsConf, conf, false)
|
||||
}
|
||||
|
||||
func dialQuic(ctx context.Context, address string, opts []dialer.Option, cDialer C.Dialer, tlsConf *tls.Config, conf *quic.Config, early bool) (net.PacketConn, *quic.Conn, error) {
|
||||
d := dialer.NewDialer(
|
||||
dialer.WithOptions(opts...),
|
||||
dialer.WithNetDialer(dialer.NetDialerFunc(func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
addrPort, err := netip.ParseAddrPort(address) // the dialer will resolve the domain to ip
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
udpAddr := net.UDPAddrFromAddrPort(addrPort)
|
||||
packetConn, err := cDialer.ListenPacket(ctx, "udp", "", udpAddr.AddrPort())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
transport := quic.Transport{Conn: packetConn}
|
||||
transport.SetCreatedConn(true) // auto close conn
|
||||
transport.SetSingleUse(true) // auto close transport
|
||||
|
||||
var quicConn *quic.Conn
|
||||
if early {
|
||||
quicConn, err = transport.DialEarly(ctx, udpAddr, tlsConf, conf)
|
||||
} else {
|
||||
quicConn, err = transport.Dial(ctx, udpAddr, tlsConf, conf)
|
||||
}
|
||||
if err != nil {
|
||||
_ = packetConn.Close()
|
||||
return nil, err
|
||||
}
|
||||
return quicNetConn{Conn: quicConn, pc: packetConn}, nil
|
||||
})),
|
||||
)
|
||||
c, err := d.DialContext(ctx, "udp", address)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
nc := c.(quicNetConn)
|
||||
return nc.pc, nc.Conn, nil
|
||||
}
|
||||
|
||||
type quicNetConn struct {
|
||||
*quic.Conn
|
||||
pc net.PacketConn
|
||||
}
|
||||
|
||||
func (q quicNetConn) Close() error {
|
||||
err := q.Conn.CloseWithError(0, "")
|
||||
_ = q.pc.Close() // always close the packetConn
|
||||
return err
|
||||
}
|
||||
|
||||
func (q quicNetConn) Read(b []byte) (n int, err error) {
|
||||
panic("should not call Read on quicNetConn")
|
||||
}
|
||||
|
||||
func (q quicNetConn) Write(b []byte) (n int, err error) {
|
||||
panic("should not call Write on quicNetConn")
|
||||
}
|
||||
|
||||
func (q quicNetConn) SetDeadline(t time.Time) error {
|
||||
panic("should not call SetDeadline on quicNetConn")
|
||||
}
|
||||
|
||||
func (q quicNetConn) SetReadDeadline(t time.Time) error {
|
||||
panic("should not call SetReadDeadline on quicNetConn")
|
||||
}
|
||||
|
||||
func (q quicNetConn) SetWriteDeadline(t time.Time) error {
|
||||
panic("should not call SetWriteDeadline on quicNetConn")
|
||||
}
|
||||
|
||||
var _ net.Conn = quicNetConn{}
|
||||
Reference in New Issue
Block a user