Updated the lookup system to allow for CIDR queries in addition to IP and domain searches. This includes modifications to the API, frontend components, and documentation to reflect the new capabilities. The LookupSearchForm and LookupComponent were adjusted to accommodate CIDR input, and relevant tests were added to ensure functionality. Co-authored-by: Cursor <[email protected]>
317 lines
8.0 KiB
Go
317 lines
8.0 KiB
Go
package lookup
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net/netip"
|
|
"testing"
|
|
|
|
"evobgp/internal/store"
|
|
)
|
|
|
|
func TestLookupIPEntryAndSnapshot(t *testing.T) {
|
|
m := store.NewMemory()
|
|
m.SeedDemo()
|
|
tenant, _, modIP, _, _ := m.DemoIDs()
|
|
|
|
cid := ""
|
|
comms, err := m.ListCommunities(tenant)
|
|
if err != nil || len(comms) == 0 {
|
|
t.Fatal("expected demo community")
|
|
}
|
|
cid = comms[0].ID
|
|
|
|
def := cid
|
|
if _, err := m.UpdateModule(tenant, modIP, &store.ModulePatch{DefaultCommunityID: &def}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
entryComm := cid
|
|
e, err := m.CreateIPRangeEntry(tenant, modIP, &store.IPRangeEntry{
|
|
Prefix: "203.0.113.0/24",
|
|
CommunityID: &entryComm,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err := m.SetModulePrefixSnapshot(tenant, modIP, "hash1", []store.PrefixRow{
|
|
{Prefix: "203.0.113.0/24", CommunityID: &cid, Source: "ip_range"},
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
res, err := Lookup(context.Background(), m, tenant, "203.0.113.10")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if res.QueryKind != KindIP || res.Normalized != "203.0.113.10" {
|
|
t.Fatalf("kind/normalized: %+v", res)
|
|
}
|
|
if !res.Matched || res.MatchCount < 2 {
|
|
t.Fatalf("expected entry+snapshot matches, got %+v", res)
|
|
}
|
|
|
|
var entryHit, snapHit bool
|
|
for _, hit := range res.Matches {
|
|
if hit.Layer == LayerEntry && hit.EntryID == e.ID {
|
|
entryHit = true
|
|
if hit.Community != "demo-comm" || hit.CommunityTitle != "Demo" {
|
|
t.Fatalf("entry community: %+v", hit)
|
|
}
|
|
}
|
|
if hit.Layer == LayerSnapshot && hit.MatchedValue == "203.0.113.0/24" {
|
|
snapHit = true
|
|
}
|
|
}
|
|
if !entryHit || !snapHit {
|
|
t.Fatalf("missing layers entry=%v snap=%v matches=%+v", entryHit, snapHit, res.Matches)
|
|
}
|
|
}
|
|
|
|
func TestLookupIPCommunityFallback(t *testing.T) {
|
|
m := store.NewMemory()
|
|
m.SeedDemo()
|
|
tenant, _, modIP, _, _ := m.DemoIDs()
|
|
comms, _ := m.ListCommunities(tenant)
|
|
cid := comms[0].ID
|
|
if _, err := m.UpdateModule(tenant, modIP, &store.ModulePatch{DefaultCommunityID: &cid}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := m.CreateIPRangeEntry(tenant, modIP, &store.IPRangeEntry{Prefix: "10.0.0.0/8"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
res, err := Lookup(context.Background(), m, tenant, "10.1.2.3")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !res.Matched {
|
|
t.Fatal("expected match")
|
|
}
|
|
found := false
|
|
for _, hit := range res.Matches {
|
|
if hit.Layer == LayerEntry {
|
|
found = true
|
|
if hit.CommunityID == nil || *hit.CommunityID != cid {
|
|
t.Fatalf("expected default community, got %+v", hit)
|
|
}
|
|
if hit.Community != "demo-comm" {
|
|
t.Fatalf("community value: %+v", hit)
|
|
}
|
|
}
|
|
}
|
|
if !found {
|
|
t.Fatal("no entry match")
|
|
}
|
|
}
|
|
|
|
func TestLookupDomainEntryAndSnapshot(t *testing.T) {
|
|
m := store.NewMemory()
|
|
m.SeedDemo()
|
|
tenant, _, _, _, _ := m.DemoIDs()
|
|
|
|
mod, err := m.CreateModule(tenant, &store.Module{
|
|
Type: "DOMAINS",
|
|
Name: "demo-domains",
|
|
Enabled: true,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
comms, _ := m.ListCommunities(tenant)
|
|
cid := comms[0].ID
|
|
|
|
e, err := m.CreateDomainEntry(tenant, mod.ID, &store.DomainEntry{
|
|
FQDN: "Example.COM.",
|
|
CommunityID: &cid,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := m.SetModulePrefixSnapshot(tenant, mod.ID, "hash-d", []store.PrefixRow{
|
|
{Prefix: "198.51.100.1/32", CommunityID: &cid, Source: "domain"},
|
|
{Prefix: "203.0.113.9/32", CommunityID: &cid, Source: "other"},
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
noDNS := func(context.Context, string) ([]netip.Addr, error) { return nil, nil }
|
|
res, err := LookupWithResolver(context.Background(), m, tenant, "example.com", noDNS)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if res.QueryKind != KindDomain || res.Normalized != "example.com" {
|
|
t.Fatalf("kind/normalized: %+v", res)
|
|
}
|
|
if !res.Matched {
|
|
t.Fatal("expected match")
|
|
}
|
|
|
|
var entryHit, snapHit, otherSnap bool
|
|
for _, hit := range res.Matches {
|
|
if hit.Layer == LayerEntry && hit.EntryID == e.ID {
|
|
entryHit = true
|
|
}
|
|
if hit.Layer == LayerSnapshot && hit.MatchedValue == "198.51.100.1/32" {
|
|
snapHit = true
|
|
}
|
|
if hit.MatchedValue == "203.0.113.9/32" {
|
|
otherSnap = true
|
|
}
|
|
}
|
|
if !entryHit || !snapHit {
|
|
t.Fatalf("entry=%v snap=%v matches=%+v", entryHit, snapHit, res.Matches)
|
|
}
|
|
if otherSnap {
|
|
t.Fatal("non-domain snapshot source should be excluded")
|
|
}
|
|
}
|
|
|
|
func TestLookupDomainResolvedIPAgainstRanges(t *testing.T) {
|
|
m := store.NewMemory()
|
|
m.SeedDemo()
|
|
tenant, _, modIP, _, _ := m.DemoIDs()
|
|
comms, _ := m.ListCommunities(tenant)
|
|
cid := comms[0].ID
|
|
if _, err := m.UpdateModule(tenant, modIP, &store.ModulePatch{DefaultCommunityID: &cid}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := m.CreateIPRangeEntry(tenant, modIP, &store.IPRangeEntry{
|
|
Prefix: "203.0.113.0/24",
|
|
CommunityID: &cid,
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := m.SetModulePrefixSnapshot(tenant, modIP, "hash-r", []store.PrefixRow{
|
|
{Prefix: "203.0.113.0/24", CommunityID: &cid, Source: "ip_range"},
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
fake := func(_ context.Context, host string) ([]netip.Addr, error) {
|
|
if host != "google.com" {
|
|
t.Fatalf("unexpected host %q", host)
|
|
}
|
|
return []netip.Addr{netip.MustParseAddr("203.0.113.50")}, nil
|
|
}
|
|
|
|
res, err := LookupWithResolver(context.Background(), m, tenant, "google.com", fake)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if res.QueryKind != KindDomain {
|
|
t.Fatalf("kind: %+v", res)
|
|
}
|
|
if len(res.ResolvedIPs) != 1 || res.ResolvedIPs[0] != "203.0.113.50" {
|
|
t.Fatalf("resolved_ips: %+v", res.ResolvedIPs)
|
|
}
|
|
if !res.Matched {
|
|
t.Fatalf("expected IP membership via resolve, got %+v", res)
|
|
}
|
|
var viaResolve bool
|
|
for _, hit := range res.Matches {
|
|
if hit.ResolvedIP == "203.0.113.50" && hit.MatchedValue == "203.0.113.0/24" {
|
|
viaResolve = true
|
|
}
|
|
}
|
|
if !viaResolve {
|
|
t.Fatalf("missing resolved-ip match: %+v", res.Matches)
|
|
}
|
|
}
|
|
|
|
func TestLookupCIDREntryAndSnapshot(t *testing.T) {
|
|
m := store.NewMemory()
|
|
m.SeedDemo()
|
|
tenant, _, modIP, _, _ := m.DemoIDs()
|
|
comms, _ := m.ListCommunities(tenant)
|
|
cid := comms[0].ID
|
|
|
|
e, err := m.CreateIPRangeEntry(tenant, modIP, &store.IPRangeEntry{
|
|
Prefix: "203.0.113.0/24",
|
|
CommunityID: &cid,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := m.SetModulePrefixSnapshot(tenant, modIP, "hash-cidr", []store.PrefixRow{
|
|
{Prefix: "203.0.113.0/24", CommunityID: &cid, Source: "ip_range"},
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
res, err := Lookup(context.Background(), m, tenant, "203.0.113.0/24")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if res.QueryKind != KindCIDR || res.Normalized != "203.0.113.0/24" {
|
|
t.Fatalf("kind/normalized: %+v", res)
|
|
}
|
|
if !res.Matched || res.MatchCount < 2 {
|
|
t.Fatalf("expected entry+snapshot, got %+v", res)
|
|
}
|
|
|
|
var entryHit, snapHit bool
|
|
for _, hit := range res.Matches {
|
|
if hit.Layer == LayerEntry && hit.EntryID == e.ID {
|
|
entryHit = true
|
|
}
|
|
if hit.Layer == LayerSnapshot && hit.MatchedValue == "203.0.113.0/24" {
|
|
snapHit = true
|
|
}
|
|
}
|
|
if !entryHit || !snapHit {
|
|
t.Fatalf("entry=%v snap=%v matches=%+v", entryHit, snapHit, res.Matches)
|
|
}
|
|
|
|
// Narrower query covered by wider entry.
|
|
res2, err := Lookup(context.Background(), m, tenant, "203.0.113.128/25")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if res2.QueryKind != KindCIDR || !res2.Matched {
|
|
t.Fatalf("expected cover match: %+v", res2)
|
|
}
|
|
}
|
|
|
|
func TestLookupNoMatch(t *testing.T) {
|
|
m := store.NewMemory()
|
|
m.SeedDemo()
|
|
tenant, _, _, _, _ := m.DemoIDs()
|
|
|
|
res, err := Lookup(context.Background(), m, tenant, "192.0.2.1")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if res.Matched || res.MatchCount != 0 || len(res.Matches) != 0 {
|
|
t.Fatalf("expected empty: %+v", res)
|
|
}
|
|
}
|
|
|
|
func TestLookupInvalid(t *testing.T) {
|
|
m := store.NewMemory()
|
|
m.SeedDemo()
|
|
tenant, _, _, _, _ := m.DemoIDs()
|
|
ctx := context.Background()
|
|
|
|
_, err := Lookup(ctx, m, tenant, "")
|
|
if !errors.Is(err, store.ErrInvalidInput) {
|
|
t.Fatalf("empty: %v", err)
|
|
}
|
|
_, err = Lookup(ctx, m, tenant, "not a host")
|
|
if !errors.Is(err, store.ErrInvalidInput) {
|
|
t.Fatalf("spaces: %v", err)
|
|
}
|
|
_, err = Lookup(ctx, m, tenant, "localhost")
|
|
if !errors.Is(err, store.ErrInvalidInput) {
|
|
t.Fatalf("single label: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestNormalizeFQDN(t *testing.T) {
|
|
got, ok := normalizeFQDN(" Example.COM. ")
|
|
if !ok || got != "example.com" {
|
|
t.Fatalf("got %q ok=%v", got, ok)
|
|
}
|
|
}
|