-
Notifications
You must be signed in to change notification settings - Fork 105
Add NetworksWithin #65
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
d1c282d
7abc676
c9e1da3
5797a3f
dfc02ff
30a393a
5e7ecc7
5800f39
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
@@ -1,6 +1,8 @@ | ||||
package maxminddb | ||||
|
||||
import "net" | ||||
import ( | ||||
"net" | ||||
) | ||||
|
||||
// Internal structure used to keep track of nodes we still need to visit. | ||||
type netNode struct { | ||||
|
@@ -17,22 +19,52 @@ type Networks struct { | |||
err error | ||||
} | ||||
|
||||
var allIPv4 = &net.IPNet{IP: make(net.IP, 4), Mask: net.CIDRMask(0, 32)} | ||||
var allIPv6 = &net.IPNet{IP: make(net.IP, 16), Mask: net.CIDRMask(0, 128)} | ||||
|
||||
// Networks returns an iterator that can be used to traverse all networks in | ||||
// the database. | ||||
// | ||||
// Please note that a MaxMind DB may map IPv4 networks into several locations | ||||
// in an IPv6 database. This iterator will iterate over all of these | ||||
// locations separately. | ||||
func (r *Reader) Networks() *Networks { | ||||
s := 4 | ||||
var networks *Networks | ||||
if r.Metadata.IPVersion == 6 { | ||||
s = 16 | ||||
networks = r.NetworksWithin(allIPv6) | ||||
} else { | ||||
networks = r.NetworksWithin(allIPv4) | ||||
} | ||||
|
||||
return networks | ||||
} | ||||
|
||||
// NetworksWithin returns an iterator that can be used to traverse all networks | ||||
// in the database which are contained in a given network. | ||||
// | ||||
// Please note that a MaxMind DB may map IPv4 networks into several locations | ||||
// in an IPv6 database. This iterator will iterate over all of these locations | ||||
// separately. | ||||
// | ||||
// If the provided network is contained within a network in the database, the | ||||
// iterator will iterate over exactly one network, the containing network. | ||||
func (r *Reader) NetworksWithin(network *net.IPNet) *Networks { | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This needs documentation. I think your earlier PR had documentation. Also, an example similar to this one for maxminddb-golang/example_test.go Line 56 in 7abc676
This would get included in the generated documentation. |
||||
ip := network.IP | ||||
prefixLength, _ := network.Mask.Size() | ||||
|
||||
if r.Metadata.IPVersion == 6 && len(ip) == net.IPv4len { | ||||
ip = net.IP.To16(ip) | ||||
prefixLength += 96 | ||||
} | ||||
|
||||
pointer, bit := r.traverseTree(ip, 0, uint(prefixLength)) | ||||
return &Networks{ | ||||
reader: r, | ||||
nodes: []netNode{ | ||||
{ | ||||
ip: make(net.IP, s), | ||||
ip: ip, | ||||
bit: uint(bit), | ||||
pointer: pointer, | ||||
}, | ||||
}, | ||||
} | ||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ package maxminddb | |
|
||
import ( | ||
"fmt" | ||
"net" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
|
@@ -46,3 +47,192 @@ func TestNetworksWithInvalidSearchTree(t *testing.T) { | |
assert.NotNil(t, n.Err(), "no error received when traversing an broken search tree") | ||
assert.Equal(t, n.Err().Error(), "invalid search tree at 128.128.128.128/32") | ||
} | ||
|
||
type networkTest struct { | ||
Network string | ||
Database string | ||
Expected []string | ||
} | ||
|
||
var tests = []networkTest{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks good. I notice that we don't have any tests for networks with no records. Could you add a test case for that? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added in 5800f39 |
||
networkTest{ | ||
Network: "0.0.0.0/0", | ||
Database: "ipv4", | ||
Expected: []string{ | ||
"1.1.1.1/32", | ||
"1.1.1.2/31", | ||
"1.1.1.4/30", | ||
"1.1.1.8/29", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it would be good to have a test that selects multiple records, but only a subset of the ones within an address space. For instance, |
||
"1.1.1.16/28", | ||
"1.1.1.32/32", | ||
}, | ||
}, | ||
networkTest{ | ||
Network: "1.1.1.1/30", | ||
Database: "ipv4", | ||
Expected: []string{ | ||
"1.1.1.1/32", | ||
"1.1.1.2/31", | ||
}, | ||
}, | ||
networkTest{ | ||
Network: "1.1.1.1/32", | ||
Database: "ipv4", | ||
Expected: []string{ | ||
"1.1.1.1/32", | ||
}, | ||
}, | ||
networkTest{ | ||
Network: "255.255.255.0/24", | ||
Database: "ipv4", | ||
Expected: []string(nil), | ||
}, | ||
networkTest{ | ||
Network: "1.1.1.1/32", | ||
Database: "mixed", | ||
Expected: []string{ | ||
"1.1.1.1/32", | ||
}, | ||
}, | ||
networkTest{ | ||
Network: "255.255.255.0/24", | ||
Database: "mixed", | ||
Expected: []string(nil), | ||
}, | ||
networkTest{ | ||
Network: "::1:ffff:ffff/128", | ||
Database: "ipv6", | ||
Expected: []string{ | ||
"::1:ffff:ffff/128", | ||
}, | ||
}, | ||
networkTest{ | ||
Network: "::/0", | ||
Database: "ipv6", | ||
Expected: []string{ | ||
"::1:ffff:ffff/128", | ||
"::2:0:0/122", | ||
"::2:0:40/124", | ||
"::2:0:50/125", | ||
"::2:0:58/127", | ||
}, | ||
}, | ||
networkTest{ | ||
Network: "::2:0:40/123", | ||
Database: "ipv6", | ||
Expected: []string{ | ||
"::2:0:40/124", | ||
"::2:0:50/125", | ||
"::2:0:58/127", | ||
}, | ||
}, | ||
networkTest{ | ||
Network: "0:0:0:0:0:ffff:ffff:ff00/120", | ||
Database: "ipv6", | ||
Expected: []string(nil), | ||
}, | ||
networkTest{ | ||
Network: "0.0.0.0/0", | ||
Database: "mixed", | ||
Expected: []string{ | ||
"1.1.1.1/32", | ||
"1.1.1.2/31", | ||
"1.1.1.4/30", | ||
"1.1.1.8/29", | ||
"1.1.1.16/28", | ||
"1.1.1.32/32", | ||
}, | ||
}, | ||
networkTest{ | ||
Network: "1.1.1.16/28", | ||
Database: "mixed", | ||
Expected: []string{ | ||
"1.1.1.16/28", | ||
}, | ||
}, | ||
networkTest{ | ||
Network: "::/0", | ||
Database: "ipv4", | ||
Expected: []string{ | ||
"101:101::/32", | ||
"101:102::/31", | ||
"101:104::/30", | ||
"101:108::/29", | ||
"101:110::/28", | ||
"101:120::/32", | ||
}, | ||
}, | ||
networkTest{ | ||
Network: "101:104::/30", | ||
Database: "ipv4", | ||
Expected: []string{ | ||
"101:104::/30", | ||
}, | ||
}, | ||
} | ||
|
||
func TestNetworksWithin(t *testing.T) { | ||
for _, v := range tests { | ||
for _, recordSize := range []uint{24, 28, 32} { | ||
fileName := testFile(fmt.Sprintf("MaxMind-DB-test-%s-%d.mmdb", v.Database, recordSize)) | ||
reader, err := Open(fileName) | ||
require.Nil(t, err, "unexpected error while opening database: %v", err) | ||
defer reader.Close() | ||
|
||
_, network, err := net.ParseCIDR(v.Network) | ||
assert.Nil(t, err) | ||
n := reader.NetworksWithin(network) | ||
var innerIPs []string | ||
|
||
for n.Next() { | ||
record := struct { | ||
IP string `maxminddb:"ip"` | ||
}{} | ||
network, err := n.Network(&record) | ||
assert.Nil(t, err) | ||
innerIPs = append(innerIPs, network.String()) | ||
} | ||
|
||
assert.Equal(t, v.Expected, innerIPs) | ||
assert.Nil(t, n.Err()) | ||
} | ||
} | ||
} | ||
|
||
var geoIPTests = []networkTest{ | ||
networkTest{ | ||
Network: "81.2.69.128/26", | ||
Database: "GeoIP2-Country-Test.mmdb", | ||
Expected: []string{ | ||
"81.2.69.142/31", | ||
"81.2.69.144/28", | ||
"81.2.69.160/27", | ||
}, | ||
}, | ||
} | ||
|
||
func TestGeoIPNetworksWithin(t *testing.T) { | ||
for _, v := range geoIPTests { | ||
fileName := testFile(v.Database) | ||
reader, err := Open(fileName) | ||
require.Nil(t, err, "unexpected error while opening database: %v", err) | ||
defer reader.Close() | ||
|
||
_, network, err := net.ParseCIDR(v.Network) | ||
assert.Nil(t, err) | ||
n := reader.NetworksWithin(network) | ||
var innerIPs []string | ||
|
||
for n.Next() { | ||
record := struct { | ||
IP string `maxminddb:"ip"` | ||
}{} | ||
network, err := n.Network(&record) | ||
assert.Nil(t, err) | ||
innerIPs = append(innerIPs, network.String()) | ||
} | ||
|
||
assert.Equal(t, v.Expected, innerIPs) | ||
assert.Nil(t, n.Err()) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A note clarifying what happens when the provided network is within a network in the database would be worthwhile, e.g., something like, "If the provided network is contained within a network in the database, the iterator will iterate over exactly one network, the containing network." I am not attached to the wording so feel free to make it clearer.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Works for me! Added in 5e7ecc7