diff --git a/Makefile b/Makefile index 039f5368..75cc46d0 100644 --- a/Makefile +++ b/Makefile @@ -144,7 +144,7 @@ all-arch: $(PLATFORM_LIST) $(WINDOWS_ARCH_LIST) releases: $(gz_releases) $(zip_releases) vet: - go vet ./... + go test ./... lint: golangci-lint run ./... diff --git a/common/cache/lrucache.go b/common/cache/lrucache.go index 25a1b48e..0bea06f6 100644 --- a/common/cache/lrucache.go +++ b/common/cache/lrucache.go @@ -216,6 +216,15 @@ func (c *LruCache) deleteElement(le *list.Element) { } } +func (c *LruCache) Clear() error { + c.mu.Lock() + + c.cache = make(map[any]*list.Element) + + c.mu.Unlock() + return nil +} + type entry struct { key any value any diff --git a/component/fakeip/cachefile.go b/component/fakeip/cachefile.go index 04bdc65c..9e5f22f4 100644 --- a/component/fakeip/cachefile.go +++ b/component/fakeip/cachefile.go @@ -53,3 +53,8 @@ func (c *cachefileStore) Exist(ip net.IP) bool { // CloneTo implements store.CloneTo // already persistence func (c *cachefileStore) CloneTo(store store) {} + +// FlushFakeIP implements store.FlushFakeIP +func (c *cachefileStore) FlushFakeIP() error { + return c.cache.FlushFakeIP() +} diff --git a/component/fakeip/memory.go b/component/fakeip/memory.go index c6c6873d..a7ff3708 100644 --- a/component/fakeip/memory.go +++ b/component/fakeip/memory.go @@ -67,3 +67,8 @@ func (m *memoryStore) CloneTo(store store) { m.cache.CloneTo(ms.cache) } } + +// FlushFakeIP implements store.FlushFakeIP +func (m *memoryStore) FlushFakeIP() error { + return m.cache.Clear() +} diff --git a/component/fakeip/pool.go b/component/fakeip/pool.go index 68484899..e93873c9 100644 --- a/component/fakeip/pool.go +++ b/component/fakeip/pool.go @@ -18,6 +18,7 @@ type store interface { DelByIP(ip net.IP) Exist(ip net.IP) bool CloneTo(store) + FlushFakeIP() error } // Pool is a implementation about fake ip generator without storage @@ -120,6 +121,10 @@ func (p *Pool) get(host string) net.IP { return ip } +func (p *Pool) FlushFakeIP() error { + return p.store.FlushFakeIP() +} + func ipToUint(ip net.IP) uint32 { v := uint32(ip[0]) << 24 v += uint32(ip[1]) << 16 diff --git a/component/fakeip/pool_test.go b/component/fakeip/pool_test.go index 1e30bd94..86e80a2d 100644 --- a/component/fakeip/pool_test.go +++ b/component/fakeip/pool_test.go @@ -193,3 +193,59 @@ func TestPool_Error(t *testing.T) { assert.Error(t, err) } + +func TestPool_FlushFileCache(t *testing.T) { + _, ipnet, _ := net.ParseCIDR("192.168.0.1/28") + pools, tempfile, err := createPools(Options{ + IPNet: ipnet, + Size: 10, + }) + assert.Nil(t, err) + defer os.Remove(tempfile) + + for _, pool := range pools { + foo := pool.Lookup("foo.com") + bar := pool.Lookup("baz.com") + bax := pool.Lookup("baz.com") + fox := pool.Lookup("foo.com") + + err = pool.FlushFakeIP() + assert.Nil(t, err) + + baz := pool.Lookup("foo.com") + next := pool.Lookup("baz.com") + nero := pool.Lookup("foo.com") + + assert.Equal(t, foo, fox) + assert.NotEqual(t, foo, baz) + assert.Equal(t, bar, bax) + assert.NotEqual(t, bar, next) + assert.Equal(t, baz, nero) + } +} + +func TestPool_FlushMemoryCache(t *testing.T) { + _, ipnet, _ := net.ParseCIDR("192.168.0.1/28") + pool, _ := New(Options{ + IPNet: ipnet, + Size: 10, + }) + + foo := pool.Lookup("foo.com") + bar := pool.Lookup("baz.com") + bax := pool.Lookup("baz.com") + fox := pool.Lookup("foo.com") + + err := pool.FlushFakeIP() + assert.Nil(t, err) + + baz := pool.Lookup("foo.com") + next := pool.Lookup("baz.com") + nero := pool.Lookup("foo.com") + + assert.Equal(t, foo, fox) + assert.NotEqual(t, foo, baz) + assert.Equal(t, bar, bax) + assert.NotEqual(t, bar, next) + assert.Equal(t, baz, nero) +} diff --git a/component/profile/cachefile/cache.go b/component/profile/cachefile/cache.go index 71113567..3d2dd1de 100644 --- a/component/profile/cachefile/cache.go +++ b/component/profile/cachefile/cache.go @@ -132,6 +132,17 @@ func (c *CacheFile) GetFakeip(key []byte) []byte { return bucket.Get(key) } +func (c *CacheFile) FlushFakeIP() error { + err := c.DB.Batch(func(t *bbolt.Tx) error { + bucket := t.Bucket(bucketFakeip) + if bucket == nil { + return nil + } + return t.DeleteBucket(bucketFakeip) + }) + return err +} + func (c *CacheFile) Close() error { return c.DB.Close() } diff --git a/component/resolver/enhancer.go b/component/resolver/enhancer.go index 68f1d5d1..9df3f54b 100644 --- a/component/resolver/enhancer.go +++ b/component/resolver/enhancer.go @@ -13,6 +13,7 @@ type Enhancer interface { IsFakeBroadcastIP(net.IP) bool IsExistFakeIP(net.IP) bool FindHostByIP(net.IP) (string, bool) + FlushFakeIP() error } func FakeIPEnabled() bool { @@ -62,3 +63,10 @@ func FindHostByIP(ip net.IP) (string, bool) { return "", false } + +func FlushFakeIP() error { + if mapper := DefaultHostMapper; mapper != nil { + return mapper.FlushFakeIP() + } + return nil +} diff --git a/dns/enhancer.go b/dns/enhancer.go index f42fa268..9bf568c7 100644 --- a/dns/enhancer.go +++ b/dns/enhancer.go @@ -84,6 +84,13 @@ func (h *ResolverEnhancer) PatchFrom(o *ResolverEnhancer) { } } +func (h *ResolverEnhancer) FlushFakeIP() error { + if h.fakePool != nil { + return h.fakePool.FlushFakeIP() + } + return nil +} + func NewEnhancer(cfg Config) *ResolverEnhancer { var fakePool *fakeip.Pool var mapping *cache.LruCache diff --git a/hub/route/cache.go b/hub/route/cache.go new file mode 100644 index 00000000..bdfd2e35 --- /dev/null +++ b/hub/route/cache.go @@ -0,0 +1,26 @@ +package route + +import ( + "net/http" + + "github.com/Dreamacro/clash/component/resolver" + + "github.com/go-chi/chi/v5" + "github.com/go-chi/render" +) + +func cacheRouter() http.Handler { + r := chi.NewRouter() + r.Post("/fakeip/flush", flushFakeIPPool) + return r +} + +func flushFakeIPPool(w http.ResponseWriter, r *http.Request) { + err := resolver.FlushFakeIP() + if err != nil { + render.Status(r, http.StatusBadRequest) + render.JSON(w, r, newError(err.Error())) + return + } + render.NoContent(w, r) +} diff --git a/hub/route/server.go b/hub/route/server.go index e7fea3d6..cad8e49d 100644 --- a/hub/route/server.go +++ b/hub/route/server.go @@ -73,6 +73,7 @@ func Start(addr string, secret string) { r.Mount("/providers/proxies", proxyProviderRouter()) r.Mount("/providers/rules", ruleProviderRouter()) r.Mount("/script", scriptRouter()) + r.Mount("/cache", cacheRouter()) }) if uiPath != "" { @@ -132,7 +133,7 @@ func authentication(next http.Handler) http.Handler { } func hello(w http.ResponseWriter, r *http.Request) { - render.JSON(w, r, render.M{"hello": "clash"}) + render.JSON(w, r, render.M{"hello": "clash.meta"}) } func traffic(w http.ResponseWriter, r *http.Request) { diff --git a/listener/tun/device/tun/tun_wireguard.go b/listener/tun/device/tun/tun_wireguard.go index dabd1252..50db6511 100644 --- a/listener/tun/device/tun/tun_wireguard.go +++ b/listener/tun/device/tun/tun_wireguard.go @@ -3,9 +3,10 @@ package tun import ( + "errors" "fmt" + "os" "runtime" - "strings" "github.com/Dreamacro/clash/common/pool" "github.com/Dreamacro/clash/listener/tun/device" @@ -53,9 +54,7 @@ func Open(name string, mtu uint32) (_ device.Device, err error) { nt, err := tun.CreateTUN(t.name, forcedMTU) // forcedMTU do not work on wintun, need to be setting by other way // retry if abnormal exit on Windows at last time - if err != nil && runtime.GOOS == "windows" && - strings.HasSuffix(err.Error(), "file already exists.") { - + if err != nil && runtime.GOOS == "windows" && errors.Is(err, os.ErrExist) { nt, err = tun.CreateTUN(t.name, forcedMTU) } @@ -80,7 +79,9 @@ func (t *TUN) Read(packet []byte) (int, error) { } buff := pool.Get(t.offset + cap(packet)) - defer pool.Put(buff) + defer func() { + _ = pool.Put(buff) + }() n, err := t.nt.Read(buff, t.offset) if err != nil { diff --git a/main.go b/main.go index ed9d76c3..e4b85b9b 100644 --- a/main.go +++ b/main.go @@ -107,7 +107,7 @@ func main() { } sigCh := make(chan os.Signal, 1) - signal.Notify(sigCh, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM) + signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) <-sigCh // cleanup