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