git.sr.ht/~pingoo/stdx@v0.0.0-20240218134121-094174641f6e/mmdb/reader_test.go (about) 1 package mmdb 2 3 import ( 4 "errors" 5 "fmt" 6 "math/big" 7 "math/rand" 8 "net" 9 "os" 10 "path/filepath" 11 "testing" 12 "time" 13 14 "github.com/stretchr/testify/assert" 15 "github.com/stretchr/testify/require" 16 ) 17 18 func TestReader(t *testing.T) { 19 for _, recordSize := range []uint{24, 28, 32} { 20 for _, ipVersion := range []uint{4, 6} { 21 fileName := fmt.Sprintf( 22 testFile("MaxMind-DB-test-ipv%d-%d.mmdb"), 23 ipVersion, 24 recordSize, 25 ) 26 reader, err := Open(fileName) 27 require.NoError(t, err, "unexpected error while opening database: %v", err) 28 checkMetadata(t, reader, ipVersion, recordSize) 29 30 if ipVersion == 4 { 31 checkIpv4(t, reader) 32 } else { 33 checkIpv6(t, reader) 34 } 35 } 36 } 37 } 38 39 func TestReaderBytes(t *testing.T) { 40 for _, recordSize := range []uint{24, 28, 32} { 41 for _, ipVersion := range []uint{4, 6} { 42 fileName := fmt.Sprintf( 43 testFile("MaxMind-DB-test-ipv%d-%d.mmdb"), 44 ipVersion, 45 recordSize, 46 ) 47 bytes, err := os.ReadFile(fileName) 48 require.NoError(t, err) 49 reader, err := FromBytes(bytes) 50 require.NoError(t, err, "unexpected error while opening bytes: %v", err) 51 52 checkMetadata(t, reader, ipVersion, recordSize) 53 54 if ipVersion == 4 { 55 checkIpv4(t, reader) 56 } else { 57 checkIpv6(t, reader) 58 } 59 } 60 } 61 } 62 63 func TestLookupNetwork(t *testing.T) { 64 bigInt := new(big.Int) 65 bigInt.SetString("1329227995784915872903807060280344576", 10) 66 decoderRecord := map[string]any{ 67 "array": []any{ 68 uint64(1), 69 uint64(2), 70 uint64(3), 71 }, 72 "boolean": true, 73 "bytes": []uint8{ 74 0x0, 75 0x0, 76 0x0, 77 0x2a, 78 }, 79 "double": 42.123456, 80 "float": float32(1.1), 81 "int32": -268435456, 82 "map": map[string]any{ 83 "mapX": map[string]any{ 84 "arrayX": []any{ 85 uint64(0x7), 86 uint64(0x8), 87 uint64(0x9), 88 }, 89 "utf8_stringX": "hello", 90 }, 91 }, 92 "uint128": bigInt, 93 "uint16": uint64(0x64), 94 "uint32": uint64(0x10000000), 95 "uint64": uint64(0x1000000000000000), 96 "utf8_string": "unicode! ☯ - ♫", 97 } 98 99 tests := []struct { 100 IP net.IP 101 DBFile string 102 ExpectedCIDR string 103 ExpectedRecord any 104 ExpectedOK bool 105 }{ 106 { 107 IP: net.ParseIP("1.1.1.1"), 108 DBFile: "MaxMind-DB-test-ipv6-32.mmdb", 109 ExpectedCIDR: "1.0.0.0/8", 110 ExpectedRecord: nil, 111 ExpectedOK: false, 112 }, 113 { 114 IP: net.ParseIP("::1:ffff:ffff"), 115 DBFile: "MaxMind-DB-test-ipv6-24.mmdb", 116 ExpectedCIDR: "::1:ffff:ffff/128", 117 ExpectedRecord: map[string]any{"ip": "::1:ffff:ffff"}, 118 ExpectedOK: true, 119 }, 120 { 121 IP: net.ParseIP("::2:0:1"), 122 DBFile: "MaxMind-DB-test-ipv6-24.mmdb", 123 ExpectedCIDR: "::2:0:0/122", 124 ExpectedRecord: map[string]any{"ip": "::2:0:0"}, 125 ExpectedOK: true, 126 }, 127 { 128 IP: net.ParseIP("1.1.1.1"), 129 DBFile: "MaxMind-DB-test-ipv4-24.mmdb", 130 ExpectedCIDR: "1.1.1.1/32", 131 ExpectedRecord: map[string]any{"ip": "1.1.1.1"}, 132 ExpectedOK: true, 133 }, 134 { 135 IP: net.ParseIP("1.1.1.3"), 136 DBFile: "MaxMind-DB-test-ipv4-24.mmdb", 137 ExpectedCIDR: "1.1.1.2/31", 138 ExpectedRecord: map[string]any{"ip": "1.1.1.2"}, 139 ExpectedOK: true, 140 }, 141 { 142 IP: net.ParseIP("1.1.1.3"), 143 DBFile: "MaxMind-DB-test-decoder.mmdb", 144 ExpectedCIDR: "1.1.1.0/24", 145 ExpectedRecord: decoderRecord, 146 ExpectedOK: true, 147 }, 148 { 149 IP: net.ParseIP("::ffff:1.1.1.128"), 150 DBFile: "MaxMind-DB-test-decoder.mmdb", 151 ExpectedCIDR: "1.1.1.0/24", 152 ExpectedRecord: decoderRecord, 153 ExpectedOK: true, 154 }, 155 { 156 IP: net.ParseIP("::1.1.1.128"), 157 DBFile: "MaxMind-DB-test-decoder.mmdb", 158 ExpectedCIDR: "::101:100/120", 159 ExpectedRecord: decoderRecord, 160 ExpectedOK: true, 161 }, 162 { 163 IP: net.ParseIP("200.0.2.1"), 164 DBFile: "MaxMind-DB-no-ipv4-search-tree.mmdb", 165 ExpectedCIDR: "::/64", 166 ExpectedRecord: "::0/64", 167 ExpectedOK: true, 168 }, 169 { 170 IP: net.ParseIP("::200.0.2.1"), 171 DBFile: "MaxMind-DB-no-ipv4-search-tree.mmdb", 172 ExpectedCIDR: "::/64", 173 ExpectedRecord: "::0/64", 174 ExpectedOK: true, 175 }, 176 { 177 IP: net.ParseIP("0:0:0:0:ffff:ffff:ffff:ffff"), 178 DBFile: "MaxMind-DB-no-ipv4-search-tree.mmdb", 179 ExpectedCIDR: "::/64", 180 ExpectedRecord: "::0/64", 181 ExpectedOK: true, 182 }, 183 { 184 IP: net.ParseIP("ef00::"), 185 DBFile: "MaxMind-DB-no-ipv4-search-tree.mmdb", 186 ExpectedCIDR: "8000::/1", 187 ExpectedRecord: nil, 188 ExpectedOK: false, 189 }, 190 } 191 192 for _, test := range tests { 193 t.Run(fmt.Sprintf("%s - %s", test.DBFile, test.IP), func(t *testing.T) { 194 var record any 195 reader, err := Open(testFile(test.DBFile)) 196 require.NoError(t, err) 197 198 network, ok, err := reader.LookupNetwork(test.IP, &record) 199 require.NoError(t, err) 200 assert.Equal(t, test.ExpectedOK, ok) 201 assert.Equal(t, test.ExpectedCIDR, network.String()) 202 assert.Equal(t, test.ExpectedRecord, record) 203 }) 204 } 205 } 206 207 func TestDecodingToInterface(t *testing.T) { 208 reader, err := Open(testFile("MaxMind-DB-test-decoder.mmdb")) 209 require.NoError(t, err, "unexpected error while opening database: %v", err) 210 211 var recordInterface any 212 err = reader.Lookup(net.ParseIP("::1.1.1.0"), &recordInterface) 213 require.NoError(t, err, "unexpected error while doing lookup: %v", err) 214 215 checkDecodingToInterface(t, recordInterface) 216 } 217 218 func TestMetadataPointer(t *testing.T) { 219 _, err := Open(testFile("MaxMind-DB-test-metadata-pointers.mmdb")) 220 require.NoError(t, err, "unexpected error while opening database: %v", err) 221 } 222 223 func checkDecodingToInterface(t *testing.T, recordInterface any) { 224 record := recordInterface.(map[string]any) 225 assert.Equal(t, []any{uint64(1), uint64(2), uint64(3)}, record["array"]) 226 assert.Equal(t, true, record["boolean"]) 227 assert.Equal(t, []byte{0x00, 0x00, 0x00, 0x2a}, record["bytes"]) 228 assert.Equal(t, 42.123456, record["double"]) 229 assert.Equal(t, float32(1.1), record["float"]) 230 assert.Equal(t, -268435456, record["int32"]) 231 assert.Equal(t, 232 map[string]any{ 233 "mapX": map[string]any{ 234 "arrayX": []any{uint64(7), uint64(8), uint64(9)}, 235 "utf8_stringX": "hello", 236 }, 237 }, 238 record["map"], 239 ) 240 241 assert.Equal(t, uint64(100), record["uint16"]) 242 assert.Equal(t, uint64(268435456), record["uint32"]) 243 assert.Equal(t, uint64(1152921504606846976), record["uint64"]) 244 assert.Equal(t, "unicode! ☯ - ♫", record["utf8_string"]) 245 bigInt := new(big.Int) 246 bigInt.SetString("1329227995784915872903807060280344576", 10) 247 assert.Equal(t, bigInt, record["uint128"]) 248 } 249 250 type TestType struct { 251 Array []uint `maxminddb:"array"` 252 Boolean bool `maxminddb:"boolean"` 253 Bytes []byte `maxminddb:"bytes"` 254 Double float64 `maxminddb:"double"` 255 Float float32 `maxminddb:"float"` 256 Int32 int32 `maxminddb:"int32"` 257 Map map[string]any `maxminddb:"map"` 258 Uint16 uint16 `maxminddb:"uint16"` 259 Uint32 uint32 `maxminddb:"uint32"` 260 Uint64 uint64 `maxminddb:"uint64"` 261 Uint128 big.Int `maxminddb:"uint128"` 262 Utf8String string `maxminddb:"utf8_string"` 263 } 264 265 func TestDecoder(t *testing.T) { 266 reader, err := Open(testFile("MaxMind-DB-test-decoder.mmdb")) 267 require.NoError(t, err) 268 269 verify := func(result TestType) { 270 assert.Equal(t, []uint{uint(1), uint(2), uint(3)}, result.Array) 271 assert.Equal(t, true, result.Boolean) 272 assert.Equal(t, []byte{0x00, 0x00, 0x00, 0x2a}, result.Bytes) 273 assert.Equal(t, 42.123456, result.Double) 274 assert.Equal(t, float32(1.1), result.Float) 275 assert.Equal(t, int32(-268435456), result.Int32) 276 277 assert.Equal(t, 278 map[string]any{ 279 "mapX": map[string]any{ 280 "arrayX": []any{uint64(7), uint64(8), uint64(9)}, 281 "utf8_stringX": "hello", 282 }, 283 }, 284 result.Map, 285 ) 286 287 assert.Equal(t, uint16(100), result.Uint16) 288 assert.Equal(t, uint32(268435456), result.Uint32) 289 assert.Equal(t, uint64(1152921504606846976), result.Uint64) 290 assert.Equal(t, "unicode! ☯ - ♫", result.Utf8String) 291 bigInt := new(big.Int) 292 bigInt.SetString("1329227995784915872903807060280344576", 10) 293 assert.Equal(t, bigInt, &result.Uint128) 294 } 295 296 { 297 // Directly lookup and decode. 298 var result TestType 299 require.NoError(t, reader.Lookup(net.ParseIP("::1.1.1.0"), &result)) 300 verify(result) 301 } 302 { 303 // Lookup record offset, then Decode. 304 var result TestType 305 offset, err := reader.LookupOffset(net.ParseIP("::1.1.1.0")) 306 require.NoError(t, err) 307 assert.NotEqual(t, NotFound, offset) 308 309 assert.NoError(t, reader.Decode(offset, &result)) 310 verify(result) 311 } 312 313 assert.NoError(t, reader.Close()) 314 } 315 316 type TestInterface interface { 317 method() bool 318 } 319 320 func (t *TestType) method() bool { 321 return t.Boolean 322 } 323 324 func TestStructInterface(t *testing.T) { 325 var result TestInterface = &TestType{} 326 327 reader, err := Open(testFile("MaxMind-DB-test-decoder.mmdb")) 328 require.NoError(t, err) 329 330 require.NoError(t, reader.Lookup(net.ParseIP("::1.1.1.0"), &result)) 331 332 assert.Equal(t, true, result.method()) 333 } 334 335 func TestNonEmptyNilInterface(t *testing.T) { 336 var result TestInterface 337 338 reader, err := Open(testFile("MaxMind-DB-test-decoder.mmdb")) 339 require.NoError(t, err) 340 341 err = reader.Lookup(net.ParseIP("::1.1.1.0"), &result) 342 assert.Equal( 343 t, 344 "mmdb: cannot unmarshal map into type mmdb.TestInterface", 345 err.Error(), 346 ) 347 } 348 349 type CityTraits struct { 350 AutonomousSystemNumber uint `json:"autonomous_system_number,omitempty" mmdb:"autonomous_system_number"` 351 } 352 353 type City struct { 354 Traits CityTraits `maxminddb:"traits"` 355 } 356 357 func TestEmbeddedStructAsInterface(t *testing.T) { 358 var city City 359 var result any = city.Traits 360 361 db, err := Open(testFile("GeoIP2-ISP-Test.mmdb")) 362 require.NoError(t, err) 363 364 assert.NoError(t, db.Lookup(net.ParseIP("1.128.0.0"), &result)) 365 } 366 367 type BoolInterface interface { 368 true() bool 369 } 370 371 type Bool bool 372 373 func (b Bool) true() bool { 374 return bool(b) 375 } 376 377 type ValueTypeTestType struct { 378 Boolean BoolInterface `maxminddb:"boolean"` 379 } 380 381 func TestValueTypeInterface(t *testing.T) { 382 var result ValueTypeTestType 383 result.Boolean = Bool(false) 384 385 reader, err := Open(testFile("MaxMind-DB-test-decoder.mmdb")) 386 require.NoError(t, err) 387 388 // although it would be nice to support cases like this, I am not sure it 389 // is possible to do so in a general way. 390 assert.Error(t, reader.Lookup(net.ParseIP("::1.1.1.0"), &result)) 391 } 392 393 type NestedMapX struct { 394 UTF8StringX string `maxminddb:"utf8_stringX"` 395 } 396 397 type NestedPointerMapX struct { 398 ArrayX []int `maxminddb:"arrayX"` 399 } 400 401 type PointerMap struct { 402 MapX struct { 403 Ignored string 404 NestedMapX 405 *NestedPointerMapX 406 } `maxminddb:"mapX"` 407 } 408 409 type TestPointerType struct { 410 Array *[]uint `maxminddb:"array"` 411 Boolean *bool `maxminddb:"boolean"` 412 Bytes *[]byte `maxminddb:"bytes"` 413 Double *float64 `maxminddb:"double"` 414 Float *float32 `maxminddb:"float"` 415 Int32 *int32 `maxminddb:"int32"` 416 Map *PointerMap `maxminddb:"map"` 417 Uint16 *uint16 `maxminddb:"uint16"` 418 Uint32 *uint32 `maxminddb:"uint32"` 419 420 // Test for pointer to pointer 421 Uint64 **uint64 `maxminddb:"uint64"` 422 Uint128 *big.Int `maxminddb:"uint128"` 423 Utf8String *string `maxminddb:"utf8_string"` 424 } 425 426 func TestComplexStructWithNestingAndPointer(t *testing.T) { 427 reader, err := Open(testFile("MaxMind-DB-test-decoder.mmdb")) 428 assert.NoError(t, err) 429 430 var result TestPointerType 431 432 err = reader.Lookup(net.ParseIP("::1.1.1.0"), &result) 433 require.NoError(t, err) 434 435 assert.Equal(t, []uint{uint(1), uint(2), uint(3)}, *result.Array) 436 assert.Equal(t, true, *result.Boolean) 437 assert.Equal(t, []byte{0x00, 0x00, 0x00, 0x2a}, *result.Bytes) 438 assert.Equal(t, 42.123456, *result.Double) 439 assert.Equal(t, float32(1.1), *result.Float) 440 assert.Equal(t, int32(-268435456), *result.Int32) 441 442 assert.Equal(t, []int{7, 8, 9}, result.Map.MapX.ArrayX) 443 444 assert.Equal(t, "hello", result.Map.MapX.UTF8StringX) 445 446 assert.Equal(t, uint16(100), *result.Uint16) 447 assert.Equal(t, uint32(268435456), *result.Uint32) 448 assert.Equal(t, uint64(1152921504606846976), **result.Uint64) 449 assert.Equal(t, "unicode! ☯ - ♫", *result.Utf8String) 450 bigInt := new(big.Int) 451 bigInt.SetString("1329227995784915872903807060280344576", 10) 452 assert.Equal(t, bigInt, result.Uint128) 453 454 assert.NoError(t, reader.Close()) 455 } 456 457 func TestNestedOffsetDecode(t *testing.T) { 458 db, err := Open(testFile("GeoIP2-City-Test.mmdb")) 459 require.NoError(t, err) 460 461 off, err := db.LookupOffset(net.ParseIP("81.2.69.142")) 462 assert.NotEqual(t, off, NotFound) 463 require.NoError(t, err) 464 465 var root struct { 466 CountryOffset uintptr `maxminddb:"country"` 467 468 Location struct { 469 Latitude float64 `maxminddb:"latitude"` 470 // Longitude is directly nested within the parent map. 471 LongitudeOffset uintptr `maxminddb:"longitude"` 472 // TimeZone is indirected via a pointer. 473 TimeZoneOffset uintptr `maxminddb:"time_zone"` 474 } `maxminddb:"location"` 475 } 476 assert.NoError(t, db.Decode(off, &root)) 477 assert.Equal(t, 51.5142, root.Location.Latitude) 478 479 var longitude float64 480 assert.NoError(t, db.Decode(root.Location.LongitudeOffset, &longitude)) 481 assert.Equal(t, -0.0931, longitude) 482 483 var timeZone string 484 assert.NoError(t, db.Decode(root.Location.TimeZoneOffset, &timeZone)) 485 assert.Equal(t, "Europe/London", timeZone) 486 487 var country struct { 488 IsoCode string `maxminddb:"iso_code"` 489 } 490 assert.NoError(t, db.Decode(root.CountryOffset, &country)) 491 assert.Equal(t, "GB", country.IsoCode) 492 493 assert.NoError(t, db.Close()) 494 } 495 496 func TestDecodingUint16IntoInt(t *testing.T) { 497 reader, err := Open(testFile("MaxMind-DB-test-decoder.mmdb")) 498 require.NoError(t, err, "unexpected error while opening database: %v", err) 499 500 var result struct { 501 Uint16 int `maxminddb:"uint16"` 502 } 503 err = reader.Lookup(net.ParseIP("::1.1.1.0"), &result) 504 require.NoError(t, err) 505 506 assert.Equal(t, 100, result.Uint16) 507 } 508 509 func TestIpv6inIpv4(t *testing.T) { 510 reader, err := Open(testFile("MaxMind-DB-test-ipv4-24.mmdb")) 511 require.NoError(t, err, "unexpected error while opening database: %v", err) 512 513 var result TestType 514 err = reader.Lookup(net.ParseIP("2001::"), &result) 515 516 var emptyResult TestType 517 assert.Equal(t, emptyResult, result) 518 519 expected := errors.New( 520 "error looking up '2001::': you attempted to look up an IPv6 address in an IPv4-only database", 521 ) 522 assert.Equal(t, expected, err) 523 assert.NoError(t, reader.Close(), "error on close") 524 } 525 526 func TestBrokenDoubleDatabase(t *testing.T) { 527 reader, err := Open(testFile("GeoIP2-City-Test-Broken-Double-Format.mmdb")) 528 require.NoError(t, err, "unexpected error while opening database: %v", err) 529 530 var result any 531 err = reader.Lookup(net.ParseIP("2001:220::"), &result) 532 533 expected := newInvalidDatabaseError( 534 "the MaxMind DB file's data section contains bad data (float 64 size of 2)", 535 ) 536 assert.Equal(t, expected, err) 537 assert.NoError(t, reader.Close(), "error on close") 538 } 539 540 func TestInvalidNodeCountDatabase(t *testing.T) { 541 _, err := Open(testFile("GeoIP2-City-Test-Invalid-Node-Count.mmdb")) 542 543 expected := newInvalidDatabaseError("the MaxMind DB contains invalid metadata") 544 assert.Equal(t, expected, err) 545 } 546 547 func TestMissingDatabase(t *testing.T) { 548 reader, err := Open("file-does-not-exist.mmdb") 549 assert.Nil(t, reader, "received reader when doing lookups on DB that doesn't exist") 550 assert.Regexp(t, "open file-does-not-exist.mmdb.*", err) 551 } 552 553 func TestNonDatabase(t *testing.T) { 554 reader, err := Open("README.md") 555 assert.Nil(t, reader, "received reader when doing lookups on DB that doesn't exist") 556 assert.Equal(t, "error opening database: invalid MaxMind DB file", err.Error()) 557 } 558 559 func TestDecodingToNonPointer(t *testing.T) { 560 reader, err := Open(testFile("MaxMind-DB-test-decoder.mmdb")) 561 require.NoError(t, err) 562 563 var recordInterface any 564 err = reader.Lookup(net.ParseIP("::1.1.1.0"), recordInterface) 565 assert.Equal(t, "result param must be a pointer", err.Error()) 566 assert.NoError(t, reader.Close(), "error on close") 567 } 568 569 func TestNilLookup(t *testing.T) { 570 reader, err := Open(testFile("MaxMind-DB-test-decoder.mmdb")) 571 require.NoError(t, err) 572 573 var recordInterface any 574 err = reader.Lookup(nil, recordInterface) 575 assert.Equal(t, "IP passed to Lookup cannot be nil", err.Error()) 576 assert.NoError(t, reader.Close(), "error on close") 577 } 578 579 func TestUsingClosedDatabase(t *testing.T) { 580 reader, err := Open(testFile("MaxMind-DB-test-decoder.mmdb")) 581 require.NoError(t, err) 582 require.NoError(t, reader.Close()) 583 584 var recordInterface any 585 586 err = reader.Lookup(nil, recordInterface) 587 assert.Equal(t, "cannot call Lookup on a closed database", err.Error()) 588 589 _, err = reader.LookupOffset(nil) 590 assert.Equal(t, "cannot call LookupOffset on a closed database", err.Error()) 591 592 err = reader.Decode(0, recordInterface) 593 assert.Equal(t, "cannot call Decode on a closed database", err.Error()) 594 } 595 596 func checkMetadata(t *testing.T, reader *Reader, ipVersion, recordSize uint) { 597 metadata := reader.Metadata 598 599 assert.Equal(t, uint(2), metadata.BinaryFormatMajorVersion) 600 601 assert.Equal(t, uint(0), metadata.BinaryFormatMinorVersion) 602 assert.IsType(t, uint(0), metadata.BuildEpoch) 603 assert.Equal(t, "Test", metadata.DatabaseType) 604 605 assert.Equal(t, metadata.Description, 606 map[string]string{ 607 "en": "Test Database", 608 "zh": "Test Database Chinese", 609 }) 610 assert.Equal(t, ipVersion, metadata.IPVersion) 611 assert.Equal(t, []string{"en", "zh"}, metadata.Languages) 612 613 if ipVersion == 4 { 614 assert.Equal(t, uint(164), metadata.NodeCount) 615 } else { 616 assert.Equal(t, uint(416), metadata.NodeCount) 617 } 618 619 assert.Equal(t, recordSize, metadata.RecordSize) 620 } 621 622 func checkIpv4(t *testing.T, reader *Reader) { 623 for i := uint(0); i < 6; i++ { 624 address := fmt.Sprintf("1.1.1.%d", uint(1)<<i) 625 ip := net.ParseIP(address) 626 627 var result map[string]string 628 err := reader.Lookup(ip, &result) 629 assert.NoError(t, err, "unexpected error while doing lookup: %v", err) 630 assert.Equal(t, map[string]string{"ip": address}, result) 631 } 632 pairs := map[string]string{ 633 "1.1.1.3": "1.1.1.2", 634 "1.1.1.5": "1.1.1.4", 635 "1.1.1.7": "1.1.1.4", 636 "1.1.1.9": "1.1.1.8", 637 "1.1.1.15": "1.1.1.8", 638 "1.1.1.17": "1.1.1.16", 639 "1.1.1.31": "1.1.1.16", 640 } 641 642 for keyAddress, valueAddress := range pairs { 643 data := map[string]string{"ip": valueAddress} 644 645 ip := net.ParseIP(keyAddress) 646 647 var result map[string]string 648 err := reader.Lookup(ip, &result) 649 assert.NoError(t, err, "unexpected error while doing lookup: %v", err) 650 assert.Equal(t, data, result) 651 } 652 653 for _, address := range []string{"1.1.1.33", "255.254.253.123"} { 654 ip := net.ParseIP(address) 655 656 var result map[string]string 657 err := reader.Lookup(ip, &result) 658 assert.NoError(t, err, "unexpected error while doing lookup: %v", err) 659 assert.Nil(t, result) 660 } 661 } 662 663 func checkIpv6(t *testing.T, reader *Reader) { 664 subnets := []string{ 665 "::1:ffff:ffff", "::2:0:0", 666 "::2:0:40", "::2:0:50", "::2:0:58", 667 } 668 669 for _, address := range subnets { 670 var result map[string]string 671 err := reader.Lookup(net.ParseIP(address), &result) 672 assert.NoError(t, err, "unexpected error while doing lookup: %v", err) 673 assert.Equal(t, map[string]string{"ip": address}, result) 674 } 675 676 pairs := map[string]string{ 677 "::2:0:1": "::2:0:0", 678 "::2:0:33": "::2:0:0", 679 "::2:0:39": "::2:0:0", 680 "::2:0:41": "::2:0:40", 681 "::2:0:49": "::2:0:40", 682 "::2:0:52": "::2:0:50", 683 "::2:0:57": "::2:0:50", 684 "::2:0:59": "::2:0:58", 685 } 686 687 for keyAddress, valueAddress := range pairs { 688 data := map[string]string{"ip": valueAddress} 689 var result map[string]string 690 err := reader.Lookup(net.ParseIP(keyAddress), &result) 691 assert.NoError(t, err, "unexpected error while doing lookup: %v", err) 692 assert.Equal(t, data, result) 693 } 694 695 for _, address := range []string{"1.1.1.33", "255.254.253.123", "89fa::"} { 696 var result map[string]string 697 err := reader.Lookup(net.ParseIP(address), &result) 698 assert.NoError(t, err, "unexpected error while doing lookup: %v", err) 699 assert.Nil(t, result) 700 } 701 } 702 703 func BenchmarkOpen(b *testing.B) { 704 var db *Reader 705 var err error 706 for i := 0; i < b.N; i++ { 707 db, err = Open("GeoLite2-City.mmdb") 708 if err != nil { 709 b.Error(err) 710 } 711 } 712 assert.NotNil(b, db) 713 assert.NoError(b, db.Close(), "error on close") 714 } 715 716 func BenchmarkInterfaceLookup(b *testing.B) { 717 db, err := Open("GeoLite2-City.mmdb") 718 require.NoError(b, err) 719 720 //nolint:gosec // this is a test 721 r := rand.New(rand.NewSource(time.Now().UnixNano())) 722 var result any 723 724 ip := make(net.IP, 4) 725 for i := 0; i < b.N; i++ { 726 randomIPv4Address(r, ip) 727 err = db.Lookup(ip, &result) 728 if err != nil { 729 b.Error(err) 730 } 731 } 732 assert.NoError(b, db.Close(), "error on close") 733 } 734 735 func BenchmarkInterfaceLookupNetwork(b *testing.B) { 736 db, err := Open("GeoLite2-City.mmdb") 737 require.NoError(b, err) 738 739 //nolint:gosec // this is a test 740 r := rand.New(rand.NewSource(time.Now().UnixNano())) 741 var result any 742 743 ip := make(net.IP, 4) 744 for i := 0; i < b.N; i++ { 745 randomIPv4Address(r, ip) 746 _, _, err = db.LookupNetwork(ip, &result) 747 if err != nil { 748 b.Error(err) 749 } 750 } 751 assert.NoError(b, db.Close(), "error on close") 752 } 753 754 type fullCity struct { 755 City struct { 756 GeoNameID uint `maxminddb:"geoname_id"` 757 Names map[string]string `maxminddb:"names"` 758 } `maxminddb:"city"` 759 Continent struct { 760 Code string `maxminddb:"code"` 761 GeoNameID uint `maxminddb:"geoname_id"` 762 Names map[string]string `maxminddb:"names"` 763 } `maxminddb:"continent"` 764 Country struct { 765 GeoNameID uint `maxminddb:"geoname_id"` 766 IsInEuropeanUnion bool `maxminddb:"is_in_european_union"` 767 IsoCode string `maxminddb:"iso_code"` 768 Names map[string]string `maxminddb:"names"` 769 } `maxminddb:"country"` 770 Location struct { 771 AccuracyRadius uint16 `maxminddb:"accuracy_radius"` 772 Latitude float64 `maxminddb:"latitude"` 773 Longitude float64 `maxminddb:"longitude"` 774 MetroCode uint `maxminddb:"metro_code"` 775 TimeZone string `maxminddb:"time_zone"` 776 } `maxminddb:"location"` 777 Postal struct { 778 Code string `maxminddb:"code"` 779 } `maxminddb:"postal"` 780 RegisteredCountry struct { 781 GeoNameID uint `maxminddb:"geoname_id"` 782 IsInEuropeanUnion bool `maxminddb:"is_in_european_union"` 783 IsoCode string `maxminddb:"iso_code"` 784 Names map[string]string `maxminddb:"names"` 785 } `maxminddb:"registered_country"` 786 RepresentedCountry struct { 787 GeoNameID uint `maxminddb:"geoname_id"` 788 IsInEuropeanUnion bool `maxminddb:"is_in_european_union"` 789 IsoCode string `maxminddb:"iso_code"` 790 Names map[string]string `maxminddb:"names"` 791 Type string `maxminddb:"type"` 792 } `maxminddb:"represented_country"` 793 Subdivisions []struct { 794 GeoNameID uint `maxminddb:"geoname_id"` 795 IsoCode string `maxminddb:"iso_code"` 796 Names map[string]string `maxminddb:"names"` 797 } `maxminddb:"subdivisions"` 798 Traits struct { 799 IsAnonymousProxy bool `maxminddb:"is_anonymous_proxy"` 800 IsSatelliteProvider bool `maxminddb:"is_satellite_provider"` 801 } `maxminddb:"traits"` 802 } 803 804 func BenchmarkCityLookup(b *testing.B) { 805 db, err := Open("GeoLite2-City.mmdb") 806 require.NoError(b, err) 807 808 //nolint:gosec // this is a test 809 r := rand.New(rand.NewSource(time.Now().UnixNano())) 810 var result fullCity 811 812 ip := make(net.IP, 4) 813 for i := 0; i < b.N; i++ { 814 randomIPv4Address(r, ip) 815 err = db.Lookup(ip, &result) 816 if err != nil { 817 b.Error(err) 818 } 819 } 820 assert.NoError(b, db.Close(), "error on close") 821 } 822 823 func BenchmarkCityLookupNetwork(b *testing.B) { 824 db, err := Open("GeoLite2-City.mmdb") 825 require.NoError(b, err) 826 827 //nolint:gosec // this is a test 828 r := rand.New(rand.NewSource(time.Now().UnixNano())) 829 var result fullCity 830 831 ip := make(net.IP, 4) 832 for i := 0; i < b.N; i++ { 833 randomIPv4Address(r, ip) 834 _, _, err = db.LookupNetwork(ip, &result) 835 if err != nil { 836 b.Error(err) 837 } 838 } 839 assert.NoError(b, db.Close(), "error on close") 840 } 841 842 func BenchmarkCountryCode(b *testing.B) { 843 db, err := Open("GeoLite2-City.mmdb") 844 require.NoError(b, err) 845 846 type MinCountry struct { 847 Country struct { 848 IsoCode string `maxminddb:"iso_code"` 849 } `maxminddb:"country"` 850 } 851 852 //nolint:gosec // this is a test 853 r := rand.New(rand.NewSource(0)) 854 var result MinCountry 855 856 ip := make(net.IP, 4) 857 for i := 0; i < b.N; i++ { 858 randomIPv4Address(r, ip) 859 err = db.Lookup(ip, &result) 860 if err != nil { 861 b.Error(err) 862 } 863 } 864 assert.NoError(b, db.Close(), "error on close") 865 } 866 867 func randomIPv4Address(r *rand.Rand, ip []byte) { 868 num := r.Uint32() 869 ip[0] = byte(num >> 24) 870 ip[1] = byte(num >> 16) 871 ip[2] = byte(num >> 8) 872 ip[3] = byte(num) 873 } 874 875 func testFile(file string) string { 876 return filepath.Join("test-data", "test-data", file) 877 }