Skip to content

Commit 8f32dec

Browse files
authored
Merge pull request #155 from oschwald/greg/iterate-over-empty-records
Allow iterating over empty networks
2 parents 8d04368 + f509ad1 commit 8f32dec

File tree

3 files changed

+85
-16
lines changed

3 files changed

+85
-16
lines changed

.golangci.toml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ disable = [
1212
"cyclop",
1313
"depguard",
1414
"err113",
15-
"execinquery",
1615
"exhaustive",
1716
"exhaustruct",
1817
"exportloopref",
@@ -21,7 +20,6 @@ disable = [
2120
"gochecknoglobals",
2221
"gocognit",
2322
"godox",
24-
"gomnd",
2523
"inamedparam",
2624
"interfacebloat",
2725
"mnd",

traverse.go

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ type netNode struct {
1616

1717
type networkOptions struct {
1818
includeAliasedNetworks bool
19+
includeEmptyNetworks bool
1920
}
2021

2122
var (
@@ -33,30 +34,42 @@ func IncludeAliasedNetworks(networks *networkOptions) {
3334
networks.includeAliasedNetworks = true
3435
}
3536

36-
// Networks returns an iterator that can be used to traverse all networks in
37+
// IncludeEmptyNetworks is an option for Networks and NetworksWithin
38+
// that makes them include networks without any data in the iteration.
39+
func IncludeEmptyNetworks(networks *networkOptions) {
40+
networks.includeEmptyNetworks = true
41+
}
42+
43+
// Networks returns an iterator that can be used to traverse the networks in
3744
// the database.
3845
//
3946
// Please note that a MaxMind DB may map IPv4 networks into several locations
4047
// in an IPv6 database. This iterator will only iterate over these once by
4148
// default. To iterate over all the IPv4 network locations, use the
42-
// IncludeAliasedNetworks option.
49+
// [IncludeAliasedNetworks] option.
50+
//
51+
// Networks without data are excluded by default. To include them, use
52+
// [IncludeEmptyNetworks].
4353
func (r *Reader) Networks(options ...NetworksOption) iter.Seq[Result] {
4454
if r.Metadata.IPVersion == 6 {
4555
return r.NetworksWithin(allIPv6, options...)
4656
}
4757
return r.NetworksWithin(allIPv4, options...)
4858
}
4959

50-
// NetworksWithin returns an iterator that can be used to traverse all networks
60+
// NetworksWithin returns an iterator that can be used to traverse the networks
5161
// in the database which are contained in a given prefix.
5262
//
5363
// Please note that a MaxMind DB may map IPv4 networks into several locations
54-
// in an IPv6 database. This iterator will iterate over all of these locations
55-
// separately. To only iterate over the IPv4 networks once, use the
56-
// SkipAliasedNetworks option.
64+
// in an IPv6 database. This iterator will only iterate over these once by
65+
// default. To iterate over all the IPv4 network locations, use the
66+
// [IncludeAliasedNetworks] option.
5767
//
5868
// If the provided prefix is contained within a network in the database, the
5969
// iterator will iterate over exactly one network, the containing network.
70+
//
71+
// Networks without data are excluded by default. To include them, use
72+
// [IncludeEmptyNetworks].
6073
func (r *Reader) NetworksWithin(prefix netip.Prefix, options ...NetworksOption) iter.Seq[Result] {
6174
return func(yield func(Result) bool) {
6275
if r.Metadata.IPVersion == 4 && prefix.Addr().Is6() {
@@ -106,7 +119,20 @@ func (r *Reader) NetworksWithin(prefix netip.Prefix, options ...NetworksOption)
106119
node := nodes[len(nodes)-1]
107120
nodes = nodes[:len(nodes)-1]
108121

109-
for node.pointer != r.Metadata.NodeCount {
122+
for {
123+
if node.pointer == r.Metadata.NodeCount {
124+
if n.includeEmptyNetworks {
125+
ok := yield(Result{
126+
ip: mappedIP(node.ip),
127+
offset: notFound,
128+
prefixLen: uint8(node.bit),
129+
})
130+
if !ok {
131+
return
132+
}
133+
}
134+
break
135+
}
110136
// This skips IPv4 aliases without hardcoding the networks that the writer
111137
// currently aliases.
112138
if !n.includeAliasedNetworks && r.ipv4Start != 0 &&
@@ -115,15 +141,10 @@ func (r *Reader) NetworksWithin(prefix netip.Prefix, options ...NetworksOption)
115141
}
116142

117143
if node.pointer > r.Metadata.NodeCount {
118-
ip := node.ip
119-
if isInIPv4Subtree(ip) {
120-
ip = v6ToV4(ip)
121-
}
122-
123144
offset, err := r.resolveDataPointer(node.pointer)
124145
ok := yield(Result{
125146
decoder: r.decoder,
126-
ip: ip,
147+
ip: mappedIP(node.ip),
127148
offset: uint(offset),
128149
prefixLen: uint8(node.bit),
129150
err: err,
@@ -171,6 +192,13 @@ func (r *Reader) NetworksWithin(prefix netip.Prefix, options ...NetworksOption)
171192

172193
var ipv4SubtreeBoundary = netip.MustParseAddr("::255.255.255.255").Next()
173194

195+
func mappedIP(ip netip.Addr) netip.Addr {
196+
if isInIPv4Subtree(ip) {
197+
return v6ToV4(ip)
198+
}
199+
return ip
200+
}
201+
174202
// isInIPv4Subtree returns true if the IP is in the database's IPv4 subtree.
175203
func isInIPv4Subtree(ip netip.Addr) bool {
176204
return ip.Is4() || ip.Less(ipv4SubtreeBoundary)

traverse_test.go

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package maxminddb
33
import (
44
"fmt"
55
"net/netip"
6+
"reflect"
7+
"runtime"
68
"strconv"
79
"strings"
810
"testing"
@@ -244,6 +246,43 @@ var tests = []networkTest{
244246
"::2:0:58/127",
245247
},
246248
},
249+
{
250+
Network: "1.0.0.0/8",
251+
Database: "mixed",
252+
Expected: []string{
253+
"1.0.0.0/16",
254+
"1.1.0.0/24",
255+
"1.1.1.0/32",
256+
"1.1.1.1/32",
257+
"1.1.1.2/31",
258+
"1.1.1.4/30",
259+
"1.1.1.8/29",
260+
"1.1.1.16/28",
261+
"1.1.1.32/32",
262+
"1.1.1.33/32",
263+
"1.1.1.34/31",
264+
"1.1.1.36/30",
265+
"1.1.1.40/29",
266+
"1.1.1.48/28",
267+
"1.1.1.64/26",
268+
"1.1.1.128/25",
269+
"1.1.2.0/23",
270+
"1.1.4.0/22",
271+
"1.1.8.0/21",
272+
"1.1.16.0/20",
273+
"1.1.32.0/19",
274+
"1.1.64.0/18",
275+
"1.1.128.0/17",
276+
"1.2.0.0/15",
277+
"1.4.0.0/14",
278+
"1.8.0.0/13",
279+
"1.16.0.0/12",
280+
"1.32.0.0/11",
281+
"1.64.0.0/10",
282+
"1.128.0.0/9",
283+
},
284+
Options: []NetworksOption{IncludeEmptyNetworks},
285+
},
247286
{
248287
Network: "1.1.1.16/28",
249288
Database: "mixed",
@@ -263,12 +302,16 @@ var tests = []networkTest{
263302
func TestNetworksWithin(t *testing.T) {
264303
for _, v := range tests {
265304
for _, recordSize := range []uint{24, 28, 32} {
305+
var opts []string
306+
for _, o := range v.Options {
307+
opts = append(opts, runtime.FuncForPC(reflect.ValueOf(o).Pointer()).Name())
308+
}
266309
name := fmt.Sprintf(
267310
"%s-%d: %s, options: %v",
268311
v.Database,
269312
recordSize,
270313
v.Network,
271-
len(v.Options) != 0,
314+
opts,
272315
)
273316
t.Run(name, func(t *testing.T) {
274317
fileName := testFile(fmt.Sprintf("MaxMind-DB-test-%s-%d.mmdb", v.Database, recordSize))

0 commit comments

Comments
 (0)