summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDave Henderson <dhenderson@gmail.com>2023-02-05 02:11:28 +0000
committerGitHub <noreply@github.com>2023-02-05 02:11:28 +0000
commit08d70cf321ffede08205594ae4e7633f944647da (patch)
tree33461cb75f2d63e91be1d8a7ec5a9e72ce962f12
parentaf3e81ac52f93f07aea644b6397dbca97e5d30aa (diff)
parentedf224ccf7c66498b2d9743fcfb29bf4c0960931 (diff)
Merge pull request #1615 from hairyhenderson/deprecate-netaddr-funcs
Replace netaddr-based IP parsing funcs with new netip/netipx-based ones
-rw-r--r--docs-src/content/functions/net.yml73
-rw-r--r--docs/content/functions/net.md123
-rw-r--r--funcs/net.go144
-rw-r--r--funcs/net_test.go124
-rw-r--r--go.mod4
-rw-r--r--go.sum7
-rw-r--r--internal/cidr/cidr.go138
-rw-r--r--internal/cidr/cidr_test.go203
-rw-r--r--internal/tests/integration/net_test.go2
9 files changed, 732 insertions, 86 deletions
diff --git a/docs-src/content/functions/net.yml b/docs-src/content/functions/net.yml
index 67b7664a..45a9b1ec 100644
--- a/docs-src/content/functions/net.yml
+++ b/docs-src/content/functions/net.yml
@@ -126,7 +126,27 @@ funcs:
[
"v=spf1 -all"
]
+ - name: net.ParseAddr
+ description: |
+ Parse the given string as an IP address (a
+ [`netip.Addr`](https://pkg.go.dev/net/netip#Addr)).
+
+ Any of `netip.Addr`'s methods may be called on the resulting value. See
+ [the docs](https://pkg.go.dev/net/netip#Addr) for details.
+ pipeline: true
+ arguments:
+ - name: addr
+ required: true
+ description: The IP string to parse. It must be either an IPv4 or IPv6 address.
+ examples:
+ - |
+ $ gomplate -i '{{ (net.ParseAddr "192.168.0.1").IsPrivate }}'
+ true
+ $ gomplate -i '{{ $ip := net.ParseAddr (net.LookupIP "example.com") -}}
+ {{ $ip.Prefix 12 }}'
+ 93.176.0.0/12
- name: net.ParseIP
+ deprecated: Use [`net.ParseAddr`](#net-parseaddr) instead.
description: |
Parse the given string as an IP address (a `netaddr.IP` from the
[`inet.af/netaddr`](https://pkg.go.dev/inet.af/netaddr) package).
@@ -145,7 +165,31 @@ funcs:
$ gomplate -i '{{ $ip := net.ParseIP (net.LookupIP "example.com") -}}
{{ $ip.Prefix 12 }}'
93.176.0.0/12
+ - name: net.ParsePrefix
+ description: |
+ Parse the given string as an IP address prefix (CIDR) representing an IP
+ network (a [`netip.Prefix`](https://pkg.go.dev/net/netip#Prefix)).
+
+ The string can be in the form `"192.168.1.0/24"` or `"2001::db8::/32"`,
+ the CIDR notations defined in [RFC 4632][] and [RFC 4291][].
+
+ Any of `netip.Prefix`'s methods may be called on the resulting value. See
+ [the docs](https://pkg.go.dev/net/netip#Prefix) for details.
+ pipeline: true
+ arguments:
+ - name: prefix
+ required: true
+ description: The IP address prefix to parse. It must represent either an IPv4 or IPv6 prefix, containing a `/`.
+ examples:
+ - |
+ $ gomplate -i '{{ (net.ParsePrefix "192.168.0.0/24").Range }}'
+ 192.168.0.0-192.168.0.255
+ $ gomplate -i '{{ $ip := net.ParseAddr (net.LookupIP "example.com") -}}
+ {{ $net := net.ParsePrefix "93.184.0.0/16" -}}
+ {{ $net.Contains $ip }}'
+ true
- name: net.ParseIPPrefix
+ deprecated: Use [`net.ParsePrefix`](#net-parseprefix) instead.
description: |
Parse the given string as an IP address prefix (CIDR) representing an IP
network (a `netaddr.IPPrefix` from the
@@ -172,7 +216,36 @@ funcs:
$ gomplate -i '{{ $net := net.ParseIPPrefix "93.184.0.0/12" -}}
{{ $net.Range }}'
93.176.0.0-93.191.255.255
+ - name: net.ParseRange
+ experimental: true
+ description: |
+ Parse the given string as an inclusive range of IP addresses from the same
+ address family (a [`netipx.IPRange`](https://pkg.go.dev/go4.org/netipx#IPRange)
+ from the [`go4.org/netipx`](https://pkg.go.dev/go4.org/netipx) module).
+
+ The string must contain a hyphen (`-`).
+
+ Any of `netipx.IPRange`'s methods may be called on the resulting value.
+ See [the docs](https://pkg.go.dev/go4.org/netipx#IPRange) for details.
+
+ Note that this function is experimental for now, because it uses a
+ [third-party module](https://pkg.go.dev/go4.org/netipx) which may be
+ brought into the standard library in the future, which may require
+ breaking changes to this function.
+ pipeline: true
+ arguments:
+ - name: iprange
+ required: true
+ description: The IP address range to parse. It must represent either an IPv4 or IPv6 range, containing a `-`.
+ examples:
+ - |
+ $ gomplate -i '{{ (net.ParseRange "192.168.0.0-192.168.0.255").To }}'
+ 192.168.0.255
+ $ gomplate -i '{{ $range := net.ParseRange "1.2.3.0-1.2.3.233" -}}
+ {{ $range.Prefixes }}'
+ [1.2.3.0/25 1.2.3.128/26 1.2.3.192/27 1.2.3.224/29 1.2.3.232/31]
- name: net.ParseIPRange
+ deprecated: Use [`net.ParseRange`](#net-parserange) instead.
description: |
Parse the given string as an inclusive range of IP addresses from the same
address family (a `netaddr.IPRange` from the [`inet.af/netaddr`][] package).
diff --git a/docs/content/functions/net.md b/docs/content/functions/net.md
index ca0d8da1..371c5be4 100644
--- a/docs/content/functions/net.md
+++ b/docs/content/functions/net.md
@@ -213,7 +213,41 @@ $ gomplate -i '{{net.LookupTXT "example.com" | data.ToJSONPretty " " }}'
]
```
-## `net.ParseIP`
+## `net.ParseAddr`
+
+Parse the given string as an IP address (a
+[`netip.Addr`](https://pkg.go.dev/net/netip#Addr)).
+
+Any of `netip.Addr`'s methods may be called on the resulting value. See
+[the docs](https://pkg.go.dev/net/netip#Addr) for details.
+
+### Usage
+
+```go
+net.ParseAddr addr
+```
+```go
+addr | net.ParseAddr
+```
+
+### Arguments
+
+| name | description |
+|------|-------------|
+| `addr` | _(required)_ The IP string to parse. It must be either an IPv4 or IPv6 address. |
+
+### Examples
+
+```console
+$ gomplate -i '{{ (net.ParseAddr "192.168.0.1").IsPrivate }}'
+true
+$ gomplate -i '{{ $ip := net.ParseAddr (net.LookupIP "example.com") -}}
+ {{ $ip.Prefix 12 }}'
+93.176.0.0/12
+```
+
+## `net.ParseIP` _(deprecated)_
+**Deprecation Notice:** Use [`net.ParseAddr`](#net-parseaddr) instead.
Parse the given string as an IP address (a `netaddr.IP` from the
[`inet.af/netaddr`](https://pkg.go.dev/inet.af/netaddr) package).
@@ -246,7 +280,45 @@ $ gomplate -i '{{ $ip := net.ParseIP (net.LookupIP "example.com") -}}
93.176.0.0/12
```
-## `net.ParseIPPrefix`
+## `net.ParsePrefix`
+
+Parse the given string as an IP address prefix (CIDR) representing an IP
+network (a [`netip.Prefix`](https://pkg.go.dev/net/netip#Prefix)).
+
+The string can be in the form `"192.168.1.0/24"` or `"2001::db8::/32"`,
+the CIDR notations defined in [RFC 4632][] and [RFC 4291][].
+
+Any of `netip.Prefix`'s methods may be called on the resulting value. See
+[the docs](https://pkg.go.dev/net/netip#Prefix) for details.
+
+### Usage
+
+```go
+net.ParsePrefix prefix
+```
+```go
+prefix | net.ParsePrefix
+```
+
+### Arguments
+
+| name | description |
+|------|-------------|
+| `prefix` | _(required)_ The IP address prefix to parse. It must represent either an IPv4 or IPv6 prefix, containing a `/`. |
+
+### Examples
+
+```console
+$ gomplate -i '{{ (net.ParsePrefix "192.168.0.0/24").Range }}'
+192.168.0.0-192.168.0.255
+$ gomplate -i '{{ $ip := net.ParseAddr (net.LookupIP "example.com") -}}
+ {{ $net := net.ParsePrefix "93.184.0.0/16" -}}
+ {{ $net.Contains $ip }}'
+true
+```
+
+## `net.ParseIPPrefix` _(deprecated)_
+**Deprecation Notice:** Use [`net.ParsePrefix`](#net-parseprefix) instead.
Parse the given string as an IP address prefix (CIDR) representing an IP
network (a `netaddr.IPPrefix` from the
@@ -287,7 +359,52 @@ $ gomplate -i '{{ $net := net.ParseIPPrefix "93.184.0.0/12" -}}
93.176.0.0-93.191.255.255
```
-## `net.ParseIPRange`
+## `net.ParseRange` _(experimental)_
+**Experimental:** This function is [_experimental_][experimental] and may be enabled with the [`--experimental`][experimental] flag.
+
+[experimental]: ../config/#experimental
+
+Parse the given string as an inclusive range of IP addresses from the same
+address family (a [`netipx.IPRange`](https://pkg.go.dev/go4.org/netipx#IPRange)
+from the [`go4.org/netipx`](https://pkg.go.dev/go4.org/netipx) module).
+
+The string must contain a hyphen (`-`).
+
+Any of `netipx.IPRange`'s methods may be called on the resulting value.
+See [the docs](https://pkg.go.dev/go4.org/netipx#IPRange) for details.
+
+Note that this function is experimental for now, because it uses a
+[third-party module](https://pkg.go.dev/go4.org/netipx) which may be
+brought into the standard library in the future, which may require
+breaking changes to this function.
+
+### Usage
+
+```go
+net.ParseRange iprange
+```
+```go
+iprange | net.ParseRange
+```
+
+### Arguments
+
+| name | description |
+|------|-------------|
+| `iprange` | _(required)_ The IP address range to parse. It must represent either an IPv4 or IPv6 range, containing a `-`. |
+
+### Examples
+
+```console
+$ gomplate -i '{{ (net.ParseRange "192.168.0.0-192.168.0.255").To }}'
+192.168.0.255
+$ gomplate -i '{{ $range := net.ParseRange "1.2.3.0-1.2.3.233" -}}
+ {{ $range.Prefixes }}'
+[1.2.3.0/25 1.2.3.128/26 1.2.3.192/27 1.2.3.224/29 1.2.3.232/31]
+```
+
+## `net.ParseIPRange` _(deprecated)_
+**Deprecation Notice:** Use [`net.ParseRange`](#net-parserange) instead.
Parse the given string as an inclusive range of IP addresses from the same
address family (a `netaddr.IPRange` from the [`inet.af/netaddr`][] package).
diff --git a/funcs/net.go b/funcs/net.go
index 0d87b98a..ab692cc1 100644
--- a/funcs/net.go
+++ b/funcs/net.go
@@ -2,14 +2,16 @@ package funcs
import (
"context"
+ "fmt"
"math/big"
stdnet "net"
"net/netip"
- "github.com/apparentlymart/go-cidr/cidr"
"github.com/hairyhenderson/gomplate/v3/conv"
+ "github.com/hairyhenderson/gomplate/v3/internal/cidr"
+ "github.com/hairyhenderson/gomplate/v3/internal/deprecated"
"github.com/hairyhenderson/gomplate/v3/net"
- "github.com/pkg/errors"
+ "go4.org/netipx"
"inet.af/netaddr"
)
@@ -73,46 +75,76 @@ func (f NetFuncs) LookupTXT(name interface{}) ([]string, error) {
}
// ParseIP -
-func (f NetFuncs) ParseIP(ip interface{}) (netaddr.IP, error) {
+//
+// Deprecated: use [ParseAddr] instead
+func (f *NetFuncs) ParseIP(ip interface{}) (netaddr.IP, error) {
+ deprecated.WarnDeprecated(f.ctx, "net.ParseIP is deprecated - use net.ParseAddr instead")
return netaddr.ParseIP(conv.ToString(ip))
}
// ParseIPPrefix -
-func (f NetFuncs) ParseIPPrefix(ipprefix interface{}) (netaddr.IPPrefix, error) {
+//
+// Deprecated: use [ParsePrefix] instead
+func (f *NetFuncs) ParseIPPrefix(ipprefix interface{}) (netaddr.IPPrefix, error) {
+ deprecated.WarnDeprecated(f.ctx, "net.ParseIPPrefix is deprecated - use net.ParsePrefix instead")
return netaddr.ParseIPPrefix(conv.ToString(ipprefix))
}
// ParseIPRange -
-func (f NetFuncs) ParseIPRange(iprange interface{}) (netaddr.IPRange, error) {
+//
+// Deprecated: use [ParseRange] instead
+func (f *NetFuncs) ParseIPRange(iprange interface{}) (netaddr.IPRange, error) {
+ deprecated.WarnDeprecated(f.ctx, "net.ParseIPRange is deprecated - use net.ParseRange instead")
return netaddr.ParseIPRange(conv.ToString(iprange))
}
-func (f NetFuncs) parseStdnetIPNet(prefix interface{}) (*stdnet.IPNet, error) {
- switch p := prefix.(type) {
- case *stdnet.IPNet:
- return p, nil
- case netaddr.IPPrefix:
- return p.Masked().IPNet(), nil
- case netip.Prefix:
- net := &stdnet.IPNet{
- IP: p.Masked().Addr().AsSlice(),
- Mask: stdnet.CIDRMask(p.Bits(), p.Addr().BitLen()),
- }
- return net, nil
- default:
- _, network, err := stdnet.ParseCIDR(conv.ToString(prefix))
- return network, err
- }
+// ParseAddr -
+func (f NetFuncs) ParseAddr(ip interface{}) (netip.Addr, error) {
+ return netip.ParseAddr(conv.ToString(ip))
}
+// ParsePrefix -
+func (f NetFuncs) ParsePrefix(ipprefix interface{}) (netip.Prefix, error) {
+ return netip.ParsePrefix(conv.ToString(ipprefix))
+}
+
+// ParseRange -
+//
+// Experimental: this API may change in the future
+func (f NetFuncs) ParseRange(iprange interface{}) (netipx.IPRange, error) {
+ return netipx.ParseIPRange(conv.ToString(iprange))
+}
+
+// func (f *NetFuncs) parseStdnetIPNet(prefix interface{}) (*stdnet.IPNet, error) {
+// switch p := prefix.(type) {
+// case *stdnet.IPNet:
+// return p, nil
+// case netaddr.IPPrefix:
+// deprecated.WarnDeprecated(f.ctx,
+// "support for netaddr.IPPrefix is deprecated - use net.ParsePrefix to produce a netip.Prefix instead")
+// return p.Masked().IPNet(), nil
+// case netip.Prefix:
+// net := &stdnet.IPNet{
+// IP: p.Masked().Addr().AsSlice(),
+// Mask: stdnet.CIDRMask(p.Bits(), p.Addr().BitLen()),
+// }
+// return net, nil
+// default:
+// _, network, err := stdnet.ParseCIDR(conv.ToString(prefix))
+// return network, err
+// }
+// }
+
// TODO: look at using this instead of parseStdnetIPNet
//
//nolint:unused
-func (f NetFuncs) parseNetipPrefix(prefix interface{}) (netip.Prefix, error) {
+func (f *NetFuncs) parseNetipPrefix(prefix interface{}) (netip.Prefix, error) {
switch p := prefix.(type) {
case *stdnet.IPNet:
return f.ipPrefixFromIPNet(p), nil
case netaddr.IPPrefix:
+ deprecated.WarnDeprecated(f.ctx,
+ "support for netaddr.IPPrefix is deprecated - use net.ParsePrefix to produce a netip.Prefix instead")
return f.ipPrefixFromIPNet(p.Masked().IPNet()), nil
case netip.Prefix:
return p, nil
@@ -121,10 +153,10 @@ func (f NetFuncs) parseNetipPrefix(prefix interface{}) (netip.Prefix, error) {
}
}
-func (f NetFuncs) ipFromNetIP(n stdnet.IP) netip.Addr {
- ip, _ := netip.AddrFromSlice(n)
- return ip
-}
+// func (f NetFuncs) ipFromNetIP(n stdnet.IP) netip.Addr {
+// ip, _ := netip.AddrFromSlice(n)
+// return ip
+// }
func (f NetFuncs) ipPrefixFromIPNet(n *stdnet.IPNet) netip.Prefix {
ip, _ := netip.AddrFromSlice(n.IP)
@@ -134,52 +166,62 @@ func (f NetFuncs) ipPrefixFromIPNet(n *stdnet.IPNet) netip.Prefix {
// CIDRHost -
// Experimental!
-func (f NetFuncs) CIDRHost(hostnum interface{}, prefix interface{}) (netip.Addr, error) {
+func (f *NetFuncs) CIDRHost(hostnum interface{}, prefix interface{}) (netip.Addr, error) {
if err := checkExperimental(f.ctx); err != nil {
return netip.Addr{}, err
}
- network, err := f.parseStdnetIPNet(prefix)
+ network, err := f.parseNetipPrefix(prefix)
if err != nil {
return netip.Addr{}, err
}
ip, err := cidr.HostBig(network, big.NewInt(conv.ToInt64(hostnum)))
- return f.ipFromNetIP(ip), err
+ return ip, err
}
// CIDRNetmask -
// Experimental!
-func (f NetFuncs) CIDRNetmask(prefix interface{}) (netip.Addr, error) {
+func (f *NetFuncs) CIDRNetmask(prefix interface{}) (netip.Addr, error) {
if err := checkExperimental(f.ctx); err != nil {
return netip.Addr{}, err
}
- network, err := f.parseStdnetIPNet(prefix)
+ p, err := f.parseNetipPrefix(prefix)
if err != nil {
return netip.Addr{}, err
}
- netmask := stdnet.IP(network.Mask)
- return f.ipFromNetIP(netmask), nil
+ // fill an appropriately sized byte slice with as many 1s as prefix bits
+ b := make([]byte, p.Addr().BitLen()/8)
+ for i := 0; i < p.Bits(); i++ {
+ b[i/8] |= 1 << uint(7-i%8)
+ }
+
+ m, ok := netip.AddrFromSlice(b)
+ if !ok {
+ return netip.Addr{}, fmt.Errorf("invalid netmask")
+ }
+
+ return m, nil
}
// CIDRSubnets -
// Experimental!
-func (f NetFuncs) CIDRSubnets(newbits interface{}, prefix interface{}) ([]netip.Prefix, error) {
+func (f *NetFuncs) CIDRSubnets(newbits interface{}, prefix interface{}) ([]netip.Prefix, error) {
if err := checkExperimental(f.ctx); err != nil {
return nil, err
}
- network, err := f.parseStdnetIPNet(prefix)
+ network, err := f.parseNetipPrefix(prefix)
if err != nil {
return nil, err
}
nBits := conv.ToInt(newbits)
if nBits < 1 {
- return nil, errors.Errorf("must extend prefix by at least one bit")
+ return nil, fmt.Errorf("must extend prefix by at least one bit")
}
maxNetNum := int64(1 << uint64(nBits))
@@ -189,7 +231,7 @@ func (f NetFuncs) CIDRSubnets(newbits interface{}, prefix interface{}) ([]netip.
if err != nil {
return nil, err
}
- retValues[i] = f.ipPrefixFromIPNet(subnet)
+ retValues[i] = subnet
}
return retValues, nil
@@ -197,22 +239,22 @@ func (f NetFuncs) CIDRSubnets(newbits interface{}, prefix interface{}) ([]netip.
// CIDRSubnetSizes -
// Experimental!
-func (f NetFuncs) CIDRSubnetSizes(args ...interface{}) ([]netip.Prefix, error) {
+func (f *NetFuncs) CIDRSubnetSizes(args ...interface{}) ([]netip.Prefix, error) {
if err := checkExperimental(f.ctx); err != nil {
return nil, err
}
if len(args) < 2 {
- return nil, errors.Errorf("wrong number of args: want 2 or more, got %d", len(args))
+ return nil, fmt.Errorf("wrong number of args: want 2 or more, got %d", len(args))
}
- network, err := f.parseStdnetIPNet(args[len(args)-1])
+ network, err := f.parseNetipPrefix(args[len(args)-1])
if err != nil {
return nil, err
}
newbits := conv.ToInts(args[:len(args)-1]...)
- startPrefixLen, _ := network.Mask.Size()
+ startPrefixLen := network.Bits()
firstLength := newbits[0]
firstLength += startPrefixLen
@@ -222,38 +264,38 @@ func (f NetFuncs) CIDRSubnetSizes(args ...interface{}) ([]netip.Prefix, error) {
for i, length := range newbits {
if length < 1 {
- return nil, errors.Errorf("must extend prefix by at least one bit")
+ return nil, fmt.Errorf("must extend prefix by at least one bit")
}
// For portability with 32-bit systems where the subnet number
// will be a 32-bit int, we only allow extension of 32 bits in
// one call even if we're running on a 64-bit machine.
// (Of course, this is significant only for IPv6.)
if length > 32 {
- return nil, errors.Errorf("may not extend prefix by more than 32 bits")
+ return nil, fmt.Errorf("may not extend prefix by more than 32 bits")
}
length += startPrefixLen
- if length > (len(network.IP) * 8) {
+ if length > network.Addr().BitLen() {
protocol := "IP"
- switch len(network.IP) {
- case stdnet.IPv4len:
+ switch {
+ case network.Addr().Is4():
protocol = "IPv4"
- case stdnet.IPv6len:
+ case network.Addr().Is6():
protocol = "IPv6"
}
- return nil, errors.Errorf("would extend prefix to %d bits, which is too long for an %s address", length, protocol)
+ return nil, fmt.Errorf("would extend prefix to %d bits, which is too long for an %s address", length, protocol)
}
next, rollover := cidr.NextSubnet(current, length)
- if rollover || !network.Contains(next.IP) {
+ if rollover || !network.Contains(next.Addr()) {
// If we run out of suffix bits in the base CIDR prefix then
// NextSubnet will start incrementing the prefix bits, which
// we don't allow because it would then allocate addresses
// outside of the caller's given prefix.
- return nil, errors.Errorf("not enough remaining address space for a subnet with a prefix of %d bits after %s", length, current.String())
+ return nil, fmt.Errorf("not enough remaining address space for a subnet with a prefix of %d bits after %s", length, current.String())
}
current = next
- retValues[i] = f.ipPrefixFromIPNet(current)
+ retValues[i] = current
}
return retValues, nil
diff --git a/funcs/net_test.go b/funcs/net_test.go
index e8914fa6..642a356c 100644
--- a/funcs/net_test.go
+++ b/funcs/net_test.go
@@ -9,6 +9,7 @@ import (
"github.com/hairyhenderson/gomplate/v3/internal/config"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
"inet.af/netaddr"
)
@@ -39,12 +40,12 @@ func TestNetLookupIP(t *testing.T) {
func TestParseIP(t *testing.T) {
t.Parallel()
- n := NetFuncs{}
+ n := testNetNS()
_, err := n.ParseIP("not an IP")
assert.Error(t, err)
ip, err := n.ParseIP("2001:470:20::2")
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.Equal(t, netaddr.IPFrom16([16]byte{
0x20, 0x01, 0x04, 0x70,
0, 0x20, 0, 0,
@@ -56,7 +57,7 @@ func TestParseIP(t *testing.T) {
func TestParseIPPrefix(t *testing.T) {
t.Parallel()
- n := NetFuncs{}
+ n := testNetNS()
_, err := n.ParseIPPrefix("not an IP")
assert.Error(t, err)
@@ -64,14 +65,14 @@ func TestParseIPPrefix(t *testing.T) {
assert.Error(t, err)
ipprefix, err := n.ParseIPPrefix("192.168.0.2/28")
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.Equal(t, "192.168.0.0/28", ipprefix.Masked().String())
}
func TestParseIPRange(t *testing.T) {
t.Parallel()
- n := NetFuncs{}
+ n := testNetNS()
_, err := n.ParseIPRange("not an IP")
assert.Error(t, err)
@@ -79,10 +80,56 @@ func TestParseIPRange(t *testing.T) {
assert.Error(t, err)
iprange, err := n.ParseIPRange("192.168.0.2-192.168.23.255")
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.Equal(t, "192.168.0.2-192.168.23.255", iprange.String())
}
+func TestParseAddr(t *testing.T) {
+ t.Parallel()
+
+ n := testNetNS()
+ _, err := n.ParseAddr("not an IP")
+ assert.Error(t, err)
+
+ ip, err := n.ParseAddr("2001:470:20::2")
+ require.NoError(t, err)
+ assert.Equal(t, netip.AddrFrom16([16]byte{
+ 0x20, 0x01, 0x04, 0x70,
+ 0, 0x20, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0x02,
+ }), ip)
+}
+
+func TestParsePrefix(t *testing.T) {
+ t.Parallel()
+
+ n := testNetNS()
+ _, err := n.ParsePrefix("not an IP")
+ assert.Error(t, err)
+
+ _, err = n.ParsePrefix("1.1.1.1")
+ assert.Error(t, err)
+
+ ipprefix, err := n.ParsePrefix("192.168.0.2/28")
+ require.NoError(t, err)
+ assert.Equal(t, "192.168.0.0/28", ipprefix.Masked().String())
+}
+
+func TestParseRange(t *testing.T) {
+ t.Parallel()
+
+ n := testNetNS()
+ _, err := n.ParseRange("not an IP")
+ assert.Error(t, err)
+
+ _, err = n.ParseRange("1.1.1.1")
+ assert.Error(t, err)
+
+ iprange, err := n.ParseRange("192.168.0.2-192.168.23.255")
+ require.NoError(t, err)
+ assert.Equal(t, "192.168.0.2-192.168.23.255", iprange.String())
+}
func testNetNS() *NetFuncs {
return &NetFuncs{ctx: config.SetExperimental(context.Background())}
}
@@ -94,48 +141,48 @@ func TestCIDRHost(t *testing.T) {
_, netIP, _ := stdnet.ParseCIDR("10.12.127.0/20")
ip, err := n.CIDRHost(16, netIP)
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.Equal(t, "10.12.112.16", ip.String())
ip, err = n.CIDRHost(268, netIP)
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.Equal(t, "10.12.113.12", ip.String())
_, netIP, _ = stdnet.ParseCIDR("fd00:fd12:3456:7890:00a2::/72")
ip, err = n.CIDRHost(34, netIP)
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.Equal(t, "fd00:fd12:3456:7890::22", ip.String())
// inet.af/netaddr.IPPrefix
ipPrefix, _ := n.ParseIPPrefix("10.12.127.0/20")
ip, err = n.CIDRHost(16, ipPrefix)
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.Equal(t, "10.12.112.16", ip.String())
ip, err = n.CIDRHost(268, ipPrefix)
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.Equal(t, "10.12.113.12", ip.String())
ipPrefix, _ = n.ParseIPPrefix("fd00:fd12:3456:7890:00a2::/72")
ip, err = n.CIDRHost(34, ipPrefix)
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.Equal(t, "fd00:fd12:3456:7890::22", ip.String())
// net/netip.Prefix
prefix := netip.MustParsePrefix("10.12.127.0/20")
ip, err = n.CIDRHost(16, prefix)
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.Equal(t, "10.12.112.16", ip.String())
ip, err = n.CIDRHost(268, prefix)
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.Equal(t, "10.12.113.12", ip.String())
prefix = netip.MustParsePrefix("fd00:fd12:3456:7890:00a2::/72")
ip, err = n.CIDRHost(34, prefix)
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.Equal(t, "fd00:fd12:3456:7890::22", ip.String())
}
@@ -143,11 +190,11 @@ func TestCIDRNetmask(t *testing.T) {
n := testNetNS()
ip, err := n.CIDRNetmask("10.0.0.0/12")
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.Equal(t, "255.240.0.0", ip.String())
ip, err = n.CIDRNetmask("fd00:fd12:3456:7890:00a2::/72")
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.Equal(t, "ffff:ffff:ffff:ffff:ff00::", ip.String())
}
@@ -156,11 +203,11 @@ func TestCIDRSubnets(t *testing.T) {
network := netip.MustParsePrefix("10.0.0.0/16")
subnets, err := n.CIDRSubnets(-1, network)
- assert.Nil(t, subnets)
assert.Error(t, err)
+ assert.Nil(t, subnets)
subnets, err = n.CIDRSubnets(2, network)
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.Len(t, subnets, 4)
assert.Equal(t, "10.0.0.0/18", subnets[0].String())
assert.Equal(t, "10.0.64.0/18", subnets[1].String())
@@ -170,25 +217,50 @@ func TestCIDRSubnets(t *testing.T) {
func TestCIDRSubnetSizes(t *testing.T) {
n := testNetNS()
- network := netip.MustParsePrefix("10.1.0.0/16")
- subnets, err := n.CIDRSubnetSizes(network)
- assert.Nil(t, subnets)
+ subnets, err := n.CIDRSubnetSizes(netip.MustParsePrefix("10.1.0.0/16"))
assert.Error(t, err)
-
- subnets, err = n.CIDRSubnetSizes(32, network)
assert.Nil(t, subnets)
+
+ subnets, err = n.CIDRSubnetSizes(32, netip.MustParsePrefix("10.1.0.0/16"))
assert.Error(t, err)
+ assert.Nil(t, subnets)
- subnets, err = n.CIDRSubnetSizes(-1, network)
+ subnets, err = n.CIDRSubnetSizes(127, netip.MustParsePrefix("ffff::/48"))
+ assert.Error(t, err)
assert.Nil(t, subnets)
+
+ subnets, err = n.CIDRSubnetSizes(-1, netip.MustParsePrefix("10.1.0.0/16"))
assert.Error(t, err)
+ assert.Nil(t, subnets)
+
+ network := netip.MustParsePrefix("8000::/1")
+ subnets, err = n.CIDRSubnetSizes(1, 2, 2, network)
+ require.NoError(t, err)
+ assert.Len(t, subnets, 3)
+ assert.Equal(t, "8000::/2", subnets[0].String())
+ assert.Equal(t, "c000::/3", subnets[1].String())
+ assert.Equal(t, "e000::/3", subnets[2].String())
+ network = netip.MustParsePrefix("10.1.0.0/16")
subnets, err = n.CIDRSubnetSizes(4, 4, 8, 4, network)
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.Len(t, subnets, 4)
assert.Equal(t, "10.1.0.0/20", subnets[0].String())
assert.Equal(t, "10.1.16.0/20", subnets[1].String())
assert.Equal(t, "10.1.32.0/24", subnets[2].String())
assert.Equal(t, "10.1.48.0/20", subnets[3].String())
+
+ network = netip.MustParsePrefix("2016:1234:5678:9abc:ffff:ffff:ffff:cafe/64")
+ subnets, err = n.CIDRSubnetSizes(2, 2, 3, 3, 6, 6, 8, 10, network)
+ require.NoError(t, err)
+ assert.Len(t, subnets, 8)
+ assert.Equal(t, "2016:1234:5678:9abc::/66", subnets[0].String())
+ assert.Equal(t, "2016:1234:5678:9abc:4000::/66", subnets[1].String())
+ assert.Equal(t, "2016:1234:5678:9abc:8000::/67", subnets[2].String())
+ assert.Equal(t, "2016:1234:5678:9abc:a000::/67", subnets[3].String())
+ assert.Equal(t, "2016:1234:5678:9abc:c000::/70", subnets[4].String())
+ assert.Equal(t, "2016:1234:5678:9abc:c400::/70", subnets[5].String())
+ assert.Equal(t, "2016:1234:5678:9abc:c800::/72", subnets[6].String())
+ assert.Equal(t, "2016:1234:5678:9abc:c900::/74", subnets[7].String())
}
diff --git a/go.mod b/go.mod
index ef4c2abe..bf57ac5c 100644
--- a/go.mod
+++ b/go.mod
@@ -5,7 +5,6 @@ go 1.18
require (
github.com/Masterminds/goutils v1.1.1
github.com/Shopify/ejson v1.3.3
- github.com/apparentlymart/go-cidr v1.1.0
github.com/aws/aws-sdk-go v1.44.189
github.com/docker/libkv v0.2.2-0.20180912205406-458977154600
github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa
@@ -28,6 +27,7 @@ require (
github.com/stretchr/testify v1.8.1
github.com/ugorji/go/codec v1.2.8
github.com/zealic/xignore v0.3.3
+ go4.org/netipx v0.0.0-20230125063823-8449b0a6169f
gocloud.dev v0.28.0
golang.org/x/crypto v0.5.0
golang.org/x/sys v0.4.0
@@ -132,7 +132,7 @@ require (
go.opencensus.io v0.24.0 // indirect
go.uber.org/atomic v1.10.0 // indirect
go4.org/intern v0.0.0-20220617035311-6925f38cc365 // indirect
- go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 // indirect
+ go4.org/unsafe/assume-no-moving-gc v0.0.0-20230204201903-c31fa085b70e // indirect
golang.org/x/net v0.5.0 // indirect
golang.org/x/oauth2 v0.2.0 // indirect
golang.org/x/time v0.2.0 // indirect
diff --git a/go.sum b/go.sum
index 85a68236..355958dc 100644
--- a/go.sum
+++ b/go.sum
@@ -506,8 +506,6 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuW
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
-github.com/apparentlymart/go-cidr v1.1.0 h1:2mAhrMoF+nhXqxTzSZMUzDHkLjmIHC+Zzn4tdgBZjnU=
-github.com/apparentlymart/go-cidr v1.1.0/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
@@ -1990,9 +1988,12 @@ go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
go4.org/intern v0.0.0-20211027215823-ae77deb06f29/go.mod h1:cS2ma+47FKrLPdXFpr7CuxiTW3eyJbWew4qx0qtQWDA=
go4.org/intern v0.0.0-20220617035311-6925f38cc365 h1:t9hFvR102YlOqU0fQn1wgwhNvSbHGBbbJxX9JKfU3l0=
go4.org/intern v0.0.0-20220617035311-6925f38cc365/go.mod h1:WXRv3p7T6gzt0CcJm43AAKdKVZmcQbwwC7EwquU5BZU=
+go4.org/netipx v0.0.0-20230125063823-8449b0a6169f h1:ketMxHg+vWm3yccyYiq+uK8D3fRmna2Fcj+awpQp84s=
+go4.org/netipx v0.0.0-20230125063823-8449b0a6169f/go.mod h1:tgPU4N2u9RByaTN3NC2p9xOzyFpte4jYwsIIRF7XlSc=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
-go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 h1:FyBZqvoA/jbNzuAWLQE2kG820zMAkcilx6BMjGbL/E4=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
+go4.org/unsafe/assume-no-moving-gc v0.0.0-20230204201903-c31fa085b70e h1:AY/D6WBvaYJLmXK9VTIAX0tokDhrkkqdvIUwOU2nxio=
+go4.org/unsafe/assume-no-moving-gc v0.0.0-20230204201903-c31fa085b70e/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
gocloud.dev v0.28.0 h1:PjL1f9zu8epY1pFCIHdrQnJRZzRcDyAr18hNTkXIKlQ=
gocloud.dev v0.28.0/go.mod h1:nzSs01FpRYyIb/OqXLNNa+NMPZG9CdTUY/pGLgSpIN0=
golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
diff --git a/internal/cidr/cidr.go b/internal/cidr/cidr.go
new file mode 100644
index 00000000..7ac4b053
--- /dev/null
+++ b/internal/cidr/cidr.go
@@ -0,0 +1,138 @@
+package cidr
+
+import (
+ "fmt"
+ "math/big"
+ "net/netip"
+
+ "go4.org/netipx"
+)
+
+// taken from github.com/apparentelymart/go-cidr/ and modified to use the net/netip
+// package instead of the stdlib net package - this will hopefully be merged back
+// upstream at some point
+
+// SubnetBig takes a parent CIDR range and creates a subnet within it with the
+// given number of additional prefix bits and the given network number. It
+// differs from Subnet in that it takes a *big.Int for the num, instead of an int.
+//
+// For example, 10.3.0.0/16, extended by 8 bits, with a network number of 5,
+// becomes 10.3.5.0/24 .
+func SubnetBig(base netip.Prefix, newBits int, num *big.Int) (netip.Prefix, error) {
+ parentLen := base.Bits()
+ addrLen := base.Addr().BitLen()
+
+ newPrefixLen := parentLen + newBits
+
+ if newPrefixLen > addrLen {
+ return netip.Prefix{}, fmt.Errorf("insufficient address space to extend prefix of %d by %d", parentLen, newBits)
+ }
+
+ maxNetNum := uint64(1<<uint64(newBits)) - 1
+ if num.Uint64() > maxNetNum {
+ return netip.Prefix{}, fmt.Errorf("prefix extension of %d does not accommodate a subnet numbered %d", newBits, num)
+ }
+
+ prefix := netip.PrefixFrom(insertNumIntoIP(base.Masked().Addr(), num, newPrefixLen), newPrefixLen)
+
+ return prefix, nil
+}
+
+// HostBig takes a parent CIDR range and turns it into a host IP address with
+// the given host number. It differs from Host in that it takes a *big.Int for
+// the num, instead of an int.
+//
+// For example, 10.3.0.0/16 with a host number of 2 gives 10.3.0.2.
+func HostBig(base netip.Prefix, num *big.Int) (netip.Addr, error) {
+ parentLen := base.Bits()
+ addrLen := base.Addr().BitLen()
+
+ hostLen := addrLen - parentLen
+
+ maxHostNum := big.NewInt(int64(1))
+ maxHostNum.Lsh(maxHostNum, uint(hostLen))
+ maxHostNum.Sub(maxHostNum, big.NewInt(1))
+
+ numUint64 := big.NewInt(int64(num.Uint64()))
+ if num.Cmp(big.NewInt(0)) == -1 {
+ numUint64.Neg(num)
+ numUint64.Sub(numUint64, big.NewInt(int64(1)))
+ num.Sub(maxHostNum, numUint64)
+ }
+
+ if numUint64.Cmp(maxHostNum) == 1 {
+ return netip.Addr{}, fmt.Errorf("prefix of %d does not accommodate a host numbered %d", parentLen, num)
+ }
+
+ return insertNumIntoIP(base.Masked().Addr(), num, addrLen), nil
+}
+
+func ipToInt(ip netip.Addr) (*big.Int, int) {
+ val := &big.Int{}
+ val.SetBytes(ip.AsSlice())
+
+ return val, ip.BitLen()
+}
+
+func intToIP(ipInt *big.Int, bits int) netip.Addr {
+ ipBytes := ipInt.Bytes()
+ ret := make([]byte, bits/8)
+ // Pack our IP bytes into the end of the return array,
+ // since big.Int.Bytes() removes front zero padding.
+ for i := 1; i <= len(ipBytes); i++ {
+ ret[len(ret)-i] = ipBytes[len(ipBytes)-i]
+ }
+
+ addr, ok := netip.AddrFromSlice(ret)
+ if !ok {
+ panic("invalid IP address")
+ }
+
+ return addr
+}
+
+func insertNumIntoIP(ip netip.Addr, bigNum *big.Int, prefixLen int) netip.Addr {
+ ipInt, totalBits := ipToInt(ip)
+ bigNum.Lsh(bigNum, uint(totalBits-prefixLen))
+ ipInt.Or(ipInt, bigNum)
+ return intToIP(ipInt, totalBits)
+}
+
+// PreviousSubnet returns the subnet of the desired mask in the IP space
+// just lower than the start of Prefix provided. If the IP space rolls over
+// then the second return value is true
+func PreviousSubnet(network netip.Prefix, prefixLen int) (netip.Prefix, bool) {
+ previousIP := network.Masked().Addr().Prev()
+
+ previous, err := previousIP.Prefix(prefixLen)
+ if err != nil {
+ return netip.Prefix{}, false
+ }
+ if !previous.IsValid() {
+ return previous, true
+ }
+
+ return previous.Masked(), false
+}
+
+// NextSubnet returns the next available subnet of the desired mask size
+// starting for the maximum IP of the offset subnet
+// If the IP exceeds the maxium IP then the second return value is true
+func NextSubnet(network netip.Prefix, prefixLen int) (netip.Prefix, bool) {
+ currentLast := netipx.PrefixLastIP(network)
+
+ currentSubnet, err := currentLast.Prefix(prefixLen)
+ if err != nil {
+ return netip.Prefix{}, false
+ }
+
+ last := netipx.PrefixLastIP(currentSubnet).Next()
+ next, err := last.Prefix(prefixLen)
+ if err != nil {
+ return netip.Prefix{}, false
+ }
+ if !last.IsValid() {
+ return next, true
+ }
+ return next, false
+}
diff --git a/internal/cidr/cidr_test.go b/internal/cidr/cidr_test.go
new file mode 100644
index 00000000..1b8cc90a
--- /dev/null
+++ b/internal/cidr/cidr_test.go
@@ -0,0 +1,203 @@
+package cidr
+
+import (
+ "fmt"
+ "math/big"
+ "net/netip"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestSubnetBig(t *testing.T) {
+ cases := []struct {
+ base string
+ num *big.Int
+ out string
+ bits int
+ err bool
+ }{
+ {
+ base: "192.168.2.0/20",
+ bits: 4,
+ num: big.NewInt(int64(6)),
+ out: "192.168.6.0/24",
+ },
+ {
+ base: "192.168.2.0/20",
+ bits: 4,
+ num: big.NewInt(int64(0)),
+ out: "192.168.0.0/24",
+ },
+ {
+ base: "192.168.0.0/31",
+ bits: 1,
+ num: big.NewInt(int64(1)),
+ out: "192.168.0.1/32",
+ },
+ {
+ base: "192.168.0.0/21",
+ bits: 4,
+ num: big.NewInt(int64(7)),
+ out: "192.168.3.128/25",
+ },
+ {
+ base: "fe80::/48",
+ bits: 16,
+ num: big.NewInt(int64(6)),
+ out: "fe80:0:0:6::/64",
+ },
+ {
+ base: "fe80::/48",
+ bits: 33,
+ num: big.NewInt(int64(6)),
+ out: "fe80::3:0:0:0/81",
+ },
+ {
+ base: "fe80::/49",
+ bits: 16,
+ num: big.NewInt(int64(7)),
+ out: "fe80:0:0:3:8000::/65",
+ },
+ {
+ base: "192.168.2.0/31",
+ bits: 2,
+ num: big.NewInt(int64(0)),
+ err: true, // not enough bits to expand into
+ },
+ {
+ base: "fe80::/126",
+ bits: 4,
+ num: big.NewInt(int64(0)),
+ err: true, // not enough bits to expand into
+ },
+ {
+ base: "192.168.2.0/24",
+ bits: 4,
+ num: big.NewInt(int64(16)),
+ err: true, // can't fit 16 into 4 bits
+ },
+ }
+
+ for _, testCase := range cases {
+ t.Run(fmt.Sprintf("SubnetBig(%#v,%#v,%#v)", testCase.base, testCase.bits, testCase.num), func(t *testing.T) {
+ base := netip.MustParsePrefix(testCase.base)
+
+ subnet, err := SubnetBig(base, testCase.bits, testCase.num)
+ if testCase.err {
+ assert.Error(t, err)
+ } else {
+ assert.NoError(t, err)
+ assert.Equal(t, testCase.out, subnet.String())
+ }
+ })
+ }
+}
+
+func TestHostBig(t *testing.T) {
+ cases := []struct {
+ prefix string
+ num *big.Int
+ out string
+ err bool
+ }{
+ {
+ prefix: "192.168.2.0/20",
+ num: big.NewInt(int64(6)),
+ out: "192.168.0.6",
+ },
+ {
+ prefix: "192.168.0.0/20",
+ num: big.NewInt(int64(257)),
+ out: "192.168.1.1",
+ },
+ {
+ prefix: "2001:db8::/32",
+ num: big.NewInt(int64(1)),
+ out: "2001:db8::1",
+ },
+ {
+ prefix: "192.168.1.0/24",
+ num: big.NewInt(int64(256)),
+ err: true, // only 0-255 will fit in 8 bits
+ },
+ {
+ prefix: "192.168.0.0/30",
+ num: big.NewInt(int64(-3)),
+ out: "192.168.0.1", // 4 address (0-3) in 2 bits; 3rd from end = 1
+ },
+ {
+ prefix: "192.168.0.0/30",
+ num: big.NewInt(int64(-4)),
+ out: "192.168.0.0", // 4 address (0-3) in 2 bits; 4th from end = 0
+ },
+ {
+ prefix: "192.168.0.0/30",
+ num: big.NewInt(int64(-5)),
+ err: true, // 4 address (0-3) in 2 bits; cannot accommodate 5
+ },
+ {
+ prefix: "fd9d:bc11:4020::/64",
+ num: big.NewInt(int64(2)),
+ out: "fd9d:bc11:4020::2",
+ },
+ {
+ prefix: "fd9d:bc11:4020::/64",
+ num: big.NewInt(int64(-2)),
+ out: "fd9d:bc11:4020:0:ffff:ffff:ffff:fffe",
+ },
+ }
+
+ for _, testCase := range cases {
+ t.Run(fmt.Sprintf("HostBig(%v,%v)", testCase.prefix, testCase.num), func(t *testing.T) {
+ network := netip.MustParsePrefix(testCase.prefix)
+
+ gotIP, err := HostBig(network, testCase.num)
+ if testCase.err {
+ assert.Error(t, err)
+ } else {
+ assert.NoError(t, err)
+ assert.Equal(t, testCase.out, gotIP.String())
+ }
+ })
+ }
+}
+
+func TestPreviousNextSubnet(t *testing.T) {
+ testCases := []struct {
+ next, prev string
+ overflow bool
+ }{
+ {"10.0.0.0/24", "9.255.255.0/24", false},
+ {"100.0.0.0/26", "99.255.255.192/26", false},
+ {"0.0.0.0/26", "255.255.255.192/26", true},
+ {"2001:db8:e000::/36", "2001:db8:d000::/36", false},
+ {"::/64", "ffff:ffff:ffff:ffff::/64", true},
+ }
+ for _, tc := range testCases {
+ c1 := netip.MustParsePrefix(tc.next)
+ c2 := netip.MustParsePrefix(tc.prev)
+ mask := c1.Bits()
+
+ p1, rollback := PreviousSubnet(c1, mask)
+ if tc.overflow {
+ assert.True(t, rollback)
+ continue
+ }
+
+ assert.Equal(t, c2.String(), p1.String())
+ }
+
+ for _, tc := range testCases {
+ c1 := netip.MustParsePrefix(tc.next)
+ c2 := netip.MustParsePrefix(tc.prev)
+ mask := c1.Bits()
+
+ n1, rollover := NextSubnet(c2, mask)
+ if tc.overflow {
+ assert.True(t, rollover)
+ continue
+ }
+ assert.Equal(t, c1, n1)
+ }
+}
diff --git a/internal/tests/integration/net_test.go b/internal/tests/integration/net_test.go
index 35218eaf..a0ff4e6f 100644
--- a/internal/tests/integration/net_test.go
+++ b/internal/tests/integration/net_test.go
@@ -9,7 +9,7 @@ func TestNet_LookupIP(t *testing.T) {
}
func TestNet_CIDRHost(t *testing.T) {
- inOutTestExperimental(t, `{{ net.ParseIPPrefix "10.12.127.0/20" | net.CIDRHost 16 }}`, "10.12.112.16")
+ inOutTestExperimental(t, `{{ net.ParsePrefix "10.12.127.0/20" | net.CIDRHost 16 }}`, "10.12.112.16")
inOutTestExperimental(t, `{{ "10.12.127.0/20" | net.CIDRHost 16 }}`, "10.12.112.16")
inOutTestExperimental(t, `{{ net.CIDRHost 268 "10.12.127.0/20" }}`, "10.12.113.12")
inOutTestExperimental(t, `{{ net.CIDRHost 34 "fd00:fd12:3456:7890:00a2::/72" }}`, "fd00:fd12:3456:7890::22")