Skip to content

Commit 1b2ea14

Browse files
committed
feat: support ipv6 dual stack fallback for masque/trusttunnel/xhttp h3 mode
1 parent 7b73775 commit 1b2ea14

7 files changed

Lines changed: 127 additions & 81 deletions

File tree

adapter/outbound/masque.go

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -257,26 +257,11 @@ func (w *Masque) run(ctx context.Context) error {
257257
return err
258258
}
259259
} else {
260-
var udpAddr *net.UDPAddr
261-
udpAddr, err = resolveUDPAddr(ctx, "udp", w.addr, w.prefer)
260+
var quicConn *quic.Conn
261+
pc, quicConn, err = common.DialQuic(ctx, w.addr, w.DialOptions(), w.dialer, w.tlsConfig, w.quicConfig)
262262
if err != nil {
263263
return err
264264
}
265-
266-
pc, err = w.dialer.ListenPacket(ctx, "udp", "", udpAddr.AddrPort())
267-
if err != nil {
268-
return err
269-
}
270-
271-
transport := quic.Transport{Conn: pc}
272-
transport.SetCreatedConn(true) // auto close conn
273-
transport.SetSingleUse(true) // auto close transport
274-
quicConn, err := transport.Dial(ctx, udpAddr, w.tlsConfig, w.quicConfig)
275-
if err != nil {
276-
_ = pc.Close()
277-
return err
278-
}
279-
280265
common.SetCongestionController(quicConn, w.option.CongestionController, w.option.CWND)
281266

282267
closer, ipConn, err = masque.ConnectTunnel(ctx, quicConn, w.uri)

adapter/outbound/trusttunnel.go

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package outbound
33
import (
44
"context"
55
"net"
6-
"net/netip"
76
"strconv"
87

98
N "github.com/metacubex/mihomo/common/net"
@@ -103,14 +102,8 @@ func NewTrustTunnel(option TrustTunnelOption) (*TrustTunnel, error) {
103102
outbound.dialer = option.NewDialer(outbound.DialOptions())
104103

105104
tOption := trusttunnel.ClientOptions{
106-
Dialer: outbound.dialer,
107-
ResolvUDP: func(ctx context.Context, server string) (netip.AddrPort, error) {
108-
udpAddr, err := resolveUDPAddr(ctx, "udp", server, option.IPVersion)
109-
if err != nil {
110-
return netip.AddrPort{}, err
111-
}
112-
return udpAddr.AddrPort(), nil
113-
},
105+
Dialer: outbound.dialer,
106+
DialOptions: outbound.DialOptions,
114107
Server: addr,
115108
Username: option.UserName,
116109
Password: option.Password,

adapter/outbound/vless.go

Lines changed: 3 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
tlsC "github.com/metacubex/mihomo/component/tls"
1717
C "github.com/metacubex/mihomo/constant"
1818
"github.com/metacubex/mihomo/transport/gun"
19+
"github.com/metacubex/mihomo/transport/tuic/common"
1920
"github.com/metacubex/mihomo/transport/vless"
2021
"github.com/metacubex/mihomo/transport/vless/encryption"
2122
"github.com/metacubex/mihomo/transport/vmess"
@@ -588,26 +589,11 @@ func NewVless(option VlessOption) (*Vless, error) {
588589
return nil, err
589590
}
590591

591-
udpAddr, err := resolveUDPAddr(ctx, "udp", v.addr, v.prefer)
592-
if err != nil {
593-
return nil, err
594-
}
595592
err = v.echConfig.ClientHandle(ctx, tlsConfig)
596593
if err != nil {
597594
return nil, err
598595
}
599-
packetConn, err := v.dialer.ListenPacket(ctx, "udp", "", udpAddr.AddrPort())
600-
if err != nil {
601-
return nil, err
602-
}
603-
transport := quic.Transport{Conn: packetConn}
604-
transport.SetCreatedConn(true) // auto close conn
605-
transport.SetSingleUse(true) // auto close transport
606-
quicConn, err := transport.DialEarly(ctx, udpAddr, tlsConfig, cfg)
607-
if err != nil {
608-
_ = packetConn.Close()
609-
return nil, err
610-
}
596+
_, quicConn, err := common.DialQuicEarly(ctx, v.addr, v.DialOptions(), v.dialer, tlsConfig, cfg)
611597
return quicConn, nil
612598
},
613599
v.option.ALPN,
@@ -743,26 +729,11 @@ func NewVless(option VlessOption) (*Vless, error) {
743729
return nil, err
744730
}
745731

746-
udpAddr, err := resolveUDPAddr(ctx, "udp", downloadAddr, v.prefer)
747-
if err != nil {
748-
return nil, err
749-
}
750732
err = downloadEchConfig.ClientHandle(ctx, tlsConfig)
751733
if err != nil {
752734
return nil, err
753735
}
754-
packetConn, err := v.dialer.ListenPacket(ctx, "udp", "", udpAddr.AddrPort())
755-
if err != nil {
756-
return nil, err
757-
}
758-
transport := quic.Transport{Conn: packetConn}
759-
transport.SetCreatedConn(true) // auto close conn
760-
transport.SetSingleUse(true) // auto close transport
761-
quicConn, err := transport.DialEarly(ctx, udpAddr, tlsConfig, cfg)
762-
if err != nil {
763-
_ = packetConn.Close()
764-
return nil, err
765-
}
736+
_, quicConn, err := common.DialQuicEarly(ctx, downloadAddr, v.DialOptions(), v.dialer, tlsConfig, cfg)
766737
return quicConn, nil
767738
},
768739
downloadALPN,

component/dialer/options.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ type NetDialer interface {
2424
DialContext(ctx context.Context, network, address string) (net.Conn, error)
2525
}
2626

27+
type NetDialerFunc func(ctx context.Context, network, address string) (net.Conn, error)
28+
29+
func (f NetDialerFunc) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
30+
return f(ctx, network, address)
31+
}
32+
2733
type option struct {
2834
interfaceName string
2935
fallbackBind bool
@@ -115,6 +121,14 @@ func WithOption(o option) Option {
115121
}
116122
}
117123

124+
func WithOptions(options ...Option) Option {
125+
return func(opt *option) {
126+
for _, o := range options {
127+
o(opt)
128+
}
129+
}
130+
}
131+
118132
func IsZeroOptions(opts []Option) bool {
119133
return applyOptions(opts...) == option{}
120134
}

transport/trusttunnel/client.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@ import (
66
"fmt"
77
"io"
88
"net"
9-
"net/netip"
109
"net/url"
1110
"sync"
1211
"sync/atomic"
1312
"time"
1413

1514
"github.com/metacubex/mihomo/common/httputils"
1615
"github.com/metacubex/mihomo/common/once"
16+
"github.com/metacubex/mihomo/component/dialer"
1717
C "github.com/metacubex/mihomo/constant"
1818
"github.com/metacubex/mihomo/transport/vmess"
1919

@@ -22,11 +22,11 @@ import (
2222
"golang.org/x/exp/slices"
2323
)
2424

25-
type ResolvUDPFunc func(ctx context.Context, server string) (netip.AddrPort, error)
25+
type DialOptionsFunc func() []dialer.Option
2626

2727
type ClientOptions struct {
2828
Dialer C.Dialer
29-
ResolvUDP ResolvUDPFunc
29+
DialOptions DialOptionsFunc // for quic
3030
Server string
3131
Username string
3232
Password string
@@ -43,7 +43,7 @@ type ClientOptions struct {
4343
type Client struct {
4444
ctx context.Context
4545
dialer C.Dialer
46-
resolv ResolvUDPFunc
46+
dialOptions DialOptionsFunc
4747
server string
4848
auth string
4949
roundTripper http.RoundTripper
@@ -55,11 +55,11 @@ type Client struct {
5555

5656
func NewClient(ctx context.Context, options ClientOptions) (client *Client, err error) {
5757
client = &Client{
58-
ctx: ctx,
59-
dialer: options.Dialer,
60-
resolv: options.ResolvUDP,
61-
server: options.Server,
62-
auth: buildAuth(options.Username, options.Password),
58+
ctx: ctx,
59+
dialer: options.Dialer,
60+
dialOptions: options.DialOptions,
61+
server: options.Server,
62+
auth: buildAuth(options.Username, options.Password),
6363
}
6464
if options.QUIC {
6565
if len(options.TLSConfig.NextProtos) == 0 {

transport/trusttunnel/quic.go

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -30,26 +30,14 @@ func (c *Client) quicRoundTripper(tlsConfig *vmess.TLSConfig, congestionControlN
3030
Allow0RTT: false,
3131
},
3232
Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (*quic.Conn, error) {
33-
addrPort, err := c.resolv(ctx, c.server)
33+
err := tlsConfig.ECH.ClientHandle(ctx, tlsCfg)
3434
if err != nil {
3535
return nil, err
3636
}
37-
err = tlsConfig.ECH.ClientHandle(ctx, tlsCfg)
37+
_, quicConn, err := common.DialQuicEarly(ctx, addr, c.dialOptions(), c.dialer, tlsCfg, cfg)
3838
if err != nil {
3939
return nil, err
4040
}
41-
packetConn, err := c.dialer.ListenPacket(ctx, "udp", "", addrPort)
42-
if err != nil {
43-
return nil, err
44-
}
45-
transport := quic.Transport{Conn: packetConn}
46-
transport.SetCreatedConn(true) // auto close conn
47-
transport.SetSingleUse(true) // auto close transport
48-
quicConn, err := transport.DialEarly(ctx, net.UDPAddrFromAddrPort(addrPort), tlsCfg, cfg)
49-
if err != nil {
50-
_ = packetConn.Close()
51-
return nil, err
52-
}
5341
common.SetCongestionController(quicConn, congestionControlName, cwnd)
5442
return quicConn, nil
5543
},

transport/tuic/common/dial.go

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package common
2+
3+
import (
4+
"context"
5+
"net"
6+
"net/netip"
7+
"time"
8+
9+
"github.com/metacubex/mihomo/component/dialer"
10+
C "github.com/metacubex/mihomo/constant"
11+
12+
"github.com/metacubex/quic-go"
13+
"github.com/metacubex/tls"
14+
)
15+
16+
// DialQuicEarly dials a new connection, attempting to use 0-RTT if possible.
17+
func DialQuicEarly(ctx context.Context, address string, opts []dialer.Option, dialer C.Dialer, tlsConf *tls.Config, conf *quic.Config) (net.PacketConn, *quic.Conn, error) {
18+
return dialQuic(ctx, address, opts, dialer, tlsConf, conf, true)
19+
}
20+
21+
// DialQuic dials a new connection to a remote host (not using 0-RTT).
22+
func DialQuic(ctx context.Context, address string, opts []dialer.Option, dialer C.Dialer, tlsConf *tls.Config, conf *quic.Config) (net.PacketConn, *quic.Conn, error) {
23+
return dialQuic(ctx, address, opts, dialer, tlsConf, conf, false)
24+
}
25+
26+
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) {
27+
d := dialer.NewDialer(
28+
dialer.WithOptions(opts...),
29+
dialer.WithNetDialer(dialer.NetDialerFunc(func(ctx context.Context, network, address string) (net.Conn, error) {
30+
addrPort, err := netip.ParseAddrPort(address) // the dialer will resolve the domain to ip
31+
if err != nil {
32+
return nil, err
33+
}
34+
udpAddr := net.UDPAddrFromAddrPort(addrPort)
35+
packetConn, err := cDialer.ListenPacket(ctx, "udp", "", udpAddr.AddrPort())
36+
if err != nil {
37+
return nil, err
38+
}
39+
transport := quic.Transport{Conn: packetConn}
40+
transport.SetCreatedConn(true) // auto close conn
41+
transport.SetSingleUse(true) // auto close transport
42+
43+
var quicConn *quic.Conn
44+
if early {
45+
quicConn, err = transport.DialEarly(ctx, udpAddr, tlsConf, conf)
46+
} else {
47+
quicConn, err = transport.Dial(ctx, udpAddr, tlsConf, conf)
48+
}
49+
if err != nil {
50+
_ = packetConn.Close()
51+
return nil, err
52+
}
53+
return quicNetConn{Conn: quicConn, pc: packetConn}, nil
54+
})),
55+
)
56+
c, err := d.DialContext(ctx, "udp", address)
57+
if err != nil {
58+
return nil, nil, err
59+
}
60+
nc := c.(quicNetConn)
61+
return nc.pc, nc.Conn, nil
62+
}
63+
64+
type quicNetConn struct {
65+
*quic.Conn
66+
pc net.PacketConn
67+
}
68+
69+
func (q quicNetConn) Close() error {
70+
err := q.Conn.CloseWithError(0, "")
71+
_ = q.pc.Close() // always close the packetConn
72+
return err
73+
}
74+
75+
func (q quicNetConn) Read(b []byte) (n int, err error) {
76+
panic("should not call Read on quicNetConn")
77+
}
78+
79+
func (q quicNetConn) Write(b []byte) (n int, err error) {
80+
panic("should not call Write on quicNetConn")
81+
}
82+
83+
func (q quicNetConn) SetDeadline(t time.Time) error {
84+
panic("should not call SetDeadline on quicNetConn")
85+
}
86+
87+
func (q quicNetConn) SetReadDeadline(t time.Time) error {
88+
panic("should not call SetReadDeadline on quicNetConn")
89+
}
90+
91+
func (q quicNetConn) SetWriteDeadline(t time.Time) error {
92+
panic("should not call SetWriteDeadline on quicNetConn")
93+
}
94+
95+
var _ net.Conn = quicNetConn{}

0 commit comments

Comments
 (0)