From cc0f8e9eb3682389b96b94a5f34e1aa68c05d4f7 Mon Sep 17 00:00:00 2001 From: keakon Date: Fri, 8 Mar 2024 22:38:41 +0800 Subject: [PATCH] feat: implement port hopping (#1064) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * implement port hopping using sing and sing-quic * 更新quic-go * 更新sing * Update go.sum --------- Co-authored-by: wwqgtxx --- adapter/outbound/hysteria2.go | 95 +++++++++++++++++++++++++++++------ go.mod | 4 +- go.sum | 8 +-- 3 files changed, 85 insertions(+), 22 deletions(-) diff --git a/adapter/outbound/hysteria2.go b/adapter/outbound/hysteria2.go index 47272ec806..849fa36867 100644 --- a/adapter/outbound/hysteria2.go +++ b/adapter/outbound/hysteria2.go @@ -5,15 +5,19 @@ import ( "crypto/tls" "errors" "fmt" + "math/rand" "net" "runtime" "strconv" + "strings" + "time" CN "github.com/metacubex/mihomo/common/net" "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" tuicCommon "github.com/metacubex/mihomo/transport/tuic/common" "github.com/metacubex/sing-quic/hysteria2" @@ -25,6 +29,9 @@ func init() { hysteria2.SetCongestionController = tuicCommon.SetCongestionController } +const minHopInterval = 5 +const defaultHopInterval = 30 + type Hysteria2 struct { *Base @@ -35,22 +42,24 @@ type Hysteria2 struct { type Hysteria2Option struct { BasicOption - Name string `proxy:"name"` - Server string `proxy:"server"` - Port int `proxy:"port"` - Up string `proxy:"up,omitempty"` - Down string `proxy:"down,omitempty"` - Password string `proxy:"password,omitempty"` - Obfs string `proxy:"obfs,omitempty"` - ObfsPassword string `proxy:"obfs-password,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"` - CWND int `proxy:"cwnd,omitempty"` - UdpMTU int `proxy:"udp-mtu,omitempty"` + Name string `proxy:"name"` + Server string `proxy:"server"` + Port int `proxy:"port"` + Ports string `proxy:"ports,omitempty"` + HopInterval time.Duration `proxy:"hop-interval,omitempty"` + Up string `proxy:"up,omitempty"` + Down string `proxy:"down,omitempty"` + Password string `proxy:"password,omitempty"` + Obfs string `proxy:"obfs,omitempty"` + ObfsPassword string `proxy:"obfs-password,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"` + CWND int `proxy:"cwnd,omitempty"` + UdpMTU int `proxy:"udp-mtu,omitempty"` } func (h *Hysteria2) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) { @@ -82,6 +91,41 @@ func closeHysteria2(h *Hysteria2) { } } +func parsePorts(portStr string) (ports []uint16) { + portStrs := strings.Split(portStr, ",") + for _, portStr := range portStrs { + if strings.Contains(portStr, "-") { + // Port range + portRange := strings.Split(portStr, "-") + if len(portRange) != 2 { + return nil + } + start, err := strconv.ParseUint(portRange[0], 10, 16) + if err != nil { + return nil + } + end, err := strconv.ParseUint(portRange[1], 10, 16) + if err != nil { + return nil + } + if start > end { + start, end = end, start + } + for i := start; i <= end; i++ { + ports = append(ports, uint16(i)) + } + } else { + // Single port + port, err := strconv.ParseUint(portStr, 10, 16) + if err != nil { + return nil + } + ports = append(ports, uint16(port)) + } + } + return ports +} + func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) { addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) var salamanderPassword string @@ -129,6 +173,7 @@ func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) { clientOptions := hysteria2.ClientOptions{ Context: context.TODO(), Dialer: singDialer, + Logger: log.SingLogger, ServerAddress: M.ParseSocksaddrHostPort(option.Server, uint16(option.Port)), SendBPS: StringToBps(option.Up), ReceiveBPS: StringToBps(option.Down), @@ -140,6 +185,24 @@ func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) { UdpMTU: option.UdpMTU, } + if option.Ports != "" { + ports := parsePorts(option.Ports) + if len(ports) > 0 { + for _, port := range ports { + clientOptions.ServerAddresses = append(clientOptions.ServerAddresses, M.ParseSocksaddrHostPort(option.Server, port)) + } + clientOptions.ServerAddressIndex = rand.Intn(len(clientOptions.ServerAddresses)) + clientOptions.ServerAddress = clientOptions.ServerAddresses[clientOptions.ServerAddressIndex] + + if option.HopInterval == 0 { + option.HopInterval = defaultHopInterval + } else if option.HopInterval < minHopInterval { + option.HopInterval = minHopInterval + } + clientOptions.HopInterval = option.HopInterval * time.Second + } + } + client, err := hysteria2.NewClient(clientOptions) if err != nil { return nil, err diff --git a/go.mod b/go.mod index 7c28d2bf45..e37ebc8efd 100644 --- a/go.mod +++ b/go.mod @@ -19,8 +19,8 @@ require ( github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 github.com/mdlayher/netlink v1.7.2 github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 - github.com/metacubex/quic-go v0.41.1-0.20240120014142-a02f4a533d4a - github.com/metacubex/sing-quic v0.0.0-20240130040922-cbe613c88f20 + github.com/metacubex/quic-go v0.41.1-0.20240307164142-46c6f7cdf2d1 + github.com/metacubex/sing-quic v0.0.0-20240308143007-4dd80423c25a github.com/metacubex/sing-shadowsocks v0.2.6 github.com/metacubex/sing-shadowsocks2 v0.2.0 github.com/metacubex/sing-tun v0.2.1-0.20240214100323-23e40bfb9067 diff --git a/go.sum b/go.sum index 957b30deb9..5723981371 100644 --- a/go.sum +++ b/go.sum @@ -104,12 +104,12 @@ github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvO github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88= github.com/metacubex/gvisor v0.0.0-20240214095142-666a73bcf165 h1:QIQI4gEm+gTwVNdiAyF4EIz5cHm7kSlfDGFpYlAa5dg= github.com/metacubex/gvisor v0.0.0-20240214095142-666a73bcf165/go.mod h1:SKY70wiF1UTSoyuDZyKPMsUC6MsMxh8Y3ZNkIa6J3fU= -github.com/metacubex/quic-go v0.41.1-0.20240120014142-a02f4a533d4a h1:IMr75VdMnDUhkANZemUWqmOPLfwnemiIaCHRnGCdAsY= -github.com/metacubex/quic-go v0.41.1-0.20240120014142-a02f4a533d4a/go.mod h1:F/t8VnA47xoia8ABlNA4InkZjssvFJ5p6E6jKdbkgAs= +github.com/metacubex/quic-go v0.41.1-0.20240307164142-46c6f7cdf2d1 h1:63zKmEWU4MB5MjUSCmeDhm3OzilF7ypXWPq0gAA2GE8= +github.com/metacubex/quic-go v0.41.1-0.20240307164142-46c6f7cdf2d1/go.mod h1:F/t8VnA47xoia8ABlNA4InkZjssvFJ5p6E6jKdbkgAs= github.com/metacubex/sing v0.0.0-20240111014253-f1818b6a82b2 h1:upEO8dt9WDBavhgcgkXB3hRcwVNbkTbnd+xyzy6ZQZo= github.com/metacubex/sing v0.0.0-20240111014253-f1818b6a82b2/go.mod h1:9pfuAH6mZfgnz/YjP6xu5sxx882rfyjpcrTdUpd6w3g= -github.com/metacubex/sing-quic v0.0.0-20240130040922-cbe613c88f20 h1:wt7ydRxm9Pvw+un6KD97tjLJHMrkzp83HyiGkoz6e7k= -github.com/metacubex/sing-quic v0.0.0-20240130040922-cbe613c88f20/go.mod h1:bdHqEysJclB9BzIa5jcKKSZ1qua+YEPjR8fOzzE3vZU= +github.com/metacubex/sing-quic v0.0.0-20240308143007-4dd80423c25a h1:ATj0jL+cp7n+NT3T010cXK5KoVvAbeGhZFtUFHvq2BU= +github.com/metacubex/sing-quic v0.0.0-20240308143007-4dd80423c25a/go.mod h1:WyY0zYxv+o+18R/Ece+QFontlgXoobKbNqbtYn2zjz8= github.com/metacubex/sing-shadowsocks v0.2.6 h1:6oEB3QcsFYnNiFeoevcXrCwJ3sAablwVSgtE9R3QeFQ= github.com/metacubex/sing-shadowsocks v0.2.6/go.mod h1:zIkMeSnb8Mbf4hdqhw0pjzkn1d99YJ3JQm/VBg5WMTg= github.com/metacubex/sing-shadowsocks2 v0.2.0 h1:hqwT/AfI5d5UdPefIzR6onGHJfDXs5zgOM5QSgaM/9A=