|
| 1 | +package lib |
| 2 | + |
| 3 | +import ( |
| 4 | + "net" |
| 5 | + "net/netip" |
| 6 | + "os" |
| 7 | + "path/filepath" |
| 8 | + "testing" |
| 9 | + |
| 10 | + "github.com/oschwald/maxminddb-golang/v2" |
| 11 | +) |
| 12 | + |
| 13 | +// TestReadLookupOnIPv6Database verifies that both IPv4 and IPv6 lookups work |
| 14 | +// on IPv6 databases. The IPv4 cases are a regression test: netip.AddrFromSlice |
| 15 | +// on a 16-byte net.IP (as returned by net.ParseIP for IPv4) produces an |
| 16 | +// IPv4-mapped-IPv6 address (::ffff:x.x.x.x) which maxminddb-golang/v2 does |
| 17 | +// not resolve through the IPv4 subtree. The fix is to call .Unmap() before |
| 18 | +// lookup. |
| 19 | +func TestReadLookupOnIPv6Database(t *testing.T) { |
| 20 | + // Create a small IPv6 MMDB with IPv4 data via import. |
| 21 | + tempDir := t.TempDir() |
| 22 | + inputFile := filepath.Join(tempDir, "input.csv") |
| 23 | + mmdbFile := filepath.Join(tempDir, "test.mmdb") |
| 24 | + |
| 25 | + csvData := "network,descr\n127.0.0.0/8,Loopback\n192.168.0.0/16,Private-Use\n2001:db8::/32,Documentation\nfc00::/7,Unique-Local\n" |
| 26 | + if err := os.WriteFile(inputFile, []byte(csvData), 0644); err != nil { |
| 27 | + t.Fatal(err) |
| 28 | + } |
| 29 | + |
| 30 | + err := CmdImport(CmdImportFlags{ |
| 31 | + Ip: 6, |
| 32 | + Size: 32, |
| 33 | + Merge: "none", |
| 34 | + In: inputFile, |
| 35 | + Out: mmdbFile, |
| 36 | + Csv: true, |
| 37 | + }, []string{}, func() {}) |
| 38 | + if err != nil { |
| 39 | + t.Fatalf("import failed: %s", err) |
| 40 | + } |
| 41 | + |
| 42 | + db, err := maxminddb.Open(mmdbFile) |
| 43 | + if err != nil { |
| 44 | + t.Fatalf("failed to open MMDB: %s", err) |
| 45 | + } |
| 46 | + defer db.Close() |
| 47 | + |
| 48 | + if db.Metadata.IPVersion != 6 { |
| 49 | + t.Fatalf("expected IPv6 database, got version %d", db.Metadata.IPVersion) |
| 50 | + } |
| 51 | + |
| 52 | + tests := []struct { |
| 53 | + ip string |
| 54 | + expected string |
| 55 | + }{ |
| 56 | + {"127.0.0.1", "Loopback"}, |
| 57 | + {"192.168.1.1", "Private-Use"}, |
| 58 | + {"2001:db8::1", "Documentation"}, |
| 59 | + {"fd00::1", "Unique-Local"}, |
| 60 | + } |
| 61 | + |
| 62 | + for _, tc := range tests { |
| 63 | + t.Run(tc.ip, func(t *testing.T) { |
| 64 | + // net.ParseIP returns a 16-byte slice for IPv4, same as |
| 65 | + // iputil.IPListFromAllSrcs. This is the code path that |
| 66 | + // CmdRead uses. |
| 67 | + ip := net.ParseIP(tc.ip) |
| 68 | + if len(ip) != 16 { |
| 69 | + t.Fatalf("expected 16-byte IP, got %d", len(ip)) |
| 70 | + } |
| 71 | + |
| 72 | + addr, ok := netip.AddrFromSlice(ip) |
| 73 | + if !ok { |
| 74 | + t.Fatalf("AddrFromSlice failed for %s", tc.ip) |
| 75 | + } |
| 76 | + addr = addr.Unmap() |
| 77 | + |
| 78 | + var record map[string]interface{} |
| 79 | + if err := db.Lookup(addr).Decode(&record); err != nil { |
| 80 | + t.Fatalf("lookup failed: %s", err) |
| 81 | + } |
| 82 | + if len(record) == 0 { |
| 83 | + t.Fatalf("empty record for %s", tc.ip) |
| 84 | + } |
| 85 | + if got := record["descr"]; got != tc.expected { |
| 86 | + t.Errorf("got descr=%v, want %s", got, tc.expected) |
| 87 | + } |
| 88 | + }) |
| 89 | + } |
| 90 | +} |
0 commit comments