github.com/metacubex/mihomo@v1.18.5/adapter/outbound/hysteria.go (about)

     1  package outbound
     2  
     3  import (
     4  	"context"
     5  	"crypto/tls"
     6  	"encoding/base64"
     7  	"fmt"
     8  	"net"
     9  	"net/netip"
    10  	"strconv"
    11  	"time"
    12  
    13  	"github.com/metacubex/quic-go"
    14  	"github.com/metacubex/quic-go/congestion"
    15  	M "github.com/sagernet/sing/common/metadata"
    16  
    17  	"github.com/metacubex/mihomo/component/ca"
    18  	"github.com/metacubex/mihomo/component/dialer"
    19  	"github.com/metacubex/mihomo/component/proxydialer"
    20  	C "github.com/metacubex/mihomo/constant"
    21  	"github.com/metacubex/mihomo/log"
    22  	hyCongestion "github.com/metacubex/mihomo/transport/hysteria/congestion"
    23  	"github.com/metacubex/mihomo/transport/hysteria/core"
    24  	"github.com/metacubex/mihomo/transport/hysteria/obfs"
    25  	"github.com/metacubex/mihomo/transport/hysteria/pmtud_fix"
    26  	"github.com/metacubex/mihomo/transport/hysteria/transport"
    27  	"github.com/metacubex/mihomo/transport/hysteria/utils"
    28  )
    29  
    30  const (
    31  	mbpsToBps = 125000
    32  
    33  	DefaultStreamReceiveWindow     = 15728640 // 15 MB/s
    34  	DefaultConnectionReceiveWindow = 67108864 // 64 MB/s
    35  
    36  	DefaultALPN        = "hysteria"
    37  	DefaultProtocol    = "udp"
    38  	DefaultHopInterval = 10
    39  )
    40  
    41  type Hysteria struct {
    42  	*Base
    43  
    44  	option *HysteriaOption
    45  	client *core.Client
    46  }
    47  
    48  func (h *Hysteria) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
    49  	tcpConn, err := h.client.DialTCP(metadata.String(), metadata.DstPort, h.genHdc(ctx, opts...))
    50  	if err != nil {
    51  		return nil, err
    52  	}
    53  
    54  	return NewConn(tcpConn, h), nil
    55  }
    56  
    57  func (h *Hysteria) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
    58  	udpConn, err := h.client.DialUDP(h.genHdc(ctx, opts...))
    59  	if err != nil {
    60  		return nil, err
    61  	}
    62  	return newPacketConn(&hyPacketConn{udpConn}, h), nil
    63  }
    64  
    65  func (h *Hysteria) genHdc(ctx context.Context, opts ...dialer.Option) utils.PacketDialer {
    66  	return &hyDialerWithContext{
    67  		ctx: context.Background(),
    68  		hyDialer: func(network string) (net.PacketConn, error) {
    69  			var err error
    70  			var cDialer C.Dialer = dialer.NewDialer(h.Base.DialOptions(opts...)...)
    71  			if len(h.option.DialerProxy) > 0 {
    72  				cDialer, err = proxydialer.NewByName(h.option.DialerProxy, cDialer)
    73  				if err != nil {
    74  					return nil, err
    75  				}
    76  			}
    77  			rAddrPort, _ := netip.ParseAddrPort(h.Addr())
    78  			return cDialer.ListenPacket(ctx, network, "", rAddrPort)
    79  		},
    80  		remoteAddr: func(addr string) (net.Addr, error) {
    81  			return resolveUDPAddrWithPrefer(ctx, "udp", addr, h.prefer)
    82  		},
    83  	}
    84  }
    85  
    86  type HysteriaOption struct {
    87  	BasicOption
    88  	Name                string   `proxy:"name"`
    89  	Server              string   `proxy:"server"`
    90  	Port                int      `proxy:"port,omitempty"`
    91  	Ports               string   `proxy:"ports,omitempty"`
    92  	Protocol            string   `proxy:"protocol,omitempty"`
    93  	ObfsProtocol        string   `proxy:"obfs-protocol,omitempty"` // compatible with Stash
    94  	Up                  string   `proxy:"up"`
    95  	UpSpeed             int      `proxy:"up-speed,omitempty"` // compatible with Stash
    96  	Down                string   `proxy:"down"`
    97  	DownSpeed           int      `proxy:"down-speed,omitempty"` // compatible with Stash
    98  	Auth                string   `proxy:"auth,omitempty"`
    99  	AuthString          string   `proxy:"auth-str,omitempty"`
   100  	Obfs                string   `proxy:"obfs,omitempty"`
   101  	SNI                 string   `proxy:"sni,omitempty"`
   102  	SkipCertVerify      bool     `proxy:"skip-cert-verify,omitempty"`
   103  	Fingerprint         string   `proxy:"fingerprint,omitempty"`
   104  	ALPN                []string `proxy:"alpn,omitempty"`
   105  	CustomCA            string   `proxy:"ca,omitempty"`
   106  	CustomCAString      string   `proxy:"ca-str,omitempty"`
   107  	ReceiveWindowConn   int      `proxy:"recv-window-conn,omitempty"`
   108  	ReceiveWindow       int      `proxy:"recv-window,omitempty"`
   109  	DisableMTUDiscovery bool     `proxy:"disable-mtu-discovery,omitempty"`
   110  	FastOpen            bool     `proxy:"fast-open,omitempty"`
   111  	HopInterval         int      `proxy:"hop-interval,omitempty"`
   112  }
   113  
   114  func (c *HysteriaOption) Speed() (uint64, uint64, error) {
   115  	var up, down uint64
   116  	up = StringToBps(c.Up)
   117  	if up == 0 {
   118  		return 0, 0, fmt.Errorf("invaild upload speed: %s", c.Up)
   119  	}
   120  
   121  	down = StringToBps(c.Down)
   122  	if down == 0 {
   123  		return 0, 0, fmt.Errorf("invaild download speed: %s", c.Down)
   124  	}
   125  
   126  	return up, down, nil
   127  }
   128  
   129  func NewHysteria(option HysteriaOption) (*Hysteria, error) {
   130  	clientTransport := &transport.ClientTransport{
   131  		Dialer: &net.Dialer{
   132  			Timeout: 8 * time.Second,
   133  		},
   134  	}
   135  	addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
   136  	ports := option.Ports
   137  
   138  	serverName := option.Server
   139  	if option.SNI != "" {
   140  		serverName = option.SNI
   141  	}
   142  
   143  	tlsConfig := &tls.Config{
   144  		ServerName:         serverName,
   145  		InsecureSkipVerify: option.SkipCertVerify,
   146  		MinVersion:         tls.VersionTLS13,
   147  	}
   148  
   149  	var err error
   150  	tlsConfig, err = ca.GetTLSConfig(tlsConfig, option.Fingerprint, option.CustomCA, option.CustomCAString)
   151  	if err != nil {
   152  		return nil, err
   153  	}
   154  
   155  	if len(option.ALPN) > 0 {
   156  		tlsConfig.NextProtos = option.ALPN
   157  	} else {
   158  		tlsConfig.NextProtos = []string{DefaultALPN}
   159  	}
   160  	quicConfig := &quic.Config{
   161  		InitialStreamReceiveWindow:     uint64(option.ReceiveWindowConn),
   162  		MaxStreamReceiveWindow:         uint64(option.ReceiveWindowConn),
   163  		InitialConnectionReceiveWindow: uint64(option.ReceiveWindow),
   164  		MaxConnectionReceiveWindow:     uint64(option.ReceiveWindow),
   165  		KeepAlivePeriod:                10 * time.Second,
   166  		DisablePathMTUDiscovery:        option.DisableMTUDiscovery,
   167  		EnableDatagrams:                true,
   168  	}
   169  	if option.ObfsProtocol != "" {
   170  		option.Protocol = option.ObfsProtocol
   171  	}
   172  	if option.Protocol == "" {
   173  		option.Protocol = DefaultProtocol
   174  	}
   175  	if option.HopInterval == 0 {
   176  		option.HopInterval = DefaultHopInterval
   177  	}
   178  	hopInterval := time.Duration(int64(option.HopInterval)) * time.Second
   179  	if option.ReceiveWindow == 0 {
   180  		quicConfig.InitialStreamReceiveWindow = DefaultStreamReceiveWindow / 10
   181  		quicConfig.MaxStreamReceiveWindow = DefaultStreamReceiveWindow
   182  	}
   183  	if option.ReceiveWindow == 0 {
   184  		quicConfig.InitialConnectionReceiveWindow = DefaultConnectionReceiveWindow / 10
   185  		quicConfig.MaxConnectionReceiveWindow = DefaultConnectionReceiveWindow
   186  	}
   187  	if !quicConfig.DisablePathMTUDiscovery && pmtud_fix.DisablePathMTUDiscovery {
   188  		log.Infoln("hysteria: Path MTU Discovery is not yet supported on this platform")
   189  	}
   190  
   191  	var auth = []byte(option.AuthString)
   192  	if option.Auth != "" {
   193  		auth, err = base64.StdEncoding.DecodeString(option.Auth)
   194  		if err != nil {
   195  			return nil, err
   196  		}
   197  	}
   198  	var obfuscator obfs.Obfuscator
   199  	if len(option.Obfs) > 0 {
   200  		obfuscator = obfs.NewXPlusObfuscator([]byte(option.Obfs))
   201  	}
   202  
   203  	up, down, err := option.Speed()
   204  	if err != nil {
   205  		return nil, err
   206  	}
   207  	if option.UpSpeed != 0 {
   208  		up = uint64(option.UpSpeed * mbpsToBps)
   209  	}
   210  	if option.DownSpeed != 0 {
   211  		down = uint64(option.DownSpeed * mbpsToBps)
   212  	}
   213  	client, err := core.NewClient(
   214  		addr, ports, option.Protocol, auth, tlsConfig, quicConfig, clientTransport, up, down, func(refBPS uint64) congestion.CongestionControl {
   215  			return hyCongestion.NewBrutalSender(congestion.ByteCount(refBPS))
   216  		}, obfuscator, hopInterval, option.FastOpen,
   217  	)
   218  	if err != nil {
   219  		return nil, fmt.Errorf("hysteria %s create error: %w", addr, err)
   220  	}
   221  	return &Hysteria{
   222  		Base: &Base{
   223  			name:   option.Name,
   224  			addr:   addr,
   225  			tp:     C.Hysteria,
   226  			udp:    true,
   227  			tfo:    option.FastOpen,
   228  			iface:  option.Interface,
   229  			rmark:  option.RoutingMark,
   230  			prefer: C.NewDNSPrefer(option.IPVersion),
   231  		},
   232  		option: &option,
   233  		client: client,
   234  	}, nil
   235  }
   236  
   237  type hyPacketConn struct {
   238  	core.UDPConn
   239  }
   240  
   241  func (c *hyPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
   242  	b, addrStr, err := c.UDPConn.ReadFrom()
   243  	if err != nil {
   244  		return
   245  	}
   246  	n = copy(p, b)
   247  	addr = M.ParseSocksaddr(addrStr).UDPAddr()
   248  	return
   249  }
   250  
   251  func (c *hyPacketConn) WaitReadFrom() (data []byte, put func(), addr net.Addr, err error) {
   252  	b, addrStr, err := c.UDPConn.ReadFrom()
   253  	if err != nil {
   254  		return
   255  	}
   256  	data = b
   257  	addr = M.ParseSocksaddr(addrStr).UDPAddr()
   258  	return
   259  }
   260  
   261  func (c *hyPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
   262  	err = c.UDPConn.WriteTo(p, M.SocksaddrFromNet(addr).String())
   263  	if err != nil {
   264  		return
   265  	}
   266  	n = len(p)
   267  	return
   268  }
   269  
   270  type hyDialerWithContext struct {
   271  	hyDialer   func(network string) (net.PacketConn, error)
   272  	ctx        context.Context
   273  	remoteAddr func(host string) (net.Addr, error)
   274  }
   275  
   276  func (h *hyDialerWithContext) ListenPacket(rAddr net.Addr) (net.PacketConn, error) {
   277  	network := "udp"
   278  	if addrPort, err := netip.ParseAddrPort(rAddr.String()); err == nil {
   279  		network = dialer.ParseNetwork(network, addrPort.Addr())
   280  	}
   281  	return h.hyDialer(network)
   282  }
   283  
   284  func (h *hyDialerWithContext) Context() context.Context {
   285  	return h.ctx
   286  }
   287  
   288  func (h *hyDialerWithContext) RemoteAddr(host string) (net.Addr, error) {
   289  	return h.remoteAddr(host)
   290  }