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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +22 -0
- data/Gemfile.lock +114 -0
- data/README.dev.md +1 -1
- data/README.md +26 -6
- data/Rakefile +1 -0
- data/lib/maxmind/geoip2/client.rb +18 -9
- data/lib/maxmind/geoip2/model/anonymous_plus.rb +46 -0
- data/lib/maxmind/geoip2/model/city.rb +5 -6
- data/lib/maxmind/geoip2/model/connection_type.rb +3 -2
- data/lib/maxmind/geoip2/model/enterprise.rb +2 -3
- data/lib/maxmind/geoip2/model/insights.rb +3 -5
- data/lib/maxmind/geoip2/reader.rb +26 -1
- data/lib/maxmind/geoip2/record/location.rb +3 -3
- data/lib/maxmind/geoip2/record/traits.rb +40 -28
- data/lib/maxmind/geoip2/version.rb +8 -0
- data/maxmind-geoip2.gemspec +11 -6
- data/test/data/LICENSE-APACHE +202 -0
- data/test/data/LICENSE-MIT +17 -0
- data/test/data/MaxMind-DB-spec.md +1 -2
- data/test/data/README.md +8 -1
- data/test/data/cmd/write-test-data/main.go +68 -0
- data/test/data/go.mod +13 -0
- data/test/data/go.sum +16 -0
- data/test/data/pkg/writer/decoder.go +178 -0
- data/test/data/pkg/writer/geoip2.go +184 -0
- data/test/data/pkg/writer/ip.go +39 -0
- data/test/data/pkg/writer/maxmind.go +246 -0
- data/test/data/pkg/writer/nestedstructures.go +73 -0
- data/test/data/pkg/writer/writer.go +61 -0
- data/test/data/source-data/GeoIP-Anonymous-Plus-Test.json +175 -0
- data/test/data/source-data/GeoIP2-Anonymous-IP-Test.json +6 -0
- data/test/data/source-data/GeoIP2-City-Test.json +392 -5
- data/test/data/source-data/GeoIP2-Connection-Type-Test.json +15 -10
- data/test/data/source-data/GeoIP2-Country-Test.json +99 -25
- data/test/data/source-data/GeoIP2-Domain-Test.json +5 -0
- data/test/data/source-data/GeoIP2-Enterprise-Test.json +371 -6
- data/test/data/source-data/GeoIP2-IP-Risk-Test.json +31 -0
- data/test/data/source-data/GeoIP2-Precision-Enterprise-Sandbox-Test.json +296 -0
- data/test/data/source-data/GeoIP2-Precision-Enterprise-Test.json +1159 -175
- data/test/data/source-data/GeoIP2-Static-IP-Score-Test.json +15 -0
- data/test/data/source-data/GeoIP2-User-Count-Test.json +18 -0
- data/test/data/source-data/GeoLite2-City-Test.json +168 -3
- data/test/data/source-data/GeoLite2-Country-Test.json +92 -3
- data/test/data/test-data/GeoIP-Anonymous-Plus-Test.mmdb +0 -0
- data/test/data/test-data/GeoIP2-Anonymous-IP-Test.mmdb +0 -0
- data/test/data/test-data/GeoIP2-City-Test-Broken-Double-Format.mmdb +0 -0
- data/test/data/test-data/GeoIP2-City-Test-Invalid-Node-Count.mmdb +0 -0
- data/test/data/test-data/GeoIP2-City-Test.mmdb +0 -0
- data/test/data/test-data/GeoIP2-Connection-Type-Test.mmdb +0 -0
- data/test/data/test-data/GeoIP2-Country-Test.mmdb +0 -0
- data/test/data/test-data/GeoIP2-DensityIncome-Test.mmdb +0 -0
- data/test/data/test-data/GeoIP2-Domain-Test.mmdb +0 -0
- data/test/data/test-data/GeoIP2-Enterprise-Test.mmdb +0 -0
- data/test/data/test-data/GeoIP2-IP-Risk-Test.mmdb +0 -0
- data/test/data/test-data/GeoIP2-ISP-Test.mmdb +0 -0
- data/test/data/test-data/GeoIP2-Precision-Enterprise-Test.mmdb +0 -0
- data/test/data/test-data/GeoIP2-Static-IP-Score-Test.mmdb +0 -0
- data/test/data/test-data/GeoIP2-User-Count-Test.mmdb +0 -0
- data/test/data/test-data/GeoLite2-ASN-Test.mmdb +0 -0
- data/test/data/test-data/GeoLite2-City-Test.mmdb +0 -0
- data/test/data/test-data/GeoLite2-Country-Test.mmdb +0 -0
- data/test/data/test-data/MaxMind-DB-no-ipv4-search-tree.mmdb +0 -0
- data/test/data/test-data/MaxMind-DB-string-value-entries.mmdb +0 -0
- data/test/data/test-data/MaxMind-DB-test-broken-pointers-24.mmdb +0 -0
- data/test/data/test-data/MaxMind-DB-test-broken-search-tree-24.mmdb +0 -0
- data/test/data/test-data/MaxMind-DB-test-decoder.mmdb +0 -0
- data/test/data/test-data/MaxMind-DB-test-ipv4-24.mmdb +0 -0
- data/test/data/test-data/MaxMind-DB-test-ipv4-28.mmdb +0 -0
- data/test/data/test-data/MaxMind-DB-test-ipv4-32.mmdb +0 -0
- data/test/data/test-data/MaxMind-DB-test-ipv6-24.mmdb +0 -0
- data/test/data/test-data/MaxMind-DB-test-ipv6-28.mmdb +0 -0
- data/test/data/test-data/MaxMind-DB-test-ipv6-32.mmdb +0 -0
- data/test/data/test-data/MaxMind-DB-test-metadata-pointers.mmdb +0 -0
- data/test/data/test-data/MaxMind-DB-test-mixed-24.mmdb +0 -0
- data/test/data/test-data/MaxMind-DB-test-mixed-28.mmdb +0 -0
- data/test/data/test-data/MaxMind-DB-test-mixed-32.mmdb +0 -0
- data/test/data/test-data/MaxMind-DB-test-nested.mmdb +0 -0
- data/test/data/test-data/MaxMind-DB-test-pointer-decoder.mmdb +0 -0
- data/test/data/test-data/README.md +28 -12
- data/test/test_client.rb +18 -2
- data/test/test_reader.rb +42 -1
- metadata +25 -13
- data/test/data/LICENSE +0 -4
- data/test/data/perltidyrc +0 -12
- data/test/data/source-data/README +0 -15
- 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
|
+
}
|