Clash.Meta/adapter/outbound/hysteria.go

303 lines
8.8 KiB
Go
Raw Normal View History

2022-06-07 13:38:45 +08:00
package outbound
import (
"context"
"crypto/tls"
"encoding/base64"
2022-06-07 13:38:45 +08:00
"fmt"
"net"
2022-12-11 09:25:46 +08:00
"net/netip"
"runtime"
2022-06-07 13:38:45 +08:00
"strconv"
"time"
"github.com/metacubex/quic-go"
"github.com/metacubex/quic-go/congestion"
M "github.com/sagernet/sing/common/metadata"
CN "github.com/metacubex/mihomo/common/net"
2023-11-03 21:01:45 +08:00
"github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/proxydialer"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log"
hyCongestion "github.com/metacubex/mihomo/transport/hysteria/congestion"
"github.com/metacubex/mihomo/transport/hysteria/core"
"github.com/metacubex/mihomo/transport/hysteria/obfs"
"github.com/metacubex/mihomo/transport/hysteria/pmtud_fix"
"github.com/metacubex/mihomo/transport/hysteria/transport"
"github.com/metacubex/mihomo/transport/hysteria/utils"
2022-06-07 13:38:45 +08:00
)
const (
mbpsToBps = 125000
2022-06-07 13:38:45 +08:00
DefaultStreamReceiveWindow = 15728640 // 15 MB/s
DefaultConnectionReceiveWindow = 67108864 // 64 MB/s
DefaultALPN = "hysteria"
DefaultProtocol = "udp"
DefaultHopInterval = 10
2022-06-07 13:38:45 +08:00
)
type Hysteria struct {
*Base
option *HysteriaOption
client *core.Client
closeCh chan struct{} // for test
2022-06-07 13:38:45 +08:00
}
func (h *Hysteria) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
tcpConn, err := h.client.DialTCP(metadata.String(), metadata.DstPort, h.genHdc(ctx, opts...))
2022-06-07 13:38:45 +08:00
if err != nil {
return nil, err
}
return NewConn(CN.NewRefConn(tcpConn, h), h), nil
2022-06-07 13:38:45 +08:00
}
func (h *Hysteria) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
udpConn, err := h.client.DialUDP(h.genHdc(ctx, opts...))
if err != nil {
return nil, err
}
return newPacketConn(CN.NewRefPacketConn(&hyPacketConn{udpConn}, h), h), nil
}
func (h *Hysteria) genHdc(ctx context.Context, opts ...dialer.Option) utils.PacketDialer {
return &hyDialerWithContext{
ctx: context.Background(),
hyDialer: func(network string, rAddr net.Addr) (net.PacketConn, error) {
var err error
var cDialer C.Dialer = dialer.NewDialer(h.Base.DialOptions(opts...)...)
if len(h.option.DialerProxy) > 0 {
cDialer, err = proxydialer.NewByName(h.option.DialerProxy, cDialer)
if err != nil {
return nil, err
}
}
rAddrPort, _ := netip.ParseAddrPort(rAddr.String())
return cDialer.ListenPacket(ctx, network, "", rAddrPort)
},
2022-08-29 12:10:46 +08:00
remoteAddr: func(addr string) (net.Addr, error) {
return resolveUDPAddrWithPrefer(ctx, "udp", addr, h.prefer)
2022-08-29 12:10:46 +08:00
},
}
2022-06-07 13:38:45 +08:00
}
type HysteriaOption struct {
BasicOption
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port,omitempty"`
2022-11-27 12:52:14 +08:00
Ports string `proxy:"ports,omitempty"`
Protocol string `proxy:"protocol,omitempty"`
ObfsProtocol string `proxy:"obfs-protocol,omitempty"` // compatible with Stash
Up string `proxy:"up"`
UpSpeed int `proxy:"up-speed,omitempty"` // compatible with Stash
Down string `proxy:"down"`
DownSpeed int `proxy:"down-speed,omitempty"` // compatible with Stash
Auth string `proxy:"auth,omitempty"`
AuthString string `proxy:"auth-str,omitempty"`
Obfs string `proxy:"obfs,omitempty"`
SNI string `proxy:"sni,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
Fingerprint string `proxy:"fingerprint,omitempty"`
ALPN []string `proxy:"alpn,omitempty"`
CustomCA string `proxy:"ca,omitempty"`
CustomCAString string `proxy:"ca-str,omitempty"`
ReceiveWindowConn int `proxy:"recv-window-conn,omitempty"`
ReceiveWindow int `proxy:"recv-window,omitempty"`
DisableMTUDiscovery bool `proxy:"disable-mtu-discovery,omitempty"`
FastOpen bool `proxy:"fast-open,omitempty"`
HopInterval int `proxy:"hop-interval,omitempty"`
2022-06-07 13:38:45 +08:00
}
func (c *HysteriaOption) Speed() (uint64, uint64, error) {
var up, down uint64
2023-09-21 14:52:26 +08:00
up = StringToBps(c.Up)
if up == 0 {
return 0, 0, fmt.Errorf("invaild upload speed: %s", c.Up)
}
2023-09-21 14:52:26 +08:00
down = StringToBps(c.Down)
2022-06-25 12:43:47 +08:00
if down == 0 {
return 0, 0, fmt.Errorf("invaild download speed: %s", c.Down)
}
2022-06-07 13:38:45 +08:00
return up, down, nil
}
func NewHysteria(option HysteriaOption) (*Hysteria, error) {
clientTransport := &transport.ClientTransport{}
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
ports := option.Ports
2022-06-07 13:38:45 +08:00
2022-06-08 01:47:50 +08:00
serverName := option.Server
if option.SNI != "" {
2022-06-12 00:00:42 +08:00
serverName = option.SNI
2022-06-08 01:47:50 +08:00
}
2022-07-11 13:42:28 +08:00
tlsConfig := &tls.Config{
2022-06-08 01:47:50 +08:00
ServerName: serverName,
2022-06-07 13:38:45 +08:00
InsecureSkipVerify: option.SkipCertVerify,
MinVersion: tls.VersionTLS13,
2022-07-11 13:42:28 +08:00
}
2022-07-12 14:32:34 +08:00
var err error
2023-09-22 14:45:34 +08:00
tlsConfig, err = ca.GetTLSConfig(tlsConfig, option.Fingerprint, option.CustomCA, option.CustomCAString)
if err != nil {
return nil, err
2022-07-11 13:42:28 +08:00
}
2022-06-07 13:38:45 +08:00
if len(option.ALPN) > 0 {
tlsConfig.NextProtos = option.ALPN
2022-06-07 13:38:45 +08:00
} else {
tlsConfig.NextProtos = []string{DefaultALPN}
}
quicConfig := &quic.Config{
InitialStreamReceiveWindow: uint64(option.ReceiveWindowConn),
MaxStreamReceiveWindow: uint64(option.ReceiveWindowConn),
InitialConnectionReceiveWindow: uint64(option.ReceiveWindow),
MaxConnectionReceiveWindow: uint64(option.ReceiveWindow),
KeepAlivePeriod: 10 * time.Second,
DisablePathMTUDiscovery: option.DisableMTUDiscovery,
2022-06-07 13:38:45 +08:00
EnableDatagrams: true,
}
if option.ObfsProtocol != "" {
option.Protocol = option.ObfsProtocol
}
2022-06-08 01:47:50 +08:00
if option.Protocol == "" {
option.Protocol = DefaultProtocol
}
if option.HopInterval == 0 {
option.HopInterval = DefaultHopInterval
}
hopInterval := time.Duration(int64(option.HopInterval)) * time.Second
if option.ReceiveWindow == 0 {
2022-08-28 13:41:19 +08:00
quicConfig.InitialStreamReceiveWindow = DefaultStreamReceiveWindow / 10
2022-06-07 13:38:45 +08:00
quicConfig.MaxStreamReceiveWindow = DefaultStreamReceiveWindow
}
if option.ReceiveWindow == 0 {
2022-08-28 13:41:19 +08:00
quicConfig.InitialConnectionReceiveWindow = DefaultConnectionReceiveWindow / 10
2022-06-07 13:38:45 +08:00
quicConfig.MaxConnectionReceiveWindow = DefaultConnectionReceiveWindow
}
if !quicConfig.DisablePathMTUDiscovery && pmtud_fix.DisablePathMTUDiscovery {
log.Infoln("hysteria: Path MTU Discovery is not yet supported on this platform")
}
var auth = []byte(option.AuthString)
if option.Auth != "" {
auth, err = base64.StdEncoding.DecodeString(option.Auth)
if err != nil {
return nil, err
}
}
2022-06-07 13:38:45 +08:00
var obfuscator obfs.Obfuscator
if len(option.Obfs) > 0 {
obfuscator = obfs.NewXPlusObfuscator([]byte(option.Obfs))
}
up, down, err := option.Speed()
if err != nil {
return nil, err
}
if option.UpSpeed != 0 {
up = uint64(option.UpSpeed * mbpsToBps)
}
if option.DownSpeed != 0 {
down = uint64(option.DownSpeed * mbpsToBps)
}
2022-06-07 13:38:45 +08:00
client, err := core.NewClient(
addr, ports, option.Protocol, auth, tlsConfig, quicConfig, clientTransport, up, down, func(refBPS uint64) congestion.CongestionControl {
2022-06-07 13:38:45 +08:00
return hyCongestion.NewBrutalSender(congestion.ByteCount(refBPS))
}, obfuscator, hopInterval, option.FastOpen,
2022-06-07 13:38:45 +08:00
)
if err != nil {
return nil, fmt.Errorf("hysteria %s create error: %w", addr, err)
}
outbound := &Hysteria{
2022-06-07 13:38:45 +08:00
Base: &Base{
2022-08-28 13:41:19 +08:00
name: option.Name,
addr: addr,
tp: C.Hysteria,
udp: true,
tfo: option.FastOpen,
2022-08-28 13:41:19 +08:00
iface: option.Interface,
rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion),
2022-06-07 13:38:45 +08:00
},
option: &option,
client: client,
}
runtime.SetFinalizer(outbound, closeHysteria)
return outbound, nil
}
func closeHysteria(h *Hysteria) {
if h.client != nil {
_ = h.client.Close()
}
if h.closeCh != nil {
close(h.closeCh)
}
2022-06-07 13:38:45 +08:00
}
type hyPacketConn struct {
core.UDPConn
}
func (c *hyPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
b, addrStr, err := c.UDPConn.ReadFrom()
if err != nil {
return
}
n = copy(p, b)
addr = M.ParseSocksaddr(addrStr).UDPAddr()
return
}
func (c *hyPacketConn) WaitReadFrom() (data []byte, put func(), addr net.Addr, err error) {
b, addrStr, err := c.UDPConn.ReadFrom()
if err != nil {
return
}
data = b
addr = M.ParseSocksaddr(addrStr).UDPAddr()
return
}
2022-06-07 13:38:45 +08:00
func (c *hyPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
err = c.UDPConn.WriteTo(p, M.SocksaddrFromNet(addr).String())
if err != nil {
return
}
n = len(p)
return
}
2022-06-07 15:49:10 +08:00
type hyDialerWithContext struct {
hyDialer func(network string, rAddr net.Addr) (net.PacketConn, error)
2022-08-28 13:41:19 +08:00
ctx context.Context
remoteAddr func(host string) (net.Addr, error)
}
2022-12-11 13:41:44 +08:00
func (h *hyDialerWithContext) ListenPacket(rAddr net.Addr) (net.PacketConn, error) {
network := "udp"
if addrPort, err := netip.ParseAddrPort(rAddr.String()); err == nil {
network = dialer.ParseNetwork(network, addrPort.Addr())
2022-12-11 13:41:44 +08:00
}
return h.hyDialer(network, rAddr)
}
2022-06-07 15:49:10 +08:00
func (h *hyDialerWithContext) Context() context.Context {
return h.ctx
2022-06-07 15:49:10 +08:00
}
2022-08-28 13:41:19 +08:00
func (h *hyDialerWithContext) RemoteAddr(host string) (net.Addr, error) {
2022-12-11 13:41:44 +08:00
return h.remoteAddr(host)
2022-08-28 13:41:19 +08:00
}