github.com/sagernet/sing-box@v1.9.0-rc.20/outbound/hysteria.go (about)

     1  //go:build with_quic
     2  
     3  package outbound
     4  
     5  import (
     6  	"context"
     7  	"net"
     8  	"os"
     9  
    10  	"github.com/sagernet/sing-box/adapter"
    11  	"github.com/sagernet/sing-box/common/dialer"
    12  	"github.com/sagernet/sing-box/common/humanize"
    13  	"github.com/sagernet/sing-box/common/tls"
    14  	C "github.com/sagernet/sing-box/constant"
    15  	"github.com/sagernet/sing-box/log"
    16  	"github.com/sagernet/sing-box/option"
    17  	"github.com/sagernet/sing-quic/hysteria"
    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  )
    24  
    25  var (
    26  	_ adapter.Outbound                = (*TUIC)(nil)
    27  	_ adapter.InterfaceUpdateListener = (*TUIC)(nil)
    28  )
    29  
    30  type Hysteria struct {
    31  	myOutboundAdapter
    32  	client *hysteria.Client
    33  }
    34  
    35  func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaOutboundOptions) (*Hysteria, error) {
    36  	options.UDPFragmentDefault = true
    37  	if options.TLS == nil || !options.TLS.Enabled {
    38  		return nil, C.ErrTLSRequired
    39  	}
    40  	tlsConfig, err := tls.NewClient(ctx, options.Server, common.PtrValueOrDefault(options.TLS))
    41  	if err != nil {
    42  		return nil, err
    43  	}
    44  	outboundDialer, err := dialer.New(router, options.DialerOptions)
    45  	if err != nil {
    46  		return nil, err
    47  	}
    48  	networkList := options.Network.Build()
    49  	var password string
    50  	if options.AuthString != "" {
    51  		password = options.AuthString
    52  	} else {
    53  		password = string(options.Auth)
    54  	}
    55  	var sendBps, receiveBps uint64
    56  	if len(options.Up) > 0 {
    57  		sendBps, err = humanize.ParseBytes(options.Up)
    58  		if err != nil {
    59  			return nil, E.Cause(err, "invalid up speed format: ", options.Up)
    60  		}
    61  	} else {
    62  		sendBps = uint64(options.UpMbps) * hysteria.MbpsToBps
    63  	}
    64  	if len(options.Down) > 0 {
    65  		receiveBps, err = humanize.ParseBytes(options.Down)
    66  		if receiveBps == 0 {
    67  			return nil, E.New("invalid down speed format: ", options.Down)
    68  		}
    69  	} else {
    70  		receiveBps = uint64(options.DownMbps) * hysteria.MbpsToBps
    71  	}
    72  	client, err := hysteria.NewClient(hysteria.ClientOptions{
    73  		Context:       ctx,
    74  		Dialer:        outboundDialer,
    75  		Logger:        logger,
    76  		ServerAddress: options.ServerOptions.Build(),
    77  		SendBPS:       sendBps,
    78  		ReceiveBPS:    receiveBps,
    79  		XPlusPassword: options.Obfs,
    80  		Password:      password,
    81  		TLSConfig:     tlsConfig,
    82  		UDPDisabled:   !common.Contains(networkList, N.NetworkUDP),
    83  
    84  		ConnReceiveWindow:   options.ReceiveWindowConn,
    85  		StreamReceiveWindow: options.ReceiveWindow,
    86  		DisableMTUDiscovery: options.DisableMTUDiscovery,
    87  	})
    88  	if err != nil {
    89  		return nil, err
    90  	}
    91  	return &Hysteria{
    92  		myOutboundAdapter: myOutboundAdapter{
    93  			protocol:     C.TypeHysteria,
    94  			network:      networkList,
    95  			router:       router,
    96  			logger:       logger,
    97  			tag:          tag,
    98  			dependencies: withDialerDependency(options.DialerOptions),
    99  		},
   100  		client: client,
   101  	}, nil
   102  }
   103  
   104  func (h *Hysteria) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
   105  	switch N.NetworkName(network) {
   106  	case N.NetworkTCP:
   107  		h.logger.InfoContext(ctx, "outbound connection to ", destination)
   108  		return h.client.DialConn(ctx, destination)
   109  	case N.NetworkUDP:
   110  		conn, err := h.ListenPacket(ctx, destination)
   111  		if err != nil {
   112  			return nil, err
   113  		}
   114  		return bufio.NewBindPacketConn(conn, destination), nil
   115  	default:
   116  		return nil, E.New("unsupported network: ", network)
   117  	}
   118  }
   119  
   120  func (h *Hysteria) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
   121  	h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
   122  	return h.client.ListenPacket(ctx, destination)
   123  }
   124  
   125  func (h *Hysteria) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
   126  	return NewConnection(ctx, h, conn, metadata)
   127  }
   128  
   129  func (h *Hysteria) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
   130  	return NewPacketConnection(ctx, h, conn, metadata)
   131  }
   132  
   133  func (h *Hysteria) InterfaceUpdated() error {
   134  	return h.client.CloseWithError(E.New("network changed"))
   135  }
   136  
   137  func (h *Hysteria) Close() error {
   138  	return h.client.CloseWithError(os.ErrClosed)
   139  }