package fakeip import ( "fmt" "net/netip" "os" "testing" "time" "github.com/metacubex/mihomo/component/profile/cachefile" "github.com/metacubex/mihomo/component/trie" C "github.com/metacubex/mihomo/constant" "github.com/metacubex/bbolt" "github.com/stretchr/testify/assert" ) func createPools(options Options) ([]*Pool, string, error) { pool, err := New(options) if err != nil { return nil, "", err } filePool, tempfile, err := createCachefileStore(options) if err != nil { return nil, "", err } return []*Pool{pool, filePool}, tempfile, nil } func createCachefileStore(options Options) (*Pool, string, error) { pool, err := New(options) if err != nil { return nil, "", err } f, err := os.CreateTemp("", "mihomo") if err != nil { return nil, "", err } db, err := bbolt.Open(f.Name(), 0o666, &bbolt.Options{Timeout: time.Second}) if err != nil { return nil, "", err } pool.store = newCachefileStore(&cachefile.CacheFile{DB: db}) return pool, f.Name(), nil } func TestPool_Basic(t *testing.T) { ipnet := netip.MustParsePrefix("192.168.0.0/28") pools, tempfile, err := createPools(Options{ IPNet: ipnet, Size: 10, }) assert.Nil(t, err) defer os.Remove(tempfile) for _, pool := range pools { first := pool.Lookup("foo.com") last := pool.Lookup("bar.com") bar, exist := pool.LookBack(last) assert.Equal(t, first, netip.AddrFrom4([4]byte{192, 168, 0, 4})) assert.Equal(t, pool.Lookup("foo.com"), netip.AddrFrom4([4]byte{192, 168, 0, 4})) assert.Equal(t, last, netip.AddrFrom4([4]byte{192, 168, 0, 5})) assert.True(t, exist) assert.Equal(t, bar, "bar.com") assert.Equal(t, pool.Gateway(), netip.AddrFrom4([4]byte{192, 168, 0, 1})) assert.Equal(t, pool.Broadcast(), netip.AddrFrom4([4]byte{192, 168, 0, 15})) assert.Equal(t, pool.IPNet().String(), ipnet.String()) assert.True(t, pool.Exist(netip.AddrFrom4([4]byte{192, 168, 0, 5}))) assert.False(t, pool.Exist(netip.AddrFrom4([4]byte{192, 168, 0, 6}))) assert.False(t, pool.Exist(netip.MustParseAddr("::1"))) } } func TestPool_BasicV6(t *testing.T) { ipnet := netip.MustParsePrefix("2001:4860:4860::8888/118") pools, tempfile, err := createPools(Options{ IPNet: ipnet, Size: 10, }) assert.Nil(t, err) defer os.Remove(tempfile) for _, pool := range pools { first := pool.Lookup("foo.com") last := pool.Lookup("bar.com") bar, exist := pool.LookBack(last) assert.Equal(t, first, netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8804")) assert.Equal(t, pool.Lookup("foo.com"), netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8804")) assert.Equal(t, last, netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8805")) assert.True(t, exist) assert.Equal(t, bar, "bar.com") assert.Equal(t, pool.Gateway(), netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8801")) assert.Equal(t, pool.Broadcast(), netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8bff")) assert.Equal(t, pool.IPNet().String(), ipnet.String()) assert.True(t, pool.Exist(netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8805"))) assert.False(t, pool.Exist(netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8806"))) assert.False(t, pool.Exist(netip.MustParseAddr("127.0.0.1"))) } } func TestPool_Case_Insensitive(t *testing.T) { ipnet := netip.MustParsePrefix("192.168.0.1/29") pools, tempfile, err := createPools(Options{ IPNet: ipnet, Size: 10, }) assert.Nil(t, err) defer os.Remove(tempfile) for _, pool := range pools { first := pool.Lookup("foo.com") last := pool.Lookup("Foo.Com") foo, exist := pool.LookBack(last) assert.Equal(t, first, pool.Lookup("Foo.Com")) assert.Equal(t, pool.Lookup("fOo.cOM"), first) assert.True(t, exist) assert.Equal(t, foo, "foo.com") } } func TestPool_CycleUsed(t *testing.T) { ipnet := netip.MustParsePrefix("192.168.0.16/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("bar.com") for i := 0; i < 9; i++ { pool.Lookup(fmt.Sprintf("%d.com", i)) } baz := pool.Lookup("baz.com") next := pool.Lookup("foo.com") assert.Equal(t, foo, baz) assert.Equal(t, next, bar) } } func TestPool_Skip(t *testing.T) { ipnet := netip.MustParsePrefix("192.168.0.1/29") tree := trie.New[struct{}]() assert.NoError(t, tree.Insert("example.com", struct{}{})) assert.False(t, tree.IsEmpty()) pools, tempfile, err := createPools(Options{ IPNet: ipnet, Size: 10, Host: []C.DomainMatcher{tree.NewDomainSet()}, }) assert.Nil(t, err) defer os.Remove(tempfile) for _, pool := range pools { assert.True(t, pool.ShouldSkipped("example.com")) assert.False(t, pool.ShouldSkipped("foo.com")) assert.False(t, pool.shouldSkipped("baz.com")) } } func TestPool_SkipWhiteList(t *testing.T) { ipnet := netip.MustParsePrefix("192.168.0.1/29") tree := trie.New[struct{}]() assert.NoError(t, tree.Insert("example.com", struct{}{})) assert.False(t, tree.IsEmpty()) pools, tempfile, err := createPools(Options{ IPNet: ipnet, Size: 10, Host: []C.DomainMatcher{tree.NewDomainSet()}, Mode: C.FilterWhiteList, }) assert.Nil(t, err) defer os.Remove(tempfile) for _, pool := range pools { assert.False(t, pool.ShouldSkipped("example.com")) assert.True(t, pool.ShouldSkipped("foo.com")) assert.True(t, pool.ShouldSkipped("baz.com")) } } func TestPool_MaxCacheSize(t *testing.T) { ipnet := netip.MustParsePrefix("192.168.0.1/24") pool, _ := New(Options{ IPNet: ipnet, Size: 2, }) first := pool.Lookup("foo.com") pool.Lookup("bar.com") pool.Lookup("baz.com") next := pool.Lookup("foo.com") assert.NotEqual(t, first, next) } func TestPool_DoubleMapping(t *testing.T) { ipnet := netip.MustParsePrefix("192.168.0.1/24") pool, _ := New(Options{ IPNet: ipnet, Size: 2, }) // fill cache fooIP := pool.Lookup("foo.com") bazIP := pool.Lookup("baz.com") // make foo.com hot pool.Lookup("foo.com") // should drop baz.com barIP := pool.Lookup("bar.com") _, fooExist := pool.LookBack(fooIP) _, bazExist := pool.LookBack(bazIP) _, barExist := pool.LookBack(barIP) newBazIP := pool.Lookup("baz.com") assert.True(t, fooExist) assert.False(t, bazExist) assert.True(t, barExist) assert.NotEqual(t, bazIP, newBazIP) } func TestPool_Clone(t *testing.T) { ipnet := netip.MustParsePrefix("192.168.0.1/24") pool, _ := New(Options{ IPNet: ipnet, Size: 2, }) first := pool.Lookup("foo.com") last := pool.Lookup("bar.com") assert.Equal(t, first, netip.AddrFrom4([4]byte{192, 168, 0, 4})) assert.Equal(t, last, netip.AddrFrom4([4]byte{192, 168, 0, 5})) newPool, _ := New(Options{ IPNet: ipnet, Size: 2, }) newPool.CloneFrom(pool) _, firstExist := newPool.LookBack(first) _, lastExist := newPool.LookBack(last) assert.True(t, firstExist) assert.True(t, lastExist) } func TestPool_Error(t *testing.T) { ipnet := netip.MustParsePrefix("192.168.0.1/31") _, err := New(Options{ IPNet: ipnet, Size: 10, }) assert.Error(t, err) } func TestPool_FlushFileCache(t *testing.T) { ipnet := netip.MustParsePrefix("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) next := pool.Lookup("baz.com") baz := pool.Lookup("foo.com") nero := pool.Lookup("foo.com") assert.Equal(t, foo, fox) assert.Equal(t, foo, next) assert.NotEqual(t, foo, baz) assert.Equal(t, bar, bax) assert.Equal(t, bar, baz) assert.NotEqual(t, bar, next) assert.Equal(t, baz, nero) } } func TestPool_FlushMemoryCache(t *testing.T) { ipnet := netip.MustParsePrefix("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) next := pool.Lookup("baz.com") baz := pool.Lookup("foo.com") nero := pool.Lookup("foo.com") assert.Equal(t, foo, fox) assert.Equal(t, foo, next) assert.NotEqual(t, foo, baz) assert.Equal(t, bar, bax) assert.Equal(t, bar, baz) assert.NotEqual(t, bar, next) assert.Equal(t, baz, nero) }