maxmind-geoip2 1.1.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -0
  3. data/Gemfile.lock +114 -0
  4. data/README.dev.md +1 -1
  5. data/README.md +26 -6
  6. data/Rakefile +1 -0
  7. data/lib/maxmind/geoip2/client.rb +18 -9
  8. data/lib/maxmind/geoip2/model/anonymous_plus.rb +46 -0
  9. data/lib/maxmind/geoip2/model/city.rb +5 -6
  10. data/lib/maxmind/geoip2/model/connection_type.rb +3 -2
  11. data/lib/maxmind/geoip2/model/enterprise.rb +2 -3
  12. data/lib/maxmind/geoip2/model/insights.rb +3 -5
  13. data/lib/maxmind/geoip2/reader.rb +26 -1
  14. data/lib/maxmind/geoip2/record/location.rb +3 -3
  15. data/lib/maxmind/geoip2/record/traits.rb +40 -28
  16. data/lib/maxmind/geoip2/version.rb +8 -0
  17. data/maxmind-geoip2.gemspec +11 -6
  18. data/test/data/LICENSE-APACHE +202 -0
  19. data/test/data/LICENSE-MIT +17 -0
  20. data/test/data/MaxMind-DB-spec.md +1 -2
  21. data/test/data/README.md +8 -1
  22. data/test/data/cmd/write-test-data/main.go +68 -0
  23. data/test/data/go.mod +13 -0
  24. data/test/data/go.sum +16 -0
  25. data/test/data/pkg/writer/decoder.go +178 -0
  26. data/test/data/pkg/writer/geoip2.go +184 -0
  27. data/test/data/pkg/writer/ip.go +39 -0
  28. data/test/data/pkg/writer/maxmind.go +246 -0
  29. data/test/data/pkg/writer/nestedstructures.go +73 -0
  30. data/test/data/pkg/writer/writer.go +61 -0
  31. data/test/data/source-data/GeoIP-Anonymous-Plus-Test.json +175 -0
  32. data/test/data/source-data/GeoIP2-Anonymous-IP-Test.json +6 -0
  33. data/test/data/source-data/GeoIP2-City-Test.json +392 -5
  34. data/test/data/source-data/GeoIP2-Connection-Type-Test.json +15 -10
  35. data/test/data/source-data/GeoIP2-Country-Test.json +99 -25
  36. data/test/data/source-data/GeoIP2-Domain-Test.json +5 -0
  37. data/test/data/source-data/GeoIP2-Enterprise-Test.json +371 -6
  38. data/test/data/source-data/GeoIP2-IP-Risk-Test.json +31 -0
  39. data/test/data/source-data/GeoIP2-Precision-Enterprise-Sandbox-Test.json +296 -0
  40. data/test/data/source-data/GeoIP2-Precision-Enterprise-Test.json +1159 -175
  41. data/test/data/source-data/GeoIP2-Static-IP-Score-Test.json +15 -0
  42. data/test/data/source-data/GeoIP2-User-Count-Test.json +18 -0
  43. data/test/data/source-data/GeoLite2-City-Test.json +168 -3
  44. data/test/data/source-data/GeoLite2-Country-Test.json +92 -3
  45. data/test/data/test-data/GeoIP-Anonymous-Plus-Test.mmdb +0 -0
  46. data/test/data/test-data/GeoIP2-Anonymous-IP-Test.mmdb +0 -0
  47. data/test/data/test-data/GeoIP2-City-Test-Broken-Double-Format.mmdb +0 -0
  48. data/test/data/test-data/GeoIP2-City-Test-Invalid-Node-Count.mmdb +0 -0
  49. data/test/data/test-data/GeoIP2-City-Test.mmdb +0 -0
  50. data/test/data/test-data/GeoIP2-Connection-Type-Test.mmdb +0 -0
  51. data/test/data/test-data/GeoIP2-Country-Test.mmdb +0 -0
  52. data/test/data/test-data/GeoIP2-DensityIncome-Test.mmdb +0 -0
  53. data/test/data/test-data/GeoIP2-Domain-Test.mmdb +0 -0
  54. data/test/data/test-data/GeoIP2-Enterprise-Test.mmdb +0 -0
  55. data/test/data/test-data/GeoIP2-IP-Risk-Test.mmdb +0 -0
  56. data/test/data/test-data/GeoIP2-ISP-Test.mmdb +0 -0
  57. data/test/data/test-data/GeoIP2-Precision-Enterprise-Test.mmdb +0 -0
  58. data/test/data/test-data/GeoIP2-Static-IP-Score-Test.mmdb +0 -0
  59. data/test/data/test-data/GeoIP2-User-Count-Test.mmdb +0 -0
  60. data/test/data/test-data/GeoLite2-ASN-Test.mmdb +0 -0
  61. data/test/data/test-data/GeoLite2-City-Test.mmdb +0 -0
  62. data/test/data/test-data/GeoLite2-Country-Test.mmdb +0 -0
  63. data/test/data/test-data/MaxMind-DB-no-ipv4-search-tree.mmdb +0 -0
  64. data/test/data/test-data/MaxMind-DB-string-value-entries.mmdb +0 -0
  65. data/test/data/test-data/MaxMind-DB-test-broken-pointers-24.mmdb +0 -0
  66. data/test/data/test-data/MaxMind-DB-test-broken-search-tree-24.mmdb +0 -0
  67. data/test/data/test-data/MaxMind-DB-test-decoder.mmdb +0 -0
  68. data/test/data/test-data/MaxMind-DB-test-ipv4-24.mmdb +0 -0
  69. data/test/data/test-data/MaxMind-DB-test-ipv4-28.mmdb +0 -0
  70. data/test/data/test-data/MaxMind-DB-test-ipv4-32.mmdb +0 -0
  71. data/test/data/test-data/MaxMind-DB-test-ipv6-24.mmdb +0 -0
  72. data/test/data/test-data/MaxMind-DB-test-ipv6-28.mmdb +0 -0
  73. data/test/data/test-data/MaxMind-DB-test-ipv6-32.mmdb +0 -0
  74. data/test/data/test-data/MaxMind-DB-test-metadata-pointers.mmdb +0 -0
  75. data/test/data/test-data/MaxMind-DB-test-mixed-24.mmdb +0 -0
  76. data/test/data/test-data/MaxMind-DB-test-mixed-28.mmdb +0 -0
  77. data/test/data/test-data/MaxMind-DB-test-mixed-32.mmdb +0 -0
  78. data/test/data/test-data/MaxMind-DB-test-nested.mmdb +0 -0
  79. data/test/data/test-data/MaxMind-DB-test-pointer-decoder.mmdb +0 -0
  80. data/test/data/test-data/README.md +28 -12
  81. data/test/test_client.rb +18 -2
  82. data/test/test_reader.rb +42 -1
  83. metadata +25 -13
  84. data/test/data/LICENSE +0 -4
  85. data/test/data/perltidyrc +0 -12
  86. data/test/data/source-data/README +0 -15
  87. data/test/data/test-data/write-test-data.pl +0 -695
