diff --git a/.github/update.log b/.github/update.log index 885fbd9771..370eaff37c 100644 --- a/.github/update.log +++ b/.github/update.log @@ -716,3 +716,4 @@ Update On Wed Jul 24 20:33:56 CEST 2024 Update On Thu Jul 25 20:31:26 CEST 2024 Update On Fri Jul 26 20:35:53 CEST 2024 Update On Sat Jul 27 20:31:51 CEST 2024 +Update On Sun Jul 28 20:32:24 CEST 2024 diff --git a/clash-meta-android/build.gradle.kts b/clash-meta-android/build.gradle.kts index 4e29bb7eef..2b50cb9c80 100644 --- a/clash-meta-android/build.gradle.kts +++ b/clash-meta-android/build.gradle.kts @@ -40,8 +40,8 @@ subprojects { minSdk = 21 targetSdk = 31 - versionName = "2.10.1" - versionCode = 210001 + versionName = "2.10.2" + versionCode = 210002 resValue("string", "release_name", "v$versionName") resValue("integer", "release_code", "$versionCode") diff --git a/clash-meta-android/core/src/foss/golang/clash/adapter/inbound/addition.go b/clash-meta-android/core/src/foss/golang/clash/adapter/inbound/addition.go index ed560818d8..894910aac1 100644 --- a/clash-meta-android/core/src/foss/golang/clash/adapter/inbound/addition.go +++ b/clash-meta-android/core/src/foss/golang/clash/adapter/inbound/addition.go @@ -69,3 +69,5 @@ func WithDSCP(dscp uint8) Addition { metadata.DSCP = dscp } } + +func Placeholder(metadata *C.Metadata) {} diff --git a/clash-meta-android/core/src/foss/golang/clash/adapter/outboundgroup/loadbalance.go b/clash-meta-android/core/src/foss/golang/clash/adapter/outboundgroup/loadbalance.go index 4cb0db004f..738ed15479 100644 --- a/clash-meta-android/core/src/foss/golang/clash/adapter/outboundgroup/loadbalance.go +++ b/clash-meta-android/core/src/foss/golang/clash/adapter/outboundgroup/loadbalance.go @@ -205,7 +205,6 @@ func strategyStickySessions(url string) strategyFn { proxy := proxies[nowIdx] if proxy.AliveForTestUrl(url) { if nowIdx != idx { - lruCache.Delete(key) lruCache.Set(key, nowIdx) } @@ -215,7 +214,6 @@ func strategyStickySessions(url string) strategyFn { } } - lruCache.Delete(key) lruCache.Set(key, 0) return proxies[0] } 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 67dc104dfb..efc38aabee 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 @@ -69,7 +69,7 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide } if groupOption.IncludeAllProviders { - groupOption.Use = append(groupOption.Use, AllProviders...) + groupOption.Use = AllProviders } if groupOption.IncludeAllProxies { if groupOption.Filter != "" { diff --git a/clash-meta-android/core/src/foss/golang/clash/common/lru/lrucache.go b/clash-meta-android/core/src/foss/golang/clash/common/lru/lrucache.go index 6f32ed18b1..35f605b10c 100644 --- a/clash-meta-android/core/src/foss/golang/clash/common/lru/lrucache.go +++ b/clash-meta-android/core/src/foss/golang/clash/common/lru/lrucache.go @@ -223,6 +223,10 @@ func (c *LruCache[K, V]) Delete(key K) { c.mu.Lock() defer c.mu.Unlock() + c.delete(key) +} + +func (c *LruCache[K, V]) delete(key K) { if le, ok := c.cache[key]; ok { c.deleteElement(le) } @@ -255,6 +259,34 @@ func (c *LruCache[K, V]) Clear() error { return nil } +// Compute either sets the computed new value for the key or deletes +// the value for the key. When the delete result of the valueFn function +// is set to true, the value will be deleted, if it exists. When delete +// is set to false, the value is updated to the newValue. +// The ok result indicates whether value was computed and stored, thus, is +// present in the map. The actual result contains the new value in cases where +// the value was computed and stored. +func (c *LruCache[K, V]) Compute( + key K, + valueFn func(oldValue V, loaded bool) (newValue V, delete bool), +) (actual V, ok bool) { + c.mu.Lock() + defer c.mu.Unlock() + + if el := c.get(key); el != nil { + actual, ok = el.value, true + } + if newValue, del := valueFn(actual, ok); del { + if ok { // data not in cache, so needn't delete + c.delete(key) + } + return lo.Empty[V](), false + } else { + c.set(key, newValue) + return newValue, true + } +} + type entry[K comparable, V any] struct { key K value V diff --git a/clash-meta-android/core/src/foss/golang/clash/common/queue/queue.go b/clash-meta-android/core/src/foss/golang/clash/common/queue/queue.go index cb58e2f5a2..d1b6beebe5 100644 --- a/clash-meta-android/core/src/foss/golang/clash/common/queue/queue.go +++ b/clash-meta-android/core/src/foss/golang/clash/common/queue/queue.go @@ -59,8 +59,8 @@ func (q *Queue[T]) Copy() []T { // Len returns the number of items in this queue. func (q *Queue[T]) Len() int64 { - q.lock.Lock() - defer q.lock.Unlock() + q.lock.RLock() + defer q.lock.RUnlock() return int64(len(q.items)) } diff --git a/clash-meta-android/core/src/foss/golang/clash/common/utils/callback.go b/clash-meta-android/core/src/foss/golang/clash/common/utils/callback.go index df950d3a81..ad734c0fd6 100644 --- a/clash-meta-android/core/src/foss/golang/clash/common/utils/callback.go +++ b/clash-meta-android/core/src/foss/golang/clash/common/utils/callback.go @@ -17,8 +17,8 @@ func NewCallback[T any]() *Callback[T] { } func (c *Callback[T]) Register(item func(T)) io.Closer { - c.mutex.RLock() - defer c.mutex.RUnlock() + c.mutex.Lock() + defer c.mutex.Unlock() element := c.list.PushBack(item) return &callbackCloser[T]{ element: element, diff --git a/clash-meta-android/core/src/foss/golang/clash/component/cidr/ipcidr_set.go b/clash-meta-android/core/src/foss/golang/clash/component/cidr/ipcidr_set.go index 521fabab13..4907146039 100644 --- a/clash-meta-android/core/src/foss/golang/clash/component/cidr/ipcidr_set.go +++ b/clash-meta-android/core/src/foss/golang/clash/component/cidr/ipcidr_set.go @@ -57,6 +57,16 @@ func (set *IpCidrSet) Merge() error { return nil } +func (set *IpCidrSet) Foreach(f func(prefix netip.Prefix) bool) { + for _, r := range set.rr { + for _, prefix := range r.Prefixes() { + if !f(prefix) { + return + } + } + } +} + // ToIPSet not safe convert to *netipx.IPSet // be careful, must be used after Merge func (set *IpCidrSet) ToIPSet() *netipx.IPSet { diff --git a/clash-meta-android/core/src/foss/golang/clash/component/cidr/ipcidr_set_bin.go b/clash-meta-android/core/src/foss/golang/clash/component/cidr/ipcidr_set_bin.go new file mode 100644 index 0000000000..f6a0348856 --- /dev/null +++ b/clash-meta-android/core/src/foss/golang/clash/component/cidr/ipcidr_set_bin.go @@ -0,0 +1,77 @@ +package cidr + +import ( + "encoding/binary" + "errors" + "io" + "net/netip" + + "go4.org/netipx" +) + +func (ss *IpCidrSet) WriteBin(w io.Writer) (err error) { + // version + _, err = w.Write([]byte{1}) + if err != nil { + return err + } + + // rr + err = binary.Write(w, binary.BigEndian, int64(len(ss.rr))) + if err != nil { + return err + } + for _, r := range ss.rr { + err = binary.Write(w, binary.BigEndian, r.From().As16()) + if err != nil { + return err + } + err = binary.Write(w, binary.BigEndian, r.To().As16()) + if err != nil { + return err + } + } + + return nil +} + +func ReadIpCidrSet(r io.Reader) (ss *IpCidrSet, err error) { + // version + version := make([]byte, 1) + _, err = io.ReadFull(r, version) + if err != nil { + return nil, err + } + if version[0] != 1 { + return nil, errors.New("version is invalid") + } + + ss = NewIpCidrSet() + var length int64 + + // rr + err = binary.Read(r, binary.BigEndian, &length) + if err != nil { + return nil, err + } + if length < 1 { + return nil, errors.New("length is invalid") + } + ss.rr = make([]netipx.IPRange, length) + for i := int64(0); i < length; i++ { + var a16 [16]byte + err = binary.Read(r, binary.BigEndian, &a16) + if err != nil { + return nil, err + } + from := netip.AddrFrom16(a16).Unmap() + err = binary.Read(r, binary.BigEndian, &a16) + if err != nil { + return nil, err + } + to := netip.AddrFrom16(a16).Unmap() + ss.rr[i] = netipx.IPRangeFrom(from, to) + } + + return ss, nil +} diff --git a/clash-meta-android/core/src/foss/golang/clash/component/process/process.go b/clash-meta-android/core/src/foss/golang/clash/component/process/process.go index 76ec2c45e2..84020c4d38 100644 --- a/clash-meta-android/core/src/foss/golang/clash/component/process/process.go +++ b/clash-meta-android/core/src/foss/golang/clash/component/process/process.go @@ -3,6 +3,8 @@ package process import ( "errors" "net/netip" + + C "github.com/metacubex/mihomo/constant" ) var ( @@ -19,3 +21,18 @@ const ( func FindProcessName(network string, srcIP netip.Addr, srcPort int) (uint32, string, error) { return findProcessName(network, srcIP, srcPort) } + +// PackageNameResolver +// never change type traits because it's used in CFMA +type PackageNameResolver func(metadata *C.Metadata) (string, error) + +// DefaultPackageNameResolver +// never change type traits because it's used in CFMA +var DefaultPackageNameResolver PackageNameResolver + +func FindPackageName(metadata *C.Metadata) (string, error) { + if resolver := DefaultPackageNameResolver; resolver != nil { + return resolver(metadata) + } + return "", ErrPlatformNotSupport +} diff --git a/clash-meta-android/core/src/foss/golang/clash/component/process/process_android.go b/clash-meta-android/core/src/foss/golang/clash/component/process/process_android.go deleted file mode 100644 index fd5d3b6cba..0000000000 --- a/clash-meta-android/core/src/foss/golang/clash/component/process/process_android.go +++ /dev/null @@ -1,16 +0,0 @@ -//go:build android && cmfa - -package process - -import "github.com/metacubex/mihomo/constant" - -type PackageNameResolver func(metadata *constant.Metadata) (string, error) - -var DefaultPackageNameResolver PackageNameResolver - -func FindPackageName(metadata *constant.Metadata) (string, error) { - if resolver := DefaultPackageNameResolver; resolver != nil { - return resolver(metadata) - } - return "", ErrPlatformNotSupport -} diff --git a/clash-meta-android/core/src/foss/golang/clash/component/process/process_common.go b/clash-meta-android/core/src/foss/golang/clash/component/process/process_common.go deleted file mode 100644 index fa7eeb9fc8..0000000000 --- a/clash-meta-android/core/src/foss/golang/clash/component/process/process_common.go +++ /dev/null @@ -1,9 +0,0 @@ -//go:build !(android && cmfa) - -package process - -import "github.com/metacubex/mihomo/constant" - -func FindPackageName(metadata *constant.Metadata) (string, error) { - return "", nil -} diff --git a/clash-meta-android/core/src/foss/golang/clash/component/process/process_darwin.go b/clash-meta-android/core/src/foss/golang/clash/component/process/process_darwin.go index 67d2e8332b..c02771ed78 100644 --- a/clash-meta-android/core/src/foss/golang/clash/component/process/process_darwin.go +++ b/clash-meta-android/core/src/foss/golang/clash/component/process/process_darwin.go @@ -46,12 +46,12 @@ func findProcessName(network string, ip netip.Addr, port int) (uint32, string, e isIPv4 := ip.Is4() - value, err := syscall.Sysctl(spath) + value, err := unix.SysctlRaw(spath) if err != nil { return 0, "", err } - buf := []byte(value) + buf := value itemSize := structSize if network == TCP { // rup8(sizeof(xtcpcb_n)) diff --git a/clash-meta-android/core/src/foss/golang/clash/component/process/process_linux.go b/clash-meta-android/core/src/foss/golang/clash/component/process/process_linux.go index 45c89e5a5a..3ce45ae816 100644 --- a/clash-meta-android/core/src/foss/golang/clash/component/process/process_linux.go +++ b/clash-meta-android/core/src/foss/golang/clash/component/process/process_linux.go @@ -2,23 +2,19 @@ package process import ( "bytes" - "context" "encoding/binary" "fmt" "net/netip" "os" + "path" "path/filepath" "runtime" "strings" - "sync" "syscall" "unicode" "unsafe" - "github.com/metacubex/mihomo/log" - "github.com/mdlayher/netlink" - tun "github.com/metacubex/sing-tun" "golang.org/x/sys/unix" ) @@ -63,25 +59,11 @@ type inetDiagResponse struct { INode uint32 } -type MyCallback struct{} - -var ( - packageManager tun.PackageManager - once sync.Once -) - -func (cb *MyCallback) OnPackagesUpdated(packageCount int, sharedCount int) {} - -func (cb *MyCallback) NewError(ctx context.Context, err error) { - log.Warnln("%s", err) -} - func findProcessName(network string, ip netip.Addr, srcPort int) (uint32, string, error) { uid, inode, err := resolveSocketByNetlink(network, ip, srcPort) if err != nil { return 0, "", err } - pp, err := resolveProcessNameByProcSearch(inode, uid) return uid, pp, err } @@ -177,44 +159,38 @@ func resolveProcessNameByProcSearch(inode, uid uint32) (string, error) { if err != nil { continue } + if runtime.GOOS == "android" { if bytes.Equal(buffer[:n], socket) { - return findPackageName(uid), nil + cmdline, err := os.ReadFile(path.Join(processPath, "cmdline")) + if err != nil { + return "", err + } + + return splitCmdline(cmdline), nil } } else { if bytes.Equal(buffer[:n], socket) { return os.Readlink(filepath.Join(processPath, "exe")) } } - } } return "", fmt.Errorf("process of uid(%d),inode(%d) not found", uid, inode) } -func findPackageName(uid uint32) string { - once.Do(func() { - callback := &MyCallback{} - var err error - packageManager, err = tun.NewPackageManager(callback) - if err != nil { - log.Warnln("%s", err) - } - err = packageManager.Start() - if err != nil { - log.Warnln("%s", err) - return - } +func splitCmdline(cmdline []byte) string { + cmdline = bytes.Trim(cmdline, " ") + + idx := bytes.IndexFunc(cmdline, func(r rune) bool { + return unicode.IsControl(r) || unicode.IsSpace(r) }) - if sharedPackage, loaded := packageManager.SharedPackageByID(uid % 100000); loaded { - return sharedPackage + if idx == -1 { + return filepath.Base(string(cmdline)) } - if packageName, loaded := packageManager.PackageByID(uid % 100000); loaded { - return packageName - } - return "" + return filepath.Base(string(cmdline[:idx])) } func isPid(s string) bool { diff --git a/clash-meta-android/core/src/foss/golang/clash/component/resource/fetcher.go b/clash-meta-android/core/src/foss/golang/clash/component/resource/fetcher.go index e62912937d..c82a54a3d1 100644 --- a/clash-meta-android/core/src/foss/golang/clash/component/resource/fetcher.go +++ b/clash-meta-android/core/src/foss/golang/clash/component/resource/fetcher.go @@ -10,6 +10,7 @@ import ( types "github.com/metacubex/mihomo/constant/provider" "github.com/metacubex/mihomo/log" + "github.com/sagernet/fswatch" "github.com/samber/lo" ) @@ -30,6 +31,7 @@ type Fetcher[V any] struct { parser Parser[V] interval time.Duration OnUpdate func(V) + watcher *fswatch.Watcher } func (f *Fetcher[V]) Name() string { @@ -113,7 +115,20 @@ func (f *Fetcher[V]) Initial() (V, error) { f.hash = md5.Sum(buf) // pull contents automatically - if f.interval > 0 { + if f.vehicle.Type() == types.File { + f.watcher, err = fswatch.NewWatcher(fswatch.Options{ + Path: []string{f.vehicle.Path()}, + Direct: true, + Callback: f.update, + }) + if err != nil { + return lo.Empty[V](), err + } + err = f.watcher.Start() + if err != nil { + return lo.Empty[V](), err + } + } else if f.interval > 0 { go f.pullLoop() } @@ -155,6 +170,9 @@ func (f *Fetcher[V]) Destroy() error { if f.interval > 0 { f.done <- struct{}{} } + if f.watcher != nil { + _ = f.watcher.Close() + } return nil } @@ -170,27 +188,31 @@ func (f *Fetcher[V]) pullLoop() { select { case <-timer.C: timer.Reset(f.interval) - elm, same, err := f.Update() - if err != nil { - log.Errorln("[Provider] %s pull error: %s", f.Name(), err.Error()) - continue - } - - if same { - log.Debugln("[Provider] %s's content doesn't change", f.Name()) - continue - } - - log.Infoln("[Provider] %s's content update", f.Name()) - if f.OnUpdate != nil { - f.OnUpdate(elm) - } + f.update(f.vehicle.Path()) case <-f.done: return } } } +func (f *Fetcher[V]) update(path string) { + elm, same, err := f.Update() + if err != nil { + log.Errorln("[Provider] %s pull error: %s", f.Name(), err.Error()) + return + } + + if same { + log.Debugln("[Provider] %s's content doesn't change", f.Name()) + return + } + + log.Infoln("[Provider] %s's content update", f.Name()) + if f.OnUpdate != nil { + f.OnUpdate(elm) + } +} + func safeWrite(path string, buf []byte) error { dir := filepath.Dir(path) diff --git a/clash-meta-android/core/src/foss/golang/clash/component/sniffer/dispatcher.go b/clash-meta-android/core/src/foss/golang/clash/component/sniffer/dispatcher.go index 97bf162969..4438638dad 100644 --- a/clash-meta-android/core/src/foss/golang/clash/component/sniffer/dispatcher.go +++ b/clash-meta-android/core/src/foss/golang/clash/component/sniffer/dispatcher.go @@ -5,7 +5,6 @@ import ( "fmt" "net" "net/netip" - "sync" "time" "github.com/metacubex/mihomo/common/lru" @@ -30,7 +29,6 @@ type SnifferDispatcher struct { forceDomain *trie.DomainSet skipSNI *trie.DomainSet skipList *lru.LruCache[string, uint8] - rwMux sync.RWMutex forceDnsMapping bool parsePureIp bool } @@ -85,14 +83,11 @@ func (sd *SnifferDispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata return false } - sd.rwMux.RLock() dst := fmt.Sprintf("%s:%d", metadata.DstIP, metadata.DstPort) if count, ok := sd.skipList.Get(dst); ok && count > 5 { log.Debugln("[Sniffer] Skip sniffing[%s] due to multiple failures", dst) - defer sd.rwMux.RUnlock() return false } - sd.rwMux.RUnlock() if host, err := sd.sniffDomain(conn, metadata); err != nil { sd.cacheSniffFailed(metadata) @@ -104,9 +99,7 @@ func (sd *SnifferDispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata return false } - sd.rwMux.RLock() sd.skipList.Delete(dst) - sd.rwMux.RUnlock() sd.replaceDomain(metadata, host, overrideDest) return true @@ -176,14 +169,13 @@ func (sd *SnifferDispatcher) sniffDomain(conn *N.BufferedConn, metadata *C.Metad } func (sd *SnifferDispatcher) cacheSniffFailed(metadata *C.Metadata) { - sd.rwMux.Lock() dst := fmt.Sprintf("%s:%d", metadata.DstIP, metadata.DstPort) - count, _ := sd.skipList.Get(dst) - if count <= 5 { - count++ - } - sd.skipList.Set(dst, count) - sd.rwMux.Unlock() + sd.skipList.Compute(dst, func(oldValue uint8, loaded bool) (newValue uint8, delete bool) { + if oldValue <= 5 { + oldValue++ + } + return oldValue, false + }) } func NewCloseSnifferDispatcher() (*SnifferDispatcher, error) { diff --git a/clash-meta-android/core/src/foss/golang/clash/component/trie/domain.go b/clash-meta-android/core/src/foss/golang/clash/component/trie/domain.go index 3decbb0255..db30402ede 100644 --- a/clash-meta-android/core/src/foss/golang/clash/component/trie/domain.go +++ b/clash-meta-android/core/src/foss/golang/clash/component/trie/domain.go @@ -123,16 +123,18 @@ func (t *DomainTrie[T]) Optimize() { t.root.optimize() } -func (t *DomainTrie[T]) Foreach(print func(domain string, data T)) { +func (t *DomainTrie[T]) Foreach(fn func(domain string, data T) bool) { for key, data := range t.root.getChildren() { - recursion([]string{key}, data, print) + recursion([]string{key}, data, fn) if data != nil && data.inited { - print(joinDomain([]string{key}), data.data) + if !fn(joinDomain([]string{key}), data.data) { + return + } } } } -func recursion[T any](items []string, node *Node[T], fn func(domain string, data T)) { +func recursion[T any](items []string, node *Node[T], fn func(domain string, data T) bool) bool { for key, data := range node.getChildren() { newItems := append([]string{key}, items...) if data != nil && data.inited { @@ -140,10 +142,15 @@ func recursion[T any](items []string, node *Node[T], fn func(domain string, data if domain[0] == domainStepByte { domain = complexWildcard + domain } - fn(domain, data.Data()) + if !fn(domain, data.Data()) { + return false + } + } + if !recursion(newItems, data, fn) { + return false } - recursion(newItems, data, fn) } + return true } func joinDomain(items []string) string { diff --git a/clash-meta-android/core/src/foss/golang/clash/component/trie/domain_set.go b/clash-meta-android/core/src/foss/golang/clash/component/trie/domain_set.go index 860d1235d5..7778d13379 100644 --- a/clash-meta-android/core/src/foss/golang/clash/component/trie/domain_set.go +++ b/clash-meta-android/core/src/foss/golang/clash/component/trie/domain_set.go @@ -28,8 +28,9 @@ type qElt struct{ s, e, col int } // NewDomainSet creates a new *DomainSet struct, from a DomainTrie. func (t *DomainTrie[T]) NewDomainSet() *DomainSet { reserveDomains := make([]string, 0) - t.Foreach(func(domain string, data T) { + t.Foreach(func(domain string, data T) bool { reserveDomains = append(reserveDomains, utils.Reverse(domain)) + return true }) // ensure that the same prefix is continuous // and according to the ascending sequence of length @@ -136,6 +137,41 @@ func (ss *DomainSet) Has(key string) bool { } +func (ss *DomainSet) keys(f func(key string) bool) { + var currentKey []byte + var traverse func(int, int) bool + traverse = func(nodeId, bmIdx int) bool { + if getBit(ss.leaves, nodeId) != 0 { + if !f(string(currentKey)) { + return false + } + } + + for ; ; bmIdx++ { + if getBit(ss.labelBitmap, bmIdx) != 0 { + return true + } + nextLabel := ss.labels[bmIdx-nodeId] + currentKey = append(currentKey, nextLabel) + nextNodeId := countZeros(ss.labelBitmap, ss.ranks, bmIdx+1) + nextBmIdx := selectIthOne(ss.labelBitmap, ss.ranks, ss.selects, nextNodeId-1) + 1 + if !traverse(nextNodeId, nextBmIdx) { + return false + } + currentKey = currentKey[:len(currentKey)-1] + } + } + + traverse(0, 0) + return +} + +func (ss *DomainSet) Foreach(f func(key string) bool) { + ss.keys(func(key string) bool { + return f(utils.Reverse(key)) + }) +} + func setBit(bm *[]uint64, i int, v int) { for i>>6 >= len(*bm) { *bm = append(*bm, 0) diff --git a/clash-meta-android/core/src/foss/golang/clash/component/trie/domain_set_bin.go b/clash-meta-android/core/src/foss/golang/clash/component/trie/domain_set_bin.go new file mode 100644 index 0000000000..27d15802e0 --- /dev/null +++ b/clash-meta-android/core/src/foss/golang/clash/component/trie/domain_set_bin.go @@ -0,0 +1,115 @@ +package trie + +import ( + "encoding/binary" + "errors" + "io" +) + +func (ss *DomainSet) WriteBin(w io.Writer) (err error) { + // version + _, err = w.Write([]byte{1}) + if err != nil { + return err + } + + // leaves + err = binary.Write(w, binary.BigEndian, int64(len(ss.leaves))) + if err != nil { + return err + } + for _, d := range ss.leaves { + err = binary.Write(w, binary.BigEndian, d) + if err != nil { + return err + } + } + + // labelBitmap + err = binary.Write(w, binary.BigEndian, int64(len(ss.labelBitmap))) + if err != nil { + return err + } + for _, d := range ss.labelBitmap { + err = binary.Write(w, binary.BigEndian, d) + if err != nil { + return err + } + } + + // labels + err = binary.Write(w, binary.BigEndian, int64(len(ss.labels))) + if err != nil { + return err + } + _, err = w.Write(ss.labels) + if err != nil { + return err + } + + return nil +} + +func ReadDomainSetBin(r io.Reader) (ds *DomainSet, err error) { + // version + version := make([]byte, 1) + _, err = io.ReadFull(r, version) + if err != nil { + return nil, err + } + if version[0] != 1 { + return nil, errors.New("version is invalid") + } + + ds = &DomainSet{} + var length int64 + + // leaves + err = binary.Read(r, binary.BigEndian, &length) + if err != nil { + return nil, err + } + if length < 1 { + return nil, errors.New("length is invalid") + } + ds.leaves = make([]uint64, length) + for i := int64(0); i < length; i++ { + err = binary.Read(r, binary.BigEndian, &ds.leaves[i]) + if err != nil { + return nil, err + } + } + + // labelBitmap + err = binary.Read(r, binary.BigEndian, &length) + if err != nil { + return nil, err + } + if length < 1 { + return nil, errors.New("length is invalid") + } + ds.labelBitmap = make([]uint64, length) + for i := int64(0); i < length; i++ { + err = binary.Read(r, binary.BigEndian, &ds.labelBitmap[i]) + if err != nil { + return nil, err + } + } + + // labels + err = binary.Read(r, binary.BigEndian, &length) + if err != nil { + return nil, err + } + if length < 1 { + return nil, errors.New("length is invalid") + } + ds.labels = make([]byte, length) + _, err = io.ReadFull(r, ds.labels) + if err != nil { + return nil, err + } + + ds.init() + return ds, nil +} diff --git a/clash-meta-android/core/src/foss/golang/clash/component/trie/domain_set_test.go b/clash-meta-android/core/src/foss/golang/clash/component/trie/domain_set_test.go index 77106d5ffc..e343d11d1c 100644 --- a/clash-meta-android/core/src/foss/golang/clash/component/trie/domain_set_test.go +++ b/clash-meta-android/core/src/foss/golang/clash/component/trie/domain_set_test.go @@ -1,12 +1,29 @@ package trie_test import ( + "golang.org/x/exp/slices" "testing" "github.com/metacubex/mihomo/component/trie" "github.com/stretchr/testify/assert" ) +func testDump(t *testing.T, tree *trie.DomainTrie[struct{}], set *trie.DomainSet) { + var dataSrc []string + tree.Foreach(func(domain string, data struct{}) bool { + dataSrc = append(dataSrc, domain) + return true + }) + slices.Sort(dataSrc) + var dataSet []string + set.Foreach(func(key string) bool { + dataSet = append(dataSet, key) + return true + }) + slices.Sort(dataSet) + assert.Equal(t, dataSrc, dataSet) +} + func TestDomainSet(t *testing.T) { tree := trie.New[struct{}]() domainSet := []string{ @@ -33,6 +50,7 @@ func TestDomainSet(t *testing.T) { assert.True(t, set.Has("google.com")) assert.False(t, set.Has("qq.com")) assert.False(t, set.Has("www.baidu.com")) + testDump(t, tree, set) } func TestDomainSetComplexWildcard(t *testing.T) { @@ -55,6 +73,7 @@ func TestDomainSetComplexWildcard(t *testing.T) { assert.False(t, set.Has("google.com")) assert.True(t, set.Has("www.baidu.com")) assert.True(t, set.Has("test.test.baidu.com")) + testDump(t, tree, set) } func TestDomainSetWildcard(t *testing.T) { @@ -82,4 +101,5 @@ func TestDomainSetWildcard(t *testing.T) { assert.False(t, set.Has("a.www.google.com")) assert.False(t, set.Has("test.qq.com")) assert.False(t, set.Has("test.test.test.qq.com")) + testDump(t, tree, set) } diff --git a/clash-meta-android/core/src/foss/golang/clash/component/trie/domain_test.go b/clash-meta-android/core/src/foss/golang/clash/component/trie/domain_test.go index 4c5d8002d8..916f61076d 100644 --- a/clash-meta-android/core/src/foss/golang/clash/component/trie/domain_test.go +++ b/clash-meta-android/core/src/foss/golang/clash/component/trie/domain_test.go @@ -121,8 +121,9 @@ func TestTrie_Foreach(t *testing.T) { assert.NoError(t, tree.Insert(domain, localIP)) } count := 0 - tree.Foreach(func(domain string, data netip.Addr) { + tree.Foreach(func(domain string, data netip.Addr) bool { count++ + return true }) assert.Equal(t, 7, count) } 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 5676f7aae4..ecd94c7dd9 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 @@ -42,6 +42,7 @@ import ( T "github.com/metacubex/mihomo/tunnel" orderedmap "github.com/wk8/go-ordered-map/v2" + "golang.org/x/exp/slices" "gopkg.in/yaml.v3" ) @@ -96,6 +97,7 @@ type Controller struct { ExternalControllerTLS string `json:"-"` ExternalControllerUnix string `json:"-"` ExternalUI string `json:"-"` + ExternalDohServer string `json:"-"` Secret string `json:"-"` } @@ -322,6 +324,7 @@ type RawConfig struct { ExternalUI string `yaml:"external-ui"` ExternalUIURL string `yaml:"external-ui-url" json:"external-ui-url"` ExternalUIName string `yaml:"external-ui-name" json:"external-ui-name"` + ExternalDohServer string `yaml:"external-doh-server"` Secret string `yaml:"secret"` Interface string `yaml:"interface-name"` RoutingMark int `yaml:"routing-mark"` @@ -504,7 +507,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) { }, Sniffer: RawSniffer{ Enable: false, - Sniffing: []string{}, + Sniff: map[string]RawSniffingConfig{}, ForceDomain: []string{}, SkipDomain: []string{}, Ports: []string{}, @@ -697,6 +700,7 @@ func parseGeneral(cfg *RawConfig) (*General, error) { Secret: cfg.Secret, ExternalControllerUnix: cfg.ExternalControllerUnix, ExternalControllerTLS: cfg.ExternalControllerTLS, + ExternalDohServer: cfg.ExternalDohServer, }, UnifiedDelay: cfg.UnifiedDelay, Mode: cfg.Mode, @@ -789,6 +793,9 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[ AllProviders = append(AllProviders, name) } + slices.Sort(AllProxies) + slices.Sort(AllProviders) + // parse proxy group for idx, mapping := range groupsConfig { group, err := outboundgroup.ParseProxyGroup(mapping, proxies, providersMap, AllProxies, AllProviders) @@ -1087,13 +1094,16 @@ func parseNameServer(servers []string, respectRules bool, preferH3 bool) ([]dns. case "tls": addr, err = hostWithDefaultPort(u.Host, "853") dnsNetType = "tcp-tls" // DNS over TLS - case "https": + case "http", "https": addr, err = hostWithDefaultPort(u.Host, "443") + dnsNetType = "https" // DNS over HTTPS + if u.Scheme == "http" { + addr, err = hostWithDefaultPort(u.Host, "80") + } if err == nil { proxyName = "" - clearURL := url.URL{Scheme: "https", Host: addr, Path: u.Path, User: u.User} + clearURL := url.URL{Scheme: u.Scheme, Host: addr, Path: u.Path, User: u.User} addr = clearURL.String() - dnsNetType = "https" // DNS over HTTPS if len(u.Fragment) != 0 { for _, s := range strings.Split(u.Fragment, "&") { arr := strings.Split(s, "=") @@ -1566,7 +1576,7 @@ func parseSniffer(snifferRaw RawSniffer) (*Sniffer, error) { } } } else { - if sniffer.Enable { + if sniffer.Enable && len(snifferRaw.Sniffing) != 0 { // Deprecated: Use Sniff instead log.Warnln("Deprecated: Use Sniff instead") } diff --git a/clash-meta-android/core/src/foss/golang/clash/constant/provider/interface.go b/clash-meta-android/core/src/foss/golang/clash/constant/provider/interface.go index f7dfc9cc60..bd6b6e9470 100644 --- a/clash-meta-android/core/src/foss/golang/clash/constant/provider/interface.go +++ b/clash-meta-android/core/src/foss/golang/clash/constant/provider/interface.go @@ -1,6 +1,8 @@ package provider import ( + "fmt" + "github.com/metacubex/mihomo/common/utils" "github.com/metacubex/mihomo/constant" ) @@ -110,9 +112,37 @@ func (rt RuleBehavior) String() string { } } +func (rt RuleBehavior) Byte() byte { + switch rt { + case Domain: + return 0 + case IPCIDR: + return 1 + case Classical: + return 2 + default: + return 255 + } +} + +func ParseBehavior(s string) (behavior RuleBehavior, err error) { + switch s { + case "domain": + behavior = Domain + case "ipcidr": + behavior = IPCIDR + case "classical": + behavior = Classical + default: + err = fmt.Errorf("unsupported behavior type: %s", s) + } + return +} + const ( YamlRule RuleFormat = iota TextRule + MrsRule ) type RuleFormat int @@ -123,11 +153,27 @@ func (rf RuleFormat) String() string { return "YamlRule" case TextRule: return "TextRule" + case MrsRule: + return "MrsRule" default: return "Unknown" } } +func ParseRuleFormat(s string) (format RuleFormat, err error) { + switch s { + case "", "yaml": + format = YamlRule + case "text": + format = TextRule + case "mrs": + format = MrsRule + default: + err = fmt.Errorf("unsupported format type: %s", s) + } + return +} + type Tunnel interface { Providers() map[string]ProxyProvider RuleProviders() map[string]RuleProvider diff --git a/clash-meta-android/core/src/foss/golang/clash/dns/doh.go b/clash-meta-android/core/src/foss/golang/clash/dns/doh.go index 54b8279657..97e01ea720 100644 --- a/clash-meta-android/core/src/foss/golang/clash/dns/doh.go +++ b/clash-meta-android/core/src/foss/golang/clash/dns/doh.go @@ -61,10 +61,12 @@ type dnsOverHTTPS struct { // for this upstream. quicConfig *quic.Config quicConfigGuard sync.Mutex - url *url.URL - httpVersions []C.HTTPVersion - dialer *dnsDialer - addr string + + url *url.URL + httpVersions []C.HTTPVersion + dialer *dnsDialer + addr string + skipCertVerify bool } // type check @@ -93,6 +95,10 @@ func newDoHClient(urlString string, r *Resolver, preferH3 bool, params map[strin httpVersions: httpVersions, } + if params["skip-cert-verify"] == "true" { + doh.skipCertVerify = true + } + runtime.SetFinalizer(doh, (*dnsOverHTTPS).Close) return doh @@ -102,6 +108,7 @@ func newDoHClient(urlString string, r *Resolver, preferH3 bool, params map[strin func (doh *dnsOverHTTPS) Address() string { return doh.addr } + func (doh *dnsOverHTTPS) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) { // Quote from https://www.rfc-editor.org/rfc/rfc8484.html: // In order to maximize HTTP cache friendliness, DoH clients using media @@ -178,19 +185,9 @@ func (doh *dnsOverHTTPS) closeClient(client *http.Client) (err error) { return nil } -// exchangeHTTPS logs the request and its result and calls exchangeHTTPSClient. -func (doh *dnsOverHTTPS) exchangeHTTPS(ctx context.Context, client *http.Client, req *D.Msg) (resp *D.Msg, err error) { - resp, err = doh.exchangeHTTPSClient(ctx, client, req) - return resp, err -} - -// exchangeHTTPSClient sends the DNS query to a DoH resolver using the specified +// exchangeHTTPS sends the DNS query to a DoH resolver using the specified // http.Client instance. -func (doh *dnsOverHTTPS) exchangeHTTPSClient( - ctx context.Context, - client *http.Client, - req *D.Msg, -) (resp *D.Msg, err error) { +func (doh *dnsOverHTTPS) exchangeHTTPS(ctx context.Context, client *http.Client, req *D.Msg) (resp *D.Msg, err error) { buf, err := req.Pack() if err != nil { return nil, fmt.Errorf("packing message: %w", err) @@ -204,24 +201,24 @@ func (doh *dnsOverHTTPS) exchangeHTTPSClient( method = http3.MethodGet0RTT } - url := doh.url - url.RawQuery = fmt.Sprintf("dns=%s", base64.RawURLEncoding.EncodeToString(buf)) - httpReq, err := http.NewRequestWithContext(ctx, method, url.String(), nil) + requestUrl := *doh.url // don't modify origin url + requestUrl.RawQuery = fmt.Sprintf("dns=%s", base64.RawURLEncoding.EncodeToString(buf)) + httpReq, err := http.NewRequestWithContext(ctx, method, requestUrl.String(), nil) if err != nil { - return nil, fmt.Errorf("creating http request to %s: %w", url, err) + return nil, fmt.Errorf("creating http request to %s: %w", doh.url, err) } httpReq.Header.Set("Accept", "application/dns-message") httpReq.Header.Set("User-Agent", "") httpResp, err := client.Do(httpReq) if err != nil { - return nil, fmt.Errorf("requesting %s: %w", url, err) + return nil, fmt.Errorf("requesting %s: %w", doh.url, err) } defer httpResp.Body.Close() body, err := io.ReadAll(httpResp.Body) if err != nil { - return nil, fmt.Errorf("reading %s: %w", url, err) + return nil, fmt.Errorf("reading %s: %w", doh.url, err) } if httpResp.StatusCode != http.StatusOK { @@ -230,7 +227,7 @@ func (doh *dnsOverHTTPS) exchangeHTTPSClient( "expected status %d, got %d from %s", http.StatusOK, httpResp.StatusCode, - url, + doh.url, ) } @@ -239,7 +236,7 @@ func (doh *dnsOverHTTPS) exchangeHTTPSClient( if err != nil { return nil, fmt.Errorf( "unpacking response from %s: body is %s: %w", - url, + doh.url, body, err, ) @@ -373,9 +370,21 @@ func (doh *dnsOverHTTPS) createClient(ctx context.Context) (*http.Client, error) // HTTP3 is enabled in the upstream options). If this attempt is successful, // it returns an HTTP3 transport, otherwise it returns the H1/H2 transport. func (doh *dnsOverHTTPS) createTransport(ctx context.Context) (t http.RoundTripper, err error) { + transport := &http.Transport{ + DisableCompression: true, + DialContext: doh.dialer.DialContext, + IdleConnTimeout: transportDefaultIdleConnTimeout, + MaxConnsPerHost: dohMaxConnsPerHost, + MaxIdleConns: dohMaxIdleConns, + } + + if doh.url.Scheme == "http" { + return transport, nil + } + tlsConfig := ca.GetGlobalTLSConfig( &tls.Config{ - InsecureSkipVerify: false, + InsecureSkipVerify: doh.skipCertVerify, MinVersion: tls.VersionTLS12, SessionTicketsDisabled: false, }) @@ -384,6 +393,7 @@ func (doh *dnsOverHTTPS) createTransport(ctx context.Context) (t http.RoundTripp nextProtos = append(nextProtos, string(v)) } tlsConfig.NextProtos = nextProtos + transport.TLSClientConfig = tlsConfig if slices.Contains(doh.httpVersions, C.HTTPVersion3) { // First, we attempt to create an HTTP3 transport. If the probe QUIC @@ -402,18 +412,10 @@ func (doh *dnsOverHTTPS) createTransport(ctx context.Context) (t http.RoundTripp return nil, errors.New("HTTP1/1 and HTTP2 are not supported by this upstream") } - transport := &http.Transport{ - TLSClientConfig: tlsConfig, - DisableCompression: true, - DialContext: doh.dialer.DialContext, - IdleConnTimeout: transportDefaultIdleConnTimeout, - MaxConnsPerHost: dohMaxConnsPerHost, - MaxIdleConns: dohMaxIdleConns, - // Since we have a custom DialContext, we need to use this field to - // make golang http.Client attempt to use HTTP/2. Otherwise, it would - // only be used when negotiated on the TLS level. - ForceAttemptHTTP2: true, - } + // Since we have a custom DialContext, we need to use this field to + // make golang http.Client attempt to use HTTP/2. Otherwise, it would + // only be used when negotiated on the TLS level. + transport.ForceAttemptHTTP2 = true // Explicitly configure transport to use HTTP/2. // diff --git a/clash-meta-android/core/src/foss/golang/clash/dns/resolver.go b/clash-meta-android/core/src/foss/golang/clash/dns/resolver.go index 28ffec6f15..fc228761a3 100644 --- a/clash-meta-android/core/src/foss/golang/clash/dns/resolver.go +++ b/clash-meta-android/core/src/foss/golang/clash/dns/resolver.go @@ -146,9 +146,12 @@ func (r *Resolver) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, e }() q := m.Question[0] + domain := msgToDomain(m) + _, qTypeStr := msgToQtype(m) cacheM, expireTime, hit := r.cache.GetWithExpire(q.String()) if hit { - log.Debugln("[DNS] cache hit for %s, expire at %s", q.Name, expireTime.Format("2006-01-02 15:04:05")) + ips := msgToIP(cacheM) + log.Debugln("[DNS] cache hit %s --> %s %s, expire at %s", domain, ips, qTypeStr, expireTime.Format("2006-01-02 15:04:05")) now := time.Now() msg = cacheM.Copy() if expireTime.Before(now) { diff --git a/clash-meta-android/core/src/foss/golang/clash/dns/util.go b/clash-meta-android/core/src/foss/golang/clash/dns/util.go index e4ec5917cf..50459cc120 100644 --- a/clash-meta-android/core/src/foss/golang/clash/dns/util.go +++ b/clash-meta-android/core/src/foss/golang/clash/dns/util.go @@ -173,11 +173,20 @@ func msgToDomain(msg *D.Msg) string { return "" } +func msgToQtype(msg *D.Msg) (uint16, string) { + if len(msg.Question) > 0 { + qType := msg.Question[0].Qtype + return qType, D.Type(qType).String() + } + return 0, "" +} + func batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.Msg, cache bool, err error) { cache = true fast, ctx := picker.WithTimeout[*D.Msg](ctx, resolver.DefaultDNSTimeout) defer fast.Close() domain := msgToDomain(m) + qType, qTypeStr := msgToQtype(m) var noIpMsg *D.Msg for _, client := range clients { if _, isRCodeClient := client.(rcodeClient); isRCodeClient { @@ -186,7 +195,7 @@ func batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.M } client := client // shadow define client to ensure the value captured by the closure will not be changed in the next loop fast.Go(func() (*D.Msg, error) { - log.Debugln("[DNS] resolve %s from %s", domain, client.Address()) + log.Debugln("[DNS] resolve %s %s from %s", domain, qTypeStr, client.Address()) m, err := client.ExchangeContext(ctx, m) if err != nil { return nil, err @@ -195,20 +204,18 @@ func batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.M // so we would ignore RCode errors from RCode clients. return nil, errors.New("server failure: " + D.RcodeToString[m.Rcode]) } - if ips := msgToIP(m); len(m.Question) > 0 { - qType := m.Question[0].Qtype - log.Debugln("[DNS] %s --> %s %s from %s", domain, ips, D.Type(qType), client.Address()) - switch qType { - case D.TypeAAAA: - if len(ips) == 0 { - noIpMsg = m - return nil, resolver.ErrIPNotFound - } - case D.TypeA: - if len(ips) == 0 { - noIpMsg = m - return nil, resolver.ErrIPNotFound - } + ips := msgToIP(m) + log.Debugln("[DNS] %s --> %s %s from %s", domain, ips, qTypeStr, client.Address()) + switch qType { + case D.TypeAAAA: + if len(ips) == 0 { + noIpMsg = m + return nil, resolver.ErrIPNotFound + } + case D.TypeA: + if len(ips) == 0 { + noIpMsg = m + return nil, resolver.ErrIPNotFound } } return m, nil 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 9c51bc10b3..d7c686d01f 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 @@ -70,6 +70,10 @@ external-ui: /path/to/ui/folder/ external-ui-name: xd external-ui-url: "https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip" +# 在RESTful API端口上开启DOH服务器 +# !!!该URL不会验证secret, 如果开启请自行保证安全问题 !!! +external-doh-server: /dns-query + # interface-name: en0 # 设置出口网卡 # 全局 TLS 指纹,优先低于 proxy 内的 client-fingerprint @@ -938,6 +942,24 @@ rule-providers: interval: 259200 path: /path/to/save/file.yaml type: file + rule3: + # mrs类型ruleset,目前仅支持domain和ipcidr(即不支持classical), + # + # 对于behavior=domain: + # - format=yaml 可以通过“mihomo convert-ruleset domain yaml XXX.yaml XXX.mrs”转换到mrs格式 + # - format=text 可以通过“mihomo convert-ruleset domain text XXX.text XXX.mrs”转换到mrs格式 + # - XXX.mrs 可以通过"mihomo convert-ruleset domain mrs XXX.mrs XXX.text"转换回text格式(暂不支持转换回ymal格式) + # + # 对于behavior=ipcidr: + # - format=yaml 可以通过“mihomo convert-ruleset ipcidr yaml XXX.yaml XXX.mrs”转换到mrs格式 + # - format=text 可以通过“mihomo convert-ruleset ipcidr text XXX.text XXX.mrs”转换到mrs格式 + # - XXX.mrs 可以通过"mihomo convert-ruleset ipcidr mrs XXX.mrs XXX.text"转换回text格式(暂不支持转换回ymal格式) + # + type: http + url: "url" + format: mrs + behavior: domain + path: /path/to/save/file.mrs rules: - RULE-SET,rule1,REJECT - IP-ASN,1,PROXY 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 4a96d14208..d5ac6beed6 100644 --- a/clash-meta-android/core/src/foss/golang/clash/go.mod +++ b/clash-meta-android/core/src/foss/golang/clash/go.mod @@ -4,7 +4,6 @@ go 1.20 require ( github.com/3andne/restls-client-go v0.1.6 - github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da github.com/bahlo/generic-list-go v0.2.0 github.com/cilium/ebpf v0.12.3 github.com/coreos/go-iptables v0.7.0 @@ -15,17 +14,19 @@ require ( github.com/gobwas/ws v1.4.0 github.com/gofrs/uuid/v5 v5.2.0 github.com/insomniacslk/dhcp v0.0.0-20240529192340-51bc6136a0a6 + github.com/klauspost/compress v1.17.9 github.com/klauspost/cpuid/v2 v2.2.8 github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 github.com/mdlayher/netlink v1.7.2 + github.com/metacubex/chacha v0.1.0 github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 github.com/metacubex/quic-go v0.45.1-0.20240610004319-163fee60637e github.com/metacubex/randv2 v0.2.0 github.com/metacubex/sing-quic v0.0.0-20240518034124-7696d3f7da72 - github.com/metacubex/sing-shadowsocks v0.2.6 - github.com/metacubex/sing-shadowsocks2 v0.2.0 - github.com/metacubex/sing-tun v0.2.7-0.20240627012306-9d1f5fc0b45e - github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f + github.com/metacubex/sing-shadowsocks v0.2.7 + github.com/metacubex/sing-shadowsocks2 v0.2.1 + github.com/metacubex/sing-tun v0.2.7-0.20240719141246-19c49ac9589d + github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9 github.com/metacubex/sing-wireguard v0.0.0-20240618022557-a6efaa37127a github.com/metacubex/tfo-go v0.0.0-20240228025757-be1269474a66 github.com/metacubex/utls v1.6.6 @@ -35,8 +36,9 @@ require ( github.com/oschwald/maxminddb-golang v1.12.0 github.com/puzpuzpuz/xsync/v3 v3.2.0 github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a + github.com/sagernet/fswatch v0.1.1 github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a - github.com/sagernet/sing v0.5.0-alpha.10 + github.com/sagernet/sing v0.5.0-alpha.13 github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6 github.com/sagernet/sing-shadowtls v0.1.4 github.com/sagernet/wireguard-go v0.0.0-20231209092712-9a439356a62e @@ -45,13 +47,14 @@ require ( github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.9.0 github.com/wk8/go-ordered-map/v2 v2.1.8 + gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7 go.uber.org/automaxprocs v1.5.3 go4.org/netipx v0.0.0-20231129151722-fdeea329fbba golang.org/x/crypto v0.24.0 golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 golang.org/x/net v0.26.0 golang.org/x/sync v0.7.0 - golang.org/x/sys v0.21.0 + golang.org/x/sys v0.22.0 google.golang.org/protobuf v1.34.2 gopkg.in/yaml.v3 v3.0.1 lukechampine.com/blake3 v1.3.0 @@ -80,7 +83,6 @@ require ( github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect github.com/hashicorp/yamux v0.1.1 // indirect github.com/josharian/native v1.1.0 // indirect - github.com/klauspost/compress v1.17.4 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mdlayher/socket v0.4.1 // indirect @@ -111,4 +113,4 @@ require ( golang.org/x/tools v0.22.0 // indirect ) -replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20240617013425-3e3bd9dab6a2 +replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20240724044459-6f3cf5896297 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 29a5d385de..7e9cd5d8a9 100644 --- a/clash-meta-android/core/src/foss/golang/clash/go.sum +++ b/clash-meta-android/core/src/foss/golang/clash/go.sum @@ -5,8 +5,6 @@ github.com/RyuaNerin/go-krypto v1.2.4 h1:mXuNdK6M317aPV0llW6Xpjbo4moOlPF7Yxz4tb4 github.com/RyuaNerin/go-krypto v1.2.4/go.mod h1:QqCYkoutU3yInyD9INt2PGolVRsc3W4oraQadVGXJ/8= github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 h1:cDVUiFo+npB0ZASqnw4q90ylaVAbnYyx0JYqK4YcGok= github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344/go.mod h1:9pIqrY6SXNL8vjRQE5Hd/OL5GyK/9MrGUWs87z/eFfk= -github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= -github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= @@ -83,8 +81,8 @@ github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFF github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= -github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= -github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -99,6 +97,8 @@ github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/ github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= +github.com/metacubex/chacha v0.1.0 h1:tg9RSJ18NvL38cCWNyYH1eiG6qDCyyXIaTLQthon0sc= +github.com/metacubex/chacha v0.1.0/go.mod h1:Djn9bPZxLTXbJFSeyo0/qzEzQI+gUSSzttuzZM75GH8= github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI= github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88= github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec h1:HxreOiFTUrJXJautEo8rnE1uKTVGY8wtZepY1Tii/Nc= @@ -107,18 +107,18 @@ github.com/metacubex/quic-go v0.45.1-0.20240610004319-163fee60637e h1:bLYn3GuRvW github.com/metacubex/quic-go v0.45.1-0.20240610004319-163fee60637e/go.mod h1:Yza2H7Ax1rxWPUcJx0vW+oAt9EsPuSiyQFhFabUPzwU= github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiLs= github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY= -github.com/metacubex/sing v0.0.0-20240617013425-3e3bd9dab6a2 h1:N5tidgg/FRmkgPw/AjRwhLUinKDx/ODCSbvv9xqRoLM= -github.com/metacubex/sing v0.0.0-20240617013425-3e3bd9dab6a2/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/metacubex/sing v0.0.0-20240724044459-6f3cf5896297 h1:YG/JkwGPbca5rUtEMHIu8ZuqzR7BSVm1iqY8hNoMeMA= +github.com/metacubex/sing v0.0.0-20240724044459-6f3cf5896297/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/metacubex/sing-quic v0.0.0-20240518034124-7696d3f7da72 h1:Wr4g1HCb5Z/QIFwFiVNjO2qL+dRu25+Mdn9xtAZZ+ew= github.com/metacubex/sing-quic v0.0.0-20240518034124-7696d3f7da72/go.mod h1:g7Mxj7b7zm7YVqD975mk/hSmrb0A0G4bVvIMr2MMzn8= -github.com/metacubex/sing-shadowsocks v0.2.6 h1:6oEB3QcsFYnNiFeoevcXrCwJ3sAablwVSgtE9R3QeFQ= -github.com/metacubex/sing-shadowsocks v0.2.6/go.mod h1:zIkMeSnb8Mbf4hdqhw0pjzkn1d99YJ3JQm/VBg5WMTg= -github.com/metacubex/sing-shadowsocks2 v0.2.0 h1:hqwT/AfI5d5UdPefIzR6onGHJfDXs5zgOM5QSgaM/9A= -github.com/metacubex/sing-shadowsocks2 v0.2.0/go.mod h1:LCKF6j1P94zN8ZS+LXRK1gmYTVGB3squivBSXAFnOg8= -github.com/metacubex/sing-tun v0.2.7-0.20240627012306-9d1f5fc0b45e h1:o+zohxPRo45P35fS9u1zfdBgr+L/7S0ObGU6YjbVBIc= -github.com/metacubex/sing-tun v0.2.7-0.20240627012306-9d1f5fc0b45e/go.mod h1:WwJGbCx7bQcBzuQXiDOJvZH27R0kIjKNNlISIWsL6kM= -github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f h1:QjXrHKbTMBip/C+R79bvbfr42xH1gZl3uFb0RELdZiQ= -github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f/go.mod h1:olVkD4FChQ5gKMHG4ZzuD7+fMkJY1G8vwOKpRehjrmY= +github.com/metacubex/sing-shadowsocks v0.2.7 h1:9f3Dt2+71TNp0e202llA2ug5h/rkWs2EZxQ5IMpf+9g= +github.com/metacubex/sing-shadowsocks v0.2.7/go.mod h1:X3x88XtJpBxG0W0/ECOJL6Ib0SJ3xdniAkU/6/RMWU0= +github.com/metacubex/sing-shadowsocks2 v0.2.1 h1:XIZBXlazp8EEoPp1S0DViAhLkJakjQ2f+AOwwdKKNYg= +github.com/metacubex/sing-shadowsocks2 v0.2.1/go.mod h1:BhOug03a/RbI7y6hp6q+6ITM1dXjnLTmeWBHSTwvv2Q= +github.com/metacubex/sing-tun v0.2.7-0.20240719141246-19c49ac9589d h1:iYlepjRCYlPXtELupDL+pQjGqkCnQz4KQOfKImP9sog= +github.com/metacubex/sing-tun v0.2.7-0.20240719141246-19c49ac9589d/go.mod h1:olbEx9yVcaw5tHTNlRamRoxmMKcvDvcVS1YLnQGzvWE= +github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9 h1:OAXiCosqY8xKDp3pqTW3qbrCprZ1l6WkrXSFSCwyY4I= +github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9/go.mod h1:olVkD4FChQ5gKMHG4ZzuD7+fMkJY1G8vwOKpRehjrmY= github.com/metacubex/sing-wireguard v0.0.0-20240618022557-a6efaa37127a h1:NpSGclHJUYndUwBmyIpFBSoBVg8PoVX7QQKhYg0DjM0= github.com/metacubex/sing-wireguard v0.0.0-20240618022557-a6efaa37127a/go.mod h1:uY+BYb0UEknLrqvbGcwi9i++KgrKxsurysgI6G1Pveo= github.com/metacubex/tfo-go v0.0.0-20240228025757-be1269474a66 h1:as/aO/fM8nv4W4pOr9EETP6kV/Oaujk3fUNyQSJK61c= @@ -158,6 +158,8 @@ github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58 github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkkD2QgdTuzQG263YZ+2emfpeyGqW0= github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM= +github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs= +github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o= github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis= github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I= @@ -212,6 +214,8 @@ github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/ github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7 h1:UNrDfkQqiEYzdMlNsVvBYOAJWZjdktqFE9tQh5BT2+4= +gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7/go.mod h1:E+rxHvJG9H6PUdzq9NRG6csuLN3XUx98BfGOVWNYnXs= gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec h1:FpfFs4EhNehiVfzQttTuxanPIT43FtkkCFypIod8LHo= gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec/go.mod h1:BZ1RAoRPbCxum9Grlv5aeksu2H8BiKehBYooU2LFiOQ= go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= @@ -252,8 +256,9 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= diff --git a/clash-meta-android/core/src/foss/golang/clash/hub/hub.go b/clash-meta-android/core/src/foss/golang/clash/hub/hub.go index 38779e13b6..57c91aaef9 100644 --- a/clash-meta-android/core/src/foss/golang/clash/hub/hub.go +++ b/clash-meta-android/core/src/foss/golang/clash/hub/hub.go @@ -50,11 +50,12 @@ func Parse(options ...Option) error { if cfg.General.ExternalController != "" { go route.Start(cfg.General.ExternalController, cfg.General.ExternalControllerTLS, - cfg.General.Secret, cfg.TLS.Certificate, cfg.TLS.PrivateKey, cfg.General.LogLevel == log.DEBUG) + cfg.General.Secret, cfg.TLS.Certificate, cfg.TLS.PrivateKey, cfg.General.ExternalDohServer, + cfg.General.LogLevel == log.DEBUG) } if cfg.General.ExternalControllerUnix != "" { - go route.StartUnix(cfg.General.ExternalControllerUnix, cfg.General.LogLevel == log.DEBUG) + go route.StartUnix(cfg.General.ExternalControllerUnix, cfg.General.ExternalDohServer, cfg.General.LogLevel == log.DEBUG) } executor.ApplyConfig(cfg, true) diff --git a/clash-meta-android/core/src/foss/golang/clash/hub/route/doh.go b/clash-meta-android/core/src/foss/golang/clash/hub/route/doh.go new file mode 100644 index 0000000000..bb5416c436 --- /dev/null +++ b/clash-meta-android/core/src/foss/golang/clash/hub/route/doh.go @@ -0,0 +1,63 @@ +package route + +import ( + "context" + "encoding/base64" + "io" + "net/http" + + "github.com/metacubex/mihomo/component/resolver" + + "github.com/go-chi/render" +) + +func dohRouter() http.Handler { + return http.HandlerFunc(dohHandler) +} + +func dohHandler(w http.ResponseWriter, r *http.Request) { + if resolver.DefaultResolver == nil { + render.Status(r, http.StatusInternalServerError) + render.PlainText(w, r, "DNS section is disabled") + return + } + + var dnsData []byte + var err error + switch r.Method { + case "GET": + dnsData, err = base64.RawURLEncoding.DecodeString(r.URL.Query().Get("dns")) + case "POST": + if r.Header.Get("Content-Type") != "application/dns-message" { + render.Status(r, http.StatusInternalServerError) + render.PlainText(w, r, "invalid content-type") + return + } + reader := io.LimitReader(r.Body, 65535) // according to rfc8484, the maximum size of the DNS message is 65535 bytes + dnsData, err = io.ReadAll(reader) + _ = r.Body.Close() + default: + render.Status(r, http.StatusMethodNotAllowed) + render.PlainText(w, r, "method not allowed") + return + } + if err != nil { + render.Status(r, http.StatusInternalServerError) + render.PlainText(w, r, err.Error()) + return + } + + ctx, cancel := context.WithTimeout(context.Background(), resolver.DefaultDNSTimeout) + defer cancel() + + dnsData, err = resolver.RelayDnsPacket(ctx, dnsData, dnsData) + if err != nil { + render.Status(r, http.StatusInternalServerError) + render.PlainText(w, r, err.Error()) + return + } + + w.Header().Set("Content-Type", "application/dns-message") + w.WriteHeader(http.StatusOK) + _, _ = w.Write(dnsData) +} diff --git a/clash-meta-android/core/src/foss/golang/clash/hub/route/server.go b/clash-meta-android/core/src/foss/golang/clash/hub/route/server.go index c28782b9c3..165c7c6970 100644 --- a/clash-meta-android/core/src/foss/golang/clash/hub/route/server.go +++ b/clash-meta-android/core/src/foss/golang/clash/hub/route/server.go @@ -50,7 +50,7 @@ func SetUIPath(path string) { uiPath = C.Path.Resolve(path) } -func router(isDebug bool, withAuth bool) *chi.Mux { +func router(isDebug bool, withAuth bool, dohServer string) *chi.Mux { r := chi.NewRouter() corsM := cors.New(cors.Options{ AllowedOrigins: []string{"*"}, @@ -104,11 +104,15 @@ func router(isDebug bool, withAuth bool) *chi.Mux { }) }) } + if len(dohServer) > 0 && dohServer[0] == '/' { + r.Mount(dohServer, dohRouter()) + } + return r } func Start(addr string, tlsAddr string, secret string, - certificate, privateKey string, isDebug bool) { + certificate, privateKey string, dohServer string, isDebug bool) { if serverAddr != "" { return } @@ -133,7 +137,7 @@ func Start(addr string, tlsAddr string, secret string, serverAddr = l.Addr().String() log.Infoln("RESTful API tls listening at: %s", serverAddr) tlsServe := &http.Server{ - Handler: router(isDebug, true), + Handler: router(isDebug, true, dohServer), TLSConfig: &tls.Config{ Certificates: []tls.Certificate{c}, }, @@ -152,13 +156,13 @@ func Start(addr string, tlsAddr string, secret string, serverAddr = l.Addr().String() log.Infoln("RESTful API listening at: %s", serverAddr) - if err = http.Serve(l, router(isDebug, true)); err != nil { + if err = http.Serve(l, router(isDebug, true, dohServer)); err != nil { log.Errorln("External controller serve error: %s", err) } } -func StartUnix(addr string, isDebug bool) { +func StartUnix(addr string, dohServer string, isDebug bool) { addr = C.Path.Resolve(addr) dir := filepath.Dir(addr) @@ -186,7 +190,7 @@ func StartUnix(addr string, isDebug bool) { serverAddr = l.Addr().String() log.Infoln("RESTful API unix listening at: %s", serverAddr) - if err = http.Serve(l, router(isDebug, false)); err != nil { + if err = http.Serve(l, router(isDebug, false, dohServer)); err != nil { log.Errorln("External controller unix serve error: %s", err) } } diff --git a/clash-meta-android/core/src/foss/golang/clash/listener/http/client.go b/clash-meta-android/core/src/foss/golang/clash/listener/http/client.go index dfd1985f8c..0f084fca7b 100644 --- a/clash-meta-android/core/src/foss/golang/clash/listener/http/client.go +++ b/clash-meta-android/core/src/foss/golang/clash/listener/http/client.go @@ -13,7 +13,7 @@ import ( "github.com/metacubex/mihomo/transport/socks5" ) -func newClient(srcConn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition) *http.Client { +func newClient(srcConn net.Conn, tunnel C.Tunnel, additions []inbound.Addition) *http.Client { // additions using slice let caller can change its value (without size) after newClient return return &http.Client{ Transport: &http.Transport{ // from http.DefaultTransport @@ -21,6 +21,7 @@ func newClient(srcConn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition) IdleConnTimeout: 90 * time.Second, TLSHandshakeTimeout: 10 * time.Second, ExpectContinueTimeout: 1 * time.Second, + DisableCompression: true, // prevents the Transport add "Accept-Encoding: gzip" DialContext: func(context context.Context, network, address string) (net.Conn, error) { if network != "tcp" && network != "tcp4" && network != "tcp6" { return nil, errors.New("unsupported network " + network) diff --git a/clash-meta-android/core/src/foss/golang/clash/listener/http/proxy.go b/clash-meta-android/core/src/foss/golang/clash/listener/http/proxy.go index c77f92307f..b2f312a578 100644 --- a/clash-meta-android/core/src/foss/golang/clash/listener/http/proxy.go +++ b/clash-meta-android/core/src/foss/golang/clash/listener/http/proxy.go @@ -10,10 +10,9 @@ import ( "sync" "github.com/metacubex/mihomo/adapter/inbound" - "github.com/metacubex/mihomo/common/lru" N "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/component/auth" C "github.com/metacubex/mihomo/constant" - authStore "github.com/metacubex/mihomo/listener/auth" "github.com/metacubex/mihomo/log" ) @@ -31,8 +30,10 @@ func (b *bodyWrapper) Read(p []byte) (n int, err error) { return n, err } -func HandleConn(c net.Conn, tunnel C.Tunnel, cache *lru.LruCache[string, bool], additions ...inbound.Addition) { - client := newClient(c, tunnel, additions...) +func HandleConn(c net.Conn, tunnel C.Tunnel, authenticator auth.Authenticator, additions ...inbound.Addition) { + additions = append(additions, inbound.Placeholder) // Add a placeholder for InUser + inUserIdx := len(additions) - 1 + client := newClient(c, tunnel, additions) defer client.CloseIdleConnections() ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -41,7 +42,8 @@ func HandleConn(c net.Conn, tunnel C.Tunnel, cache *lru.LruCache[string, bool], conn := N.NewBufferedConn(c) keepAlive := true - trusted := cache == nil // disable authenticate if lru is nil + trusted := authenticator == nil // disable authenticate if lru is nil + lastUser := "" for keepAlive { peekMutex.Lock() @@ -57,12 +59,10 @@ func HandleConn(c net.Conn, tunnel C.Tunnel, cache *lru.LruCache[string, bool], var resp *http.Response - if !trusted { - var user string - resp, user = authenticate(request, cache) - additions = append(additions, inbound.WithInUser(user)) - trusted = resp == nil - } + var user string + resp, user = authenticate(request, authenticator) // always call authenticate function to get user + trusted = trusted || resp == nil + additions[inUserIdx] = inbound.WithInUser(user) if trusted { if request.Method == http.MethodConnect { @@ -89,6 +89,13 @@ func HandleConn(c net.Conn, tunnel C.Tunnel, cache *lru.LruCache[string, bool], return // hijack connection } + // ensure there is a client with correct additions + // when the authenticated user changed, outbound client should close idle connections + if user != lastUser { + client.CloseIdleConnections() + lastUser = user + } + removeHopByHopHeaders(request.Header) removeExtraHTTPHostPort(request) @@ -138,34 +145,24 @@ func HandleConn(c net.Conn, tunnel C.Tunnel, cache *lru.LruCache[string, bool], _ = conn.Close() } -func authenticate(request *http.Request, cache *lru.LruCache[string, bool]) (resp *http.Response, u string) { - authenticator := authStore.Authenticator() +func authenticate(request *http.Request, authenticator auth.Authenticator) (resp *http.Response, user string) { if inbound.SkipAuthRemoteAddress(request.RemoteAddr) { authenticator = nil } - if authenticator != nil { - credential := parseBasicProxyAuthorization(request) - if credential == "" { - resp := responseWith(request, http.StatusProxyAuthRequired) - resp.Header.Set("Proxy-Authenticate", "Basic") - return resp, "" - } - - authed, exist := cache.Get(credential) - if !exist { - user, pass, err := decodeBasicProxyAuthorization(credential) - authed = err == nil && authenticator.Verify(user, pass) - u = user - cache.Set(credential, authed) - } - if !authed { - log.Infoln("Auth failed from %s", request.RemoteAddr) - - return responseWith(request, http.StatusForbidden), u - } + credential := parseBasicProxyAuthorization(request) + if credential == "" && authenticator != nil { + resp = responseWith(request, http.StatusProxyAuthRequired) + resp.Header.Set("Proxy-Authenticate", "Basic") + return } - - return nil, u + user, pass, err := decodeBasicProxyAuthorization(credential) + authed := authenticator == nil || (err == nil && authenticator.Verify(user, pass)) + if !authed { + log.Infoln("Auth failed from %s", request.RemoteAddr) + return responseWith(request, http.StatusForbidden), user + } + log.Debugln("Auth success from %s -> %s", request.RemoteAddr, user) + return } func responseWith(request *http.Request, statusCode int) *http.Response { diff --git a/clash-meta-android/core/src/foss/golang/clash/listener/http/server.go b/clash-meta-android/core/src/foss/golang/clash/listener/http/server.go index 8fc9da5962..77e10f0841 100644 --- a/clash-meta-android/core/src/foss/golang/clash/listener/http/server.go +++ b/clash-meta-android/core/src/foss/golang/clash/listener/http/server.go @@ -4,9 +4,10 @@ import ( "net" "github.com/metacubex/mihomo/adapter/inbound" - "github.com/metacubex/mihomo/common/lru" + "github.com/metacubex/mihomo/component/auth" C "github.com/metacubex/mihomo/constant" "github.com/metacubex/mihomo/constant/features" + authStore "github.com/metacubex/mihomo/listener/auth" ) type Listener struct { @@ -32,10 +33,20 @@ func (l *Listener) Close() error { } func New(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener, error) { - return NewWithAuthenticate(addr, tunnel, true, additions...) + return NewWithAuthenticator(addr, tunnel, authStore.Authenticator(), additions...) } +// NewWithAuthenticate +// never change type traits because it's used in CFMA func NewWithAuthenticate(addr string, tunnel C.Tunnel, authenticate bool, additions ...inbound.Addition) (*Listener, error) { + authenticator := authStore.Authenticator() + if !authenticate { + authenticator = nil + } + return NewWithAuthenticator(addr, tunnel, authenticator, additions...) +} + +func NewWithAuthenticator(addr string, tunnel C.Tunnel, authenticator auth.Authenticator, additions ...inbound.Addition) (*Listener, error) { isDefault := false if len(additions) == 0 { isDefault = true @@ -50,11 +61,6 @@ func NewWithAuthenticate(addr string, tunnel C.Tunnel, authenticate bool, additi return nil, err } - var c *lru.LruCache[string, bool] - if authenticate { - c = lru.New[string, bool](lru.WithAge[string, bool](30)) - } - hl := &Listener{ listener: l, addr: addr, @@ -79,7 +85,7 @@ func NewWithAuthenticate(addr string, tunnel C.Tunnel, authenticate bool, additi continue } } - go HandleConn(conn, tunnel, c, additions...) + go HandleConn(conn, tunnel, authenticator, additions...) } }() diff --git a/clash-meta-android/core/src/foss/golang/clash/listener/mixed/mixed.go b/clash-meta-android/core/src/foss/golang/clash/listener/mixed/mixed.go index 367b7a3683..773cabe3f1 100644 --- a/clash-meta-android/core/src/foss/golang/clash/listener/mixed/mixed.go +++ b/clash-meta-android/core/src/foss/golang/clash/listener/mixed/mixed.go @@ -4,9 +4,9 @@ import ( "net" "github.com/metacubex/mihomo/adapter/inbound" - "github.com/metacubex/mihomo/common/lru" N "github.com/metacubex/mihomo/common/net" C "github.com/metacubex/mihomo/constant" + authStore "github.com/metacubex/mihomo/listener/auth" "github.com/metacubex/mihomo/listener/http" "github.com/metacubex/mihomo/listener/socks" "github.com/metacubex/mihomo/transport/socks4" @@ -16,7 +16,6 @@ import ( type Listener struct { listener net.Listener addr string - cache *lru.LruCache[string, bool] closed bool } @@ -53,7 +52,6 @@ func New(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener ml := &Listener{ listener: l, addr: addr, - cache: lru.New[string, bool](lru.WithAge[string, bool](30)), } go func() { for { @@ -70,14 +68,14 @@ func New(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener continue } } - go handleConn(c, tunnel, ml.cache, additions...) + go handleConn(c, tunnel, additions...) } }() return ml, nil } -func handleConn(conn net.Conn, tunnel C.Tunnel, cache *lru.LruCache[string, bool], additions ...inbound.Addition) { +func handleConn(conn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition) { N.TCPKeepAlive(conn) bufConn := N.NewBufferedConn(conn) @@ -92,6 +90,6 @@ func handleConn(conn net.Conn, tunnel C.Tunnel, cache *lru.LruCache[string, bool case socks5.Version: socks.HandleSocks5(bufConn, tunnel, additions...) default: - http.HandleConn(bufConn, tunnel, cache, additions...) + http.HandleConn(bufConn, tunnel, authStore.Authenticator(), additions...) } } diff --git a/clash-meta-android/core/src/foss/golang/clash/listener/sing_tun/server_android.go b/clash-meta-android/core/src/foss/golang/clash/listener/sing_tun/server_android.go index ac41282d51..bd5c4bd071 100644 --- a/clash-meta-android/core/src/foss/golang/clash/listener/sing_tun/server_android.go +++ b/clash-meta-android/core/src/foss/golang/clash/listener/sing_tun/server_android.go @@ -1,29 +1,80 @@ package sing_tun import ( + "errors" + "runtime" + "sync" + + "github.com/metacubex/mihomo/component/process" + "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/constant/features" "github.com/metacubex/mihomo/log" - tun "github.com/metacubex/sing-tun" + + "github.com/metacubex/sing-tun" "github.com/sagernet/netlink" "golang.org/x/sys/unix" - "runtime" ) -func (l *Listener) buildAndroidRules(tunOptions *tun.Options) error { - packageManager, err := tun.NewPackageManager(l.handler) +type packageManagerCallback struct{} + +func (cb *packageManagerCallback) OnPackagesUpdated(packageCount int, sharedCount int) {} + +func newPackageManager() (tun.PackageManager, error) { + packageManager, err := tun.NewPackageManager(tun.PackageManagerOptions{ + Callback: &packageManagerCallback{}, + Logger: log.SingLogger, + }) if err != nil { - return err + return nil, err } err = packageManager.Start() + if err != nil { + return nil, err + } + return packageManager, nil +} + +var ( + globalPM tun.PackageManager + pmOnce sync.Once + pmErr error +) + +func getPackageManager() (tun.PackageManager, error) { + pmOnce.Do(func() { + globalPM, pmErr = newPackageManager() + }) + return globalPM, pmErr +} + +func (l *Listener) buildAndroidRules(tunOptions *tun.Options) error { + packageManager, err := getPackageManager() if err != nil { return err } - l.packageManager = packageManager tunOptions.BuildAndroidRules(packageManager, l.handler) return nil } -func (h *ListenerHandler) OnPackagesUpdated(packages int, sharedUsers int) { - return +func findPackageName(metadata *constant.Metadata) (string, error) { + packageManager, err := getPackageManager() + if err != nil { + return "", err + } + uid := metadata.Uid + if sharedPackage, loaded := packageManager.SharedPackageByID(uid % 100000); loaded { + return sharedPackage, nil + } + if packageName, loaded := packageManager.PackageByID(uid % 100000); loaded { + return packageName, nil + } + return "", errors.New("package not found") +} + +func init() { + if !features.CMFA { + process.DefaultPackageNameResolver = findPackageName + } } func (l *Listener) openAndroidHotspot(tunOptions tun.Options) { diff --git a/clash-meta-android/core/src/foss/golang/clash/main.go b/clash-meta-android/core/src/foss/golang/clash/main.go index 61f1d683bd..cd903ce6d9 100644 --- a/clash-meta-android/core/src/foss/golang/clash/main.go +++ b/clash-meta-android/core/src/foss/golang/clash/main.go @@ -17,6 +17,7 @@ import ( "github.com/metacubex/mihomo/hub" "github.com/metacubex/mihomo/hub/executor" "github.com/metacubex/mihomo/log" + "github.com/metacubex/mihomo/rules/provider" "go.uber.org/automaxprocs/maxprocs" ) @@ -48,6 +49,12 @@ func init() { func main() { _, _ = maxprocs.Set(maxprocs.Logger(func(string, ...any) {})) + + if len(os.Args) > 1 && os.Args[1] == "convert-ruleset" { + provider.ConvertMain(os.Args[2:]) + return + } + if version { fmt.Printf("Mihomo Meta %s %s %s with %s %s\n", C.Version, runtime.GOOS, runtime.GOARCH, runtime.Version(), C.BuildTime) diff --git a/clash-meta-android/core/src/foss/golang/clash/rules/provider/classical_strategy.go b/clash-meta-android/core/src/foss/golang/clash/rules/provider/classical_strategy.go index 8353ebce40..205a8e5996 100644 --- a/clash-meta-android/core/src/foss/golang/clash/rules/provider/classical_strategy.go +++ b/clash-meta-android/core/src/foss/golang/clash/rules/provider/classical_strategy.go @@ -5,6 +5,7 @@ import ( "strings" C "github.com/metacubex/mihomo/constant" + P "github.com/metacubex/mihomo/constant/provider" "github.com/metacubex/mihomo/log" ) @@ -16,6 +17,10 @@ type classicalStrategy struct { parse func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error) } +func (c *classicalStrategy) Behavior() P.RuleBehavior { + return P.Classical +} + func (c *classicalStrategy) Match(metadata *C.Metadata) bool { for _, rule := range c.rules { if m, _ := rule.Match(metadata); m { diff --git a/clash-meta-android/core/src/foss/golang/clash/rules/provider/domain_strategy.go b/clash-meta-android/core/src/foss/golang/clash/rules/provider/domain_strategy.go index c0787d5839..b893f038b2 100644 --- a/clash-meta-android/core/src/foss/golang/clash/rules/provider/domain_strategy.go +++ b/clash-meta-android/core/src/foss/golang/clash/rules/provider/domain_strategy.go @@ -1,9 +1,16 @@ package provider import ( + "errors" + "io" + "strings" + "github.com/metacubex/mihomo/component/trie" C "github.com/metacubex/mihomo/constant" + P "github.com/metacubex/mihomo/constant/provider" "github.com/metacubex/mihomo/log" + + "golang.org/x/exp/slices" ) type domainStrategy struct { @@ -12,6 +19,10 @@ type domainStrategy struct { domainSet *trie.DomainSet } +func (d *domainStrategy) Behavior() P.RuleBehavior { + return P.Domain +} + func (d *domainStrategy) ShouldFindProcess() bool { return false } @@ -35,6 +46,10 @@ func (d *domainStrategy) Reset() { } func (d *domainStrategy) Insert(rule string) { + if strings.ContainsRune(rule, '/') { + log.Warnln("invalid domain:[%s]", rule) + return + } err := d.domainTrie.Insert(rule, struct{}{}) if err != nil { log.Warnln("invalid domain:[%s]", rule) @@ -48,6 +63,45 @@ func (d *domainStrategy) FinishInsert() { d.domainTrie = nil } +func (d *domainStrategy) FromMrs(r io.Reader, count int) error { + domainSet, err := trie.ReadDomainSetBin(r) + if err != nil { + return err + } + d.count = count + d.domainSet = domainSet + return nil +} + +func (d *domainStrategy) WriteMrs(w io.Writer) error { + if d.domainSet == nil { + return errors.New("nil domainSet") + } + return d.domainSet.WriteBin(w) +} + +func (d *domainStrategy) DumpMrs(f func(key string) bool) { + if d.domainSet != nil { + var keys []string + d.domainSet.Foreach(func(key string) bool { + keys = append(keys, key) + return true + }) + slices.Sort(keys) + + for _, key := range keys { + if _, ok := slices.BinarySearch(keys, "+."+key); ok { + continue // ignore the rules added by trie internal processing + } + if !f(key) { + return + } + } + } +} + +var _ mrsRuleStrategy = (*domainStrategy)(nil) + func NewDomainStrategy() *domainStrategy { return &domainStrategy{} } diff --git a/clash-meta-android/core/src/foss/golang/clash/rules/provider/ipcidr_strategy.go b/clash-meta-android/core/src/foss/golang/clash/rules/provider/ipcidr_strategy.go index d0545c7cc2..9efffed96b 100644 --- a/clash-meta-android/core/src/foss/golang/clash/rules/provider/ipcidr_strategy.go +++ b/clash-meta-android/core/src/foss/golang/clash/rules/provider/ipcidr_strategy.go @@ -1,8 +1,13 @@ package provider import ( + "errors" + "io" + "net/netip" + "github.com/metacubex/mihomo/component/cidr" C "github.com/metacubex/mihomo/constant" + P "github.com/metacubex/mihomo/constant/provider" "github.com/metacubex/mihomo/log" "go4.org/netipx" @@ -15,6 +20,10 @@ type ipcidrStrategy struct { //trie *trie.IpCidrTrie } +func (i *ipcidrStrategy) Behavior() P.RuleBehavior { + return P.IPCIDR +} + func (i *ipcidrStrategy) ShouldFindProcess() bool { return false } @@ -54,6 +63,34 @@ func (i *ipcidrStrategy) FinishInsert() { i.cidrSet.Merge() } +func (i *ipcidrStrategy) FromMrs(r io.Reader, count int) error { + cidrSet, err := cidr.ReadIpCidrSet(r) + if err != nil { + return err + } + i.count = count + i.cidrSet = cidrSet + if i.count > 0 { + i.shouldResolveIP = true + } + return nil +} + +func (i *ipcidrStrategy) WriteMrs(w io.Writer) error { + if i.cidrSet == nil { + return errors.New("nil cidrSet") + } + return i.cidrSet.WriteBin(w) +} + +func (i *ipcidrStrategy) DumpMrs(f func(key string) bool) { + if i.cidrSet != nil { + i.cidrSet.Foreach(func(prefix netip.Prefix) bool { + return f(prefix.String()) + }) + } +} + func (i *ipcidrStrategy) ToIpCidr() *netipx.IPSet { return i.cidrSet.ToIPSet() } diff --git a/clash-meta-android/core/src/foss/golang/clash/rules/provider/mrs_converter.go b/clash-meta-android/core/src/foss/golang/clash/rules/provider/mrs_converter.go new file mode 100644 index 0000000000..edc24e7eea --- /dev/null +++ b/clash-meta-android/core/src/foss/golang/clash/rules/provider/mrs_converter.go @@ -0,0 +1,120 @@ +package provider + +import ( + "encoding/binary" + "errors" + "fmt" + "io" + "os" + + P "github.com/metacubex/mihomo/constant/provider" + + "github.com/klauspost/compress/zstd" +) + +func ConvertToMrs(buf []byte, behavior P.RuleBehavior, format P.RuleFormat, w io.Writer) (err error) { + strategy := newStrategy(behavior, nil) + strategy, err = rulesParse(buf, strategy, format) + if err != nil { + return err + } + if strategy.Count() == 0 { + return errors.New("empty rule") + } + if _strategy, ok := strategy.(mrsRuleStrategy); ok { + if format == P.MrsRule { // export to TextRule + _strategy.DumpMrs(func(key string) bool { + _, err = fmt.Fprintln(w, key) + if err != nil { + return false + } + return true + }) + return nil + } + + var encoder *zstd.Encoder + encoder, err = zstd.NewWriter(w) + if err != nil { + return err + } + defer func() { + zstdErr := encoder.Close() + if err == nil { + err = zstdErr + } + }() + + // header + _, err = encoder.Write(MrsMagicBytes[:]) + if err != nil { + return err + } + + // behavior + _behavior := []byte{behavior.Byte()} + _, err = encoder.Write(_behavior[:]) + if err != nil { + return err + } + + // count + count := int64(_strategy.Count()) + err = binary.Write(encoder, binary.BigEndian, count) + if err != nil { + return err + } + + // extra (reserved for future using) + var extra []byte + err = binary.Write(encoder, binary.BigEndian, int64(len(extra))) + if err != nil { + return err + } + _, err = encoder.Write(extra) + if err != nil { + return err + } + + return _strategy.WriteMrs(encoder) + } else { + return ErrInvalidFormat + } +} + +func ConvertMain(args []string) { + if len(args) > 3 { + behavior, err := P.ParseBehavior(args[0]) + if err != nil { + panic(err) + } + format, err := P.ParseRuleFormat(args[1]) + if err != nil { + panic(err) + } + source := args[2] + target := args[3] + + sourceFile, err := os.ReadFile(source) + if err != nil { + panic(err) + } + + targetFile, err := os.OpenFile(target, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) + if err != nil { + panic(err) + } + + err = ConvertToMrs(sourceFile, behavior, format, targetFile) + if err != nil { + panic(err) + } + + err = targetFile.Close() + if err != nil { + panic(err) + } + } else { + panic("Usage: convert-ruleset ") + } +} diff --git a/clash-meta-android/core/src/foss/golang/clash/rules/provider/mrs_reader.go b/clash-meta-android/core/src/foss/golang/clash/rules/provider/mrs_reader.go new file mode 100644 index 0000000000..66f62127c8 --- /dev/null +++ b/clash-meta-android/core/src/foss/golang/clash/rules/provider/mrs_reader.go @@ -0,0 +1,72 @@ +package provider + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "io" + + "github.com/klauspost/compress/zstd" +) + +var MrsMagicBytes = [4]byte{'M', 'R', 'S', 1} // MRSv1 + +func rulesMrsParse(buf []byte, strategy ruleStrategy) (ruleStrategy, error) { + if _strategy, ok := strategy.(mrsRuleStrategy); ok { + reader, err := zstd.NewReader(bytes.NewReader(buf)) + if err != nil { + return nil, err + } + defer reader.Close() + + // header + var header [4]byte + _, err = io.ReadFull(reader, header[:]) + if err != nil { + return nil, err + } + if header != MrsMagicBytes { + return nil, fmt.Errorf("invalid MrsMagic bytes") + } + + // behavior + var _behavior [1]byte + _, err = io.ReadFull(reader, _behavior[:]) + if err != nil { + return nil, err + } + if _behavior[0] != strategy.Behavior().Byte() { + return nil, fmt.Errorf("invalid behavior") + } + + // count + var count int64 + err = binary.Read(reader, binary.BigEndian, &count) + if err != nil { + return nil, err + } + + // extra (reserved for future using) + var length int64 + err = binary.Read(reader, binary.BigEndian, &length) + if err != nil { + return nil, err + } + if length < 0 { + return nil, errors.New("length is invalid") + } + if length > 0 { + extra := make([]byte, length) + _, err = io.ReadFull(reader, extra) + if err != nil { + return nil, err + } + } + + err = _strategy.FromMrs(reader, int(count)) + return strategy, err + } else { + return nil, ErrInvalidFormat + } +} diff --git a/clash-meta-android/core/src/foss/golang/clash/rules/provider/parse.go b/clash-meta-android/core/src/foss/golang/clash/rules/provider/parse.go index a20da28d5c..227debb3a0 100644 --- a/clash-meta-android/core/src/foss/golang/clash/rules/provider/parse.go +++ b/clash-meta-android/core/src/foss/golang/clash/rules/provider/parse.go @@ -32,28 +32,13 @@ func ParseRuleProvider(name string, mapping map[string]interface{}, parse func(t if err := decoder.Decode(mapping, schema); err != nil { return nil, err } - var behavior P.RuleBehavior - - switch schema.Behavior { - case "domain": - behavior = P.Domain - case "ipcidr": - behavior = P.IPCIDR - case "classical": - behavior = P.Classical - default: - return nil, fmt.Errorf("unsupported behavior type: %s", schema.Behavior) + behavior, err := P.ParseBehavior(schema.Behavior) + if err != nil { + return nil, err } - - var format P.RuleFormat - - switch schema.Format { - case "", "yaml": - format = P.YamlRule - case "text": - format = P.TextRule - default: - return nil, fmt.Errorf("unsupported format type: %s", schema.Format) + format, err := P.ParseRuleFormat(schema.Format) + if err != nil { + return nil, err } var vehicle P.Vehicle diff --git a/clash-meta-android/core/src/foss/golang/clash/rules/provider/provider.go b/clash-meta-android/core/src/foss/golang/clash/rules/provider/provider.go index 6c03c6e5f3..b9524c35e6 100644 --- a/clash-meta-android/core/src/foss/golang/clash/rules/provider/provider.go +++ b/clash-meta-android/core/src/foss/golang/clash/rules/provider/provider.go @@ -4,16 +4,17 @@ import ( "bytes" "encoding/json" "errors" + "io" "runtime" "strings" "time" - "gopkg.in/yaml.v3" - "github.com/metacubex/mihomo/common/pool" "github.com/metacubex/mihomo/component/resource" C "github.com/metacubex/mihomo/constant" P "github.com/metacubex/mihomo/constant/provider" + + "gopkg.in/yaml.v3" ) var tunnel P.Tunnel @@ -43,6 +44,7 @@ type RulePayload struct { } type ruleStrategy interface { + Behavior() P.RuleBehavior Match(metadata *C.Metadata) bool Count() int ShouldResolveIP() bool @@ -52,6 +54,13 @@ type ruleStrategy interface { FinishInsert() } +type mrsRuleStrategy interface { + ruleStrategy + FromMrs(r io.Reader, count int) error + WriteMrs(w io.Writer) error + DumpMrs(f func(key string) bool) +} + func (rp *ruleSetProvider) Type() P.ProviderType { return P.Rule } @@ -152,9 +161,13 @@ func newStrategy(behavior P.RuleBehavior, parse func(tp, payload, target string, } var ErrNoPayload = errors.New("file must have a `payload` field") +var ErrInvalidFormat = errors.New("invalid format") func rulesParse(buf []byte, strategy ruleStrategy, format P.RuleFormat) (ruleStrategy, error) { strategy.Reset() + if format == P.MrsRule { + return rulesMrsParse(buf, strategy) + } schema := &RulePayload{} @@ -228,6 +241,8 @@ func rulesParse(buf []byte, strategy ruleStrategy, format P.RuleFormat) (ruleStr if len(schema.Payload) > 0 { str = schema.Payload[0] } + default: + return nil, ErrInvalidFormat } if str == "" { diff --git a/clash-meta-android/core/src/foss/golang/clash/transport/shadowsocks/core/cipher.go b/clash-meta-android/core/src/foss/golang/clash/transport/shadowsocks/core/cipher.go index 44b2e8d42c..7cb12c45c3 100644 --- a/clash-meta-android/core/src/foss/golang/clash/transport/shadowsocks/core/cipher.go +++ b/clash-meta-android/core/src/foss/golang/clash/transport/shadowsocks/core/cipher.go @@ -34,6 +34,11 @@ const ( aeadAes256Gcm = "AEAD_AES_256_GCM" aeadChacha20Poly1305 = "AEAD_CHACHA20_POLY1305" aeadXChacha20Poly1305 = "AEAD_XCHACHA20_POLY1305" + aeadChacha8Poly1305 = "AEAD_CHACHA8_POLY1305" + aeadXChacha8Poly1305 = "AEAD_XCHACHA8_POLY1305" + aeadAes128Ccm = "AEAD_AES_128_CCM" + aeadAes192Ccm = "AEAD_AES_192_CCM" + aeadAes256Ccm = "AEAD_AES_256_CCM" ) // List of AEAD ciphers: key size in bytes and constructor @@ -46,6 +51,11 @@ var aeadList = map[string]struct { aeadAes256Gcm: {32, shadowaead.AESGCM}, aeadChacha20Poly1305: {32, shadowaead.Chacha20Poly1305}, aeadXChacha20Poly1305: {32, shadowaead.XChacha20Poly1305}, + aeadChacha8Poly1305: {32, shadowaead.Chacha8Poly1305}, + aeadXChacha8Poly1305: {32, shadowaead.XChacha8Poly1305}, + aeadAes128Ccm: {16, shadowaead.AESCCM}, + aeadAes192Ccm: {24, shadowaead.AESCCM}, + aeadAes256Ccm: {32, shadowaead.AESCCM}, } // List of stream ciphers: key size in bytes and constructor @@ -95,6 +105,16 @@ func PickCipher(name string, key []byte, password string) (Cipher, error) { name = aeadAes192Gcm case "AES-256-GCM": name = aeadAes256Gcm + case "CHACHA8-IETF-POLY1305": + name = aeadChacha8Poly1305 + case "XCHACHA8-IETF-POLY1305": + name = aeadXChacha8Poly1305 + case "AES-128-CCM": + name = aeadAes128Ccm + case "AES-192-CCM": + name = aeadAes192Ccm + case "AES-256-CCM": + name = aeadAes256Ccm } if choice, ok := aeadList[name]; ok { diff --git a/clash-meta-android/core/src/foss/golang/clash/transport/shadowsocks/shadowaead/cipher.go b/clash-meta-android/core/src/foss/golang/clash/transport/shadowsocks/shadowaead/cipher.go index 3cf7574974..7981d5b15a 100644 --- a/clash-meta-android/core/src/foss/golang/clash/transport/shadowsocks/shadowaead/cipher.go +++ b/clash-meta-android/core/src/foss/golang/clash/transport/shadowsocks/shadowaead/cipher.go @@ -7,6 +7,8 @@ import ( "io" "strconv" + "github.com/metacubex/chacha" + "gitlab.com/go-extension/aes-ccm" "golang.org/x/crypto/chacha20poly1305" "golang.org/x/crypto/hkdf" ) @@ -75,6 +77,25 @@ func AESGCM(psk []byte) (Cipher, error) { return &metaCipher{psk: psk, makeAEAD: aesGCM}, nil } +func aesCCM(key []byte) (cipher.AEAD, error) { + blk, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + return ccm.NewCCM(blk) +} + +// AESCCM creates a new Cipher with a pre-shared key. len(psk) must be +// one of 16, 24, or 32 to select AES-128/196/256-GCM. +func AESCCM(psk []byte) (Cipher, error) { + switch l := len(psk); l { + case 16, 24, 32: // AES 128/196/256 + default: + return nil, aes.KeySizeError(l) + } + return &metaCipher{psk: psk, makeAEAD: aesCCM}, nil +} + // Chacha20Poly1305 creates a new Cipher with a pre-shared key. len(psk) // must be 32. func Chacha20Poly1305(psk []byte) (Cipher, error) { @@ -92,3 +113,21 @@ func XChacha20Poly1305(psk []byte) (Cipher, error) { } return &metaCipher{psk: psk, makeAEAD: chacha20poly1305.NewX}, nil } + +// Chacha8Poly1305 creates a new Cipher with a pre-shared key. len(psk) +// must be 32. +func Chacha8Poly1305(psk []byte) (Cipher, error) { + if len(psk) != chacha.KeySize { + return nil, KeySizeError(chacha.KeySize) + } + return &metaCipher{psk: psk, makeAEAD: chacha.NewChaCha8IETFPoly1305}, nil +} + +// XChacha8Poly1305 creates a new Cipher with a pre-shared key. len(psk) +// must be 32. +func XChacha8Poly1305(psk []byte) (Cipher, error) { + if len(psk) != chacha.KeySize { + return nil, KeySizeError(chacha.KeySize) + } + return &metaCipher{psk: psk, makeAEAD: chacha.NewXChaCha20IETFPoly1305}, nil +} diff --git a/clash-meta-android/core/src/foss/golang/clash/transport/shadowsocks/shadowstream/old_chacha20.go b/clash-meta-android/core/src/foss/golang/clash/transport/shadowsocks/shadowstream/old_chacha20.go index 65737fccd1..eb61232e30 100644 --- a/clash-meta-android/core/src/foss/golang/clash/transport/shadowsocks/shadowstream/old_chacha20.go +++ b/clash-meta-android/core/src/foss/golang/clash/transport/shadowsocks/shadowstream/old_chacha20.go @@ -2,7 +2,8 @@ package shadowstream import ( "crypto/cipher" - "github.com/aead/chacha20/chacha" + + "github.com/metacubex/chacha" ) type chacha20key []byte @@ -11,7 +12,7 @@ func (k chacha20key) IVSize() int { return chacha.NonceSize } func (k chacha20key) Encrypter(iv []byte) cipher.Stream { - c, _ := chacha.NewCipher(iv, k, 20) + c, _ := chacha.NewChaCha20(iv, k) return c } func (k chacha20key) Decrypter(iv []byte) cipher.Stream { 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 1a6f104dc9..5dd468f3b4 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 @@ -622,6 +622,10 @@ func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) { metadata.Process = filepath.Base(path) metadata.ProcessPath = path metadata.Uid = uid + + if pkg, err := P.FindPackageName(metadata); err == nil { // for android (not CMFA) package names + metadata.Process = pkg + } } } else { // check package names diff --git a/clash-meta-android/core/src/foss/golang/go.mod b/clash-meta-android/core/src/foss/golang/go.mod index 8e8d0e9788..a22a6a9d5a 100644 --- a/clash-meta-android/core/src/foss/golang/go.mod +++ b/clash-meta-android/core/src/foss/golang/go.mod @@ -11,7 +11,6 @@ require ( github.com/Kr328/tun2socket v0.0.0-20220414050025-d07c78d06d34 // indirect github.com/RyuaNerin/go-krypto v1.2.4 // indirect github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 // indirect - github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect github.com/andybalholm/brotli v1.0.6 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/buger/jsonparser v1.1.1 // indirect @@ -37,23 +36,24 @@ require ( github.com/hashicorp/yamux v0.1.1 // indirect github.com/insomniacslk/dhcp v0.0.0-20240529192340-51bc6136a0a6 // indirect github.com/josharian/native v1.1.0 // indirect - github.com/klauspost/compress v1.17.4 // indirect + github.com/klauspost/compress v1.17.9 // indirect github.com/klauspost/cpuid/v2 v2.2.8 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mdlayher/netlink v1.7.2 // indirect github.com/mdlayher/socket v0.4.1 // indirect + github.com/metacubex/chacha v0.1.0 // indirect github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 // indirect github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec // indirect github.com/metacubex/mihomo v1.7.0 // indirect github.com/metacubex/quic-go v0.45.1-0.20240610004319-163fee60637e // indirect github.com/metacubex/randv2 v0.2.0 // indirect github.com/metacubex/sing-quic v0.0.0-20240518034124-7696d3f7da72 // indirect - github.com/metacubex/sing-shadowsocks v0.2.6 // indirect - github.com/metacubex/sing-shadowsocks2 v0.2.0 // indirect - github.com/metacubex/sing-tun v0.2.7-0.20240627012306-9d1f5fc0b45e // indirect - github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f // indirect + github.com/metacubex/sing-shadowsocks v0.2.7 // indirect + github.com/metacubex/sing-shadowsocks2 v0.2.1 // indirect + github.com/metacubex/sing-tun v0.2.7-0.20240719141246-19c49ac9589d // indirect + github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9 // indirect github.com/metacubex/sing-wireguard v0.0.0-20240618022557-a6efaa37127a // indirect github.com/metacubex/tfo-go v0.0.0-20240228025757-be1269474a66 // indirect github.com/metacubex/utls v1.6.6 // indirect @@ -69,9 +69,10 @@ require ( github.com/quic-go/qpack v0.4.0 // indirect github.com/quic-go/qtls-go1-20 v0.4.1 // indirect github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a // indirect + github.com/sagernet/fswatch v0.1.1 // indirect github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect github.com/sagernet/nftables v0.3.0-beta.4 // indirect - github.com/sagernet/sing v0.5.0-alpha.10 // indirect + github.com/sagernet/sing v0.5.0-alpha.13 // indirect github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6 // indirect github.com/sagernet/sing-shadowtls v0.1.4 // indirect github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect @@ -89,6 +90,7 @@ require ( github.com/vishvananda/netns v0.0.4 // indirect github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect + gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7 // indirect gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect go.uber.org/mock v0.4.0 // indirect go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect @@ -97,7 +99,7 @@ require ( golang.org/x/mod v0.18.0 // indirect golang.org/x/net v0.26.0 // indirect golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.21.0 // indirect + golang.org/x/sys v0.22.0 // indirect golang.org/x/text v0.16.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.22.0 // indirect @@ -107,7 +109,7 @@ require ( lukechampine.com/blake3 v1.3.0 // indirect ) -replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20240617013425-3e3bd9dab6a2 +replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20240724044459-6f3cf5896297 replace cfa => ../../main/golang diff --git a/clash-meta-android/core/src/foss/golang/go.sum b/clash-meta-android/core/src/foss/golang/go.sum index 206a150909..630a9ffbb4 100644 --- a/clash-meta-android/core/src/foss/golang/go.sum +++ b/clash-meta-android/core/src/foss/golang/go.sum @@ -7,8 +7,6 @@ github.com/RyuaNerin/go-krypto v1.2.4 h1:mXuNdK6M317aPV0llW6Xpjbo4moOlPF7Yxz4tb4 github.com/RyuaNerin/go-krypto v1.2.4/go.mod h1:QqCYkoutU3yInyD9INt2PGolVRsc3W4oraQadVGXJ/8= github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 h1:cDVUiFo+npB0ZASqnw4q90ylaVAbnYyx0JYqK4YcGok= github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344/go.mod h1:9pIqrY6SXNL8vjRQE5Hd/OL5GyK/9MrGUWs87z/eFfk= -github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= -github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= @@ -77,8 +75,8 @@ github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFF github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= -github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= -github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -93,6 +91,8 @@ github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/ github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= +github.com/metacubex/chacha v0.1.0 h1:tg9RSJ18NvL38cCWNyYH1eiG6qDCyyXIaTLQthon0sc= +github.com/metacubex/chacha v0.1.0/go.mod h1:Djn9bPZxLTXbJFSeyo0/qzEzQI+gUSSzttuzZM75GH8= github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI= github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88= github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec h1:HxreOiFTUrJXJautEo8rnE1uKTVGY8wtZepY1Tii/Nc= @@ -101,18 +101,18 @@ github.com/metacubex/quic-go v0.45.1-0.20240610004319-163fee60637e h1:bLYn3GuRvW github.com/metacubex/quic-go v0.45.1-0.20240610004319-163fee60637e/go.mod h1:Yza2H7Ax1rxWPUcJx0vW+oAt9EsPuSiyQFhFabUPzwU= github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiLs= github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY= -github.com/metacubex/sing v0.0.0-20240617013425-3e3bd9dab6a2 h1:N5tidgg/FRmkgPw/AjRwhLUinKDx/ODCSbvv9xqRoLM= -github.com/metacubex/sing v0.0.0-20240617013425-3e3bd9dab6a2/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/metacubex/sing v0.0.0-20240724044459-6f3cf5896297 h1:YG/JkwGPbca5rUtEMHIu8ZuqzR7BSVm1iqY8hNoMeMA= +github.com/metacubex/sing v0.0.0-20240724044459-6f3cf5896297/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/metacubex/sing-quic v0.0.0-20240518034124-7696d3f7da72 h1:Wr4g1HCb5Z/QIFwFiVNjO2qL+dRu25+Mdn9xtAZZ+ew= github.com/metacubex/sing-quic v0.0.0-20240518034124-7696d3f7da72/go.mod h1:g7Mxj7b7zm7YVqD975mk/hSmrb0A0G4bVvIMr2MMzn8= -github.com/metacubex/sing-shadowsocks v0.2.6 h1:6oEB3QcsFYnNiFeoevcXrCwJ3sAablwVSgtE9R3QeFQ= -github.com/metacubex/sing-shadowsocks v0.2.6/go.mod h1:zIkMeSnb8Mbf4hdqhw0pjzkn1d99YJ3JQm/VBg5WMTg= -github.com/metacubex/sing-shadowsocks2 v0.2.0 h1:hqwT/AfI5d5UdPefIzR6onGHJfDXs5zgOM5QSgaM/9A= -github.com/metacubex/sing-shadowsocks2 v0.2.0/go.mod h1:LCKF6j1P94zN8ZS+LXRK1gmYTVGB3squivBSXAFnOg8= -github.com/metacubex/sing-tun v0.2.7-0.20240627012306-9d1f5fc0b45e h1:o+zohxPRo45P35fS9u1zfdBgr+L/7S0ObGU6YjbVBIc= -github.com/metacubex/sing-tun v0.2.7-0.20240627012306-9d1f5fc0b45e/go.mod h1:WwJGbCx7bQcBzuQXiDOJvZH27R0kIjKNNlISIWsL6kM= -github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f h1:QjXrHKbTMBip/C+R79bvbfr42xH1gZl3uFb0RELdZiQ= -github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f/go.mod h1:olVkD4FChQ5gKMHG4ZzuD7+fMkJY1G8vwOKpRehjrmY= +github.com/metacubex/sing-shadowsocks v0.2.7 h1:9f3Dt2+71TNp0e202llA2ug5h/rkWs2EZxQ5IMpf+9g= +github.com/metacubex/sing-shadowsocks v0.2.7/go.mod h1:X3x88XtJpBxG0W0/ECOJL6Ib0SJ3xdniAkU/6/RMWU0= +github.com/metacubex/sing-shadowsocks2 v0.2.1 h1:XIZBXlazp8EEoPp1S0DViAhLkJakjQ2f+AOwwdKKNYg= +github.com/metacubex/sing-shadowsocks2 v0.2.1/go.mod h1:BhOug03a/RbI7y6hp6q+6ITM1dXjnLTmeWBHSTwvv2Q= +github.com/metacubex/sing-tun v0.2.7-0.20240719141246-19c49ac9589d h1:iYlepjRCYlPXtELupDL+pQjGqkCnQz4KQOfKImP9sog= +github.com/metacubex/sing-tun v0.2.7-0.20240719141246-19c49ac9589d/go.mod h1:olbEx9yVcaw5tHTNlRamRoxmMKcvDvcVS1YLnQGzvWE= +github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9 h1:OAXiCosqY8xKDp3pqTW3qbrCprZ1l6WkrXSFSCwyY4I= +github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9/go.mod h1:olVkD4FChQ5gKMHG4ZzuD7+fMkJY1G8vwOKpRehjrmY= github.com/metacubex/sing-wireguard v0.0.0-20240618022557-a6efaa37127a h1:NpSGclHJUYndUwBmyIpFBSoBVg8PoVX7QQKhYg0DjM0= github.com/metacubex/sing-wireguard v0.0.0-20240618022557-a6efaa37127a/go.mod h1:uY+BYb0UEknLrqvbGcwi9i++KgrKxsurysgI6G1Pveo= github.com/metacubex/tfo-go v0.0.0-20240228025757-be1269474a66 h1:as/aO/fM8nv4W4pOr9EETP6kV/Oaujk3fUNyQSJK61c= @@ -151,6 +151,8 @@ github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58 github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkkD2QgdTuzQG263YZ+2emfpeyGqW0= github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM= +github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs= +github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o= github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis= github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I= @@ -205,6 +207,8 @@ github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/ github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7 h1:UNrDfkQqiEYzdMlNsVvBYOAJWZjdktqFE9tQh5BT2+4= +gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7/go.mod h1:E+rxHvJG9H6PUdzq9NRG6csuLN3XUx98BfGOVWNYnXs= gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec h1:FpfFs4EhNehiVfzQttTuxanPIT43FtkkCFypIod8LHo= gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec/go.mod h1:BZ1RAoRPbCxum9Grlv5aeksu2H8BiKehBYooU2LFiOQ= go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= @@ -243,8 +247,9 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= diff --git a/clash-meta-android/core/src/main/golang/go.mod b/clash-meta-android/core/src/main/golang/go.mod index d2676f4ff5..b2fd71288c 100644 --- a/clash-meta-android/core/src/main/golang/go.mod +++ b/clash-meta-android/core/src/main/golang/go.mod @@ -14,13 +14,12 @@ require ( replace github.com/metacubex/mihomo => ../../foss/golang/clash -replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20240617013425-3e3bd9dab6a2 +replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20240724044459-6f3cf5896297 require ( github.com/3andne/restls-client-go v0.1.6 // indirect github.com/RyuaNerin/go-krypto v1.2.4 // indirect github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 // indirect - github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect github.com/andybalholm/brotli v1.0.6 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/buger/jsonparser v1.1.1 // indirect @@ -45,22 +44,23 @@ require ( github.com/hashicorp/yamux v0.1.1 // indirect github.com/insomniacslk/dhcp v0.0.0-20240529192340-51bc6136a0a6 // indirect github.com/josharian/native v1.1.0 // indirect - github.com/klauspost/compress v1.17.4 // indirect + github.com/klauspost/compress v1.17.9 // indirect github.com/klauspost/cpuid/v2 v2.2.8 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mdlayher/netlink v1.7.2 // indirect github.com/mdlayher/socket v0.4.1 // indirect + github.com/metacubex/chacha v0.1.0 // indirect github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 // indirect github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec // indirect github.com/metacubex/quic-go v0.45.1-0.20240610004319-163fee60637e // indirect github.com/metacubex/randv2 v0.2.0 // indirect github.com/metacubex/sing-quic v0.0.0-20240518034124-7696d3f7da72 // indirect - github.com/metacubex/sing-shadowsocks v0.2.6 // indirect - github.com/metacubex/sing-shadowsocks2 v0.2.0 // indirect - github.com/metacubex/sing-tun v0.2.7-0.20240627012306-9d1f5fc0b45e // indirect - github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f // indirect + github.com/metacubex/sing-shadowsocks v0.2.7 // indirect + github.com/metacubex/sing-shadowsocks2 v0.2.1 // indirect + github.com/metacubex/sing-tun v0.2.7-0.20240719141246-19c49ac9589d // indirect + github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9 // indirect github.com/metacubex/sing-wireguard v0.0.0-20240618022557-a6efaa37127a // indirect github.com/metacubex/tfo-go v0.0.0-20240228025757-be1269474a66 // indirect github.com/metacubex/utls v1.6.6 // indirect @@ -74,9 +74,10 @@ require ( github.com/quic-go/qpack v0.4.0 // indirect github.com/quic-go/qtls-go1-20 v0.4.1 // indirect github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a // indirect + github.com/sagernet/fswatch v0.1.1 // indirect github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect github.com/sagernet/nftables v0.3.0-beta.4 // indirect - github.com/sagernet/sing v0.5.0-alpha.10 // indirect + github.com/sagernet/sing v0.5.0-alpha.13 // indirect github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6 // indirect github.com/sagernet/sing-shadowtls v0.1.4 // indirect github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect @@ -94,6 +95,7 @@ require ( github.com/vishvananda/netns v0.0.4 // indirect github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect + gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7 // indirect gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect go.uber.org/mock v0.4.0 // indirect go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect @@ -101,7 +103,7 @@ require ( golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect golang.org/x/mod v0.18.0 // indirect golang.org/x/net v0.26.0 // indirect - golang.org/x/sys v0.21.0 // indirect + golang.org/x/sys v0.22.0 // indirect golang.org/x/text v0.16.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.22.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 206a150909..630a9ffbb4 100644 --- a/clash-meta-android/core/src/main/golang/go.sum +++ b/clash-meta-android/core/src/main/golang/go.sum @@ -7,8 +7,6 @@ github.com/RyuaNerin/go-krypto v1.2.4 h1:mXuNdK6M317aPV0llW6Xpjbo4moOlPF7Yxz4tb4 github.com/RyuaNerin/go-krypto v1.2.4/go.mod h1:QqCYkoutU3yInyD9INt2PGolVRsc3W4oraQadVGXJ/8= github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 h1:cDVUiFo+npB0ZASqnw4q90ylaVAbnYyx0JYqK4YcGok= github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344/go.mod h1:9pIqrY6SXNL8vjRQE5Hd/OL5GyK/9MrGUWs87z/eFfk= -github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= -github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= @@ -77,8 +75,8 @@ github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFF github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= -github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= -github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -93,6 +91,8 @@ github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/ github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= +github.com/metacubex/chacha v0.1.0 h1:tg9RSJ18NvL38cCWNyYH1eiG6qDCyyXIaTLQthon0sc= +github.com/metacubex/chacha v0.1.0/go.mod h1:Djn9bPZxLTXbJFSeyo0/qzEzQI+gUSSzttuzZM75GH8= github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI= github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88= github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec h1:HxreOiFTUrJXJautEo8rnE1uKTVGY8wtZepY1Tii/Nc= @@ -101,18 +101,18 @@ github.com/metacubex/quic-go v0.45.1-0.20240610004319-163fee60637e h1:bLYn3GuRvW github.com/metacubex/quic-go v0.45.1-0.20240610004319-163fee60637e/go.mod h1:Yza2H7Ax1rxWPUcJx0vW+oAt9EsPuSiyQFhFabUPzwU= github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiLs= github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY= -github.com/metacubex/sing v0.0.0-20240617013425-3e3bd9dab6a2 h1:N5tidgg/FRmkgPw/AjRwhLUinKDx/ODCSbvv9xqRoLM= -github.com/metacubex/sing v0.0.0-20240617013425-3e3bd9dab6a2/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/metacubex/sing v0.0.0-20240724044459-6f3cf5896297 h1:YG/JkwGPbca5rUtEMHIu8ZuqzR7BSVm1iqY8hNoMeMA= +github.com/metacubex/sing v0.0.0-20240724044459-6f3cf5896297/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/metacubex/sing-quic v0.0.0-20240518034124-7696d3f7da72 h1:Wr4g1HCb5Z/QIFwFiVNjO2qL+dRu25+Mdn9xtAZZ+ew= github.com/metacubex/sing-quic v0.0.0-20240518034124-7696d3f7da72/go.mod h1:g7Mxj7b7zm7YVqD975mk/hSmrb0A0G4bVvIMr2MMzn8= -github.com/metacubex/sing-shadowsocks v0.2.6 h1:6oEB3QcsFYnNiFeoevcXrCwJ3sAablwVSgtE9R3QeFQ= -github.com/metacubex/sing-shadowsocks v0.2.6/go.mod h1:zIkMeSnb8Mbf4hdqhw0pjzkn1d99YJ3JQm/VBg5WMTg= -github.com/metacubex/sing-shadowsocks2 v0.2.0 h1:hqwT/AfI5d5UdPefIzR6onGHJfDXs5zgOM5QSgaM/9A= -github.com/metacubex/sing-shadowsocks2 v0.2.0/go.mod h1:LCKF6j1P94zN8ZS+LXRK1gmYTVGB3squivBSXAFnOg8= -github.com/metacubex/sing-tun v0.2.7-0.20240627012306-9d1f5fc0b45e h1:o+zohxPRo45P35fS9u1zfdBgr+L/7S0ObGU6YjbVBIc= -github.com/metacubex/sing-tun v0.2.7-0.20240627012306-9d1f5fc0b45e/go.mod h1:WwJGbCx7bQcBzuQXiDOJvZH27R0kIjKNNlISIWsL6kM= -github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f h1:QjXrHKbTMBip/C+R79bvbfr42xH1gZl3uFb0RELdZiQ= -github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f/go.mod h1:olVkD4FChQ5gKMHG4ZzuD7+fMkJY1G8vwOKpRehjrmY= +github.com/metacubex/sing-shadowsocks v0.2.7 h1:9f3Dt2+71TNp0e202llA2ug5h/rkWs2EZxQ5IMpf+9g= +github.com/metacubex/sing-shadowsocks v0.2.7/go.mod h1:X3x88XtJpBxG0W0/ECOJL6Ib0SJ3xdniAkU/6/RMWU0= +github.com/metacubex/sing-shadowsocks2 v0.2.1 h1:XIZBXlazp8EEoPp1S0DViAhLkJakjQ2f+AOwwdKKNYg= +github.com/metacubex/sing-shadowsocks2 v0.2.1/go.mod h1:BhOug03a/RbI7y6hp6q+6ITM1dXjnLTmeWBHSTwvv2Q= +github.com/metacubex/sing-tun v0.2.7-0.20240719141246-19c49ac9589d h1:iYlepjRCYlPXtELupDL+pQjGqkCnQz4KQOfKImP9sog= +github.com/metacubex/sing-tun v0.2.7-0.20240719141246-19c49ac9589d/go.mod h1:olbEx9yVcaw5tHTNlRamRoxmMKcvDvcVS1YLnQGzvWE= +github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9 h1:OAXiCosqY8xKDp3pqTW3qbrCprZ1l6WkrXSFSCwyY4I= +github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9/go.mod h1:olVkD4FChQ5gKMHG4ZzuD7+fMkJY1G8vwOKpRehjrmY= github.com/metacubex/sing-wireguard v0.0.0-20240618022557-a6efaa37127a h1:NpSGclHJUYndUwBmyIpFBSoBVg8PoVX7QQKhYg0DjM0= github.com/metacubex/sing-wireguard v0.0.0-20240618022557-a6efaa37127a/go.mod h1:uY+BYb0UEknLrqvbGcwi9i++KgrKxsurysgI6G1Pveo= github.com/metacubex/tfo-go v0.0.0-20240228025757-be1269474a66 h1:as/aO/fM8nv4W4pOr9EETP6kV/Oaujk3fUNyQSJK61c= @@ -151,6 +151,8 @@ github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58 github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkkD2QgdTuzQG263YZ+2emfpeyGqW0= github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM= +github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs= +github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o= github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis= github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I= @@ -205,6 +207,8 @@ github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/ github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7 h1:UNrDfkQqiEYzdMlNsVvBYOAJWZjdktqFE9tQh5BT2+4= +gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7/go.mod h1:E+rxHvJG9H6PUdzq9NRG6csuLN3XUx98BfGOVWNYnXs= gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec h1:FpfFs4EhNehiVfzQttTuxanPIT43FtkkCFypIod8LHo= gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec/go.mod h1:BZ1RAoRPbCxum9Grlv5aeksu2H8BiKehBYooU2LFiOQ= go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= @@ -243,8 +247,9 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= diff --git a/clash-meta/component/cidr/ipcidr_set.go b/clash-meta/component/cidr/ipcidr_set.go index 521fabab13..4907146039 100644 --- a/clash-meta/component/cidr/ipcidr_set.go +++ b/clash-meta/component/cidr/ipcidr_set.go @@ -57,6 +57,16 @@ func (set *IpCidrSet) Merge() error { return nil } +func (set *IpCidrSet) Foreach(f func(prefix netip.Prefix) bool) { + for _, r := range set.rr { + for _, prefix := range r.Prefixes() { + if !f(prefix) { + return + } + } + } +} + // ToIPSet not safe convert to *netipx.IPSet // be careful, must be used after Merge func (set *IpCidrSet) ToIPSet() *netipx.IPSet { diff --git a/clash-meta/component/trie/domain.go b/clash-meta/component/trie/domain.go index 3decbb0255..db30402ede 100644 --- a/clash-meta/component/trie/domain.go +++ b/clash-meta/component/trie/domain.go @@ -123,16 +123,18 @@ func (t *DomainTrie[T]) Optimize() { t.root.optimize() } -func (t *DomainTrie[T]) Foreach(print func(domain string, data T)) { +func (t *DomainTrie[T]) Foreach(fn func(domain string, data T) bool) { for key, data := range t.root.getChildren() { - recursion([]string{key}, data, print) + recursion([]string{key}, data, fn) if data != nil && data.inited { - print(joinDomain([]string{key}), data.data) + if !fn(joinDomain([]string{key}), data.data) { + return + } } } } -func recursion[T any](items []string, node *Node[T], fn func(domain string, data T)) { +func recursion[T any](items []string, node *Node[T], fn func(domain string, data T) bool) bool { for key, data := range node.getChildren() { newItems := append([]string{key}, items...) if data != nil && data.inited { @@ -140,10 +142,15 @@ func recursion[T any](items []string, node *Node[T], fn func(domain string, data if domain[0] == domainStepByte { domain = complexWildcard + domain } - fn(domain, data.Data()) + if !fn(domain, data.Data()) { + return false + } + } + if !recursion(newItems, data, fn) { + return false } - recursion(newItems, data, fn) } + return true } func joinDomain(items []string) string { diff --git a/clash-meta/component/trie/domain_set.go b/clash-meta/component/trie/domain_set.go index 860d1235d5..7778d13379 100644 --- a/clash-meta/component/trie/domain_set.go +++ b/clash-meta/component/trie/domain_set.go @@ -28,8 +28,9 @@ type qElt struct{ s, e, col int } // NewDomainSet creates a new *DomainSet struct, from a DomainTrie. func (t *DomainTrie[T]) NewDomainSet() *DomainSet { reserveDomains := make([]string, 0) - t.Foreach(func(domain string, data T) { + t.Foreach(func(domain string, data T) bool { reserveDomains = append(reserveDomains, utils.Reverse(domain)) + return true }) // ensure that the same prefix is continuous // and according to the ascending sequence of length @@ -136,6 +137,41 @@ func (ss *DomainSet) Has(key string) bool { } +func (ss *DomainSet) keys(f func(key string) bool) { + var currentKey []byte + var traverse func(int, int) bool + traverse = func(nodeId, bmIdx int) bool { + if getBit(ss.leaves, nodeId) != 0 { + if !f(string(currentKey)) { + return false + } + } + + for ; ; bmIdx++ { + if getBit(ss.labelBitmap, bmIdx) != 0 { + return true + } + nextLabel := ss.labels[bmIdx-nodeId] + currentKey = append(currentKey, nextLabel) + nextNodeId := countZeros(ss.labelBitmap, ss.ranks, bmIdx+1) + nextBmIdx := selectIthOne(ss.labelBitmap, ss.ranks, ss.selects, nextNodeId-1) + 1 + if !traverse(nextNodeId, nextBmIdx) { + return false + } + currentKey = currentKey[:len(currentKey)-1] + } + } + + traverse(0, 0) + return +} + +func (ss *DomainSet) Foreach(f func(key string) bool) { + ss.keys(func(key string) bool { + return f(utils.Reverse(key)) + }) +} + func setBit(bm *[]uint64, i int, v int) { for i>>6 >= len(*bm) { *bm = append(*bm, 0) diff --git a/clash-meta/component/trie/domain_set_test.go b/clash-meta/component/trie/domain_set_test.go index 77106d5ffc..e343d11d1c 100644 --- a/clash-meta/component/trie/domain_set_test.go +++ b/clash-meta/component/trie/domain_set_test.go @@ -1,12 +1,29 @@ package trie_test import ( + "golang.org/x/exp/slices" "testing" "github.com/metacubex/mihomo/component/trie" "github.com/stretchr/testify/assert" ) +func testDump(t *testing.T, tree *trie.DomainTrie[struct{}], set *trie.DomainSet) { + var dataSrc []string + tree.Foreach(func(domain string, data struct{}) bool { + dataSrc = append(dataSrc, domain) + return true + }) + slices.Sort(dataSrc) + var dataSet []string + set.Foreach(func(key string) bool { + dataSet = append(dataSet, key) + return true + }) + slices.Sort(dataSet) + assert.Equal(t, dataSrc, dataSet) +} + func TestDomainSet(t *testing.T) { tree := trie.New[struct{}]() domainSet := []string{ @@ -33,6 +50,7 @@ func TestDomainSet(t *testing.T) { assert.True(t, set.Has("google.com")) assert.False(t, set.Has("qq.com")) assert.False(t, set.Has("www.baidu.com")) + testDump(t, tree, set) } func TestDomainSetComplexWildcard(t *testing.T) { @@ -55,6 +73,7 @@ func TestDomainSetComplexWildcard(t *testing.T) { assert.False(t, set.Has("google.com")) assert.True(t, set.Has("www.baidu.com")) assert.True(t, set.Has("test.test.baidu.com")) + testDump(t, tree, set) } func TestDomainSetWildcard(t *testing.T) { @@ -82,4 +101,5 @@ func TestDomainSetWildcard(t *testing.T) { assert.False(t, set.Has("a.www.google.com")) assert.False(t, set.Has("test.qq.com")) assert.False(t, set.Has("test.test.test.qq.com")) + testDump(t, tree, set) } diff --git a/clash-meta/component/trie/domain_test.go b/clash-meta/component/trie/domain_test.go index 4c5d8002d8..916f61076d 100644 --- a/clash-meta/component/trie/domain_test.go +++ b/clash-meta/component/trie/domain_test.go @@ -121,8 +121,9 @@ func TestTrie_Foreach(t *testing.T) { assert.NoError(t, tree.Insert(domain, localIP)) } count := 0 - tree.Foreach(func(domain string, data netip.Addr) { + tree.Foreach(func(domain string, data netip.Addr) bool { count++ + return true }) assert.Equal(t, 7, count) } diff --git a/clash-meta/docs/config.yaml b/clash-meta/docs/config.yaml index 669c8be7ef..d7c686d01f 100644 --- a/clash-meta/docs/config.yaml +++ b/clash-meta/docs/config.yaml @@ -944,10 +944,17 @@ rule-providers: type: file rule3: # mrs类型ruleset,目前仅支持domain和ipcidr(即不支持classical), - # behavior=domain,format=yaml 可以通过“mihomo convert-ruleset domain yaml XXX.yaml XXX.mrs”转换得到 - # behavior=domain,format=text 可以通过“mihomo convert-ruleset domain text XXX.text XXX.mrs”转换得到 - # behavior=ipcidr,format=yaml 可以通过“mihomo convert-ruleset ipcidr yaml XXX.yaml XXX.mrs”转换得到 - # behavior=ipcidr,format=text 可以通过“mihomo convert-ruleset ipcidr text XXX.text XXX.mrs”转换得到 + # + # 对于behavior=domain: + # - format=yaml 可以通过“mihomo convert-ruleset domain yaml XXX.yaml XXX.mrs”转换到mrs格式 + # - format=text 可以通过“mihomo convert-ruleset domain text XXX.text XXX.mrs”转换到mrs格式 + # - XXX.mrs 可以通过"mihomo convert-ruleset domain mrs XXX.mrs XXX.text"转换回text格式(暂不支持转换回ymal格式) + # + # 对于behavior=ipcidr: + # - format=yaml 可以通过“mihomo convert-ruleset ipcidr yaml XXX.yaml XXX.mrs”转换到mrs格式 + # - format=text 可以通过“mihomo convert-ruleset ipcidr text XXX.text XXX.mrs”转换到mrs格式 + # - XXX.mrs 可以通过"mihomo convert-ruleset ipcidr mrs XXX.mrs XXX.text"转换回text格式(暂不支持转换回ymal格式) + # type: http url: "url" format: mrs diff --git a/clash-meta/rules/provider/domain_strategy.go b/clash-meta/rules/provider/domain_strategy.go index a999f5bd1c..b893f038b2 100644 --- a/clash-meta/rules/provider/domain_strategy.go +++ b/clash-meta/rules/provider/domain_strategy.go @@ -9,6 +9,8 @@ import ( C "github.com/metacubex/mihomo/constant" P "github.com/metacubex/mihomo/constant/provider" "github.com/metacubex/mihomo/log" + + "golang.org/x/exp/slices" ) type domainStrategy struct { @@ -78,6 +80,26 @@ func (d *domainStrategy) WriteMrs(w io.Writer) error { return d.domainSet.WriteBin(w) } +func (d *domainStrategy) DumpMrs(f func(key string) bool) { + if d.domainSet != nil { + var keys []string + d.domainSet.Foreach(func(key string) bool { + keys = append(keys, key) + return true + }) + slices.Sort(keys) + + for _, key := range keys { + if _, ok := slices.BinarySearch(keys, "+."+key); ok { + continue // ignore the rules added by trie internal processing + } + if !f(key) { + return + } + } + } +} + var _ mrsRuleStrategy = (*domainStrategy)(nil) func NewDomainStrategy() *domainStrategy { diff --git a/clash-meta/rules/provider/ipcidr_strategy.go b/clash-meta/rules/provider/ipcidr_strategy.go index 87cf7a2d8e..9efffed96b 100644 --- a/clash-meta/rules/provider/ipcidr_strategy.go +++ b/clash-meta/rules/provider/ipcidr_strategy.go @@ -3,6 +3,7 @@ package provider import ( "errors" "io" + "net/netip" "github.com/metacubex/mihomo/component/cidr" C "github.com/metacubex/mihomo/constant" @@ -82,6 +83,14 @@ func (i *ipcidrStrategy) WriteMrs(w io.Writer) error { return i.cidrSet.WriteBin(w) } +func (i *ipcidrStrategy) DumpMrs(f func(key string) bool) { + if i.cidrSet != nil { + i.cidrSet.Foreach(func(prefix netip.Prefix) bool { + return f(prefix.String()) + }) + } +} + func (i *ipcidrStrategy) ToIpCidr() *netipx.IPSet { return i.cidrSet.ToIPSet() } diff --git a/clash-meta/rules/provider/mrs_converter.go b/clash-meta/rules/provider/mrs_converter.go index a08301982e..edc24e7eea 100644 --- a/clash-meta/rules/provider/mrs_converter.go +++ b/clash-meta/rules/provider/mrs_converter.go @@ -3,6 +3,7 @@ package provider import ( "encoding/binary" "errors" + "fmt" "io" "os" @@ -21,6 +22,17 @@ func ConvertToMrs(buf []byte, behavior P.RuleBehavior, format P.RuleFormat, w io return errors.New("empty rule") } if _strategy, ok := strategy.(mrsRuleStrategy); ok { + if format == P.MrsRule { // export to TextRule + _strategy.DumpMrs(func(key string) bool { + _, err = fmt.Fprintln(w, key) + if err != nil { + return false + } + return true + }) + return nil + } + var encoder *zstd.Encoder encoder, err = zstd.NewWriter(w) if err != nil { diff --git a/clash-meta/rules/provider/provider.go b/clash-meta/rules/provider/provider.go index 8c5d7f9407..b9524c35e6 100644 --- a/clash-meta/rules/provider/provider.go +++ b/clash-meta/rules/provider/provider.go @@ -58,6 +58,7 @@ type mrsRuleStrategy interface { ruleStrategy FromMrs(r io.Reader, count int) error WriteMrs(w io.Writer) error + DumpMrs(f func(key string) bool) } func (rp *ruleSetProvider) Type() P.ProviderType { diff --git a/clash-nyanpasu/.husky/commit-msg b/clash-nyanpasu/.husky/commit-msg index e7800ba6da..38bf73a126 100644 --- a/clash-nyanpasu/.husky/commit-msg +++ b/clash-nyanpasu/.husky/commit-msg @@ -1,4 +1 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" - pnpm commitlint --edit ${1} diff --git a/clash-nyanpasu/.husky/pre-commit b/clash-nyanpasu/.husky/pre-commit index 88dacb6c5b..b35cd4654c 100755 --- a/clash-nyanpasu/.husky/pre-commit +++ b/clash-nyanpasu/.husky/pre-commit @@ -1,6 +1,3 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" - # If tty is available, apply fix from https://github.com/typicode/husky/issues/968#issuecomment-1176848345 if sh -c ": >/dev/tty" >/dev/null 2>/dev/null; then exec >/dev/tty 2>&1; fi diff --git a/clash-nyanpasu/backend/Cargo.lock b/clash-nyanpasu/backend/Cargo.lock index aec57bff53..ee3ab9c983 100644 --- a/clash-nyanpasu/backend/Cargo.lock +++ b/clash-nyanpasu/backend/Cargo.lock @@ -477,6 +477,17 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "atomic_enum" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99e1aca718ea7b89985790c94aad72d77533063fe00bc497bb79a7c2dae6a661" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + [[package]] name = "auto-launch" version = "0.5.0" @@ -517,6 +528,64 @@ dependencies = [ "arrayvec 0.7.4", ] +[[package]] +name = "axum" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf" +dependencies = [ + "async-trait", + "axum-core", + "base64 0.21.7", + "bytes", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.4.1", + "hyper-util", + "itoa 1.0.11", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sha1", + "sync_wrapper 1.0.1", + "tokio", + "tokio-tungstenite", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper 0.1.2", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "backon" version = "0.4.4" @@ -937,6 +1006,7 @@ dependencies = [ "ansi-str", "anyhow", "async-trait", + "atomic_enum", "auto-launch", "backon", "base64 0.22.1", @@ -1417,6 +1487,12 @@ dependencies = [ "parking_lot_core", ] +[[package]] +name = "data-encoding" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" + [[package]] name = "deelevate" version = "0.2.0" @@ -1623,7 +1699,7 @@ dependencies = [ [[package]] name = "dirs-utils" version = "0.1.0" -source = "git+https://github.com/LibNyanpasu/nyanpasu-utils.git#a4d554586049a548528ed88c3f33a44450610f51" +source = "git+https://github.com/LibNyanpasu/nyanpasu-utils.git#28f4f6234b67a2d806bd28a90112cb1da67bd0f6" dependencies = [ "dirs-next", "thiserror", @@ -3575,6 +3651,12 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + [[package]] name = "maybe-rayon" version = "0.1.1" @@ -3977,10 +4059,11 @@ dependencies = [ [[package]] name = "nyanpasu-ipc" -version = "1.0.3" -source = "git+https://github.com/LibNyanpasu/nyanpasu-service.git#10b455d0ee1c366f1ed3fcaa26b37764ab00c69b" +version = "1.0.4" +source = "git+https://github.com/LibNyanpasu/nyanpasu-service.git#0978120ffa67cda1d50fd5cf80bda320d7e744f3" dependencies = [ "anyhow", + "axum", "derive_builder", "futures", "futures-util", @@ -4002,7 +4085,7 @@ dependencies = [ [[package]] name = "nyanpasu-utils" version = "0.1.0" -source = "git+https://github.com/LibNyanpasu/nyanpasu-utils.git#a4d554586049a548528ed88c3f33a44450610f51" +source = "git+https://github.com/LibNyanpasu/nyanpasu-utils.git#28f4f6234b67a2d806bd28a90112cb1da67bd0f6" dependencies = [ "constcat", "derive_builder", @@ -4284,7 +4367,7 @@ dependencies = [ [[package]] name = "os-utils" version = "0.1.0" -source = "git+https://github.com/LibNyanpasu/nyanpasu-utils.git#a4d554586049a548528ed88c3f33a44450610f51" +source = "git+https://github.com/LibNyanpasu/nyanpasu-utils.git#28f4f6234b67a2d806bd28a90112cb1da67bd0f6" dependencies = [ "nix 0.29.0", "shared_child", @@ -5189,7 +5272,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "sync_wrapper", + "sync_wrapper 0.1.2", "system-configuration", "tokio", "tokio-native-tls", @@ -5606,6 +5689,16 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" +dependencies = [ + "itoa 1.0.11", + "serde", +] + [[package]] name = "serde_repr" version = "0.1.19" @@ -6069,6 +6162,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +[[package]] +name = "sync_wrapper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" + [[package]] name = "sys-locale" version = "0.2.4" @@ -6683,9 +6782,9 @@ checksum = "c7c4ceeeca15c8384bbc3e011dbd8fccb7f068a440b752b7d9b32ceb0ca0e2e8" [[package]] name = "tokio" -version = "1.39.1" +version = "1.39.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d040ac2b29ab03b09d4129c2f5bbd012a3ac2f79d38ff506a4bf8dd34b0eac8a" +checksum = "daa4fb1bc778bd6f04cbfc4bb2d06a7396a8f299dc33ea1900cedaa316f467b1" dependencies = [ "backtrace", "bytes", @@ -6730,6 +6829,18 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-tungstenite" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite", +] + [[package]] name = "tokio-util" version = "0.7.11" @@ -6835,6 +6946,7 @@ dependencies = [ "tokio", "tower-layer", "tower-service", + "tracing", ] [[package]] @@ -6855,6 +6967,7 @@ version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ + "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -6988,6 +7101,25 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "tungstenite" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http 1.1.0", + "httparse", + "log", + "rand 0.8.5", + "sha1", + "thiserror", + "url", + "utf-8", +] + [[package]] name = "typenum" version = "1.17.0" diff --git a/clash-nyanpasu/backend/tauri/Cargo.toml b/clash-nyanpasu/backend/tauri/Cargo.toml index 6ca1a9ad50..7c6cd1497e 100644 --- a/clash-nyanpasu/backend/tauri/Cargo.toml +++ b/clash-nyanpasu/backend/tauri/Cargo.toml @@ -18,7 +18,9 @@ rustc_version = "0.4" semver = "1.0" [dependencies] -nyanpasu-ipc = { git = "https://github.com/LibNyanpasu/nyanpasu-service.git" } +nyanpasu-ipc = { git = "https://github.com/LibNyanpasu/nyanpasu-service.git", features = [ + "client", +] } nyanpasu-utils = { git = "https://github.com/LibNyanpasu/nyanpasu-utils.git" } which = "6" anyhow = "1.0" @@ -112,6 +114,7 @@ humansize = "2.1.3" convert_case = "0.6.0" os_pipe = "1.2.0" whoami = "1.5.1" +atomic_enum = "0.3.0" [target.'cfg(target_os = "macos")'.dependencies] cocoa = "0.25.0" diff --git a/clash-nyanpasu/backend/tauri/src/core/clash/core.rs b/clash-nyanpasu/backend/tauri/src/core/clash/core.rs index 0fef62f9fc..16f6dea192 100644 --- a/clash-nyanpasu/backend/tauri/src/core/clash/core.rs +++ b/clash-nyanpasu/backend/tauri/src/core/clash/core.rs @@ -6,7 +6,10 @@ use crate::{ utils::dirs, }; use anyhow::{bail, Result}; -use nyanpasu_ipc::{api::status::CoreState, utils::get_current_ts}; +use nyanpasu_ipc::{ + api::{core::start::CoreStartReq, status::CoreState}, + utils::get_current_ts, +}; use nyanpasu_utils::{ core::{ instance::{CoreInstance, CoreInstanceBuilder}, @@ -40,13 +43,18 @@ pub enum RunType { impl Default for RunType { fn default() -> Self { - let enable_service = Config::verge() - .latest() - .enable_service_mode - .unwrap_or(false); - if enable_service { + let enable_service = { + *Config::verge() + .latest() + .enable_service_mode + .as_ref() + .unwrap_or(&false) + }; + if enable_service && crate::core::service::ipc::get_ipc_state().is_connected() { + tracing::info!("run core as service"); RunType::Service } else { + tracing::info!("run core as child process"); RunType::Normal } } @@ -59,7 +67,10 @@ enum Instance { stated_changed_at: Arc, kill_flag: Arc, }, - Service, + Service { + config_path: PathBuf, + core_type: nyanpasu_utils::core::CoreType, + }, } impl Instance { @@ -93,7 +104,10 @@ impl Instance { stated_changed_at: Arc::new(AtomicI64::new(get_current_ts())), }) } - RunType::Service => Ok(Instance::Service), + RunType::Service => Ok(Instance::Service { + config_path, + core_type, + }), RunType::Elevated => { todo!() } @@ -111,13 +125,16 @@ impl Instance { let child = child.lock(); child.clone() }; - let is_premium = { + let (is_premium, core_type) = { let child = child.lock(); - matches!( - child.core_type, - nyanpasu_utils::core::CoreType::Clash( - nyanpasu_utils::core::ClashCoreType::ClashPremium - ) + ( + matches!( + child.core_type, + nyanpasu_utils::core::CoreType::Clash( + nyanpasu_utils::core::ClashCoreType::ClashPremium + ) + ), + child.core_type.clone(), ) }; let (tx, mut rx) = tokio::sync::mpsc::channel::>(1); // use mpsc channel just to avoid type moved error, though it never fails @@ -127,7 +144,7 @@ impl Instance { tokio::spawn(async move { match instance.run().await { Ok((_, mut rx)) => { - kill_flag.store(false, Ordering::Relaxed); // reset kill flag + kill_flag.store(false, Ordering::Release); // reset kill flag let mut err_buf: Vec = Vec::with_capacity(6); loop { if let Some(event) = rx.recv().await { @@ -135,19 +152,19 @@ impl Instance { CommandEvent::Stdout(line) => { if is_premium { let log = api::parse_log(line.clone()); - log::info!(target: "app", "[clash]: {}", log); + log::info!(target: "app", "[{}]: {}", core_type, log); } else { - log::info!(target: "app", "[clash]: {}", line); + log::info!(target: "app", "[{}]: {}", core_type, line); } Logger::global().set_log(line); } CommandEvent::Stderr(line) => { - log::error!(target: "app", "[clash]: {}", line); + log::error!(target: "app", "[{}]: {}", core_type, line); err_buf.push(line.clone()); Logger::global().set_log(line); } CommandEvent::Error(e) => { - log::error!(target: "app", "[clash]: {}", e); + log::error!(target: "app", "[{}]: {}", core_type, e); let err = anyhow::anyhow!(format!( "{}\n{}", e, @@ -177,7 +194,7 @@ impl Instance { )); tracing::error!("{}\n{}", err, err_buf.join("\n")); if tx.send(Err(err)).await.is_err() - && !kill_flag.load(Ordering::Relaxed) + && !kill_flag.load(Ordering::Acquire) { std::thread::spawn(move || { block_on(async { @@ -213,14 +230,24 @@ impl Instance { rx.recv().await.unwrap()?; Ok(()) } - Instance::Service => { - todo!() + Instance::Service { + config_path, + core_type, + } => { + let payload = CoreStartReq { + config_file: Cow::Borrowed(config_path), + core_type: Cow::Borrowed(core_type), + }; + nyanpasu_ipc::client::shortcuts::Client::service_default() + .start_core(&payload) + .await?; + Ok(()) } } } pub async fn stop(&self) -> Result<()> { - let state = self.state(); + let state = self.state().await; match self { Instance::Child { child, @@ -230,7 +257,7 @@ impl Instance { if matches!(state.as_ref(), CoreState::Stopped(_)) { anyhow::bail!("core is already stopped"); } - kill_flag.store(true, Ordering::Relaxed); + kill_flag.store(true, Ordering::Release); let child = { let child = child.lock(); child.clone() @@ -239,35 +266,47 @@ impl Instance { stated_changed_at.store(get_current_ts(), Ordering::Relaxed); Ok(()) } - Instance::Service => { - todo!() + Instance::Service { .. } => { + Ok(nyanpasu_ipc::client::shortcuts::Client::service_default() + .stop_core() + .await?) } } } pub async fn restart(&self) -> Result<()> { - let state = self.state(); + let state = self.state().await; if matches!(state.as_ref(), CoreState::Running) { self.stop().await?; } self.start().await } - pub fn state<'a>(&self) -> Cow<'a, CoreState> { + pub async fn state<'a>(&self) -> Cow<'a, CoreState> { match self { Instance::Child { child, .. } => { let this = child.lock(); - Cow::Owned(match this.state() { + Cow::Borrowed(match this.state() { nyanpasu_utils::core::instance::CoreInstanceState::Running => { - CoreState::Running + &CoreState::Running } nyanpasu_utils::core::instance::CoreInstanceState::Stopped => { - CoreState::Stopped(None) + &CoreState::Stopped(None) } }) } - Instance::Service => { - todo!() + Instance::Service { .. } => { + let status = nyanpasu_ipc::client::shortcuts::Client::service_default() + .status() + .await + .map(|info| match info.core_infos.state { + nyanpasu_ipc::api::status::CoreState::Running => CoreState::Running, + nyanpasu_ipc::api::status::CoreState::Stopped(_) => { + CoreState::Stopped(None) + } + }) + .unwrap_or(CoreState::Stopped(None)); + Cow::Owned(status) } } } @@ -331,7 +370,7 @@ impl CoreManager { instance.as_ref().cloned() }; if let Some(instance) = instance.as_ref() { - if matches!(instance.state().as_ref(), CoreState::Running) { + if matches!(instance.state().await.as_ref(), CoreState::Running) { log::debug!(target: "app", "core is already running, stop it first..."); instance.stop().await?; } @@ -343,7 +382,7 @@ impl CoreManager { Config::clash() .latest() .prepare_external_controller_port()?; - let instance = Arc::new(Instance::try_new(RunType::Normal)?); + let instance = Arc::new(Instance::try_new(RunType::default())?); #[cfg(target_os = "macos")] { @@ -406,7 +445,7 @@ impl CoreManager { this.take() }; if let Some(instance) = instance { - if matches!(instance.state().as_ref(), CoreState::Running) { + if matches!(instance.state().await.as_ref(), CoreState::Running) { log::debug!(target: "app", "core is running, stop it first..."); instance.stop().await?; } diff --git a/clash-nyanpasu/backend/tauri/src/core/mod.rs b/clash-nyanpasu/backend/tauri/src/core/mod.rs index e09ee06481..c95a84412e 100644 --- a/clash-nyanpasu/backend/tauri/src/core/mod.rs +++ b/clash-nyanpasu/backend/tauri/src/core/mod.rs @@ -8,7 +8,6 @@ pub mod sysopt; pub mod tasks; pub mod tray; pub mod updater; -pub mod win_service; pub mod win_uwp; pub use self::clash::core::*; pub mod migration; diff --git a/clash-nyanpasu/backend/tauri/src/core/service/control.rs b/clash-nyanpasu/backend/tauri/src/core/service/control.rs index 7f1b5e413c..6c618a4ca2 100644 --- a/clash-nyanpasu/backend/tauri/src/core/service/control.rs +++ b/clash-nyanpasu/backend/tauri/src/core/service/control.rs @@ -41,6 +41,28 @@ pub async fn install_service() -> anyhow::Result<()> { child.code().unwrap() ); } + // Due to most platform, the service will be started automatically after installed + if !super::ipc::HEALTH_CHECK_RUNNING.load(std::sync::atomic::Ordering::Relaxed) { + super::ipc::spawn_health_check(); + } + Ok(()) +} + +pub async fn update_service() -> anyhow::Result<()> { + let child = tokio::task::spawn_blocking(move || { + RunasCommand::new(SERVICE_PATH.as_path()) + .args(&["update"]) + .gui(true) + .show(true) + .status() + }) + .await??; + if !child.success() { + anyhow::bail!( + "failed to update service, exit code: {}", + child.code().unwrap() + ); + } Ok(()) } @@ -59,6 +81,12 @@ pub async fn uninstall_service() -> anyhow::Result<()> { child.code().unwrap() ); } + let _ = super::ipc::KILL_FLAG.compare_exchange( + false, + true, + std::sync::atomic::Ordering::Acquire, + std::sync::atomic::Ordering::Relaxed, + ); Ok(()) } @@ -77,6 +105,9 @@ pub async fn start_service() -> anyhow::Result<()> { child.code().unwrap() ); } + if !super::ipc::HEALTH_CHECK_RUNNING.load(std::sync::atomic::Ordering::Acquire) { + super::ipc::spawn_health_check(); + } Ok(()) } @@ -95,6 +126,12 @@ pub async fn stop_service() -> anyhow::Result<()> { child.code().unwrap() ); } + let _ = super::ipc::KILL_FLAG.compare_exchange_weak( + false, + true, + std::sync::atomic::Ordering::Acquire, + std::sync::atomic::Ordering::Relaxed, + ); Ok(()) } @@ -113,10 +150,13 @@ pub async fn restart_service() -> anyhow::Result<()> { child.code().unwrap() ); } + if !super::ipc::HEALTH_CHECK_RUNNING.load(std::sync::atomic::Ordering::Acquire) { + super::ipc::spawn_health_check(); + } Ok(()) } -pub async fn status() -> anyhow::Result { +pub async fn status<'a>() -> anyhow::Result> { let child = tokio::process::Command::new(SERVICE_PATH.as_path()) .args(["status", "--json"]) .output() @@ -128,6 +168,5 @@ pub async fn status() -> anyhow::Result { ); } let mut status = String::from_utf8(child.stdout)?; - let status: nyanpasu_ipc::types::ServiceStatus = unsafe { simd_json::from_str(&mut status)? }; - Ok(status) + Ok(unsafe { simd_json::serde::from_str(&mut status)? }) } diff --git a/clash-nyanpasu/backend/tauri/src/core/service/ipc.rs b/clash-nyanpasu/backend/tauri/src/core/service/ipc.rs index ffe170603a..e451ee0052 100644 --- a/clash-nyanpasu/backend/tauri/src/core/service/ipc.rs +++ b/clash-nyanpasu/backend/tauri/src/core/service/ipc.rs @@ -1,4 +1,84 @@ +use std::sync::atomic::{AtomicBool, Ordering}; + +use atomic_enum::atomic_enum; + +use delay_timer::prelude::future_lite::block_on; +use nyanpasu_ipc::types::ServiceStatus; +use serde::Serialize; +use tracing_attributes::instrument; + +#[derive(PartialEq, Eq, Serialize)] +#[serde(rename_all = "snake_case")] +#[atomic_enum] pub enum IpcState { Connected, Disconnected, } + +impl IpcState { + pub fn is_connected(&self) -> bool { + *self == IpcState::Connected + } +} + +static IPC_STATE: AtomicIpcState = AtomicIpcState::new(IpcState::Disconnected); +pub(super) static KILL_FLAG: AtomicBool = AtomicBool::new(false); +pub(super) static HEALTH_CHECK_RUNNING: AtomicBool = AtomicBool::new(false); + +pub fn get_ipc_state() -> IpcState { + IPC_STATE.load(Ordering::Relaxed) +} + +pub(super) fn set_ipc_state(state: IpcState) { + IPC_STATE.store(state, Ordering::Relaxed); +} + +pub(super) fn spawn_health_check() { + KILL_FLAG.store(false, Ordering::Relaxed); + std::thread::spawn(|| { + HEALTH_CHECK_RUNNING.store(true, Ordering::Release); + block_on(async { + loop { + if KILL_FLAG.load(Ordering::Acquire) { + HEALTH_CHECK_RUNNING.store(false, Ordering::Release); + break; + } + health_check().await; + tokio::time::sleep(std::time::Duration::from_secs(5)).await; + } + }) + }); +} + +#[instrument] +async fn health_check() { + match super::control::status().await { + Ok(info) => match info.status { + ServiceStatus::Running => { + let _ = IPC_STATE.compare_exchange_weak( + IpcState::Disconnected, + IpcState::Connected, + Ordering::SeqCst, + Ordering::Relaxed, + ); + } + ServiceStatus::Stopped | ServiceStatus::NotInstalled => { + let _ = IPC_STATE.compare_exchange_weak( + IpcState::Connected, + IpcState::Disconnected, + Ordering::SeqCst, + Ordering::Relaxed, + ); + } + }, + Err(e) => { + tracing::error!("IPC health check failed: {}", e); + let _ = IPC_STATE.compare_exchange_weak( + IpcState::Connected, + IpcState::Disconnected, + Ordering::SeqCst, + Ordering::Relaxed, + ); + } + } +} diff --git a/clash-nyanpasu/backend/tauri/src/core/service/mod.rs b/clash-nyanpasu/backend/tauri/src/core/service/mod.rs index d76476eade..0a0dc25c0a 100644 --- a/clash-nyanpasu/backend/tauri/src/core/service/mod.rs +++ b/clash-nyanpasu/backend/tauri/src/core/service/mod.rs @@ -1,8 +1,9 @@ use std::path::PathBuf; +use nyanpasu_ipc::types::StatusInfo; use once_cell::sync::Lazy; -use crate::utils::dirs::app_install_dir; +use crate::{config::Config, utils::dirs::app_install_dir}; pub mod control; pub mod ipc; @@ -12,3 +13,24 @@ static SERVICE_PATH: Lazy = Lazy::new(|| { let app_path = app_install_dir().unwrap(); app_path.join(format!("{}{}", SERVICE_NAME, std::env::consts::EXE_SUFFIX)) }); + +pub async fn init_service() { + let enable_service = { + *Config::verge() + .latest() + .enable_service_mode + .as_ref() + .unwrap_or(&false) + }; + if let Ok(StatusInfo { + status: nyanpasu_ipc::types::ServiceStatus::Running, + .. + }) = control::status().await + { + if !enable_service { + control::stop_service().await.unwrap(); + } else { + ipc::spawn_health_check() + } + } +} diff --git a/clash-nyanpasu/backend/tauri/src/core/win_service.rs b/clash-nyanpasu/backend/tauri/src/core/win_service.rs deleted file mode 100644 index 4521cd1d56..0000000000 --- a/clash-nyanpasu/backend/tauri/src/core/win_service.rs +++ /dev/null @@ -1,181 +0,0 @@ -#![cfg(target_os = "windows")] - -use crate::{ - config::{nyanpasu::ClashCore, Config}, - utils::dirs, -}; -use anyhow::{bail, Context, Result}; -use deelevate::{PrivilegeLevel, Token}; -use runas::Command as RunasCommand; -use serde::{Deserialize, Serialize}; -use std::{ - collections::HashMap, env::current_exe, os::windows::process::CommandExt, path::PathBuf, - process::Command as StdCommand, time::Duration, -}; -use tokio::time::sleep; - -const SERVICE_URL: &str = "http://127.0.0.1:33211"; - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct ResponseBody { - pub core_type: Option, - pub bin_path: String, - pub config_dir: String, - pub log_file: String, -} - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct JsonResponse { - pub code: u64, - pub msg: String, - pub data: Option, -} - -/// Install the Clash Nyanpasu Service -/// 该函数应该在协程或者线程中执行,避免UAC弹窗阻塞主线程 -pub async fn install_service() -> Result<()> { - let binary_path = dirs::service_path()?; - let install_path = binary_path.with_file_name("install-service.exe"); - - if !install_path.exists() { - bail!("installer exe not found"); - } - - let token = Token::with_current_process()?; - let level = token.privilege_level()?; - - let status = match level { - PrivilegeLevel::NotPrivileged => RunasCommand::new(install_path).show(false).status()?, - _ => StdCommand::new(install_path) - .creation_flags(0x08000000) - .status()?, - }; - - if !status.success() { - bail!( - "failed to install service with status {}", - status.code().unwrap() - ); - } - - Ok(()) -} - -/// Uninstall the Clash Nyanpasu Service -/// 该函数应该在协程或者线程中执行,避免UAC弹窗阻塞主线程 -pub async fn uninstall_service() -> Result<()> { - let binary_path = dirs::service_path()?; - let uninstall_path = binary_path.with_file_name("uninstall-service.exe"); - - if !uninstall_path.exists() { - bail!("uninstaller exe not found"); - } - - let token = Token::with_current_process()?; - let level = token.privilege_level()?; - - let status = match level { - PrivilegeLevel::NotPrivileged => RunasCommand::new(uninstall_path).show(false).status()?, - _ => StdCommand::new(uninstall_path) - .creation_flags(0x08000000) - .status()?, - }; - - if !status.success() { - bail!( - "failed to uninstall service with status {}", - status.code().unwrap() - ); - } - - Ok(()) -} - -/// check the windows service status -pub async fn check_service() -> Result { - let url = format!("{SERVICE_URL}/get_clash"); - let response = reqwest::ClientBuilder::new() - .no_proxy() - .build()? - .get(url) - .send() - .await - .context("failed to connect to the Clash Nyanpasu Service")? - .json::() - .await - .context("failed to parse the Clash Nyanpasu Service response")?; - - Ok(response) -} - -/// start the clash by service -pub(super) async fn run_core_by_service(config_file: &PathBuf) -> Result<()> { - let status = check_service().await?; - - if status.code == 0 { - stop_core_by_service().await?; - sleep(Duration::from_secs(1)).await; - } - - let clash_core = { Config::verge().latest().clash_core.clone() }; - let clash_core = clash_core.unwrap_or(ClashCore::ClashPremium); - - let clash_bin = format!("{clash_core}.exe"); - let bin_path = current_exe()?.with_file_name(clash_bin); - let bin_path = dirs::path_to_str(&bin_path)?; - - let config_dir = dirs::app_home_dir()?; - let config_dir = dirs::path_to_str(&config_dir)?; - - let log_path = dirs::service_log_file()?; - let log_path = dirs::path_to_str(&log_path)?; - - let config_file = dirs::path_to_str(config_file)?; - - let mut map = HashMap::new(); - // TODO: confirm the backend service logic - let core_type = clash_core.to_string(); - map.insert("core_type", core_type.as_str()); - map.insert("bin_path", bin_path); - map.insert("config_dir", config_dir); - map.insert("config_file", config_file); - map.insert("log_file", log_path); - - let url = format!("{SERVICE_URL}/start_clash"); - let res = reqwest::ClientBuilder::new() - .no_proxy() - .build()? - .post(url) - .json(&map) - .send() - .await? - .json::() - .await - .context("failed to connect to the Clash Nyanpasu Service")?; - - if res.code != 0 { - bail!(res.msg); - } - - Ok(()) -} - -/// stop the clash by service -pub(super) async fn stop_core_by_service() -> Result<()> { - let url = format!("{SERVICE_URL}/stop_clash"); - let res = reqwest::ClientBuilder::new() - .no_proxy() - .build()? - .post(url) - .send() - .await? - .json::() - .await - .context("failed to connect to the Clash Nyanpasu Service")?; - - if res.code != 0 { - bail!(res.msg); - } - - Ok(()) -} diff --git a/clash-nyanpasu/backend/tauri/src/ipc.rs b/clash-nyanpasu/backend/tauri/src/ipc.rs index f17c6c2767..ee643054ab 100644 --- a/clash-nyanpasu/backend/tauri/src/ipc.rs +++ b/clash-nyanpasu/backend/tauri/src/ipc.rs @@ -464,42 +464,38 @@ pub mod uwp { } } -#[cfg(windows)] pub mod service { use super::{wrap_err, CmdResult}; - use crate::core::win_service; + use crate::core::service; #[tauri::command] - pub async fn check_service() -> CmdResult { - wrap_err!(win_service::check_service().await) + pub async fn status_service<'a>() -> CmdResult> { + wrap_err!(service::control::status().await) } #[tauri::command] pub async fn install_service() -> CmdResult { - wrap_err!(win_service::install_service().await) + wrap_err!(service::control::install_service().await) } #[tauri::command] pub async fn uninstall_service() -> CmdResult { - wrap_err!(win_service::uninstall_service().await) + wrap_err!(service::control::uninstall_service().await) } -} - -#[cfg(not(windows))] -pub mod service { - use super::*; #[tauri::command] - pub async fn check_service() -> CmdResult { - Ok(()) + pub async fn start_service() -> CmdResult { + wrap_err!(service::control::start_service().await) } + #[tauri::command] - pub async fn install_service() -> CmdResult { - Ok(()) + pub async fn stop_service() -> CmdResult { + wrap_err!(service::control::stop_service().await) } + #[tauri::command] - pub async fn uninstall_service() -> CmdResult { - Ok(()) + pub async fn restart_service() -> CmdResult { + wrap_err!(service::control::restart_service().await) } } diff --git a/clash-nyanpasu/backend/tauri/src/main.rs b/clash-nyanpasu/backend/tauri/src/main.rs index ff62a00389..6de76bcf3c 100644 --- a/clash-nyanpasu/backend/tauri/src/main.rs +++ b/clash-nyanpasu/backend/tauri/src/main.rs @@ -189,9 +189,12 @@ fn main() -> std::io::Result<()> { ipc::get_custom_app_dir, ipc::set_custom_app_dir, // service mode - ipc::service::check_service, + ipc::service::status_service, ipc::service::install_service, ipc::service::uninstall_service, + ipc::service::start_service, + ipc::service::stop_service, + ipc::service::restart_service, ipc::is_portable, ipc::get_proxies, ipc::select_proxy, diff --git a/clash-nyanpasu/backend/tauri/src/utils/init/mod.rs b/clash-nyanpasu/backend/tauri/src/utils/init/mod.rs index 4a65d91cb0..d8c0e80344 100644 --- a/clash-nyanpasu/backend/tauri/src/utils/init/mod.rs +++ b/clash-nyanpasu/backend/tauri/src/utils/init/mod.rs @@ -226,62 +226,37 @@ pub fn init_resources() -> Result<()> { /// initialize service resources /// after tauri setup -#[cfg(target_os = "windows")] pub fn init_service() -> Result<()> { - let service_dir = dirs::service_dir()?; - let res_dir = dirs::app_resources_dir()?; + use nyanpasu_utils::runtime::block_on; - if !service_dir.exists() { - let _ = fs::create_dir_all(&service_dir); - } - if !res_dir.exists() { - let _ = fs::create_dir_all(&res_dir); - } - - let file_list = [ - "clash-verge-service.exe", - "install-service.exe", - "uninstall-service.exe", - ]; - - // copy the resource file - // if the source file is newer than the destination file, copy it over - for file in file_list.iter() { - let src_path = res_dir.join(file); - let dest_path = service_dir.join(file); - - let handle_copy = || { - match fs::copy(&src_path, &dest_path) { - Ok(_) => log::debug!(target: "app", "resources copied '{file}'"), - Err(err) => { - log::error!(target: "app", "failed to copy resources '{file}', {err}") - } - }; + block_on(async move { + let enable_service = { + *Config::verge() + .latest() + .enable_service_mode + .as_ref() + .unwrap_or(&false) }; - - if src_path.exists() && !dest_path.exists() { - handle_copy(); - continue; + if enable_service { + match crate::core::service::control::status().await { + Ok(status) => { + if let Some(info) = status.server { + let server_ver = semver::Version::parse(info.version.as_ref()).unwrap(); + let app_ver = semver::Version::parse(status.version.as_ref()).unwrap(); + if app_ver > server_ver { + if let Err(e) = crate::core::service::control::update_service().await { + log::error!(target: "app", "failed to update service: {:?}", e); + } + } + } + } + Err(e) => { + log::error!(target: "app", "failed to get service status: {:?}", e); + } + } } - - let src_modified = fs::metadata(&src_path).and_then(|m| m.modified()); - let dest_modified = fs::metadata(&dest_path).and_then(|m| m.modified()); - - match (src_modified, dest_modified) { - (Ok(src_modified), Ok(dest_modified)) => { - if src_modified > dest_modified { - handle_copy(); - } else { - log::debug!(target: "app", "skipping resource copy '{file}'"); - } - } - _ => { - log::debug!(target: "app", "failed to get modified '{file}'"); - handle_copy(); - } - }; - } - + crate::core::service::init_service().await; + }); Ok(()) } diff --git a/clash-nyanpasu/backend/tauri/src/utils/resolve.rs b/clash-nyanpasu/backend/tauri/src/utils/resolve.rs index efb745203a..e9d00ed6b7 100644 --- a/clash-nyanpasu/backend/tauri/src/utils/resolve.rs +++ b/clash-nyanpasu/backend/tauri/src/utils/resolve.rs @@ -76,7 +76,6 @@ pub fn resolve_setup(app: &mut App) { handle::Handle::global().init(app.app_handle()); log_err!(init::init_resources()); - #[cfg(target_os = "windows")] log_err!(init::init_service()); // 处理随机端口 diff --git a/clash-nyanpasu/frontend/interface/ipc/useNyanpasu.ts b/clash-nyanpasu/frontend/interface/ipc/useNyanpasu.ts index 848b04bde4..7f1ee454c6 100644 --- a/clash-nyanpasu/frontend/interface/ipc/useNyanpasu.ts +++ b/clash-nyanpasu/frontend/interface/ipc/useNyanpasu.ts @@ -62,7 +62,7 @@ export const useNyanpasu = (options?: { const getSystemProxy = useSWR("getSystemProxy", service.getSystemProxy); - const getServiceStatus = useSWR("getServiceStatus", service.checkService); + const getServiceStatus = useSWR("getServiceStatus", service.statusService); const setServiceStatus = async (type: "install" | "uninstall") => { if (type === "install") { diff --git a/clash-nyanpasu/frontend/interface/service/tauri.ts b/clash-nyanpasu/frontend/interface/service/tauri.ts index bb151a1ab9..3f73824f0e 100644 --- a/clash-nyanpasu/frontend/interface/service/tauri.ts +++ b/clash-nyanpasu/frontend/interface/service/tauri.ts @@ -120,19 +120,15 @@ export const getSystemProxy = async () => { return await invoke("get_sys_proxy"); }; -export const checkService = async () => { +export const statusService = async () => { try { - const result = await invoke<{ code: number }>("check_service"); - - if (result?.code === 0) { - return "active"; - } else if (result?.code === 400) { - return "installed"; - } else { - return "unknown"; - } + const result = await invoke<{ + status: "running" | "stopped" | "not_installed"; + }>("status_service"); + return result.status; } catch (e) { - return "uninstall"; + console.error(e); + return "not_installed"; } }; @@ -144,6 +140,18 @@ export const uninstallService = async () => { return await invoke("uninstall_service"); }; +export const startService = async () => { + return await invoke("start_service"); +}; + +export const stopService = async () => { + return await invoke("stop_service"); +}; + +export const restartService = async () => { + return await invoke("restart_service"); +}; + export const openAppConfigDir = async () => { return await invoke("open_app_config_dir"); }; diff --git a/clash-nyanpasu/frontend/nyanpasu/src/components/profiles/modules/side-chain.tsx b/clash-nyanpasu/frontend/nyanpasu/src/components/profiles/modules/side-chain.tsx index 8e57f2dd70..2a36fa68c5 100644 --- a/clash-nyanpasu/frontend/nyanpasu/src/components/profiles/modules/side-chain.tsx +++ b/clash-nyanpasu/frontend/nyanpasu/src/components/profiles/modules/side-chain.tsx @@ -76,8 +76,8 @@ export const SideChain = ({ global, profile, onChainEdit }: SideChainProps) => { const handleChainClick = useLockFn(async (uid: string) => { const chains = global - ? getProfiles.data?.chain ?? [] - : profile?.chains ?? []; + ? (getProfiles.data?.chain ?? []) + : (profile?.chains ?? []); const updatedChains = chains.includes(uid) ? chains.filter((chain) => chain !== uid) diff --git a/clash-nyanpasu/manifest/version.json b/clash-nyanpasu/manifest/version.json index 362691a25a..981c9ff80a 100644 --- a/clash-nyanpasu/manifest/version.json +++ b/clash-nyanpasu/manifest/version.json @@ -2,7 +2,7 @@ "manifest_version": 1, "latest": { "mihomo": "v1.18.6", - "mihomo_alpha": "alpha-303f6e4", + "mihomo_alpha": "alpha-1db3e45", "clash_rs": "v0.1.20", "clash_premium": "2023-09-05-gdcc8d87" }, @@ -36,5 +36,5 @@ "darwin-x64": "clash-darwin-amd64-n{}.gz" } }, - "updated_at": "2024-07-26T22:20:15.529Z" + "updated_at": "2024-07-27T22:19:39.996Z" } diff --git a/clash-nyanpasu/package.json b/clash-nyanpasu/package.json index fd94ff659d..1f7a23fe45 100644 --- a/clash-nyanpasu/package.json +++ b/clash-nyanpasu/package.json @@ -55,7 +55,7 @@ "@tauri-apps/cli": "1.6.0", "@types/fs-extra": "11.0.4", "@types/lodash-es": "4.17.12", - "@types/node": "20.14.12", + "@types/node": "20.14.13", "autoprefixer": "10.4.19", "conventional-changelog-conventionalcommits": "8.0.0", "cross-env": "7.0.3", diff --git a/clash-nyanpasu/pnpm-lock.yaml b/clash-nyanpasu/pnpm-lock.yaml index 692638b76f..1333c20131 100644 --- a/clash-nyanpasu/pnpm-lock.yaml +++ b/clash-nyanpasu/pnpm-lock.yaml @@ -24,7 +24,7 @@ importers: devDependencies: '@commitlint/cli': specifier: 19.3.0 - version: 19.3.0(@types/node@20.14.12)(typescript@5.5.4) + version: 19.3.0(@types/node@20.14.13)(typescript@5.5.4) '@commitlint/config-conventional': specifier: 19.2.2 version: 19.2.2 @@ -41,8 +41,8 @@ importers: specifier: 4.17.12 version: 4.17.12 '@types/node': - specifier: 20.14.12 - version: 20.14.12 + specifier: 20.14.13 + version: 20.14.13 autoprefixer: specifier: 10.4.19 version: 10.4.19(postcss@8.4.40) @@ -184,7 +184,7 @@ importers: version: 11.13.0(@emotion/react@11.13.0(react@19.0.0-rc-df783f9ea1-20240708)(types-react@19.0.0-rc.1))(react@19.0.0-rc-df783f9ea1-20240708)(types-react@19.0.0-rc.1) '@generouted/react-router': specifier: 1.19.6 - version: 1.19.6(react-router-dom@6.25.1(react-dom@19.0.0-rc-df783f9ea1-20240708(react@19.0.0-rc-df783f9ea1-20240708))(react@19.0.0-rc-df783f9ea1-20240708))(react@19.0.0-rc-df783f9ea1-20240708)(vite@5.3.5(@types/node@20.14.12)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)) + version: 1.19.6(react-router-dom@6.25.1(react-dom@19.0.0-rc-df783f9ea1-20240708(react@19.0.0-rc-df783f9ea1-20240708))(react@19.0.0-rc-df783f9ea1-20240708))(react@19.0.0-rc-df783f9ea1-20240708)(vite@5.3.5(@types/node@20.14.13)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)) '@juggle/resize-observer': specifier: 3.4.0 version: 3.4.0 @@ -296,10 +296,10 @@ importers: version: 7.17.0(eslint@8.57.0)(typescript@5.5.4) '@vitejs/plugin-react': specifier: 4.3.1 - version: 4.3.1(vite@5.3.5(@types/node@20.14.12)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)) + version: 4.3.1(vite@5.3.5(@types/node@20.14.13)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)) '@vitejs/plugin-react-swc': specifier: 3.7.0 - version: 3.7.0(vite@5.3.5(@types/node@20.14.12)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)) + version: 3.7.0(vite@5.3.5(@types/node@20.14.13)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)) clsx: specifier: 2.1.1 version: 2.1.1 @@ -314,19 +314,19 @@ importers: version: 2.1.3 vite: specifier: 5.3.5 - version: 5.3.5(@types/node@20.14.12)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0) + version: 5.3.5(@types/node@20.14.13)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0) vite-plugin-monaco-editor: specifier: npm:vite-plugin-monaco-editor-new@1.1.3 version: vite-plugin-monaco-editor-new@1.1.3(monaco-editor@0.50.0) vite-plugin-sass-dts: specifier: 1.3.25 - version: 1.3.25(postcss@8.4.40)(prettier@3.3.3)(sass@1.77.8)(vite@5.3.5(@types/node@20.14.12)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)) + version: 1.3.25(postcss@8.4.40)(prettier@3.3.3)(sass@1.77.8)(vite@5.3.5(@types/node@20.14.13)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)) vite-plugin-svgr: specifier: 4.2.0 - version: 4.2.0(rollup@4.17.2)(typescript@5.5.4)(vite@5.3.5(@types/node@20.14.12)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)) + version: 4.2.0(rollup@4.17.2)(typescript@5.5.4)(vite@5.3.5(@types/node@20.14.13)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)) vite-tsconfig-paths: specifier: 4.3.2 - version: 4.3.2(typescript@5.5.4)(vite@5.3.5(@types/node@20.14.12)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)) + version: 4.3.2(typescript@5.5.4)(vite@5.3.5(@types/node@20.14.13)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)) frontend/ui: dependencies: @@ -2053,8 +2053,8 @@ packages: '@types/node@20.12.10': resolution: {integrity: sha512-Eem5pH9pmWBHoGAT8Dr5fdc5rYA+4NAovdM4EktRPVAAiJhmWWfQrA0cFhAbOsQdSfIHjAud6YdkbL69+zSKjw==} - '@types/node@20.14.12': - resolution: {integrity: sha512-r7wNXakLeSsGT0H1AU863vS2wa5wBOK4bWMjZz2wj+8nBx+m5PeIn0k8AloSLpRuiwdRQZwarZqHE4FNArPuJQ==} + '@types/node@20.14.13': + resolution: {integrity: sha512-+bHoGiZb8UiQ0+WEtmph2IWQCjIqg8MDZMAV+ppRRhUZnquF5mQkP/9vpSwJClEiSM/C7fZZExPzfU0vJTyp8w==} '@types/parse-json@4.0.2': resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} @@ -6161,11 +6161,11 @@ snapshots: '@babel/helper-validator-identifier': 7.24.5 to-fast-properties: 2.0.0 - '@commitlint/cli@19.3.0(@types/node@20.14.12)(typescript@5.5.4)': + '@commitlint/cli@19.3.0(@types/node@20.14.13)(typescript@5.5.4)': dependencies: '@commitlint/format': 19.3.0 '@commitlint/lint': 19.2.2 - '@commitlint/load': 19.2.0(@types/node@20.14.12)(typescript@5.5.4) + '@commitlint/load': 19.2.0(@types/node@20.14.13)(typescript@5.5.4) '@commitlint/read': 19.2.1 '@commitlint/types': 19.0.3 execa: 8.0.1 @@ -6212,7 +6212,7 @@ snapshots: '@commitlint/rules': 19.0.3 '@commitlint/types': 19.0.3 - '@commitlint/load@19.2.0(@types/node@20.14.12)(typescript@5.5.4)': + '@commitlint/load@19.2.0(@types/node@20.14.13)(typescript@5.5.4)': dependencies: '@commitlint/config-validator': 19.0.3 '@commitlint/execute-rule': 19.0.0 @@ -6220,7 +6220,7 @@ snapshots: '@commitlint/types': 19.0.3 chalk: 5.3.0 cosmiconfig: 9.0.0(typescript@5.5.4) - cosmiconfig-typescript-loader: 5.0.0(@types/node@20.14.12)(cosmiconfig@9.0.0(typescript@5.5.4))(typescript@5.5.4) + cosmiconfig-typescript-loader: 5.0.0(@types/node@20.14.13)(cosmiconfig@9.0.0(typescript@5.5.4))(typescript@5.5.4) lodash.isplainobject: 4.0.6 lodash.merge: 4.6.2 lodash.uniq: 4.5.0 @@ -6594,13 +6594,13 @@ snapshots: postcss: 7.0.32 purgecss: 2.3.0 - '@generouted/react-router@1.19.6(react-router-dom@6.25.1(react-dom@19.0.0-rc-df783f9ea1-20240708(react@19.0.0-rc-df783f9ea1-20240708))(react@19.0.0-rc-df783f9ea1-20240708))(react@19.0.0-rc-df783f9ea1-20240708)(vite@5.3.5(@types/node@20.14.12)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0))': + '@generouted/react-router@1.19.6(react-router-dom@6.25.1(react-dom@19.0.0-rc-df783f9ea1-20240708(react@19.0.0-rc-df783f9ea1-20240708))(react@19.0.0-rc-df783f9ea1-20240708))(react@19.0.0-rc-df783f9ea1-20240708)(vite@5.3.5(@types/node@20.14.13)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0))': dependencies: fast-glob: 3.3.2 - generouted: 1.19.6(vite@5.3.5(@types/node@20.14.12)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)) + generouted: 1.19.6(vite@5.3.5(@types/node@20.14.13)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)) react: 19.0.0-rc-df783f9ea1-20240708 react-router-dom: 6.25.1(react-dom@19.0.0-rc-df783f9ea1-20240708(react@19.0.0-rc-df783f9ea1-20240708))(react@19.0.0-rc-df783f9ea1-20240708) - vite: 5.3.5(@types/node@20.14.12)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0) + vite: 5.3.5(@types/node@20.14.13)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0) '@humanwhocodes/config-array@0.11.14': dependencies: @@ -7351,12 +7351,12 @@ snapshots: dependencies: '@types/http-cache-semantics': 4.0.4 '@types/keyv': 3.1.4 - '@types/node': 20.14.12 + '@types/node': 20.14.13 '@types/responselike': 1.0.3 '@types/conventional-commits-parser@5.0.0': dependencies: - '@types/node': 20.14.12 + '@types/node': 20.14.13 '@types/d3-array@3.2.1': {} @@ -7492,7 +7492,7 @@ snapshots: '@types/fs-extra@11.0.4': dependencies: '@types/jsonfile': 6.1.4 - '@types/node': 20.14.12 + '@types/node': 20.14.13 '@types/geojson@7946.0.14': {} @@ -7506,11 +7506,11 @@ snapshots: '@types/jsonfile@6.1.4': dependencies: - '@types/node': 20.14.12 + '@types/node': 20.14.13 '@types/keyv@3.1.4': dependencies: - '@types/node': 20.14.12 + '@types/node': 20.14.13 '@types/lodash-es@4.17.12': dependencies: @@ -7530,7 +7530,7 @@ snapshots: dependencies: undici-types: 5.26.5 - '@types/node@20.14.12': + '@types/node@20.14.13': dependencies: undici-types: 5.26.5 @@ -7552,7 +7552,7 @@ snapshots: '@types/responselike@1.0.3': dependencies: - '@types/node': 20.14.12 + '@types/node': 20.14.13 '@types/unist@2.0.10': {} @@ -7560,7 +7560,7 @@ snapshots: '@types/yauzl@2.10.3': dependencies: - '@types/node': 20.14.12 + '@types/node': 20.14.13 optional: true '@typescript-eslint/eslint-plugin@7.17.0(@typescript-eslint/parser@7.17.0(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0)(typescript@5.5.4)': @@ -7646,21 +7646,21 @@ snapshots: '@ungap/structured-clone@1.2.0': {} - '@vitejs/plugin-react-swc@3.7.0(vite@5.3.5(@types/node@20.14.12)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0))': + '@vitejs/plugin-react-swc@3.7.0(vite@5.3.5(@types/node@20.14.13)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0))': dependencies: '@swc/core': 1.6.1 - vite: 5.3.5(@types/node@20.14.12)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0) + vite: 5.3.5(@types/node@20.14.13)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0) transitivePeerDependencies: - '@swc/helpers' - '@vitejs/plugin-react@4.3.1(vite@5.3.5(@types/node@20.14.12)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0))': + '@vitejs/plugin-react@4.3.1(vite@5.3.5(@types/node@20.14.13)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0))': dependencies: '@babel/core': 7.24.5 '@babel/plugin-transform-react-jsx-self': 7.24.5(@babel/core@7.24.5) '@babel/plugin-transform-react-jsx-source': 7.24.1(@babel/core@7.24.5) '@types/babel__core': 7.20.5 react-refresh: 0.14.2 - vite: 5.3.5(@types/node@20.14.12)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0) + vite: 5.3.5(@types/node@20.14.13)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0) transitivePeerDependencies: - supports-color @@ -8143,9 +8143,9 @@ snapshots: dependencies: is-what: 3.14.1 - cosmiconfig-typescript-loader@5.0.0(@types/node@20.14.12)(cosmiconfig@9.0.0(typescript@5.5.4))(typescript@5.5.4): + cosmiconfig-typescript-loader@5.0.0(@types/node@20.14.13)(cosmiconfig@9.0.0(typescript@5.5.4))(typescript@5.5.4): dependencies: - '@types/node': 20.14.12 + '@types/node': 20.14.13 cosmiconfig: 9.0.0(typescript@5.5.4) jiti: 1.21.0 typescript: 5.5.4 @@ -9146,9 +9146,9 @@ snapshots: functions-have-names@1.2.3: {} - generouted@1.19.6(vite@5.3.5(@types/node@20.14.12)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)): + generouted@1.19.6(vite@5.3.5(@types/node@20.14.13)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)): dependencies: - vite: 5.3.5(@types/node@20.14.12)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0) + vite: 5.3.5(@types/node@20.14.13)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0) gensync@1.0.0-beta.2: {} @@ -11706,43 +11706,43 @@ snapshots: esbuild: 0.19.12 monaco-editor: 0.50.0 - vite-plugin-sass-dts@1.3.25(postcss@8.4.40)(prettier@3.3.3)(sass@1.77.8)(vite@5.3.5(@types/node@20.14.12)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)): + vite-plugin-sass-dts@1.3.25(postcss@8.4.40)(prettier@3.3.3)(sass@1.77.8)(vite@5.3.5(@types/node@20.14.13)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)): dependencies: postcss: 8.4.40 postcss-js: 4.0.1(postcss@8.4.40) prettier: 3.3.3 sass: 1.77.8 - vite: 5.3.5(@types/node@20.14.12)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0) + vite: 5.3.5(@types/node@20.14.13)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0) - vite-plugin-svgr@4.2.0(rollup@4.17.2)(typescript@5.5.4)(vite@5.3.5(@types/node@20.14.12)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)): + vite-plugin-svgr@4.2.0(rollup@4.17.2)(typescript@5.5.4)(vite@5.3.5(@types/node@20.14.13)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)): dependencies: '@rollup/pluginutils': 5.1.0(rollup@4.17.2) '@svgr/core': 8.1.0(typescript@5.5.4) '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.5.4)) - vite: 5.3.5(@types/node@20.14.12)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0) + vite: 5.3.5(@types/node@20.14.13)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0) transitivePeerDependencies: - rollup - supports-color - typescript - vite-tsconfig-paths@4.3.2(typescript@5.5.4)(vite@5.3.5(@types/node@20.14.12)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)): + vite-tsconfig-paths@4.3.2(typescript@5.5.4)(vite@5.3.5(@types/node@20.14.13)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)): dependencies: debug: 4.3.4 globrex: 0.1.2 tsconfck: 3.0.3(typescript@5.5.4) optionalDependencies: - vite: 5.3.5(@types/node@20.14.12)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0) + vite: 5.3.5(@types/node@20.14.13)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0) transitivePeerDependencies: - supports-color - typescript - vite@5.3.5(@types/node@20.14.12)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0): + vite@5.3.5(@types/node@20.14.13)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0): dependencies: esbuild: 0.21.5 postcss: 8.4.40 rollup: 4.17.2 optionalDependencies: - '@types/node': 20.14.12 + '@types/node': 20.14.13 fsevents: 2.3.3 less: 4.2.0 sass: 1.77.8 diff --git a/lede/package/kernel/linux/modules/netfilter.mk b/lede/package/kernel/linux/modules/netfilter.mk index e585818a80..00f9e85d8d 100644 --- a/lede/package/kernel/linux/modules/netfilter.mk +++ b/lede/package/kernel/linux/modules/netfilter.mk @@ -1218,6 +1218,7 @@ define KernelPackage/nft-netdev DEPENDS:=+kmod-nft-core KCONFIG:= \ CONFIG_NETFILTER_INGRESS=y \ + CONFIG_NETFILTER_EGRESS=y \ CONFIG_NF_TABLES_NETDEV \ CONFIG_NF_DUP_NETDEV \ CONFIG_NFT_DUP_NETDEV \ diff --git a/mihomo/component/cidr/ipcidr_set.go b/mihomo/component/cidr/ipcidr_set.go index 521fabab13..4907146039 100644 --- a/mihomo/component/cidr/ipcidr_set.go +++ b/mihomo/component/cidr/ipcidr_set.go @@ -57,6 +57,16 @@ func (set *IpCidrSet) Merge() error { return nil } +func (set *IpCidrSet) Foreach(f func(prefix netip.Prefix) bool) { + for _, r := range set.rr { + for _, prefix := range r.Prefixes() { + if !f(prefix) { + return + } + } + } +} + // ToIPSet not safe convert to *netipx.IPSet // be careful, must be used after Merge func (set *IpCidrSet) ToIPSet() *netipx.IPSet { diff --git a/mihomo/component/trie/domain.go b/mihomo/component/trie/domain.go index 3decbb0255..db30402ede 100644 --- a/mihomo/component/trie/domain.go +++ b/mihomo/component/trie/domain.go @@ -123,16 +123,18 @@ func (t *DomainTrie[T]) Optimize() { t.root.optimize() } -func (t *DomainTrie[T]) Foreach(print func(domain string, data T)) { +func (t *DomainTrie[T]) Foreach(fn func(domain string, data T) bool) { for key, data := range t.root.getChildren() { - recursion([]string{key}, data, print) + recursion([]string{key}, data, fn) if data != nil && data.inited { - print(joinDomain([]string{key}), data.data) + if !fn(joinDomain([]string{key}), data.data) { + return + } } } } -func recursion[T any](items []string, node *Node[T], fn func(domain string, data T)) { +func recursion[T any](items []string, node *Node[T], fn func(domain string, data T) bool) bool { for key, data := range node.getChildren() { newItems := append([]string{key}, items...) if data != nil && data.inited { @@ -140,10 +142,15 @@ func recursion[T any](items []string, node *Node[T], fn func(domain string, data if domain[0] == domainStepByte { domain = complexWildcard + domain } - fn(domain, data.Data()) + if !fn(domain, data.Data()) { + return false + } + } + if !recursion(newItems, data, fn) { + return false } - recursion(newItems, data, fn) } + return true } func joinDomain(items []string) string { diff --git a/mihomo/component/trie/domain_set.go b/mihomo/component/trie/domain_set.go index 860d1235d5..7778d13379 100644 --- a/mihomo/component/trie/domain_set.go +++ b/mihomo/component/trie/domain_set.go @@ -28,8 +28,9 @@ type qElt struct{ s, e, col int } // NewDomainSet creates a new *DomainSet struct, from a DomainTrie. func (t *DomainTrie[T]) NewDomainSet() *DomainSet { reserveDomains := make([]string, 0) - t.Foreach(func(domain string, data T) { + t.Foreach(func(domain string, data T) bool { reserveDomains = append(reserveDomains, utils.Reverse(domain)) + return true }) // ensure that the same prefix is continuous // and according to the ascending sequence of length @@ -136,6 +137,41 @@ func (ss *DomainSet) Has(key string) bool { } +func (ss *DomainSet) keys(f func(key string) bool) { + var currentKey []byte + var traverse func(int, int) bool + traverse = func(nodeId, bmIdx int) bool { + if getBit(ss.leaves, nodeId) != 0 { + if !f(string(currentKey)) { + return false + } + } + + for ; ; bmIdx++ { + if getBit(ss.labelBitmap, bmIdx) != 0 { + return true + } + nextLabel := ss.labels[bmIdx-nodeId] + currentKey = append(currentKey, nextLabel) + nextNodeId := countZeros(ss.labelBitmap, ss.ranks, bmIdx+1) + nextBmIdx := selectIthOne(ss.labelBitmap, ss.ranks, ss.selects, nextNodeId-1) + 1 + if !traverse(nextNodeId, nextBmIdx) { + return false + } + currentKey = currentKey[:len(currentKey)-1] + } + } + + traverse(0, 0) + return +} + +func (ss *DomainSet) Foreach(f func(key string) bool) { + ss.keys(func(key string) bool { + return f(utils.Reverse(key)) + }) +} + func setBit(bm *[]uint64, i int, v int) { for i>>6 >= len(*bm) { *bm = append(*bm, 0) diff --git a/mihomo/component/trie/domain_set_test.go b/mihomo/component/trie/domain_set_test.go index 77106d5ffc..e343d11d1c 100644 --- a/mihomo/component/trie/domain_set_test.go +++ b/mihomo/component/trie/domain_set_test.go @@ -1,12 +1,29 @@ package trie_test import ( + "golang.org/x/exp/slices" "testing" "github.com/metacubex/mihomo/component/trie" "github.com/stretchr/testify/assert" ) +func testDump(t *testing.T, tree *trie.DomainTrie[struct{}], set *trie.DomainSet) { + var dataSrc []string + tree.Foreach(func(domain string, data struct{}) bool { + dataSrc = append(dataSrc, domain) + return true + }) + slices.Sort(dataSrc) + var dataSet []string + set.Foreach(func(key string) bool { + dataSet = append(dataSet, key) + return true + }) + slices.Sort(dataSet) + assert.Equal(t, dataSrc, dataSet) +} + func TestDomainSet(t *testing.T) { tree := trie.New[struct{}]() domainSet := []string{ @@ -33,6 +50,7 @@ func TestDomainSet(t *testing.T) { assert.True(t, set.Has("google.com")) assert.False(t, set.Has("qq.com")) assert.False(t, set.Has("www.baidu.com")) + testDump(t, tree, set) } func TestDomainSetComplexWildcard(t *testing.T) { @@ -55,6 +73,7 @@ func TestDomainSetComplexWildcard(t *testing.T) { assert.False(t, set.Has("google.com")) assert.True(t, set.Has("www.baidu.com")) assert.True(t, set.Has("test.test.baidu.com")) + testDump(t, tree, set) } func TestDomainSetWildcard(t *testing.T) { @@ -82,4 +101,5 @@ func TestDomainSetWildcard(t *testing.T) { assert.False(t, set.Has("a.www.google.com")) assert.False(t, set.Has("test.qq.com")) assert.False(t, set.Has("test.test.test.qq.com")) + testDump(t, tree, set) } diff --git a/mihomo/component/trie/domain_test.go b/mihomo/component/trie/domain_test.go index 4c5d8002d8..916f61076d 100644 --- a/mihomo/component/trie/domain_test.go +++ b/mihomo/component/trie/domain_test.go @@ -121,8 +121,9 @@ func TestTrie_Foreach(t *testing.T) { assert.NoError(t, tree.Insert(domain, localIP)) } count := 0 - tree.Foreach(func(domain string, data netip.Addr) { + tree.Foreach(func(domain string, data netip.Addr) bool { count++ + return true }) assert.Equal(t, 7, count) } diff --git a/mihomo/docs/config.yaml b/mihomo/docs/config.yaml index 669c8be7ef..d7c686d01f 100644 --- a/mihomo/docs/config.yaml +++ b/mihomo/docs/config.yaml @@ -944,10 +944,17 @@ rule-providers: type: file rule3: # mrs类型ruleset,目前仅支持domain和ipcidr(即不支持classical), - # behavior=domain,format=yaml 可以通过“mihomo convert-ruleset domain yaml XXX.yaml XXX.mrs”转换得到 - # behavior=domain,format=text 可以通过“mihomo convert-ruleset domain text XXX.text XXX.mrs”转换得到 - # behavior=ipcidr,format=yaml 可以通过“mihomo convert-ruleset ipcidr yaml XXX.yaml XXX.mrs”转换得到 - # behavior=ipcidr,format=text 可以通过“mihomo convert-ruleset ipcidr text XXX.text XXX.mrs”转换得到 + # + # 对于behavior=domain: + # - format=yaml 可以通过“mihomo convert-ruleset domain yaml XXX.yaml XXX.mrs”转换到mrs格式 + # - format=text 可以通过“mihomo convert-ruleset domain text XXX.text XXX.mrs”转换到mrs格式 + # - XXX.mrs 可以通过"mihomo convert-ruleset domain mrs XXX.mrs XXX.text"转换回text格式(暂不支持转换回ymal格式) + # + # 对于behavior=ipcidr: + # - format=yaml 可以通过“mihomo convert-ruleset ipcidr yaml XXX.yaml XXX.mrs”转换到mrs格式 + # - format=text 可以通过“mihomo convert-ruleset ipcidr text XXX.text XXX.mrs”转换到mrs格式 + # - XXX.mrs 可以通过"mihomo convert-ruleset ipcidr mrs XXX.mrs XXX.text"转换回text格式(暂不支持转换回ymal格式) + # type: http url: "url" format: mrs diff --git a/mihomo/rules/provider/domain_strategy.go b/mihomo/rules/provider/domain_strategy.go index a999f5bd1c..b893f038b2 100644 --- a/mihomo/rules/provider/domain_strategy.go +++ b/mihomo/rules/provider/domain_strategy.go @@ -9,6 +9,8 @@ import ( C "github.com/metacubex/mihomo/constant" P "github.com/metacubex/mihomo/constant/provider" "github.com/metacubex/mihomo/log" + + "golang.org/x/exp/slices" ) type domainStrategy struct { @@ -78,6 +80,26 @@ func (d *domainStrategy) WriteMrs(w io.Writer) error { return d.domainSet.WriteBin(w) } +func (d *domainStrategy) DumpMrs(f func(key string) bool) { + if d.domainSet != nil { + var keys []string + d.domainSet.Foreach(func(key string) bool { + keys = append(keys, key) + return true + }) + slices.Sort(keys) + + for _, key := range keys { + if _, ok := slices.BinarySearch(keys, "+."+key); ok { + continue // ignore the rules added by trie internal processing + } + if !f(key) { + return + } + } + } +} + var _ mrsRuleStrategy = (*domainStrategy)(nil) func NewDomainStrategy() *domainStrategy { diff --git a/mihomo/rules/provider/ipcidr_strategy.go b/mihomo/rules/provider/ipcidr_strategy.go index 87cf7a2d8e..9efffed96b 100644 --- a/mihomo/rules/provider/ipcidr_strategy.go +++ b/mihomo/rules/provider/ipcidr_strategy.go @@ -3,6 +3,7 @@ package provider import ( "errors" "io" + "net/netip" "github.com/metacubex/mihomo/component/cidr" C "github.com/metacubex/mihomo/constant" @@ -82,6 +83,14 @@ func (i *ipcidrStrategy) WriteMrs(w io.Writer) error { return i.cidrSet.WriteBin(w) } +func (i *ipcidrStrategy) DumpMrs(f func(key string) bool) { + if i.cidrSet != nil { + i.cidrSet.Foreach(func(prefix netip.Prefix) bool { + return f(prefix.String()) + }) + } +} + func (i *ipcidrStrategy) ToIpCidr() *netipx.IPSet { return i.cidrSet.ToIPSet() } diff --git a/mihomo/rules/provider/mrs_converter.go b/mihomo/rules/provider/mrs_converter.go index a08301982e..edc24e7eea 100644 --- a/mihomo/rules/provider/mrs_converter.go +++ b/mihomo/rules/provider/mrs_converter.go @@ -3,6 +3,7 @@ package provider import ( "encoding/binary" "errors" + "fmt" "io" "os" @@ -21,6 +22,17 @@ func ConvertToMrs(buf []byte, behavior P.RuleBehavior, format P.RuleFormat, w io return errors.New("empty rule") } if _strategy, ok := strategy.(mrsRuleStrategy); ok { + if format == P.MrsRule { // export to TextRule + _strategy.DumpMrs(func(key string) bool { + _, err = fmt.Fprintln(w, key) + if err != nil { + return false + } + return true + }) + return nil + } + var encoder *zstd.Encoder encoder, err = zstd.NewWriter(w) if err != nil { diff --git a/mihomo/rules/provider/provider.go b/mihomo/rules/provider/provider.go index 8c5d7f9407..b9524c35e6 100644 --- a/mihomo/rules/provider/provider.go +++ b/mihomo/rules/provider/provider.go @@ -58,6 +58,7 @@ type mrsRuleStrategy interface { ruleStrategy FromMrs(r io.Reader, count int) error WriteMrs(w io.Writer) error + DumpMrs(f func(key string) bool) } func (rp *ruleSetProvider) Type() P.ProviderType { diff --git a/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua b/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua index 5e3a0c6090..1606bd1a3d 100644 --- a/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua +++ b/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua @@ -85,6 +85,35 @@ local doh_validate = function(self, value, t) return nil, translate("DoH request address") .. " " .. translate("Format must be:") .. " URL,IP" end +local chinadns_dot_validate = function(self, value, t) + if value ~= "" then + value = api.trim(value) + -- Define patterns for IPv4, IPv6, domain, and port + local ipv4_pattern = "(%d+%.%d+%.%d+%.%d+)" + local ipv6_pattern = "([%[%]a-fA-F0-9:]+)" -- IPv6 addresses are wrapped in [] + local domain_pattern = "([%w-_%.]+)" + local port_pattern = "(%d+)" + -- Define patterns for the different formats + local patterns = { + "^tls://" .. domain_pattern .. "@" .. ipv4_pattern .. "#" .. port_pattern .. "$", -- tls://域名@ip#端口 + "^tls://" .. ipv4_pattern .. "#" .. port_pattern .. "$", -- tls://ip#端口 + "^tls://" .. domain_pattern .. "@" .. ipv4_pattern .. "$", -- tls://域名@ip + "^tls://" .. ipv4_pattern .. "$", -- tls://ip + "^tls://" .. domain_pattern .. "@" .. ipv6_pattern .. "#" .. port_pattern .. "$", -- tls://域名@[IPv6]#端口 + "^tls://" .. ipv6_pattern .. "#" .. port_pattern .. "$", -- tls://[IPv6]#端口 + "^tls://" .. domain_pattern .. "@" .. ipv6_pattern .. "$", -- tls://域名@[IPv6] + "^tls://" .. ipv6_pattern .. "$" -- tls://[IPv6] + } + -- Check if the string matches any of the patterns + for _, pattern in ipairs(patterns) do + if value:match(pattern) then + return value + end + end + return nil, translate("Direct DNS") .. " DoT " .. translate("Format must be:") .. " tls://Domain@IP(#Port) or tls://IP(#Port)" + end +end + m:append(Template(appname .. "/global/status")) s = m:section(TypedSection, "global") @@ -266,20 +295,48 @@ dns_shunt = s:taboption("DNS", ListValue, "dns_shunt", "DNS " .. translate("Shun dns_shunt:value("dnsmasq", "Dnsmasq") dns_shunt:value("chinadns-ng", "Dnsmasq + ChinaDNS-NG") -o = s:taboption("DNS", Value, "direct_dns", translate("Direct DNS")) -o.datatype = "or(ipaddr,ipaddrport)" +o = s:taboption("DNS", ListValue, "direct_dns_mode", translate("Direct DNS") .. " " .. translate("Request protocol")) o.default = "" o:value("", translate("Auto")) -o:value("223.5.5.5") -o:value("223.6.6.6") -o:value("114.114.114.114") -o:value("119.29.29.29") -o:value("180.76.76.76") -o:value("1.12.12.12") -o:value("120.53.53.53") +o:value("udp", "UDP") +o:value("tcp", "TCP") +if os.execute("chinadns-ng -V | grep -i wolfssl >/dev/null") == 0 then + o:value("dot", "DoT") +end +--TO DO +--o:value("doh", "DoH") o:depends({dns_shunt = "dnsmasq"}) o:depends({dns_shunt = "chinadns-ng"}) +o = s:taboption("DNS", Value, "direct_dns_udp", translate("Direct DNS")) +o.datatype = "or(ipaddr,ipaddrport)" +o.default = "223.5.5.5" +o:value("223.5.5.5") +o:value("223.6.6.6") +o:value("119.29.29.29") +o:value("180.184.1.1") +o:value("180.184.2.2") +o:value("114.114.114.114") +o:depends("direct_dns_mode", "udp") + +o = s:taboption("DNS", Value, "direct_dns_tcp", translate("Direct DNS")) +o.datatype = "or(ipaddr,ipaddrport)" +o.default = "223.5.5.5" +o:value("223.5.5.5") +o:value("223.6.6.6") +o:value("180.184.1.1") +o:value("180.184.2.2") +o:depends("direct_dns_mode", "tcp") + +o = s:taboption("DNS", Value, "direct_dns_dot", translate("Direct DNS")) +o.default = "tls://dot.pub@1.12.12.12" +o:value("tls://dot.pub@1.12.12.12") +o:value("tls://dot.pub@120.53.53.53") +o:value("tls://dot.360.cn@36.99.170.86") +o:value("tls://dot.360.cn@101.198.191.4") +o.validate = chinadns_dot_validate +o:depends("direct_dns_mode", "dot") + o = s:taboption("DNS", Flag, "filter_proxy_ipv6", translate("Filter Proxy Host IPv6"), translate("Experimental feature.")) o.default = "0" diff --git a/openwrt-passwall/luci-app-passwall/root/usr/share/passwall/app.sh b/openwrt-passwall/luci-app-passwall/root/usr/share/passwall/app.sh index 03dc8c7b1e..27672a3e61 100755 --- a/openwrt-passwall/luci-app-passwall/root/usr/share/passwall/app.sh +++ b/openwrt-passwall/luci-app-passwall/root/usr/share/passwall/app.sh @@ -1298,6 +1298,38 @@ stop_crontab() { start_dns() { echolog "DNS域名解析:" + local direct_dns_mode=$(config_t_get global direct_dns_mode "auto") + case "$direct_dns_mode" in + udp) + LOCAL_DNS=$(config_t_get global direct_dns_udp 223.5.5.5 | sed 's/:/#/g') + ;; + tcp) + LOCAL_DNS="127.0.0.1#${dns_listen_port}" + dns_listen_port=$(expr $dns_listen_port + 1) + local DIRECT_DNS=$(config_t_get global direct_dns_tcp 223.5.5.5 | sed 's/:/#/g') + ln_run "$(first_type dns2tcp)" dns2tcp "/dev/null" -L "${LOCAL_DNS}" -R "$(get_first_dns DIRECT_DNS 53)" -v + echolog " - dns2tcp(${LOCAL_DNS}) -> tcp://$(get_first_dns DIRECT_DNS 53 | sed 's/#/:/g')" + echolog " * 请确保上游直连 DNS 支持 TCP 查询。" + ;; + dot) + if [ "$(chinadns-ng -V | grep -i wolfssl)" != "nil" ]; then + LOCAL_DNS="127.0.0.1#${dns_listen_port}" + local cdns_listen_port=${dns_listen_port} + dns_listen_port=$(expr $dns_listen_port + 1) + local DIRECT_DNS=$(config_t_get global direct_dns_dot "tls://dot.pub@1.12.12.12") + ln_run "$(first_type chinadns-ng)" chinadns-ng "/dev/null" -b 127.0.0.1 -l ${cdns_listen_port}@udp -c ${DIRECT_DNS} -d chn + echolog " - ChinaDNS-NG(${LOCAL_DNS}) -> ${DIRECT_DNS}" + echolog " * 请确保上游直连 DNS 支持 DoT 查询。" + else + echolog " - 你的ChinaDNS-NG版本不支持DoT,直连DNS将使用默认UDP地址。" + fi + ;; + auto) + #Automatic logic is already done by default + : + ;; + esac + TUN_DNS="127.0.0.1#${dns_listen_port}" [ "${resolve_dns}" == "1" ] && TUN_DNS="127.0.0.1#${resolve_dns_port}" @@ -1880,9 +1912,6 @@ DEFAULT_DNSMASQ_CFGID=$(uci show dhcp.@dnsmasq[0] | awk -F '.' '{print $2}' | a DEFAULT_DNS=$(uci show dhcp.@dnsmasq[0] | grep "\.server=" | awk -F '=' '{print $2}' | sed "s/'//g" | tr ' ' '\n' | grep -v "\/" | head -2 | sed ':label;N;s/\n/,/;b label') [ -z "${DEFAULT_DNS}" ] && [ "$(echo $ISP_DNS | tr ' ' '\n' | wc -l)" -le 2 ] && DEFAULT_DNS=$(echo -n $ISP_DNS | tr ' ' '\n' | head -2 | tr '\n' ',') LOCAL_DNS="${DEFAULT_DNS:-119.29.29.29,223.5.5.5}" -DIRECT_DNS=$(config_t_get global direct_dns "auto") -#Automatic logic is already done by default -[ "${DIRECT_DNS}" != "auto" ] && LOCAL_DNS=$(echo ${DIRECT_DNS} | sed 's/:/#/g') DNS_QUERY_STRATEGY="UseIP" [ "$FILTER_PROXY_IPV6" = "1" ] && DNS_QUERY_STRATEGY="UseIPv4" diff --git a/openwrt-passwall/luci-app-passwall/root/usr/share/passwall/rules/direct_ip b/openwrt-passwall/luci-app-passwall/root/usr/share/passwall/rules/direct_ip index 04e855dc1a..b73edaa63a 100644 --- a/openwrt-passwall/luci-app-passwall/root/usr/share/passwall/rules/direct_ip +++ b/openwrt-passwall/luci-app-passwall/root/usr/share/passwall/rules/direct_ip @@ -6,3 +6,5 @@ 180.76.76.76 1.12.12.12 120.53.53.53 +180.184.1.1 +180.184.2.2 diff --git a/shadowsocks-rust/Cargo.lock b/shadowsocks-rust/Cargo.lock index abf5ad8ab4..92cf2cf17a 100644 --- a/shadowsocks-rust/Cargo.lock +++ b/shadowsocks-rust/Cargo.lock @@ -121,9 +121,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.14" +version = "0.6.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" dependencies = [ "anstyle", "anstyle-parse", @@ -136,33 +136,33 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" [[package]] name = "anstyle-parse" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" dependencies = [ "anstyle", "windows-sys 0.52.0", @@ -182,9 +182,9 @@ checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" [[package]] name = "arrayref" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" +checksum = "9d151e35f61089500b617991b791fc8bfd237ae50cd5950803758a179b41e67a" [[package]] name = "arrayvec" @@ -194,9 +194,9 @@ checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" [[package]] name = "async-compression" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd066d0b4ef8ecb03a55319dc13aa6910616d0f44008a045bb1835af830abff5" +checksum = "fec134f64e2bc57411226dfc4e52dec859ddfc7e711fc5e07b612584f000e4aa" dependencies = [ "brotli", "flate2", @@ -216,7 +216,7 @@ checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.72", ] [[package]] @@ -391,7 +391,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.72", ] [[package]] @@ -450,13 +450,12 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.4" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9711f33475c22aab363b05564a17d7b789bf3dfec5ebabb586adee56f0e271b5" +checksum = "2aba8f4e9906c7ce3c73463f62a7f0c65183ada1a2d47e397cc8810827f9694f" dependencies = [ "jobserver", "libc", - "once_cell", ] [[package]] @@ -532,18 +531,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.9" +version = "4.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64acc1846d54c1fe936a78dc189c34e28d3f5afc348403f28ecf53660b9b8462" +checksum = "35723e6a11662c2afb578bcf0b88bf6ea8e21282a953428f240574fcc3a2b5b3" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.9" +version = "4.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb8393d67ba2e7bfaf28a23458e4e2b543cc73a99595511eb207fdb8aede942" +checksum = "49eb96cbfa7cfa35017b7cd548c75b14c3118c98b423041d70562665e07fb0fa" dependencies = [ "anstream", "anstyle", @@ -554,9 +553,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" [[package]] name = "cmake" @@ -569,9 +568,9 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" [[package]] name = "const-oid" @@ -710,7 +709,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.72", ] [[package]] @@ -798,7 +797,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.72", ] [[package]] @@ -858,14 +857,14 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.72", ] [[package]] name = "env_filter" -version = "0.1.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" +checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" dependencies = [ "log", "regex", @@ -873,9 +872,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.3" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" +checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" dependencies = [ "anstream", "anstyle", @@ -1067,7 +1066,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.72", ] [[package]] @@ -1457,7 +1456,7 @@ dependencies = [ "http 1.1.0", "hyper", "hyper-util", - "rustls 0.23.11", + "rustls 0.23.12", "rustls-native-certs 0.7.1", "rustls-pki-types", "tokio", @@ -1640,7 +1639,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.72", ] [[package]] @@ -1752,9 +1751,9 @@ dependencies = [ [[package]] name = "is_terminal_polyfill" -version = "1.70.0" +version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itoa" @@ -1784,9 +1783,9 @@ dependencies = [ [[package]] name = "jobserver" -version = "0.1.31" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" dependencies = [ "libc", ] @@ -1845,9 +1844,9 @@ checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libloading" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e310b3a6b5907f99202fcdb4960ff45b93735d7c7d96b760fcff8db2dc0e103d" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" dependencies = [ "cfg-if", "windows-targets 0.52.6", @@ -2032,6 +2031,18 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "mio" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4" +dependencies = [ + "hermit-abi", + "libc", + "wasi", + "windows-sys 0.52.0", +] + [[package]] name = "native-tls" version = "0.2.12" @@ -2075,7 +2086,7 @@ dependencies = [ "kqueue", "libc", "log", - "mio", + "mio 0.8.11", "walkdir", "windows-sys 0.48.0", ] @@ -2105,16 +2116,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi", - "libc", -] - [[package]] name = "num_threads" version = "0.1.7" @@ -2126,9 +2127,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.1" +version = "0.36.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "081b846d1d56ddfc18fdf1a922e4f6e07a11768ea1b92dec44e42b72712ccfce" +checksum = "3f203fa8daa7bb185f760ae12bd8e097f63d17041dcdcaf675ac54cdf863170e" dependencies = [ "memchr", ] @@ -2168,7 +2169,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.72", ] [[package]] @@ -2284,7 +2285,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.2", + "redox_syscall 0.5.3", "smallvec", "windows-targets 0.52.6", ] @@ -2326,7 +2327,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.72", ] [[package]] @@ -2357,7 +2358,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.72", ] [[package]] @@ -2503,9 +2504,9 @@ dependencies = [ "bytes", "pin-project-lite", "quinn-proto 0.11.3", - "quinn-udp 0.5.2", + "quinn-udp 0.5.4", "rustc-hash", - "rustls 0.23.11", + "rustls 0.23.12", "thiserror", "tokio", "tracing", @@ -2538,7 +2539,7 @@ dependencies = [ "rand", "ring 0.17.8", "rustc-hash", - "rustls 0.23.11", + "rustls 0.23.12", "slab", "thiserror", "tinyvec", @@ -2560,14 +2561,13 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.2" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9096629c45860fc7fb143e125eb826b5e721e10be3263160c7d60ca832cf8c46" +checksum = "8bffec3605b73c6f1754535084a85229fa8a30f86014e6c81aeec4abb68b0285" dependencies = [ "libc", "once_cell", "socket2", - "tracing", "windows-sys 0.52.0", ] @@ -2636,9 +2636,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" +checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" dependencies = [ "bitflags 2.6.0", ] @@ -2728,7 +2728,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn 0.11.2", - "rustls 0.23.11", + "rustls 0.23.12", "rustls-native-certs 0.7.1", "rustls-pemfile 2.1.2", "rustls-pki-types", @@ -2889,15 +2889,15 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.11" +version = "0.23.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4828ea528154ae444e5a642dbb7d5623354030dc9822b83fd9bb79683c7399d0" +checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" dependencies = [ "log", "once_cell", "ring 0.17.8", "rustls-pki-types", - "rustls-webpki 0.102.5", + "rustls-webpki 0.102.6", "subtle", "zeroize", ] @@ -2964,9 +2964,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.102.5" +version = "0.102.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a6fccd794a42c2c105b513a2f62bc3fd8f3ba57a4593677ceb0bd035164d78" +checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e" dependencies = [ "ring 0.17.8", "rustls-pki-types", @@ -3095,7 +3095,7 @@ checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.72", ] [[package]] @@ -3225,7 +3225,7 @@ dependencies = [ [[package]] name = "shadowsocks-rust" -version = "1.20.2" +version = "1.20.3" dependencies = [ "base64 0.22.1", "build-time", @@ -3266,7 +3266,7 @@ dependencies = [ [[package]] name = "shadowsocks-service" -version = "1.20.2" +version = "1.20.3" dependencies = [ "arc-swap", "async-trait", @@ -3488,9 +3488,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.71" +version = "2.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b146dcf730474b4bcd16c311627b31ede9ab149045db4d6088b3becaea046462" +checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" dependencies = [ "proc-macro2", "quote", @@ -3511,7 +3511,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.72", ] [[package]] @@ -3601,14 +3601,14 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.72", ] [[package]] name = "thread-id" -version = "4.2.1" +version = "4.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0ec81c46e9eb50deaa257be2f148adf052d1fb7701cfd55ccfab2525280b70b" +checksum = "cfe8f25bbdd100db7e1d34acf7fd2dc59c4bf8f7483f505eaa7d4f12f76cc0ea" dependencies = [ "libc", "winapi", @@ -3684,32 +3684,31 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.38.0" +version = "1.39.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" +checksum = "daa4fb1bc778bd6f04cbfc4bb2d06a7396a8f299dc33ea1900cedaa316f467b1" dependencies = [ "backtrace", "bytes", "libc", - "mio", - "num_cpus", + "mio 1.0.1", "parking_lot 0.12.3", "pin-project-lite", "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "tokio-macros" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.72", ] [[package]] @@ -3738,7 +3737,7 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls 0.23.11", + "rustls 0.23.12", "rustls-pki-types", "tokio", ] @@ -3820,7 +3819,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.72", ] [[package]] @@ -3871,9 +3870,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "tun2" -version = "2.0.2" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa06f8128a534de505cae0a1d394ca3f7acb8504580b24d23ba774c48baaa98e" +checksum = "50ff242bea1c5ceb9b6aa4918cf340a6c157e1328a2389c5353cf91049d8cf17" dependencies = [ "bytes", "cfg-if", @@ -4023,9 +4022,9 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "walkdir" @@ -4073,7 +4072,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.72", "wasm-bindgen-shared", ] @@ -4107,7 +4106,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.72", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4180,16 +4179,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" -dependencies = [ - "windows-core", - "windows-targets 0.52.6", -] - [[package]] name = "windows-core" version = "0.52.0" @@ -4371,15 +4360,15 @@ dependencies = [ [[package]] name = "wintun" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b3c8c8876c686f8a2d6376999ac1c9a24c74d2968551c9394f7e89127783685" +checksum = "b196f9328341b035820c54beebca487823e2e20a5977f284f2af2a0ee8f04400" dependencies = [ "c2rust-bitfields", "libloading", "log", "thiserror", - "windows", + "windows-sys 0.52.0", ] [[package]] @@ -4429,7 +4418,7 @@ checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.72", "synstructure", ] @@ -4450,7 +4439,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.72", ] [[package]] @@ -4470,7 +4459,7 @@ checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.72", "synstructure", ] @@ -4499,7 +4488,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.72", ] [[package]] diff --git a/shadowsocks-rust/Cargo.toml b/shadowsocks-rust/Cargo.toml index 9cd4252d5a..425f98c098 100644 --- a/shadowsocks-rust/Cargo.toml +++ b/shadowsocks-rust/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "shadowsocks-rust" -version = "1.20.2" +version = "1.20.3" authors = ["Shadowsocks Contributors"] description = "shadowsocks is a fast tunnel proxy that helps you bypass firewalls." repository = "https://github.com/shadowsocks/shadowsocks-rust" @@ -248,7 +248,7 @@ jemallocator = { version = "0.5", optional = true } snmalloc-rs = { version = "0.3", optional = true } rpmalloc = { version = "0.2", optional = true } -shadowsocks-service = { version = "1.20.2", path = "./crates/shadowsocks-service" } +shadowsocks-service = { version = "1.20.3", path = "./crates/shadowsocks-service" } windows-service = { version = "0.7", optional = true } diff --git a/shadowsocks-rust/crates/shadowsocks-service/Cargo.toml b/shadowsocks-rust/crates/shadowsocks-service/Cargo.toml index 024881f9a9..2e5b4617d9 100644 --- a/shadowsocks-rust/crates/shadowsocks-service/Cargo.toml +++ b/shadowsocks-rust/crates/shadowsocks-service/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "shadowsocks-service" -version = "1.20.2" +version = "1.20.3" authors = ["Shadowsocks Contributors"] description = "shadowsocks is a fast tunnel proxy that helps you bypass firewalls." repository = "https://github.com/shadowsocks/shadowsocks-rust" diff --git a/shadowsocks-rust/crates/shadowsocks-service/src/local/loadbalancing/server_stat.rs b/shadowsocks-rust/crates/shadowsocks-service/src/local/loadbalancing/server_stat.rs index 9091ac20d7..e417797104 100644 --- a/shadowsocks-rust/crates/shadowsocks-service/src/local/loadbalancing/server_stat.rs +++ b/shadowsocks-rust/crates/shadowsocks-service/src/local/loadbalancing/server_stat.rs @@ -33,6 +33,8 @@ pub struct ServerStatData { pub latency_stdev: f64, /// Score's average pub latency_mean: f64, + /// Score's median absolute deviation + pub latency_mad: u32, } /// Statistic of a remote server @@ -77,6 +79,7 @@ impl ServerStat { fail_rate: 1.0, latency_stdev: max_latency_stdev, latency_mean: max_server_rtt as f64, + latency_mad: max_server_rtt, }, } } @@ -86,24 +89,27 @@ impl ServerStat { let nrtt = self.data.latency_median as f64 / self.max_server_rtt as f64; // Normalize stdev - let nstdev = self.data.latency_stdev / self.max_latency_stdev; + // let nstdev = self.data.latency_stdev / self.max_latency_stdev; + // Mormalize mad + let nmad = self.data.latency_mad as f64 / self.max_server_rtt as f64; const SCORE_RTT_WEIGHT: f64 = 1.0; const SCORE_FAIL_WEIGHT: f64 = 3.0; - const SCORE_STDEV_WEIGHT: f64 = 1.0; + // const SCORE_STDEV_WEIGHT: f64 = 0.0; + const SCORE_MAD_WEIGHT: f64 = 1.0; // [EPSILON, 1] // Just for avoiding divide by 0 let user_weight = self.user_weight.max(f32::EPSILON); - // Score = (norm_lat * 1.0 + prop_err * 3.0 + stdev * 1.0) / 5.0 / user_weight + // Score = (norm_lat * 1.0 + prop_err * 3.0 + (stdev || mad) * 1.0) / 5.0 / user_weight // // 1. The lower latency, the better // 2. The lower errored count, the better - // 3. The lower latency's stdev, the better + // 3. The lower latency's stdev / mad, the better // 4. The higher user's weight, the better - let score = (nrtt * SCORE_RTT_WEIGHT + self.data.fail_rate * SCORE_FAIL_WEIGHT + nstdev * SCORE_STDEV_WEIGHT) - / (SCORE_RTT_WEIGHT + SCORE_FAIL_WEIGHT + SCORE_STDEV_WEIGHT) + let score = (nrtt * SCORE_RTT_WEIGHT + self.data.fail_rate * SCORE_FAIL_WEIGHT + nmad * SCORE_MAD_WEIGHT) + / (SCORE_RTT_WEIGHT + SCORE_FAIL_WEIGHT + SCORE_MAD_WEIGHT) / user_weight as f64; // Times 10000 converts to u32, for 0.0001 precision @@ -144,8 +150,11 @@ impl ServerStat { // Error rate self.data.fail_rate = cerr as f64 / self.latency_queue.len() as f64; + self.data.latency_median = self.max_server_rtt; self.data.latency_stdev = self.max_latency_stdev; self.data.latency_mean = self.max_server_rtt as f64; + self.data.latency_mad = self.max_server_rtt; + if !vlat.is_empty() { vlat.sort_unstable(); @@ -159,23 +168,39 @@ impl ServerStat { }; if vlat.len() > 1 { - // STDEV let n = vlat.len() as f64; - let mut total_lat = 0; - for s in &vlat { - total_lat += *s; - } + // mean + let total_lat: u32 = vlat.iter().sum(); self.data.latency_mean = total_lat as f64 / n; - let mut acc_diff = 0.0; - for s in &vlat { - let diff = *s as f64 - self.data.latency_mean; - acc_diff += diff * diff; - } + + // STDEV + let acc_mean_diff_square: f64 = vlat + .iter() + .map(|s| { + let diff = *s as f64 - self.data.latency_mean; + diff * diff + }) + .sum(); // Corrected Sample Standard Deviation - self.data.latency_stdev = ((1.0 / (n - 1.0)) * acc_diff).sqrt(); + self.data.latency_stdev = (acc_mean_diff_square / (n - 1.0)).sqrt(); + + // MAD + let mut vlat_abs_diff: Vec = vlat + .iter() + .map(|s| (*s as i32 - self.data.latency_median as i32).abs() as u32) + .collect(); + vlat_abs_diff.sort_unstable(); + + let abs_diff_median_mid = vlat_abs_diff.len() / 2; + self.data.latency_mad = if vlat_abs_diff.len() % 2 == 0 { + (vlat_abs_diff[abs_diff_median_mid] + vlat_abs_diff[abs_diff_median_mid - 1]) / 2 + } else { + vlat_abs_diff[abs_diff_median_mid] + }; } else { self.data.latency_mean = vlat[0] as f64; + self.data.latency_mad = 0; } } diff --git a/shadowsocks-rust/debian/changelog b/shadowsocks-rust/debian/changelog index 1dd600e7ac..f0c7fac109 100644 --- a/shadowsocks-rust/debian/changelog +++ b/shadowsocks-rust/debian/changelog @@ -1,3 +1,13 @@ +shadowsocks-rust (1.20.3) unstable; urgency=medium + + ## Features + + - `local`: Ping Balancer scores replaced standard deviation with median absolute deviation, which should help focusing less on outlying observations in latency samples. + + ## Bug Fixes + + - #1589 `local-tun`: Removes linking to [`SetInterfaceDnsSettings`](https://learn.microsoft.com/en-us/windows/win32/api/netioapi/nf-netioapi-setinterfacednssettings) on Windows + shadowsocks-rust (1.20.2) unstable; urgency=medium ## Features diff --git a/small/luci-app-mosdns/Makefile b/small/luci-app-mosdns/Makefile index bb832df9e0..892e28bfa6 100644 --- a/small/luci-app-mosdns/Makefile +++ b/small/luci-app-mosdns/Makefile @@ -1,7 +1,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-mosdns -PKG_VERSION:=1.5.22 +PKG_VERSION:=1.5.23 PKG_RELEASE:=1 LUCI_TITLE:=LuCI Support for mosdns diff --git a/small/luci-app-mosdns/luasrc/model/cbi/mosdns/basic.lua b/small/luci-app-mosdns/luasrc/model/cbi/mosdns/basic.lua index 5d6d76d7d8..dcd8f48712 100644 --- a/small/luci-app-mosdns/luasrc/model/cbi/mosdns/basic.lua +++ b/small/luci-app-mosdns/luasrc/model/cbi/mosdns/basic.lua @@ -150,20 +150,25 @@ o.rmempty = false o.default = false o:depends("configfile", "/var/etc/mosdns.json") -o = s:taboption("advanced", Value, "cache_size", translate("DNS Cache Size"), translate("DNS cache size (in piece). To disable caching, please set to 0.")) +o = s:taboption("advanced", Flag, "cache", translate("Enable DNS Cache")) +o.rmempty = false +o.default = false +o:depends("configfile", "/var/etc/mosdns.json") + +o = s:taboption("advanced", Value, "cache_size", translate("DNS Cache Size"), translate("DNS cache size (in piece).")) o.datatype = "and(uinteger,min(0))" o.default = "8000" -o:depends("configfile", "/var/etc/mosdns.json") +o:depends("cache", "1") o = s:taboption("advanced", Value, "lazy_cache_ttl", translate("Lazy Cache TTL"), translate("Lazy cache survival time (in second). To disable Lazy Cache, please set to 0.")) o.datatype = "and(uinteger,min(0))" o.default = "86400" -o:depends("configfile", "/var/etc/mosdns.json") +o:depends("cache", "1") o = s:taboption("advanced", Flag, "dump_file", translate("Cache Dump"), translate("Save the cache locally and reload the cache dump on the next startup")) o.rmempty = false o.default = false -o:depends("configfile", "/var/etc/mosdns.json") +o:depends("cache", "1") o = s:taboption("advanced", Value, "dump_interval", translate("Auto Save Cache Interval")) o.datatype = "and(uinteger,min(0))" diff --git a/small/luci-app-mosdns/po/zh-cn/mosdns.po b/small/luci-app-mosdns/po/zh-cn/mosdns.po index 15989d5bab..d60405403f 100644 --- a/small/luci-app-mosdns/po/zh-cn/mosdns.po +++ b/small/luci-app-mosdns/po/zh-cn/mosdns.po @@ -292,11 +292,14 @@ msgstr "防止 DNS 泄漏" msgid "Enable this option fallback policy forces forwarding to remote DNS" msgstr "启用此选项 fallback 策略会强制转发到远程 DNS" +msgid "Enable DNS Cache" +msgstr "启用 DNS 缓存" + msgid "DNS Cache Size" msgstr "DNS 缓存大小" -msgid "DNS cache size (in piece). To disable caching, please set to 0." -msgstr "DNS 缓存大小(单位:条),要禁用缓存,请设置为 0" +msgid "DNS cache size (in piece)." +msgstr "DNS 缓存大小(单位:条)" msgid "Lazy Cache TTL" msgstr "乐观缓存 TTL" diff --git a/small/luci-app-mosdns/root/etc/config/mosdns b/small/luci-app-mosdns/root/etc/config/mosdns index a15981f5a1..11e2e843e9 100644 --- a/small/luci-app-mosdns/root/etc/config/mosdns +++ b/small/luci-app-mosdns/root/etc/config/mosdns @@ -11,9 +11,7 @@ config mosdns 'config' option configfile '/var/etc/mosdns.json' option log_level 'info' option log_file '/var/log/mosdns.log' - option cache_size '8000' - option lazy_cache_ttl '86400' - option dump_file '0' + option cache '0' option concurrent '1' option idle_timeout '30' option minimal_ttl '0' diff --git a/small/luci-app-mosdns/root/etc/init.d/mosdns b/small/luci-app-mosdns/root/etc/init.d/mosdns index aa16291f48..b4ddc15386 100755 --- a/small/luci-app-mosdns/root/etc/init.d/mosdns +++ b/small/luci-app-mosdns/root/etc/init.d/mosdns @@ -33,6 +33,7 @@ get_config() { config_get enabled $1 enabled 0 config_get adblock $1 adblock 0 config_get ad_source $1 ad_source "" + config_get cache $1 cache 0 config_get cache_size $1 cache_size 8000 config_get lazy_cache_ttl $1 lazy_cache_ttl 86400 config_get dump_file $1 dump_file 0 @@ -231,18 +232,20 @@ generate_config() { json_close_object json_close_object # plugin: lazy_cache - json_add_object - json_add_string "tag" "lazy_cache" - json_add_string "type" "cache" - json_add_object "args" - json_add_int "size" "$cache_size" - json_add_int "lazy_cache_ttl" "$lazy_cache_ttl" - [ "$dump_file" -eq 1 ] && { - json_add_string "dump_file" "/etc/mosdns/cache.dump" - json_add_int "dump_interval" "$dump_interval" + [ "$cache" -eq 1 ] && { + json_add_object + json_add_string "tag" "lazy_cache" + json_add_string "type" "cache" + json_add_object "args" + json_add_int "size" "$cache_size" + json_add_int "lazy_cache_ttl" "$lazy_cache_ttl" + [ "$dump_file" -eq 1 ] && { + json_add_string "dump_file" "/etc/mosdns/cache.dump" + json_add_int "dump_interval" "$dump_interval" + } + json_close_object + json_close_object } - json_close_object - json_close_object # plugin: forward_xinfeng_udp json_add_object json_add_string "tag" "forward_xinfeng_udp" @@ -573,15 +576,17 @@ generate_config() { json_add_object json_add_string "exec" "jump has_resp_sequence" json_close_object - json_add_object - json_add_array "matches" - json_add_string "" "!qname \$ddnslist" - json_add_string "" "!qname \$blocklist" - json_add_string "" "!qname \$adlist" - json_add_string "" "!qname \$local_ptr" - json_close_array - json_add_string "exec" "\$lazy_cache" - json_close_object + [ "$cache" -eq 1 ] && { + json_add_object + json_add_array "matches" + json_add_string "" "!qname \$ddnslist" + json_add_string "" "!qname \$blocklist" + json_add_string "" "!qname \$adlist" + json_add_string "" "!qname \$local_ptr" + json_close_array + json_add_string "exec" "\$lazy_cache" + json_close_object + } json_add_object json_add_string "exec" "\$redirect" json_close_object diff --git a/small/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua b/small/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua index 5e3a0c6090..1606bd1a3d 100644 --- a/small/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua +++ b/small/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua @@ -85,6 +85,35 @@ local doh_validate = function(self, value, t) return nil, translate("DoH request address") .. " " .. translate("Format must be:") .. " URL,IP" end +local chinadns_dot_validate = function(self, value, t) + if value ~= "" then + value = api.trim(value) + -- Define patterns for IPv4, IPv6, domain, and port + local ipv4_pattern = "(%d+%.%d+%.%d+%.%d+)" + local ipv6_pattern = "([%[%]a-fA-F0-9:]+)" -- IPv6 addresses are wrapped in [] + local domain_pattern = "([%w-_%.]+)" + local port_pattern = "(%d+)" + -- Define patterns for the different formats + local patterns = { + "^tls://" .. domain_pattern .. "@" .. ipv4_pattern .. "#" .. port_pattern .. "$", -- tls://域名@ip#端口 + "^tls://" .. ipv4_pattern .. "#" .. port_pattern .. "$", -- tls://ip#端口 + "^tls://" .. domain_pattern .. "@" .. ipv4_pattern .. "$", -- tls://域名@ip + "^tls://" .. ipv4_pattern .. "$", -- tls://ip + "^tls://" .. domain_pattern .. "@" .. ipv6_pattern .. "#" .. port_pattern .. "$", -- tls://域名@[IPv6]#端口 + "^tls://" .. ipv6_pattern .. "#" .. port_pattern .. "$", -- tls://[IPv6]#端口 + "^tls://" .. domain_pattern .. "@" .. ipv6_pattern .. "$", -- tls://域名@[IPv6] + "^tls://" .. ipv6_pattern .. "$" -- tls://[IPv6] + } + -- Check if the string matches any of the patterns + for _, pattern in ipairs(patterns) do + if value:match(pattern) then + return value + end + end + return nil, translate("Direct DNS") .. " DoT " .. translate("Format must be:") .. " tls://Domain@IP(#Port) or tls://IP(#Port)" + end +end + m:append(Template(appname .. "/global/status")) s = m:section(TypedSection, "global") @@ -266,20 +295,48 @@ dns_shunt = s:taboption("DNS", ListValue, "dns_shunt", "DNS " .. translate("Shun dns_shunt:value("dnsmasq", "Dnsmasq") dns_shunt:value("chinadns-ng", "Dnsmasq + ChinaDNS-NG") -o = s:taboption("DNS", Value, "direct_dns", translate("Direct DNS")) -o.datatype = "or(ipaddr,ipaddrport)" +o = s:taboption("DNS", ListValue, "direct_dns_mode", translate("Direct DNS") .. " " .. translate("Request protocol")) o.default = "" o:value("", translate("Auto")) -o:value("223.5.5.5") -o:value("223.6.6.6") -o:value("114.114.114.114") -o:value("119.29.29.29") -o:value("180.76.76.76") -o:value("1.12.12.12") -o:value("120.53.53.53") +o:value("udp", "UDP") +o:value("tcp", "TCP") +if os.execute("chinadns-ng -V | grep -i wolfssl >/dev/null") == 0 then + o:value("dot", "DoT") +end +--TO DO +--o:value("doh", "DoH") o:depends({dns_shunt = "dnsmasq"}) o:depends({dns_shunt = "chinadns-ng"}) +o = s:taboption("DNS", Value, "direct_dns_udp", translate("Direct DNS")) +o.datatype = "or(ipaddr,ipaddrport)" +o.default = "223.5.5.5" +o:value("223.5.5.5") +o:value("223.6.6.6") +o:value("119.29.29.29") +o:value("180.184.1.1") +o:value("180.184.2.2") +o:value("114.114.114.114") +o:depends("direct_dns_mode", "udp") + +o = s:taboption("DNS", Value, "direct_dns_tcp", translate("Direct DNS")) +o.datatype = "or(ipaddr,ipaddrport)" +o.default = "223.5.5.5" +o:value("223.5.5.5") +o:value("223.6.6.6") +o:value("180.184.1.1") +o:value("180.184.2.2") +o:depends("direct_dns_mode", "tcp") + +o = s:taboption("DNS", Value, "direct_dns_dot", translate("Direct DNS")) +o.default = "tls://dot.pub@1.12.12.12" +o:value("tls://dot.pub@1.12.12.12") +o:value("tls://dot.pub@120.53.53.53") +o:value("tls://dot.360.cn@36.99.170.86") +o:value("tls://dot.360.cn@101.198.191.4") +o.validate = chinadns_dot_validate +o:depends("direct_dns_mode", "dot") + o = s:taboption("DNS", Flag, "filter_proxy_ipv6", translate("Filter Proxy Host IPv6"), translate("Experimental feature.")) o.default = "0" diff --git a/small/luci-app-passwall/root/usr/share/passwall/app.sh b/small/luci-app-passwall/root/usr/share/passwall/app.sh index 03dc8c7b1e..27672a3e61 100755 --- a/small/luci-app-passwall/root/usr/share/passwall/app.sh +++ b/small/luci-app-passwall/root/usr/share/passwall/app.sh @@ -1298,6 +1298,38 @@ stop_crontab() { start_dns() { echolog "DNS域名解析:" + local direct_dns_mode=$(config_t_get global direct_dns_mode "auto") + case "$direct_dns_mode" in + udp) + LOCAL_DNS=$(config_t_get global direct_dns_udp 223.5.5.5 | sed 's/:/#/g') + ;; + tcp) + LOCAL_DNS="127.0.0.1#${dns_listen_port}" + dns_listen_port=$(expr $dns_listen_port + 1) + local DIRECT_DNS=$(config_t_get global direct_dns_tcp 223.5.5.5 | sed 's/:/#/g') + ln_run "$(first_type dns2tcp)" dns2tcp "/dev/null" -L "${LOCAL_DNS}" -R "$(get_first_dns DIRECT_DNS 53)" -v + echolog " - dns2tcp(${LOCAL_DNS}) -> tcp://$(get_first_dns DIRECT_DNS 53 | sed 's/#/:/g')" + echolog " * 请确保上游直连 DNS 支持 TCP 查询。" + ;; + dot) + if [ "$(chinadns-ng -V | grep -i wolfssl)" != "nil" ]; then + LOCAL_DNS="127.0.0.1#${dns_listen_port}" + local cdns_listen_port=${dns_listen_port} + dns_listen_port=$(expr $dns_listen_port + 1) + local DIRECT_DNS=$(config_t_get global direct_dns_dot "tls://dot.pub@1.12.12.12") + ln_run "$(first_type chinadns-ng)" chinadns-ng "/dev/null" -b 127.0.0.1 -l ${cdns_listen_port}@udp -c ${DIRECT_DNS} -d chn + echolog " - ChinaDNS-NG(${LOCAL_DNS}) -> ${DIRECT_DNS}" + echolog " * 请确保上游直连 DNS 支持 DoT 查询。" + else + echolog " - 你的ChinaDNS-NG版本不支持DoT,直连DNS将使用默认UDP地址。" + fi + ;; + auto) + #Automatic logic is already done by default + : + ;; + esac + TUN_DNS="127.0.0.1#${dns_listen_port}" [ "${resolve_dns}" == "1" ] && TUN_DNS="127.0.0.1#${resolve_dns_port}" @@ -1880,9 +1912,6 @@ DEFAULT_DNSMASQ_CFGID=$(uci show dhcp.@dnsmasq[0] | awk -F '.' '{print $2}' | a DEFAULT_DNS=$(uci show dhcp.@dnsmasq[0] | grep "\.server=" | awk -F '=' '{print $2}' | sed "s/'//g" | tr ' ' '\n' | grep -v "\/" | head -2 | sed ':label;N;s/\n/,/;b label') [ -z "${DEFAULT_DNS}" ] && [ "$(echo $ISP_DNS | tr ' ' '\n' | wc -l)" -le 2 ] && DEFAULT_DNS=$(echo -n $ISP_DNS | tr ' ' '\n' | head -2 | tr '\n' ',') LOCAL_DNS="${DEFAULT_DNS:-119.29.29.29,223.5.5.5}" -DIRECT_DNS=$(config_t_get global direct_dns "auto") -#Automatic logic is already done by default -[ "${DIRECT_DNS}" != "auto" ] && LOCAL_DNS=$(echo ${DIRECT_DNS} | sed 's/:/#/g') DNS_QUERY_STRATEGY="UseIP" [ "$FILTER_PROXY_IPV6" = "1" ] && DNS_QUERY_STRATEGY="UseIPv4" diff --git a/small/luci-app-passwall/root/usr/share/passwall/rules/direct_ip b/small/luci-app-passwall/root/usr/share/passwall/rules/direct_ip index 04e855dc1a..b73edaa63a 100644 --- a/small/luci-app-passwall/root/usr/share/passwall/rules/direct_ip +++ b/small/luci-app-passwall/root/usr/share/passwall/rules/direct_ip @@ -6,3 +6,5 @@ 180.76.76.76 1.12.12.12 120.53.53.53 +180.184.1.1 +180.184.2.2 diff --git a/v2rayng/V2rayNG/app/build.gradle.kts b/v2rayng/V2rayNG/app/build.gradle.kts index 8cff84c732..8cf6fa15ad 100644 --- a/v2rayng/V2rayNG/app/build.gradle.kts +++ b/v2rayng/V2rayNG/app/build.gradle.kts @@ -96,44 +96,44 @@ android { dependencies { implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.aar","*.jar")))) - testImplementation("junit:junit:4.13.2") + testImplementation(libs.junit) - implementation("com.google.android.flexbox:flexbox:3.0.0") + implementation(libs.flexbox) // Androidx - implementation("androidx.constraintlayout:constraintlayout:2.1.4") - implementation("androidx.legacy:legacy-support-v4:1.0.0") - implementation("androidx.appcompat:appcompat:1.7.0") - implementation("com.google.android.material:material:1.12.0") - implementation("androidx.cardview:cardview:1.0.0") - implementation("androidx.preference:preference-ktx:1.2.1") - implementation("androidx.recyclerview:recyclerview:1.3.2") - implementation("androidx.fragment:fragment-ktx:1.8.1") - implementation("androidx.multidex:multidex:2.0.1") - implementation("androidx.viewpager2:viewpager2:1.1.0") + implementation(libs.constraintlayout) + implementation(libs.legacy.support.v4) + implementation(libs.appcompat) + implementation(libs.material) + implementation(libs.cardview) + implementation(libs.preference.ktx) + implementation(libs.recyclerview) + implementation(libs.fragment.ktx) + implementation(libs.multidex) + implementation(libs.viewpager2) // Androidx ktx - implementation("androidx.activity:activity-ktx:1.9.0") - implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.3") - implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.8.3") - implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.3") + implementation(libs.activity.ktx) + implementation(libs.lifecycle.viewmodel.ktx) + implementation(libs.lifecycle.livedata.ktx) + implementation(libs.lifecycle.runtime.ktx) //kotlin - implementation("org.jetbrains.kotlin:kotlin-reflect:1.9.23") - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1") - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1") + implementation(libs.kotlin.reflect) + implementation(libs.kotlinx.coroutines.core) + implementation(libs.kotlinx.coroutines.android) - implementation("com.tencent:mmkv-static:1.3.4") - implementation("com.google.code.gson:gson:2.11.0") - implementation("io.reactivex:rxjava:1.3.8") - implementation("io.reactivex:rxandroid:1.2.1") - implementation("com.tbruyelle.rxpermissions:rxpermissions:0.9.4@aar") - implementation("me.drakeet.support:toastcompat:1.1.0") - implementation("com.blacksquircle.ui:editorkit:2.9.0") - implementation("com.blacksquircle.ui:language-base:2.9.0") - implementation("com.blacksquircle.ui:language-json:2.9.0") - implementation("io.github.g00fy2.quickie:quickie-bundled:1.9.0") - implementation("com.google.zxing:core:3.5.3") + implementation(libs.mmkv.static) + implementation(libs.gson) + implementation(libs.rxjava) + implementation(libs.rxandroid) + implementation(libs.rxpermissions) + implementation(libs.toastcompat) + implementation(libs.editorkit) + implementation(libs.language.base) + implementation(libs.language.json) + implementation(libs.quickie.bundled) + implementation(libs.core) - implementation("androidx.work:work-runtime-ktx:2.8.1") - implementation("androidx.work:work-multiprocess:2.8.1") + implementation(libs.work.runtime.ktx) + implementation(libs.work.multiprocess) } \ No newline at end of file diff --git a/v2rayng/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayServiceManager.kt b/v2rayng/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayServiceManager.kt index 0f605eadc7..82cf11884f 100644 --- a/v2rayng/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayServiceManager.kt +++ b/v2rayng/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayServiceManager.kt @@ -73,7 +73,7 @@ object V2RayServiceManager { } else { context.toast(R.string.toast_services_start) } - val intent = if (settingsStorage?.decodeString(AppConfig.PREF_MODE) ?: "VPN" == "VPN") { + val intent = if ((settingsStorage?.decodeString(AppConfig.PREF_MODE) ?: "VPN") == "VPN") { Intent(context.applicationContext, V2RayVpnService::class.java) } else { Intent(context.applicationContext, V2RayProxyOnlyService::class.java) diff --git a/v2rayng/V2rayNG/build.gradle.kts b/v2rayng/V2rayNG/build.gradle.kts index b45fb3494f..a4a8ba4770 100644 --- a/v2rayng/V2rayNG/build.gradle.kts +++ b/v2rayng/V2rayNG/build.gradle.kts @@ -1,6 +1,6 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.2.2" apply false - id("com.android.library") version "8.2.2" apply false + id("com.android.application") version "8.5.0" apply false + id("com.android.library") version "8.5.0" apply false id("org.jetbrains.kotlin.android") version "1.9.23" apply false } diff --git a/v2rayng/V2rayNG/gradle/libs.versions.toml b/v2rayng/V2rayNG/gradle/libs.versions.toml new file mode 100644 index 0000000000..ed07266935 --- /dev/null +++ b/v2rayng/V2rayNG/gradle/libs.versions.toml @@ -0,0 +1,63 @@ +[versions] +activityKtx = "1.9.1" +appcompat = "1.7.0" +cardview = "1.0.0" +constraintlayout = "2.1.4" +core = "3.5.3" +editorkit = "2.9.0" +flexbox = "3.0.0" +fragmentKtx = "1.8.2" +gson = "2.11.0" +junit = "4.13.2" +kotlinReflect = "1.9.23" +kotlinxCoroutinesCore = "1.8.1" +legacySupportV4 = "1.0.0" +lifecycleViewmodelKtx = "2.8.4" +material = "1.12.0" +mmkvStatic = "1.3.4" +multidex = "2.0.1" +preferenceKtx = "1.2.1" +quickieBundled = "1.9.0" +recyclerview = "1.3.2" +rxandroid = "1.2.1" +rxjava = "1.3.8" +rxpermissions = "0.9.4" +toastcompat = "1.1.0" +viewpager2 = "1.1.0" +workRuntimeKtx = "2.8.1" + +[libraries] +activity-ktx = { module = "androidx.activity:activity-ktx", version.ref = "activityKtx" } +appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" } +cardview = { module = "androidx.cardview:cardview", version.ref = "cardview" } +constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "constraintlayout" } +core = { module = "com.google.zxing:core", version.ref = "core" } +editorkit = { module = "com.blacksquircle.ui:editorkit", version.ref = "editorkit" } +flexbox = { module = "com.google.android.flexbox:flexbox", version.ref = "flexbox" } +fragment-ktx = { module = "androidx.fragment:fragment-ktx", version.ref = "fragmentKtx" } +gson = { module = "com.google.code.gson:gson", version.ref = "gson" } +junit = { module = "junit:junit", version.ref = "junit" } +kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlinReflect" } +kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinxCoroutinesCore" } +kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutinesCore" } +language-base = { module = "com.blacksquircle.ui:language-base", version.ref = "editorkit" } +language-json = { module = "com.blacksquircle.ui:language-json", version.ref = "editorkit" } +legacy-support-v4 = { module = "androidx.legacy:legacy-support-v4", version.ref = "legacySupportV4" } +lifecycle-livedata-ktx = { module = "androidx.lifecycle:lifecycle-livedata-ktx", version.ref = "lifecycleViewmodelKtx" } +lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycleViewmodelKtx" } +lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "lifecycleViewmodelKtx" } +material = { module = "com.google.android.material:material", version.ref = "material" } +mmkv-static = { module = "com.tencent:mmkv-static", version.ref = "mmkvStatic" } +multidex = { module = "androidx.multidex:multidex", version.ref = "multidex" } +quickie-bundled = { module = "io.github.g00fy2.quickie:quickie-bundled", version.ref = "quickieBundled" } +recyclerview = { module = "androidx.recyclerview:recyclerview", version.ref = "recyclerview" } +preference-ktx = { module = "androidx.preference:preference-ktx", version.ref = "preferenceKtx" } +rxandroid = { module = "io.reactivex:rxandroid", version.ref = "rxandroid" } +rxjava = { module = "io.reactivex:rxjava", version.ref = "rxjava" } +rxpermissions = { module = "com.tbruyelle.rxpermissions:rxpermissions", version.ref = "rxpermissions" } +toastcompat = { module = "me.drakeet.support:toastcompat", version.ref = "toastcompat" } +viewpager2 = { module = "androidx.viewpager2:viewpager2", version.ref = "viewpager2" } +work-multiprocess = { module = "androidx.work:work-multiprocess", version.ref = "workRuntimeKtx" } +work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "workRuntimeKtx" } + +[plugins] \ No newline at end of file diff --git a/v2rayu/V2rayU/Base.lproj/ConfigWindow.xib b/v2rayu/V2rayU/Base.lproj/ConfigWindow.xib index 127ea0854a..d84b3a9779 100644 --- a/v2rayu/V2rayU/Base.lproj/ConfigWindow.xib +++ b/v2rayu/V2rayU/Base.lproj/ConfigWindow.xib @@ -36,11 +36,10 @@ - + - @@ -73,6 +72,8 @@ + + @@ -1145,17 +1146,59 @@ Gw - - + + - + + + + + + + + + + + + + + + NSAllRomanInputSourcesLocaleIdentifier + + + + + + + + + + + + + + + + + + + + + NSAllRomanInputSourcesLocaleIdentifier + + + + + + + - + @@ -1172,24 +1215,6 @@ Gw - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -