Merge branch 'Alpha' into port-hopping2

# Conflicts:
#	adapter/outbound/hysteria2.go
This commit is contained in:
keakon 2024-01-24 16:09:39 +08:00
commit 6e858ce69a
34 changed files with 242 additions and 66 deletions

View file

@ -15,7 +15,7 @@ on:
- Alpha
jobs:
# Send "core-updated" to MetaCubeX/MihomoForAndroid to trigger update-dependencies
# Send "core-updated" to MetaCubeX/ClashMetaForAndroid to trigger update-dependencies
trigger-CMFA-update:
runs-on: ubuntu-latest
steps:
@ -27,7 +27,7 @@ jobs:
- name: Trigger update-dependencies
run: |
curl -X POST https://api.github.com/repos/MetaCubeX/MihomoForAndroid/dispatches \
curl -X POST https://api.github.com/repos/MetaCubeX/ClashMetaForAndroid/dispatches \
-H "Accept: application/vnd.github.everest-preview+json" \
-H "Authorization: token ${{ steps.generate-token.outputs.token }}" \
-d '{"event_type": "core-updated"}'
-d '{"event_type": "core-updated"}'

View file

@ -63,3 +63,9 @@ func WithInAddr(addr net.Addr) Addition {
}
}
}
func WithDSCP(dscp uint8) Addition {
return func(metadata *C.Metadata) {
metadata.DSCP = dscp
}
}

View file

@ -141,7 +141,7 @@ func (f *Fallback) Set(name string) error {
if !p.AliveForTestUrl(f.testUrl) {
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(5000))
defer cancel()
expectedStatus, _ := utils.NewIntRanges[uint16](f.expectedStatus)
expectedStatus, _ := utils.NewUnsignedRanges[uint16](f.expectedStatus)
_, _ = p.URLTest(ctx, f.testUrl, expectedStatus)
}

View file

