Skip to content

Commit 8df8e4b

Browse files
committed
Add the ability to skip aliased networks
when using Networks and NetworksWithin. This is probably what most people wanted in the first place. Ideally, this would be the default behavior, but that is potentially a breaking change for people. The code is more complicated than would otherwise be necessary as I am trying to support the old behavior exactly as it was.
1 parent 77acebd commit 8df8e4b

File tree

3 files changed

+138
-76
lines changed

3 files changed

+138
-76
lines changed

example_test.go

Lines changed: 2 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ func ExampleReader_Networks() {
6464
Domain string `maxminddb:"connection_type"`
6565
}{}
6666

67-
networks := db.Networks()
67+
networks := db.Networks(maxminddb.SkipAliasedNetworks)
6868
for networks.Next() {
6969
subnet, err := networks.Network(&record)
7070
if err != nil {
@@ -76,25 +76,6 @@ func ExampleReader_Networks() {
7676
log.Fatal(networks.Err())
7777
}
7878
// Output:
79-
// ::100:0/120: Dialup
80-
// ::100:100/120: Cable/DSL
81-
// ::100:200/119: Dialup
82-
// ::100:400/118: Dialup
83-
// ::100:800/117: Dialup
84-
// ::100:1000/116: Dialup
85-
// ::100:2000/115: Dialup
86-
// ::100:4000/114: Dialup
87-
// ::100:8000/113: Dialup
88-
// ::50d6:0/116: Cellular
89-
// ::6001:0/112: Cable/DSL
90-
// ::600a:0/111: Cable/DSL
91-
// ::6045:0/112: Cable/DSL
92-
// ::605e:0/111: Cable/DSL
93-
// ::6c60:0/107: Cellular
94-
// ::af10:c700/120: Dialup
95-
// ::bb9c:8a00/120: Cable/DSL
96-
// ::c9f3:c800/120: Corporate
97-
// ::cfb3:3000/116: Cellular
9879
// 1.0.0.0/24: Dialup
9980
// 1.0.1.0/24: Cable/DSL
10081
// 1.0.2.0/23: Dialup
@@ -114,44 +95,6 @@ func ExampleReader_Networks() {
11495
// 187.156.138.0/24: Cable/DSL
11596
// 201.243.200.0/24: Corporate
11697
// 207.179.48.0/20: Cellular
117-
// 2001:0:100::/56: Dialup
118-
// 2001:0:100:100::/56: Cable/DSL
119-
// 2001:0:100:200::/55: Dialup
120-
// 2001:0:100:400::/54: Dialup
121-
// 2001:0:100:800::/53: Dialup
122-
// 2001:0:100:1000::/52: Dialup
123-
// 2001:0:100:2000::/51: Dialup
124-
// 2001:0:100:4000::/50: Dialup
125-
// 2001:0:100:8000::/49: Dialup
126-
// 2001:0:50d6::/52: Cellular
127-
// 2001:0:6001::/48: Cable/DSL
128-
// 2001:0:600a::/47: Cable/DSL
129-
// 2001:0:6045::/48: Cable/DSL
130-
// 2001:0:605e::/47: Cable/DSL
131-
// 2001:0:6c60::/43: Cellular
132-
// 2001:0:af10:c700::/56: Dialup
133-
// 2001:0:bb9c:8a00::/56: Cable/DSL
134-
// 2001:0:c9f3:c800::/56: Corporate
135-
// 2001:0:cfb3:3000::/52: Cellular
136-
// 2002:100::/40: Dialup
137-
// 2002:100:100::/40: Cable/DSL
138-
// 2002:100:200::/39: Dialup
139-
// 2002:100:400::/38: Dialup
140-
// 2002:100:800::/37: Dialup
141-
// 2002:100:1000::/36: Dialup
142-
// 2002:100:2000::/35: Dialup
143-
// 2002:100:4000::/34: Dialup
144-
// 2002:100:8000::/33: Dialup
145-
// 2002:50d6::/36: Cellular
146-
// 2002:6001::/32: Cable/DSL
147-
// 2002:600a::/31: Cable/DSL
148-
// 2002:6045::/32: Cable/DSL
149-
// 2002:605e::/31: Cable/DSL
150-
// 2002:6c60::/27: Cellular
151-
// 2002:af10:c700::/40: Dialup
152-
// 2002:bb9c:8a00::/40: Cable/DSL
153-
// 2002:c9f3:c800::/40: Corporate
154-
// 2002:cfb3:3000::/36: Cellular
15598
// 2003::/24: Cable/DSL
15699
}
157100

@@ -173,7 +116,7 @@ func ExampleReader_NetworksWithin() {
173116
log.Fatal(err)
174117
}
175118

176-
networks := db.NetworksWithin(network)
119+
networks := db.NetworksWithin(network, maxminddb.SkipAliasedNetworks)
177120
for networks.Next() {
178121
subnet, err := networks.Network(&record)
179122
if err != nil {

traverse.go

Lines changed: 71 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,25 +18,40 @@ type Networks struct {
1818
nodes []netNode // Nodes we still have to visit.
1919
lastNode netNode
2020
err error
21+
22+
skipAliasedNetworks bool
2123
}
2224

2325
var (
2426
allIPv4 = &net.IPNet{IP: make(net.IP, 4), Mask: net.CIDRMask(0, 32)}
2527
allIPv6 = &net.IPNet{IP: make(net.IP, 16), Mask: net.CIDRMask(0, 128)}
2628
)
2729

30+
// NetworksOption are options for Networks and NetworksWithin
31+
type NetworksOption func(*Networks)
32+
33+
// SkipAliasedNetworks is an option for Networks and NetworksWithin that
34+
// makes them not iterate over aliases of the IPv4 subtree in an IPv6
35+
// database, e.g., ::ffff:0:0/96, 2001::/32, and 2002::/16.
36+
//
37+
// You most likely want to set this. The only reason it isn't the default
38+
// behavior is to provide backwards compatibility to existing users.
39+
func SkipAliasedNetworks(networks *Networks) {
40+
networks.skipAliasedNetworks = true
41+
}
42+
2843
// Networks returns an iterator that can be used to traverse all networks in
2944
// the database.
3045
//
3146
// Please note that a MaxMind DB may map IPv4 networks into several locations
3247
// in an IPv6 database. This iterator will iterate over all of these
3348
// locations separately.
34-
func (r *Reader) Networks() *Networks {
49+
func (r *Reader) Networks(options ...NetworksOption) *Networks {
3550
var networks *Networks
3651
if r.Metadata.IPVersion == 6 {
37-
networks = r.NetworksWithin(allIPv6)
52+
networks = r.NetworksWithin(allIPv6, options...)
3853
} else {
39-
networks = r.NetworksWithin(allIPv4)
54+
networks = r.NetworksWithin(allIPv4, options...)
4055
}
4156

4257
return networks
@@ -51,7 +66,7 @@ func (r *Reader) Networks() *Networks {
5166
//
5267
// If the provided network is contained within a network in the database, the
5368
// iterator will iterate over exactly one network, the containing network.
54-
func (r *Reader) NetworksWithin(network *net.IPNet) *Networks {
69+
func (r *Reader) NetworksWithin(network *net.IPNet, options ...NetworksOption) *Networks {
5570
if r.Metadata.IPVersion == 4 && network.IP.To4() == nil {
5671
return &Networks{
5772
err: fmt.Errorf(
@@ -61,25 +76,33 @@ func (r *Reader) NetworksWithin(network *net.IPNet) *Networks {
6176
}
6277
}
6378

79+
networks := &Networks{reader: r}
80+
for _, option := range options {
81+
option(networks)
82+
}
83+
6484
ip := network.IP
6585
prefixLength, _ := network.Mask.Size()
6686

6787
if r.Metadata.IPVersion == 6 && len(ip) == net.IPv4len {
68-
ip = net.IP.To16(ip)
88+
if networks.skipAliasedNetworks {
89+
ip = net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ip[0], ip[1], ip[2], ip[3]}
90+
} else {
91+
ip = ip.To16()
92+
}
6993
prefixLength += 96
7094
}
7195

7296
pointer, bit := r.traverseTree(ip, 0, uint(prefixLength))
73-
return &Networks{
74-
reader: r,
75-
nodes: []netNode{
76-
{
77-
ip: ip,
78-
bit: uint(bit),
79-
pointer: pointer,
80-
},
97+
networks.nodes = []netNode{
98+
{
99+
ip: ip,
100+
bit: uint(bit),
101+
pointer: pointer,
81102
},
82103
}
104+
105+
return networks
83106
}
84107

85108
// Next prepares the next network for reading with the Network method. It
@@ -94,6 +117,13 @@ func (n *Networks) Next() bool {
94117
n.nodes = n.nodes[:len(n.nodes)-1]
95118

96119
for node.pointer != n.reader.Metadata.NodeCount {
120+
// This skips IPv4 aliases without hardcoding the networks that the writer
121+
// currently aliases.
122+
if n.skipAliasedNetworks && n.reader.ipv4Start != 0 &&
123+
node.pointer == n.reader.ipv4Start && !isInIPv4Subtree(node.ip) {
124+
break
125+
}
126+
97127
if node.pointer > n.reader.Metadata.NodeCount {
98128
n.lastNode = node
99129
return true
@@ -135,13 +165,39 @@ func (n *Networks) Network(result interface{}) (*net.IPNet, error) {
135165
return nil, err
136166
}
137167

168+
ip := n.lastNode.ip
169+
prefixLength := int(n.lastNode.bit)
170+
171+
// We do this because uses of SkipAliasedNetworks expect the IPv4 networks
172+
// to be returned as IPv4 networks. If we are not skipping aliased
173+
// networks, then the user will get IPv4 networks from the ::FFFF:0:0/96
174+
// network as Go automatically converts those.
175+
if n.skipAliasedNetworks && isInIPv4Subtree(ip) {
176+
ip = ip[12:]
177+
prefixLength -= 96
178+
}
179+
138180
return &net.IPNet{
139-
IP: n.lastNode.ip,
140-
Mask: net.CIDRMask(int(n.lastNode.bit), len(n.lastNode.ip)*8),
181+
IP: ip,
182+
Mask: net.CIDRMask(prefixLength, len(ip)*8),
141183
}, nil
142184
}
143185

144186
// Err returns an error, if any, that was encountered during iteration.
145187
func (n *Networks) Err() error {
146188
return n.err
147189
}
190+
191+
// isInIPv4Subtree returns true if the IP is an IPv6 address in the database's
192+
// IPv4 subtree.
193+
func isInIPv4Subtree(ip net.IP) bool {
194+
if len(ip) != 16 {
195+
return false
196+
}
197+
for i := 0; i < 12; i++ {
198+
if ip[i] != 0 {
199+
return false
200+
}
201+
}
202+
return true
203+
}

traverse_test.go

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ type networkTest struct {
5353
Network string
5454
Database string
5555
Expected []string
56+
Options []NetworksOption
5657
}
5758

5859
var tests = []networkTest{
@@ -106,6 +107,7 @@ var tests = []networkTest{
106107
Expected: []string{
107108
"::1:ffff:ffff/128",
108109
},
110+
Options: []NetworksOption{SkipAliasedNetworks},
109111
},
110112
{
111113
Network: "::/0",
@@ -117,6 +119,7 @@ var tests = []networkTest{
117119
"::2:0:50/125",
118120
"::2:0:58/127",
119121
},
122+
Options: []NetworksOption{SkipAliasedNetworks},
120123
},
121124
{
122125
Network: "::2:0:40/123",
@@ -126,6 +129,7 @@ var tests = []networkTest{
126129
"::2:0:50/125",
127130
"::2:0:58/127",
128131
},
132+
Options: []NetworksOption{SkipAliasedNetworks},
129133
},
130134
{
131135
Network: "0:0:0:0:0:ffff:ffff:ff00/120",
@@ -145,11 +149,70 @@ var tests = []networkTest{
145149
},
146150
},
147151
{
148-
Network: "1.1.1.16/28",
152+
Network: "0.0.0.0/0",
149153
Database: "mixed",
150154
Expected: []string{
155+
"1.1.1.1/32",
156+
"1.1.1.2/31",
157+
"1.1.1.4/30",
158+
"1.1.1.8/29",
151159
"1.1.1.16/28",
160+
"1.1.1.32/32",
161+
},
162+
Options: []NetworksOption{SkipAliasedNetworks},
163+
},
164+
{
165+
Network: "::/0",
166+
Database: "mixed",
167+
Expected: []string{
168+
"::101:101/128",
169+
"::101:102/127",
170+
"::101:104/126",
171+
"::101:108/125",
172+
"::101:110/124",
173+
"::101:120/128",
174+
"::1:ffff:ffff/128",
175+
"::2:0:0/122",
176+
"::2:0:40/124",
177+
"::2:0:50/125",
178+
"::2:0:58/127",
179+
"1.1.1.1/32",
180+
"1.1.1.2/31",
181+
"1.1.1.4/30",
182+
"1.1.1.8/29",
183+
"1.1.1.16/28",
184+
"1.1.1.32/32",
185+
"2001:0:101:101::/64",
186+
"2001:0:101:102::/63",
187+
"2001:0:101:104::/62",
188+
"2001:0:101:108::/61",
189+
"2001:0:101:110::/60",
190+
"2001:0:101:120::/64",
191+
"2002:101:101::/48",
192+
"2002:101:102::/47",
193+
"2002:101:104::/46",
194+
"2002:101:108::/45",
195+
"2002:101:110::/44",
196+
"2002:101:120::/48",
197+
},
198+
},
199+
{
200+
Network: "::/0",
201+
Database: "mixed",
202+
Expected: []string{
203+
"1.1.1.1/32",
204+
"1.1.1.2/31",
205+
"1.1.1.4/30",
206+
"1.1.1.8/29",
207+
"1.1.1.16/28",
208+
"1.1.1.32/32",
209+
"::1:ffff:ffff/128",
210+
"::2:0:0/122",
211+
"::2:0:40/124",
212+
"::2:0:50/125",
213+
"::2:0:58/127",
152214
},
215+
Options: []NetworksOption{SkipAliasedNetworks},
153216
},
154217
{
155218
Network: "1.1.1.16/28",
@@ -176,7 +239,7 @@ func TestNetworksWithin(t *testing.T) {
176239

177240
_, network, err := net.ParseCIDR(v.Network)
178241
assert.Nil(t, err)
179-
n := reader.NetworksWithin(network)
242+
n := reader.NetworksWithin(network, v.Options...)
180243
var innerIPs []string
181244

182245
for n.Next() {

0 commit comments

Comments
 (0)