mirror of
https://github.com/MetaCubeX/mihomo.git
synced 2026-04-22 16:17:16 +08:00
feat: support range format for hysteria2 hop-interval
This commit is contained in:
@@ -42,7 +42,7 @@ type Hysteria2Option struct {
|
||||
Server string `proxy:"server"`
|
||||
Port int `proxy:"port,omitempty"`
|
||||
Ports string `proxy:"ports,omitempty"`
|
||||
HopInterval int `proxy:"hop-interval,omitempty"`
|
||||
HopInterval string `proxy:"hop-interval,omitempty"`
|
||||
Up string `proxy:"up,omitempty"`
|
||||
Down string `proxy:"down,omitempty"`
|
||||
Password string `proxy:"password,omitempty"`
|
||||
@@ -189,7 +189,7 @@ func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) {
|
||||
ServerAddress: M.ParseSocksaddr(addr),
|
||||
PacketListener: outbound.dialer,
|
||||
QuicDialer: qtls.QuicDialerFunc(func(ctx context.Context, addr string, dialer qtls.PacketDialer, tlsCfg *tls.Config, cfg *quic.Config, early bool) (net.PacketConn, *quic.Conn, error) {
|
||||
err = echConfig.ClientHandle(ctx, tlsCfg)
|
||||
err := echConfig.ClientHandle(ctx, tlsCfg)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -197,10 +197,9 @@ func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) {
|
||||
}),
|
||||
}
|
||||
|
||||
var ranges utils.IntRanges[uint16]
|
||||
var serverPorts []uint16
|
||||
if option.Ports != "" {
|
||||
ranges, err = utils.NewUnsignedRanges[uint16](option.Ports)
|
||||
ranges, err := utils.NewUnsignedRanges[uint16](option.Ports)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -209,12 +208,21 @@ func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) {
|
||||
return true
|
||||
})
|
||||
if len(serverPorts) > 0 {
|
||||
if option.HopInterval == 0 {
|
||||
option.HopInterval = defaultHopInterval
|
||||
} else if option.HopInterval < minHopInterval {
|
||||
option.HopInterval = minHopInterval
|
||||
hopRange, err := utils.NewUnsignedRange[uint64](option.HopInterval)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
clientOptions.HopInterval = time.Duration(option.HopInterval) * time.Second
|
||||
start, end := hopRange.Start(), hopRange.End()
|
||||
if start == 0 {
|
||||
start = defaultHopInterval
|
||||
} else if start < minHopInterval {
|
||||
start = minHopInterval
|
||||
}
|
||||
if end < start {
|
||||
end = start
|
||||
}
|
||||
clientOptions.HopInterval = time.Duration(start) * time.Second
|
||||
clientOptions.HopIntervalMax = time.Duration(end) * time.Second
|
||||
clientOptions.ServerPorts = serverPorts
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/exp/constraints"
|
||||
)
|
||||
|
||||
@@ -42,3 +46,58 @@ func (r Range[T]) Start() T {
|
||||
func (r Range[T]) End() T {
|
||||
return r.end
|
||||
}
|
||||
|
||||
func (r Range[T]) String() string {
|
||||
if r.start == r.end {
|
||||
return fmt.Sprintf("%v", r.start)
|
||||
}
|
||||
return fmt.Sprintf("%v-%v", r.start, r.end)
|
||||
}
|
||||
|
||||
func NewUnsignedRange[T constraints.Unsigned](expected string) (Range[T], error) {
|
||||
return newIntRange(expected, parseUnsigned[T])
|
||||
}
|
||||
|
||||
func NewSignedRange[T constraints.Signed](expected string) (Range[T], error) {
|
||||
return newIntRange(expected, parseSigned[T])
|
||||
}
|
||||
|
||||
func newIntRange[T constraints.Integer](s string, parseFn func(string) (T, error)) (Range[T], error) {
|
||||
s = strings.TrimSpace(s)
|
||||
if len(s) == 0 {
|
||||
return NewRange[T](0, 0), nil
|
||||
}
|
||||
status := strings.Split(s, "-")
|
||||
start, err := parseFn(strings.Trim(status[0], "[ ]"))
|
||||
if err != nil {
|
||||
return Range[T]{}, fmt.Errorf("invalid range: %s", s)
|
||||
}
|
||||
switch len(status) {
|
||||
case 1: // Port range
|
||||
return NewRange(start, start), nil
|
||||
case 2: // Single port
|
||||
end, err := parseFn(strings.Trim(status[1], "[ ]"))
|
||||
if err != nil {
|
||||
return Range[T]{}, fmt.Errorf("invalid range: %s", s)
|
||||
}
|
||||
return NewRange(start, end), nil
|
||||
default:
|
||||
return Range[T]{}, fmt.Errorf("invalid range: %s", s)
|
||||
}
|
||||
}
|
||||
|
||||
func parseUnsigned[T constraints.Unsigned](s string) (T, error) {
|
||||
if val, err := strconv.ParseUint(s, 10, 64); err == nil {
|
||||
return T(val), nil
|
||||
} else {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
func parseSigned[T constraints.Signed](s string) (T, error) {
|
||||
if val, err := strconv.ParseInt(s, 10, 64); err == nil {
|
||||
return T(val), nil
|
||||
} else {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
+4
-48
@@ -4,7 +4,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/exp/constraints"
|
||||
@@ -38,41 +37,16 @@ func newIntRangesFromList[T constraints.Integer](list []string, parseFn func(str
|
||||
continue
|
||||
}
|
||||
|
||||
status := strings.Split(s, "-")
|
||||
statusLen := len(status)
|
||||
if statusLen > 2 {
|
||||
return nil, errIntRanges
|
||||
}
|
||||
|
||||
start, err := parseFn(strings.Trim(status[0], "[ ]"))
|
||||
r, err := newIntRange[T](s, parseFn)
|
||||
if err != nil {
|
||||
return nil, errIntRanges
|
||||
}
|
||||
|
||||
switch statusLen {
|
||||
case 1: // Port range
|
||||
ranges = append(ranges, NewRange(T(start), T(start)))
|
||||
case 2: // Single port
|
||||
end, err := parseFn(strings.Trim(status[1], "[ ]"))
|
||||
if err != nil {
|
||||
return nil, errIntRanges
|
||||
}
|
||||
|
||||
ranges = append(ranges, NewRange(T(start), T(end)))
|
||||
return nil, err
|
||||
}
|
||||
ranges = append(ranges, r)
|
||||
}
|
||||
|
||||
return ranges, nil
|
||||
}
|
||||
|
||||
func parseUnsigned[T constraints.Unsigned](s string) (T, error) {
|
||||
if val, err := strconv.ParseUint(s, 10, 64); err == nil {
|
||||
return T(val), nil
|
||||
} else {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
func NewUnsignedRanges[T constraints.Unsigned](expected string) (IntRanges[T], error) {
|
||||
return newIntRanges(expected, parseUnsigned[T])
|
||||
}
|
||||
@@ -81,14 +55,6 @@ func NewUnsignedRangesFromList[T constraints.Unsigned](list []string) (IntRanges
|
||||
return newIntRangesFromList(list, parseUnsigned[T])
|
||||
}
|
||||
|
||||
func parseSigned[T constraints.Signed](s string) (T, error) {
|
||||
if val, err := strconv.ParseInt(s, 10, 64); err == nil {
|
||||
return T(val), nil
|
||||
} else {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
func NewSignedRanges[T constraints.Signed](expected string) (IntRanges[T], error) {
|
||||
return newIntRanges(expected, parseSigned[T])
|
||||
}
|
||||
@@ -118,17 +84,7 @@ func (ranges IntRanges[T]) String() string {
|
||||
|
||||
terms := make([]string, len(ranges))
|
||||
for i, r := range ranges {
|
||||
start := r.Start()
|
||||
end := r.End()
|
||||
|
||||
var term string
|
||||
if start == end {
|
||||
term = strconv.Itoa(int(start))
|
||||
} else {
|
||||
term = strconv.Itoa(int(start)) + "-" + strconv.Itoa(int(end))
|
||||
}
|
||||
|
||||
terms[i] = term
|
||||
terms[i] = r.String()
|
||||
}
|
||||
|
||||
return strings.Join(terms, "/")
|
||||
|
||||
+1
-1
@@ -982,7 +982,7 @@ proxies: # socks5
|
||||
server: server.com
|
||||
port: 443
|
||||
# ports: 1000,2000-3000,5000 # port 不可省略
|
||||
# hop-interval: 15
|
||||
# hop-interval: 15 # 支持填写"15-30"会每次随机选取其中一个值作为切换间隔,仅支持写一个范围(即不允许出现逗号)
|
||||
# up 和 down 均不写或为 0 则使用 BBR 流控
|
||||
# up: "30 Mbps" # 若不写单位,默认为 Mbps
|
||||
# down: "200 Mbps" # 若不写单位,默认为 Mbps
|
||||
|
||||
@@ -30,7 +30,7 @@ require (
|
||||
github.com/metacubex/restls-client-go v0.1.7
|
||||
github.com/metacubex/sing v0.5.7
|
||||
github.com/metacubex/sing-mux v0.3.5
|
||||
github.com/metacubex/sing-quic v0.0.0-20260411110907-47ab7e53e7c8
|
||||
github.com/metacubex/sing-quic v0.0.0-20260411121336-043f0078bb1c
|
||||
github.com/metacubex/sing-shadowsocks v0.2.12
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.7
|
||||
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2
|
||||
|
||||
@@ -127,8 +127,8 @@ github.com/metacubex/sing v0.5.7 h1:8OC+fhKFSv/l9ehEhJRaZZAOuthfZo68SteBVLe8QqM=
|
||||
github.com/metacubex/sing v0.5.7/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
|
||||
github.com/metacubex/sing-mux v0.3.5 h1:UqVN+o62SR8kJaC9/3VfOc5UiVqgVY/ef9WwfGYYkk0=
|
||||
github.com/metacubex/sing-mux v0.3.5/go.mod h1:8bT7ZKT3clRrJjYc/x5CRYibC1TX/bK73a3r3+2E+Fc=
|
||||
github.com/metacubex/sing-quic v0.0.0-20260411110907-47ab7e53e7c8 h1:yXGVlQowFsZi5TIcHVGRk2Ui7BPm6CDjKxakcFOYhPw=
|
||||
github.com/metacubex/sing-quic v0.0.0-20260411110907-47ab7e53e7c8/go.mod h1:+lgKTd52xAarGtqugALISShyw4KxnoEpYe2u0zJh26w=
|
||||
github.com/metacubex/sing-quic v0.0.0-20260411121336-043f0078bb1c h1:0WG/3NN/+67lDOXaZUPXtSyVq5jNz0xvsE3fiyVwUR0=
|
||||
github.com/metacubex/sing-quic v0.0.0-20260411121336-043f0078bb1c/go.mod h1:+lgKTd52xAarGtqugALISShyw4KxnoEpYe2u0zJh26w=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.12 h1:Wqzo8bYXrK5aWqxu/TjlTnYZzAKtKsaFQBdr6IHFaBE=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.12/go.mod h1:2e5EIaw0rxKrm1YTRmiMnDulwbGxH9hAFlrwQLQMQkU=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.7 h1:hSuuc0YpsfiqYqt1o+fP4m34BQz4e6wVj3PPBVhor3A=
|
||||
|
||||
Reference in New Issue
Block a user