@ -28,6 +28,7 @@ type GroupCommonOption struct {
Use []string `group:"use,omitempty"`
URL string `group:"url,omitempty"`
Interval int `group:"interval,omitempty"`
TestTimeout int `group:"timeout,omitempty"`
Lazy bool `group:"lazy,omitempty"`
DisableUDP bool `group:"disable-udp,omitempty"`
Filter string `group:"filter,omitempty"`
@ -80,7 +81,7 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
return nil, fmt.Errorf("%s: %w", groupName, errMissProxy)
}
expectedStatus, err := utils.NewIntRanges[uint16](groupOption.ExpectedStatus)
expectedStatus, err := utils.NewUnsignedRanges[uint16](groupOption.ExpectedStatus)
if err != nil {
return nil, fmt.Errorf("%s: %w", groupName, err)
}
@ -92,9 +93,11 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
groupOption.ExpectedStatus = status
testUrl := groupOption.URL
if groupOption.URL == "" {
groupOption.URL = C.DefaultTestURL
testUrl = groupOption.URL
if groupOption.Type != "select" && groupOption.Type != "relay" {
if groupOption.URL == "" {
groupOption.URL = C.DefaultTestURL
testUrl = groupOption.URL
}
}
if len(GroupProxies) != 0 {
@ -114,7 +117,7 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
}
}
hc := provider.NewHealthCheck(ps, testUrl, uint(groupOption.Interval), groupOption.Lazy, expectedStatus)
hc := provider.NewHealthCheck(ps, testUrl, uint(groupOption.TestTimeout), uint(groupOption.Interval), groupOption.Lazy, expectedStatus)
pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
if err != nil {

View file

@ -16,10 +16,6 @@ import (
"github.com/dlclark/regexp2"
)
const (
defaultURLTestTimeout = time.Second * 5
)
type HealthCheckOption struct {
URL string
Interval uint
@ -42,6 +38,7 @@ type HealthCheck struct {
lastTouch atomic.TypedValue[time.Time]
done chan struct{}
singleDo *singledo.Single[struct{}]
timeout time.Duration
}
func (hc *HealthCheck) process() {
@ -198,7 +195,7 @@ func (hc *HealthCheck) execute(b *batch.Batch[bool], url, uid string, option *ex
p := proxy
b.Go(p.Name(), func() (bool, error) {
ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout)
ctx, cancel := context.WithTimeout(context.Background(), hc.timeout)
defer cancel()
log.Debugln("Health Checking, proxy: %s, url: %s, id: {%s}", p.Name(), url, uid)
_, _ = p.URLTest(ctx, url, expectedStatus)
@ -212,15 +209,19 @@ func (hc *HealthCheck) close() {
hc.done <- struct{}{}
}
func NewHealthCheck(proxies []C.Proxy, url string, interval uint, lazy bool, expectedStatus utils.IntRanges[uint16]) *HealthCheck {
func NewHealthCheck(proxies []C.Proxy, url string, timeout uint, interval uint, lazy bool, expectedStatus utils.IntRanges[uint16]) *HealthCheck {
if url == "" {
// expectedStatus = nil
url = C.DefaultTestURL
}
if timeout == 0 {
timeout = 5000
}
return &HealthCheck{
proxies: proxies,
url: url,
timeout: time.Duration(timeout) * time.Millisecond,
extra: map[string]*extraOption{},
interval: time.Duration(interval) * time.Second,
lazy: lazy,

View file

@ -22,6 +22,7 @@ type healthCheckSchema struct {
Enable bool `provider:"enable"`
URL string `provider:"url"`
Interval int `provider:"interval"`
TestTimeout int `provider:"timeout,omitempty"`
Lazy bool `provider:"lazy,omitempty"`
ExpectedStatus string `provider:"expected-status,omitempty"`
}
@ -63,7 +64,7 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide
return nil, err
}
expectedStatus, err := utils.NewIntRanges[uint16](schema.HealthCheck.ExpectedStatus)
expectedStatus, err := utils.NewUnsignedRanges[uint16](schema.HealthCheck.ExpectedStatus)
if err != nil {
return nil, err
}
@ -75,7 +76,7 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide
}
hcInterval = uint(schema.HealthCheck.Interval)
}
hc := NewHealthCheck([]C.Proxy{}, schema.HealthCheck.URL, hcInterval, schema.HealthCheck.Lazy, expectedStatus)
hc := NewHealthCheck([]C.Proxy{}, schema.HealthCheck.URL, uint(schema.HealthCheck.TestTimeout), hcInterval, schema.HealthCheck.Lazy, expectedStatus)
var vehicle types.Vehicle
switch schema.Type {

View file

@ -68,7 +68,8 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
hysteria["skip-cert-verify"], _ = strconv.ParseBool(query.Get("insecure"))
proxies = append(proxies, hysteria)
case "hysteria2":
case "hysteria2", "hy2":
urlHysteria2, err := url.Parse(line)
if err != nil {
continue
@ -79,7 +80,7 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
hysteria2 := make(map[string]any, 20)
hysteria2["name"] = name
hysteria2["type"] = scheme
hysteria2["type"] = "hysteria2"
hysteria2["server"] = urlHysteria2.Hostname()
if port := urlHysteria2.Port(); port != "" {
hysteria2["port"] = port
@ -101,6 +102,7 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
hysteria2["up"] = query.Get("up")
proxies = append(proxies, hysteria2)
case "tuic":
// A temporary unofficial TUIC share link standard
// Modified from https://github.com/daeuniverse/dae/discussions/182
@ -143,7 +145,7 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
}
proxies = append(proxies, tuic)
case "trojan":
urlTrojan, err := url.Parse(line)
if err != nil {
@ -405,14 +407,27 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
if query.Get("udp-over-tcp") == "true" || query.Get("uot") == "1" {
ss["udp-over-tcp"] = true
}
if strings.Contains(query.Get("plugin"), "obfs") {
obfsParams := strings.Split(query.Get("plugin"), ";")
ss["plugin"] = "obfs"
ss["plugin-opts"] = map[string]any{
"host": obfsParams[2][10:],
"mode": obfsParams[1][5:],
plugin := query.Get("plugin")
if strings.Contains(plugin, ";") {
pluginInfo, _ := url.ParseQuery("pluginName=" + strings.ReplaceAll(plugin, ";", "&"))
pluginName := pluginInfo.Get("pluginName")
if strings.Contains(pluginName, "obfs") {
ss["plugin"] = "obfs"
ss["plugin-opts"] = map[string]any{
"mode": pluginInfo.Get("obfs"),
"host": pluginInfo.Get("obfs-host"),
}
} else if strings.Contains(pluginName, "v2ray-plugin") {
ss["plugin"] = "v2ray-plugin"
ss["plugin-opts"] = map[string]any{
"mode": pluginInfo.Get("mode"),
"host": pluginInfo.Get("host"),
"path": pluginInfo.Get("path"),
"tls": strings.Contains(plugin, "tls"),
}
}
}
proxies = append(proxies, ss)
case "ssr":

View file

@ -13,7 +13,7 @@ type IntRanges[T constraints.Integer] []Range[T]
var errIntRanges = errors.New("intRanges error")
func NewIntRanges[T constraints.Integer](expected string) (IntRanges[T], error) {
func newIntRanges[T constraints.Integer](expected string, parseFn func(string) (T, error)) (IntRanges[T], error) {
// example: 200 or 200/302 or 200-400 or 200/204/401-429/501-503
expected = strings.TrimSpace(expected)
if len(expected) == 0 || expected == "*" {
@ -25,10 +25,10 @@ func NewIntRanges[T constraints.Integer](expected string) (IntRanges[T], error)
return nil, fmt.Errorf("%w, too many ranges to use, maximum support 28 ranges", errIntRanges)
}
return NewIntRangesFromList[T](list)
return newIntRangesFromList[T](list, parseFn)
}
func NewIntRangesFromList[T constraints.Integer](list []string) (IntRanges[T], error) {
func newIntRangesFromList[T constraints.Integer](list []string, parseFn func(string) (T, error)) (IntRanges[T], error) {
var ranges IntRanges[T]
for _, s := range list {
if s == "" {
@ -41,7 +41,7 @@ func NewIntRangesFromList[T constraints.Integer](list []string) (IntRanges[T], e
return nil, errIntRanges
}
start, err := strconv.ParseInt(strings.Trim(status[0], "[ ]"), 10, 64)
start, err := parseFn(strings.Trim(status[0], "[ ]"))
if err != nil {
return nil, errIntRanges
}
@ -50,7 +50,7 @@ func NewIntRangesFromList[T constraints.Integer](list []string) (IntRanges[T], e
case 1:
ranges = append(ranges, NewRange(T(start), T(start)))
case 2:
end, err := strconv.ParseUint(strings.Trim(status[1], "[ ]"), 10, 64)
end, err := parseFn(strings.Trim(status[1], "[ ]"))
if err != nil {
return nil, errIntRanges
}
@ -62,6 +62,38 @@ func NewIntRangesFromList[T constraints.Integer](list []string) (IntRanges[T], e
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])
}
func NewUnsignedRangesFromList[T constraints.Unsigned](list []string) (IntRanges[T], error) {
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])
}
func NewSignedRangesFromList[T constraints.Signed](list []string) (IntRanges[T], error) {
return newIntRangesFromList(list, parseSigned[T])
}
func (ranges IntRanges[T]) Check(status T) bool {
if len(ranges) == 0 {
return true

View file

@ -15,6 +15,11 @@ import (
"github.com/metacubex/mihomo/constant/features"
)
const (
DefaultTCPTimeout = 5 * time.Second
DefaultUDPTimeout = DefaultTCPTimeout
)
type dialFunc func(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error)
var (

View file

@ -2,6 +2,7 @@ package dialer
import (
"context"
C "github.com/metacubex/mihomo/constant"
"github.com/sagernet/tfo-go"
"io"
"net"
@ -66,14 +67,14 @@ func (c *tfoConn) Close() error {
func (c *tfoConn) LocalAddr() net.Addr {
if c.Conn == nil {
return nil
return &net.TCPAddr{}
}
return c.Conn.LocalAddr()
}
func (c *tfoConn) RemoteAddr() net.Addr {
if c.Conn == nil {
return nil
return &net.TCPAddr{}
}
return c.Conn.RemoteAddr()
}
@ -123,7 +124,7 @@ func (c *tfoConn) WriterReplaceable() bool {
}
func dialTFO(ctx context.Context, netDialer net.Dialer, network, address string) (net.Conn, error) {
ctx, cancel := context.WithCancel(ctx)
ctx, cancel := context.WithTimeout(context.Background(), DefaultTCPTimeout)
dialer := tfo.Dialer{Dialer: netDialer, DisableTFO: false}
return &tfoConn{
dialed: make(chan bool, 1),

View file

@ -13,10 +13,6 @@ import (
"github.com/samber/lo"
)
const (
minInterval = time.Minute * 5
)
var (
fileMode os.FileMode = 0o666
dirMode os.FileMode = 0o755
@ -164,8 +160,8 @@ func (f *Fetcher[V]) Destroy() error {
func (f *Fetcher[V]) pullLoop() {
initialInterval := f.interval - time.Since(f.UpdatedAt)
if initialInterval < minInterval {
initialInterval = minInterval
if initialInterval > f.interval {
initialInterval = f.interval
}
timer := time.NewTimer(initialInterval)

View file

@ -775,7 +775,7 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
}
ps = append(ps, proxies[v])
}
hc := provider.NewHealthCheck(ps, "", 0, true, nil)
hc := provider.NewHealthCheck(ps, "", 5000, 0, true, nil)
pd, _ := provider.NewCompatibleProvider(provider.ReservedName, ps, hc)
providersMap[provider.ReservedName] = pd
@ -1473,7 +1473,7 @@ func parseSniffer(snifferRaw RawSniffer) (*Sniffer, error) {
if len(snifferRaw.Sniff) != 0 {
for sniffType, sniffConfig := range snifferRaw.Sniff {
find := false
ports, err := utils.NewIntRangesFromList[uint16](sniffConfig.Ports)
ports, err := utils.NewUnsignedRangesFromList[uint16](sniffConfig.Ports)
if err != nil {
return nil, err
}
@ -1500,7 +1500,7 @@ func parseSniffer(snifferRaw RawSniffer) (*Sniffer, error) {
// Deprecated: Use Sniff instead
log.Warnln("Deprecated: Use Sniff instead")
}
globalPorts, err := utils.NewIntRangesFromList[uint16](snifferRaw.Ports)
globalPorts, err := utils.NewUnsignedRangesFromList[uint16](snifferRaw.Ports)
if err != nil {
return nil, err
}

View file

@ -43,9 +43,9 @@ const (
)
const (
DefaultTCPTimeout = 5 * time.Second
DefaultTCPTimeout = dialer.DefaultTCPTimeout
DefaultUDPTimeout = dialer.DefaultUDPTimeout
DefaultDropTime = 12 * DefaultTCPTimeout
DefaultUDPTimeout = DefaultTCPTimeout
DefaultTLSTimeout = DefaultTCPTimeout
DefaultTestURL = "https://www.gstatic.com/generate_204"
)

View file

@ -147,6 +147,7 @@ type Metadata struct {
SpecialProxy string `json:"specialProxy"`
SpecialRules string `json:"specialRules"`
RemoteDst string `json:"remoteDestination"`
DSCP uint8 `json:"dscp"`
RawSrcAddr net.Addr `json:"-"`
RawDstAddr net.Addr `json:"-"`

View file

@ -14,6 +14,7 @@ const (
SrcPort
DstPort
InPort
DSCP
InUser
InName
InType
@ -73,6 +74,8 @@ func (rt RuleType) String() string {
return "RuleSet"
case Network:
return "Network"
case DSCP:
return "DSCP"
case Uid:
return "Uid"
case SubRules:

6
go.mod
View file

@ -20,8 +20,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.40.1-0.20231130135418-0c1b47cf9394
github.com/metacubex/sing-quic v0.0.0-20231220152840-85620b446796
github.com/metacubex/quic-go v0.41.1-0.20240120014142-a02f4a533d4a
github.com/metacubex/sing-quic v0.0.0-20240122125415-d6eb83bc6ec4
github.com/metacubex/sing-shadowsocks v0.2.6
github.com/metacubex/sing-shadowsocks2 v0.2.0
github.com/metacubex/sing-tun v0.2.0
@ -35,7 +35,7 @@ require (
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97
github.com/sagernet/sing v0.3.0
github.com/sagernet/sing-mux v0.2.0
github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6
github.com/sagernet/sing-shadowtls v0.1.4
github.com/sagernet/tfo-go v0.0.0-20231209031829-7b5343ac1dc6
github.com/sagernet/utls v1.5.4

18
go.sum
View file

@ -37,6 +37,7 @@ github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9/go.mod h1:hkIF
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 h1:8j2RH289RJplhA6WfdaPqzg1MjH2K8wX5e0uhAxrw2g=
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391/go.mod h1:K2R7GhgxrlJzHw2qiPWsCZXf/kXEJN9PLnQK73Ll0po=
github.com/ericlagergren/saferand v0.0.0-20220206064634-960a4dd2bc5c h1:RUzBDdZ+e/HEe2Nh8lYsduiPAZygUfVXJn0Ncj5sHMg=
github.com/ericlagergren/saferand v0.0.0-20220206064634-960a4dd2bc5c/go.mod h1:ETASDWf/FmEb6Ysrtd1QhjNedUU/ZQxBCRLh60bQ/UI=
github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 h1:tlDMEdcPRQKBEz5nGDMvswiajqh7k8ogWRlhRwKy5mY=
github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1/go.mod h1:4RfsapbGx2j/vU5xC/5/9qB3kn9Awp1YDiEnN43QrJ4=
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 h1:fuGucgPk5dN6wzfnxl3D0D3rVLw4v2SbBT9jb4VnxzA=
@ -54,6 +55,7 @@ github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vz
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
@ -69,6 +71,7 @@ github.com/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M=
github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
@ -78,6 +81,7 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/tink/go v1.6.1 h1:t7JHqO8Ath2w2ig5vjwQYJzhGEZymedQc90lQXUBa4I=
github.com/google/tink/go v1.6.1/go.mod h1:IGW53kTgag+st5yPhKKwJ6u2l+SSp5/v9XF7spovjlY=
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
@ -111,12 +115,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-20231209122014-3e43224c7bbc h1:+yTZ6q2EeQCAJNpKNEu5j32Pm23ShD38ElIa635wTrk=
github.com/metacubex/gvisor v0.0.0-20231209122014-3e43224c7bbc/go.mod h1:rhBU9tD5ktoGPBtXUquhWuGJ4u+8ZZzBMi2cAdv9q8Y=
github.com/metacubex/quic-go v0.40.1-0.20231130135418-0c1b47cf9394 h1:dIT+KB2hknBCrwVAXPeY9tpzzkOZP5m40yqUteRT6/Y=
github.com/metacubex/quic-go v0.40.1-0.20231130135418-0c1b47cf9394/go.mod h1:F/t8VnA47xoia8ABlNA4InkZjssvFJ5p6E6jKdbkgAs=
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/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-20231220152840-85620b446796 h1:xiCPttMGAaIh4Ad6t85VxUoUv+Sg88eXzzUvYN8gT5w=
github.com/metacubex/sing-quic v0.0.0-20231220152840-85620b446796/go.mod h1:E1e1Uu6YaJddD+c0DtJlSOkfMI0NLdOVhM60KAlcssY=
github.com/metacubex/sing-quic v0.0.0-20240122125415-d6eb83bc6ec4 h1:4EukI/UdbuaXov7VDDZf4vaP0ZmkUu827DOeQqzVysw=
github.com/metacubex/sing-quic v0.0.0-20240122125415-d6eb83bc6ec4/go.mod h1:bdHqEysJclB9BzIa5jcKKSZ1qua+YEPjR8fOzzE3vZU=
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=
@ -136,6 +140,7 @@ github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7/go.mod h1:U
github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=
github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k=
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
github.com/openacid/errors v0.8.1/go.mod h1:GUQEJJOJE3W9skHm8E8Y4phdl2LLEN8iD7c5gcGgdx0=
github.com/openacid/low v0.1.21 h1:Tr2GNu4N/+rGRYdOsEHOE89cxUIaDViZbVmKz29uKGo=
github.com/openacid/low v0.1.21/go.mod h1:q+MsKI6Pz2xsCkzV4BLj7NR5M4EX0sGz5AqotpZDVh0=
@ -153,6 +158,7 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
github.com/puzpuzpuz/xsync/v3 v3.0.2 h1:3yESHrRFYr6xzkz61LLkvNiPFXxJEAABanTQpKbAaew=
github.com/puzpuzpuz/xsync/v3 v3.0.2/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
@ -165,8 +171,8 @@ github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkk
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM=
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE=
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
github.com/sagernet/sing-mux v0.2.0 h1:4C+vd8HztJCWNYfufvgL49xaOoOHXty2+EAjnzN3IYo=
github.com/sagernet/sing-mux v0.2.0/go.mod h1:khzr9AOPocLa+g53dBplwNDz4gdsyx/YM3swtAhlkHQ=
github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6 h1:5bCAkvDDzSMITiHFjolBwpdqYsvycdTu71FsMEFXQ14=
github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6/go.mod h1:khzr9AOPocLa+g53dBplwNDz4gdsyx/YM3swtAhlkHQ=
github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k=
github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4=
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ=

View file

@ -78,7 +78,7 @@ func getGroupDelay(w http.ResponseWriter, r *http.Request) {
return
}
expectedStatus, err := utils.NewIntRanges[uint16](query.Get("expected"))
expectedStatus, err := utils.NewUnsignedRanges[uint16](query.Get("expected"))
if err != nil {
render.Status(r, http.StatusBadRequest)
render.JSON(w, r, ErrBadRequest)

View file

@ -113,7 +113,7 @@ func getProxyDelay(w http.ResponseWriter, r *http.Request) {
return
}
expectedStatus, err := utils.NewIntRanges[uint16](query.Get("expected"))
expectedStatus, err := utils.NewUnsignedRanges[uint16](query.Get("expected"))
if err != nil {
render.Status(r, http.StatusBadRequest)
render.JSON(w, r, ErrBadRequest)

View file

@ -21,6 +21,7 @@ type Hysteria2Server struct {
IgnoreClientBandwidth bool `yaml:"ignore-client-bandwidth" json:"ignore-client-bandwidth,omitempty"`
Masquerade string `yaml:"masquerade" json:"masquerade,omitempty"`
CWND int `yaml:"cwnd" json:"cwnd,omitempty"`
UdpMTU int `yaml:"udp-mtu" json:"udp-mtu,omitempty"`
MuxOption sing.MuxOption `yaml:"mux-option" json:"mux-option,omitempty"`
}

View file

@ -21,6 +21,7 @@ type Hysteria2Option struct {
IgnoreClientBandwidth bool `inbound:"ignore-client-bandwidth,omitempty"`
Masquerade string `inbound:"masquerade,omitempty"`
CWND int `inbound:"cwnd,omitempty"`
UdpMTU int `inbound:"udp-mtu,omitempty"`
MuxOption MuxOption `inbound:"mux-option,omitempty"`
}
@ -58,6 +59,7 @@ func NewHysteria2(options *Hysteria2Option) (*Hysteria2, error) {
IgnoreClientBandwidth: options.IgnoreClientBandwidth,
Masquerade: options.Masquerade,
CWND: options.CWND,
UdpMTU: options.UdpMTU,
MuxOption: options.MuxOption.Build(),
},
}, nil

View file

@ -104,6 +104,12 @@ func New(config LC.Hysteria2Server, tunnel C.Tunnel, additions ...inbound.Additi
}
}
if config.UdpMTU == 0 {
// "1200" from quic-go's MaxDatagramSize
// "-3" from quic-go's DatagramFrame.MaxDataLen
config.UdpMTU = 1200 - 3
}
service, err := hysteria2.NewService[string](hysteria2.ServiceOptions{
Context: context.Background(),
Logger: log.SingLogger,
@ -115,6 +121,7 @@ func New(config LC.Hysteria2Server, tunnel C.Tunnel, additions ...inbound.Additi
Handler: h,
MasqueradeHandler: masqueradeHandler,
CWND: config.CWND,
UdpMTU: config.UdpMTU,
})
if err != nil {
return nil, err

View file

@ -34,6 +34,14 @@ func setsockopt(rc syscall.RawConn, addr string) error {
if err == nil && isIPv6 {
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, IPV6_RECVORIGDSTADDR, 1)
}
if err == nil {
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_RECVTOS, 1)
}
if err == nil {
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, syscall.IPV6_RECVTCLASS, 1)
}
})
return err

View file

@ -79,6 +79,9 @@ func NewUDP(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*UDPLi
continue
}
dscp, _ := getDSCP(oob[:oobn])
additions = append(additions, inbound.WithDSCP(dscp))
if rAddr.Addr().Is4() {
// try to unmap 4in6 address
lAddr = netip.AddrPortFrom(lAddr.Addr().Unmap(), lAddr.Port())

View file

@ -104,7 +104,7 @@ func getOrigDst(oob []byte) (netip.AddrPort, error) {
}
// retrieve the destination address from the SCM.
sa, err := unix.ParseOrigDstAddr(&scms[0])
sa, err := unix.ParseOrigDstAddr(&scms[1])
if err != nil {
return netip.AddrPort{}, fmt.Errorf("retrieve destination: %w", err)
}
@ -122,3 +122,30 @@ func getOrigDst(oob []byte) (netip.AddrPort, error) {
return rAddr, nil
}
func getDSCP (oob []byte) (uint8, error) {
scms, err := unix.ParseSocketControlMessage(oob)
if err != nil {
return 0, fmt.Errorf("parse control message: %w", err)
}
dscp, err := parseDSCP(&scms[0])
if err != nil {
return 0, fmt.Errorf("retrieve DSCP: %w", err)
}
return dscp, nil
}
func parseDSCP(m *unix.SocketControlMessage) (uint8, error) {
switch {
case m.Header.Level == unix.SOL_IP && m.Header.Type == unix.IP_TOS:
dscp := uint8(m.Data[0] >> 2)
return dscp, nil
case m.Header.Level == unix.SOL_IPV6 && m.Header.Type == unix.IPV6_TCLASS:
dscp := uint8(m.Data[0] >> 2)
return dscp, nil
default:
return 0, nil
}
}

View file

@ -12,6 +12,10 @@ func getOrigDst(oob []byte) (netip.AddrPort, error) {
return netip.AddrPort{}, errors.New("UDP redir not supported on current platform")
}
func getDSCP(oob []byte) (uint8, error) {
return 0, errors.New("UDP redir not supported on current platform")
}
func dialUDP(network string, lAddr, rAddr netip.AddrPort) (*net.UDPConn, error) {
return nil, errors.New("UDP redir not supported on current platform")
}

49
rules/common/dscp.go Normal file
View file

@ -0,0 +1,49 @@
package common
import (
"fmt"
"github.com/metacubex/mihomo/common/utils"
C "github.com/metacubex/mihomo/constant"
)
type DSCP struct {
*Base
ranges utils.IntRanges[uint8]
payload string
adapter string
}
func (d *DSCP) RuleType() C.RuleType {
return C.DSCP
}
func (d *DSCP) Match(metadata *C.Metadata) (bool, string) {
return d.ranges.Check(metadata.DSCP), d.adapter
}
func (d *DSCP) Adapter() string {
return d.adapter
}
func (d *DSCP) Payload() string {
return d.payload
}
func NewDSCP(dscp string, adapter string) (*DSCP, error) {
ranges, err := utils.NewUnsignedRanges[uint8](dscp)
if err != nil {
return nil, fmt.Errorf("parse DSCP rule fail: %w", err)
}
for _, r := range ranges {
if r.End() > 63 {
return nil, fmt.Errorf("DSCP couldn't be negative or exceed 63")
}
}
return &DSCP{
Base: &Base{},
payload: dscp,
ranges: ranges,
adapter: adapter,
}, nil
}

View file

@ -39,7 +39,7 @@ func (p *Port) Payload() string {
}
func NewPort(port string, adapter string, ruleType C.RuleType) (*Port, error) {
portRanges, err := utils.NewIntRanges[uint16](port)
portRanges, err := utils.NewUnsignedRanges[uint16](port)
if err != nil {
return nil, fmt.Errorf("%w, %w", errPayload, err)
}

View file

@ -21,7 +21,7 @@ func NewUid(oUid, adapter string) (*Uid, error) {
return nil, fmt.Errorf("uid rule not support this platform")
}
uidRange, err := utils.NewIntRanges[uint32](oUid)
uidRange, err := utils.NewUnsignedRanges[uint32](oUid)
if err != nil {
return nil, fmt.Errorf("%w, %w", errPayload, err)
}

View file

@ -38,6 +38,8 @@ func ParseRule(tp, payload, target string, params []string, subRules map[string]
parsed, parseErr = RC.NewPort(payload, target, C.DstPort)
case "IN-PORT":
parsed, parseErr = RC.NewPort(payload, target, C.InPort)
case "DSCP":
parsed, parseErr = RC.NewDSCP(payload, target)
case "PROCESS-NAME":
parsed, parseErr = RC.NewProcess(payload, target, true)
case "PROCESS-PATH":

View file

@ -402,10 +402,11 @@ func (c *quicPktConn) WriteTo(p []byte, addr string) error {
_ = struc.Pack(&msgBuf, &msg)
err = c.Session.SendDatagram(msgBuf.Bytes())
if err != nil {
if errSize, ok := err.(quic.ErrMessageTooLarge); ok {
var errSize *quic.DatagramTooLargeError
if errors.As(err, &errSize) {
// need to frag
msg.MsgID = uint16(fastrand.Intn(0xFFFF)) + 1 // msgID must be > 0 when fragCount > 1
fragMsgs := fragUDPMessage(msg, int(errSize))
fragMsgs := fragUDPMessage(msg, int(errSize.PeerMaxDatagramFrameSize))
for _, fragMsg := range fragMsgs {
msgBuf.Reset()
_ = struc.Pack(&msgBuf, &fragMsg)

View file

@ -123,7 +123,7 @@ func (q *quicStreamPacketConn) WaitReadFrom() (data []byte, put func(), addr net
func (q *quicStreamPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
if q.udpRelayMode != common.QUIC && len(p) > q.maxUdpRelayPacketSize {
return 0, quic.ErrMessageTooLarge(q.maxUdpRelayPacketSize)
return 0, &quic.DatagramTooLargeError{PeerMaxDatagramFrameSize: int64(q.maxUdpRelayPacketSize)}
}
if q.closed {
return 0, net.ErrClosed

View file

@ -12,7 +12,9 @@ import (
// MaxFragSize is a safe udp relay packet size
// because tuicv5 support udp fragment so we unneeded to do a magic modify for quic-go to increase MaxDatagramFrameSize
// it may not work fine in some platform
var MaxFragSize = 1200 - PacketOverHead
// "1200" from quic-go's MaxDatagramSize
// "-3" from quic-go's DatagramFrame.MaxDataLen
var MaxFragSize = 1200 - PacketOverHead - 3
func fragWriteNative(quicConn quic.Connection, packet Packet, buf *bytes.Buffer, fragSize int) (err error) {
fullPayload := packet.DATA

View file

@ -137,7 +137,7 @@ func (q *quicStreamPacketConn) WaitReadFrom() (data []byte, put func(), addr net
func (q *quicStreamPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
if len(p) > 0xffff { // uint16 max
return 0, quic.ErrMessageTooLarge(0xffff)
return 0, &quic.DatagramTooLargeError{PeerMaxDatagramFrameSize: 0xffff}
}
if q.closed {
return 0, net.ErrClosed
@ -187,9 +187,9 @@ func (q *quicStreamPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err erro
err = q.quicConn.SendDatagram(data)
}
var tooLarge quic.ErrMessageTooLarge
var tooLarge *quic.DatagramTooLargeError
if errors.As(err, &tooLarge) {
err = fragWriteNative(q.quicConn, packet, buf, int(tooLarge)-PacketOverHead)
err = fragWriteNative(q.quicConn, packet, buf, int(tooLarge.PeerMaxDatagramFrameSize)-PacketOverHead)
}
if err != nil {
return