github.com/inazumav/sing-box@v0.0.0-20230926072359-ab51429a14f1/outbound/tuic.go (about)

     1  //go:build with_quic
     2  
     3  package outbound
     4  
     5  import (
     6  	"context"
     7  	"net"
     8  	"os"
     9  	"time"
    10  
    11  	"github.com/inazumav/sing-box/adapter"
    12  	"github.com/inazumav/sing-box/common/dialer"
    13  	"github.com/inazumav/sing-box/common/tls"
    14  	C "github.com/inazumav/sing-box/constant"
    15  	"github.com/inazumav/sing-box/log"
    16  	"github.com/inazumav/sing-box/option"
    17  	"github.com/inazumav/sing-box/transport/tuic"
    18  	"github.com/sagernet/sing/common"
    19  	"github.com/sagernet/sing/common/bufio"
    20  	E "github.com/sagernet/sing/common/exceptions"
    21  	M "github.com/sagernet/sing/common/metadata"
    22  	N "github.com/sagernet/sing/common/network"
    23  	"github.com/sagernet/sing/common/uot"
    24  
    25  	"github.com/gofrs/uuid/v5"
    26  )
    27  
    28  var (
    29  	_ adapter.Outbound                = (*TUIC)(nil)
    30  	_ adapter.InterfaceUpdateListener = (*TUIC)(nil)
    31  )
    32  
    33  type TUIC struct {
    34  	myOutboundAdapter
    35  	client    *tuic.Client
    36  	udpStream bool
    37  }
    38  
    39  func NewTUIC(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TUICOutboundOptions) (*TUIC, error) {
    40  	options.UDPFragmentDefault = true
    41  	if options.TLS == nil || !options.TLS.Enabled {
    42  		return nil, C.ErrTLSRequired
    43  	}
    44  	tlsConfig, err := tls.NewClient(ctx, options.Server, common.PtrValueOrDefault(options.TLS))
    45  	if err != nil {
    46  		return nil, err
    47  	}
    48  	userUUID, err := uuid.FromString(options.UUID)
    49  	if err != nil {
    50  		return nil, E.Cause(err, "invalid uuid")
    51  	}
    52  	var tuicUDPStream bool
    53  	if options.UDPOverStream && options.UDPRelayMode != "" {
    54  		return nil, E.New("udp_over_stream is conflict with udp_relay_mode")
    55  	}
    56  	switch options.UDPRelayMode {
    57  	case "native":
    58  	case "quic":
    59  		tuicUDPStream = true
    60  	}
    61  	outboundDialer, err := dialer.New(router, options.DialerOptions)
    62  	if err != nil {
    63  		return nil, err
    64  	}
    65  	client, err := tuic.NewClient(tuic.ClientOptions{
    66  		Context:           ctx,
    67  		Dialer:            outboundDialer,
    68  		ServerAddress:     options.ServerOptions.Build(),
    69  		TLSConfig:         tlsConfig,
    70  		UUID:              userUUID,
    71  		Password:          options.Password,
    72  		CongestionControl: options.CongestionControl,
    73  		UDPStream:         tuicUDPStream,
    74  		ZeroRTTHandshake:  options.ZeroRTTHandshake,
    75  		Heartbeat:         time.Duration(options.Heartbeat),
    76  	})
    77  	if err != nil {
    78  		return nil, err
    79  	}
    80  	return &TUIC{
    81  		myOutboundAdapter: myOutboundAdapter{
    82  			protocol:     C.TypeTUIC,
    83  			network:      options.Network.Build(),
    84  			router:       router,
    85  			logger:       logger,
    86  			tag:          tag,
    87  			dependencies: withDialerDependency(options.DialerOptions),
    88  		},
    89  		client:    client,
    90  		udpStream: options.UDPOverStream,
    91  	}, nil
    92  }
    93  
    94  func (h *TUIC) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
    95  	switch N.NetworkName(network) {
    96  	case N.NetworkTCP:
    97  		h.logger.InfoContext(ctx, "outbound connection to ", destination)
    98  		return h.client.DialConn(ctx, destination)
    99  	case N.NetworkUDP:
   100  		if h.udpStream {
   101  			h.logger.InfoContext(ctx, "outbound stream packet connection to ", destination)
   102  			streamConn, err := h.client.DialConn(ctx, uot.RequestDestination(uot.Version))
   103  			if err != nil {
   104  				return nil, err
   105  			}
   106  			return uot.NewLazyConn(streamConn, uot.Request{
   107  				IsConnect:   true,
   108  				Destination: destination,
   109  			}), nil
   110  		} else {
   111  			conn, err := h.ListenPacket(ctx, destination)
   112  			if err != nil {
   113  				return nil, err
   114  			}
   115  			return bufio.NewBindPacketConn(conn, destination), nil
   116  		}
   117  	default:
   118  		return nil, E.New("unsupported network: ", network)
   119  	}
   120  }
   121  
   122  func (h *TUIC) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
   123  	if h.udpStream {
   124  		h.logger.InfoContext(ctx, "outbound stream packet connection to ", destination)
   125  		streamConn, err := h.client.DialConn(ctx, uot.RequestDestination(uot.Version))
   126  		if err != nil {
   127  			return nil, err
   128  		}
   129  		return uot.NewLazyConn(streamConn, uot.Request{
   130  			IsConnect:   false,
   131  			Destination: destination,
   132  		}), nil
   133  	} else {
   134  		h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
   135  		return h.client.ListenPacket(ctx)
   136  	}
   137  }
   138  
   139  func (h *TUIC) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
   140  	return NewConnection(ctx, h, conn, metadata)
   141  }
   142  
   143  func (h *TUIC) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
   144  	return NewPacketConnection(ctx, h, conn, metadata)
   145  }
   146  
   147  func (h *TUIC) InterfaceUpdated() {
   148  	_ = h.client.CloseWithError(E.New("network changed"))
   149  }
   150  
   151  func (h *TUIC) Close() error {
   152  	return h.client.CloseWithError(os.ErrClosed)
   153  }