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  }