@@ -0,0 +1,178 @@
1
+ package writer
2
+
3
+ import (
4
+ "bytes"
5
+ "encoding/binary"
6
+ "fmt"
7
+ "math"
8
+ "math/big"
9
+ "net/netip"
10
+
11
+ "github.com/maxmind/mmdbwriter"
12
+ "github.com/maxmind/mmdbwriter/mmdbtype"
13
+ "go4.org/netipx"
14
+ )
15
+
16
+ // WriteDecoderTestDB writes an mmdb file with all possible record value types.
17
+ func (w *Writer) WriteDecoderTestDB() error {
18
+ dbWriter, err := mmdbwriter.New(
19
+ mmdbwriter.Options{
20
+ DatabaseType: "MaxMind DB Decoder Test",
21
+ Description: map[string]string{
22
+ "en": "MaxMind DB Decoder Test database - contains every MaxMind DB data type",
23
+ },
24
+ DisableIPv4Aliasing: false,
25
+ IncludeReservedNetworks: true,
26
+ IPVersion: 6,
27
+ Languages: []string{"en"},
28
+ RecordSize: 24,
29
+ },
30
+ )
31
+ if err != nil {
32
+ return fmt.Errorf("creating mmdbwriter: %w", err)
33
+ }
34
+
35
+ addrs, err := parseIPSlice(ipSample)
36
+ if err != nil {
37
+ return fmt.Errorf("parsing ip addresses: %w", err)
38
+ }
39
+ if err := insertAllTypes(dbWriter, addrs); err != nil {
40
+ return fmt.Errorf("inserting all types records: %w", err)
41
+ }
42
+
43
+ zeroAddr, err := netip.ParsePrefix("::0.0.0.0/128")
44
+ if err != nil {
45
+ return fmt.Errorf("parsing ip: %w", err)
46
+ }
47
+ if err := insertAllTypesZero(dbWriter, []netip.Prefix{zeroAddr}); err != nil {
48
+ return fmt.Errorf("inserting all types records: %w", err)
49
+ }
50
+
51
+ maxAddr, err := netip.ParsePrefix("::255.255.255.255/128")
52
+ if err != nil {
53
+ return fmt.Errorf("parsing ip: %w", err)
54
+ }
55
+ if err := insertNumericMax(dbWriter, []netip.Prefix{maxAddr}); err != nil {
56
+ return fmt.Errorf("inserting all types records: %w", err)
57
+ }
58
+
59
+ if err := w.write(dbWriter, "MaxMind-DB-test-decoder.mmdb"); err != nil {
60
+ return fmt.Errorf("writing database: %w", err)
61
+ }
62
+ return nil
63
+ }
64
+
65
+ // insertAllTypes inserts records with all possible value types.
66
+ func insertAllTypes(w *mmdbwriter.Tree, ipAddresses []netip.Prefix) error {
67
+ buf := new(bytes.Buffer)
68
+ if err := binary.Write(buf, binary.BigEndian, uint32(42)); err != nil {
69
+ return fmt.Errorf("creating buffer for all types record: %w", err)
70
+ }
71
+
72
+ ui64 := big.Int{}
73
+ ui64.Lsh(big.NewInt(1), 60)
74
+
75
+ ui128 := big.Int{}
76
+ ui128.Lsh(big.NewInt(1), 120)
77
+ mmdbUint128 := mmdbtype.Uint128(ui128)
78
+
79
+ allTypes := mmdbtype.Map{
80
+ "array": mmdbtype.Slice{
81
+ mmdbtype.Uint32(1),
82
+ mmdbtype.Uint32(2),
83
+ mmdbtype.Uint32(3),
84
+ },
85
+ "bytes": mmdbtype.Bytes(buf.Bytes()),
86
+ "boolean": mmdbtype.Bool(true),
87
+ "double": mmdbtype.Float64(42.123456),
88
+ "float": mmdbtype.Float32(1.1),
89
+ "int32": mmdbtype.Int32(-1 * math.Pow(2, 28)),
90
+ "map": mmdbtype.Map{
91
+ "mapX": mmdbtype.Map{
92
+ "utf8_stringX": mmdbtype.String("hello"),
93
+ "arrayX": mmdbtype.Slice{
94
+ mmdbtype.Uint32(7),
95
+ mmdbtype.Uint32(8),
96
+ mmdbtype.Uint32(9),
97
+ },
98
+ },
99
+ },
100
+ "uint16": mmdbtype.Uint16(100),
101
+ "uint32": mmdbtype.Uint32(math.Pow(2, 28)),
102
+ "uint64": mmdbtype.Uint64(ui64.Uint64()),
103
+ "uint128": mmdbUint128.Copy(),
104
+ "utf8_string": mmdbtype.String("unicode! ☯ - ♫"),
105
+ }
106
+
107
+ for _, addr := range ipAddresses {
108
+ err := w.Insert(
109
+ netipx.PrefixIPNet(addr),
110
+ allTypes,
111
+ )
112
+ if err != nil {
113
+ return fmt.Errorf("inserting ip: %w", err)
114
+ }
115
+ }
116
+ return nil
117
+ }
118
+
119
+ // insertAllTypesZero inserts records with all possible value types with zero values.
120
+ func insertAllTypesZero(w *mmdbwriter.Tree, ipAddresses []netip.Prefix) error {
121
+ var uint128 big.Int
122
+ mmdbUint128 := mmdbtype.Uint128(uint128)
123
+
124
+ zeroValues := mmdbtype.Map{
125
+ "array": mmdbtype.Slice{},
126
+ "bytes": mmdbtype.Bytes([]byte{}),
127
+ "boolean": mmdbtype.Bool(false),
128
+ "double": mmdbtype.Float64(0),
129
+ "float": mmdbtype.Float32(0),
130
+ "int32": mmdbtype.Int32(0),
131
+ "map": mmdbtype.Map{},
132
+ "uint16": mmdbtype.Uint16(0),
133
+ "uint32": mmdbtype.Uint32(0),
134
+ "uint64": mmdbtype.Uint64(0),
135
+ "uint128": mmdbUint128.Copy(),
136
+ "utf8_string": mmdbtype.String(""),
137
+ }
138
+
139
+ for _, addr := range ipAddresses {
140
+ err := w.Insert(
141
+ netipx.PrefixIPNet(addr),
142
+ zeroValues,
143
+ )
144
+ if err != nil {
145
+ return fmt.Errorf("inserting ip: %w", err)
146
+ }
147
+ }
148
+ return nil
149
+ }
150
+
151
+ // insertNumericMax inserts records with numeric types maxed out.
152
+ func insertNumericMax(w *mmdbwriter.Tree, ipAddresses []netip.Prefix) error {
153
+ var uint128Max big.Int
154
+ uint128Max.Exp(big.NewInt(2), big.NewInt(128), nil)
155
+ uint128Max.Sub(&uint128Max, big.NewInt(1))
156
+ mmdbUint128 := mmdbtype.Uint128(uint128Max)
157
+
158
+ numMax := mmdbtype.Map{
159
+ "double": mmdbtype.Float64(math.Inf(1)),
160
+ "float": mmdbtype.Float32(float32(math.Inf(1))),
161
+ "int32": mmdbtype.Int32(1<<31 - 1),
162
+ "uint16": mmdbtype.Uint16(0xffff),
163
+ "uint32": mmdbtype.Uint32(0xffffffff),
164
+ "uint64": mmdbtype.Uint64(0xffffffffffffffff),
165
+ "uint128": mmdbUint128.Copy(),
166
+ }
167
+
168
+ for _, addr := range ipAddresses {
169
+ err := w.Insert(
170
+ netipx.PrefixIPNet(addr),
171
+ numMax,
172
+ )
173
+ if err != nil {
174
+ return fmt.Errorf("inserting ip: %w", err)
175
+ }
176
+ }
177
+ return nil
178
+ }
@@ -0,0 +1,184 @@
1
+ package writer
2
+
3
+ import (
4
+ "encoding/json"
5
+ "fmt"
6
+ "net/netip"
7
+ "os"
8
+ "path/filepath"
9
+ "strings"
10
+
11
+ "github.com/maxmind/mmdbwriter"
12
+ "github.com/maxmind/mmdbwriter/mmdbtype"
13
+ "go4.org/netipx"
14
+ )
15
+
16
+ // WriteGeoIP2TestDB writes GeoIP2 test mmdb files.
17
+ func (w *Writer) WriteGeoIP2TestDB() error {
18
+ dbTypes := []string{
19
+ "GeoIP-Anonymous-Plus",
20
+ "GeoIP2-Anonymous-IP",
21
+ "GeoIP2-City",
22
+ "GeoIP2-Connection-Type",
23
+ "GeoIP2-Country",
24
+ "GeoIP2-DensityIncome",
25
+ "GeoIP2-Domain",
26
+ "GeoIP2-Enterprise",
27
+ "GeoIP2-IP-Risk",
28
+ "GeoIP2-ISP",
29
+ "GeoIP2-Precision-Enterprise",
30
+ "GeoIP2-Static-IP-Score",
31
+ "GeoIP2-User-Count",
32
+ "GeoLite2-ASN",
33
+ "GeoLite2-City",
34
+ "GeoLite2-Country",
35
+ }
36
+
37
+ for _, dbType := range dbTypes {
38
+ languages := []string{"en"}
39
+ description := map[string]string{
40
+ "en": strings.ReplaceAll(dbType, "-", " ") +
41
+ " Test Database (fake GeoIP2 data, for example purposes only)",
42
+ }
43
+
44
+ if dbType == "GeoIP2-City" {
45
+ languages = append(languages, "zh")
46
+ description["zh"] = "小型数据库"
47
+ }
48
+
49
+ dbWriter, err := mmdbwriter.New(
50
+ mmdbwriter.Options{
51
+ DatabaseType: dbType,
52
+ Description: description,
53
+ DisableIPv4Aliasing: false,
54
+ IPVersion: 6,
55
+ Languages: languages,
56
+ RecordSize: 28,
57
+ },
58
+ )
59
+ if err != nil {
60
+ return fmt.Errorf("creating mmdbwriter: %w", err)
61
+ }
62
+
63
+ if dbType == "GeoIP2-Anonymous-IP" || dbType == "GeoIP-Anonymous-Plus" {
64
+ if err := populateAllNetworks(dbWriter); err != nil {
65
+ return fmt.Errorf("inserting all networks: %w", err)
66
+ }
67
+ }
68
+
69
+ jsonFileName := dbType + "-Test.json"
70
+ if err := w.insertJSON(dbWriter, jsonFileName); err != nil {
71
+ return fmt.Errorf("inserting json: %w", err)
72
+ }
73
+
74
+ dbFileName := dbType + "-Test.mmdb"
75
+ if err := w.write(dbWriter, dbFileName); err != nil {
76
+ return fmt.Errorf("writing database: %w", err)
77
+ }
78
+ }
79
+
80
+ return nil
81
+ }
82
+
83
+ // insertJSON reads and parses a json file into mmdbtypes values and inserts
84
+ // them into the mmdbwriter tree.
85
+ func (w *Writer) insertJSON(dbWriter *mmdbwriter.Tree, fileName string) error {
86
+ file, err := os.Open(filepath.Clean(filepath.Join(w.source, fileName)))
87
+ if err != nil {
88
+ return fmt.Errorf("opening json file: %w", err)
89
+ }
90
+ defer file.Close()
91
+
92
+ var data []map[string]any
93
+ if err := json.NewDecoder(file).Decode(&data); err != nil {
94
+ return fmt.Errorf("decoding json file: %w", err)
95
+ }
96
+
97
+ for _, record := range data {
98
+ for k, v := range record {
99
+ prefix, err := netip.ParsePrefix(k)
100
+ if err != nil {
101
+ return fmt.Errorf("parsing ip: %w", err)
102
+ }
103
+
104
+ mmdbValue, err := toMMDBType(prefix.String(), v)
105
+ if err != nil {
106
+ return fmt.Errorf("converting value to mmdbtype: %w", err)
107
+ }
108
+
109
+ err = dbWriter.Insert(
110
+ netipx.PrefixIPNet(prefix),
111
+ mmdbValue,
112
+ )
113
+ if err != nil {
114
+ return fmt.Errorf("inserting ip: %w", err)
115
+ }
116
+ }
117
+ }
118
+ return nil
119
+ }
120
+
121
+ // toMMDBType key converts field values read from json into their corresponding mmdbtype.DataType.
122
+ // It makes some assumptions for numeric types based on previous knowledge about field types.
123
+ func toMMDBType(key string, value any) (mmdbtype.DataType, error) {
124
+ switch v := value.(type) {
125
+ case bool:
126
+ return mmdbtype.Bool(v), nil
127
+ case string:
128
+ return mmdbtype.String(v), nil
129
+ case map[string]any:
130
+ m := mmdbtype.Map{}
131
+ for innerKey, val := range v {
132
+ innerVal, err := toMMDBType(innerKey, val)
133
+ if err != nil {
134
+ return nil, fmt.Errorf("parsing mmdbtype.Map for key %q: %w", key, err)
135
+ }
136
+ m[mmdbtype.String(innerKey)] = innerVal
137
+ }
138
+ return m, nil
139
+ case []any:
140
+ s := mmdbtype.Slice{}
141
+ for _, val := range v {
142
+ innerVal, err := toMMDBType(key, val)
143
+ if err != nil {
144
+ return nil, fmt.Errorf("parsing mmdbtype.Slice for key %q: %w", key, err)
145
+ }
146
+ s = append(s, innerVal)
147
+ }
148
+ return s, nil
149
+ case float64:
150
+ switch key {
151
+ case "accuracy_radius", "anonymizer_confidence", "confidence", "metro_code":
152
+ return mmdbtype.Uint16(v), nil
153
+ case "autonomous_system_number", "average_income",
154
+ "geoname_id", "ipv4_24", "ipv4_32", "ipv6_32",
155
+ "ipv6_48", "ipv6_64", "population_density":
156
+ return mmdbtype.Uint32(v), nil
157
+ case "ip_risk", "latitude", "longitude", "score",
158
+ "static_ip_score":
159
+ return mmdbtype.Float64(v), nil
160
+ default:
161
+ return nil, fmt.Errorf("unsupported numeric type for key %q: %T", key, value)
162
+ }
163
+ default:
164
+ return nil, fmt.Errorf("unsupported type for key %q: %T", key, value)
165
+ }
166
+ }
167
+
168
+ // populate all networks inserts all networks into the writer with an empty map value.
169
+ func populateAllNetworks(w *mmdbwriter.Tree) error {
170
+ defaultNet, err := netip.ParsePrefix("::/0")
171
+ if err != nil {
172
+ return fmt.Errorf("parsing ip: %w", err)
173
+ }
174
+
175
+ err = w.Insert(
176
+ netipx.PrefixIPNet(defaultNet),
177
+ mmdbtype.Map{},
178
+ )
179
+ if err != nil {
180
+ return fmt.Errorf("inserting ip: %w", err)
181
+ }
182
+
183
+ return nil
184
+ }
@@ -0,0 +1,39 @@
1
+ package writer
2
+
3
+ import (
4
+ "fmt"
5
+ "net/netip"
6
+
7
+ "go4.org/netipx"
8
+ )
9
+
10
+ // parseIPRange takes IP addresses in string presentation form that represent a
11
+ // range and returns an IP range.
12
+ func parseIPRange(from, to string) (netipx.IPRange, error) {
13
+ startIP, err := netip.ParseAddr(from)
14
+ if err != nil {
15
+ return netipx.IPRange{}, fmt.Errorf("parsing %s as an IP: %w", from, err)
16
+ }
17
+ endIP, err := netip.ParseAddr(to)
18
+ if err != nil {
19
+ return netipx.IPRange{}, fmt.Errorf("parsing %s as an IP: %w", to, err)
20
+ }
21
+ ipRange := netipx.IPRangeFrom(startIP, endIP)
22
+ if !ipRange.IsValid() {
23
+ return netipx.IPRange{}, fmt.Errorf("%s-%s is an invalid IP range", startIP, endIP)
24
+ }
25
+ return ipRange, nil
26
+ }
27
+
28
+ // parseIPSlice parses a slice of IP address strings and returns a slice of netip.Prefix.
29
+ func parseIPSlice(ipAddresses []string) ([]netip.Prefix, error) {
30
+ var addrs []netip.Prefix
31
+ for _, ip := range ipAddresses {
32
+ addr, err := netip.ParsePrefix(ip)
33
+ if err != nil {
34
+ return nil, fmt.Errorf("parsing %s as an IP: %w", ip, err)
35
+ }
36
+ addrs = append(addrs, addr)
37
+ }
38
+ return addrs, nil
39
+ }
@@ -0,0 +1,246 @@
1
+ package writer
2
+
3
+ import (
4
+ "fmt"
5
+ "net/netip"
6
+
7
+ "github.com/maxmind/mmdbwriter"
8
+ "github.com/maxmind/mmdbwriter/mmdbtype"
9
+ "go4.org/netipx"
10
+ )
11
+
12
+ // WriteIPv4TestDB writes mmdb files for an ip range between 1.1.1.1 and 1.1.1.32
13
+ // with various record sizes.
14
+ func (w *Writer) WriteIPv4TestDB() error {
15
+ ipRange, err := parseIPRange("1.1.1.1", "1.1.1.32")
16
+ if err != nil {
17
+ return fmt.Errorf("parsing ip range: %w", err)
18
+ }
19
+
20
+ for _, recordSize := range []int{24, 28, 32} {
21
+ err := w.writeMaxMindTestDB(
22
+ recordSize,
23
+ []netipx.IPRange{ipRange},
24
+ "ipv4",
25
+ )
26
+ if err != nil {
27
+ return fmt.Errorf("writing test database: %w", err)
28
+ }
29
+ }
30
+
31
+ return nil
32
+ }
33
+
34
+ // WriteIPv6TestDB writes mmdb files for an ip range between ::1:ffff:ffff and ::2:0000:0059
35
+ // with various record sizes.
36
+ func (w *Writer) WriteIPv6TestDB() error {
37
+ ipRange, err := parseIPRange("::1:ffff:ffff", "::2:0000:0059")
38
+ if err != nil {
39
+ return fmt.Errorf("parsing ip range: %w", err)
40
+ }
41
+
42
+ for _, recordSize := range []int{24, 28, 32} {
43
+ err := w.writeMaxMindTestDB(
44
+ recordSize,
45
+ []netipx.IPRange{ipRange},
46
+ "ipv6",
47
+ )
48
+ if err != nil {
49
+ return fmt.Errorf("writing test database: %w", err)
50
+ }
51
+ }
52
+
53
+ return nil
54
+ }
55
+
56
+ // WriteMixedIPTestDB writes mmdb files for a mixed ip version range between ::1:ffff:ffff and
57
+ // ::2:0000:0059
58
+ // with various record sizes.
59
+ func (w *Writer) WriteMixedIPTestDB() error {
60
+ ipv6Range, err := parseIPRange("::1:ffff:ffff", "::2:0000:0059")
61
+ if err != nil {
62
+ return fmt.Errorf("parsing ip range: %w", err)
63
+ }
64
+
65
+ ipv4Range, err := parseIPRange("1.1.1.1", "1.1.1.32")
66
+ if err != nil {
67
+ return fmt.Errorf("parsing ip range: %w", err)
68
+ }
69
+
70
+ for _, recordSize := range []int{24, 28, 32} {
71
+ err := w.writeMaxMindTestDB(
72
+ recordSize,
73
+ []netipx.IPRange{ipv6Range, ipv4Range},
74
+ "mixed",
75
+ )
76
+ if err != nil {
77
+ return fmt.Errorf("writing test database: %w", err)
78
+ }
79
+ }
80
+
81
+ return nil
82
+ }
83
+
84
+ // writeMaxMindTestDB writes test mmdb files.
85
+ func (w *Writer) writeMaxMindTestDB(
86
+ recordSize int,
87
+ ipRange []netipx.IPRange,
88
+ ipVersionName string,
89
+ ) error {
90
+ ipVersion := 6
91
+ if ipRange[0].From().Is4() {
92
+ ipVersion = 4
93
+ }
94
+
95
+ metadata := map[string]string{}
96
+ metadata["en"] = "Test Database"
97
+ metadata["zh"] = "Test Database Chinese"
98
+
99
+ dbWriter, err := mmdbwriter.New(
100
+ mmdbwriter.Options{
101
+ DatabaseType: "Test",
102
+ Description: metadata,
103
+ DisableIPv4Aliasing: ipVersion == 4,
104
+ IPVersion: ipVersion,
105
+ Languages: []string{"en", "zh"},
106
+ RecordSize: recordSize,
107
+ },
108
+ )
109
+ if err != nil {
110
+ return fmt.Errorf("creating mmdbwriter: %w", err)
111
+ }
112
+
113
+ for _, ir := range ipRange {
114
+ for _, prefix := range ir.Prefixes() {
115
+ ipString := prefix.Addr().String()
116
+ if ipVersion == 6 && prefix.Addr().Is4() {
117
+ ipString = "::" + ipString
118
+ }
119
+
120
+ err := dbWriter.Insert(
121
+ netipx.PrefixIPNet(prefix),
122
+ mmdbtype.Map{
123
+ "ip": mmdbtype.String(ipString),
124
+ },
125
+ )
126
+ if err != nil {
127
+ return fmt.Errorf("inserting ip: %w", err)
128
+ }
129
+ }
130
+ }
131
+
132
+ fileName := fmt.Sprintf("MaxMind-DB-test-%s-%d.mmdb", ipVersionName, recordSize)
133
+ if err := w.write(dbWriter, fileName); err != nil {
134
+ return fmt.Errorf("writing database: %w", err)
135
+ }
136
+
137
+ return nil
138
+ }
139
+
140
+ // WriteNoIPv4TestDB writes an mmdb file with no ipv4 records.
141
+ func (w *Writer) WriteNoIPv4TestDB() error {
142
+ dbWriter, err := mmdbwriter.New(
143
+ mmdbwriter.Options{
144
+ DatabaseType: "MaxMind DB No IPv4 Search Tree",
145
+ Description: map[string]string{
146
+ "en": "MaxMind DB No IPv4 Search Tree",
147
+ },
148
+ DisableIPv4Aliasing: true,
149
+ IncludeReservedNetworks: true,
150
+ IPVersion: 6,
151
+ Languages: []string{"en"},
152
+ RecordSize: 24,
153
+ },
154
+ )
155
+ if err != nil {
156
+ return fmt.Errorf("creating mmdbwriter: %w", err)
157
+ }
158
+
159
+ addr, err := netip.ParsePrefix("::/64")
160
+ if err != nil {
161
+ return fmt.Errorf("parsing ip: %w", err)
162
+ }
163
+
164
+ err = dbWriter.Insert(
165
+ netipx.PrefixIPNet(addr),
166
+ mmdbtype.String(addr.String()),
167
+ )
168
+ if err != nil {
169
+ return fmt.Errorf("inserting ip: %w", err)
170
+ }
171
+
172
+ if err := w.write(dbWriter, "MaxMind-DB-no-ipv4-search-tree.mmdb"); err != nil {
173
+ return fmt.Errorf("writing database: %w", err)
174
+ }
175
+ return nil
176
+ }
177
+
178
+ // WriteNoMapTestDB writes an mmdb file where each record points to
179
+ // a string value.
180
+ func (w *Writer) WriteNoMapTestDB() error {
181
+ dbWriter, err := mmdbwriter.New(
182
+ mmdbwriter.Options{
183
+ DatabaseType: "MaxMind DB String Value Entries",
184
+ Description: map[string]string{
185
+ "en": "MaxMind DB String Value Entries (no maps or arrays as values)",
186
+ },
187
+ IPVersion: 4,
188
+ Languages: []string{"en"},
189
+ RecordSize: 24,
190
+ },
191
+ )
192
+ if err != nil {
193
+ return fmt.Errorf("creating mmdbwriter: %w", err)
194
+ }
195
+
196
+ ipRange, err := parseIPRange("1.1.1.1", "1.1.1.32")
197
+ if err != nil {
198
+ return fmt.Errorf("parsing ip range: %w", err)
199
+ }
200
+
201
+ for _, prefix := range ipRange.Prefixes() {
202
+ err := dbWriter.Insert(
203
+ netipx.PrefixIPNet(prefix),
204
+ mmdbtype.String(prefix.String()),
205
+ )
206
+ if err != nil {
207
+ return fmt.Errorf("inserting ip: %w", err)
208
+ }
209
+ }
210
+
211
+ if err := w.write(dbWriter, "MaxMind-DB-string-value-entries.mmdb"); err != nil {
212
+ return fmt.Errorf("writing database: %w", err)
213
+ }
214
+ return nil
215
+ }
216
+
217
+ // WriteMetadataPointersTestDB writes an mmdb file with metadata pointers allowed.
218
+ func (w *Writer) WriteMetadataPointersTestDB() error {
219
+ repeatedString := "Lots of pointers in metadata"
220
+ dbWriter, err := mmdbwriter.New(
221
+ mmdbwriter.Options{
222
+ DatabaseType: repeatedString,
223
+ Description: map[string]string{
224
+ "en": repeatedString,
225
+ "es": repeatedString,
226
+ "zh": repeatedString,
227
+ },
228
+ DisableIPv4Aliasing: true,
229
+ IPVersion: 6,
230
+ Languages: []string{"en", "es", "zh"},
231
+ RecordSize: 24,
232
+ },
233
+ )
234
+ if err != nil {
235
+ return fmt.Errorf("creating mmdbwriter: %w", err)
236
+ }
237
+
238
+ if err := populateAllNetworks(dbWriter); err != nil {
239
+ return fmt.Errorf("inserting all networks: %w", err)
240
+ }
241
+
242
+ if err := w.write(dbWriter, "MaxMind-DB-test-metadata-pointers.mmdb"); err != nil {
243
+ return fmt.Errorf("writing database: %w", err)
244
+ }
245
+ return nil
246
+ }