diff --git a/.github/update.log b/.github/update.log index f171135561..46c1b3ab8f 100644 --- a/.github/update.log +++ b/.github/update.log @@ -1198,3 +1198,4 @@ Update On Thu Nov 27 19:38:17 CET 2025 Update On Fri Nov 28 19:37:50 CET 2025 Update On Sat Nov 29 19:37:01 CET 2025 Update On Sun Nov 30 19:38:16 CET 2025 +Update On Mon Dec 1 19:44:38 CET 2025 diff --git a/clash-meta-android/build.gradle.kts b/clash-meta-android/build.gradle.kts index 2ed94f9daf..d6d2e0dbf1 100644 --- a/clash-meta-android/build.gradle.kts +++ b/clash-meta-android/build.gradle.kts @@ -58,8 +58,8 @@ subprojects { minSdk = 21 targetSdk = 35 - versionName = "2.11.19" - versionCode = 211019 + versionName = "2.11.20" + versionCode = 211020 resValue("string", "release_name", "v$versionName") resValue("integer", "release_code", "$versionCode") diff --git a/clash-meta-android/core/src/foss/golang/clash/.github/workflows/test.yml b/clash-meta-android/core/src/foss/golang/clash/.github/workflows/test.yml index c6ac69cd46..ddddb5eee5 100644 --- a/clash-meta-android/core/src/foss/golang/clash/.github/workflows/test.yml +++ b/clash-meta-android/core/src/foss/golang/clash/.github/workflows/test.yml @@ -54,6 +54,11 @@ jobs: cd $(go env GOROOT) patch --verbose -p 1 < $GITHUB_WORKSPACE/.github/patch/go${{matrix.go-version}}.patch + - name: Remove inbound test for macOS + if: ${{ runner.os == 'macOS' }} + run: | + rm -rf listener/inbound/*_test.go + - name: Test run: go test ./... -v -count=1 diff --git a/clash-meta-android/core/src/foss/golang/clash/adapter/outbound/mieru.go b/clash-meta-android/core/src/foss/golang/clash/adapter/outbound/mieru.go index 8ef9cfd758..e09b1898f2 100644 --- a/clash-meta-android/core/src/foss/golang/clash/adapter/outbound/mieru.go +++ b/clash-meta-android/core/src/foss/golang/clash/adapter/outbound/mieru.go @@ -4,12 +4,14 @@ import ( "context" "fmt" "net" + "net/netip" "strconv" "sync" CN "github.com/metacubex/mihomo/common/net" "github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/proxydialer" + "github.com/metacubex/mihomo/component/resolver" C "github.com/metacubex/mihomo/constant" mieruclient "github.com/enfein/mieru/v3/apis/client" @@ -40,6 +42,45 @@ type MieruOption struct { HandshakeMode string `proxy:"handshake-mode,omitempty"` } +type mieruPacketDialer struct { + C.Dialer +} + +var _ mierucommon.PacketDialer = (*mieruPacketDialer)(nil) + +func (pd mieruPacketDialer) ListenPacket(ctx context.Context, network, laddr, raddr string) (net.PacketConn, error) { + rAddrPort, err := netip.ParseAddrPort(raddr) + if err != nil { + return nil, fmt.Errorf("invalid address %s: %w", raddr, err) + } + return pd.Dialer.ListenPacket(ctx, network, laddr, rAddrPort) +} + +type mieruDNSResolver struct { + prefer C.DNSPrefer +} + +var _ mierucommon.DNSResolver = (*mieruDNSResolver)(nil) + +func (dr mieruDNSResolver) LookupIP(ctx context.Context, network, host string) (_ []net.IP, err error) { + var ip netip.Addr + switch dr.prefer { + case C.IPv4Only: + ip, err = resolver.ResolveIPv4WithResolver(ctx, host, resolver.ProxyServerHostResolver) + case C.IPv6Only: + ip, err = resolver.ResolveIPv6WithResolver(ctx, host, resolver.ProxyServerHostResolver) + case C.IPv6Prefer: + ip, err = resolver.ResolveIPPrefer6WithResolver(ctx, host, resolver.ProxyServerHostResolver) + default: + ip, err = resolver.ResolveIPWithResolver(ctx, host, resolver.ProxyServerHostResolver) + } + if err != nil { + return nil, fmt.Errorf("can't resolve ip: %w", err) + } + // TODO: handle IP4P (due to interface limitations, it's currently impossible to modify the port here) + return []net.IP{ip.AsSlice()}, nil +} + // DialContext implements C.ProxyAdapter func (m *Mieru) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { if err := m.ensureClientIsRunning(); err != nil { @@ -102,6 +143,8 @@ func (m *Mieru) ensureClientIsRunning() error { return err } config.Dialer = dialer + config.PacketDialer = mieruPacketDialer{Dialer: dialer} + config.Resolver = mieruDNSResolver{prefer: m.prefer} if err := m.client.Store(config); err != nil { return err } @@ -158,23 +201,21 @@ func (m *Mieru) Close() error { } func metadataToMieruNetAddrSpec(metadata *C.Metadata) mierumodel.NetAddrSpec { + spec := mierumodel.NetAddrSpec{ + Net: metadata.NetWork.String(), + } if metadata.Host != "" { - return mierumodel.NetAddrSpec{ - AddrSpec: mierumodel.AddrSpec{ - FQDN: metadata.Host, - Port: int(metadata.DstPort), - }, - Net: "tcp", + spec.AddrSpec = mierumodel.AddrSpec{ + FQDN: metadata.Host, + Port: int(metadata.DstPort), } } else { - return mierumodel.NetAddrSpec{ - AddrSpec: mierumodel.AddrSpec{ - IP: metadata.DstIP.AsSlice(), - Port: int(metadata.DstPort), - }, - Net: "tcp", + spec.AddrSpec = mierumodel.AddrSpec{ + IP: metadata.DstIP.AsSlice(), + Port: int(metadata.DstPort), } } + return spec } func buildMieruClientConfig(option MieruOption) (*mieruclient.ClientConfig, error) { @@ -182,7 +223,13 @@ func buildMieruClientConfig(option MieruOption) (*mieruclient.ClientConfig, erro return nil, fmt.Errorf("failed to validate mieru option: %w", err) } - transportProtocol := mierupb.TransportProtocol_TCP.Enum() + var transportProtocol = mierupb.TransportProtocol_UNKNOWN_TRANSPORT_PROTOCOL.Enum() + switch option.Transport { + case "TCP": + transportProtocol = mierupb.TransportProtocol_TCP.Enum() + case "UDP": + transportProtocol = mierupb.TransportProtocol_UDP.Enum() + } var server *mierupb.ServerEndpoint if net.ParseIP(option.Server) != nil { // server is an IP address @@ -240,6 +287,9 @@ func buildMieruClientConfig(option MieruOption) (*mieruclient.ClientConfig, erro }, Servers: []*mierupb.ServerEndpoint{server}, }, + DNSConfig: &mierucommon.ClientDNSConfig{ + BypassDialerDNS: true, + }, } if multiplexing, ok := mierupb.MultiplexingLevel_value[option.Multiplexing]; ok { config.Profile.Multiplexing = &mierupb.MultiplexingConfig{ @@ -284,8 +334,8 @@ func validateMieruOption(option MieruOption) error { } } - if option.Transport != "TCP" { - return fmt.Errorf("transport must be TCP") + if option.Transport != "TCP" && option.Transport != "UDP" { + return fmt.Errorf("transport must be TCP or UDP") } if option.UserName == "" { return fmt.Errorf("username is empty") diff --git a/clash-meta-android/core/src/foss/golang/clash/adapter/outbound/mieru_test.go b/clash-meta-android/core/src/foss/golang/clash/adapter/outbound/mieru_test.go index 086b791044..d80b27696c 100644 --- a/clash-meta-android/core/src/foss/golang/clash/adapter/outbound/mieru_test.go +++ b/clash-meta-android/core/src/foss/golang/clash/adapter/outbound/mieru_test.go @@ -34,7 +34,7 @@ func TestNewMieru(t *testing.T) { Name: "test", Server: "example.com", Port: 10003, - Transport: "TCP", + Transport: "UDP", UserName: "test", Password: "test", }, diff --git a/clash-meta-android/core/src/foss/golang/clash/adapter/outbound/sudoku.go b/clash-meta-android/core/src/foss/golang/clash/adapter/outbound/sudoku.go new file mode 100644 index 0000000000..9b4a9c9db1 --- /dev/null +++ b/clash-meta-android/core/src/foss/golang/clash/adapter/outbound/sudoku.go @@ -0,0 +1,270 @@ +package outbound + +import ( + "context" + "crypto/sha256" + "encoding/binary" + "fmt" + "io" + "net" + "strconv" + "strings" + "time" + + "github.com/metacubex/mihomo/log" + + "github.com/saba-futai/sudoku/apis" + "github.com/saba-futai/sudoku/pkg/crypto" + "github.com/saba-futai/sudoku/pkg/obfs/httpmask" + "github.com/saba-futai/sudoku/pkg/obfs/sudoku" + + N "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/component/dialer" + "github.com/metacubex/mihomo/component/proxydialer" + C "github.com/metacubex/mihomo/constant" +) + +type Sudoku struct { + *Base + option *SudokuOption + table *sudoku.Table + baseConf apis.ProtocolConfig +} + +type SudokuOption struct { + BasicOption + Name string `proxy:"name"` + Server string `proxy:"server"` + Port int `proxy:"port"` + Key string `proxy:"key"` + AEADMethod string `proxy:"aead-method,omitempty"` + PaddingMin *int `proxy:"padding-min,omitempty"` + PaddingMax *int `proxy:"padding-max,omitempty"` + TableType string `proxy:"table-type,omitempty"` // "prefer_ascii" or "prefer_entropy" + HTTPMask bool `proxy:"http-mask,omitempty"` +} + +// DialContext implements C.ProxyAdapter +func (s *Sudoku) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { + return s.DialContextWithDialer(ctx, dialer.NewDialer(s.DialOptions()...), metadata) +} + +// DialContextWithDialer implements C.ProxyAdapter +func (s *Sudoku) DialContextWithDialer(ctx context.Context, d C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) { + if len(s.option.DialerProxy) > 0 { + d, err = proxydialer.NewByName(s.option.DialerProxy, d) + if err != nil { + return nil, err + } + } + + cfg, err := s.buildConfig(metadata) + if err != nil { + return nil, err + } + + c, err := d.DialContext(ctx, "tcp", s.addr) + if err != nil { + return nil, fmt.Errorf("%s connect error: %w", s.addr, err) + } + + defer func() { + safeConnClose(c, err) + }() + + if ctx.Done() != nil { + done := N.SetupContextForConn(ctx, c) + defer done(&err) + } + + c, err = s.streamConn(c, cfg) + if err != nil { + return nil, err + } + + return NewConn(c, s), nil +} + +// ListenPacketContext implements C.ProxyAdapter +func (s *Sudoku) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) { + return nil, C.ErrNotSupport +} + +// SupportUOT implements C.ProxyAdapter +func (s *Sudoku) SupportUOT() bool { + return false // Sudoku protocol only supports TCP +} + +// SupportWithDialer implements C.ProxyAdapter +func (s *Sudoku) SupportWithDialer() C.NetWork { + return C.TCP +} + +// ProxyInfo implements C.ProxyAdapter +func (s *Sudoku) ProxyInfo() C.ProxyInfo { + info := s.Base.ProxyInfo() + info.DialerProxy = s.option.DialerProxy + return info +} + +func (s *Sudoku) buildConfig(metadata *C.Metadata) (*apis.ProtocolConfig, error) { + if metadata == nil || metadata.DstPort == 0 || !metadata.Valid() { + return nil, fmt.Errorf("invalid metadata for sudoku outbound") + } + + cfg := s.baseConf + cfg.TargetAddress = metadata.RemoteAddress() + + if err := cfg.ValidateClient(); err != nil { + return nil, err + } + return &cfg, nil +} + +func (s *Sudoku) streamConn(rawConn net.Conn, cfg *apis.ProtocolConfig) (_ net.Conn, err error) { + if !cfg.DisableHTTPMask { + if err = httpmask.WriteRandomRequestHeader(rawConn, cfg.ServerAddress); err != nil { + return nil, fmt.Errorf("write http mask failed: %w", err) + } + } + + obfsConn := sudoku.NewConn(rawConn, cfg.Table, cfg.PaddingMin, cfg.PaddingMax, false) + cConn, err := crypto.NewAEADConn(obfsConn, cfg.Key, cfg.AEADMethod) + if err != nil { + return nil, fmt.Errorf("setup crypto failed: %w", err) + } + + handshake := buildSudokuHandshakePayload(cfg.Key) + if _, err = cConn.Write(handshake[:]); err != nil { + cConn.Close() + return nil, fmt.Errorf("send handshake failed: %w", err) + } + + if err = writeTargetAddress(cConn, cfg.TargetAddress); err != nil { + cConn.Close() + return nil, fmt.Errorf("send target address failed: %w", err) + } + + return cConn, nil +} + +func NewSudoku(option SudokuOption) (*Sudoku, error) { + if option.Server == "" { + return nil, fmt.Errorf("server is required") + } + if option.Port <= 0 || option.Port > 65535 { + return nil, fmt.Errorf("invalid port: %d", option.Port) + } + if option.Key == "" { + return nil, fmt.Errorf("key is required") + } + + tableType := strings.ToLower(option.TableType) + if tableType == "" { + tableType = "prefer_ascii" + } + if tableType != "prefer_ascii" && tableType != "prefer_entropy" { + return nil, fmt.Errorf("table-type must be prefer_ascii or prefer_entropy") + } + + seed := option.Key + if recoveredFromKey, err := crypto.RecoverPublicKey(option.Key); err == nil { + seed = crypto.EncodePoint(recoveredFromKey) + } + + start := time.Now() + table := sudoku.NewTable(seed, tableType) + log.Infoln("[Sudoku] Tables initialized (%s) in %v", tableType, time.Since(start)) + + defaultConf := apis.DefaultConfig() + paddingMin := defaultConf.PaddingMin + paddingMax := defaultConf.PaddingMax + if option.PaddingMin != nil { + paddingMin = *option.PaddingMin + } + if option.PaddingMax != nil { + paddingMax = *option.PaddingMax + } + if option.PaddingMin == nil && option.PaddingMax != nil && paddingMax < paddingMin { + paddingMin = paddingMax + } + if option.PaddingMax == nil && option.PaddingMin != nil && paddingMax < paddingMin { + paddingMax = paddingMin + } + + baseConf := apis.ProtocolConfig{ + ServerAddress: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), + Key: option.Key, + AEADMethod: defaultConf.AEADMethod, + Table: table, + PaddingMin: paddingMin, + PaddingMax: paddingMax, + HandshakeTimeoutSeconds: defaultConf.HandshakeTimeoutSeconds, + DisableHTTPMask: !option.HTTPMask, + } + if option.AEADMethod != "" { + baseConf.AEADMethod = option.AEADMethod + } + + return &Sudoku{ + Base: &Base{ + name: option.Name, + addr: baseConf.ServerAddress, + tp: C.Sudoku, + udp: false, + tfo: option.TFO, + mpTcp: option.MPTCP, + iface: option.Interface, + rmark: option.RoutingMark, + prefer: C.NewDNSPrefer(option.IPVersion), + }, + option: &option, + table: table, + baseConf: baseConf, + }, nil +} + +func buildSudokuHandshakePayload(key string) [16]byte { + var payload [16]byte + binary.BigEndian.PutUint64(payload[:8], uint64(time.Now().Unix())) + hash := sha256.Sum256([]byte(key)) + copy(payload[8:], hash[:8]) + return payload +} + +func writeTargetAddress(w io.Writer, rawAddr string) error { + host, portStr, err := net.SplitHostPort(rawAddr) + if err != nil { + return err + } + + portInt, err := net.LookupPort("tcp", portStr) + if err != nil { + return err + } + + var buf []byte + if ip := net.ParseIP(host); ip != nil { + if ip4 := ip.To4(); ip4 != nil { + buf = append(buf, 0x01) // IPv4 + buf = append(buf, ip4...) + } else { + buf = append(buf, 0x04) // IPv6 + buf = append(buf, ip...) + } + } else { + if len(host) > 255 { + return fmt.Errorf("domain too long") + } + buf = append(buf, 0x03) // domain + buf = append(buf, byte(len(host))) + buf = append(buf, host...) + } + + var portBytes [2]byte + binary.BigEndian.PutUint16(portBytes[:], uint16(portInt)) + buf = append(buf, portBytes[:]...) + + _, err = w.Write(buf) + return err +} diff --git a/clash-meta-android/core/src/foss/golang/clash/adapter/outboundgroup/parser.go b/clash-meta-android/core/src/foss/golang/clash/adapter/outboundgroup/parser.go index 2dcdd628b2..4640685594 100644 --- a/clash-meta-android/core/src/foss/golang/clash/adapter/outboundgroup/parser.go +++ b/clash-meta-android/core/src/foss/golang/clash/adapter/outboundgroup/parser.go @@ -186,7 +186,7 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide strategy := parseStrategy(config) return NewLoadBalance(groupOption, providers, strategy) case "relay": - group = NewRelay(groupOption, providers) + return nil, fmt.Errorf("%w: The group [%s] with relay type was removed, please using dialer-proxy instead", errType, groupName) default: return nil, fmt.Errorf("%w: %s", errType, groupOption.Type) } diff --git a/clash-meta-android/core/src/foss/golang/clash/adapter/parser.go b/clash-meta-android/core/src/foss/golang/clash/adapter/parser.go index 3052ab107f..af93ebf49c 100644 --- a/clash-meta-android/core/src/foss/golang/clash/adapter/parser.go +++ b/clash-meta-android/core/src/foss/golang/clash/adapter/parser.go @@ -146,6 +146,13 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) { break } proxy, err = outbound.NewAnyTLS(*anytlsOption) + case "sudoku": + sudokuOption := &outbound.SudokuOption{} + err = decoder.Decode(mapping, sudokuOption) + if err != nil { + break + } + proxy, err = outbound.NewSudoku(*sudokuOption) default: return nil, fmt.Errorf("unsupport proxy type: %s", proxyType) } diff --git a/clash-meta-android/core/src/foss/golang/clash/common/structure/structure.go b/clash-meta-android/core/src/foss/golang/clash/common/structure/structure.go index bf79f7dd27..840e4f6cad 100644 --- a/clash-meta-android/core/src/foss/golang/clash/common/structure/structure.go +++ b/clash-meta-android/core/src/foss/golang/clash/common/structure/structure.go @@ -289,7 +289,7 @@ func (d *Decoder) decodeBool(name string, data any, val reflect.Value) (err erro case isInt(kind) && d.option.WeaklyTypedInput: val.SetBool(dataVal.Int() != 0) case isUint(kind) && d.option.WeaklyTypedInput: - val.SetString(strconv.FormatUint(dataVal.Uint(), 10)) + val.SetBool(dataVal.Uint() != 0) default: err = fmt.Errorf( "'%s' expected type '%s', got unconvertible type '%s'", diff --git a/clash-meta-android/core/src/foss/golang/clash/common/xsync/map.go b/clash-meta-android/core/src/foss/golang/clash/common/xsync/map.go index b85bf421b1..fd8faa8b1f 100644 --- a/clash-meta-android/core/src/foss/golang/clash/common/xsync/map.go +++ b/clash-meta-android/core/src/foss/golang/clash/common/xsync/map.go @@ -1,16 +1,17 @@ package xsync -// copy and modified from https://github.com/puzpuzpuz/xsync/blob/v4.1.0/map.go +// copy and modified from https://github.com/puzpuzpuz/xsync/blob/v4.2.0/map.go // which is licensed under Apache v2. // // mihomo modified: -// 1. parallel Map resize has been removed to decrease the memory using. +// 1. restore xsync/v3's LoadOrCompute api and rename to LoadOrStoreFn. // 2. the zero Map is ready for use. import ( "fmt" "math" "math/bits" + "runtime" "strings" "sync" "sync/atomic" @@ -41,8 +42,28 @@ const ( metaMask uint64 = 0xffffffffff defaultMetaMasked uint64 = defaultMeta & metaMask emptyMetaSlot uint8 = 0x80 + // minimal number of buckets to transfer when participating in cooperative + // resize; should be at least defaultMinMapTableLen + minResizeTransferStride = 64 + // upper limit for max number of additional goroutines that participate + // in cooperative resize; must be changed simultaneously with resizeCtl + // and the related code + maxResizeHelpersLimit = (1 << 5) - 1 ) +// max number of additional goroutines that participate in cooperative resize; +// "resize owner" goroutine isn't counted +var maxResizeHelpers = func() int32 { + v := int32(parallelism() - 1) + if v < 1 { + v = 1 + } + if v > maxResizeHelpersLimit { + v = maxResizeHelpersLimit + } + return v +}() + type mapResizeHint int const ( @@ -100,16 +121,25 @@ type Map[K comparable, V any] struct { initOnce sync.Once totalGrowths atomic.Int64 totalShrinks atomic.Int64 - resizing atomic.Bool // resize in progress flag - resizeMu sync.Mutex // only used along with resizeCond - resizeCond sync.Cond // used to wake up resize waiters (concurrent modifications) table atomic.Pointer[mapTable[K, V]] - minTableLen int - growOnly bool + // table being transferred to + nextTable atomic.Pointer[mapTable[K, V]] + // resize control state: combines resize sequence number (upper 59 bits) and + // the current number of resize helpers (lower 5 bits); + // odd values of resize sequence mean in-progress resize + resizeCtl atomic.Uint64 + // only used along with resizeCond + resizeMu sync.Mutex + // used to wake up resize waiters (concurrent writes) + resizeCond sync.Cond + // transfer progress index for resize + resizeIdx atomic.Int64 + minTableLen int + growOnly bool } type mapTable[K comparable, V any] struct { - buckets []bucketPadded[K, V] + buckets []bucketPadded // striped counter for number of table entries; // used to determine if a table shrinking is needed // occupies min(buckets_memory/1024, 64KB) of memory @@ -125,16 +155,16 @@ type counterStripe struct { // bucketPadded is a CL-sized map bucket holding up to // entriesPerMapBucket entries. -type bucketPadded[K comparable, V any] struct { +type bucketPadded struct { //lint:ignore U1000 ensure each bucket takes two cache lines on both 32 and 64-bit archs - pad [cacheLineSize - unsafe.Sizeof(bucket[K, V]{})]byte - bucket[K, V] + pad [cacheLineSize - unsafe.Sizeof(bucket{})]byte + bucket } -type bucket[K comparable, V any] struct { - meta atomic.Uint64 - entries [entriesPerMapBucket]atomic.Pointer[entry[K, V]] // *entry - next atomic.Pointer[bucketPadded[K, V]] // *bucketPadded +type bucket struct { + meta uint64 + entries [entriesPerMapBucket]unsafe.Pointer // *entry + next unsafe.Pointer // *bucketPadded mu sync.Mutex } @@ -194,15 +224,15 @@ func (m *Map[K, V]) init() { m.minTableLen = defaultMinMapTableLen } m.resizeCond = *sync.NewCond(&m.resizeMu) - table := newMapTable[K, V](m.minTableLen) + table := newMapTable[K, V](m.minTableLen, maphash.MakeSeed()) m.minTableLen = len(table.buckets) m.table.Store(table) } -func newMapTable[K comparable, V any](minTableLen int) *mapTable[K, V] { - buckets := make([]bucketPadded[K, V], minTableLen) +func newMapTable[K comparable, V any](minTableLen int, seed maphash.Seed) *mapTable[K, V] { + buckets := make([]bucketPadded, minTableLen) for i := range buckets { - buckets[i].meta.Store(defaultMeta) + buckets[i].meta = defaultMeta } counterLen := minTableLen >> 10 if counterLen < minMapCounterLen { @@ -214,7 +244,7 @@ func newMapTable[K comparable, V any](minTableLen int) *mapTable[K, V] { t := &mapTable[K, V]{ buckets: buckets, size: counter, - seed: maphash.MakeSeed(), + seed: seed, } return t } @@ -246,22 +276,24 @@ func (m *Map[K, V]) Load(key K) (value V, ok bool) { bidx := uint64(len(table.buckets)-1) & h1 b := &table.buckets[bidx] for { - metaw := b.meta.Load() + metaw := atomic.LoadUint64(&b.meta) markedw := markZeroBytes(metaw^h2w) & metaMask for markedw != 0 { idx := firstMarkedByteIndex(markedw) - e := b.entries[idx].Load() - if e != nil { + eptr := atomic.LoadPointer(&b.entries[idx]) + if eptr != nil { + e := (*entry[K, V])(eptr) if e.key == key { return e.value, true } } markedw &= markedw - 1 } - b = b.next.Load() - if b == nil { + bptr := atomic.LoadPointer(&b.next) + if bptr == nil { return } + b = (*bucketPadded)(bptr) } } @@ -399,7 +431,7 @@ func (m *Map[K, V]) doCompute( for { compute_attempt: var ( - emptyb *bucketPadded[K, V] + emptyb *bucketPadded emptyidx int ) table := m.table.Load() @@ -415,12 +447,13 @@ func (m *Map[K, V]) doCompute( b := rootb load: for { - metaw := b.meta.Load() + metaw := atomic.LoadUint64(&b.meta) markedw := markZeroBytes(metaw^h2w) & metaMask for markedw != 0 { idx := firstMarkedByteIndex(markedw) - e := b.entries[idx].Load() - if e != nil { + eptr := atomic.LoadPointer(&b.entries[idx]) + if eptr != nil { + e := (*entry[K, V])(eptr) if e.key == key { if loadOp == loadOrComputeOp { return e.value, true @@ -430,23 +463,24 @@ func (m *Map[K, V]) doCompute( } markedw &= markedw - 1 } - b = b.next.Load() - if b == nil { + bptr := atomic.LoadPointer(&b.next) + if bptr == nil { if loadOp == loadAndDeleteOp { return *new(V), false } break load } + b = (*bucketPadded)(bptr) } } rootb.mu.Lock() // The following two checks must go in reverse to what's // in the resize method. - if m.resizeInProgress() { - // Resize is in progress. Wait, then go for another attempt. + if seq := resizeSeq(m.resizeCtl.Load()); seq&1 == 1 { + // Resize is in progress. Help with the transfer, then go for another attempt. rootb.mu.Unlock() - m.waitForResize() + m.helpResize(seq) goto compute_attempt } if m.newerTableExists(table) { @@ -456,12 +490,13 @@ func (m *Map[K, V]) doCompute( } b := rootb for { - metaw := b.meta.Load() + metaw := b.meta markedw := markZeroBytes(metaw^h2w) & metaMask for markedw != 0 { idx := firstMarkedByteIndex(markedw) - e := b.entries[idx].Load() - if e != nil { + eptr := b.entries[idx] + if eptr != nil { + e := (*entry[K, V])(eptr) if e.key == key { // In-place update/delete. // We get a copy of the value via an interface{} on each call, @@ -475,8 +510,8 @@ func (m *Map[K, V]) doCompute( // Deletion. // First we update the hash, then the entry. newmetaw := setByte(metaw, emptyMetaSlot, idx) - b.meta.Store(newmetaw) - b.entries[idx].Store(nil) + atomic.StoreUint64(&b.meta, newmetaw) + atomic.StorePointer(&b.entries[idx], nil) rootb.mu.Unlock() table.addSize(bidx, -1) // Might need to shrink the table if we left bucket empty. @@ -488,7 +523,7 @@ func (m *Map[K, V]) doCompute( newe := new(entry[K, V]) newe.key = key newe.value = newv - b.entries[idx].Store(newe) + atomic.StorePointer(&b.entries[idx], unsafe.Pointer(newe)) case CancelOp: newv = oldv } @@ -512,7 +547,7 @@ func (m *Map[K, V]) doCompute( emptyidx = idx } } - if b.next.Load() == nil { + if b.next == nil { if emptyb != nil { // Insertion into an existing bucket. var zeroV V @@ -526,8 +561,8 @@ func (m *Map[K, V]) doCompute( newe.key = key newe.value = newValue // First we update meta, then the entry. - emptyb.meta.Store(setByte(emptyb.meta.Load(), h2, emptyidx)) - emptyb.entries[emptyidx].Store(newe) + atomic.StoreUint64(&emptyb.meta, setByte(emptyb.meta, h2, emptyidx)) + atomic.StorePointer(&emptyb.entries[emptyidx], unsafe.Pointer(newe)) rootb.mu.Unlock() table.addSize(bidx, 1) return newValue, computeOnly @@ -549,19 +584,19 @@ func (m *Map[K, V]) doCompute( return newValue, false default: // Create and append a bucket. - newb := new(bucketPadded[K, V]) - newb.meta.Store(setByte(defaultMeta, h2, 0)) + newb := new(bucketPadded) + newb.meta = setByte(defaultMeta, h2, 0) newe := new(entry[K, V]) newe.key = key newe.value = newValue - newb.entries[0].Store(newe) - b.next.Store(newb) + newb.entries[0] = unsafe.Pointer(newe) + atomic.StorePointer(&b.next, unsafe.Pointer(newb)) rootb.mu.Unlock() table.addSize(bidx, 1) return newValue, computeOnly } } - b = b.next.Load() + b = (*bucketPadded)(b.next) } } } @@ -570,13 +605,21 @@ func (m *Map[K, V]) newerTableExists(table *mapTable[K, V]) bool { return table != m.table.Load() } -func (m *Map[K, V]) resizeInProgress() bool { - return m.resizing.Load() +func resizeSeq(ctl uint64) uint64 { + return ctl >> 5 +} + +func resizeHelpers(ctl uint64) uint64 { + return ctl & maxResizeHelpersLimit +} + +func resizeCtl(seq uint64, helpers uint64) uint64 { + return (seq << 5) | (helpers & maxResizeHelpersLimit) } func (m *Map[K, V]) waitForResize() { m.resizeMu.Lock() - for m.resizeInProgress() { + for resizeSeq(m.resizeCtl.Load())&1 == 1 { m.resizeCond.Wait() } m.resizeMu.Unlock() @@ -593,9 +636,9 @@ func (m *Map[K, V]) resize(knownTable *mapTable[K, V], hint mapResizeHint) { } } // Slow path. - if !m.resizing.CompareAndSwap(false, true) { - // Someone else started resize. Wait for it to finish. - m.waitForResize() + seq := resizeSeq(m.resizeCtl.Load()) + if seq&1 == 1 || !m.resizeCtl.CompareAndSwap(resizeCtl(seq, 0), resizeCtl(seq+1, 0)) { + m.helpResize(seq) return } var newTable *mapTable[K, V] @@ -604,64 +647,189 @@ func (m *Map[K, V]) resize(knownTable *mapTable[K, V], hint mapResizeHint) { switch hint { case mapGrowHint: // Grow the table with factor of 2. + // We must keep the same table seed here to keep the same hash codes + // allowing us to avoid locking destination buckets when resizing. m.totalGrowths.Add(1) - newTable = newMapTable[K, V](tableLen << 1) + newTable = newMapTable[K, V](tableLen<<1, table.seed) case mapShrinkHint: shrinkThreshold := int64((tableLen * entriesPerMapBucket) / mapShrinkFraction) if tableLen > m.minTableLen && table.sumSize() <= shrinkThreshold { // Shrink the table with factor of 2. + // It's fine to generate a new seed since full locking + // is required anyway. m.totalShrinks.Add(1) - newTable = newMapTable[K, V](tableLen >> 1) + newTable = newMapTable[K, V](tableLen>>1, maphash.MakeSeed()) } else { // No need to shrink. Wake up all waiters and give up. m.resizeMu.Lock() - m.resizing.Store(false) + m.resizeCtl.Store(resizeCtl(seq+2, 0)) m.resizeCond.Broadcast() m.resizeMu.Unlock() return } case mapClearHint: - newTable = newMapTable[K, V](m.minTableLen) + newTable = newMapTable[K, V](m.minTableLen, maphash.MakeSeed()) default: panic(fmt.Sprintf("unexpected resize hint: %d", hint)) } + // Copy the data only if we're not clearing the map. if hint != mapClearHint { - for i := 0; i < tableLen; i++ { - copied := copyBucket(&table.buckets[i], newTable) - newTable.addSizePlain(uint64(i), copied) - } + // Set up cooperative transfer state. + // Next table must be published as the last step. + m.resizeIdx.Store(0) + m.nextTable.Store(newTable) + // Copy the buckets. + m.transfer(table, newTable) + } + + // We're about to publish the new table, but before that + // we must wait for all helpers to finish. + for resizeHelpers(m.resizeCtl.Load()) != 0 { + runtime.Gosched() } - // Publish the new table and wake up all waiters. m.table.Store(newTable) + m.nextTable.Store(nil) + ctl := resizeCtl(seq+1, 0) + newCtl := resizeCtl(seq+2, 0) + // Increment the sequence number and wake up all waiters. m.resizeMu.Lock() - m.resizing.Store(false) + // There may be slowpoke helpers who have just incremented + // the helper counter. This CAS loop makes sure to wait + // for them to back off. + for !m.resizeCtl.CompareAndSwap(ctl, newCtl) { + runtime.Gosched() + } m.resizeCond.Broadcast() m.resizeMu.Unlock() } -func copyBucket[K comparable, V any]( - b *bucketPadded[K, V], +func (m *Map[K, V]) helpResize(seq uint64) { + for { + table := m.table.Load() + nextTable := m.nextTable.Load() + if resizeSeq(m.resizeCtl.Load()) == seq { + if nextTable == nil || nextTable == table { + // Carry on until the next table is set by the main + // resize goroutine or until the resize finishes. + runtime.Gosched() + continue + } + // The resize is still in-progress, so let's try registering + // as a helper. + for { + ctl := m.resizeCtl.Load() + if resizeSeq(ctl) != seq || resizeHelpers(ctl) >= uint64(maxResizeHelpers) { + // The resize has ended or there are too many helpers. + break + } + if m.resizeCtl.CompareAndSwap(ctl, ctl+1) { + // Yay, we're a resize helper! + m.transfer(table, nextTable) + // Don't forget to unregister as a helper. + m.resizeCtl.Add(^uint64(0)) + break + } + } + m.waitForResize() + } + break + } +} + +func (m *Map[K, V]) transfer(table, newTable *mapTable[K, V]) { + tableLen := len(table.buckets) + newTableLen := len(newTable.buckets) + stride := (tableLen >> 3) / int(maxResizeHelpers) + if stride < minResizeTransferStride { + stride = minResizeTransferStride + } + for { + // Claim work by incrementing resizeIdx. + nextIdx := m.resizeIdx.Add(int64(stride)) + start := int(nextIdx) - stride + if start < 0 { + start = 0 + } + if start > tableLen { + break + } + end := int(nextIdx) + if end > tableLen { + end = tableLen + } + // Transfer buckets in this range. + total := 0 + if newTableLen > tableLen { + // We're growing the table with 2x multiplier, so entries from a N bucket can + // only be transferred to N and 2*N buckets in the new table. Thus, destination + // buckets written by the resize helpers don't intersect, so we don't need to + // acquire locks in the destination buckets. + for i := start; i < end; i++ { + total += transferBucketUnsafe(&table.buckets[i], newTable) + } + } else { + // We're shrinking the table, so all locks must be acquired. + for i := start; i < end; i++ { + total += transferBucket(&table.buckets[i], newTable) + } + } + // The exact counter stripe doesn't matter here, so pick up the one + // that corresponds to the start value to avoid contention. + newTable.addSize(uint64(start), total) + } +} + +// Doesn't acquire dest bucket lock. +func transferBucketUnsafe[K comparable, V any]( + b *bucketPadded, destTable *mapTable[K, V], ) (copied int) { rootb := b rootb.mu.Lock() for { for i := 0; i < entriesPerMapBucket; i++ { - if e := b.entries[i].Load(); e != nil { + if eptr := b.entries[i]; eptr != nil { + e := (*entry[K, V])(eptr) hash := maphash.Comparable(destTable.seed, e.key) bidx := uint64(len(destTable.buckets)-1) & h1(hash) destb := &destTable.buckets[bidx] - appendToBucket(h2(hash), b.entries[i].Load(), destb) + appendToBucket(h2(hash), e, destb) copied++ } } - if next := b.next.Load(); next == nil { + if b.next == nil { rootb.mu.Unlock() return - } else { - b = next } + b = (*bucketPadded)(b.next) + } +} + +func transferBucket[K comparable, V any]( + b *bucketPadded, + destTable *mapTable[K, V], +) (copied int) { + rootb := b + rootb.mu.Lock() + for { + for i := 0; i < entriesPerMapBucket; i++ { + if eptr := b.entries[i]; eptr != nil { + e := (*entry[K, V])(eptr) + hash := maphash.Comparable(destTable.seed, e.key) + bidx := uint64(len(destTable.buckets)-1) & h1(hash) + destb := &destTable.buckets[bidx] + destb.mu.Lock() + appendToBucket(h2(hash), e, destb) + destb.mu.Unlock() + copied++ + } + } + if b.next == nil { + rootb.mu.Unlock() + return + } + b = (*bucketPadded)(b.next) } } @@ -691,16 +859,15 @@ func (m *Map[K, V]) Range(f func(key K, value V) bool) { rootb.mu.Lock() for { for i := 0; i < entriesPerMapBucket; i++ { - if entry := b.entries[i].Load(); entry != nil { - bentries = append(bentries, entry) + if b.entries[i] != nil { + bentries = append(bentries, (*entry[K, V])(b.entries[i])) } } - if next := b.next.Load(); next == nil { + if b.next == nil { rootb.mu.Unlock() break - } else { - b = next } + b = (*bucketPadded)(b.next) } // Call the function for all copied entries. for j, e := range bentries { @@ -727,24 +894,25 @@ func (m *Map[K, V]) Size() int { return int(m.table.Load().sumSize()) } -func appendToBucket[K comparable, V any](h2 uint8, e *entry[K, V], b *bucketPadded[K, V]) { +// It is safe to use plain stores here because the destination bucket must be +// either locked or exclusively written to by the helper during resize. +func appendToBucket[K comparable, V any](h2 uint8, e *entry[K, V], b *bucketPadded) { for { for i := 0; i < entriesPerMapBucket; i++ { - if b.entries[i].Load() == nil { - b.meta.Store(setByte(b.meta.Load(), h2, i)) - b.entries[i].Store(e) + if b.entries[i] == nil { + b.meta = setByte(b.meta, h2, i) + b.entries[i] = unsafe.Pointer(e) return } } - if next := b.next.Load(); next == nil { - newb := new(bucketPadded[K, V]) - newb.meta.Store(setByte(defaultMeta, h2, 0)) - newb.entries[0].Store(e) - b.next.Store(newb) + if b.next == nil { + newb := new(bucketPadded) + newb.meta = setByte(defaultMeta, h2, 0) + newb.entries[0] = unsafe.Pointer(e) + b.next = unsafe.Pointer(newb) return - } else { - b = next } + b = (*bucketPadded)(b.next) } } @@ -753,11 +921,6 @@ func (table *mapTable[K, V]) addSize(bucketIdx uint64, delta int) { atomic.AddInt64(&table.size[cidx].c, int64(delta)) } -func (table *mapTable[K, V]) addSizePlain(bucketIdx uint64, delta int) { - cidx := uint64(len(table.size)-1) & bucketIdx - table.size[cidx].c += int64(delta) -} - func (table *mapTable[K, V]) sumSize() int64 { sum := int64(0) for i := range table.size { @@ -856,7 +1019,7 @@ func (m *Map[K, V]) Stats() MapStats { nentriesLocal := 0 stats.Capacity += entriesPerMapBucket for i := 0; i < entriesPerMapBucket; i++ { - if b.entries[i].Load() != nil { + if atomic.LoadPointer(&b.entries[i]) != nil { stats.Size++ nentriesLocal++ } @@ -865,11 +1028,10 @@ func (m *Map[K, V]) Stats() MapStats { if nentriesLocal == 0 { stats.EmptyBuckets++ } - if next := b.next.Load(); next == nil { + if b.next == nil { break - } else { - b = next } + b = (*bucketPadded)(atomic.LoadPointer(&b.next)) stats.TotalBuckets++ } if nentries < stats.MinEntries { @@ -906,6 +1068,15 @@ func nextPowOf2(v uint32) uint32 { return v } +func parallelism() uint32 { + maxProcs := uint32(runtime.GOMAXPROCS(0)) + numCores := uint32(runtime.NumCPU()) + if maxProcs < numCores { + return maxProcs + } + return numCores +} + func broadcast(b uint8) uint64 { return 0x101010101010101 * uint64(b) } @@ -920,6 +1091,7 @@ func markZeroBytes(w uint64) uint64 { return ((w - 0x0101010101010101) & (^w) & 0x8080808080808080) } +// Sets byte of the input word at the specified index to the given value. func setByte(w uint64, b uint8, idx int) uint64 { shift := idx << 3 return (w &^ (0xff << shift)) | (uint64(b) << shift) diff --git a/clash-meta-android/core/src/foss/golang/clash/common/xsync/map_test.go b/clash-meta-android/core/src/foss/golang/clash/common/xsync/map_test.go index b40d412bbb..72ebfaea9d 100644 --- a/clash-meta-android/core/src/foss/golang/clash/common/xsync/map_test.go +++ b/clash-meta-android/core/src/foss/golang/clash/common/xsync/map_test.go @@ -3,6 +3,7 @@ package xsync import ( "math" "math/rand" + "runtime" "strconv" "sync" "sync/atomic" @@ -53,11 +54,11 @@ func runParallel(b *testing.B, benchFn func(pb *testing.PB)) { } func TestMap_BucketStructSize(t *testing.T) { - size := unsafe.Sizeof(bucketPadded[string, int64]{}) + size := unsafe.Sizeof(bucketPadded{}) if size != 64 { t.Fatalf("size of 64B (one cache line) is expected, got: %d", size) } - size = unsafe.Sizeof(bucketPadded[struct{}, int32]{}) + size = unsafe.Sizeof(bucketPadded{}) if size != 64 { t.Fatalf("size of 64B (one cache line) is expected, got: %d", size) } @@ -743,10 +744,7 @@ func TestNewMapGrowOnly_OnlyShrinksOnClear(t *testing.T) { } func TestMapResize(t *testing.T) { - testMapResize(t, NewMap[string, int]()) -} - -func testMapResize(t *testing.T, m *Map[string, int]) { + m := NewMap[string, int]() const numEntries = 100_000 for i := 0; i < numEntries; i++ { @@ -810,6 +808,147 @@ func TestMapResize_CounterLenLimit(t *testing.T) { } } +func testParallelResize(t *testing.T, numGoroutines int) { + m := NewMap[int, int]() + + // Fill the map to trigger resizing + const initialEntries = 10000 + const newEntries = 5000 + for i := 0; i < initialEntries; i++ { + m.Store(i, i*2) + } + + // Start concurrent operations that should trigger helping behavior + var wg sync.WaitGroup + + // Launch goroutines that will encounter resize operations + for g := 0; g < numGoroutines; g++ { + wg.Add(1) + go func(goroutineID int) { + defer wg.Done() + + // Perform many operations to trigger resize and helping + for i := 0; i < newEntries; i++ { + key := goroutineID*newEntries + i + initialEntries + m.Store(key, key*2) + + // Verify the value + if val, ok := m.Load(key); !ok || val != key*2 { + t.Errorf("Failed to load key %d: got %v, %v", key, val, ok) + return + } + } + }(g) + } + + wg.Wait() + + // Verify all entries are present + finalSize := m.Size() + expectedSize := initialEntries + numGoroutines*newEntries + if finalSize != expectedSize { + t.Errorf("Expected size %d, got %d", expectedSize, finalSize) + } + + stats := m.Stats() + if stats.TotalGrowths == 0 { + t.Error("Expected at least one table growth due to concurrent operations") + } +} + +func TestMapParallelResize(t *testing.T) { + testParallelResize(t, 1) + testParallelResize(t, runtime.GOMAXPROCS(0)) + testParallelResize(t, 100) +} + +func testParallelResizeWithSameKeys(t *testing.T, numGoroutines int) { + m := NewMap[int, int]() + + // Fill the map to trigger resizing + const entries = 1000 + for i := 0; i < entries; i++ { + m.Store(2*i, 2*i) + } + + // Start concurrent operations that should trigger helping behavior + var wg sync.WaitGroup + + // Launch goroutines that will encounter resize operations + for g := 0; g < numGoroutines; g++ { + wg.Add(1) + go func(goroutineID int) { + defer wg.Done() + for i := 0; i < 10*entries; i++ { + m.Store(i, i) + } + }(g) + } + + wg.Wait() + + // Verify all entries are present + finalSize := m.Size() + expectedSize := 10 * entries + if finalSize != expectedSize { + t.Errorf("Expected size %d, got %d", expectedSize, finalSize) + } + + stats := m.Stats() + if stats.TotalGrowths == 0 { + t.Error("Expected at least one table growth due to concurrent operations") + } +} + +func TestMapParallelResize_IntersectingKeys(t *testing.T) { + testParallelResizeWithSameKeys(t, 1) + testParallelResizeWithSameKeys(t, runtime.GOMAXPROCS(0)) + testParallelResizeWithSameKeys(t, 100) +} + +func testParallelShrinking(t *testing.T, numGoroutines int) { + m := NewMap[int, int]() + + // Fill the map to trigger resizing + const entries = 100000 + for i := 0; i < entries; i++ { + m.Store(i, i) + } + + // Start concurrent operations that should trigger helping behavior + var wg sync.WaitGroup + + // Launch goroutines that will encounter resize operations + for g := 0; g < numGoroutines; g++ { + wg.Add(1) + go func(goroutineID int) { + defer wg.Done() + for i := 0; i < entries; i++ { + m.Delete(i) + } + }(g) + } + + wg.Wait() + + // Verify all entries are present + finalSize := m.Size() + if finalSize != 0 { + t.Errorf("Expected size 0, got %d", finalSize) + } + + stats := m.Stats() + if stats.TotalShrinks == 0 { + t.Error("Expected at least one table shrinking due to concurrent operations") + } +} + +func TestMapParallelShrinking(t *testing.T) { + testParallelShrinking(t, 1) + testParallelShrinking(t, runtime.GOMAXPROCS(0)) + testParallelShrinking(t, 100) +} + func parallelSeqMapGrower(m *Map[int, int], numEntries int, positive bool, cdone chan bool) { for i := 0; i < numEntries; i++ { if positive { @@ -1459,7 +1598,7 @@ func BenchmarkMapRange(b *testing.B) { } // Benchmarks noop performance of Compute -func BenchmarkCompute(b *testing.B) { +func BenchmarkMapCompute(b *testing.B) { tests := []struct { Name string Op ComputeOp @@ -1487,6 +1626,57 @@ func BenchmarkCompute(b *testing.B) { } } +func BenchmarkMapParallelRehashing(b *testing.B) { + tests := []struct { + name string + goroutines int + numEntries int + }{ + {"1goroutine_10M", 1, 10_000_000}, + {"4goroutines_10M", 4, 10_000_000}, + {"8goroutines_10M", 8, 10_000_000}, + } + for _, test := range tests { + b.Run(test.name, func(b *testing.B) { + for i := 0; i < b.N; i++ { + m := NewMap[int, int]() + + var wg sync.WaitGroup + entriesPerGoroutine := test.numEntries / test.goroutines + + start := time.Now() + + for g := 0; g < test.goroutines; g++ { + wg.Add(1) + go func(goroutineID int) { + defer wg.Done() + base := goroutineID * entriesPerGoroutine + for j := 0; j < entriesPerGoroutine; j++ { + key := base + j + m.Store(key, key) + } + }(g) + } + + wg.Wait() + duration := time.Since(start) + + b.ReportMetric(float64(test.numEntries)/duration.Seconds(), "entries/s") + + finalSize := m.Size() + if finalSize != test.numEntries { + b.Fatalf("Expected size %d, got %d", test.numEntries, finalSize) + } + + stats := m.Stats() + if stats.TotalGrowths == 0 { + b.Error("Expected at least one table growth during rehashing") + } + } + }) + } +} + func TestNextPowOf2(t *testing.T) { if nextPowOf2(0) != 1 { t.Error("nextPowOf2 failed") diff --git a/clash-meta-android/core/src/foss/golang/clash/component/memory/memory_darwin.go b/clash-meta-android/core/src/foss/golang/clash/component/memory/memory_darwin.go index 1dd33af32a..63356fe539 100644 --- a/clash-meta-android/core/src/foss/golang/clash/component/memory/memory_darwin.go +++ b/clash-meta-android/core/src/foss/golang/clash/component/memory/memory_darwin.go @@ -1,9 +1,9 @@ package memory import ( + "syscall" "unsafe" - - "github.com/ebitengine/purego" + _ "unsafe" ) const PROC_PIDTASKINFO = 4 @@ -29,24 +29,12 @@ type ProcTaskInfo struct { Priority int32 } -const System = "/usr/lib/libSystem.B.dylib" - -type ProcPidInfoFunc func(pid, flavor int32, arg uint64, buffer uintptr, bufferSize int32) int32 - -const ProcPidInfoSym = "proc_pidinfo" - func GetMemoryInfo(pid int32) (*MemoryInfoStat, error) { - lib, err := purego.Dlopen(System, purego.RTLD_LAZY|purego.RTLD_GLOBAL) - if err != nil { - return nil, err - } - defer purego.Dlclose(lib) - - var procPidInfo ProcPidInfoFunc - purego.RegisterLibFunc(&procPidInfo, lib, ProcPidInfoSym) - var ti ProcTaskInfo - procPidInfo(pid, PROC_PIDTASKINFO, 0, uintptr(unsafe.Pointer(&ti)), int32(unsafe.Sizeof(ti))) + _, _, errno := syscall_syscall6(proc_pidinfo_trampoline_addr, uintptr(pid), PROC_PIDTASKINFO, 0, uintptr(unsafe.Pointer(&ti)), unsafe.Sizeof(ti), 0) + if errno != 0 { + return nil, errno + } ret := &MemoryInfoStat{ RSS: uint64(ti.Resident_size), @@ -54,3 +42,26 @@ func GetMemoryInfo(pid int32) (*MemoryInfoStat, error) { } return ret, nil } + +var proc_pidinfo_trampoline_addr uintptr + +//go:cgo_import_dynamic proc_pidinfo proc_pidinfo "/usr/lib/libSystem.B.dylib" + +// from golang.org/x/sys@v0.30.0/unix/syscall_darwin_libSystem.go + +// Implemented in the runtime package (runtime/sys_darwin.go) +func syscall_syscall(fn, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) +func syscall_syscall6(fn, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err syscall.Errno) +func syscall_syscall6X(fn, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err syscall.Errno) +func syscall_syscall9(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1, r2 uintptr, err syscall.Errno) // 32-bit only +func syscall_rawSyscall(fn, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) +func syscall_rawSyscall6(fn, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err syscall.Errno) +func syscall_syscallPtr(fn, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) + +//go:linkname syscall_syscall syscall.syscall +//go:linkname syscall_syscall6 syscall.syscall6 +//go:linkname syscall_syscall6X syscall.syscall6X +//go:linkname syscall_syscall9 syscall.syscall9 +//go:linkname syscall_rawSyscall syscall.rawSyscall +//go:linkname syscall_rawSyscall6 syscall.rawSyscall6 +//go:linkname syscall_syscallPtr syscall.syscallPtr diff --git a/clash-meta-android/core/src/foss/golang/clash/component/memory/memory_darwin_amd64.s b/clash-meta-android/core/src/foss/golang/clash/component/memory/memory_darwin_amd64.s new file mode 100644 index 0000000000..4c17419a57 --- /dev/null +++ b/clash-meta-android/core/src/foss/golang/clash/component/memory/memory_darwin_amd64.s @@ -0,0 +1,9 @@ +// go run mkasm.go darwin amd64 +// Code generated by the command above; DO NOT EDIT. + +#include "textflag.h" + +TEXT proc_pidinfo_trampoline<>(SB),NOSPLIT,$0-0 + JMP proc_pidinfo(SB) +GLOBL ·proc_pidinfo_trampoline_addr(SB), RODATA, $8 +DATA ·proc_pidinfo_trampoline_addr(SB)/8, $proc_pidinfo_trampoline<>(SB) diff --git a/clash-meta-android/core/src/foss/golang/clash/component/memory/memory_darwin_arm64.s b/clash-meta-android/core/src/foss/golang/clash/component/memory/memory_darwin_arm64.s new file mode 100644 index 0000000000..645bc68042 --- /dev/null +++ b/clash-meta-android/core/src/foss/golang/clash/component/memory/memory_darwin_arm64.s @@ -0,0 +1,9 @@ +// go run mkasm.go darwin arm64 +// Code generated by the command above; DO NOT EDIT. + +#include "textflag.h" + +TEXT proc_pidinfo_trampoline<>(SB),NOSPLIT,$0-0 + JMP proc_pidinfo(SB) +GLOBL ·proc_pidinfo_trampoline_addr(SB), RODATA, $8 +DATA ·proc_pidinfo_trampoline_addr(SB)/8, $proc_pidinfo_trampoline<>(SB) diff --git a/clash-meta-android/core/src/foss/golang/clash/config/config.go b/clash-meta-android/core/src/foss/golang/clash/config/config.go index efc8903248..301126a61e 100644 --- a/clash-meta-android/core/src/foss/golang/clash/config/config.go +++ b/clash-meta-android/core/src/foss/golang/clash/config/config.go @@ -162,6 +162,7 @@ type DNS struct { FakeIPRange6 netip.Prefix FakeIPPool6 *fakeip.Pool FakeIPSkipper *fakeip.Skipper + FakeIPTTL int NameServerPolicy []dns.Policy ProxyServerNameserver []dns.NameServer DirectNameServer []dns.NameServer @@ -228,6 +229,7 @@ type RawDNS struct { FakeIPRange6 string `yaml:"fake-ip-range6" json:"fake-ip-range6"` FakeIPFilter []string `yaml:"fake-ip-filter" json:"fake-ip-filter"` FakeIPFilterMode C.FilterMode `yaml:"fake-ip-filter-mode" json:"fake-ip-filter-mode"` + FakeIPTTL int `yaml:"fake-ip-ttl" json:"fake-ip-ttl"` DefaultNameserver []string `yaml:"default-nameserver" json:"default-nameserver"` CacheAlgorithm string `yaml:"cache-algorithm" json:"cache-algorithm"` CacheMaxSize int `yaml:"cache-max-size" json:"cache-max-size"` @@ -490,6 +492,7 @@ func DefaultRawConfig() *RawConfig { IPv6Timeout: 100, EnhancedMode: C.DNSMapping, FakeIPRange: "198.18.0.1/16", + FakeIPTTL: 1, FallbackFilter: RawFallbackFilter{ GeoIP: true, GeoIPCode: "CN", @@ -1458,6 +1461,7 @@ func parseDNS(rawCfg *RawConfig, ruleProviders map[string]P.RuleProvider) (*DNS, Mode: cfg.FakeIPFilterMode, } dnsCfg.FakeIPSkipper = skipper + dnsCfg.FakeIPTTL = cfg.FakeIPTTL if dnsCfg.FakeIPRange.IsValid() { pool, err := fakeip.New(fakeip.Options{ diff --git a/clash-meta-android/core/src/foss/golang/clash/constant/adapters.go b/clash-meta-android/core/src/foss/golang/clash/constant/adapters.go index 97e9235b5c..1cd146c107 100644 --- a/clash-meta-android/core/src/foss/golang/clash/constant/adapters.go +++ b/clash-meta-android/core/src/foss/golang/clash/constant/adapters.go @@ -44,6 +44,7 @@ const ( Ssh Mieru AnyTLS + Sudoku ) const ( @@ -230,6 +231,8 @@ func (at AdapterType) String() string { return "Mieru" case AnyTLS: return "AnyTLS" + case Sudoku: + return "Sudoku" case Relay: return "Relay" case Selector: diff --git a/clash-meta-android/core/src/foss/golang/clash/constant/metadata.go b/clash-meta-android/core/src/foss/golang/clash/constant/metadata.go index 23cdcd8616..63f08d43b8 100644 --- a/clash-meta-android/core/src/foss/golang/clash/constant/metadata.go +++ b/clash-meta-android/core/src/foss/golang/clash/constant/metadata.go @@ -39,6 +39,7 @@ const ( HYSTERIA2 ANYTLS MIERU + SUDOKU INNER ) @@ -112,6 +113,8 @@ func (t Type) String() string { return "AnyTLS" case MIERU: return "Mieru" + case SUDOKU: + return "Sudoku" case INNER: return "Inner" default: @@ -154,6 +157,8 @@ func ParseType(t string) (*Type, error) { res = ANYTLS case "MIERU": res = MIERU + case "SUDOKU": + res = SUDOKU case "INNER": res = INNER default: diff --git a/clash-meta-android/core/src/foss/golang/clash/dns/enhancer.go b/clash-meta-android/core/src/foss/golang/clash/dns/enhancer.go index c84f8c32fe..0661362fd7 100644 --- a/clash-meta-android/core/src/foss/golang/clash/dns/enhancer.go +++ b/clash-meta-android/core/src/foss/golang/clash/dns/enhancer.go @@ -1,6 +1,7 @@ package dns import ( + "errors" "net/netip" "github.com/metacubex/mihomo/common/lru" @@ -13,6 +14,7 @@ type ResolverEnhancer struct { fakeIPPool *fakeip.Pool fakeIPPool6 *fakeip.Pool fakeIPSkipper *fakeip.Skipper + fakeIPTTL int mapping *lru.LruCache[netip.Addr, string] useHosts bool } @@ -31,11 +33,15 @@ func (h *ResolverEnhancer) IsExistFakeIP(ip netip.Addr) bool { } if pool := h.fakeIPPool; pool != nil { - return pool.Exist(ip) + if pool.Exist(ip) { + return true + } } if pool6 := h.fakeIPPool6; pool6 != nil { - return pool6.Exist(ip) + if pool6.Exist(ip) { + return true + } } return false @@ -47,11 +53,15 @@ func (h *ResolverEnhancer) IsFakeIP(ip netip.Addr) bool { } if pool := h.fakeIPPool; pool != nil { - return pool.IPNet().Contains(ip) && ip != pool.Gateway() && ip != pool.Broadcast() + if pool.IPNet().Contains(ip) && ip != pool.Gateway() && ip != pool.Broadcast() { + return true + } } if pool6 := h.fakeIPPool6; pool6 != nil { - return pool6.IPNet().Contains(ip) && ip != pool6.Gateway() && ip != pool6.Broadcast() + if pool6.IPNet().Contains(ip) && ip != pool6.Gateway() && ip != pool6.Broadcast() { + return true + } } return false @@ -63,11 +73,15 @@ func (h *ResolverEnhancer) IsFakeBroadcastIP(ip netip.Addr) bool { } if pool := h.fakeIPPool; pool != nil { - return pool.Broadcast() == ip + if pool.Broadcast() == ip { + return true + } } if pool6 := h.fakeIPPool6; pool6 != nil { - return pool6.Broadcast() == ip + if pool6.Broadcast() == ip { + return true + } } return false @@ -102,11 +116,19 @@ func (h *ResolverEnhancer) InsertHostByIP(ip netip.Addr, host string) { } func (h *ResolverEnhancer) FlushFakeIP() error { + var errs []error if pool := h.fakeIPPool; pool != nil { - return pool.FlushFakeIP() + if err := pool.FlushFakeIP(); err != nil { + errs = append(errs, err) + } } if pool6 := h.fakeIPPool6; pool6 != nil { - return pool6.FlushFakeIP() + if err := pool6.FlushFakeIP(); err != nil { + errs = append(errs, err) + } + } + if len(errs) > 0 { + return errors.Join(errs...) } return nil } @@ -141,6 +163,7 @@ type EnhancerConfig struct { FakeIPPool *fakeip.Pool FakeIPPool6 *fakeip.Pool FakeIPSkipper *fakeip.Skipper + FakeIPTTL int UseHosts bool } @@ -156,6 +179,10 @@ func NewEnhancer(cfg EnhancerConfig) *ResolverEnhancer { e.fakeIPPool6 = cfg.FakeIPPool6 } e.fakeIPSkipper = cfg.FakeIPSkipper + e.fakeIPTTL = cfg.FakeIPTTL + if e.fakeIPTTL < 1 { + e.fakeIPTTL = 1 + } e.mapping = lru.New(lru.WithSize[netip.Addr, string](4096)) } diff --git a/clash-meta-android/core/src/foss/golang/clash/dns/middleware.go b/clash-meta-android/core/src/foss/golang/clash/dns/middleware.go index 8472eb94e2..180cf00efb 100644 --- a/clash-meta-android/core/src/foss/golang/clash/dns/middleware.go +++ b/clash-meta-android/core/src/foss/golang/clash/dns/middleware.go @@ -64,7 +64,7 @@ func withHosts(mapping *lru.LruCache[netip.Addr, string]) middleware { if mapping != nil { mapping.SetWithExpire(ipAddr, host, time.Now().Add(time.Second*10)) } - } else if q.Qtype == D.TypeAAAA { + } else if ipAddr.Is6() && q.Qtype == D.TypeAAAA { rr := &D.AAAA{} rr.Hdr = D.RR_Header{Name: q.Name, Rrtype: D.TypeAAAA, Class: D.ClassINET, Ttl: 10} rr.AAAA = ipAddr.AsSlice() @@ -146,7 +146,7 @@ func withMapping(mapping *lru.LruCache[netip.Addr, string]) middleware { } } -func withFakeIP(skipper *fakeip.Skipper, fakePool *fakeip.Pool, fakePool6 *fakeip.Pool) middleware { +func withFakeIP(skipper *fakeip.Skipper, fakePool *fakeip.Pool, fakePool6 *fakeip.Pool, fakeIPTTL int) middleware { return func(next handler) handler { return func(ctx *icontext.DNSContext, r *D.Msg) (*D.Msg, error) { q := r.Question[0] @@ -186,7 +186,7 @@ func withFakeIP(skipper *fakeip.Skipper, fakePool *fakeip.Pool, fakePool6 *fakei msg.Answer = []D.RR{rr} ctx.SetType(icontext.DNSTypeFakeIP) - setMsgTTL(msg, 1) + setMsgTTL(msg, uint32(fakeIPTTL)) msg.SetRcode(r, D.RcodeSuccess) msg.Authoritative = true msg.RecursionAvailable = true @@ -238,7 +238,7 @@ func newHandler(resolver *Resolver, mapper *ResolverEnhancer) handler { } if mapper.mode == C.DNSFakeIP { - middlewares = append(middlewares, withFakeIP(mapper.fakeIPSkipper, mapper.fakeIPPool, mapper.fakeIPPool6)) + middlewares = append(middlewares, withFakeIP(mapper.fakeIPSkipper, mapper.fakeIPPool, mapper.fakeIPPool6, mapper.fakeIPTTL)) } if mapper.mode != C.DNSNormal { diff --git a/clash-meta-android/core/src/foss/golang/clash/docs/config.yaml b/clash-meta-android/core/src/foss/golang/clash/docs/config.yaml index e71b06f482..bef2c65914 100644 --- a/clash-meta-android/core/src/foss/golang/clash/docs/config.yaml +++ b/clash-meta-android/core/src/foss/golang/clash/docs/config.yaml @@ -275,6 +275,8 @@ dns: # 配置fake-ip-filter的匹配模式,默认为blacklist,即如果匹配成功不返回fake-ip # 可设置为whitelist,即只有匹配成功才返回fake-ip fake-ip-filter-mode: blacklist + # 配置fakeip查询返回的TTL,非必要情况下请勿修改 + fake-ip-ttl: 1 # use-hosts: true # 查询 hosts @@ -1027,7 +1029,7 @@ proxies: # socks5 server: 1.2.3.4 port: 2999 # port-range: 2090-2099 #(不可同时填写 port 和 port-range) - transport: TCP # 只支持 TCP + transport: TCP # 支持 TCP 或者 UDP udp: true # 支持 UDP over TCP username: user password: password @@ -1036,6 +1038,18 @@ proxies: # socks5 # 如果想开启 0-RTT 握手,请设置为 HANDSHAKE_NO_WAIT,否则请设置为 HANDSHAKE_STANDARD。默认值为 HANDSHAKE_STANDARD # handshake-mode: HANDSHAKE_STANDARD + # sudoku + - name: sudoku + type: sudoku + server: serverip # 1.2.3.4 + port: 443 + key: "" # 如果你使用sudoku生成的ED25519密钥对,请填写密钥对中的私钥,否则填入和服务端相同的uuid + aead-method: chacha20-poly1305 # 可选值:chacha20-poly1305、aes-128-gcm、none 我们保证在none的情况下sudoku混淆层仍然确保安全 + padding-min: 2 # 最小填充字节数 + padding-max: 7 # 最大填充字节数 + table-type: prefer_ascii # 可选值:prefer_ascii、prefer_entropy 前者全ascii映射,后者保证熵值(汉明1)低于3 + http-mask: true # 是否启用http掩码 + # anytls - name: anytls type: anytls @@ -1565,6 +1579,19 @@ listeners: username1: password1 username2: password2 + - name: sudoku-in-1 + type: sudoku + port: 8443 # 仅支持单端口 + listen: 0.0.0.0 + key: "" # 如果你使用sudoku生成的ED25519密钥对,此处是密钥对中的公钥,当然,你也可以仅仅使用任意uuid充当key + aead-method: chacha20-poly1305 # 支持chacha20-poly1305或者aes-128-gcm以及none,sudoku的混淆层可以确保none情况下数据安全 + padding-min: 1 # 填充最小长度 + padding-max: 15 # 填充最大长度,均不建议过大 + seed: "" # 如果你不使用ED25519密钥对,就请填入uuid,否则仍然是公钥 + table-type: prefer_ascii # 可选值:prefer_ascii、prefer_entropy 前者全ascii映射,后者保证熵值(汉明1)低于3 + handshake-timeout: 5 # optional + + - name: trojan-in-1 type: trojan port: 10819 # 支持使用ports格式,例如200,302 or 200,204,401-429,501-503 @@ -1713,3 +1740,4 @@ listeners: # alpn: # - h3 # max-udp-relay-packet-size: 1500 + diff --git a/clash-meta-android/core/src/foss/golang/clash/go.mod b/clash-meta-android/core/src/foss/golang/clash/go.mod index 17f18b2deb..35dfcbe2b1 100644 --- a/clash-meta-android/core/src/foss/golang/clash/go.mod +++ b/clash-meta-android/core/src/foss/golang/clash/go.mod @@ -6,8 +6,7 @@ require ( github.com/bahlo/generic-list-go v0.2.0 github.com/coreos/go-iptables v0.8.0 github.com/dlclark/regexp2 v1.11.5 - github.com/ebitengine/purego v0.9.1 - github.com/enfein/mieru/v3 v3.22.1 + github.com/enfein/mieru/v3 v3.26.0 github.com/go-chi/chi/v5 v5.2.3 github.com/go-chi/render v1.0.3 github.com/gobwas/ws v1.4.0 @@ -23,7 +22,7 @@ require ( github.com/metacubex/chacha v0.1.5 github.com/metacubex/fswatch v0.1.1 github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 - github.com/metacubex/kcp-go v0.0.0-20251105084629-8c93f4bf37be + github.com/metacubex/kcp-go v0.0.0-20251111012849-7455698490e9 github.com/metacubex/quic-go v0.55.1-0.20251024060151-bd465f127128 github.com/metacubex/randv2 v0.2.0 github.com/metacubex/restls-client-go v0.1.7 @@ -33,17 +32,18 @@ require ( 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 - github.com/metacubex/sing-tun v0.4.9 + github.com/metacubex/sing-tun v0.4.10 github.com/metacubex/sing-vmess v0.2.4 github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f - github.com/metacubex/smux v0.0.0-20250922175018-15c9a6a78719 - github.com/metacubex/tfo-go v0.0.0-20251024101424-368b42b59148 + github.com/metacubex/smux v0.0.0-20251111013112-03f8d12dafc1 + github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443 github.com/metacubex/utls v1.8.3 github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f github.com/miekg/dns v1.1.63 // lastest version compatible with golang1.20 github.com/mroth/weightedrand/v2 v2.1.0 github.com/openacid/low v0.1.21 github.com/oschwald/maxminddb-golang v1.12.0 // lastest version compatible with golang1.20 + github.com/saba-futai/sudoku v0.0.1-g github.com/sagernet/cors v1.2.1 github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a github.com/samber/lo v1.52.0 @@ -64,6 +64,7 @@ require ( ) require ( + filippo.io/edwards25519 v1.1.0 // indirect github.com/RyuaNerin/go-krypto v1.3.0 // indirect github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 // indirect github.com/ajg/form v1.5.1 // indirect diff --git a/clash-meta-android/core/src/foss/golang/clash/go.sum b/clash-meta-android/core/src/foss/golang/clash/go.sum index d8e51c5abc..45210afb98 100644 --- a/clash-meta-android/core/src/foss/golang/clash/go.sum +++ b/clash-meta-android/core/src/foss/golang/clash/go.sum @@ -1,3 +1,5 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/RyuaNerin/go-krypto v1.3.0 h1:smavTzSMAx8iuVlGb4pEwl9MD2qicqMzuXR2QWp2/Pg= github.com/RyuaNerin/go-krypto v1.3.0/go.mod h1:9R9TU936laAIqAmjcHo/LsaXYOZlymudOAxjaBf62UM= github.com/RyuaNerin/testingutil v0.1.0 h1:IYT6JL57RV3U2ml3dLHZsVtPOP6yNK7WUVdzzlpNrss= @@ -23,10 +25,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A= -github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= -github.com/enfein/mieru/v3 v3.22.1 h1:/XGYYXpEhEJlxosmtbpEJkhtRLHB8IToG7LB8kU2ZDY= -github.com/enfein/mieru/v3 v3.22.1/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM= +github.com/enfein/mieru/v3 v3.26.0 h1:ZsxCFkh3UfGSu9LL6EQ9+b97uxTJ7/AnJmLMyrbjSDI= +github.com/enfein/mieru/v3 v3.26.0/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM= github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358 h1:kXYqH/sL8dS/FdoFjr12ePjnLPorPo2FsnrHNuXSDyo= github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358/go.mod h1:hkIFzoiIPZYxdFOOLyDho59b7SrDfo+w3h+yWdlg45I= github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 h1:8j2RH289RJplhA6WfdaPqzg1MjH2K8wX5e0uhAxrw2g= @@ -108,8 +108,8 @@ 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-20250919004547-6122b699a301 h1:N5GExQJqYAH3gOCshpp2u/J3CtNYzMctmlb0xK9wtbQ= github.com/metacubex/gvisor v0.0.0-20250919004547-6122b699a301/go.mod h1:8LpS0IJW1VmWzUm3ylb0e2SK5QDm5lO/2qwWLZgRpBU= -github.com/metacubex/kcp-go v0.0.0-20251105084629-8c93f4bf37be h1:Y7SigZIqfv/+RIA/D7R6EbB9p+brPRoGOM6zobSmRIM= -github.com/metacubex/kcp-go v0.0.0-20251105084629-8c93f4bf37be/go.mod h1:HIJZW4QMhbBqXuqC1ly6Hn0TEYT2SzRw58ns1yGhXTs= +github.com/metacubex/kcp-go v0.0.0-20251111012849-7455698490e9 h1:7m3tRPrLpKOLOvZ/Lp4XCxz0t7rg9t9K35x6TahjR8o= +github.com/metacubex/kcp-go v0.0.0-20251111012849-7455698490e9/go.mod h1:HIJZW4QMhbBqXuqC1ly6Hn0TEYT2SzRw58ns1yGhXTs= github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 h1:1Qpuy+sU3DmyX9HwI+CrBT/oLNJngvBorR2RbajJcqo= github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793/go.mod h1:RjRNb4G52yAgfR+Oe/kp9G4PJJ97Fnj89eY1BFO3YyA= github.com/metacubex/quic-go v0.55.1-0.20251024060151-bd465f127128 h1:I1uvJl206/HbkzEAZpLgGkZgUveOZb+P+6oTUj7dN+o= @@ -131,16 +131,16 @@ github.com/metacubex/sing-shadowsocks2 v0.2.7 h1:hSuuc0YpsfiqYqt1o+fP4m34BQz4e6w github.com/metacubex/sing-shadowsocks2 v0.2.7/go.mod h1:vOEbfKC60txi0ca+yUlqEwOGc3Obl6cnSgx9Gf45KjE= github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 h1:gXU+MYPm7Wme3/OAY2FFzVq9d9GxPHOqu5AQfg/ddhI= github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2/go.mod h1:mbfboaXauKJNIHJYxQRa+NJs4JU9NZfkA+I33dS2+9E= -github.com/metacubex/sing-tun v0.4.9 h1:jY0Yyt8nnN3yQRN/jTxgqNCmGi1dsFdxdIi7pQUlVVU= -github.com/metacubex/sing-tun v0.4.9/go.mod h1:L/TjQY5JEGy8nvsuYmy/XgMFMCPiF0+AWSFCYfS6r9w= +github.com/metacubex/sing-tun v0.4.10 h1:DllQTERAcqQyiEl4L/R7Ia0jCiSzZzikw2kL8N85p0E= +github.com/metacubex/sing-tun v0.4.10/go.mod h1:L/TjQY5JEGy8nvsuYmy/XgMFMCPiF0+AWSFCYfS6r9w= github.com/metacubex/sing-vmess v0.2.4 h1:Tx6AGgCiEf400E/xyDuYyafsel6sGbR8oF7RkAaus6I= github.com/metacubex/sing-vmess v0.2.4/go.mod h1:21R5R1u90uUvBQF0owoooEu96/SAYYD56nDrwm6nFaM= github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f h1:Sr/DYKYofKHKc4GF3qkRGNuj6XA6c0eqPgEDN+VAsYU= github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f/go.mod h1:jpAkVLPnCpGSfNyVmj6Cq4YbuZsFepm/Dc+9BAOcR80= -github.com/metacubex/smux v0.0.0-20250922175018-15c9a6a78719 h1:T6qCCfolRDAVJKeaPW/mXwNLjnlo65AYN7WS2jrBNaM= -github.com/metacubex/smux v0.0.0-20250922175018-15c9a6a78719/go.mod h1:4bPD8HWx9jPJ9aE4uadgyN7D1/Wz3KmPy+vale8sKLE= -github.com/metacubex/tfo-go v0.0.0-20251024101424-368b42b59148 h1:Zd0QqciLIhv9MKbGKTPEgN8WUFsgQGA1WJBy6spEnVU= -github.com/metacubex/tfo-go v0.0.0-20251024101424-368b42b59148/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw= +github.com/metacubex/smux v0.0.0-20251111013112-03f8d12dafc1 h1:a6DF0ze9miXes+rdwl8a4Wkvfpe0lXYU82sPJfDzz6s= +github.com/metacubex/smux v0.0.0-20251111013112-03f8d12dafc1/go.mod h1:4bPD8HWx9jPJ9aE4uadgyN7D1/Wz3KmPy+vale8sKLE= +github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443 h1:H6TnfM12tOoTizYE/qBHH3nEuibIelmHI+BVSxVJr8o= +github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw= github.com/metacubex/utls v1.8.3 h1:0m/yCxm3SK6kWve2lKiFb1pue1wHitJ8sQQD4Ikqde4= github.com/metacubex/utls v1.8.3/go.mod h1:kncGGVhFaoGn5M3pFe3SXhZCzsbCJayNOH4UEqTKTko= github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f h1:FGBPRb1zUabhPhDrlKEjQ9lgIwQ6cHL4x8M9lrERhbk= @@ -171,6 +171,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= +github.com/saba-futai/sudoku v0.0.1-g h1:4q6OuAA6COaRW+CgoQtdim5AUPzzm0uOkvbYpJnOaBE= +github.com/saba-futai/sudoku v0.0.1-g/go.mod h1:2ZRzRwz93cS2K/o2yOG4CPJEltcvk5y6vbvUmjftGU0= github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ= github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI= github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis= diff --git a/clash-meta-android/core/src/foss/golang/clash/hub/executor/executor.go b/clash-meta-android/core/src/foss/golang/clash/hub/executor/executor.go index e92a8de448..184e64de79 100644 --- a/clash-meta-android/core/src/foss/golang/clash/hub/executor/executor.go +++ b/clash-meta-android/core/src/foss/golang/clash/hub/executor/executor.go @@ -269,6 +269,7 @@ func updateDNS(c *config.DNS, generalIPv6 bool) { FakeIPPool: c.FakeIPPool, FakeIPPool6: c.FakeIPPool6, FakeIPSkipper: c.FakeIPSkipper, + FakeIPTTL: c.FakeIPTTL, UseHosts: c.UseHosts, }) @@ -449,12 +450,7 @@ func patchSelectGroup(proxies map[string]C.Proxy) { return } - for name, proxy := range proxies { - outbound, ok := proxy.(C.Proxy) - if !ok { - continue - } - + for name, outbound := range proxies { selector, ok := outbound.Adapter().(outboundgroup.SelectAble) if !ok { continue diff --git a/clash-meta-android/core/src/foss/golang/clash/listener/config/sudoku.go b/clash-meta-android/core/src/foss/golang/clash/listener/config/sudoku.go new file mode 100644 index 0000000000..855795f1c3 --- /dev/null +++ b/clash-meta-android/core/src/foss/golang/clash/listener/config/sudoku.go @@ -0,0 +1,22 @@ +package config + +import "encoding/json" + +// SudokuServer describes a Sudoku inbound server configuration. +// It is internal to the listener layer and mainly used for logging and wiring. +type SudokuServer struct { + Enable bool `json:"enable"` + Listen string `json:"listen"` + Key string `json:"key"` + AEADMethod string `json:"aead-method,omitempty"` + PaddingMin *int `json:"padding-min,omitempty"` + PaddingMax *int `json:"padding-max,omitempty"` + Seed string `json:"seed,omitempty"` + TableType string `json:"table-type,omitempty"` + HandshakeTimeoutSecond *int `json:"handshake-timeout,omitempty"` +} + +func (s SudokuServer) String() string { + b, _ := json.Marshal(s) + return string(b) +} diff --git a/clash-meta-android/core/src/foss/golang/clash/listener/http/utils.go b/clash-meta-android/core/src/foss/golang/clash/listener/http/utils.go index e0793ff3bb..eb19283da2 100644 --- a/clash-meta-android/core/src/foss/golang/clash/listener/http/utils.go +++ b/clash-meta-android/core/src/foss/golang/clash/listener/http/utils.go @@ -63,7 +63,11 @@ func removeExtraHTTPHostPort(req *http.Request) { // parseBasicProxyAuthorization parse header Proxy-Authorization and return base64-encoded credential func parseBasicProxyAuthorization(request *http.Request) string { value := request.Header.Get("Proxy-Authorization") - if !strings.HasPrefix(value, "Basic ") { + const prefix = "Basic " + // According to RFC7617, the scheme should be case-insensitive. + // In practice, some implementations do use different case styles, causing authentication to fail + // eg: https://github.com/algesten/ureq/blob/381fd42cfcb80a5eb709d64860aa0ae726f17b8e/src/unversioned/transport/connect.rs#L118 + if len(value) < len(prefix) || !strings.EqualFold(value[:len(prefix)], prefix) { return "" } diff --git a/clash-meta-android/core/src/foss/golang/clash/listener/inbound/common_test.go b/clash-meta-android/core/src/foss/golang/clash/listener/inbound/common_test.go index 72874c7b54..4178035b27 100644 --- a/clash-meta-android/core/src/foss/golang/clash/listener/inbound/common_test.go +++ b/clash-meta-android/core/src/foss/golang/clash/listener/inbound/common_test.go @@ -59,11 +59,13 @@ func init() { } type TestTunnel struct { - HandleTCPConnFn func(conn net.Conn, metadata *C.Metadata) - HandleUDPPacketFn func(packet C.UDPPacket, metadata *C.Metadata) - NatTableFn func() C.NatTable - CloseFn func() error - DoTestFn func(t *testing.T, proxy C.ProxyAdapter) + HandleTCPConnFn func(conn net.Conn, metadata *C.Metadata) + HandleUDPPacketFn func(packet C.UDPPacket, metadata *C.Metadata) + NatTableFn func() C.NatTable + CloseFn func() error + DoTestFn func(t *testing.T, proxy C.ProxyAdapter) + DoSequentialTestFn func(t *testing.T, proxy C.ProxyAdapter) + DoConcurrentTestFn func(t *testing.T, proxy C.ProxyAdapter) } func (tt *TestTunnel) HandleTCPConn(conn net.Conn, metadata *C.Metadata) { @@ -86,6 +88,14 @@ func (tt *TestTunnel) DoTest(t *testing.T, proxy C.ProxyAdapter) { tt.DoTestFn(t, proxy) } +func (tt *TestTunnel) DoSequentialTest(t *testing.T, proxy C.ProxyAdapter) { + tt.DoSequentialTestFn(t, proxy) +} + +func (tt *TestTunnel) DoConcurrentTest(t *testing.T, proxy C.ProxyAdapter) { + tt.DoConcurrentTestFn(t, proxy) +} + type TestTunnelListener struct { ch chan net.Conn ctx context.Context @@ -213,6 +223,40 @@ func NewHttpTestTunnel() *TestTunnel { } assert.Equal(t, httpData[:size], data) } + + sequentialTestFn := func(t *testing.T, proxy C.ProxyAdapter) { + // Sequential testing for debugging + t.Run("Sequential", func(t *testing.T) { + testFn(t, proxy, "http", len(httpData)) + testFn(t, proxy, "https", len(httpData)) + }) + } + + concurrentTestFn := func(t *testing.T, proxy C.ProxyAdapter) { + // Concurrent testing to detect stress + t.Run("Concurrent", func(t *testing.T) { + wg := sync.WaitGroup{} + num := len(httpData) / 1024 + for i := 1; i <= num; i++ { + i := i + wg.Add(1) + go func() { + testFn(t, proxy, "https", i*1024) + defer wg.Done() + }() + } + for i := 1; i <= num; i++ { + i := i + wg.Add(1) + go func() { + testFn(t, proxy, "http", i*1024) + defer wg.Done() + }() + } + wg.Wait() + }) + } + tunnel := &TestTunnel{ HandleTCPConnFn: func(conn net.Conn, metadata *C.Metadata) { defer conn.Close() @@ -252,36 +296,11 @@ func NewHttpTestTunnel() *TestTunnel { }, CloseFn: ln.Close, DoTestFn: func(t *testing.T, proxy C.ProxyAdapter) { - - // Sequential testing for debugging - t.Run("Sequential", func(t *testing.T) { - testFn(t, proxy, "http", len(httpData)) - testFn(t, proxy, "https", len(httpData)) - }) - - // Concurrent testing to detect stress - t.Run("Concurrent", func(t *testing.T) { - wg := sync.WaitGroup{} - num := len(httpData) / 1024 - for i := 1; i <= num; i++ { - i := i - wg.Add(1) - go func() { - testFn(t, proxy, "https", i*1024) - defer wg.Done() - }() - } - for i := 1; i <= num; i++ { - i := i - wg.Add(1) - go func() { - testFn(t, proxy, "http", i*1024) - defer wg.Done() - }() - } - wg.Wait() - }) + sequentialTestFn(t, proxy) + concurrentTestFn(t, proxy) }, + DoSequentialTestFn: sequentialTestFn, + DoConcurrentTestFn: concurrentTestFn, } return tunnel } diff --git a/clash-meta-android/core/src/foss/golang/clash/listener/inbound/mieru.go b/clash-meta-android/core/src/foss/golang/clash/listener/inbound/mieru.go index cf8cc403bc..8a5718a636 100644 --- a/clash-meta-android/core/src/foss/golang/clash/listener/inbound/mieru.go +++ b/clash-meta-android/core/src/foss/golang/clash/listener/inbound/mieru.go @@ -90,6 +90,8 @@ func (m *Mieru) Listen(tunnel C.Tunnel) error { if err != nil { if !m.server.IsRunning() { break + } else { + continue } } go mieru.Handle(c, tunnel, req, additions...) diff --git a/clash-meta-android/core/src/foss/golang/clash/listener/inbound/mieru_test.go b/clash-meta-android/core/src/foss/golang/clash/listener/inbound/mieru_test.go index d163b49dcc..12aa680ccc 100644 --- a/clash-meta-android/core/src/foss/golang/clash/listener/inbound/mieru_test.go +++ b/clash-meta-android/core/src/foss/golang/clash/listener/inbound/mieru_test.go @@ -149,12 +149,18 @@ func TestNewMieru(t *testing.T) { } func TestInboundMieru(t *testing.T) { - t.Run("HANDSHAKE_STANDARD", func(t *testing.T) { + t.Run("TCP_HANDSHAKE_STANDARD", func(t *testing.T) { testInboundMieruTCP(t, "HANDSHAKE_STANDARD") }) - t.Run("HANDSHAKE_NO_WAIT", func(t *testing.T) { + t.Run("TCP_HANDSHAKE_NO_WAIT", func(t *testing.T) { testInboundMieruTCP(t, "HANDSHAKE_NO_WAIT") }) + t.Run("UDP_HANDSHAKE_STANDARD", func(t *testing.T) { + testInboundMieruUDP(t, "HANDSHAKE_STANDARD") + }) + t.Run("UDP_HANDSHAKE_NO_WAIT", func(t *testing.T) { + testInboundMieruUDP(t, "HANDSHAKE_NO_WAIT") + }) } func testInboundMieruTCP(t *testing.T, handshakeMode string) { @@ -168,7 +174,7 @@ func testInboundMieruTCP(t *testing.T, handshakeMode string) { inboundOptions := inbound.MieruOption{ BaseOption: inbound.BaseOption{ - NameStr: "mieru_inbound", + NameStr: "mieru_inbound_tcp", Listen: "127.0.0.1", Port: strconv.Itoa(port), }, @@ -194,7 +200,7 @@ func testInboundMieruTCP(t *testing.T, handshakeMode string) { return } outboundOptions := outbound.MieruOption{ - Name: "mieru_outbound", + Name: "mieru_outbound_tcp", Server: addrPort.Addr().String(), Port: int(addrPort.Port()), Transport: "TCP", @@ -210,3 +216,57 @@ func testInboundMieruTCP(t *testing.T, handshakeMode string) { tunnel.DoTest(t, out) } + +func testInboundMieruUDP(t *testing.T, handshakeMode string) { + t.Parallel() + l, err := net.ListenPacket("udp", "127.0.0.1:0") + if !assert.NoError(t, err) { + return + } + port := l.LocalAddr().(*net.UDPAddr).Port + l.Close() + + inboundOptions := inbound.MieruOption{ + BaseOption: inbound.BaseOption{ + NameStr: "mieru_inbound_udp", + Listen: "127.0.0.1", + Port: strconv.Itoa(port), + }, + Transport: "UDP", + Users: map[string]string{"test": "password"}, + } + in, err := inbound.NewMieru(&inboundOptions) + if !assert.NoError(t, err) { + return + } + + tunnel := NewHttpTestTunnel() + defer tunnel.Close() + + err = in.Listen(tunnel) + if !assert.NoError(t, err) { + return + } + defer in.Close() + + addrPort, err := netip.ParseAddrPort(in.Address()) + if !assert.NoError(t, err) { + return + } + outboundOptions := outbound.MieruOption{ + Name: "mieru_outbound_udp", + Server: addrPort.Addr().String(), + Port: int(addrPort.Port()), + Transport: "UDP", + UserName: "test", + Password: "password", + HandshakeMode: handshakeMode, + } + out, err := outbound.NewMieru(outboundOptions) + if !assert.NoError(t, err) { + return + } + defer out.Close() + + tunnel.DoSequentialTest(t, out) +} diff --git a/clash-meta-android/core/src/foss/golang/clash/listener/inbound/sudoku.go b/clash-meta-android/core/src/foss/golang/clash/listener/inbound/sudoku.go new file mode 100644 index 0000000000..d6e84af322 --- /dev/null +++ b/clash-meta-android/core/src/foss/golang/clash/listener/inbound/sudoku.go @@ -0,0 +1,128 @@ +package inbound + +import ( + "errors" + "fmt" + "strings" + + "github.com/saba-futai/sudoku/apis" + + C "github.com/metacubex/mihomo/constant" + LC "github.com/metacubex/mihomo/listener/config" + sudokuListener "github.com/metacubex/mihomo/listener/sudoku" + "github.com/metacubex/mihomo/log" +) + +type SudokuOption struct { + BaseOption + Key string `inbound:"key"` + AEADMethod string `inbound:"aead-method,omitempty"` + PaddingMin *int `inbound:"padding-min,omitempty"` + PaddingMax *int `inbound:"padding-max,omitempty"` + Seed string `inbound:"seed,omitempty"` + TableType string `inbound:"table-type,omitempty"` // "prefer_ascii" or "prefer_entropy" + HandshakeTimeoutSecond *int `inbound:"handshake-timeout,omitempty"` +} + +func (o SudokuOption) Equal(config C.InboundConfig) bool { + return optionToString(o) == optionToString(config) +} + +type Sudoku struct { + *Base + config *SudokuOption + listeners []*sudokuListener.Listener + serverConf LC.SudokuServer +} + +func NewSudoku(options *SudokuOption) (*Sudoku, error) { + if options.Key == "" { + return nil, fmt.Errorf("sudoku inbound requires key") + } + base, err := NewBase(&options.BaseOption) + if err != nil { + return nil, err + } + + defaultConf := apis.DefaultConfig() + + serverConf := LC.SudokuServer{ + Enable: true, + Listen: base.RawAddress(), + Key: options.Key, + AEADMethod: options.AEADMethod, + PaddingMin: options.PaddingMin, + PaddingMax: options.PaddingMax, + Seed: options.Seed, + TableType: options.TableType, + } + if options.HandshakeTimeoutSecond != nil { + serverConf.HandshakeTimeoutSecond = options.HandshakeTimeoutSecond + } else { + // Use Sudoku default if not specified. + v := defaultConf.HandshakeTimeoutSeconds + serverConf.HandshakeTimeoutSecond = &v + } + + return &Sudoku{ + Base: base, + config: options, + serverConf: serverConf, + }, nil +} + +// Config implements constant.InboundListener +func (s *Sudoku) Config() C.InboundConfig { + return s.config +} + +// Address implements constant.InboundListener +func (s *Sudoku) Address() string { + var addrList []string + for _, l := range s.listeners { + addrList = append(addrList, l.Address()) + } + return strings.Join(addrList, ",") +} + +// Listen implements constant.InboundListener +func (s *Sudoku) Listen(tunnel C.Tunnel) error { + if s.serverConf.Key == "" { + return fmt.Errorf("sudoku inbound requires key") + } + + var errs []error + for _, addr := range strings.Split(s.RawAddress(), ",") { + conf := s.serverConf + conf.Listen = addr + + l, err := sudokuListener.New(conf, tunnel, s.Additions()...) + if err != nil { + errs = append(errs, err) + continue + } + s.listeners = append(s.listeners, l) + } + if len(errs) > 0 { + return errors.Join(errs...) + } + + log.Infoln("Sudoku[%s] inbound listening at: %s", s.Name(), s.Address()) + return nil +} + +// Close implements constant.InboundListener +func (s *Sudoku) Close() error { + var errs []error + for _, l := range s.listeners { + if err := l.Close(); err != nil { + errs = append(errs, err) + } + } + if len(errs) > 0 { + return errors.Join(errs...) + } + return nil +} + +var _ C.InboundListener = (*Sudoku)(nil) diff --git a/clash-meta-android/core/src/foss/golang/clash/listener/inbound/sudoku_test.go b/clash-meta-android/core/src/foss/golang/clash/listener/inbound/sudoku_test.go new file mode 100644 index 0000000000..6d3e35b19e --- /dev/null +++ b/clash-meta-android/core/src/foss/golang/clash/listener/inbound/sudoku_test.go @@ -0,0 +1,91 @@ +package inbound_test + +import ( + "net/netip" + "testing" + + "github.com/metacubex/mihomo/adapter/outbound" + "github.com/metacubex/mihomo/listener/inbound" + "github.com/stretchr/testify/assert" +) + +func testInboundSudoku(t *testing.T, inboundOptions inbound.SudokuOption, outboundOptions outbound.SudokuOption) { + t.Parallel() + + inboundOptions.BaseOption = inbound.BaseOption{ + NameStr: "sudoku_inbound", + Listen: "127.0.0.1", + Port: "0", + } + in, err := inbound.NewSudoku(&inboundOptions) + if !assert.NoError(t, err) { + return + } + + tunnel := NewHttpTestTunnel() + defer tunnel.Close() + + err = in.Listen(tunnel) + if !assert.NoError(t, err) { + return + } + defer in.Close() + + addrPort, err := netip.ParseAddrPort(in.Address()) + if !assert.NoError(t, err) { + return + } + + outboundOptions.Name = "sudoku_outbound" + outboundOptions.Server = addrPort.Addr().String() + outboundOptions.Port = int(addrPort.Port()) + + out, err := outbound.NewSudoku(outboundOptions) + if !assert.NoError(t, err) { + return + } + defer out.Close() + + tunnel.DoTest(t, out) +} + +func TestInboundSudoku_Basic(t *testing.T) { + key := "test_key" + inboundOptions := inbound.SudokuOption{ + Key: key, + } + outboundOptions := outbound.SudokuOption{ + Key: key, + } + testInboundSudoku(t, inboundOptions, outboundOptions) +} + +func TestInboundSudoku_Entropy(t *testing.T) { + key := "test_key_entropy" + inboundOptions := inbound.SudokuOption{ + Key: key, + TableType: "prefer_entropy", + } + outboundOptions := outbound.SudokuOption{ + Key: key, + TableType: "prefer_entropy", + } + testInboundSudoku(t, inboundOptions, outboundOptions) +} + +func TestInboundSudoku_Padding(t *testing.T) { + key := "test_key_padding" + min := 10 + max := 100 + inboundOptions := inbound.SudokuOption{ + Key: key, + PaddingMin: &min, + PaddingMax: &max, + } + outboundOptions := outbound.SudokuOption{ + Key: key, + PaddingMin: &min, + PaddingMax: &max, + } + testInboundSudoku(t, inboundOptions, outboundOptions) +} diff --git a/clash-meta-android/core/src/foss/golang/clash/listener/mieru/server.go b/clash-meta-android/core/src/foss/golang/clash/listener/mieru/server.go index 9b1e2e6f8f..ab80106944 100644 --- a/clash-meta-android/core/src/foss/golang/clash/listener/mieru/server.go +++ b/clash-meta-android/core/src/foss/golang/clash/listener/mieru/server.go @@ -31,12 +31,28 @@ func Handle(conn net.Conn, tunnel C.Tunnel, request *mierumodel.Request, additio } // Handle the connection with tunnel. - metadata := mieruRequestToMetadata(request) - inbound.ApplyAdditions(&metadata, additions...) - switch metadata.NetWork { - case C.TCP: - tunnel.HandleTCPConn(conn, &metadata) - case C.UDP: + switch request.Command { + case mieruconstant.Socks5ConnectCmd: // TCP + metadata := &C.Metadata{ + NetWork: C.TCP, + Type: C.MIERU, + DstPort: uint16(request.DstAddr.Port), + } + if request.DstAddr.FQDN != "" { + metadata.Host = request.DstAddr.FQDN + } else if request.DstAddr.IP != nil { + metadata.DstIP, _ = netip.AddrFromSlice(request.DstAddr.IP) + metadata.DstIP = metadata.DstIP.Unmap() + } + inbound.ApplyAdditions( + metadata, + inbound.WithInName(conn.(mierucommon.UserContext).UserName()), + inbound.WithSrcAddr(conn.RemoteAddr()), + inbound.WithInAddr(conn.LocalAddr()), + ) + inbound.ApplyAdditions(metadata, additions...) + tunnel.HandleTCPConn(conn, metadata) + case mieruconstant.Socks5UDPAssociateCmd: // UDP pc := mierucommon.NewPacketOverStreamTunnel(conn) ep := N.NewEnhancePacketConn(pc) for { @@ -67,24 +83,6 @@ func Handle(conn net.Conn, tunnel C.Tunnel, request *mierumodel.Request, additio } } -func mieruRequestToMetadata(request *mierumodel.Request) C.Metadata { - m := C.Metadata{ - DstPort: uint16(request.DstAddr.Port), - } - switch request.Command { - case mieruconstant.Socks5ConnectCmd: - m.NetWork = C.TCP - case mieruconstant.Socks5UDPAssociateCmd: - m.NetWork = C.UDP - } - if request.DstAddr.FQDN != "" { - m.Host = request.DstAddr.FQDN - } else if request.DstAddr.IP != nil { - m.DstIP, _ = netip.AddrFromSlice(request.DstAddr.IP) - } - return m -} - type packet struct { pc net.PacketConn addr net.Addr // source (i.e. remote) IP & Port of the packet diff --git a/clash-meta-android/core/src/foss/golang/clash/listener/parse.go b/clash-meta-android/core/src/foss/golang/clash/listener/parse.go index 4e893bf1d0..359411419f 100644 --- a/clash-meta-android/core/src/foss/golang/clash/listener/parse.go +++ b/clash-meta-android/core/src/foss/golang/clash/listener/parse.go @@ -134,6 +134,13 @@ func ParseListener(mapping map[string]any) (C.InboundListener, error) { return nil, err } listener, err = IN.NewMieru(mieruOption) + case "sudoku": + sudokuOption := &IN.SudokuOption{} + err = decoder.Decode(mapping, sudokuOption) + if err != nil { + return nil, err + } + listener, err = IN.NewSudoku(sudokuOption) default: return nil, fmt.Errorf("unsupport proxy type: %s", proxyType) } diff --git a/clash-meta-android/core/src/foss/golang/clash/listener/sudoku/server.go b/clash-meta-android/core/src/foss/golang/clash/listener/sudoku/server.go new file mode 100644 index 0000000000..87b2abd9d3 --- /dev/null +++ b/clash-meta-android/core/src/foss/golang/clash/listener/sudoku/server.go @@ -0,0 +1,139 @@ +package sudoku + +import ( + "net" + "strings" + + "github.com/saba-futai/sudoku/apis" + sudokuobfs "github.com/saba-futai/sudoku/pkg/obfs/sudoku" + + "github.com/metacubex/mihomo/adapter/inbound" + C "github.com/metacubex/mihomo/constant" + LC "github.com/metacubex/mihomo/listener/config" + "github.com/metacubex/mihomo/transport/socks5" +) + +type Listener struct { + listener net.Listener + addr string + closed bool + protoConf apis.ProtocolConfig +} + +// RawAddress implements C.Listener +func (l *Listener) RawAddress() string { + return l.addr +} + +// Address implements C.Listener +func (l *Listener) Address() string { + if l.listener == nil { + return "" + } + return l.listener.Addr().String() +} + +// Close implements C.Listener +func (l *Listener) Close() error { + l.closed = true + if l.listener != nil { + return l.listener.Close() + } + return nil +} + +func (l *Listener) handleConn(conn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition) { + tunnelConn, target, err := apis.ServerHandshake(conn, &l.protoConf) + if err != nil { + _ = conn.Close() + return + } + + targetAddr := socks5.ParseAddr(target) + if targetAddr == nil { + _ = tunnelConn.Close() + return + } + + tunnel.HandleTCPConn(inbound.NewSocket(targetAddr, tunnelConn, C.SUDOKU, additions...)) +} + +func New(config LC.SudokuServer, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener, error) { + if len(additions) == 0 { + additions = []inbound.Addition{ + inbound.WithInName("DEFAULT-SUDOKU"), + inbound.WithSpecialRules(""), + } + } + + l, err := inbound.Listen("tcp", config.Listen) + if err != nil { + return nil, err + } + + seed := config.Seed + if seed == "" { + seed = config.Key + } + + tableType := strings.ToLower(config.TableType) + if tableType == "" { + tableType = "prefer_ascii" + } + + table := sudokuobfs.NewTable(seed, tableType) + + defaultConf := apis.DefaultConfig() + paddingMin := defaultConf.PaddingMin + paddingMax := defaultConf.PaddingMax + if config.PaddingMin != nil { + paddingMin = *config.PaddingMin + } + if config.PaddingMax != nil { + paddingMax = *config.PaddingMax + } + if config.PaddingMin == nil && config.PaddingMax != nil && paddingMax < paddingMin { + paddingMin = paddingMax + } + if config.PaddingMax == nil && config.PaddingMin != nil && paddingMax < paddingMin { + paddingMax = paddingMin + } + + handshakeTimeout := defaultConf.HandshakeTimeoutSeconds + if config.HandshakeTimeoutSecond != nil { + handshakeTimeout = *config.HandshakeTimeoutSecond + } + + protoConf := apis.ProtocolConfig{ + Key: config.Key, + AEADMethod: defaultConf.AEADMethod, + Table: table, + PaddingMin: paddingMin, + PaddingMax: paddingMax, + HandshakeTimeoutSeconds: handshakeTimeout, + } + if config.AEADMethod != "" { + protoConf.AEADMethod = config.AEADMethod + } + + sl := &Listener{ + listener: l, + addr: config.Listen, + protoConf: protoConf, + } + + go func() { + for { + c, err := l.Accept() + if err != nil { + if sl.closed { + break + } + continue + } + go sl.handleConn(c, tunnel, additions...) + } + }() + + return sl, nil +} diff --git a/clash-meta-android/core/src/foss/golang/clash/tunnel/dns_dialer.go b/clash-meta-android/core/src/foss/golang/clash/tunnel/dns_dialer.go index c141d96648..364cba699d 100644 --- a/clash-meta-android/core/src/foss/golang/clash/tunnel/dns_dialer.go +++ b/clash-meta-android/core/src/foss/golang/clash/tunnel/dns_dialer.go @@ -70,7 +70,9 @@ func (d *DNSDialer) DialContext(ctx context.Context, network, addr string) (net. } else { var ok bool proxyAdapter, ok = Proxies()[proxyName] - if !ok { + if ok { + metadata.SpecialProxy = proxyName // just for log + } else { opts = append(opts, dialer.WithInterface(proxyName)) } } @@ -158,7 +160,9 @@ func (d *DNSDialer) ListenPacket(ctx context.Context, network, addr string) (net } else { var ok bool proxyAdapter, ok = Proxies()[proxyName] - if !ok { + if ok { + metadata.SpecialProxy = proxyName // just for log + } else { opts = append(opts, dialer.WithInterface(proxyName)) } } diff --git a/clash-meta-android/core/src/foss/golang/clash/tunnel/tunnel.go b/clash-meta-android/core/src/foss/golang/clash/tunnel/tunnel.go index fc901eb354..1b2f41365e 100644 --- a/clash-meta-android/core/src/foss/golang/clash/tunnel/tunnel.go +++ b/clash-meta-android/core/src/foss/golang/clash/tunnel/tunnel.go @@ -627,7 +627,7 @@ func logMetadataErr(metadata *C.Metadata, rule C.Rule, proxy C.ProxyAdapter, err func logMetadata(metadata *C.Metadata, rule C.Rule, remoteConn C.Connection) { switch { case metadata.SpecialProxy != "": - log.Infoln("[%s] %s --> %s using %s", strings.ToUpper(metadata.NetWork.String()), metadata.SourceDetail(), metadata.RemoteAddress(), metadata.SpecialProxy) + log.Infoln("[%s] %s --> %s using %s", strings.ToUpper(metadata.NetWork.String()), metadata.SourceDetail(), metadata.RemoteAddress(), remoteConn.Chains().String()) case rule != nil: if rule.Payload() != "" { log.Infoln("[%s] %s --> %s match %s using %s", strings.ToUpper(metadata.NetWork.String()), metadata.SourceDetail(), metadata.RemoteAddress(), fmt.Sprintf("%s(%s)", rule.RuleType().String(), rule.Payload()), remoteConn.Chains().String()) @@ -639,7 +639,7 @@ func logMetadata(metadata *C.Metadata, rule C.Rule, remoteConn C.Connection) { case mode == Direct: log.Infoln("[%s] %s --> %s using DIRECT", strings.ToUpper(metadata.NetWork.String()), metadata.SourceDetail(), metadata.RemoteAddress()) default: - log.Infoln("[%s] %s --> %s doesn't match any rule using %s", strings.ToUpper(metadata.NetWork.String()), metadata.SourceDetail(), metadata.RemoteAddress(), remoteConn.Chains().Last()) + log.Infoln("[%s] %s --> %s doesn't match any rule using %s", strings.ToUpper(metadata.NetWork.String()), metadata.SourceDetail(), metadata.RemoteAddress(), remoteConn.Chains().String()) } } diff --git a/clash-meta-android/core/src/foss/golang/go.mod b/clash-meta-android/core/src/foss/golang/go.mod index b78c904307..27d4f6bc45 100644 --- a/clash-meta-android/core/src/foss/golang/go.mod +++ b/clash-meta-android/core/src/foss/golang/go.mod @@ -5,6 +5,7 @@ go 1.20 require cfa v0.0.0 require ( + filippo.io/edwards25519 v1.1.0 // indirect github.com/RyuaNerin/go-krypto v1.3.0 // indirect github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 // indirect github.com/ajg/form v1.5.1 // indirect @@ -13,8 +14,7 @@ require ( github.com/buger/jsonparser v1.1.1 // indirect github.com/coreos/go-iptables v0.8.0 // indirect github.com/dlclark/regexp2 v1.11.5 // indirect - github.com/ebitengine/purego v0.9.1 // indirect - github.com/enfein/mieru/v3 v3.22.1 // indirect + github.com/enfein/mieru/v3 v3.26.0 // indirect github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358 // indirect github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 // indirect github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 // indirect @@ -50,7 +50,7 @@ require ( github.com/metacubex/fswatch v0.1.1 // indirect github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 // indirect github.com/metacubex/gvisor v0.0.0-20250919004547-6122b699a301 // indirect - github.com/metacubex/kcp-go v0.0.0-20251105084629-8c93f4bf37be // indirect + github.com/metacubex/kcp-go v0.0.0-20251111012849-7455698490e9 // indirect github.com/metacubex/mihomo v1.7.0 // indirect github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 // indirect github.com/metacubex/quic-go v0.55.1-0.20251024060151-bd465f127128 // indirect @@ -62,11 +62,11 @@ require ( github.com/metacubex/sing-shadowsocks v0.2.12 // indirect github.com/metacubex/sing-shadowsocks2 v0.2.7 // indirect github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 // indirect - github.com/metacubex/sing-tun v0.4.9 // indirect + github.com/metacubex/sing-tun v0.4.10 // indirect github.com/metacubex/sing-vmess v0.2.4 // indirect github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f // indirect - github.com/metacubex/smux v0.0.0-20250922175018-15c9a6a78719 // indirect - github.com/metacubex/tfo-go v0.0.0-20251024101424-368b42b59148 // indirect + github.com/metacubex/smux v0.0.0-20251111013112-03f8d12dafc1 // indirect + github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443 // indirect github.com/metacubex/utls v1.8.3 // indirect github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f // indirect github.com/metacubex/yamux v0.0.0-20250918083631-dd5f17c0be49 // indirect @@ -78,6 +78,7 @@ require ( github.com/oschwald/maxminddb-golang v1.12.0 // indirect github.com/pierrec/lz4/v4 v4.1.14 // indirect github.com/quic-go/qpack v0.4.0 // indirect + github.com/saba-futai/sudoku v0.0.1-g // indirect github.com/sagernet/cors v1.2.1 // indirect github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect github.com/samber/lo v1.52.0 // indirect diff --git a/clash-meta-android/core/src/foss/golang/go.sum b/clash-meta-android/core/src/foss/golang/go.sum index 95f5119c9f..f60cf07b72 100644 --- a/clash-meta-android/core/src/foss/golang/go.sum +++ b/clash-meta-android/core/src/foss/golang/go.sum @@ -1,3 +1,5 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/RyuaNerin/go-krypto v1.3.0 h1:smavTzSMAx8iuVlGb4pEwl9MD2qicqMzuXR2QWp2/Pg= github.com/RyuaNerin/go-krypto v1.3.0/go.mod h1:9R9TU936laAIqAmjcHo/LsaXYOZlymudOAxjaBf62UM= github.com/RyuaNerin/testingutil v0.1.0 h1:IYT6JL57RV3U2ml3dLHZsVtPOP6yNK7WUVdzzlpNrss= @@ -22,10 +24,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A= -github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= -github.com/enfein/mieru/v3 v3.22.1 h1:/XGYYXpEhEJlxosmtbpEJkhtRLHB8IToG7LB8kU2ZDY= -github.com/enfein/mieru/v3 v3.22.1/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM= +github.com/enfein/mieru/v3 v3.26.0 h1:ZsxCFkh3UfGSu9LL6EQ9+b97uxTJ7/AnJmLMyrbjSDI= +github.com/enfein/mieru/v3 v3.26.0/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM= github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358 h1:kXYqH/sL8dS/FdoFjr12ePjnLPorPo2FsnrHNuXSDyo= github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358/go.mod h1:hkIFzoiIPZYxdFOOLyDho59b7SrDfo+w3h+yWdlg45I= github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 h1:8j2RH289RJplhA6WfdaPqzg1MjH2K8wX5e0uhAxrw2g= @@ -104,8 +104,8 @@ 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-20250919004547-6122b699a301 h1:N5GExQJqYAH3gOCshpp2u/J3CtNYzMctmlb0xK9wtbQ= github.com/metacubex/gvisor v0.0.0-20250919004547-6122b699a301/go.mod h1:8LpS0IJW1VmWzUm3ylb0e2SK5QDm5lO/2qwWLZgRpBU= -github.com/metacubex/kcp-go v0.0.0-20251105084629-8c93f4bf37be h1:Y7SigZIqfv/+RIA/D7R6EbB9p+brPRoGOM6zobSmRIM= -github.com/metacubex/kcp-go v0.0.0-20251105084629-8c93f4bf37be/go.mod h1:HIJZW4QMhbBqXuqC1ly6Hn0TEYT2SzRw58ns1yGhXTs= +github.com/metacubex/kcp-go v0.0.0-20251111012849-7455698490e9 h1:7m3tRPrLpKOLOvZ/Lp4XCxz0t7rg9t9K35x6TahjR8o= +github.com/metacubex/kcp-go v0.0.0-20251111012849-7455698490e9/go.mod h1:HIJZW4QMhbBqXuqC1ly6Hn0TEYT2SzRw58ns1yGhXTs= github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 h1:1Qpuy+sU3DmyX9HwI+CrBT/oLNJngvBorR2RbajJcqo= github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793/go.mod h1:RjRNb4G52yAgfR+Oe/kp9G4PJJ97Fnj89eY1BFO3YyA= github.com/metacubex/quic-go v0.55.1-0.20251024060151-bd465f127128 h1:I1uvJl206/HbkzEAZpLgGkZgUveOZb+P+6oTUj7dN+o= @@ -127,16 +127,16 @@ github.com/metacubex/sing-shadowsocks2 v0.2.7 h1:hSuuc0YpsfiqYqt1o+fP4m34BQz4e6w github.com/metacubex/sing-shadowsocks2 v0.2.7/go.mod h1:vOEbfKC60txi0ca+yUlqEwOGc3Obl6cnSgx9Gf45KjE= github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 h1:gXU+MYPm7Wme3/OAY2FFzVq9d9GxPHOqu5AQfg/ddhI= github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2/go.mod h1:mbfboaXauKJNIHJYxQRa+NJs4JU9NZfkA+I33dS2+9E= -github.com/metacubex/sing-tun v0.4.9 h1:jY0Yyt8nnN3yQRN/jTxgqNCmGi1dsFdxdIi7pQUlVVU= -github.com/metacubex/sing-tun v0.4.9/go.mod h1:L/TjQY5JEGy8nvsuYmy/XgMFMCPiF0+AWSFCYfS6r9w= +github.com/metacubex/sing-tun v0.4.10 h1:DllQTERAcqQyiEl4L/R7Ia0jCiSzZzikw2kL8N85p0E= +github.com/metacubex/sing-tun v0.4.10/go.mod h1:L/TjQY5JEGy8nvsuYmy/XgMFMCPiF0+AWSFCYfS6r9w= github.com/metacubex/sing-vmess v0.2.4 h1:Tx6AGgCiEf400E/xyDuYyafsel6sGbR8oF7RkAaus6I= github.com/metacubex/sing-vmess v0.2.4/go.mod h1:21R5R1u90uUvBQF0owoooEu96/SAYYD56nDrwm6nFaM= github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f h1:Sr/DYKYofKHKc4GF3qkRGNuj6XA6c0eqPgEDN+VAsYU= github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f/go.mod h1:jpAkVLPnCpGSfNyVmj6Cq4YbuZsFepm/Dc+9BAOcR80= -github.com/metacubex/smux v0.0.0-20250922175018-15c9a6a78719 h1:T6qCCfolRDAVJKeaPW/mXwNLjnlo65AYN7WS2jrBNaM= -github.com/metacubex/smux v0.0.0-20250922175018-15c9a6a78719/go.mod h1:4bPD8HWx9jPJ9aE4uadgyN7D1/Wz3KmPy+vale8sKLE= -github.com/metacubex/tfo-go v0.0.0-20251024101424-368b42b59148 h1:Zd0QqciLIhv9MKbGKTPEgN8WUFsgQGA1WJBy6spEnVU= -github.com/metacubex/tfo-go v0.0.0-20251024101424-368b42b59148/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw= +github.com/metacubex/smux v0.0.0-20251111013112-03f8d12dafc1 h1:a6DF0ze9miXes+rdwl8a4Wkvfpe0lXYU82sPJfDzz6s= +github.com/metacubex/smux v0.0.0-20251111013112-03f8d12dafc1/go.mod h1:4bPD8HWx9jPJ9aE4uadgyN7D1/Wz3KmPy+vale8sKLE= +github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443 h1:H6TnfM12tOoTizYE/qBHH3nEuibIelmHI+BVSxVJr8o= +github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw= github.com/metacubex/utls v1.8.3 h1:0m/yCxm3SK6kWve2lKiFb1pue1wHitJ8sQQD4Ikqde4= github.com/metacubex/utls v1.8.3/go.mod h1:kncGGVhFaoGn5M3pFe3SXhZCzsbCJayNOH4UEqTKTko= github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f h1:FGBPRb1zUabhPhDrlKEjQ9lgIwQ6cHL4x8M9lrERhbk= @@ -166,6 +166,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= +github.com/saba-futai/sudoku v0.0.1-g h1:4q6OuAA6COaRW+CgoQtdim5AUPzzm0uOkvbYpJnOaBE= +github.com/saba-futai/sudoku v0.0.1-g/go.mod h1:2ZRzRwz93cS2K/o2yOG4CPJEltcvk5y6vbvUmjftGU0= github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ= github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI= github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis= diff --git a/clash-meta-android/core/src/main/golang/go.mod b/clash-meta-android/core/src/main/golang/go.mod index 786f105806..17f3c9de99 100644 --- a/clash-meta-android/core/src/main/golang/go.mod +++ b/clash-meta-android/core/src/main/golang/go.mod @@ -12,6 +12,7 @@ require ( replace github.com/metacubex/mihomo => ../../foss/golang/clash require ( + filippo.io/edwards25519 v1.1.0 // indirect github.com/RyuaNerin/go-krypto v1.3.0 // indirect github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 // indirect github.com/ajg/form v1.5.1 // indirect @@ -19,8 +20,7 @@ require ( github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/buger/jsonparser v1.1.1 // indirect github.com/coreos/go-iptables v0.8.0 // indirect - github.com/ebitengine/purego v0.9.1 // indirect - github.com/enfein/mieru/v3 v3.22.1 // indirect + github.com/enfein/mieru/v3 v3.26.0 // indirect github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358 // indirect github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 // indirect github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 // indirect @@ -57,7 +57,7 @@ require ( github.com/metacubex/fswatch v0.1.1 // indirect github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 // indirect github.com/metacubex/gvisor v0.0.0-20250919004547-6122b699a301 // indirect - github.com/metacubex/kcp-go v0.0.0-20251105084629-8c93f4bf37be // indirect + github.com/metacubex/kcp-go v0.0.0-20251111012849-7455698490e9 // indirect github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 // indirect github.com/metacubex/quic-go v0.55.1-0.20251024060151-bd465f127128 // indirect github.com/metacubex/randv2 v0.2.0 // indirect @@ -68,11 +68,11 @@ require ( github.com/metacubex/sing-shadowsocks v0.2.12 // indirect github.com/metacubex/sing-shadowsocks2 v0.2.7 // indirect github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 // indirect - github.com/metacubex/sing-tun v0.4.9 // indirect + github.com/metacubex/sing-tun v0.4.10 // indirect github.com/metacubex/sing-vmess v0.2.4 // indirect github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f // indirect - github.com/metacubex/smux v0.0.0-20250922175018-15c9a6a78719 // indirect - github.com/metacubex/tfo-go v0.0.0-20251024101424-368b42b59148 // indirect + github.com/metacubex/smux v0.0.0-20251111013112-03f8d12dafc1 // indirect + github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443 // indirect github.com/metacubex/utls v1.8.3 // indirect github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f // indirect github.com/metacubex/yamux v0.0.0-20250918083631-dd5f17c0be49 // indirect @@ -84,6 +84,7 @@ require ( github.com/oschwald/maxminddb-golang v1.12.0 // indirect github.com/pierrec/lz4/v4 v4.1.14 // indirect github.com/quic-go/qpack v0.4.0 // indirect + github.com/saba-futai/sudoku v0.0.1-g // indirect github.com/sagernet/cors v1.2.1 // indirect github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect github.com/samber/lo v1.52.0 // indirect diff --git a/clash-meta-android/core/src/main/golang/go.sum b/clash-meta-android/core/src/main/golang/go.sum index 4592afba78..be217130ef 100644 --- a/clash-meta-android/core/src/main/golang/go.sum +++ b/clash-meta-android/core/src/main/golang/go.sum @@ -1,3 +1,5 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/RyuaNerin/go-krypto v1.3.0 h1:smavTzSMAx8iuVlGb4pEwl9MD2qicqMzuXR2QWp2/Pg= github.com/RyuaNerin/go-krypto v1.3.0/go.mod h1:9R9TU936laAIqAmjcHo/LsaXYOZlymudOAxjaBf62UM= github.com/RyuaNerin/testingutil v0.1.0 h1:IYT6JL57RV3U2ml3dLHZsVtPOP6yNK7WUVdzzlpNrss= @@ -22,10 +24,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A= -github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= -github.com/enfein/mieru/v3 v3.22.1 h1:/XGYYXpEhEJlxosmtbpEJkhtRLHB8IToG7LB8kU2ZDY= -github.com/enfein/mieru/v3 v3.22.1/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM= +github.com/enfein/mieru/v3 v3.26.0 h1:ZsxCFkh3UfGSu9LL6EQ9+b97uxTJ7/AnJmLMyrbjSDI= +github.com/enfein/mieru/v3 v3.26.0/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM= github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358 h1:kXYqH/sL8dS/FdoFjr12ePjnLPorPo2FsnrHNuXSDyo= github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358/go.mod h1:hkIFzoiIPZYxdFOOLyDho59b7SrDfo+w3h+yWdlg45I= github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 h1:8j2RH289RJplhA6WfdaPqzg1MjH2K8wX5e0uhAxrw2g= @@ -105,8 +105,8 @@ 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-20250919004547-6122b699a301 h1:N5GExQJqYAH3gOCshpp2u/J3CtNYzMctmlb0xK9wtbQ= github.com/metacubex/gvisor v0.0.0-20250919004547-6122b699a301/go.mod h1:8LpS0IJW1VmWzUm3ylb0e2SK5QDm5lO/2qwWLZgRpBU= -github.com/metacubex/kcp-go v0.0.0-20251105084629-8c93f4bf37be h1:Y7SigZIqfv/+RIA/D7R6EbB9p+brPRoGOM6zobSmRIM= -github.com/metacubex/kcp-go v0.0.0-20251105084629-8c93f4bf37be/go.mod h1:HIJZW4QMhbBqXuqC1ly6Hn0TEYT2SzRw58ns1yGhXTs= +github.com/metacubex/kcp-go v0.0.0-20251111012849-7455698490e9 h1:7m3tRPrLpKOLOvZ/Lp4XCxz0t7rg9t9K35x6TahjR8o= +github.com/metacubex/kcp-go v0.0.0-20251111012849-7455698490e9/go.mod h1:HIJZW4QMhbBqXuqC1ly6Hn0TEYT2SzRw58ns1yGhXTs= github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 h1:1Qpuy+sU3DmyX9HwI+CrBT/oLNJngvBorR2RbajJcqo= github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793/go.mod h1:RjRNb4G52yAgfR+Oe/kp9G4PJJ97Fnj89eY1BFO3YyA= github.com/metacubex/quic-go v0.55.1-0.20251024060151-bd465f127128 h1:I1uvJl206/HbkzEAZpLgGkZgUveOZb+P+6oTUj7dN+o= @@ -128,16 +128,16 @@ github.com/metacubex/sing-shadowsocks2 v0.2.7 h1:hSuuc0YpsfiqYqt1o+fP4m34BQz4e6w github.com/metacubex/sing-shadowsocks2 v0.2.7/go.mod h1:vOEbfKC60txi0ca+yUlqEwOGc3Obl6cnSgx9Gf45KjE= github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 h1:gXU+MYPm7Wme3/OAY2FFzVq9d9GxPHOqu5AQfg/ddhI= github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2/go.mod h1:mbfboaXauKJNIHJYxQRa+NJs4JU9NZfkA+I33dS2+9E= -github.com/metacubex/sing-tun v0.4.9 h1:jY0Yyt8nnN3yQRN/jTxgqNCmGi1dsFdxdIi7pQUlVVU= -github.com/metacubex/sing-tun v0.4.9/go.mod h1:L/TjQY5JEGy8nvsuYmy/XgMFMCPiF0+AWSFCYfS6r9w= +github.com/metacubex/sing-tun v0.4.10 h1:DllQTERAcqQyiEl4L/R7Ia0jCiSzZzikw2kL8N85p0E= +github.com/metacubex/sing-tun v0.4.10/go.mod h1:L/TjQY5JEGy8nvsuYmy/XgMFMCPiF0+AWSFCYfS6r9w= github.com/metacubex/sing-vmess v0.2.4 h1:Tx6AGgCiEf400E/xyDuYyafsel6sGbR8oF7RkAaus6I= github.com/metacubex/sing-vmess v0.2.4/go.mod h1:21R5R1u90uUvBQF0owoooEu96/SAYYD56nDrwm6nFaM= github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f h1:Sr/DYKYofKHKc4GF3qkRGNuj6XA6c0eqPgEDN+VAsYU= github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f/go.mod h1:jpAkVLPnCpGSfNyVmj6Cq4YbuZsFepm/Dc+9BAOcR80= -github.com/metacubex/smux v0.0.0-20250922175018-15c9a6a78719 h1:T6qCCfolRDAVJKeaPW/mXwNLjnlo65AYN7WS2jrBNaM= -github.com/metacubex/smux v0.0.0-20250922175018-15c9a6a78719/go.mod h1:4bPD8HWx9jPJ9aE4uadgyN7D1/Wz3KmPy+vale8sKLE= -github.com/metacubex/tfo-go v0.0.0-20251024101424-368b42b59148 h1:Zd0QqciLIhv9MKbGKTPEgN8WUFsgQGA1WJBy6spEnVU= -github.com/metacubex/tfo-go v0.0.0-20251024101424-368b42b59148/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw= +github.com/metacubex/smux v0.0.0-20251111013112-03f8d12dafc1 h1:a6DF0ze9miXes+rdwl8a4Wkvfpe0lXYU82sPJfDzz6s= +github.com/metacubex/smux v0.0.0-20251111013112-03f8d12dafc1/go.mod h1:4bPD8HWx9jPJ9aE4uadgyN7D1/Wz3KmPy+vale8sKLE= +github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443 h1:H6TnfM12tOoTizYE/qBHH3nEuibIelmHI+BVSxVJr8o= +github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw= github.com/metacubex/utls v1.8.3 h1:0m/yCxm3SK6kWve2lKiFb1pue1wHitJ8sQQD4Ikqde4= github.com/metacubex/utls v1.8.3/go.mod h1:kncGGVhFaoGn5M3pFe3SXhZCzsbCJayNOH4UEqTKTko= github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f h1:FGBPRb1zUabhPhDrlKEjQ9lgIwQ6cHL4x8M9lrERhbk= @@ -167,6 +167,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= +github.com/saba-futai/sudoku v0.0.1-g h1:4q6OuAA6COaRW+CgoQtdim5AUPzzm0uOkvbYpJnOaBE= +github.com/saba-futai/sudoku v0.0.1-g/go.mod h1:2ZRzRwz93cS2K/o2yOG4CPJEltcvk5y6vbvUmjftGU0= github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ= github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI= github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis= diff --git a/clash-meta-android/core/src/main/java/com/github/kr328/clash/core/model/Proxy.kt b/clash-meta-android/core/src/main/java/com/github/kr328/clash/core/model/Proxy.kt index 941bccd7a3..997fb1694b 100644 --- a/clash-meta-android/core/src/main/java/com/github/kr328/clash/core/model/Proxy.kt +++ b/clash-meta-android/core/src/main/java/com/github/kr328/clash/core/model/Proxy.kt @@ -37,6 +37,7 @@ data class Proxy( Ssh(false), Mieru(false), AnyTLS(false), + Sudoku(false), Relay(true), diff --git a/clash-meta/adapter/outbound/mieru.go b/clash-meta/adapter/outbound/mieru.go index d9feeba08f..e09b1898f2 100644 --- a/clash-meta/adapter/outbound/mieru.go +++ b/clash-meta/adapter/outbound/mieru.go @@ -11,6 +11,7 @@ import ( CN "github.com/metacubex/mihomo/common/net" "github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/proxydialer" + "github.com/metacubex/mihomo/component/resolver" C "github.com/metacubex/mihomo/constant" mieruclient "github.com/enfein/mieru/v3/apis/client" @@ -55,6 +56,31 @@ func (pd mieruPacketDialer) ListenPacket(ctx context.Context, network, laddr, ra return pd.Dialer.ListenPacket(ctx, network, laddr, rAddrPort) } +type mieruDNSResolver struct { + prefer C.DNSPrefer +} + +var _ mierucommon.DNSResolver = (*mieruDNSResolver)(nil) + +func (dr mieruDNSResolver) LookupIP(ctx context.Context, network, host string) (_ []net.IP, err error) { + var ip netip.Addr + switch dr.prefer { + case C.IPv4Only: + ip, err = resolver.ResolveIPv4WithResolver(ctx, host, resolver.ProxyServerHostResolver) + case C.IPv6Only: + ip, err = resolver.ResolveIPv6WithResolver(ctx, host, resolver.ProxyServerHostResolver) + case C.IPv6Prefer: + ip, err = resolver.ResolveIPPrefer6WithResolver(ctx, host, resolver.ProxyServerHostResolver) + default: + ip, err = resolver.ResolveIPWithResolver(ctx, host, resolver.ProxyServerHostResolver) + } + if err != nil { + return nil, fmt.Errorf("can't resolve ip: %w", err) + } + // TODO: handle IP4P (due to interface limitations, it's currently impossible to modify the port here) + return []net.IP{ip.AsSlice()}, nil +} + // DialContext implements C.ProxyAdapter func (m *Mieru) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { if err := m.ensureClientIsRunning(); err != nil { @@ -118,6 +144,7 @@ func (m *Mieru) ensureClientIsRunning() error { } config.Dialer = dialer config.PacketDialer = mieruPacketDialer{Dialer: dialer} + config.Resolver = mieruDNSResolver{prefer: m.prefer} if err := m.client.Store(config); err != nil { return err } @@ -260,6 +287,9 @@ func buildMieruClientConfig(option MieruOption) (*mieruclient.ClientConfig, erro }, Servers: []*mierupb.ServerEndpoint{server}, }, + DNSConfig: &mierucommon.ClientDNSConfig{ + BypassDialerDNS: true, + }, } if multiplexing, ok := mierupb.MultiplexingLevel_value[option.Multiplexing]; ok { config.Profile.Multiplexing = &mierupb.MultiplexingConfig{ diff --git a/clash-meta/component/generator/cmd.go b/clash-meta/component/generator/cmd.go index 320bb11d36..39064c7168 100644 --- a/clash-meta/component/generator/cmd.go +++ b/clash-meta/component/generator/cmd.go @@ -6,13 +6,14 @@ import ( "github.com/metacubex/mihomo/component/ech" "github.com/metacubex/mihomo/transport/vless/encryption" + "github.com/saba-futai/sudoku/pkg/crypto" "github.com/gofrs/uuid/v5" ) func Main(args []string) { if len(args) < 1 { - panic("Using: generate uuid/reality-keypair/wg-keypair/ech-keypair/vless-mlkem768/vless-x25519") + panic("Using: generate uuid/reality-keypair/wg-keypair/ech-keypair/vless-mlkem768/vless-x25519/sudoku-keypair") } switch args[0] { case "uuid": @@ -69,5 +70,19 @@ func Main(args []string) { fmt.Println("PrivateKey: " + privateKeyBase64) fmt.Println("Password: " + passwordBase64) fmt.Println("Hash32: " + hash32Base64) + case "sudoku-keypair": + // Generate Master Key + pair, err := crypto.GenerateMasterKey() + if err != nil { + panic(err) + } + // Split the master private key to get Available Private Key + availablePrivateKey, err := crypto.SplitPrivateKey(pair.Private) + if err != nil { + panic(err) + } + // Output: Available Private Key for client, Master Public Key for server + fmt.Println("PrivateKey: " + availablePrivateKey) + fmt.Println("PublicKey: " + crypto.EncodePoint(pair.Public)) } } diff --git a/clash-meta/docs/config.yaml b/clash-meta/docs/config.yaml index bef2c65914..2f959f33c3 100644 --- a/clash-meta/docs/config.yaml +++ b/clash-meta/docs/config.yaml @@ -1587,7 +1587,6 @@ listeners: aead-method: chacha20-poly1305 # 支持chacha20-poly1305或者aes-128-gcm以及none,sudoku的混淆层可以确保none情况下数据安全 padding-min: 1 # 填充最小长度 padding-max: 15 # 填充最大长度,均不建议过大 - seed: "" # 如果你不使用ED25519密钥对,就请填入uuid,否则仍然是公钥 table-type: prefer_ascii # 可选值:prefer_ascii、prefer_entropy 前者全ascii映射,后者保证熵值(汉明1)低于3 handshake-timeout: 5 # optional diff --git a/clash-meta/go.mod b/clash-meta/go.mod index 3ae50c987c..35dfcbe2b1 100644 --- a/clash-meta/go.mod +++ b/clash-meta/go.mod @@ -6,7 +6,7 @@ require ( github.com/bahlo/generic-list-go v0.2.0 github.com/coreos/go-iptables v0.8.0 github.com/dlclark/regexp2 v1.11.5 - github.com/enfein/mieru/v3 v3.24.1 + github.com/enfein/mieru/v3 v3.26.0 github.com/go-chi/chi/v5 v5.2.3 github.com/go-chi/render v1.0.3 github.com/gobwas/ws v1.4.0 diff --git a/clash-meta/go.sum b/clash-meta/go.sum index 7d24c9c06b..45210afb98 100644 --- a/clash-meta/go.sum +++ b/clash-meta/go.sum @@ -25,8 +25,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/enfein/mieru/v3 v3.24.1 h1:iX9py3+GExxJxLaxjHAEmQmoE1r0y2hDIsliija+jTI= -github.com/enfein/mieru/v3 v3.24.1/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM= +github.com/enfein/mieru/v3 v3.26.0 h1:ZsxCFkh3UfGSu9LL6EQ9+b97uxTJ7/AnJmLMyrbjSDI= +github.com/enfein/mieru/v3 v3.26.0/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM= github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358 h1:kXYqH/sL8dS/FdoFjr12ePjnLPorPo2FsnrHNuXSDyo= github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358/go.mod h1:hkIFzoiIPZYxdFOOLyDho59b7SrDfo+w3h+yWdlg45I= github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 h1:8j2RH289RJplhA6WfdaPqzg1MjH2K8wX5e0uhAxrw2g= diff --git a/clash-meta/listener/config/sudoku.go b/clash-meta/listener/config/sudoku.go index 855795f1c3..79aadf5db7 100644 --- a/clash-meta/listener/config/sudoku.go +++ b/clash-meta/listener/config/sudoku.go @@ -11,7 +11,6 @@ type SudokuServer struct { AEADMethod string `json:"aead-method,omitempty"` PaddingMin *int `json:"padding-min,omitempty"` PaddingMax *int `json:"padding-max,omitempty"` - Seed string `json:"seed,omitempty"` TableType string `json:"table-type,omitempty"` HandshakeTimeoutSecond *int `json:"handshake-timeout,omitempty"` } diff --git a/clash-meta/listener/inbound/sudoku.go b/clash-meta/listener/inbound/sudoku.go index d6e84af322..bc08772a61 100644 --- a/clash-meta/listener/inbound/sudoku.go +++ b/clash-meta/listener/inbound/sudoku.go @@ -19,7 +19,6 @@ type SudokuOption struct { AEADMethod string `inbound:"aead-method,omitempty"` PaddingMin *int `inbound:"padding-min,omitempty"` PaddingMax *int `inbound:"padding-max,omitempty"` - Seed string `inbound:"seed,omitempty"` TableType string `inbound:"table-type,omitempty"` // "prefer_ascii" or "prefer_entropy" HandshakeTimeoutSecond *int `inbound:"handshake-timeout,omitempty"` } @@ -53,7 +52,6 @@ func NewSudoku(options *SudokuOption) (*Sudoku, error) { AEADMethod: options.AEADMethod, PaddingMin: options.PaddingMin, PaddingMax: options.PaddingMax, - Seed: options.Seed, TableType: options.TableType, } if options.HandshakeTimeoutSecond != nil { diff --git a/clash-meta/listener/mieru/server.go b/clash-meta/listener/mieru/server.go index 3428c40a3c..ab80106944 100644 --- a/clash-meta/listener/mieru/server.go +++ b/clash-meta/listener/mieru/server.go @@ -44,7 +44,12 @@ func Handle(conn net.Conn, tunnel C.Tunnel, request *mierumodel.Request, additio metadata.DstIP, _ = netip.AddrFromSlice(request.DstAddr.IP) metadata.DstIP = metadata.DstIP.Unmap() } - inbound.ApplyAdditions(metadata, inbound.WithSrcAddr(conn.RemoteAddr()), inbound.WithInAddr(conn.LocalAddr())) + inbound.ApplyAdditions( + metadata, + inbound.WithInName(conn.(mierucommon.UserContext).UserName()), + inbound.WithSrcAddr(conn.RemoteAddr()), + inbound.WithInAddr(conn.LocalAddr()), + ) inbound.ApplyAdditions(metadata, additions...) tunnel.HandleTCPConn(conn, metadata) case mieruconstant.Socks5UDPAssociateCmd: // UDP diff --git a/clash-meta/listener/sudoku/server.go b/clash-meta/listener/sudoku/server.go index 87b2abd9d3..ce2b09afab 100644 --- a/clash-meta/listener/sudoku/server.go +++ b/clash-meta/listener/sudoku/server.go @@ -71,17 +71,12 @@ func New(config LC.SudokuServer, tunnel C.Tunnel, additions ...inbound.Addition) return nil, err } - seed := config.Seed - if seed == "" { - seed = config.Key - } - tableType := strings.ToLower(config.TableType) if tableType == "" { tableType = "prefer_ascii" } - table := sudokuobfs.NewTable(seed, tableType) + table := sudokuobfs.NewTable(config.Key, tableType) defaultConf := apis.DefaultConfig() paddingMin := defaultConf.PaddingMin diff --git a/clash-nyanpasu/.prettierignore b/clash-nyanpasu/.prettierignore index 466759da8f..8b9a24961f 100644 --- a/clash-nyanpasu/.prettierignore +++ b/clash-nyanpasu/.prettierignore @@ -6,6 +6,6 @@ dist/ pnpm-lock.yaml *.lock *.wxs -frontend/nyanpasu/src/routeTree.gen.ts +frontend/nyanpasu/src/route-tree.gen.ts frontend/nyanpasu/auto-imports.d.ts backend/tauri/gen/schemas/ diff --git a/clash-nyanpasu/frontend/nyanpasu/src/main.tsx b/clash-nyanpasu/frontend/nyanpasu/src/main.tsx index cb1ecbb64e..da7c42f1da 100644 --- a/clash-nyanpasu/frontend/nyanpasu/src/main.tsx +++ b/clash-nyanpasu/frontend/nyanpasu/src/main.tsx @@ -9,7 +9,7 @@ import '@csstools/normalize.css/opinionated.css' import { createRouter, RouterProvider } from '@tanstack/react-router' import './assets/styles/index.scss' import './assets/styles/tailwind.css' -import { routeTree } from './routeTree.gen' +import { routeTree } from './route-tree.gen' import './services/i18n' if (!window.ResizeObserver) { diff --git a/clash-nyanpasu/frontend/nyanpasu/src/pages/__root.tsx b/clash-nyanpasu/frontend/nyanpasu/src/pages/__root.tsx index deb961283a..940ace011c 100644 --- a/clash-nyanpasu/frontend/nyanpasu/src/pages/__root.tsx +++ b/clash-nyanpasu/frontend/nyanpasu/src/pages/__root.tsx @@ -10,7 +10,7 @@ import { ThemeModeProvider } from '@/components/layout/use-custom-theme' import UpdaterDialog from '@/components/updater/updater-dialog-wrapper' import { useNyanpasuStorageSubscribers } from '@/hooks/use-store' import { UpdaterProvider } from '@/hooks/use-updater' -import { FileRouteTypes } from '@/routeTree.gen' +import { FileRouteTypes } from '@/route-tree.gen' import { atomIsDrawer, memorizedRoutePathAtom } from '@/store' import { CssBaseline } from '@mui/material' import { StyledEngineProvider, useColorScheme } from '@mui/material/styles' diff --git a/clash-nyanpasu/frontend/nyanpasu/src/routeTree.gen.ts b/clash-nyanpasu/frontend/nyanpasu/src/route-tree.gen.ts similarity index 100% rename from clash-nyanpasu/frontend/nyanpasu/src/routeTree.gen.ts rename to clash-nyanpasu/frontend/nyanpasu/src/route-tree.gen.ts diff --git a/clash-nyanpasu/frontend/nyanpasu/src/store/index.ts b/clash-nyanpasu/frontend/nyanpasu/src/store/index.ts index a201b58412..ce4a3a521a 100644 --- a/clash-nyanpasu/frontend/nyanpasu/src/store/index.ts +++ b/clash-nyanpasu/frontend/nyanpasu/src/store/index.ts @@ -1,7 +1,7 @@ import { atom } from 'jotai' import { atomWithStorage, createJSONStorage } from 'jotai/utils' import { SortType } from '@/components/proxies/utils' -import { FileRouteTypes } from '@/routeTree.gen' +import { FileRouteTypes } from '@/route-tree.gen' import { NyanpasuStorage } from '@/services/storage' const atomWithLocalStorage = (key: string, initialValue: T) => { diff --git a/clash-nyanpasu/frontend/nyanpasu/vite.config.ts b/clash-nyanpasu/frontend/nyanpasu/vite.config.ts index bda31032ec..86a35cd102 100644 --- a/clash-nyanpasu/frontend/nyanpasu/vite.config.ts +++ b/clash-nyanpasu/frontend/nyanpasu/vite.config.ts @@ -10,7 +10,7 @@ import svgr from 'vite-plugin-svgr' import tsconfigPaths from 'vite-tsconfig-paths' // import tailwindPlugin from '@tailwindcss/vite' // import react from "@vitejs/plugin-react"; -import { TanStackRouterVite } from '@tanstack/router-plugin/vite' +import { tanstackRouter } from '@tanstack/router-plugin/vite' import legacy from '@vitejs/plugin-legacy' import react from '@vitejs/plugin-react-swc' @@ -98,7 +98,13 @@ export default defineConfig(({ command, mode }) => { }, }), builtinVars(), - TanStackRouterVite(), + tanstackRouter({ + target: 'react', + autoCodeSplitting: true, + routesDirectory: `src/pages`, + generatedRouteTree: `src/route-tree.gen.ts`, + routeFileIgnorePattern: '_modules', + }), svgr(), react({ // babel: { diff --git a/clash-nyanpasu/manifest/version.json b/clash-nyanpasu/manifest/version.json index b10550bebf..d5d6766d70 100644 --- a/clash-nyanpasu/manifest/version.json +++ b/clash-nyanpasu/manifest/version.json @@ -2,7 +2,7 @@ "manifest_version": 1, "latest": { "mihomo": "v1.19.16", - "mihomo_alpha": "alpha-93de49d", + "mihomo_alpha": "alpha-d1f89fa", "clash_rs": "v0.9.2", "clash_premium": "2023-09-05-gdcc8d87", "clash_rs_alpha": "0.9.2-alpha+sha.87c7b2c" @@ -69,5 +69,5 @@ "linux-armv7hf": "clash-armv7-unknown-linux-gnueabihf" } }, - "updated_at": "2025-11-29T22:21:10.978Z" + "updated_at": "2025-11-30T22:21:19.709Z" } diff --git a/filebrowser/frontend/pnpm-lock.yaml b/filebrowser/frontend/pnpm-lock.yaml index 698416bbab..2fd8bacc2b 100644 --- a/filebrowser/frontend/pnpm-lock.yaml +++ b/filebrowser/frontend/pnpm-lock.yaml @@ -113,10 +113,10 @@ importers: version: 8.48.0(@typescript-eslint/parser@8.37.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3) '@vitejs/plugin-legacy': specifier: ^7.2.1 - version: 7.2.1(terser@5.44.1)(vite@7.2.4(@types/node@24.10.1)(terser@5.44.1)(yaml@2.7.0)) + version: 7.2.1(terser@5.44.1)(vite@7.2.6(@types/node@24.10.1)(terser@5.44.1)(yaml@2.7.0)) '@vitejs/plugin-vue': specifier: ^6.0.1 - version: 6.0.2(vite@7.2.4(@types/node@24.10.1)(terser@5.44.1)(yaml@2.7.0))(vue@3.5.25(typescript@5.9.3)) + version: 6.0.2(vite@7.2.6(@types/node@24.10.1)(terser@5.44.1)(yaml@2.7.0))(vue@3.5.25(typescript@5.9.3)) '@vue/eslint-config-prettier': specifier: ^10.2.0 version: 10.2.0(eslint@9.39.1)(prettier@3.7.3) @@ -155,7 +155,7 @@ importers: version: 5.9.3 vite: specifier: ^7.2.2 - version: 7.2.4(@types/node@24.10.1)(terser@5.44.1)(yaml@2.7.0) + version: 7.2.6(@types/node@24.10.1)(terser@5.44.1)(yaml@2.7.0) vite-plugin-compression2: specifier: ^2.3.1 version: 2.3.1(rollup@4.53.3) @@ -2439,8 +2439,8 @@ packages: vite-plugin-compression2@2.3.1: resolution: {integrity: sha512-bnhLTsurtvOiiP6EMISIKVsOMCeTAjE6FJbyqQus3W4mtAxF7pCuC4puUIAiCgNs98tOCpqo6GIXJXTLufzIaw==} - vite@7.2.4: - resolution: {integrity: sha512-NL8jTlbo0Tn4dUEXEsUg8KeyG/Lkmc4Fnzb8JXN/Ykm9G4HNImjtABMJgkQoVjOBN/j2WAwDTRytdqJbZsah7w==} + vite@7.2.6: + resolution: {integrity: sha512-tI2l/nFHC5rLh7+5+o7QjKjSR04ivXDF4jcgV0f/bTQ+OJiITy5S6gaynVsEM+7RqzufMnVbIon6Sr5x1SDYaQ==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -3795,7 +3795,7 @@ snapshots: global: 4.4.0 is-function: 1.0.2 - '@vitejs/plugin-legacy@7.2.1(terser@5.44.1)(vite@7.2.4(@types/node@24.10.1)(terser@5.44.1)(yaml@2.7.0))': + '@vitejs/plugin-legacy@7.2.1(terser@5.44.1)(vite@7.2.6(@types/node@24.10.1)(terser@5.44.1)(yaml@2.7.0))': dependencies: '@babel/core': 7.28.5 '@babel/plugin-transform-dynamic-import': 7.27.1(@babel/core@7.28.5) @@ -3810,14 +3810,14 @@ snapshots: regenerator-runtime: 0.14.1 systemjs: 6.15.1 terser: 5.44.1 - vite: 7.2.4(@types/node@24.10.1)(terser@5.44.1)(yaml@2.7.0) + vite: 7.2.6(@types/node@24.10.1)(terser@5.44.1)(yaml@2.7.0) transitivePeerDependencies: - supports-color - '@vitejs/plugin-vue@6.0.2(vite@7.2.4(@types/node@24.10.1)(terser@5.44.1)(yaml@2.7.0))(vue@3.5.25(typescript@5.9.3))': + '@vitejs/plugin-vue@6.0.2(vite@7.2.6(@types/node@24.10.1)(terser@5.44.1)(yaml@2.7.0))(vue@3.5.25(typescript@5.9.3))': dependencies: '@rolldown/pluginutils': 1.0.0-beta.50 - vite: 7.2.4(@types/node@24.10.1)(terser@5.44.1)(yaml@2.7.0) + vite: 7.2.6(@types/node@24.10.1)(terser@5.44.1)(yaml@2.7.0) vue: 3.5.25(typescript@5.9.3) '@volar/language-core@2.4.23': @@ -4985,7 +4985,7 @@ snapshots: transitivePeerDependencies: - rollup - vite@7.2.4(@types/node@24.10.1)(terser@5.44.1)(yaml@2.7.0): + vite@7.2.6(@types/node@24.10.1)(terser@5.44.1)(yaml@2.7.0): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.3) diff --git a/filebrowser/go.mod b/filebrowser/go.mod index e828455db2..e6faa38439 100644 --- a/filebrowser/go.mod +++ b/filebrowser/go.mod @@ -17,7 +17,7 @@ require ( github.com/mholt/archives v0.1.5 github.com/mitchellh/go-homedir v1.1.0 github.com/samber/lo v1.52.0 - github.com/shirou/gopsutil/v4 v4.25.10 + github.com/shirou/gopsutil/v4 v4.25.11 github.com/spf13/afero v1.15.0 github.com/spf13/cobra v1.10.1 github.com/spf13/pflag v1.0.10 @@ -44,7 +44,7 @@ require ( github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd // indirect github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 // indirect - github.com/ebitengine/purego v0.9.0 // indirect + github.com/ebitengine/purego v0.9.1 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/go-errors/errors v1.5.1 // indirect github.com/go-ole/go-ole v1.3.0 // indirect diff --git a/filebrowser/go.sum b/filebrowser/go.sum index 443c885bf1..87b9ba85dc 100644 --- a/filebrowser/go.sum +++ b/filebrowser/go.sum @@ -75,8 +75,8 @@ github.com/dsoprea/go-utility/v2 v2.0.0-20221003142440-7a1927d49d9d/go.mod h1:LV github.com/dsoprea/go-utility/v2 v2.0.0-20221003160719-7bc88537c05e/go.mod h1:VZ7cB0pTjm1ADBWhJUOHESu4ZYy9JN+ZPqjfiW09EPU= github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 h1:DilThiXje0z+3UQ5YjYiSRRzVdtamFpvBQXKwMglWqw= github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349/go.mod h1:4GC5sXji84i/p+irqghpPFZBF8tRN/Q7+700G0/DLe8= -github.com/ebitengine/purego v0.9.0 h1:mh0zpKBIXDceC63hpvPuGLiJ8ZAa3DfrFTudmfi8A4k= -github.com/ebitengine/purego v0.9.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A= +github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ= @@ -204,8 +204,8 @@ github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDc github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik= github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw= github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0= -github.com/shirou/gopsutil/v4 v4.25.10 h1:at8lk/5T1OgtuCp+AwrDofFRjnvosn0nkN2OLQ6g8tA= -github.com/shirou/gopsutil/v4 v4.25.10/go.mod h1:+kSwyC8DRUD9XXEHCAFjK+0nuArFJM0lva+StQAcskM= +github.com/shirou/gopsutil/v4 v4.25.11 h1:X53gB7muL9Gnwwo2evPSE+SfOrltMoR6V3xJAXZILTY= +github.com/shirou/gopsutil/v4 v4.25.11/go.mod h1:EivAfP5x2EhLp2ovdpKSozecVXn1TmuG7SMzs/Wh4PU= github.com/sorairolake/lzip-go v0.3.8 h1:j5Q2313INdTA80ureWYRhX+1K78mUXfMoPZCw/ivWik= github.com/sorairolake/lzip-go v0.3.8/go.mod h1:JcBqGMV0frlxwrsE9sMWXDjqn3EeVf0/54YPsw66qkU= github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= diff --git a/lede/include/kernel-6.1 b/lede/include/kernel-6.1 index 2a1959746c..b8fd4ba2b9 100644 --- a/lede/include/kernel-6.1 +++ b/lede/include/kernel-6.1 @@ -1,2 +1,2 @@ LINUX_VERSION-6.1 = .158 -LINUX_KERNEL_HASH-6.1.158= ad068bfdb604ec0f4f7de385c8e7ab944008aa78a4aeeca94f53206e6726bfda +LINUX_KERNEL_HASH-6.1.158 = ad068bfdb604ec0f4f7de385c8e7ab944008aa78a4aeeca94f53206e6726bfda diff --git a/lede/include/kernel-6.12 b/lede/include/kernel-6.12 index 920ea0645e..73c2f33a10 100644 --- a/lede/include/kernel-6.12 +++ b/lede/include/kernel-6.12 @@ -1,2 +1,2 @@ -LINUX_VERSION-6.12 = .58 -LINUX_KERNEL_HASH-6.12.58 = 5f1c4c546660a6a81046fdfa6195306bad2c8d17c0d69876dc100a85ad4613ac +LINUX_VERSION-6.12 = .60 +LINUX_KERNEL_HASH-6.12.60 = a63096b2147411d683cecbf87622bb2ff4885bac2b3641d3d4f10250c89cdcf8 diff --git a/lede/include/kernel-6.6 b/lede/include/kernel-6.6 index 6bedc40098..e5c3c5c302 100644 --- a/lede/include/kernel-6.6 +++ b/lede/include/kernel-6.6 @@ -1,2 +1,2 @@ -LINUX_VERSION-6.6 = .116 -LINUX_KERNEL_HASH-6.6.116 = a9a59742c29be284c205dc87cbe9b065f9688488132c8f5a6057a5539230a51d +LINUX_VERSION-6.6 = .118 +LINUX_KERNEL_HASH-6.6.118 = 4bdddce35474afc8d26f74ebfbcd0e1045ecd15f69e60f53529dba143374b17d diff --git a/lede/target/linux/generic/backport-6.12/780-23-v6.14-r8169-adjust-version-numbering-for-RTL8126.patch b/lede/target/linux/generic/backport-6.12/780-23-v6.14-r8169-adjust-version-numbering-for-RTL8126.patch index c3a82985f1..9e3fd0a66e 100644 --- a/lede/target/linux/generic/backport-6.12/780-23-v6.14-r8169-adjust-version-numbering-for-RTL8126.patch +++ b/lede/target/linux/generic/backport-6.12/780-23-v6.14-r8169-adjust-version-numbering-for-RTL8126.patch @@ -183,7 +183,7 @@ Signed-off-by: Jakub Kicinski else if (tp->mac_version == RTL_GIGA_MAC_VER_63) r8168_mac_ocp_modify(tp, 0xe614, 0x0700, 0x0200); @@ -3742,8 +3742,8 @@ static void rtl_hw_start_8125_common(str - r8168_mac_ocp_modify(tp, 0xe056, 0x00f0, 0x0030); + r8168_mac_ocp_modify(tp, 0xe056, 0x00f0, 0x0000); r8168_mac_ocp_modify(tp, 0xe040, 0x1000, 0x0000); r8168_mac_ocp_modify(tp, 0xea1c, 0x0003, 0x0001); - if (tp->mac_version == RTL_GIGA_MAC_VER_65 || @@ -233,7 +233,7 @@ Signed-off-by: Jakub Kicinski padto = max_t(unsigned int, padto, ETH_ZLEN); break; default: -@@ -5293,7 +5293,7 @@ static void rtl_hw_initialize(struct rtl +@@ -5294,7 +5294,7 @@ static void rtl_hw_initialize(struct rtl case RTL_GIGA_MAC_VER_40 ... RTL_GIGA_MAC_VER_48: rtl_hw_init_8168g(tp); break; diff --git a/lede/target/linux/generic/backport-6.12/780-39-v6.16-r8169-merge-chip-versions-70-and-71-RTL8126A.patch b/lede/target/linux/generic/backport-6.12/780-39-v6.16-r8169-merge-chip-versions-70-and-71-RTL8126A.patch index da105f6fe3..ecc04ef6ae 100644 --- a/lede/target/linux/generic/backport-6.12/780-39-v6.16-r8169-merge-chip-versions-70-and-71-RTL8126A.patch +++ b/lede/target/linux/generic/backport-6.12/780-39-v6.16-r8169-merge-chip-versions-70-and-71-RTL8126A.patch @@ -69,7 +69,7 @@ Signed-off-by: Jakub Kicinski else if (tp->mac_version == RTL_GIGA_MAC_VER_63) r8168_mac_ocp_modify(tp, 0xe614, 0x0700, 0x0200); @@ -3714,8 +3710,7 @@ static void rtl_hw_start_8125_common(str - r8168_mac_ocp_modify(tp, 0xe056, 0x00f0, 0x0030); + r8168_mac_ocp_modify(tp, 0xe056, 0x00f0, 0x0000); r8168_mac_ocp_modify(tp, 0xe040, 0x1000, 0x0000); r8168_mac_ocp_modify(tp, 0xea1c, 0x0003, 0x0001); - if (tp->mac_version == RTL_GIGA_MAC_VER_70 || diff --git a/lede/target/linux/generic/backport-6.12/780-42-v6.16-r8169-add-support-for-RTL8127A.patch b/lede/target/linux/generic/backport-6.12/780-42-v6.16-r8169-add-support-for-RTL8127A.patch index 37a2d8667e..5ed1f341ec 100644 --- a/lede/target/linux/generic/backport-6.12/780-42-v6.16-r8169-add-support-for-RTL8127A.patch +++ b/lede/target/linux/generic/backport-6.12/780-42-v6.16-r8169-add-support-for-RTL8127A.patch @@ -97,7 +97,7 @@ Signed-off-by: Jakub Kicinski else if (tp->mac_version == RTL_GIGA_MAC_VER_63) r8168_mac_ocp_modify(tp, 0xe614, 0x0700, 0x0200); @@ -3708,7 +3720,8 @@ static void rtl_hw_start_8125_common(str - r8168_mac_ocp_modify(tp, 0xe056, 0x00f0, 0x0030); + r8168_mac_ocp_modify(tp, 0xe056, 0x00f0, 0x0000); r8168_mac_ocp_modify(tp, 0xe040, 0x1000, 0x0000); r8168_mac_ocp_modify(tp, 0xea1c, 0x0003, 0x0001); - if (tp->mac_version == RTL_GIGA_MAC_VER_70) diff --git a/lede/target/linux/generic/backport-6.6/780-08-v6.9-r8169-add-support-for-RTL8126A.patch b/lede/target/linux/generic/backport-6.6/780-08-v6.9-r8169-add-support-for-RTL8126A.patch index 210730d6b1..4c4b89b951 100644 --- a/lede/target/linux/generic/backport-6.6/780-08-v6.9-r8169-add-support-for-RTL8126A.patch +++ b/lede/target/linux/generic/backport-6.6/780-08-v6.9-r8169-add-support-for-RTL8126A.patch @@ -221,7 +221,7 @@ Signed-off-by: David S. Miller if (tp->mac_version == RTL_GIGA_MAC_VER_63) r8168_mac_ocp_modify(tp, 0xe63e, 0x0c30, 0x0000); @@ -3611,6 +3645,10 @@ static void rtl_hw_start_8125_common(str - r8168_mac_ocp_modify(tp, 0xe056, 0x00f0, 0x0030); + r8168_mac_ocp_modify(tp, 0xe056, 0x00f0, 0x0000); r8168_mac_ocp_modify(tp, 0xe040, 0x1000, 0x0000); r8168_mac_ocp_modify(tp, 0xea1c, 0x0003, 0x0001); + if (tp->mac_version == RTL_GIGA_MAC_VER_65) @@ -321,7 +321,7 @@ Signed-off-by: David S. Miller padto = max_t(unsigned int, padto, ETH_ZLEN); break; default: -@@ -5147,7 +5204,7 @@ static void rtl_hw_initialize(struct rtl +@@ -5149,7 +5206,7 @@ static void rtl_hw_initialize(struct rtl case RTL_GIGA_MAC_VER_40 ... RTL_GIGA_MAC_VER_48: rtl_hw_init_8168g(tp); break; diff --git a/lede/target/linux/generic/backport-6.6/780-22-v6.12-r8169-add-support-for-RTL8126A-rev.b.patch b/lede/target/linux/generic/backport-6.6/780-22-v6.12-r8169-add-support-for-RTL8126A-rev.b.patch index 3360b61fad..a921806bb6 100644 --- a/lede/target/linux/generic/backport-6.6/780-22-v6.12-r8169-add-support-for-RTL8126A-rev.b.patch +++ b/lede/target/linux/generic/backport-6.6/780-22-v6.12-r8169-add-support-for-RTL8126A-rev.b.patch @@ -181,7 +181,7 @@ Signed-off-by: Jakub Kicinski else if (tp->mac_version == RTL_GIGA_MAC_VER_63) r8168_mac_ocp_modify(tp, 0xe614, 0x0700, 0x0200); @@ -3739,7 +3748,8 @@ static void rtl_hw_start_8125_common(str - r8168_mac_ocp_modify(tp, 0xe056, 0x00f0, 0x0030); + r8168_mac_ocp_modify(tp, 0xe056, 0x00f0, 0x0000); r8168_mac_ocp_modify(tp, 0xe040, 0x1000, 0x0000); r8168_mac_ocp_modify(tp, 0xea1c, 0x0003, 0x0001); - if (tp->mac_version == RTL_GIGA_MAC_VER_65) @@ -224,7 +224,7 @@ Signed-off-by: Jakub Kicinski padto = max_t(unsigned int, padto, ETH_ZLEN); break; default: -@@ -5294,7 +5306,7 @@ static void rtl_hw_initialize(struct rtl +@@ -5296,7 +5308,7 @@ static void rtl_hw_initialize(struct rtl case RTL_GIGA_MAC_VER_40 ... RTL_GIGA_MAC_VER_48: rtl_hw_init_8168g(tp); break; diff --git a/lede/target/linux/generic/backport-6.6/780-47-v6.14-r8169-adjust-version-numbering-for-RTL8126.patch b/lede/target/linux/generic/backport-6.6/780-47-v6.14-r8169-adjust-version-numbering-for-RTL8126.patch index efa41eb67b..b1678bb9ac 100644 --- a/lede/target/linux/generic/backport-6.6/780-47-v6.14-r8169-adjust-version-numbering-for-RTL8126.patch +++ b/lede/target/linux/generic/backport-6.6/780-47-v6.14-r8169-adjust-version-numbering-for-RTL8126.patch @@ -183,7 +183,7 @@ Signed-off-by: Jakub Kicinski else if (tp->mac_version == RTL_GIGA_MAC_VER_63) r8168_mac_ocp_modify(tp, 0xe614, 0x0700, 0x0200); @@ -3720,8 +3720,8 @@ static void rtl_hw_start_8125_common(str - r8168_mac_ocp_modify(tp, 0xe056, 0x00f0, 0x0030); + r8168_mac_ocp_modify(tp, 0xe056, 0x00f0, 0x0000); r8168_mac_ocp_modify(tp, 0xe040, 0x1000, 0x0000); r8168_mac_ocp_modify(tp, 0xea1c, 0x0003, 0x0001); - if (tp->mac_version == RTL_GIGA_MAC_VER_65 || @@ -233,7 +233,7 @@ Signed-off-by: Jakub Kicinski padto = max_t(unsigned int, padto, ETH_ZLEN); break; default: -@@ -5274,7 +5274,7 @@ static void rtl_hw_initialize(struct rtl +@@ -5276,7 +5276,7 @@ static void rtl_hw_initialize(struct rtl case RTL_GIGA_MAC_VER_40 ... RTL_GIGA_MAC_VER_48: rtl_hw_init_8168g(tp); break; diff --git a/lede/target/linux/generic/hack-6.12/983-add-bcm-fullconenat-to-nft.patch b/lede/target/linux/generic/hack-6.12/983-add-bcm-fullconenat-to-nft.patch index ec868e19f1..665c88c98a 100644 --- a/lede/target/linux/generic/hack-6.12/983-add-bcm-fullconenat-to-nft.patch +++ b/lede/target/linux/generic/hack-6.12/983-add-bcm-fullconenat-to-nft.patch @@ -1,6 +1,6 @@ --- a/include/uapi/linux/netfilter/nf_tables.h +++ b/include/uapi/linux/netfilter/nf_tables.h -@@ -1477,12 +1477,14 @@ enum nft_tproxy_attributes { +@@ -1481,12 +1481,14 @@ enum nft_tproxy_attributes { * @NFTA_MASQ_FLAGS: NAT flags (see NF_NAT_RANGE_* in linux/netfilter/nf_nat.h) (NLA_U32) * @NFTA_MASQ_REG_PROTO_MIN: source register of proto range start (NLA_U32: nft_registers) * @NFTA_MASQ_REG_PROTO_MAX: source register of proto range end (NLA_U32: nft_registers) @@ -33,7 +33,7 @@ }; static int nft_masq_validate(const struct nft_ctx *ctx, -@@ -51,6 +53,9 @@ static int nft_masq_init(const struct nf +@@ -50,6 +52,9 @@ static int nft_masq_init(const struct nf if (tb[NFTA_MASQ_FLAGS]) priv->flags = ntohl(nla_get_be32(tb[NFTA_MASQ_FLAGS])); @@ -41,9 +41,9 @@ + priv->fullcone = nla_get_u8(tb[NFTA_MASQ_REG_FULLCONE]); + if (tb[NFTA_MASQ_REG_PROTO_MIN]) { - err = nft_parse_register_load(tb[NFTA_MASQ_REG_PROTO_MIN], + err = nft_parse_register_load(ctx, tb[NFTA_MASQ_REG_PROTO_MIN], &priv->sreg_proto_min, plen); -@@ -80,6 +85,9 @@ static int nft_masq_dump(struct sk_buff +@@ -79,6 +84,9 @@ static int nft_masq_dump(struct sk_buff nla_put_be32(skb, NFTA_MASQ_FLAGS, htonl(priv->flags))) goto nla_put_failure; @@ -53,7 +53,7 @@ if (priv->sreg_proto_min) { if (nft_dump_register(skb, NFTA_MASQ_REG_PROTO_MIN, priv->sreg_proto_min) || -@@ -112,6 +120,9 @@ static void nft_masq_eval(const struct n +@@ -111,6 +119,9 @@ static void nft_masq_eval(const struct n switch (nft_pf(pkt)) { case NFPROTO_IPV4: diff --git a/lede/target/linux/generic/hack-6.6/204-module_strip.patch b/lede/target/linux/generic/hack-6.6/204-module_strip.patch index 1e6573dfc5..9402ecb147 100644 --- a/lede/target/linux/generic/hack-6.6/204-module_strip.patch +++ b/lede/target/linux/generic/hack-6.6/204-module_strip.patch @@ -14,7 +14,7 @@ Signed-off-by: Felix Fietkau --- a/include/linux/module.h +++ b/include/linux/module.h -@@ -164,6 +164,7 @@ extern void cleanup_module(void); +@@ -166,6 +166,7 @@ struct module_kobject *lookup_or_create_ /* Generic info of form tag = "info" */ #define MODULE_INFO(tag, info) __MODULE_INFO(tag, tag, info) @@ -22,7 +22,7 @@ Signed-off-by: Felix Fietkau /* For userspace: you can also call me... */ #define MODULE_ALIAS(_alias) MODULE_INFO(alias, _alias) -@@ -233,12 +234,12 @@ extern void cleanup_module(void); +@@ -235,12 +236,12 @@ struct module_kobject *lookup_or_create_ * Author(s), use "Name " or just "Name", for multiple * authors use multiple MODULE_AUTHOR() statements/lines. */ @@ -38,7 +38,7 @@ Signed-off-by: Felix Fietkau /* Creates an alias so file2alias.c can find device table. */ #define MODULE_DEVICE_TABLE(type, name) \ extern typeof(name) __mod_##type##__##name##_device_table \ -@@ -265,7 +266,9 @@ extern typeof(name) __mod_##type##__##na +@@ -267,7 +268,9 @@ extern typeof(name) __mod_##type##__##na */ #if defined(MODULE) || !defined(CONFIG_SYSFS) @@ -49,7 +49,7 @@ Signed-off-by: Felix Fietkau #else #define MODULE_VERSION(_version) \ MODULE_INFO(version, _version); \ -@@ -288,7 +291,7 @@ extern typeof(name) __mod_##type##__##na +@@ -290,7 +293,7 @@ extern typeof(name) __mod_##type##__##na /* Optional firmware file (or files) needed by the module * format is simply firmware file name. Multiple firmware * files require multiple MODULE_FIRMWARE() specifiers */ @@ -88,7 +88,7 @@ Signed-off-by: Felix Fietkau --- a/kernel/module/Kconfig +++ b/kernel/module/Kconfig -@@ -389,4 +389,11 @@ config MODULES_TREE_LOOKUP +@@ -390,4 +390,11 @@ config MODULES_TREE_LOOKUP def_bool y depends on PERF_EVENTS || TRACING || CFI_CLANG diff --git a/lede/target/linux/generic/pending-6.12/681-net-remove-NETIF_F_GSO_FRAGLIST-from-NETIF_F_GSO_SOF.patch b/lede/target/linux/generic/pending-6.12/681-net-remove-NETIF_F_GSO_FRAGLIST-from-NETIF_F_GSO_SOF.patch index 4cc5717b1c..66926bd5fb 100644 --- a/lede/target/linux/generic/pending-6.12/681-net-remove-NETIF_F_GSO_FRAGLIST-from-NETIF_F_GSO_SOF.patch +++ b/lede/target/linux/generic/pending-6.12/681-net-remove-NETIF_F_GSO_FRAGLIST-from-NETIF_F_GSO_SOF.patch @@ -96,9 +96,9 @@ Signed-off-by: Felix Fietkau } --- a/net/core/sock.c +++ b/net/core/sock.c -@@ -2550,7 +2550,7 @@ void sk_setup_caps(struct sock *sk, stru - if (sk_is_tcp(sk)) - sk->sk_route_caps |= NETIF_F_GSO; +@@ -2554,7 +2554,7 @@ void sk_setup_caps(struct sock *sk, stru + icsk->icsk_ack.dst_quick_ack = dst_metric(dst, RTAX_QUICKACK); + } if (sk->sk_route_caps & NETIF_F_GSO) - sk->sk_route_caps |= NETIF_F_GSO_SOFTWARE; + sk->sk_route_caps |= NETIF_F_GSO_SOFTWARE_ALL; @@ -107,7 +107,7 @@ Signed-off-by: Felix Fietkau if (sk_can_gso(sk)) { --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h -@@ -2021,7 +2021,7 @@ void ieee80211_color_collision_detection +@@ -2023,7 +2023,7 @@ void ieee80211_color_collision_detection /* interface handling */ #define MAC80211_SUPPORTED_FEATURES_TX (NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM | \ NETIF_F_HW_CSUM | NETIF_F_SG | \ diff --git a/lede/target/linux/mediatek/patches-6.6/734-net-phy-add-Airoha-EN8801SC-PHY.patch b/lede/target/linux/mediatek/patches-6.6/734-net-phy-add-Airoha-EN8801SC-PHY.patch index 90c030fb89..fa29f04ba2 100644 --- a/lede/target/linux/mediatek/patches-6.6/734-net-phy-add-Airoha-EN8801SC-PHY.patch +++ b/lede/target/linux/mediatek/patches-6.6/734-net-phy-add-Airoha-EN8801SC-PHY.patch @@ -14,9 +14,9 @@ Signed-off-by: Robert Marko --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig -@@ -142,6 +142,11 @@ endif # RTL8366_SMI - - comment "MII PHY device drivers" +@@ -154,6 +154,11 @@ config AS21XXX_PHY + AS21210PB1 that all register with the PHY ID 0x7500 0x7500 + before the firmware is loaded. +config AIROHA_EN8801SC_PHY + tristate "Airoha EN8801SC Gigabit PHY" diff --git a/lede/target/linux/rockchip/patches-6.12/042-06-v6.14-compiler-h-add-const_true.patch b/lede/target/linux/rockchip/patches-6.12/042-06-v6.14-compiler-h-add-const_true.patch index 7f48b46e08..92e612be25 100644 --- a/lede/target/linux/rockchip/patches-6.12/042-06-v6.14-compiler-h-add-const_true.patch +++ b/lede/target/linux/rockchip/patches-6.12/042-06-v6.14-compiler-h-add-const_true.patch @@ -86,8 +86,8 @@ Signed-off-by: Yury Norov --- a/include/linux/compiler.h +++ b/include/linux/compiler.h -@@ -303,6 +303,28 @@ static inline void *offset_to_ptr(const - #define statically_true(x) (__builtin_constant_p(x) && (x)) +@@ -317,6 +317,28 @@ static inline void *offset_to_ptr(const + #define const_true(x) __builtin_choose_expr(__is_constexpr(x), x, false) /* + * Similar to statically_true() but produces a constant expression diff --git a/lede/target/linux/rockchip/patches-6.12/311-07-v6.13-initial-support-for-rk3576-ufs-controller.patch b/lede/target/linux/rockchip/patches-6.12/311-07-v6.13-initial-support-for-rk3576-ufs-controller.patch index 1dac11dd9a..19739bc321 100644 --- a/lede/target/linux/rockchip/patches-6.12/311-07-v6.13-initial-support-for-rk3576-ufs-controller.patch +++ b/lede/target/linux/rockchip/patches-6.12/311-07-v6.13-initial-support-for-rk3576-ufs-controller.patch @@ -14,8 +14,8 @@ Changes in v2: None --- a/arch/arm64/boot/dts/rockchip/rk3576.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3576.dtsi -@@ -1110,6 +1110,30 @@ - }; +@@ -1556,6 +1556,30 @@ + status = "disabled"; }; + ufshc: ufshc@2a2d0000 { diff --git a/lede/target/linux/rockchip/patches-6.12/802-arm64-dts-rockchip-add-hardware-random-number-genera.patch b/lede/target/linux/rockchip/patches-6.12/802-arm64-dts-rockchip-add-hardware-random-number-genera.patch index c9f99a9701..730f3beb33 100644 --- a/lede/target/linux/rockchip/patches-6.12/802-arm64-dts-rockchip-add-hardware-random-number-genera.patch +++ b/lede/target/linux/rockchip/patches-6.12/802-arm64-dts-rockchip-add-hardware-random-number-genera.patch @@ -33,7 +33,7 @@ Signed-off-by: wevsty + rng: rng@fe378000 { + compatible = "rockchip,trngv1"; + reg = <0x0 0xfe378000 0x0 0x200>; -+ interrupts = ; ++ interrupts = ; + clocks = <&scmi_clk SCMI_HCLK_SECURE_NS>; + clock-names = "hclk_trng"; + resets = <&scmi_reset SRST_H_TRNG_NS>; diff --git a/mieru/Makefile b/mieru/Makefile index 11108d3425..4a43920e37 100644 --- a/mieru/Makefile +++ b/mieru/Makefile @@ -32,7 +32,7 @@ PROJECT_NAME=$(shell basename "${ROOT}") # - pkg/version/current.go # # Use `tools/bump_version.sh` script to change all those files at one shot. -VERSION="3.25.0" +VERSION="3.26.0" # With .ONESHELL, each recipe is executed in a single shell instance. # This allows `cd` to affect subsequent commands in the same recipe. diff --git a/mieru/README.md b/mieru/README.md index 0b949bfc4d..a06236d145 100644 --- a/mieru/README.md +++ b/mieru/README.md @@ -29,6 +29,7 @@ The mieru proxy software suite consists of two parts, a client software called m - Desktop (Windows, MacOS, Linux) - [Clash Verge Rev](https://www.clashverge.dev/) - [Mihomo Party](https://mihomo.party/) + - [NekoBox => qr243vbi_Box](https://qr243vbi.github.io/nekobox/#/) - Android - [ClashMetaForAndroid](https://github.com/MetaCubeX/ClashMetaForAndroid) - [ClashMi](https://clashmi.app/) diff --git a/mieru/README.zh_CN.md b/mieru/README.zh_CN.md index a4956e8518..4186de697c 100644 --- a/mieru/README.zh_CN.md +++ b/mieru/README.zh_CN.md @@ -27,6 +27,7 @@ mieru 代理软件由称为 mieru【見える】的客户端软件和称为 mita - 桌面 (Windows, MacOS, Linux) - [Clash Verge Rev](https://www.clashverge.dev/) - [Mihomo Party](https://mihomo.party/) + - [NekoBox => qr243vbi_Box](https://qr243vbi.github.io/nekobox/#/) - 安卓 - [ClashMetaForAndroid](https://github.com/MetaCubeX/ClashMetaForAndroid) - [ClashMi](https://clashmi.app/) diff --git a/mieru/apis/client/client.go b/mieru/apis/client/client.go index 9598e27c02..38711a91c8 100644 --- a/mieru/apis/client/client.go +++ b/mieru/apis/client/client.go @@ -89,7 +89,7 @@ func (mc *mieruClient) Start() error { return ErrNoClientConfig } - mux, err := appctlcommon.NewClientMuxFromProfile(mc.config.Profile, mc.config.Dialer, mc.config.PacketDialer, mc.config.Resolver) + mux, err := appctlcommon.NewClientMuxFromProfile(mc.config.Profile, mc.config.Dialer, mc.config.PacketDialer, mc.config.Resolver, mc.config.DNSConfig) if err != nil { return err } diff --git a/mieru/apis/client/interface.go b/mieru/apis/client/interface.go index 4ebc0e2269..6cf8b0f89a 100644 --- a/mieru/apis/client/interface.go +++ b/mieru/apis/client/interface.go @@ -104,10 +104,12 @@ type ClientConfig struct { // If set, the resolver translates proxy server domain name into IP addresses. // - // This field is not required, if Dialer or PacketDialer is able to do DNS, - // or proxy server endpoints are IP addresses rather than domain names. - // Otherwise, the proxy server won't be reachable. + // This field is not required if proxy server endpoints are IP addresses + // rather than domain names. Otherwise, the proxy server may not be reachable. Resolver apicommon.DNSResolver + + // Optional DNS configurations. + DNSConfig *apicommon.ClientDNSConfig } // NewClient creates a blank mieru client with no client config. diff --git a/mieru/apis/common/dns.go b/mieru/apis/common/dns.go index 16f14453c2..d5aac2e00b 100644 --- a/mieru/apis/common/dns.go +++ b/mieru/apis/common/dns.go @@ -40,8 +40,17 @@ func (r NilDNSResolver) LookupIP(ctx context.Context, network, host string) ([]n var _ DNSResolver = NilDNSResolver{} +// ClientDNSConfig provides DNS configurations used by proxy client. +type ClientDNSConfig struct { + // If enabled, when creating a stream oriented network connection, + // Resolver is not used. The proxy server endpoint is passed to Dialer as is. + // + // You may enable this when Dialer has an internal mechanism to resolve DNS. + BypassDialerDNS bool +} + // ResolveTCPAddr returns a TCP address using the DNSResolver. -func ResolveTCPAddr(r DNSResolver, network, address string) (*net.TCPAddr, error) { +func ResolveTCPAddr(ctx context.Context, r DNSResolver, network, address string) (*net.TCPAddr, error) { dnsQueryNetwork := "ip" switch network { case "tcp": @@ -67,7 +76,7 @@ func ResolveTCPAddr(r DNSResolver, network, address string) (*net.TCPAddr, error return &net.TCPAddr{IP: ip, Port: port}, nil } - ips, err := r.LookupIP(context.Background(), dnsQueryNetwork, host) + ips, err := r.LookupIP(ctx, dnsQueryNetwork, host) if err != nil { return nil, err } @@ -79,7 +88,7 @@ func ResolveTCPAddr(r DNSResolver, network, address string) (*net.TCPAddr, error } // ResolveUDPAddr returns a UDP address using the DNSResolver. -func ResolveUDPAddr(r DNSResolver, network, address string) (*net.UDPAddr, error) { +func ResolveUDPAddr(ctx context.Context, r DNSResolver, network, address string) (*net.UDPAddr, error) { dnsQueryNetwork := "ip" switch network { case "udp": @@ -105,7 +114,7 @@ func ResolveUDPAddr(r DNSResolver, network, address string) (*net.UDPAddr, error return &net.UDPAddr{IP: ip, Port: port}, nil } - ips, err := r.LookupIP(context.Background(), dnsQueryNetwork, host) + ips, err := r.LookupIP(ctx, dnsQueryNetwork, host) if err != nil { return nil, err } diff --git a/mieru/apis/common/dns_test.go b/mieru/apis/common/dns_test.go index 9de4cc9d61..7e1d518c4e 100644 --- a/mieru/apis/common/dns_test.go +++ b/mieru/apis/common/dns_test.go @@ -16,6 +16,7 @@ package common import ( + "context" "net" "testing" ) @@ -72,7 +73,7 @@ func TestResolveTCPAddr(t *testing.T) { for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { - addr, err := ResolveTCPAddr(resolver, tc.network, tc.address) + addr, err := ResolveTCPAddr(context.Background(), resolver, tc.network, tc.address) if (err != nil) != tc.wantErr { t.Fatalf("ResolveTCPAddr() error = %v, wantErr %v", err, tc.wantErr) } @@ -140,7 +141,7 @@ func TestResolveUDPAddr(t *testing.T) { for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { - addr, err := ResolveUDPAddr(resolver, tc.network, tc.address) + addr, err := ResolveUDPAddr(context.Background(), resolver, tc.network, tc.address) if (err != nil) != tc.wantErr { t.Fatalf("ResolveUDPAddr() error = %v, wantErr %v", err, tc.wantErr) } diff --git a/mieru/build/package/mieru/amd64/debian/DEBIAN/control b/mieru/build/package/mieru/amd64/debian/DEBIAN/control index 0ea29592a4..8ef1584a72 100755 --- a/mieru/build/package/mieru/amd64/debian/DEBIAN/control +++ b/mieru/build/package/mieru/amd64/debian/DEBIAN/control @@ -1,5 +1,5 @@ Package: mieru -Version: 3.25.0 +Version: 3.26.0 Section: net Priority: optional Architecture: amd64 diff --git a/mieru/build/package/mieru/amd64/rpm/mieru.spec b/mieru/build/package/mieru/amd64/rpm/mieru.spec index 23dab2959e..01e1e33fab 100644 --- a/mieru/build/package/mieru/amd64/rpm/mieru.spec +++ b/mieru/build/package/mieru/amd64/rpm/mieru.spec @@ -1,5 +1,5 @@ Name: mieru -Version: 3.25.0 +Version: 3.26.0 Release: 1%{?dist} Summary: Mieru proxy client License: GPLv3+ diff --git a/mieru/build/package/mieru/arm64/debian/DEBIAN/control b/mieru/build/package/mieru/arm64/debian/DEBIAN/control index 4d43c97d06..0f883ba2c8 100755 --- a/mieru/build/package/mieru/arm64/debian/DEBIAN/control +++ b/mieru/build/package/mieru/arm64/debian/DEBIAN/control @@ -1,5 +1,5 @@ Package: mieru -Version: 3.25.0 +Version: 3.26.0 Section: net Priority: optional Architecture: arm64 diff --git a/mieru/build/package/mieru/arm64/rpm/mieru.spec b/mieru/build/package/mieru/arm64/rpm/mieru.spec index 23dab2959e..01e1e33fab 100644 --- a/mieru/build/package/mieru/arm64/rpm/mieru.spec +++ b/mieru/build/package/mieru/arm64/rpm/mieru.spec @@ -1,5 +1,5 @@ Name: mieru -Version: 3.25.0 +Version: 3.26.0 Release: 1%{?dist} Summary: Mieru proxy client License: GPLv3+ diff --git a/mieru/build/package/mita/amd64/debian/DEBIAN/control b/mieru/build/package/mita/amd64/debian/DEBIAN/control index e22cf0790f..392b2becc5 100755 --- a/mieru/build/package/mita/amd64/debian/DEBIAN/control +++ b/mieru/build/package/mita/amd64/debian/DEBIAN/control @@ -1,5 +1,5 @@ Package: mita -Version: 3.25.0 +Version: 3.26.0 Section: net Priority: optional Architecture: amd64 diff --git a/mieru/build/package/mita/amd64/rpm/mita.spec b/mieru/build/package/mita/amd64/rpm/mita.spec index 67397ab2b9..6d5aca3276 100644 --- a/mieru/build/package/mita/amd64/rpm/mita.spec +++ b/mieru/build/package/mita/amd64/rpm/mita.spec @@ -1,5 +1,5 @@ Name: mita -Version: 3.25.0 +Version: 3.26.0 Release: 1%{?dist} Summary: Mieru proxy server License: GPLv3+ diff --git a/mieru/build/package/mita/arm64/debian/DEBIAN/control b/mieru/build/package/mita/arm64/debian/DEBIAN/control index edc4b1226d..509380e098 100755 --- a/mieru/build/package/mita/arm64/debian/DEBIAN/control +++ b/mieru/build/package/mita/arm64/debian/DEBIAN/control @@ -1,5 +1,5 @@ Package: mita -Version: 3.25.0 +Version: 3.26.0 Section: net Priority: optional Architecture: arm64 diff --git a/mieru/build/package/mita/arm64/rpm/mita.spec b/mieru/build/package/mita/arm64/rpm/mita.spec index b22de6ca49..4ba4970003 100644 --- a/mieru/build/package/mita/arm64/rpm/mita.spec +++ b/mieru/build/package/mita/arm64/rpm/mita.spec @@ -1,5 +1,5 @@ Name: mita -Version: 3.25.0 +Version: 3.26.0 Release: 1%{?dist} Summary: Mieru proxy server License: GPLv3+ diff --git a/mieru/docs/server-install.md b/mieru/docs/server-install.md index 6b1563fe97..0a765b0e50 100644 --- a/mieru/docs/server-install.md +++ b/mieru/docs/server-install.md @@ -18,32 +18,32 @@ Or you can manually install and configure proxy server using the steps below. ```sh # Debian / Ubuntu - X86_64 -curl -LSO https://github.com/enfein/mieru/releases/download/v3.25.0/mita_3.25.0_amd64.deb +curl -LSO https://github.com/enfein/mieru/releases/download/v3.26.0/mita_3.26.0_amd64.deb # Debian / Ubuntu - ARM 64 -curl -LSO https://github.com/enfein/mieru/releases/download/v3.25.0/mita_3.25.0_arm64.deb +curl -LSO https://github.com/enfein/mieru/releases/download/v3.26.0/mita_3.26.0_arm64.deb # RedHat / CentOS / Rocky Linux - X86_64 -curl -LSO https://github.com/enfein/mieru/releases/download/v3.25.0/mita-3.25.0-1.x86_64.rpm +curl -LSO https://github.com/enfein/mieru/releases/download/v3.26.0/mita-3.26.0-1.x86_64.rpm # RedHat / CentOS / Rocky Linux - ARM 64 -curl -LSO https://github.com/enfein/mieru/releases/download/v3.25.0/mita-3.25.0-1.aarch64.rpm +curl -LSO https://github.com/enfein/mieru/releases/download/v3.26.0/mita-3.26.0-1.aarch64.rpm ``` ## Install mita package ```sh # Debian / Ubuntu - X86_64 -sudo dpkg -i mita_3.25.0_amd64.deb +sudo dpkg -i mita_3.26.0_amd64.deb # Debian / Ubuntu - ARM 64 -sudo dpkg -i mita_3.25.0_arm64.deb +sudo dpkg -i mita_3.26.0_arm64.deb # RedHat / CentOS / Rocky Linux - X86_64 -sudo rpm -Uvh --force mita-3.25.0-1.x86_64.rpm +sudo rpm -Uvh --force mita-3.26.0-1.x86_64.rpm # RedHat / CentOS / Rocky Linux - ARM 64 -sudo rpm -Uvh --force mita-3.25.0-1.aarch64.rpm +sudo rpm -Uvh --force mita-3.26.0-1.aarch64.rpm ``` Those instructions can also be used to upgrade the version of mita software package. diff --git a/mieru/docs/server-install.zh_CN.md b/mieru/docs/server-install.zh_CN.md index b861c34481..56692512fa 100644 --- a/mieru/docs/server-install.zh_CN.md +++ b/mieru/docs/server-install.zh_CN.md @@ -18,32 +18,32 @@ sudo python3 setup.py --lang=zh ```sh # Debian / Ubuntu - X86_64 -curl -LSO https://github.com/enfein/mieru/releases/download/v3.25.0/mita_3.25.0_amd64.deb +curl -LSO https://github.com/enfein/mieru/releases/download/v3.26.0/mita_3.26.0_amd64.deb # Debian / Ubuntu - ARM 64 -curl -LSO https://github.com/enfein/mieru/releases/download/v3.25.0/mita_3.25.0_arm64.deb +curl -LSO https://github.com/enfein/mieru/releases/download/v3.26.0/mita_3.26.0_arm64.deb # RedHat / CentOS / Rocky Linux - X86_64 -curl -LSO https://github.com/enfein/mieru/releases/download/v3.25.0/mita-3.25.0-1.x86_64.rpm +curl -LSO https://github.com/enfein/mieru/releases/download/v3.26.0/mita-3.26.0-1.x86_64.rpm # RedHat / CentOS / Rocky Linux - ARM 64 -curl -LSO https://github.com/enfein/mieru/releases/download/v3.25.0/mita-3.25.0-1.aarch64.rpm +curl -LSO https://github.com/enfein/mieru/releases/download/v3.26.0/mita-3.26.0-1.aarch64.rpm ``` ## 安装 mita 软件包 ```sh # Debian / Ubuntu - X86_64 -sudo dpkg -i mita_3.25.0_amd64.deb +sudo dpkg -i mita_3.26.0_amd64.deb # Debian / Ubuntu - ARM 64 -sudo dpkg -i mita_3.25.0_arm64.deb +sudo dpkg -i mita_3.26.0_arm64.deb # RedHat / CentOS / Rocky Linux - X86_64 -sudo rpm -Uvh --force mita-3.25.0-1.x86_64.rpm +sudo rpm -Uvh --force mita-3.26.0-1.x86_64.rpm # RedHat / CentOS / Rocky Linux - ARM 64 -sudo rpm -Uvh --force mita-3.25.0-1.aarch64.rpm +sudo rpm -Uvh --force mita-3.26.0-1.aarch64.rpm ``` 上述指令也可以用来升级 mita 软件包的版本。 diff --git a/mieru/pkg/appctl/appctlcommon/client.go b/mieru/pkg/appctl/appctlcommon/client.go index efabc691b3..b5c71603ab 100644 --- a/mieru/pkg/appctl/appctlcommon/client.go +++ b/mieru/pkg/appctl/appctlcommon/client.go @@ -82,9 +82,11 @@ func ValidateClientConfigSingleProfile(profile *pb.ClientProfile) error { return nil } -func NewClientMuxFromProfile(activeProfile *pb.ClientProfile, dialer apicommon.Dialer, packetDialer apicommon.PacketDialer, resolver apicommon.DNSResolver) (*protocol.Mux, error) { +func NewClientMuxFromProfile(activeProfile *pb.ClientProfile, dialer apicommon.Dialer, packetDialer apicommon.PacketDialer, resolver apicommon.DNSResolver, dnsConfig *apicommon.ClientDNSConfig) (*protocol.Mux, error) { var err error mux := protocol.NewMux(true) + + // Set dialer and packet dialer. if dialer != nil { mux.SetDialer(dialer) } @@ -100,6 +102,11 @@ func NewClientMuxFromProfile(activeProfile *pb.ClientProfile, dialer apicommon.D } mux.SetResolver(resolver) + // Set client DNS configuration. + if dnsConfig != nil { + mux.SetClientDNSConfig(dnsConfig) + } + // Set user name and password. user := activeProfile.GetUser() var hashedPassword []byte diff --git a/mieru/pkg/cli/client.go b/mieru/pkg/cli/client.go index ce5bdfa18c..51f07d9e9c 100644 --- a/mieru/pkg/cli/client.go +++ b/mieru/pkg/cli/client.go @@ -496,7 +496,7 @@ var clientRunFunc = func(s []string) error { wg.Add(1) go func() { rpcAddr := "localhost:" + strconv.Itoa(int(config.GetRpcPort())) - rpcTCPAddr, err := apicommon.ResolveTCPAddr(resolver, "tcp", rpcAddr) + rpcTCPAddr, err := apicommon.ResolveTCPAddr(context.Background(), resolver, "tcp", rpcAddr) if err != nil { log.Fatalf("Resolve RPC address %q failed: %v", rpcAddr, err) } @@ -527,7 +527,7 @@ var clientRunFunc = func(s []string) error { if err != nil { return fmt.Errorf(stderror.ClientGetActiveProfileFailedErr, err) } - mux, err := appctlcommon.NewClientMuxFromProfile(activeProfile, nil, nil, resolver) + mux, err := appctlcommon.NewClientMuxFromProfile(activeProfile, nil, nil, resolver, nil) if err != nil { return err } @@ -569,7 +569,7 @@ var clientRunFunc = func(s []string) error { } wg.Add(1) go func(socks5Addr string) { - socks5TCPAddr, err := apicommon.ResolveTCPAddr(resolver, "tcp", socks5Addr) + socks5TCPAddr, err := apicommon.ResolveTCPAddr(context.Background(), resolver, "tcp", socks5Addr) if err != nil { log.Fatalf("Resolve socks5 address %q failed: %v", socks5Addr, err) } diff --git a/mieru/pkg/protocol/mux.go b/mieru/pkg/protocol/mux.go index 9286322f73..d6a5a37eec 100644 --- a/mieru/pkg/protocol/mux.go +++ b/mieru/pkg/protocol/mux.go @@ -50,6 +50,7 @@ type Mux struct { dialer apicommon.Dialer packetDialer apicommon.PacketDialer resolver apicommon.DNSResolver + clientDNSConfig *apicommon.ClientDNSConfig streamListenerFactory apicommon.StreamListenerFactory packetListenerFactory apicommon.PacketListenerFactory chAccept chan net.Conn @@ -86,6 +87,7 @@ func NewMux(isClinet bool) *Mux { dialer: &net.Dialer{Timeout: 10 * time.Second, Control: sockopts.DefaultDialerControl()}, packetDialer: common.UDPDialer{Control: sockopts.DefaultDialerControl()}, resolver: &net.Resolver{}, + clientDNSConfig: &apicommon.ClientDNSConfig{}, streamListenerFactory: &net.ListenConfig{Control: sockopts.DefaultListenerControl()}, packetListenerFactory: &net.ListenConfig{Control: sockopts.DefaultListenerControl()}, chAccept: make(chan net.Conn, sessionChanCapacity), @@ -173,6 +175,15 @@ func (m *Mux) SetResolver(resolver apicommon.DNSResolver) *Mux { return m } +// SetClientDNSConfig updates the client DNS configuration used by the mux. +func (m *Mux) SetClientDNSConfig(clientDNSConfig *apicommon.ClientDNSConfig) *Mux { + m.mu.Lock() + defer m.mu.Unlock() + m.clientDNSConfig = clientDNSConfig + log.Infof("Mux client DNS configuration has been updated") + return m +} + // SetStreamListenerFactory updates the stream oriented network listener factory used by the mux. func (m *Mux) SetStreamListenerFactory(listenerFactory apicommon.StreamListenerFactory) *Mux { m.mu.Lock() @@ -410,7 +421,7 @@ func (m *Mux) acceptUnderlayLoop(ctx context.Context, properties UnderlayPropert network := properties.LocalAddr().Network() switch network { case "tcp", "tcp4", "tcp6": - tcpAddr, err := apicommon.ResolveTCPAddr(m.resolver, "tcp", laddr) + tcpAddr, err := apicommon.ResolveTCPAddr(ctx, m.resolver, "tcp", laddr) if err != nil { log.Errorf("ResolveTCPAddr() failed: %v", err) if m.acceptHasErr.CompareAndSwap(false, true) { @@ -486,7 +497,7 @@ func (m *Mux) acceptUnderlayLoop(ctx context.Context, properties UnderlayPropert }(ctx, underlay) } case "udp", "udp4", "udp6": - udpAddr, err := apicommon.ResolveUDPAddr(m.resolver, "udp", laddr) + udpAddr, err := apicommon.ResolveUDPAddr(ctx, m.resolver, "udp", laddr) if err != nil { log.Errorf("ResolveUDPAddr() failed: %v", err) if m.acceptHasErr.CompareAndSwap(false, true) { @@ -617,7 +628,7 @@ func (m *Mux) newUnderlay(ctx context.Context) (Underlay, error) { block.SetBlockContext(cipher.BlockContext{ UserName: m.username, }) - underlay, err = NewStreamUnderlay(ctx, m.dialer, m.resolver, p.RemoteAddr().Network(), p.RemoteAddr().String(), p.MTU(), block) + underlay, err = NewStreamUnderlay(ctx, m.dialer, m.resolver, m.clientDNSConfig, p.RemoteAddr().Network(), p.RemoteAddr().String(), p.MTU(), block) if err != nil { return nil, fmt.Errorf("NewTCPUnderlay() failed: %v", err) } diff --git a/mieru/pkg/protocol/underlay_packet.go b/mieru/pkg/protocol/underlay_packet.go index 0cf5fe043a..4bcad7cf04 100644 --- a/mieru/pkg/protocol/underlay_packet.go +++ b/mieru/pkg/protocol/underlay_packet.go @@ -75,12 +75,12 @@ func NewPacketUnderlay(ctx context.Context, packetDialer apicommon.PacketDialer, if !block.IsStateless() { return nil, fmt.Errorf("packet underlay block cipher must be stateless") } - remoteAddr, err := apicommon.ResolveUDPAddr(resolver, network, addr) + remoteUDPAddr, err := apicommon.ResolveUDPAddr(ctx, resolver, network, addr) if err != nil { return nil, fmt.Errorf("ResolveUDPAddr() failed: %w", err) } - conn, err := packetDialer.ListenPacket(ctx, network, "", remoteAddr.String()) + conn, err := packetDialer.ListenPacket(ctx, network, "", remoteUDPAddr.String()) if err != nil { return nil, fmt.Errorf("ListenPacket() failed: %w", err) } @@ -88,9 +88,10 @@ func NewPacketUnderlay(ctx context.Context, packetDialer apicommon.PacketDialer, baseUnderlay: *newBaseUnderlay(true, mtu), conn: conn, sessionCleanTicker: time.NewTicker(sessionCleanInterval), - serverAddr: remoteAddr, + serverAddr: remoteUDPAddr, block: block, } + // The block cipher expires after this time. u.scheduler.SetRemainingTime(cipher.KeyRefreshInterval / 2) return u, nil diff --git a/mieru/pkg/protocol/underlay_stream.go b/mieru/pkg/protocol/underlay_stream.go index 3942fe1a8a..886e30606c 100644 --- a/mieru/pkg/protocol/underlay_stream.go +++ b/mieru/pkg/protocol/underlay_stream.go @@ -63,7 +63,7 @@ var _ Underlay = &StreamUnderlay{} // "block" is the block encryption algorithm to encrypt packets. // // This function is only used by proxy client. -func NewStreamUnderlay(ctx context.Context, dialer apicommon.Dialer, resolver apicommon.DNSResolver, network, addr string, mtu int, block cipher.BlockCipher) (*StreamUnderlay, error) { +func NewStreamUnderlay(ctx context.Context, dialer apicommon.Dialer, resolver apicommon.DNSResolver, dnsConfig *apicommon.ClientDNSConfig, network, addr string, mtu int, block cipher.BlockCipher) (*StreamUnderlay, error) { switch network { case "tcp", "tcp4", "tcp6": default: @@ -72,12 +72,16 @@ func NewStreamUnderlay(ctx context.Context, dialer apicommon.Dialer, resolver ap if block.IsStateless() { return nil, fmt.Errorf("stream underlay block cipher must be stateful") } - remoteAddr, err := apicommon.ResolveTCPAddr(resolver, network, addr) - if err != nil { - return nil, fmt.Errorf("ResolveTCPAddr() failed: %w", err) + remote := addr + if dnsConfig == nil || (!dnsConfig.BypassDialerDNS) { + remoteTCPAddr, err := apicommon.ResolveTCPAddr(ctx, resolver, network, addr) + if err != nil { + return nil, fmt.Errorf("ResolveTCPAddr() failed: %w", err) + } + remote = remoteTCPAddr.String() } - conn, err := dialer.DialContext(ctx, network, remoteAddr.String()) + conn, err := dialer.DialContext(ctx, network, remote) if err != nil { return nil, fmt.Errorf("DialContext() failed: %w", err) } diff --git a/mieru/pkg/socks5/request.go b/mieru/pkg/socks5/request.go index d777ecea35..552eac09a7 100644 --- a/mieru/pkg/socks5/request.go +++ b/mieru/pkg/socks5/request.go @@ -134,10 +134,10 @@ func (s *Server) handleBind(_ context.Context, _ *model.Request, conn net.Conn) } // handleAssociate is used to handle a associate command. -func (s *Server) handleAssociate(_ context.Context, _ *model.Request, conn net.Conn) error { +func (s *Server) handleAssociate(ctx context.Context, _ *model.Request, conn net.Conn) error { // Create a UDP listener on a random port. // All the requests associated to this connection will go through this port. - udpListenerAddr, err := apicommon.ResolveUDPAddr(s.config.Resolver, "udp", common.MaybeDecorateIPv6(common.AllIPAddr())+":0") + udpListenerAddr, err := apicommon.ResolveUDPAddr(ctx, s.config.Resolver, "udp", common.MaybeDecorateIPv6(common.AllIPAddr())+":0") if err != nil { UDPAssociateErrors.Add(1) return fmt.Errorf("failed to resolve UDP address: %w", err) diff --git a/mieru/pkg/socks5/udp.go b/mieru/pkg/socks5/udp.go index 913cc3ed7e..aec20cd484 100644 --- a/mieru/pkg/socks5/udp.go +++ b/mieru/pkg/socks5/udp.go @@ -16,6 +16,7 @@ package socks5 import ( + "context" "encoding/binary" "net" "strconv" @@ -101,7 +102,7 @@ func RunUDPAssociateLoop(udpConn *net.UDPConn, conn *apicommon.PacketOverStreamT case constant.Socks5FQDNAddress: fqdnLen := buf[4] fqdn := string(buf[5 : 5+fqdnLen]) - dstAddr, err := apicommon.ResolveUDPAddr(resolver, "udp", fqdn+":"+strconv.Itoa(int(buf[5+fqdnLen])<<8+int(buf[6+fqdnLen]))) + dstAddr, err := apicommon.ResolveUDPAddr(context.Background(), resolver, "udp", fqdn+":"+strconv.Itoa(int(buf[5+fqdnLen])<<8+int(buf[6+fqdnLen]))) if err != nil { log.Debugf("UDP associate %v ResolveUDPAddr() failed: %v", udpConn.LocalAddr(), err) UDPAssociateErrors.Add(1) diff --git a/mieru/pkg/version/current.go b/mieru/pkg/version/current.go index 9020eb1c79..9904a69ad9 100644 --- a/mieru/pkg/version/current.go +++ b/mieru/pkg/version/current.go @@ -16,5 +16,5 @@ package version const ( - AppVersion = "3.25.0" + AppVersion = "3.26.0" ) diff --git a/mieru/test/cmd/exampleapiclient/exampleapiclient.go b/mieru/test/cmd/exampleapiclient/exampleapiclient.go index 74c4620dfa..54800066ca 100644 --- a/mieru/test/cmd/exampleapiclient/exampleapiclient.go +++ b/mieru/test/cmd/exampleapiclient/exampleapiclient.go @@ -42,6 +42,7 @@ var ( serverPort = flag.Int("server_port", 0, "Port number of mieru proxy server") serverProtocol = flag.String("server_protocol", "TCP", "Transport protocol: TCP or UDP") handshakeMode = flag.String("handshake_mode", "HANDSHAKE_STANDARD", "Handshake mode: HANDSHAKE_STANDARD or HANDSHAKE_NO_WAIT") + bypassDNS = flag.Bool("bypass_dns", false, "Bypass proxy server DNS resolution") debug = flag.Bool("debug", false, "Display debug messages") ) @@ -83,6 +84,12 @@ func main() { default: panic(fmt.Sprintf("Handshake mode %q is invalid", *handshakeMode)) } + var dnsConfig *apicommon.ClientDNSConfig + if *bypassDNS { + dnsConfig = &apicommon.ClientDNSConfig{ + BypassDialerDNS: true, + } + } c := client.NewClient() if err := c.Store(&client.ClientConfig{ @@ -106,6 +113,7 @@ func main() { Mtu: proto.Int32(1400), HandshakeMode: &handshakeModeConfig, }, + DNSConfig: dnsConfig, }); err != nil { panic(fmt.Sprintf("Store() failed: %v", err)) } diff --git a/mieru/test/deploy/apiclient/test.sh b/mieru/test/deploy/apiclient/test.sh index 89f6b3ed6d..bf5c1310fd 100755 --- a/mieru/test/deploy/apiclient/test.sh +++ b/mieru/test/deploy/apiclient/test.sh @@ -58,10 +58,10 @@ sleep 1 -server_ip=127.0.0.1 -server_port=6489 -server_protocol=UDP & sleep 1 ./exampleapiclient -port=1083 -username=baozi -password=manlianpenfen \ - -server_ip=127.0.0.1 -server_port=8964 -server_protocol=TCP -handshake_mode=HANDSHAKE_NO_WAIT & + -server_ip=127.0.0.1 -server_port=8964 -server_protocol=TCP -handshake_mode=HANDSHAKE_NO_WAIT -bypass_dns & sleep 1 ./exampleapiclient -port=1084 -username=baozi -password=manlianpenfen \ - -server_ip=127.0.0.1 -server_port=6489 -server_protocol=UDP -handshake_mode=HANDSHAKE_NO_WAIT & + -server_ip=127.0.0.1 -server_port=6489 -server_protocol=UDP -handshake_mode=HANDSHAKE_NO_WAIT -bypass_dns & sleep 1 echo "========== BEGIN OF TCP TEST ==========" diff --git a/mihomo/adapter/outbound/mieru.go b/mihomo/adapter/outbound/mieru.go index d9feeba08f..e09b1898f2 100644 --- a/mihomo/adapter/outbound/mieru.go +++ b/mihomo/adapter/outbound/mieru.go @@ -11,6 +11,7 @@ import ( CN "github.com/metacubex/mihomo/common/net" "github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/proxydialer" + "github.com/metacubex/mihomo/component/resolver" C "github.com/metacubex/mihomo/constant" mieruclient "github.com/enfein/mieru/v3/apis/client" @@ -55,6 +56,31 @@ func (pd mieruPacketDialer) ListenPacket(ctx context.Context, network, laddr, ra return pd.Dialer.ListenPacket(ctx, network, laddr, rAddrPort) } +type mieruDNSResolver struct { + prefer C.DNSPrefer +} + +var _ mierucommon.DNSResolver = (*mieruDNSResolver)(nil) + +func (dr mieruDNSResolver) LookupIP(ctx context.Context, network, host string) (_ []net.IP, err error) { + var ip netip.Addr + switch dr.prefer { + case C.IPv4Only: + ip, err = resolver.ResolveIPv4WithResolver(ctx, host, resolver.ProxyServerHostResolver) + case C.IPv6Only: + ip, err = resolver.ResolveIPv6WithResolver(ctx, host, resolver.ProxyServerHostResolver) + case C.IPv6Prefer: + ip, err = resolver.ResolveIPPrefer6WithResolver(ctx, host, resolver.ProxyServerHostResolver) + default: + ip, err = resolver.ResolveIPWithResolver(ctx, host, resolver.ProxyServerHostResolver) + } + if err != nil { + return nil, fmt.Errorf("can't resolve ip: %w", err) + } + // TODO: handle IP4P (due to interface limitations, it's currently impossible to modify the port here) + return []net.IP{ip.AsSlice()}, nil +} + // DialContext implements C.ProxyAdapter func (m *Mieru) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { if err := m.ensureClientIsRunning(); err != nil { @@ -118,6 +144,7 @@ func (m *Mieru) ensureClientIsRunning() error { } config.Dialer = dialer config.PacketDialer = mieruPacketDialer{Dialer: dialer} + config.Resolver = mieruDNSResolver{prefer: m.prefer} if err := m.client.Store(config); err != nil { return err } @@ -260,6 +287,9 @@ func buildMieruClientConfig(option MieruOption) (*mieruclient.ClientConfig, erro }, Servers: []*mierupb.ServerEndpoint{server}, }, + DNSConfig: &mierucommon.ClientDNSConfig{ + BypassDialerDNS: true, + }, } if multiplexing, ok := mierupb.MultiplexingLevel_value[option.Multiplexing]; ok { config.Profile.Multiplexing = &mierupb.MultiplexingConfig{ diff --git a/mihomo/component/generator/cmd.go b/mihomo/component/generator/cmd.go index 320bb11d36..39064c7168 100644 --- a/mihomo/component/generator/cmd.go +++ b/mihomo/component/generator/cmd.go @@ -6,13 +6,14 @@ import ( "github.com/metacubex/mihomo/component/ech" "github.com/metacubex/mihomo/transport/vless/encryption" + "github.com/saba-futai/sudoku/pkg/crypto" "github.com/gofrs/uuid/v5" ) func Main(args []string) { if len(args) < 1 { - panic("Using: generate uuid/reality-keypair/wg-keypair/ech-keypair/vless-mlkem768/vless-x25519") + panic("Using: generate uuid/reality-keypair/wg-keypair/ech-keypair/vless-mlkem768/vless-x25519/sudoku-keypair") } switch args[0] { case "uuid": @@ -69,5 +70,19 @@ func Main(args []string) { fmt.Println("PrivateKey: " + privateKeyBase64) fmt.Println("Password: " + passwordBase64) fmt.Println("Hash32: " + hash32Base64) + case "sudoku-keypair": + // Generate Master Key + pair, err := crypto.GenerateMasterKey() + if err != nil { + panic(err) + } + // Split the master private key to get Available Private Key + availablePrivateKey, err := crypto.SplitPrivateKey(pair.Private) + if err != nil { + panic(err) + } + // Output: Available Private Key for client, Master Public Key for server + fmt.Println("PrivateKey: " + availablePrivateKey) + fmt.Println("PublicKey: " + crypto.EncodePoint(pair.Public)) } } diff --git a/mihomo/docs/config.yaml b/mihomo/docs/config.yaml index bef2c65914..2f959f33c3 100644 --- a/mihomo/docs/config.yaml +++ b/mihomo/docs/config.yaml @@ -1587,7 +1587,6 @@ listeners: aead-method: chacha20-poly1305 # 支持chacha20-poly1305或者aes-128-gcm以及none,sudoku的混淆层可以确保none情况下数据安全 padding-min: 1 # 填充最小长度 padding-max: 15 # 填充最大长度,均不建议过大 - seed: "" # 如果你不使用ED25519密钥对,就请填入uuid,否则仍然是公钥 table-type: prefer_ascii # 可选值:prefer_ascii、prefer_entropy 前者全ascii映射,后者保证熵值(汉明1)低于3 handshake-timeout: 5 # optional diff --git a/mihomo/go.mod b/mihomo/go.mod index 3ae50c987c..35dfcbe2b1 100644 --- a/mihomo/go.mod +++ b/mihomo/go.mod @@ -6,7 +6,7 @@ require ( github.com/bahlo/generic-list-go v0.2.0 github.com/coreos/go-iptables v0.8.0 github.com/dlclark/regexp2 v1.11.5 - github.com/enfein/mieru/v3 v3.24.1 + github.com/enfein/mieru/v3 v3.26.0 github.com/go-chi/chi/v5 v5.2.3 github.com/go-chi/render v1.0.3 github.com/gobwas/ws v1.4.0 diff --git a/mihomo/go.sum b/mihomo/go.sum index 7d24c9c06b..45210afb98 100644 --- a/mihomo/go.sum +++ b/mihomo/go.sum @@ -25,8 +25,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/enfein/mieru/v3 v3.24.1 h1:iX9py3+GExxJxLaxjHAEmQmoE1r0y2hDIsliija+jTI= -github.com/enfein/mieru/v3 v3.24.1/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM= +github.com/enfein/mieru/v3 v3.26.0 h1:ZsxCFkh3UfGSu9LL6EQ9+b97uxTJ7/AnJmLMyrbjSDI= +github.com/enfein/mieru/v3 v3.26.0/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM= github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358 h1:kXYqH/sL8dS/FdoFjr12ePjnLPorPo2FsnrHNuXSDyo= github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358/go.mod h1:hkIFzoiIPZYxdFOOLyDho59b7SrDfo+w3h+yWdlg45I= github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 h1:8j2RH289RJplhA6WfdaPqzg1MjH2K8wX5e0uhAxrw2g= diff --git a/mihomo/listener/config/sudoku.go b/mihomo/listener/config/sudoku.go index 855795f1c3..79aadf5db7 100644 --- a/mihomo/listener/config/sudoku.go +++ b/mihomo/listener/config/sudoku.go @@ -11,7 +11,6 @@ type SudokuServer struct { AEADMethod string `json:"aead-method,omitempty"` PaddingMin *int `json:"padding-min,omitempty"` PaddingMax *int `json:"padding-max,omitempty"` - Seed string `json:"seed,omitempty"` TableType string `json:"table-type,omitempty"` HandshakeTimeoutSecond *int `json:"handshake-timeout,omitempty"` } diff --git a/mihomo/listener/inbound/sudoku.go b/mihomo/listener/inbound/sudoku.go index d6e84af322..bc08772a61 100644 --- a/mihomo/listener/inbound/sudoku.go +++ b/mihomo/listener/inbound/sudoku.go @@ -19,7 +19,6 @@ type SudokuOption struct { AEADMethod string `inbound:"aead-method,omitempty"` PaddingMin *int `inbound:"padding-min,omitempty"` PaddingMax *int `inbound:"padding-max,omitempty"` - Seed string `inbound:"seed,omitempty"` TableType string `inbound:"table-type,omitempty"` // "prefer_ascii" or "prefer_entropy" HandshakeTimeoutSecond *int `inbound:"handshake-timeout,omitempty"` } @@ -53,7 +52,6 @@ func NewSudoku(options *SudokuOption) (*Sudoku, error) { AEADMethod: options.AEADMethod, PaddingMin: options.PaddingMin, PaddingMax: options.PaddingMax, - Seed: options.Seed, TableType: options.TableType, } if options.HandshakeTimeoutSecond != nil { diff --git a/mihomo/listener/mieru/server.go b/mihomo/listener/mieru/server.go index 3428c40a3c..ab80106944 100644 --- a/mihomo/listener/mieru/server.go +++ b/mihomo/listener/mieru/server.go @@ -44,7 +44,12 @@ func Handle(conn net.Conn, tunnel C.Tunnel, request *mierumodel.Request, additio metadata.DstIP, _ = netip.AddrFromSlice(request.DstAddr.IP) metadata.DstIP = metadata.DstIP.Unmap() } - inbound.ApplyAdditions(metadata, inbound.WithSrcAddr(conn.RemoteAddr()), inbound.WithInAddr(conn.LocalAddr())) + inbound.ApplyAdditions( + metadata, + inbound.WithInName(conn.(mierucommon.UserContext).UserName()), + inbound.WithSrcAddr(conn.RemoteAddr()), + inbound.WithInAddr(conn.LocalAddr()), + ) inbound.ApplyAdditions(metadata, additions...) tunnel.HandleTCPConn(conn, metadata) case mieruconstant.Socks5UDPAssociateCmd: // UDP diff --git a/mihomo/listener/sudoku/server.go b/mihomo/listener/sudoku/server.go index 87b2abd9d3..ce2b09afab 100644 --- a/mihomo/listener/sudoku/server.go +++ b/mihomo/listener/sudoku/server.go @@ -71,17 +71,12 @@ func New(config LC.SudokuServer, tunnel C.Tunnel, additions ...inbound.Addition) return nil, err } - seed := config.Seed - if seed == "" { - seed = config.Key - } - tableType := strings.ToLower(config.TableType) if tableType == "" { tableType = "prefer_ascii" } - table := sudokuobfs.NewTable(seed, tableType) + table := sudokuobfs.NewTable(config.Key, tableType) defaultConf := apis.DefaultConfig() paddingMin := defaultConf.PaddingMin diff --git a/openwrt-passwall/luci-app-passwall/luasrc/passwall/api.lua b/openwrt-passwall/luci-app-passwall/luasrc/passwall/api.lua index 24641bc1b8..802572ac0f 100644 --- a/openwrt-passwall/luci-app-passwall/luasrc/passwall/api.lua +++ b/openwrt-passwall/luci-app-passwall/luasrc/passwall/api.lua @@ -506,6 +506,8 @@ end function get_valid_nodes() local show_node_info = uci_get_type("global_other", "show_node_info", "0") local nodes = {} + local default_nodes = {} + local other_nodes = {} uci:foreach(appname, "nodes", function(e) e.id = e[".name"] if e.type and e.remarks then @@ -515,7 +517,11 @@ function get_valid_nodes() if type == "sing-box" then type = "Sing-Box" end e["remark"] = "%s:[%s] " % {type .. " " .. i18n.translatef(e.protocol), e.remarks} e["node_type"] = "special" - nodes[#nodes + 1] = e + if not e.group or e.group == "" then + default_nodes[#default_nodes + 1] = e + else + other_nodes[#other_nodes + 1] = e + end end local port = e.port or e.hysteria_hop or e.hysteria2_hop if port and e.address then @@ -555,11 +561,17 @@ function get_valid_nodes() e["remark"] = "%s:[%s] %s:%s" % {type, e.remarks, address, port} end e.node_type = "normal" - nodes[#nodes + 1] = e + if not e.group or e.group == "" then + default_nodes[#default_nodes + 1] = e + else + other_nodes[#other_nodes + 1] = e + end end end end end) + for i = 1, #default_nodes do nodes[#nodes + 1] = default_nodes[i] end + for i = 1, #other_nodes do nodes[#nodes + 1] = other_nodes[i] end return nodes end diff --git a/openwrt-passwall/luci-app-passwall/luasrc/view/passwall/node_list/node_list.htm b/openwrt-passwall/luci-app-passwall/luasrc/view/passwall/node_list/node_list.htm index b0787f8725..7e6c3c3992 100644 --- a/openwrt-passwall/luci-app-passwall/luasrc/view/passwall/node_list/node_list.htm +++ b/openwrt-passwall/luci-app-passwall/luasrc/view/passwall/node_list/node_list.htm @@ -929,15 +929,6 @@ table td, .table .td { pingAllNodes(); } ); - - - - window.onload = function () { - setTimeout(function () { - pingAllNodes(); - }, 1000); - }; - //]]> diff --git a/openwrt-passwall2/luci-app-passwall2/Makefile b/openwrt-passwall2/luci-app-passwall2/Makefile index 76bbb22708..76dd6f82a0 100644 --- a/openwrt-passwall2/luci-app-passwall2/Makefile +++ b/openwrt-passwall2/luci-app-passwall2/Makefile @@ -5,7 +5,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-passwall2 -PKG_VERSION:=25.11.18 +PKG_VERSION:=25.12.2 PKG_RELEASE:=1 PKG_PO_VERSION:=$(PKG_VERSION) diff --git a/openwrt-passwall2/luci-app-passwall2/htdocs/luci-static/resources/view/passwall2/Sortable.min.js b/openwrt-passwall2/luci-app-passwall2/htdocs/luci-static/resources/view/passwall2/Sortable.min.js new file mode 100644 index 0000000000..95423a6491 --- /dev/null +++ b/openwrt-passwall2/luci-app-passwall2/htdocs/luci-static/resources/view/passwall2/Sortable.min.js @@ -0,0 +1,2 @@ +/*! Sortable 1.15.6 - MIT | git://github.com/SortableJS/Sortable.git */ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t=t||self).Sortable=e()}(this,function(){"use strict";function e(e,t){var n,o=Object.keys(e);return Object.getOwnPropertySymbols&&(n=Object.getOwnPropertySymbols(e),t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),o.push.apply(o,n)),o}function I(o){for(var t=1;tt.length)&&(e=t.length);for(var n=0,o=new Array(e);n"===e[0]&&(e=e.substring(1)),t))try{if(t.matches)return t.matches(e);if(t.msMatchesSelector)return t.msMatchesSelector(e);if(t.webkitMatchesSelector)return t.webkitMatchesSelector(e)}catch(t){return}}function g(t){return t.host&&t!==document&&t.host.nodeType?t.host:t.parentNode}function P(t,e,n,o){if(t){n=n||document;do{if(null!=e&&(">"!==e[0]||t.parentNode===n)&&f(t,e)||o&&t===n)return t}while(t!==n&&(t=g(t)))}return null}var m,v=/\s+/g;function k(t,e,n){var o;t&&e&&(t.classList?t.classList[n?"add":"remove"](e):(o=(" "+t.className+" ").replace(v," ").replace(" "+e+" "," "),t.className=(o+(n?" "+e:"")).replace(v," ")))}function R(t,e,n){var o=t&&t.style;if(o){if(void 0===n)return document.defaultView&&document.defaultView.getComputedStyle?n=document.defaultView.getComputedStyle(t,""):t.currentStyle&&(n=t.currentStyle),void 0===e?n:n[e];o[e=!(e in o||-1!==e.indexOf("webkit"))?"-webkit-"+e:e]=n+("string"==typeof n?"":"px")}}function b(t,e){var n="";if("string"==typeof t)n=t;else do{var o=R(t,"transform")}while(o&&"none"!==o&&(n=o+" "+n),!e&&(t=t.parentNode));var i=window.DOMMatrix||window.WebKitCSSMatrix||window.CSSMatrix||window.MSCSSMatrix;return i&&new i(n)}function D(t,e,n){if(t){var o=t.getElementsByTagName(e),i=0,r=o.length;if(n)for(;i=n.left-e&&i<=n.right+e,e=r>=n.top-e&&r<=n.bottom+e;return o&&e?a=t:void 0}}),a);if(e){var n,o={};for(n in t)t.hasOwnProperty(n)&&(o[n]=t[n]);o.target=o.rootEl=e,o.preventDefault=void 0,o.stopPropagation=void 0,e[K]._onDragOver(o)}}var i,r,a}function Ft(t){Z&&Z.parentNode[K]._isOutsideThisEl(t.target)}function jt(t,e){if(!t||!t.nodeType||1!==t.nodeType)throw"Sortable: `el` must be an HTMLElement, not ".concat({}.toString.call(t));this.el=t,this.options=e=a({},e),t[K]=this;var n,o,i={group:null,sort:!0,disabled:!1,store:null,handle:null,draggable:/^[uo]l$/i.test(t.nodeName)?">li":">*",swapThreshold:1,invertSwap:!1,invertedSwapThreshold:null,removeCloneOnHide:!0,direction:function(){return kt(t,this.options)},ghostClass:"sortable-ghost",chosenClass:"sortable-chosen",dragClass:"sortable-drag",ignore:"a, img",filter:null,preventOnFilter:!0,animation:0,easing:null,setData:function(t,e){t.setData("Text",e.textContent)},dropBubble:!1,dragoverBubble:!1,dataIdAttr:"data-id",delay:0,delayOnTouchOnly:!1,touchStartThreshold:(Number.parseInt?Number:window).parseInt(window.devicePixelRatio,10)||1,forceFallback:!1,fallbackClass:"sortable-fallback",fallbackOnBody:!1,fallbackTolerance:0,fallbackOffset:{x:0,y:0},supportPointer:!1!==jt.supportPointer&&"PointerEvent"in window&&(!u||c),emptyInsertThreshold:5};for(n in z.initializePlugins(this,t,i),i)n in e||(e[n]=i[n]);for(o in Rt(e),this)"_"===o.charAt(0)&&"function"==typeof this[o]&&(this[o]=this[o].bind(this));this.nativeDraggable=!e.forceFallback&&It,this.nativeDraggable&&(this.options.touchStartThreshold=1),e.supportPointer?h(t,"pointerdown",this._onTapStart):(h(t,"mousedown",this._onTapStart),h(t,"touchstart",this._onTapStart)),this.nativeDraggable&&(h(t,"dragover",this),h(t,"dragenter",this)),St.push(this.el),e.store&&e.store.get&&this.sort(e.store.get(this)||[]),a(this,A())}function Ht(t,e,n,o,i,r,a,l){var s,c,u=t[K],d=u.options.onMove;return!window.CustomEvent||y||w?(s=document.createEvent("Event")).initEvent("move",!0,!0):s=new CustomEvent("move",{bubbles:!0,cancelable:!0}),s.to=e,s.from=t,s.dragged=n,s.draggedRect=o,s.related=i||e,s.relatedRect=r||X(e),s.willInsertAfter=l,s.originalEvent=a,t.dispatchEvent(s),c=d?d.call(u,s,a):c}function Lt(t){t.draggable=!1}function Kt(){xt=!1}function Wt(t){return setTimeout(t,0)}function zt(t){return clearTimeout(t)}jt.prototype={constructor:jt,_isOutsideThisEl:function(t){this.el.contains(t)||t===this.el||(vt=null)},_getDirection:function(t,e){return"function"==typeof this.options.direction?this.options.direction.call(this,t,e,Z):this.options.direction},_onTapStart:function(e){if(e.cancelable){var n=this,o=this.el,t=this.options,i=t.preventOnFilter,r=e.type,a=e.touches&&e.touches[0]||e.pointerType&&"touch"===e.pointerType&&e,l=(a||e).target,s=e.target.shadowRoot&&(e.path&&e.path[0]||e.composedPath&&e.composedPath()[0])||l,c=t.filter;if(!function(t){Ot.length=0;var e=t.getElementsByTagName("input"),n=e.length;for(;n--;){var o=e[n];o.checked&&Ot.push(o)}}(o),!Z&&!(/mousedown|pointerdown/.test(r)&&0!==e.button||t.disabled)&&!s.isContentEditable&&(this.nativeDraggable||!u||!l||"SELECT"!==l.tagName.toUpperCase())&&!((l=P(l,t.draggable,o,!1))&&l.animated||et===l)){if(it=j(l),at=j(l,t.draggable),"function"==typeof c){if(c.call(this,e,l,this))return V({sortable:n,rootEl:s,name:"filter",targetEl:l,toEl:o,fromEl:o}),U("filter",n,{evt:e}),void(i&&e.preventDefault())}else if(c=c&&c.split(",").some(function(t){if(t=P(s,t.trim(),o,!1))return V({sortable:n,rootEl:t,name:"filter",targetEl:l,fromEl:o,toEl:o}),U("filter",n,{evt:e}),!0}))return void(i&&e.preventDefault());t.handle&&!P(s,t.handle,o,!1)||this._prepareDragStart(e,a,l)}}},_prepareDragStart:function(t,e,n){var o,i=this,r=i.el,a=i.options,l=r.ownerDocument;n&&!Z&&n.parentNode===r&&(o=X(n),J=r,$=(Z=n).parentNode,tt=Z.nextSibling,et=n,st=a.group,ut={target:jt.dragged=Z,clientX:(e||t).clientX,clientY:(e||t).clientY},ft=ut.clientX-o.left,gt=ut.clientY-o.top,this._lastX=(e||t).clientX,this._lastY=(e||t).clientY,Z.style["will-change"]="all",o=function(){U("delayEnded",i,{evt:t}),jt.eventCanceled?i._onDrop():(i._disableDelayedDragEvents(),!s&&i.nativeDraggable&&(Z.draggable=!0),i._triggerDragStart(t,e),V({sortable:i,name:"choose",originalEvent:t}),k(Z,a.chosenClass,!0))},a.ignore.split(",").forEach(function(t){D(Z,t.trim(),Lt)}),h(l,"dragover",Bt),h(l,"mousemove",Bt),h(l,"touchmove",Bt),a.supportPointer?(h(l,"pointerup",i._onDrop),this.nativeDraggable||h(l,"pointercancel",i._onDrop)):(h(l,"mouseup",i._onDrop),h(l,"touchend",i._onDrop),h(l,"touchcancel",i._onDrop)),s&&this.nativeDraggable&&(this.options.touchStartThreshold=4,Z.draggable=!0),U("delayStart",this,{evt:t}),!a.delay||a.delayOnTouchOnly&&!e||this.nativeDraggable&&(w||y)?o():jt.eventCanceled?this._onDrop():(a.supportPointer?(h(l,"pointerup",i._disableDelayedDrag),h(l,"pointercancel",i._disableDelayedDrag)):(h(l,"mouseup",i._disableDelayedDrag),h(l,"touchend",i._disableDelayedDrag),h(l,"touchcancel",i._disableDelayedDrag)),h(l,"mousemove",i._delayedDragTouchMoveHandler),h(l,"touchmove",i._delayedDragTouchMoveHandler),a.supportPointer&&h(l,"pointermove",i._delayedDragTouchMoveHandler),i._dragStartTimer=setTimeout(o,a.delay)))},_delayedDragTouchMoveHandler:function(t){t=t.touches?t.touches[0]:t;Math.max(Math.abs(t.clientX-this._lastX),Math.abs(t.clientY-this._lastY))>=Math.floor(this.options.touchStartThreshold/(this.nativeDraggable&&window.devicePixelRatio||1))&&this._disableDelayedDrag()},_disableDelayedDrag:function(){Z&&Lt(Z),clearTimeout(this._dragStartTimer),this._disableDelayedDragEvents()},_disableDelayedDragEvents:function(){var t=this.el.ownerDocument;p(t,"mouseup",this._disableDelayedDrag),p(t,"touchend",this._disableDelayedDrag),p(t,"touchcancel",this._disableDelayedDrag),p(t,"pointerup",this._disableDelayedDrag),p(t,"pointercancel",this._disableDelayedDrag),p(t,"mousemove",this._delayedDragTouchMoveHandler),p(t,"touchmove",this._delayedDragTouchMoveHandler),p(t,"pointermove",this._delayedDragTouchMoveHandler)},_triggerDragStart:function(t,e){e=e||"touch"==t.pointerType&&t,!this.nativeDraggable||e?this.options.supportPointer?h(document,"pointermove",this._onTouchMove):h(document,e?"touchmove":"mousemove",this._onTouchMove):(h(Z,"dragend",this),h(J,"dragstart",this._onDragStart));try{document.selection?Wt(function(){document.selection.empty()}):window.getSelection().removeAllRanges()}catch(t){}},_dragStarted:function(t,e){var n;Dt=!1,J&&Z?(U("dragStarted",this,{evt:e}),this.nativeDraggable&&h(document,"dragover",Ft),n=this.options,t||k(Z,n.dragClass,!1),k(Z,n.ghostClass,!0),jt.active=this,t&&this._appendGhost(),V({sortable:this,name:"start",originalEvent:e})):this._nulling()},_emulateDragOver:function(){if(dt){this._lastX=dt.clientX,this._lastY=dt.clientY,Xt();for(var t=document.elementFromPoint(dt.clientX,dt.clientY),e=t;t&&t.shadowRoot&&(t=t.shadowRoot.elementFromPoint(dt.clientX,dt.clientY))!==e;)e=t;if(Z.parentNode[K]._isOutsideThisEl(t),e)do{if(e[K])if(e[K]._onDragOver({clientX:dt.clientX,clientY:dt.clientY,target:t,rootEl:e})&&!this.options.dragoverBubble)break}while(e=g(t=e));Yt()}},_onTouchMove:function(t){if(ut){var e=this.options,n=e.fallbackTolerance,o=e.fallbackOffset,i=t.touches?t.touches[0]:t,r=Q&&b(Q,!0),a=Q&&r&&r.a,l=Q&&r&&r.d,e=At&&wt&&E(wt),a=(i.clientX-ut.clientX+o.x)/(a||1)+(e?e[0]-Tt[0]:0)/(a||1),l=(i.clientY-ut.clientY+o.y)/(l||1)+(e?e[1]-Tt[1]:0)/(l||1);if(!jt.active&&!Dt){if(n&&Math.max(Math.abs(i.clientX-this._lastX),Math.abs(i.clientY-this._lastY))E.right+10||S.clientY>x.bottom&&S.clientX>x.left:S.clientY>E.bottom+10||S.clientX>x.right&&S.clientY>x.top)||m.animated)){if(m&&(t=n,e=r,C=X(B((_=this).el,0,_.options,!0)),_=L(_.el,_.options,Q),e?t.clientX<_.left-10||t.clientY /dev/null 2>&1 &") @@ -631,7 +653,7 @@ function restore_backup() fp:close() if chunk_index + 1 == total_chunks then api.sys.call("echo '' > /tmp/log/passwall2.log") - api.log(string.format(" * PassWall2 %s", i18n.translate("Configuration file uploaded successfully…"))) + api.log(0, string.format(" * PassWall2 %s", i18n.translate("Configuration file uploaded successfully…"))) local temp_dir = '/tmp/passwall2_bak' api.sys.call("mkdir -p " .. temp_dir) if api.sys.call("tar -xzf " .. file_path .. " -C " .. temp_dir) == 0 then @@ -641,13 +663,13 @@ function restore_backup() api.sys.call("cp -f " .. temp_file .. " " .. backup_file) end end - api.log(string.format(" * PassWall2 %s", i18n.translate("Configuration restored successfully…"))) - api.log(string.format(" * PassWall2 %s", i18n.translate("Service restarting…"))) + api.log(0, string.format(" * PassWall2 %s", i18n.translate("Configuration restored successfully…"))) + api.log(0, string.format(" * PassWall2 %s", i18n.translate("Service restarting…"))) luci.sys.call('/etc/init.d/passwall2 restart > /dev/null 2>&1 &') luci.sys.call('/etc/init.d/passwall2_server restart > /dev/null 2>&1 &') result = { status = "success", message = "Upload completed", path = file_path } else - api.log(string.format(" * PassWall2 %s", i18n.translate("Configuration file decompression failed, please try again!"))) + api.log(0, string.format(" * PassWall2 %s", i18n.translate("Configuration file decompression failed, please try again!"))) result = { status = "error", message = "Decompression failed" } end api.sys.call("rm -rf " .. temp_dir) diff --git a/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe.lua b/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe.lua index cd7cc8118c..327b488cff 100644 --- a/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe.lua +++ b/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe.lua @@ -209,7 +209,7 @@ o.cfgvalue = function(t, n) str = str ~= "" and "
" .. str or "" local num = 0 m.uci:foreach(appname, "nodes", function(s) - if s["group"] ~= "" and s["group"] == remark then + if s["group"] and s["group"]:lower() == remark:lower() then num = num + 1 end end) diff --git a/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/ray.lua b/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/ray.lua index fbd6aabdf0..231965e295 100644 --- a/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/ray.lua +++ b/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/ray.lua @@ -390,6 +390,9 @@ o = s:option(Flag, _n("tls_allowInsecure"), translate("allowInsecure"), translat o.default = "0" o:depends({ [_n("tls")] = true, [_n("reality")] = false }) +o = s:option(Value, _n("tls_chain_fingerprint"), translate("TLS Chain Fingerprint (SHA256)"), translate("Once set, connects only when the server’s chain fingerprint matches.")) +o:depends({ [_n("tls")] = true, [_n("reality")] = false }) + o = s:option(Flag, _n("ech"), translate("ECH")) o.default = "0" o:depends({ [_n("tls")] = true, [_n("flow")] = "", [_n("reality")] = false }) @@ -616,8 +619,14 @@ o = s:option(TextValue, _n("xhttp_extra"), " ", translate("An XHttpObject in JSO o:depends({ [_n("use_xhttp_extra")] = true }) o.rows = 15 o.wrap = "off" +o.custom_cfgvalue = function(self, section, value) + local raw = m:get(section, "xhttp_extra") + if raw then + return api.base64Decode(raw) + end +end o.custom_write = function(self, section, value) - m:set(section, self.option:sub(1 + #option_prefix), value) + m:set(section, "xhttp_extra", api.base64Encode(value)) local success, data = pcall(jsonc.parse, value) if success and data then local address = (data.extra and data.extra.downloadSettings and data.extra.downloadSettings.address) @@ -640,7 +649,7 @@ o.validate = function(self, value) return value end o.custom_remove = function(self, section, value) - m:del(section, self.option:sub(1 + #option_prefix)) + m:del(section, "xhttp_extra") m:del(section, "download_address") end diff --git a/openwrt-passwall2/luci-app-passwall2/luasrc/passwall2/api.lua b/openwrt-passwall2/luci-app-passwall2/luasrc/passwall2/api.lua index d17f591451..b56192610b 100644 --- a/openwrt-passwall2/luci-app-passwall2/luasrc/passwall2/api.lua +++ b/openwrt-passwall2/luci-app-passwall2/luasrc/passwall2/api.lua @@ -31,8 +31,8 @@ if lang == "auto" then end i18n.setlanguage(lang) -function log(...) - local result = os.date("%Y-%m-%d %H:%M:%S: ") .. table.concat({...}, " ") +function echolog(...) + local result = table.concat({...}, " ") local f, err = io.open(LOG_FILE, "a") if f and err == nil then f:write(result .. "\n") @@ -40,6 +40,23 @@ function log(...) end end +function echolog_date(...) + local result = os.date("%Y-%m-%d %H:%M:%S: ") .. table.concat({...}, " ") + echolog(result) +end + +function log(level, ...) + local indent = "" + if level >= 1 then + for i = 1, level, 1 do + indent = indent .. " " + end + echolog_date(indent .. "- " .. table.concat({...}, " ")) + else + echolog_date(table.concat({...}, " ")) + end +end + function is_old_uci() return sys.call("grep -E 'require[ \t]*\"uci\"' /usr/lib/lua/luci/model/uci.lua >/dev/null 2>&1") == 0 end @@ -118,19 +135,15 @@ function exec_call(cmd) end function base64Decode(text) - local raw = text if not text then return '' end - text = text:gsub("%z", "") - text = text:gsub("%c", "") - text = text:gsub("_", "/") - text = text:gsub("-", "+") - local mod4 = #text % 4 - text = text .. string.sub('====', mod4 + 1) - local result = nixio.bin.b64decode(text) + local encoded = text:gsub("%z", ""):gsub("%c", ""):gsub("_", "/"):gsub("-", "+") + local mod4 = #encoded % 4 + encoded = encoded .. string.sub('====', mod4 + 1) + local result = nixio.bin.b64decode(encoded) if result then return result:gsub("%z", "") else - return raw + return text end end @@ -229,9 +242,13 @@ function url(...) return require "luci.dispatcher".build_url(url) end -function trim(text) - if not text or text == "" then return "" end - return text:match("^%s*(.-)%s*$") +function trim(s) + local len = #s + local i, j = 1, len + while i <= len and s:byte(i) <= 32 do i = i + 1 end + while j >= i and s:byte(j) <= 32 do j = j - 1 end + if i > j then return "" end + return s:sub(i, j) end function split(full, sep) @@ -450,6 +467,8 @@ end function get_valid_nodes() local show_node_info = uci_get_type("global_other", "show_node_info") or "0" local nodes = {} + local default_nodes = {} + local other_nodes = {} uci:foreach(appname, "nodes", function(e) e.id = e[".name"] if e.type and e.remarks then @@ -458,7 +477,11 @@ function get_valid_nodes() if type == "sing-box" then type = "Sing-Box" end e["remark"] = "%s:[%s] " % {type .. " " .. i18n.translatef(e.protocol), e.remarks} e["node_type"] = "special" - nodes[#nodes + 1] = e + if not e.group or e.group == "" then + default_nodes[#default_nodes + 1] = e + else + other_nodes[#other_nodes + 1] = e + end end local port = e.port or e.hysteria_hop or e.hysteria2_hop if port and e.address then @@ -498,11 +521,17 @@ function get_valid_nodes() e["remark"] = "%s:[%s] %s:%s" % {type, e.remarks, address, port} end e.node_type = "normal" - nodes[#nodes + 1] = e + if not e.group or e.group == "" then + default_nodes[#default_nodes + 1] = e + else + other_nodes[#other_nodes + 1] = e + end end end end end) + for i = 1, #default_nodes do nodes[#nodes + 1] = default_nodes[i] end + for i = 1, #other_nodes do nodes[#nodes + 1] = other_nodes[i] end return nodes end diff --git a/openwrt-passwall2/luci-app-passwall2/luasrc/passwall2/util_xray.lua b/openwrt-passwall2/luci-app-passwall2/luasrc/passwall2/util_xray.lua index 503a8dbc8b..02cf298a34 100644 --- a/openwrt-passwall2/luci-app-passwall2/luasrc/passwall2/util_xray.lua +++ b/openwrt-passwall2/luci-app-passwall2/luasrc/passwall2/util_xray.lua @@ -151,6 +151,7 @@ function gen_outbound(flag, node, tag, proxy_table) serverName = node.tls_serverName, allowInsecure = (node.tls_allowInsecure == "1") and true or false, fingerprint = (node.type == "Xray" and node.utls == "1" and node.fingerprint and node.fingerprint ~= "") and node.fingerprint or nil, + pinnedPeerCertificateChainSha256 = node.tls_chain_fingerprint and { node.tls_chain_fingerprint } or nil, echConfigList = (node.ech == "1") and node.ech_config or nil, echForceQuery = (node.ech == "1") and (node.ech_ForceQuery or "none") or nil } or nil, @@ -218,7 +219,7 @@ function gen_outbound(flag, node, tag, proxy_table) host = node.xhttp_host, -- If the code contains an "extra" section, retrieve the contents of "extra"; otherwise, assign the value directly to "extra". extra = node.xhttp_extra and (function() - local success, parsed = pcall(jsonc.parse, node.xhttp_extra) + local success, parsed = pcall(jsonc.parse, api.base64Decode(node.xhttp_extra)) if success then return parsed.extra or parsed else diff --git a/openwrt-passwall2/luci-app-passwall2/luasrc/view/passwall2/log/log.htm b/openwrt-passwall2/luci-app-passwall2/luasrc/view/passwall2/log/log.htm index 0df1e39ced..4e6714ed7e 100644 --- a/openwrt-passwall2/luci-app-passwall2/luasrc/view/passwall2/log/log.htm +++ b/openwrt-passwall2/luci-app-passwall2/luasrc/view/passwall2/log/log.htm @@ -3,13 +3,19 @@ local api = require "luci.passwall2.api" -%> <% if api.is_js_luci() then -%> @@ -102,11 +195,7 @@ table td, .table .td { function cbi_t_switch(section, tab) { if( cbi_t[section] && cbi_t[section][tab] ) { // Before switching tabs, first deselect all currently active tabs. - var btn = document.getElementById("select_all_btn"); - if (btn) { - dechecked_all_node(btn); - } - + dechecked_all_node(); var o = cbi_t[section][tab]; var h = document.getElementById('tab.' + section); for( var tid in cbi_t[section] ) { @@ -131,10 +220,7 @@ table td, .table .td { if (typeof(cbi_t_switch) === "function") { var old_switch = cbi_t_switch; cbi_t_switch = function(section, tab) { - var btn = document.getElementById("select_all_btn"); - if (btn) { - dechecked_all_node(btn); - } + dechecked_all_node(); return old_switch(section, tab); }; } @@ -225,47 +311,70 @@ table td, .table .td { document.getElementById("set_node_div").style.display="none"; document.getElementById("set_node_name").innerHTML = ""; } - - function _cbi_row_top(id) { - //It has been damaged and awaits repair or other solutions. - var dom = document.getElementById("cbi-passwall2-" + id); - if (dom) { - var trs = document.getElementById("cbi-passwall2-nodes").getElementsByClassName("cbi-section-table-row"); - if (trs && trs.length > 0) { - for (var i = 0; i < trs.length; i++) { - var up = dom.getElementsByClassName("cbi-button-up"); - if (up) { - cbi_row_swap(up[0], true, 'cbi.sts.passwall2.nodes'); - } - } - } + + function row_top(btn) { + const row = btn.closest("tr"); + if (!row) return; + const parent = row.parentNode; + let firstDataRow = parent.querySelector("tr:not(.cbi-section-table-titles)"); + if (firstDataRow && firstDataRow !== row) { + parent.insertBefore(row, firstDataRow); } } - + + function set_select_all_state(sectionChecked) { + var visibleContainer = document.querySelector('#cbi-passwall2-nodes > .cbi-tabcontainer[style*="display:block"], #cbi-passwall2-nodes > .cbi-tabcontainer[style*="display: block"]'); + if (!visibleContainer) return; + var nodes = visibleContainer.getElementsByClassName("nodes_select"); + var selectAllChk = visibleContainer.querySelector(".nodes_select_all"); + var selectAllBtn = document.getElementById("select_all_btn"); + for (var i = 0; i < nodes.length; i++) { + nodes[i].checked = sectionChecked; + } + if (selectAllChk) { + selectAllChk.checked = sectionChecked; + selectAllChk.title = sectionChecked ? "<%:DeSelect all%>" : "<%:Select all%>"; + selectAllChk.setAttribute("onclick", sectionChecked ? "dechecked_all_node(this)" : "checked_all_node(this)"); + } + if (selectAllBtn) { + selectAllBtn.value = sectionChecked ? "<%:DeSelect all%>" : "<%:Select all%>"; + selectAllBtn.setAttribute("onclick", sectionChecked ? "dechecked_all_node(this)" : "checked_all_node(this)"); + } + } + function checked_all_node(btn) { - var visibleContainer = document.querySelector('#cbi-passwall2-nodes > .cbi-tabcontainer[style*="display:block"], #cbi-passwall2-nodes > .cbi-tabcontainer[style*="display: block"]'); - if (!visibleContainer) return; - var doms = visibleContainer.getElementsByClassName("nodes_select"); - if (doms && doms.length > 0) { - for (var i = 0 ; i < doms.length; i++) { - doms[i].checked = true; - } - btn.value = "<%:DeSelect all%>"; - btn.setAttribute("onclick", "dechecked_all_node(this)"); - } + set_select_all_state(true); } - + function dechecked_all_node(btn) { + set_select_all_state(false); + } + + function update_select_state() { var visibleContainer = document.querySelector('#cbi-passwall2-nodes > .cbi-tabcontainer[style*="display:block"], #cbi-passwall2-nodes > .cbi-tabcontainer[style*="display: block"]'); if (!visibleContainer) return; - var doms = visibleContainer.getElementsByClassName("nodes_select"); - if (doms && doms.length > 0) { - for (var i = 0 ; i < doms.length; i++) { - doms[i].checked = false; - } - btn.value = "<%:Select all%>"; - btn.setAttribute("onclick", "checked_all_node(this)"); + var nodes = visibleContainer.getElementsByClassName("nodes_select"); + if (!nodes.length) return; + var selectAllChk = visibleContainer.querySelector(".nodes_select_all"); + var selectAllBtn = document.getElementById("select_all_btn"); + var checkedCount = 0; + for (var i = 0; i < nodes.length; i++) { + if (nodes[i].checked) checkedCount++; } + var allChecked = checkedCount === nodes.length; + var title = allChecked ? "<%:DeSelect all%>" : "<%:Select all%>"; + var onclickFunc = allChecked ? "dechecked_all_node(this)" : "checked_all_node(this)"; + + function updateElement(el) { + if (!el) return; + if ("checked" in el) el.checked = allChecked; + if ("title" in el) el.title = title; + if ("value" in el) el.value = title; + el.setAttribute("onclick", onclickFunc); + } + + updateElement(selectAllChk); + updateElement(selectAllBtn); } function delete_select_nodes() { @@ -321,6 +430,66 @@ table td, .table .td { return { address: address, port: port }; } + function get_node_order(group) { + let table = document.getElementById("cbi-passwall2-nodes-" + group + "-table"); + if (!table) { + return; + } + let rows = table.querySelectorAll("tr.cbi-section-table-row"); + if (!rows || rows.length === 0) { + return; + } + var ids = []; + rows.forEach(function(row) { + var id = row.id.replace("cbi-passwall2-", ""); + ids.push(id); + }); + return ids; + } + + function save_current_page_order(group) { + var table = document.getElementById("cbi-passwall2-nodes-" + group + "-table"); + if (!table) { + alert("<%:No table!%>"); + return; + } + var rows = table.querySelectorAll("tr.cbi-section-table-row"); + if (!rows || rows.length === 0) { + alert("<%:No nodes!%>"); + return; + } + var btn = document.getElementById("save_order_btn_" + group); + if (btn) { + btn.style.display = "none"; + btn.disabled = true; + } + var ids = []; + rows.forEach(function(row) { + var id = row.id.replace("cbi-passwall2-", ""); + ids.push(id); + }); + XHR.get('<%=api.url("save_node_order")%>', { + group: group, + ids: ids.join(",") + }, + function(x, result) { + if (btn) { + btn.style.display = null; + btn.disabled = false; + } + if (x && x.status === 200) { + origin_group_node_order[group] = get_node_order(group); + alert("<%:Saved current page order successfully.%>"); + if (btn) { + btn.style.display = "none"; + } + } else { + alert("<%:Save failed!%>"); + } + } + ); + } + function get_now_use_node() { XHR.get('<%=api.url("get_now_use_node")%>', null, function(x, result) { @@ -505,6 +674,79 @@ table td, .table .td { } } } + + function arraysEqual(a, b) { + if (a === b) return true; + if (a == null || b == null) return false; + if (a.length !== b.length) return false; + for (let i = 0; i < a.length; i++) { + if (a[i] !== b[i]) return false; + } + return true; + } + + // List drag and rearrange + function initSortableForTable(table) { + if (!table) return null; + let group = table.id.replace("cbi-passwall2-nodes-", "").replace("-table", "") + var root = table.querySelector('tbody') || table; + if (root._sortable_initialized) return root._sortable_instance; + root._sortable_initialized = true; + var opts = { + handle: ".drag-handle", + draggable: "tr.cbi-section-table-row", + animation: 150, + ghostClass: "dragging-row", + fallbackOnBody: true, + forceFallback: false, + swapThreshold: 0.65, + onEnd: function (evt) { + //save_current_page_order(group); // Auto save + let save_order_btn = document.getElementById("save_order_btn_" + group); + if (save_order_btn) { + const new_order = get_node_order(group); + if (!arraysEqual(new_order, origin_group_node_order[group])) { + save_order_btn.style.display = null; + } else { + save_order_btn.style.display = "none"; + } + } + } + }; + try { + var instance = Sortable.create(root, opts); + root._sortable_instance = instance; + return instance; + } catch (err) { + root._sortable_initialized = false; + console.error("Sortable init failed:", err); + return null; + } + } + + function initAllSortable(group_nodes) { + if (typeof Sortable === 'undefined') { + var retries = 0; + var maxRetries = 25; + var t = setInterval(function () { + retries++; + if (typeof Sortable !== 'undefined') { + clearInterval(t); + for (var group in group_nodes) { + var table = document.getElementById("cbi-passwall2-nodes-" + group + "-table"); + initSortableForTable(table); + } + } else if (retries >= maxRetries) { + clearInterval(t); + } + }, 200); + } else { + for (var group in group_nodes) { + var table = document.getElementById("cbi-passwall2-nodes-" + group + "-table"); + initSortableForTable(table); + } + } + } @@ -512,6 +754,9 @@ table td, .table .td {
+ @@ -522,6 +767,7 @@ table td, .table .td {
+ + <%:Remarks%> Ping TCPing
+
@@ -531,19 +777,21 @@ table td, .table .td { - {{remarks}} - {{ping}} - {{tcping}} - {{url_test}} - + + + + {{remarks}} + {{ping}} + {{tcping}} + {{url_test}} +
- - - + /{{id}}'" alt="<%:Edit%>" title="<%:Edit%>"> +
@@ -705,6 +953,13 @@ table td, .table .td { cbi_t_switch("passwall2.nodes", default_group) } + origin_group_node_order = {}; + for (let group in group_nodes) { + origin_group_node_order[group] = get_node_order(group); + } + + initAllSortable(group_nodes); + //clear expire data if (localStorage && localStorage.length > 0) { const now = Date.now(); diff --git a/openwrt-passwall2/luci-app-passwall2/luasrc/view/passwall2/server/log.htm b/openwrt-passwall2/luci-app-passwall2/luasrc/view/passwall2/server/log.htm index 4bb5c2dad2..28d9dd2594 100644 --- a/openwrt-passwall2/luci-app-passwall2/luasrc/view/passwall2/server/log.htm +++ b/openwrt-passwall2/luci-app-passwall2/luasrc/view/passwall2/server/log.htm @@ -3,13 +3,19 @@ local api = require "luci.passwall2.api" -%> diff --git a/small/mihomo/Makefile b/small/mihomo/Makefile index ee4ff27c63..f4d0679bed 100644 --- a/small/mihomo/Makefile +++ b/small/mihomo/Makefile @@ -5,12 +5,12 @@ include $(TOPDIR)/rules.mk PKG_NAME:=mihomo -PKG_VERSION:=1.19.16 +PKG_VERSION:=1.19.17 PKG_RELEASE:=1 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE_URL:=https://codeload.github.com/metacubex/mihomo/tar.gz/v$(PKG_VERSION)? -PKG_HASH:=34a506732bf29fb6c39c3e69d226999d3bf0420751a10679528177896583d551 +PKG_HASH:=8f340d40609a55cf02e4a630d51f9515a46780fcfd125deaf2019ecb9b1ee58a PKG_MAINTAINER:=Anya Lin PKG_LICENSE:=GPL-2.0 diff --git a/small/v2ray-geodata/Makefile b/small/v2ray-geodata/Makefile index fadfc9d088..c8889058aa 100644 --- a/small/v2ray-geodata/Makefile +++ b/small/v2ray-geodata/Makefile @@ -21,22 +21,22 @@ define Download/geoip HASH:=2445b44d9ae3ab9a867c9d1e0e244646c4c378622e14b9afaf3658ecf46a40b9 endef -GEOSITE_VER:=20251130074021 +GEOSITE_VER:=20251201150926 GEOSITE_FILE:=dlc.dat.$(GEOSITE_VER) define Download/geosite URL:=https://github.com/v2fly/domain-list-community/releases/download/$(GEOSITE_VER)/ URL_FILE:=dlc.dat FILE:=$(GEOSITE_FILE) - HASH:=7e4c0877aabed22425aa651859e2f705f516a154ece96f30cccef71c1dc4a500 + HASH:=868d244cdc8e55166c372bdcfef4ef73c45c77eb96420bf684af658fc34475b7 endef -GEOSITE_IRAN_VER:=202511240043 +GEOSITE_IRAN_VER:=202512010051 GEOSITE_IRAN_FILE:=iran.dat.$(GEOSITE_IRAN_VER) define Download/geosite-ir URL:=https://github.com/bootmortis/iran-hosted-domains/releases/download/$(GEOSITE_IRAN_VER)/ URL_FILE:=iran.dat FILE:=$(GEOSITE_IRAN_FILE) - HASH:=ffea8ddf7d6fd528ad8f7f5e5b9bc365454f5ea16be9353f0864b4baf75ec6c8 + HASH:=ae286762be4b810bd6b0b16d9960d52af587cdb3c1ec21f2fd0249cc0bf3eb40 endef define Package/v2ray-geodata/template diff --git a/xray-core/app/proxyman/outbound/handler.go b/xray-core/app/proxyman/outbound/handler.go index 3c3c691853..eaa1b0b2f2 100644 --- a/xray-core/app/proxyman/outbound/handler.go +++ b/xray-core/app/proxyman/outbound/handler.go @@ -317,8 +317,12 @@ func (h *Handler) Dial(ctx context.Context, dest net.Destination) (stat.Connecti conn, err := internet.Dial(ctx, dest, h.streamSettings) conn = h.getStatCouterConnection(conn) outbounds := session.OutboundsFromContext(ctx) - ob := outbounds[len(outbounds)-1] - ob.Conn = conn + if outbounds != nil { + ob := outbounds[len(outbounds)-1] + ob.Conn = conn + } else { + // for Vision's pre-connect + } return conn, err } diff --git a/xray-core/core/core.go b/xray-core/core/core.go index 4219769793..1d40a5d26d 100644 --- a/xray-core/core/core.go +++ b/xray-core/core/core.go @@ -18,8 +18,8 @@ import ( var ( Version_x byte = 25 - Version_y byte = 10 - Version_z byte = 15 + Version_y byte = 12 + Version_z byte = 1 ) var ( diff --git a/xray-core/infra/conf/vless.go b/xray-core/infra/conf/vless.go index efb480a037..f482c4d808 100644 --- a/xray-core/infra/conf/vless.go +++ b/xray-core/infra/conf/vless.go @@ -34,6 +34,7 @@ type VLessInboundConfig struct { Decryption string `json:"decryption"` Fallbacks []*VLessInboundFallback `json:"fallbacks"` Flow string `json:"flow"` + Testseed []uint32 `json:"testseed"` } // Build implements Buildable @@ -73,6 +74,10 @@ func (c *VLessInboundConfig) Build() (proto.Message, error) { return nil, errors.New(`VLESS clients: "flow" doesn't support "` + account.Flow + `" in this version`) } + if len(account.Testseed) < 4 { + account.Testseed = c.Testseed + } + if account.Encryption != "" { return nil, errors.New(`VLESS clients: "encryption" should not be in inbound settings`) } @@ -212,6 +217,8 @@ type VLessOutboundConfig struct { Seed string `json:"seed"` Encryption string `json:"encryption"` Reverse *vless.Reverse `json:"reverse"` + Testpre uint32 `json:"testpre"` + Testseed []uint32 `json:"testseed"` Vnext []*VLessOutboundVnext `json:"vnext"` } @@ -258,6 +265,8 @@ func (c *VLessOutboundConfig) Build() (proto.Message, error) { //account.Seed = c.Seed account.Encryption = c.Encryption account.Reverse = c.Reverse + account.Testpre = c.Testpre + account.Testseed = c.Testseed } else { if err := json.Unmarshal(rawUser, account); err != nil { return nil, errors.New(`VLESS users: invalid user`).Base(err) diff --git a/xray-core/proxy/proxy.go b/xray-core/proxy/proxy.go index 9f965e3e0b..aa57adae8d 100644 --- a/xray-core/proxy/proxy.go +++ b/xray-core/proxy/proxy.go @@ -296,11 +296,16 @@ type VisionWriter struct { // internal writeOnceUserUUID []byte directWriteCounter stats.Counter + + testseed []uint32 } -func NewVisionWriter(writer buf.Writer, trafficState *TrafficState, isUplink bool, ctx context.Context, conn net.Conn, ob *session.Outbound) *VisionWriter { +func NewVisionWriter(writer buf.Writer, trafficState *TrafficState, isUplink bool, ctx context.Context, conn net.Conn, ob *session.Outbound, testseed []uint32) *VisionWriter { w := make([]byte, len(trafficState.UserUUID)) copy(w, trafficState.UserUUID) + if len(testseed) < 4 { + testseed = []uint32{900, 500, 900, 256} + } return &VisionWriter{ Writer: writer, trafficState: trafficState, @@ -309,6 +314,7 @@ func NewVisionWriter(writer buf.Writer, trafficState *TrafficState, isUplink boo isUplink: isUplink, conn: conn, ob: ob, + testseed: testseed, } } @@ -347,7 +353,7 @@ func (w *VisionWriter) WriteMultiBuffer(mb buf.MultiBuffer) error { if *isPadding { if len(mb) == 1 && mb[0] == nil { - mb[0] = XtlsPadding(nil, CommandPaddingContinue, &w.writeOnceUserUUID, true, w.ctx) // we do a long padding to hide vless header + mb[0] = XtlsPadding(nil, CommandPaddingContinue, &w.writeOnceUserUUID, true, w.ctx, w.testseed) // we do a long padding to hide vless header return w.Writer.WriteMultiBuffer(mb) } isComplete := IsCompleteRecord(mb) @@ -365,13 +371,13 @@ func (w *VisionWriter) WriteMultiBuffer(mb buf.MultiBuffer) error { command = CommandPaddingDirect } } - mb[i] = XtlsPadding(b, command, &w.writeOnceUserUUID, true, w.ctx) + mb[i] = XtlsPadding(b, command, &w.writeOnceUserUUID, true, w.ctx, w.testseed) *isPadding = false // padding going to end longPadding = false continue } else if !w.trafficState.IsTLS12orAbove && w.trafficState.NumberOfPacketToFilter <= 1 { // For compatibility with earlier vision receiver, we finish padding 1 packet early *isPadding = false - mb[i] = XtlsPadding(b, CommandPaddingEnd, &w.writeOnceUserUUID, longPadding, w.ctx) + mb[i] = XtlsPadding(b, CommandPaddingEnd, &w.writeOnceUserUUID, longPadding, w.ctx, w.testseed) break } var command byte = CommandPaddingContinue @@ -381,7 +387,7 @@ func (w *VisionWriter) WriteMultiBuffer(mb buf.MultiBuffer) error { command = CommandPaddingDirect } } - mb[i] = XtlsPadding(b, command, &w.writeOnceUserUUID, longPadding, w.ctx) + mb[i] = XtlsPadding(b, command, &w.writeOnceUserUUID, longPadding, w.ctx, w.testseed) } } return w.Writer.WriteMultiBuffer(mb) @@ -488,20 +494,20 @@ func ReshapeMultiBuffer(ctx context.Context, buffer buf.MultiBuffer) buf.MultiBu } // XtlsPadding add padding to eliminate length signature during tls handshake -func XtlsPadding(b *buf.Buffer, command byte, userUUID *[]byte, longPadding bool, ctx context.Context) *buf.Buffer { +func XtlsPadding(b *buf.Buffer, command byte, userUUID *[]byte, longPadding bool, ctx context.Context, testseed []uint32) *buf.Buffer { var contentLen int32 = 0 var paddingLen int32 = 0 if b != nil { contentLen = b.Len() } - if contentLen < 900 && longPadding { - l, err := rand.Int(rand.Reader, big.NewInt(500)) + if contentLen < int32(testseed[0]) && longPadding { + l, err := rand.Int(rand.Reader, big.NewInt(int64(testseed[1]))) if err != nil { errors.LogDebugInner(ctx, err, "failed to generate padding") } - paddingLen = int32(l.Int64()) + 900 - contentLen + paddingLen = int32(l.Int64()) + int32(testseed[2]) - contentLen } else { - l, err := rand.Int(rand.Reader, big.NewInt(256)) + l, err := rand.Int(rand.Reader, big.NewInt(int64(testseed[3]))) if err != nil { errors.LogDebugInner(ctx, err, "failed to generate padding") } diff --git a/xray-core/proxy/vless/account.go b/xray-core/proxy/vless/account.go index ac00ea53f2..2f617149d4 100644 --- a/xray-core/proxy/vless/account.go +++ b/xray-core/proxy/vless/account.go @@ -22,6 +22,8 @@ func (a *Account) AsAccount() (protocol.Account, error) { Seconds: a.Seconds, Padding: a.Padding, Reverse: a.Reverse, + Testpre: a.Testpre, + Testseed: a.Testseed, }, nil } @@ -38,6 +40,9 @@ type MemoryAccount struct { Padding string Reverse *Reverse + + Testpre uint32 + Testseed []uint32 } // Equals implements protocol.Account.Equals(). @@ -58,5 +63,7 @@ func (a *MemoryAccount) ToProto() proto.Message { Seconds: a.Seconds, Padding: a.Padding, Reverse: a.Reverse, + Testpre: a.Testpre, + Testseed: a.Testseed, } } diff --git a/xray-core/proxy/vless/account.pb.go b/xray-core/proxy/vless/account.pb.go index 5822f51245..ce01ecae56 100644 --- a/xray-core/proxy/vless/account.pb.go +++ b/xray-core/proxy/vless/account.pb.go @@ -79,6 +79,8 @@ type Account struct { Seconds uint32 `protobuf:"varint,5,opt,name=seconds,proto3" json:"seconds,omitempty"` Padding string `protobuf:"bytes,6,opt,name=padding,proto3" json:"padding,omitempty"` Reverse *Reverse `protobuf:"bytes,7,opt,name=reverse,proto3" json:"reverse,omitempty"` + Testpre uint32 `protobuf:"varint,8,opt,name=testpre,proto3" json:"testpre,omitempty"` + Testseed []uint32 `protobuf:"varint,9,rep,packed,name=testseed,proto3" json:"testseed,omitempty"` } func (x *Account) Reset() { @@ -160,6 +162,20 @@ func (x *Account) GetReverse() *Reverse { return nil } +func (x *Account) GetTestpre() uint32 { + if x != nil { + return x.Testpre + } + return 0 +} + +func (x *Account) GetTestseed() []uint32 { + if x != nil { + return x.Testseed + } + return nil +} + var File_proxy_vless_account_proto protoreflect.FileDescriptor var file_proxy_vless_account_proto_rawDesc = []byte{ @@ -167,7 +183,7 @@ var file_proxy_vless_account_proto_rawDesc = []byte{ 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x10, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x22, 0x1b, 0x0a, 0x07, 0x52, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x22, 0xd0, 0x01, 0x0a, 0x07, 0x41, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x22, 0x86, 0x02, 0x0a, 0x07, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x6c, 0x6f, 0x77, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x1e, 0x0a, 0x0a, 0x65, 0x6e, @@ -180,13 +196,16 @@ var file_proxy_vless_account_proto_rawDesc = []byte{ 0x07, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x33, 0x0a, 0x07, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2e, 0x52, 0x65, 0x76, - 0x65, 0x72, 0x73, 0x65, 0x52, 0x07, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x42, 0x52, 0x0a, - 0x14, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, - 0x76, 0x6c, 0x65, 0x73, 0x73, 0x50, 0x01, 0x5a, 0x25, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, - 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, - 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6c, 0x65, 0x73, 0x73, 0xaa, 0x02, - 0x10, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x56, 0x6c, 0x65, 0x73, - 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x65, 0x72, 0x73, 0x65, 0x52, 0x07, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x12, 0x18, 0x0a, + 0x07, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, + 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x65, 0x73, 0x74, 0x73, + 0x65, 0x65, 0x64, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0d, 0x52, 0x08, 0x74, 0x65, 0x73, 0x74, 0x73, + 0x65, 0x65, 0x64, 0x42, 0x52, 0x0a, 0x14, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, + 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x50, 0x01, 0x5a, 0x25, 0x67, + 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, + 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x76, + 0x6c, 0x65, 0x73, 0x73, 0xaa, 0x02, 0x10, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x78, + 0x79, 0x2e, 0x56, 0x6c, 0x65, 0x73, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/xray-core/proxy/vless/account.proto b/xray-core/proxy/vless/account.proto index 047311dd87..d0f1ee2259 100644 --- a/xray-core/proxy/vless/account.proto +++ b/xray-core/proxy/vless/account.proto @@ -22,4 +22,7 @@ message Account { string padding = 6; Reverse reverse = 7; + + uint32 testpre = 8; + repeated uint32 testseed = 9; } diff --git a/xray-core/proxy/vless/encoding/addons.go b/xray-core/proxy/vless/encoding/addons.go index 77b0986110..724de77716 100644 --- a/xray-core/proxy/vless/encoding/addons.go +++ b/xray-core/proxy/vless/encoding/addons.go @@ -68,7 +68,7 @@ func EncodeBodyAddons(writer buf.Writer, request *protocol.RequestHeader, reques return NewMultiLengthPacketWriter(writer) } if requestAddons.Flow == vless.XRV { - return proxy.NewVisionWriter(writer, state, isUplink, context, conn, ob) + return proxy.NewVisionWriter(writer, state, isUplink, context, conn, ob, request.User.Account.(*vless.MemoryAccount).Testseed) } return writer } diff --git a/xray-core/proxy/vless/outbound/outbound.go b/xray-core/proxy/vless/outbound/outbound.go index c425151fee..504b21263e 100644 --- a/xray-core/proxy/vless/outbound/outbound.go +++ b/xray-core/proxy/vless/outbound/outbound.go @@ -7,6 +7,7 @@ import ( "encoding/base64" "reflect" "strings" + "sync" "time" "unsafe" @@ -15,6 +16,7 @@ import ( "github.com/xtls/xray-core/app/reverse" "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/buf" + xctx "github.com/xtls/xray-core/common/ctx" "github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/mux" "github.com/xtls/xray-core/common/net" @@ -52,6 +54,10 @@ type Handler struct { cone bool encryption *encryption.ClientInstance reverse *Reverse + + testpre uint32 + initpre sync.Once + preConns chan stat.Connection } // New creates a new VLess outbound handler. @@ -105,11 +111,16 @@ func New(ctx context.Context, config *Config) (*Handler, error) { }() } + handler.testpre = a.Testpre + return handler, nil } // Close implements common.Closable.Close(). func (h *Handler) Close() error { + if h.preConns != nil { + close(h.preConns) + } if h.reverse != nil { return h.reverse.Close() } @@ -128,18 +139,46 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte rec := h.server var conn stat.Connection - if err := retry.ExponentialBackoff(5, 200).On(func() error { - var err error - conn, err = dialer.Dial(ctx, rec.Destination) - if err != nil { - return err + if h.testpre > 0 && h.reverse == nil { + h.initpre.Do(func() { + h.preConns = make(chan stat.Connection) + for range h.testpre { // TODO: randomize + go func() { + defer func() { recover() }() + ctx := xctx.ContextWithID(context.Background(), session.NewID()) + for { + time.Sleep(time.Millisecond * 200) // TODO: randomize + conn, err := dialer.Dial(ctx, rec.Destination) + if err != nil { + errors.LogWarningInner(ctx, err, "pre-connect failed") + continue + } + h.preConns <- conn + } + }() + } + }) + if conn = <-h.preConns; conn == nil { + return errors.New("closed handler").AtWarning() + } + } + + if conn == nil { + if err := retry.ExponentialBackoff(5, 200).On(func() error { + var err error + conn, err = dialer.Dial(ctx, rec.Destination) + if err != nil { + return err + } + return nil + }); err != nil { + return errors.New("failed to find an available destination").Base(err).AtWarning() } - return nil - }); err != nil { - return errors.New("failed to find an available destination").Base(err).AtWarning() } defer conn.Close() + ob.Conn = conn // for Vision's pre-connect + iConn := conn if statConn, ok := iConn.(*stat.CounterConnection); ok { iConn = statConn.Connection diff --git a/yt-dlp/yt_dlp/extractor/fc2.py b/yt-dlp/yt_dlp/extractor/fc2.py index d343069fec..aa6ff6335d 100644 --- a/yt-dlp/yt_dlp/extractor/fc2.py +++ b/yt-dlp/yt_dlp/extractor/fc2.py @@ -5,6 +5,7 @@ from .common import InfoExtractor from ..networking import Request from ..utils import ( ExtractorError, + UserNotLive, js_to_json, traverse_obj, update_url_query, @@ -205,6 +206,9 @@ class FC2LiveIE(InfoExtractor): 'client_app': 'browser_hls', 'ipv6': '', }), headers={'X-Requested-With': 'XMLHttpRequest'}) + # A non-zero 'status' indicates the stream is not live, so check truthiness + if traverse_obj(control_server, ('status', {int})) and 'control_token' not in control_server: + raise UserNotLive(video_id=video_id) self._set_cookie('live.fc2.com', 'l_ortkn', control_server['orz_raw']) ws_url = update_url_query(control_server['url'], {'control_token': control_server['control_token']}) diff --git a/yt-dlp/yt_dlp/extractor/patreon.py b/yt-dlp/yt_dlp/extractor/patreon.py index 9038b4a7ff..b511994e8a 100644 --- a/yt-dlp/yt_dlp/extractor/patreon.py +++ b/yt-dlp/yt_dlp/extractor/patreon.py @@ -598,7 +598,8 @@ class PatreonCampaignIE(PatreonBaseIE): 'props', 'pageProps', 'bootstrapEnvelope', 'pageBootstrap', 'campaign', 'data', 'id', {str})) if not campaign_id: campaign_id = traverse_obj(self._search_nextjs_v13_data(webpage, vanity), ( - lambda _, v: v['type'] == 'campaign', 'id', {str}, any, {require('campaign ID')})) + ((..., 'value', 'campaign', 'data'), lambda _, v: v['type'] == 'campaign'), + 'id', {str}, any, {require('campaign ID')})) params = { 'json-api-use-default-includes': 'false', diff --git a/yt-dlp/yt_dlp/extractor/tubitv.py b/yt-dlp/yt_dlp/extractor/tubitv.py index 694a92fcd4..bb9a293b86 100644 --- a/yt-dlp/yt_dlp/extractor/tubitv.py +++ b/yt-dlp/yt_dlp/extractor/tubitv.py @@ -182,13 +182,13 @@ class TubiTvShowIE(InfoExtractor): webpage = self._download_webpage(show_url, playlist_id) data = self._search_json( - r'window\.__data\s*=', webpage, 'data', playlist_id, - transform_source=js_to_json)['video'] + r'window\.__REACT_QUERY_STATE__\s*=', webpage, 'data', playlist_id, + transform_source=js_to_json)['queries'][0]['state']['data'] # v['number'] is already a decimal string, but stringify to protect against API changes path = [lambda _, v: str(v['number']) == selected_season] if selected_season else [..., {dict}] - for season in traverse_obj(data, ('byId', lambda _, v: v['type'] == 's', 'seasons', *path)): + for season in traverse_obj(data, ('seasons', *path)): season_number = int_or_none(season.get('number')) for episode in traverse_obj(season, ('episodes', lambda _, v: v['id'])): episode_id = episode['id'] diff --git a/yt-dlp/yt_dlp/extractor/youtube/_video.py b/yt-dlp/yt_dlp/extractor/youtube/_video.py index 600e0ccda6..a792332046 100644 --- a/yt-dlp/yt_dlp/extractor/youtube/_video.py +++ b/yt-dlp/yt_dlp/extractor/youtube/_video.py @@ -4029,6 +4029,11 @@ class YoutubeIE(YoutubeBaseInfoExtractor): STREAMING_DATA_CLIENT_NAME: client_name, }) + def set_audio_lang_from_orig_subs_lang(lang_code): + for f in formats: + if f.get('acodec') != 'none' and not f.get('language'): + f['language'] = lang_code + subtitles = {} skipped_subs_clients = set() @@ -4088,7 +4093,8 @@ class YoutubeIE(YoutubeBaseInfoExtractor): orig_lang = qs.get('lang', [None])[-1] lang_name = self._get_text(caption_track, 'name', max_runs=1) - if caption_track.get('kind') != 'asr': + is_manual_subs = caption_track.get('kind') != 'asr' + if is_manual_subs: if not lang_code: continue process_language( @@ -4099,16 +4105,14 @@ class YoutubeIE(YoutubeBaseInfoExtractor): if not trans_code: continue orig_trans_code = trans_code - if caption_track.get('kind') != 'asr' and trans_code != 'und': + if is_manual_subs and trans_code != 'und': if not get_translated_subs: continue trans_code += f'-{lang_code}' trans_name += format_field(lang_name, None, ' from %s') if lang_code == f'a-{orig_trans_code}': # Set audio language based on original subtitles - for f in formats: - if f.get('acodec') != 'none' and not f.get('language'): - f['language'] = orig_trans_code + set_audio_lang_from_orig_subs_lang(orig_trans_code) # Add an "-orig" label to the original language so that it can be distinguished. # The subs are returned without "-orig" as well for compatibility process_language( @@ -4119,6 +4123,21 @@ class YoutubeIE(YoutubeBaseInfoExtractor): automatic_captions, base_url, trans_code, trans_name, client_name, pot_params if orig_lang == orig_trans_code else {'tlang': trans_code, **pot_params}) + # Extract automatic captions when the language is not in 'translationLanguages' + # e.g. Cantonese [yue], see https://github.com/yt-dlp/yt-dlp/issues/14889 + lang_code = remove_start(lang_code, 'a-') + if is_manual_subs or not lang_code or lang_code in automatic_captions: + continue + lang_name = remove_end(lang_name, ' (auto-generated)') + if caption_track.get('isTranslatable'): + # We can assume this is the original audio language + set_audio_lang_from_orig_subs_lang(lang_code) + process_language( + automatic_captions, base_url, f'{lang_code}-orig', + f'{lang_name} (Original)', client_name, pot_params) + process_language( + automatic_captions, base_url, lang_code, lang_name, client_name, pot_params) + # Avoid duplication if we've already got everything we need need_subs_langs.difference_update(subtitles) need_caps_langs.difference_update(automatic_captions)