Skip to content

Commit e2baeea

Browse files
committed
Add IncludeEmptyNetworks as Networks option
This can be useful when doing things like comparing two databases.
1 parent 4fb2531 commit e2baeea

File tree

2 files changed

+78
-4
lines changed

2 files changed

+78
-4
lines changed

traverse.go

Lines changed: 34 additions & 3 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,21 +34,30 @@ 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
4249
// [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
@@ -57,6 +67,9 @@ func (r *Reader) Networks(options ...NetworksOption) iter.Seq[Result] {
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,25 @@ 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+
ip := node.ip
126+
if isInIPv4Subtree(ip) {
127+
ip = v6ToV4(ip)
128+
}
129+
130+
ok := yield(Result{
131+
ip: ip,
132+
offset: notFound,
133+
prefixLen: uint8(node.bit),
134+
})
135+
if !ok {
136+
return
137+
}
138+
}
139+
break
140+
}
110141
// This skips IPv4 aliases without hardcoding the networks that the writer
111142
// currently aliases.
112143
if !n.includeAliasedNetworks && r.ipv4Start != 0 &&

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)