Skip to content

Commit eb40436

Browse files
june-ipinfomax-ipinfo
authored andcommitted
read: fix IPv4 lookup on IPv6 databases (#49)
1 parent 6c5b721 commit eb40436

2 files changed

Lines changed: 91 additions & 0 deletions

File tree

lib/cmd_read.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ func CmdRead(f CmdReadFlags, args []string, printHelp func()) error {
113113
}
114114
continue
115115
}
116+
addr = addr.Unmap()
116117
if err := db.Lookup(addr).Decode(&record); err != nil || len(record) == 0 {
117118
if !requiresHdr {
118119
fmt.Fprintf(os.Stderr,

lib/cmd_read_test.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
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

Comments
 (0)