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  }