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  }