2022-06-07 13:38:45 +08:00
|
|
|
package outbound
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2022-07-12 14:32:34 +08:00
|
|
|
"crypto/sha256"
|
2022-06-07 13:38:45 +08:00
|
|
|
"crypto/tls"
|
2022-09-21 23:42:33 +08:00
|
|
|
"encoding/base64"
|
2022-07-12 14:32:34 +08:00
|
|
|
"encoding/hex"
|
|
|
|
"encoding/pem"
|
2022-06-07 13:38:45 +08:00
|
|
|
"fmt"
|
|
|
|
"net"
|
2022-08-11 23:56:50 +08:00
|
|
|
"os"
|
2022-06-07 13:38:45 +08:00
|
|
|
"regexp"
|
|
|
|
"strconv"
|
|
|
|
"time"
|
|
|
|
|
2022-09-21 23:42:33 +08:00
|
|
|
"github.com/lucas-clemente/quic-go"
|
|
|
|
"github.com/lucas-clemente/quic-go/congestion"
|
|
|
|
M "github.com/sagernet/sing/common/metadata"
|
|
|
|
|
2022-06-07 13:38:45 +08:00
|
|
|
"github.com/Dreamacro/clash/component/dialer"
|
2022-09-21 23:42:33 +08:00
|
|
|
tlsC "github.com/Dreamacro/clash/component/tls"
|
2022-06-07 13:38:45 +08:00
|
|
|
C "github.com/Dreamacro/clash/constant"
|
|
|
|
"github.com/Dreamacro/clash/log"
|
2022-07-03 18:22:56 +08:00
|
|
|
hyCongestion "github.com/Dreamacro/clash/transport/hysteria/congestion"
|
2022-09-21 23:42:33 +08:00
|
|
|
"github.com/Dreamacro/clash/transport/hysteria/core"
|
|
|
|
"github.com/Dreamacro/clash/transport/hysteria/obfs"
|
|
|
|
"github.com/Dreamacro/clash/transport/hysteria/pmtud_fix"
|
|
|
|
"github.com/Dreamacro/clash/transport/hysteria/transport"
|
2022-06-07 13:38:45 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
mbpsToBps = 125000
|
|
|
|
minSpeedBPS = 16384
|
|
|
|
|
|
|
|
DefaultStreamReceiveWindow = 15728640 // 15 MB/s
|
|
|
|
DefaultConnectionReceiveWindow = 67108864 // 64 MB/s
|
|
|
|
DefaultMaxIncomingStreams = 1024
|
|
|
|
|
2022-06-08 01:47:50 +08:00
|
|
|
DefaultALPN = "hysteria"
|
|
|
|
DefaultProtocol = "udp"
|
2022-06-07 13:38:45 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
var rateStringRegexp = regexp.MustCompile(`^(\d+)\s*([KMGT]?)([Bb])ps$`)
|
|
|
|
|
|
|
|
type Hysteria struct {
|
|
|
|
*Base
|
|
|
|
|
2022-06-26 21:52:22 +08:00
|
|
|
client *core.Client
|
2022-06-07 13:38:45 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func (h *Hysteria) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
|
2022-06-26 21:52:22 +08:00
|
|
|
hdc := hyDialerWithContext{
|
2022-07-03 18:22:56 +08:00
|
|
|
ctx: context.Background(),
|
2022-06-26 21:52:22 +08:00
|
|
|
hyDialer: func() (net.PacketConn, error) {
|
|
|
|
return dialer.ListenPacket(ctx, "udp", "", h.Base.DialOptions(opts...)...)
|
|
|
|
},
|
2022-08-28 13:41:19 +08:00
|
|
|
remoteAddr: func(addr string) (net.Addr, error) {
|
|
|
|
return resolveUDPAddrWithPrefer("udp", addr, h.prefer)
|
|
|
|
},
|
2022-06-26 21:52:22 +08:00
|
|
|
}
|
2022-07-03 18:22:56 +08:00
|
|
|
|
2022-06-26 21:52:22 +08:00
|
|
|
tcpConn, err := h.client.DialTCP(metadata.RemoteAddress(), &hdc)
|
2022-06-07 13:38:45 +08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-06-26 21:52:22 +08:00
|
|
|
|
2022-06-07 13:38:45 +08:00
|
|
|
return NewConn(tcpConn, h), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (h *Hysteria) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
|
2022-06-26 21:52:22 +08:00
|
|
|
hdc := hyDialerWithContext{
|
2022-07-03 18:22:56 +08:00
|
|
|
ctx: context.Background(),
|
2022-06-26 21:52:22 +08:00
|
|
|
hyDialer: func() (net.PacketConn, error) {
|
|
|
|
return dialer.ListenPacket(ctx, "udp", "", h.Base.DialOptions(opts...)...)
|
|
|
|
},
|
2022-08-29 12:10:46 +08:00
|
|
|
remoteAddr: func(addr string) (net.Addr, error) {
|
|
|
|
return resolveUDPAddrWithPrefer("udp", addr, h.prefer)
|
|
|
|
},
|
2022-06-26 21:52:22 +08:00
|
|
|
}
|
|
|
|
udpConn, err := h.client.DialUDP(&hdc)
|
2022-06-07 13:38:45 +08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return newPacketConn(&hyPacketConn{udpConn}, h), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type HysteriaOption struct {
|
|
|
|
BasicOption
|
2022-09-21 23:42:33 +08:00
|
|
|
Name string `proxy:"name"`
|
|
|
|
Server string `proxy:"server"`
|
|
|
|
Port int `proxy:"port"`
|
|
|
|
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"`
|
2022-06-07 13:38:45 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *HysteriaOption) Speed() (uint64, uint64, error) {
|
|
|
|
var up, down uint64
|
2022-06-12 11:50:57 +08:00
|
|
|
up = stringToBps(c.Up)
|
2022-06-14 20:23:36 +08:00
|
|
|
if up == 0 {
|
|
|
|
return 0, 0, fmt.Errorf("invaild upload speed: %s", c.Up)
|
|
|
|
}
|
|
|
|
|
2022-06-12 11:50:57 +08:00
|
|
|
down = stringToBps(c.Down)
|
2022-06-25 12:43:47 +08:00
|
|
|
if down == 0 {
|
2022-06-14 20:23:36 +08:00
|
|
|
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{
|
|
|
|
Dialer: &net.Dialer{
|
|
|
|
Timeout: 8 * time.Second,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
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 bs []byte
|
|
|
|
var err error
|
|
|
|
if len(option.CustomCA) > 0 {
|
2022-08-11 23:56:50 +08:00
|
|
|
bs, err = os.ReadFile(option.CustomCA)
|
2022-07-12 14:32:34 +08:00
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("hysteria %s load ca error: %w", addr, err)
|
|
|
|
}
|
|
|
|
} else if option.CustomCAString != "" {
|
|
|
|
bs = []byte(option.CustomCAString)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(bs) > 0 {
|
|
|
|
block, _ := pem.Decode(bs)
|
|
|
|
if block == nil {
|
|
|
|
return nil, fmt.Errorf("CA cert is not PEM")
|
|
|
|
}
|
|
|
|
|
|
|
|
fpBytes := sha256.Sum256(block.Bytes)
|
|
|
|
if len(option.Fingerprint) == 0 {
|
|
|
|
option.Fingerprint = hex.EncodeToString(fpBytes[:])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-11 13:42:28 +08:00
|
|
|
if len(option.Fingerprint) != 0 {
|
|
|
|
var err error
|
|
|
|
tlsConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
tlsConfig = tlsC.GetGlobalFingerprintTLCConfig(tlsConfig)
|
|
|
|
}
|
|
|
|
|
2022-06-07 13:38:45 +08:00
|
|
|
if len(option.ALPN) > 0 {
|
2022-09-21 23:42:33 +08:00
|
|
|
tlsConfig.NextProtos = option.ALPN
|
2022-06-07 13:38:45 +08:00
|
|
|
} else {
|
|
|
|
tlsConfig.NextProtos = []string{DefaultALPN}
|
|
|
|
}
|
2022-07-12 14:32:34 +08:00
|
|
|
|
2022-06-07 13:38:45 +08:00
|
|
|
quicConfig := &quic.Config{
|
2022-06-08 01:47:50 +08:00
|
|
|
InitialStreamReceiveWindow: uint64(option.ReceiveWindowConn),
|
|
|
|
MaxStreamReceiveWindow: uint64(option.ReceiveWindowConn),
|
|
|
|
InitialConnectionReceiveWindow: uint64(option.ReceiveWindow),
|
|
|
|
MaxConnectionReceiveWindow: uint64(option.ReceiveWindow),
|
2022-07-07 12:49:52 +08:00
|
|
|
KeepAlivePeriod: 10 * time.Second,
|
2022-06-07 13:38:45 +08:00
|
|
|
DisablePathMTUDiscovery: option.DisableMTUDiscovery,
|
|
|
|
EnableDatagrams: true,
|
|
|
|
}
|
2022-09-21 23:42:33 +08:00
|
|
|
if option.ObfsProtocol != "" {
|
|
|
|
option.Protocol = option.ObfsProtocol
|
|
|
|
}
|
2022-06-08 01:47:50 +08:00
|
|
|
if option.Protocol == "" {
|
|
|
|
option.Protocol = DefaultProtocol
|
|
|
|
}
|
2022-06-07 13:38:45 +08:00
|
|
|
if option.ReceiveWindowConn == 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")
|
|
|
|
}
|
2022-06-12 11:50:57 +08:00
|
|
|
|
|
|
|
var auth = []byte(option.AuthString)
|
2022-09-21 23:42:33 +08:00
|
|
|
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))
|
|
|
|
}
|
2022-06-14 20:23:36 +08:00
|
|
|
|
|
|
|
up, down, err := option.Speed()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-09-21 23:42:33 +08:00
|
|
|
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, option.Protocol, auth, tlsConfig, quicConfig, clientTransport, up, down, func(refBPS uint64) congestion.CongestionControl {
|
|
|
|
return hyCongestion.NewBrutalSender(congestion.ByteCount(refBPS))
|
|
|
|
}, obfuscator,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("hysteria %s create error: %w", addr, err)
|
|
|
|
}
|
|
|
|
return &Hysteria{
|
|
|
|
Base: &Base{
|
2022-08-28 13:41:19 +08:00
|
|
|
name: option.Name,
|
|
|
|
addr: addr,
|
|
|
|
tp: C.Hysteria,
|
|
|
|
udp: true,
|
|
|
|
iface: option.Interface,
|
|
|
|
rmark: option.RoutingMark,
|
|
|
|
prefer: C.NewDNSPrefer(option.IPVersion),
|
2022-06-07 13:38:45 +08:00
|
|
|
},
|
2022-06-26 21:52:22 +08:00
|
|
|
client: client,
|
2022-06-07 13:38:45 +08:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func stringToBps(s string) uint64 {
|
|
|
|
if s == "" {
|
|
|
|
return 0
|
|
|
|
}
|
2022-06-12 11:50:57 +08:00
|
|
|
|
|
|
|
// when have not unit, use Mbps
|
|
|
|
if v, err := strconv.Atoi(s); err == nil {
|
|
|
|
return stringToBps(fmt.Sprintf("%d Mbps", v))
|
|
|
|
}
|
|
|
|
|
2022-06-07 13:38:45 +08:00
|
|
|
m := rateStringRegexp.FindStringSubmatch(s)
|
|
|
|
if m == nil {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
var n uint64
|
|
|
|
switch m[2] {
|
|
|
|
case "K":
|
|
|
|
n = 1 << 10
|
|
|
|
case "M":
|
|
|
|
n = 1 << 20
|
|
|
|
case "G":
|
|
|
|
n = 1 << 30
|
|
|
|
case "T":
|
|
|
|
n = 1 << 40
|
|
|
|
default:
|
|
|
|
n = 1
|
|
|
|
}
|
|
|
|
v, _ := strconv.ParseUint(m[1], 10, 64)
|
|
|
|
n = v * n
|
|
|
|
if m[3] == "b" {
|
|
|
|
// Bits, need to convert to bytes
|
|
|
|
n = n >> 3
|
|
|
|
}
|
|
|
|
return n
|
|
|
|
}
|
|
|
|
|
|
|
|
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) 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
|
|
|
|
2022-06-26 21:52:22 +08:00
|
|
|
type hyDialerWithContext struct {
|
2022-08-28 13:41:19 +08:00
|
|
|
hyDialer func() (net.PacketConn, error)
|
|
|
|
ctx context.Context
|
|
|
|
remoteAddr func(host string) (net.Addr, error)
|
2022-06-26 21:52:22 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func (h *hyDialerWithContext) ListenPacket() (net.PacketConn, error) {
|
|
|
|
return h.hyDialer()
|
|
|
|
}
|
2022-06-07 15:49:10 +08:00
|
|
|
|
2022-06-26 21:52:22 +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) {
|
|
|
|
return h.remoteAddr(host)
|
|
|
|
}
|