1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204
|
package maxminddb
import (
"fmt"
"net"
)
// Internal structure used to keep track of nodes we still need to visit.
type netNode struct {
ip net.IP
bit uint
pointer uint
}
// Networks represents a set of subnets that we are iterating over.
type Networks struct {
err error
reader *Reader
nodes []netNode
lastNode netNode
skipAliasedNetworks bool
}
var (
allIPv4 = &net.IPNet{IP: make(net.IP, 4), Mask: net.CIDRMask(0, 32)}
allIPv6 = &net.IPNet{IP: make(net.IP, 16), Mask: net.CIDRMask(0, 128)}
)
// NetworksOption are options for Networks and NetworksWithin.
type NetworksOption func(*Networks)
// SkipAliasedNetworks is an option for Networks and NetworksWithin that
// makes them not iterate over aliases of the IPv4 subtree in an IPv6
// database, e.g., ::ffff:0:0/96, 2001::/32, and 2002::/16.
//
// You most likely want to set this. The only reason it isn't the default
// behavior is to provide backwards compatibility to existing users.
func SkipAliasedNetworks(networks *Networks) {
networks.skipAliasedNetworks = true
}
// 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. To only iterate over the IPv4 networks once, use the
// SkipAliasedNetworks option.
func (r *Reader) Networks(options ...NetworksOption) *Networks {
var networks *Networks
if r.Metadata.IPVersion == 6 {
networks = r.NetworksWithin(allIPv6, options...)
} else {
networks = r.NetworksWithin(allIPv4, options...)
}
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. To only iterate over the IPv4 networks once, use the
// SkipAliasedNetworks option.
//
// 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, options ...NetworksOption) *Networks {
if r.Metadata.IPVersion == 4 && network.IP.To4() == nil {
return &Networks{
err: fmt.Errorf(
"error getting networks with '%s': you attempted to use an IPv6 network in an IPv4-only database",
network.String(),
),
}
}
networks := &Networks{reader: r}
for _, option := range options {
option(networks)
}
ip := network.IP
prefixLength, _ := network.Mask.Size()
if r.Metadata.IPVersion == 6 && len(ip) == net.IPv4len {
if networks.skipAliasedNetworks {
ip = net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ip[0], ip[1], ip[2], ip[3]}
} else {
ip = ip.To16()
}
prefixLength += 96
}
pointer, bit := r.traverseTree(ip, 0, uint(prefixLength))
networks.nodes = []netNode{
{
ip: ip,
bit: uint(bit),
pointer: pointer,
},
}
return networks
}
// Next prepares the next network for reading with the Network method. It
// returns true if there is another network to be processed and false if there
// are no more networks or if there is an error.
func (n *Networks) Next() bool {
if n.err != nil {
return false
}
for len(n.nodes) > 0 {
node := n.nodes[len(n.nodes)-1]
n.nodes = n.nodes[:len(n.nodes)-1]
for node.pointer != n.reader.Metadata.NodeCount {
// This skips IPv4 aliases without hardcoding the networks that the writer
// currently aliases.
if n.skipAliasedNetworks && n.reader.ipv4Start != 0 &&
node.pointer == n.reader.ipv4Start && !isInIPv4Subtree(node.ip) {
break
}
if node.pointer > n.reader.Metadata.NodeCount {
n.lastNode = node
return true
}
ipRight := make(net.IP, len(node.ip))
copy(ipRight, node.ip)
if len(ipRight) <= int(node.bit>>3) {
n.err = newInvalidDatabaseError(
"invalid search tree at %v/%v", ipRight, node.bit)
return false
}
ipRight[node.bit>>3] |= 1 << (7 - (node.bit % 8))
offset := node.pointer * n.reader.nodeOffsetMult
rightPointer := n.reader.nodeReader.readRight(offset)
node.bit++
n.nodes = append(n.nodes, netNode{
pointer: rightPointer,
ip: ipRight,
bit: node.bit,
})
node.pointer = n.reader.nodeReader.readLeft(offset)
}
}
return false
}
// Network returns the current network or an error if there is a problem
// decoding the data for the network. It takes a pointer to a result value to
// decode the network's data into.
func (n *Networks) Network(result any) (*net.IPNet, error) {
if n.err != nil {
return nil, n.err
}
if err := n.reader.retrieveData(n.lastNode.pointer, result); err != nil {
return nil, err
}
ip := n.lastNode.ip
prefixLength := int(n.lastNode.bit)
// We do this because uses of SkipAliasedNetworks expect the IPv4 networks
// to be returned as IPv4 networks. If we are not skipping aliased
// networks, then the user will get IPv4 networks from the ::FFFF:0:0/96
// network as Go automatically converts those.
if n.skipAliasedNetworks && isInIPv4Subtree(ip) {
ip = ip[12:]
prefixLength -= 96
}
return &net.IPNet{
IP: ip,
Mask: net.CIDRMask(prefixLength, len(ip)*8),
}, nil
}
// Err returns an error, if any, that was encountered during iteration.
func (n *Networks) Err() error {
return n.err
}
// isInIPv4Subtree returns true if the IP is an IPv6 address in the database's
// IPv4 subtree.
func isInIPv4Subtree(ip net.IP) bool {
if len(ip) != 16 {
return false
}
for i := 0; i < 12; i++ {
if ip[i] != 0 {
return false
}
}
return true
}
|