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