github.com/sagernet/sing-box@v1.9.0-rc.20/transport/v2rayquic/client.go (about) 1 //go:build with_quic 2 3 package v2rayquic 4 5 import ( 6 "context" 7 "net" 8 "sync" 9 10 "github.com/sagernet/quic-go" 11 "github.com/sagernet/sing-box/adapter" 12 "github.com/sagernet/sing-box/common/tls" 13 C "github.com/sagernet/sing-box/constant" 14 "github.com/sagernet/sing-box/option" 15 "github.com/sagernet/sing-quic" 16 "github.com/sagernet/sing/common" 17 "github.com/sagernet/sing/common/bufio" 18 M "github.com/sagernet/sing/common/metadata" 19 N "github.com/sagernet/sing/common/network" 20 ) 21 22 var _ adapter.V2RayClientTransport = (*Client)(nil) 23 24 type Client struct { 25 ctx context.Context 26 dialer N.Dialer 27 serverAddr M.Socksaddr 28 tlsConfig tls.Config 29 quicConfig *quic.Config 30 connAccess sync.Mutex 31 conn quic.Connection 32 rawConn net.Conn 33 } 34 35 func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayQUICOptions, tlsConfig tls.Config) (adapter.V2RayClientTransport, error) { 36 quicConfig := &quic.Config{ 37 DisablePathMTUDiscovery: !C.IsLinux && !C.IsWindows, 38 } 39 if len(tlsConfig.NextProtos()) == 0 { 40 tlsConfig.SetNextProtos([]string{"h2", "http/1.1"}) 41 } 42 return &Client{ 43 ctx: ctx, 44 dialer: dialer, 45 serverAddr: serverAddr, 46 tlsConfig: tlsConfig, 47 quicConfig: quicConfig, 48 }, nil 49 } 50 51 func (c *Client) offer() (quic.Connection, error) { 52 conn := c.conn 53 if conn != nil && !common.Done(conn.Context()) { 54 return conn, nil 55 } 56 c.connAccess.Lock() 57 defer c.connAccess.Unlock() 58 conn = c.conn 59 if conn != nil && !common.Done(conn.Context()) { 60 return conn, nil 61 } 62 conn, err := c.offerNew() 63 if err != nil { 64 return nil, err 65 } 66 return conn, nil 67 } 68 69 func (c *Client) offerNew() (quic.Connection, error) { 70 udpConn, err := c.dialer.DialContext(c.ctx, "udp", c.serverAddr) 71 if err != nil { 72 return nil, err 73 } 74 var packetConn net.PacketConn 75 packetConn = bufio.NewUnbindPacketConn(udpConn) 76 quicConn, err := qtls.Dial(c.ctx, packetConn, udpConn.RemoteAddr(), c.tlsConfig, c.quicConfig) 77 if err != nil { 78 packetConn.Close() 79 return nil, err 80 } 81 c.conn = quicConn 82 c.rawConn = udpConn 83 return quicConn, nil 84 } 85 86 func (c *Client) DialContext(ctx context.Context) (net.Conn, error) { 87 conn, err := c.offer() 88 if err != nil { 89 return nil, err 90 } 91 stream, err := conn.OpenStream() 92 if err != nil { 93 return nil, err 94 } 95 return &StreamWrapper{Conn: conn, Stream: stream}, nil 96 } 97 98 func (c *Client) Close() error { 99 return common.Close(c.conn, c.rawConn) 100 }