Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: c-robinson/iplib
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v2.0.2
Choose a base ref
...
head repository: c-robinson/iplib
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v2.0.3
Choose a head ref
  • 5 commits
  • 5 files changed
  • 1 contributor

Commits on Dec 31, 2023

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    182edbf View commit details

Commits on Jan 2, 2024

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    5947051 View commit details

Commits on Jan 3, 2024

  1. update license

    c-robinson committed Jan 3, 2024

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    9c00c6f View commit details

Commits on Jan 6, 2024

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    47b95ce View commit details
  2. Nets betweening and tests

     - Fix NewNetBetween to be more predictable in cases where the resu;t is a
       single-address network
    
     - add AllNetsBetween which returns all the netblocks between two supplied
       addresses
    
     - improve the test cases to eliminate duplicatation and provide equivalent
       coverage for v4 and v6
    c-robinson committed Jan 6, 2024

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    00a2086 View commit details
Showing with 152 additions and 56 deletions.
  1. +1 −1 LICENSE
  2. +65 −8 net.go
  3. +19 −5 net6.go
  4. +9 −6 net6_test.go
  5. +58 −36 net_test.go
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
The MIT License (MIT)

Copyright (c) 2021 Chad Robinson
Copyright (c) 2024 Chad Robinson

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
73 changes: 65 additions & 8 deletions net.go
Original file line number Diff line number Diff line change
@@ -17,6 +17,7 @@ type Net interface {
Mask() net.IPMask
String() string
Version() int
finalAddress() (net.IP, int)
}

// NewNet returns a new Net object containing ip at the specified masklen. In
@@ -30,21 +31,65 @@ func NewNet(ip net.IP, masklen int) Net {
return NewNet4(ip, masklen)
}

// AllNetsBetween takes two net.IPs as input and will return a slice of
// netblocks spanning the range between them, inclusively, even if it must
// return one or more single-address netblocks to do so
func AllNetsBetween(a, b net.IP) ([]Net, error) {
var lastNet Net
if EffectiveVersion(a) == IP4Version {
lastNet = Net4{}
} else {
lastNet = Net6{}
}

var nets []Net

for {
ipnet, tf, err := NewNetBetween(a, b)
if err != nil {
return nets, err
}

nets = append(nets, ipnet)
if tf {
return nets, nil
}

finalIP, _ := ipnet.finalAddress()
if CompareIPs(finalIP, b) > 0 {
return nets, nil
}

if lastNet.IP() == nil {
lastNet = ipnet
} else if CompareIPs(ipnet.IP(), lastNet.IP()) > 0 {
lastNet = ipnet
} else {
return nets, nil
}

a = NextIP(finalIP)
if CompareIPs(a, b) > 0 {
return nets, nil
}
}
}

// NewNetBetween takes two net.IP's as input and will return the largest
// netblock that can fit between them (exclusive of the IP's themselves).
// netblock that can fit between them inclusive of at least the first address.
// If there is an exact fit it will set a boolean to true, otherwise the bool
// will be false. If no fit can be found (probably because a >= b) an
// ErrNoValidRange will be returned.
// ErrNoValidRange will be returned
func NewNetBetween(a, b net.IP) (Net, bool, error) {
if CompareIPs(a, b) != -1 {
if CompareIPs(a, b) == 1 { // != -1 {
return nil, false, ErrNoValidRange
}

if EffectiveVersion(a) != EffectiveVersion(b) {
return nil, false, ErrNoValidRange
}

return fitNetworkBetween(NextIP(a), PreviousIP(b), 1)
return fitNetworkBetween(a, b, 1)
}

// ByNet implements sort.Interface for iplib.Net based on the
@@ -100,13 +145,25 @@ func ParseCIDR(s string) (net.IP, Net, error) {
func fitNetworkBetween(a, b net.IP, mask int) (Net, bool, error) {
xnet := NewNet(a, mask)

va := CompareIPs(xnet.FirstAddress(), a)
vb := CompareIPs(xnet.LastAddress(), b)
if va >= 0 && vb < 0 {
return xnet, false, nil
if CompareIPs(a, b) > 0 {
return NewNet(b, maskMax(b)), true, nil
}

finalIP, _ := xnet.finalAddress()
va := CompareIPs(xnet.IP(), a)
vb := CompareIPs(finalIP, b)
if va == 0 && vb == 0 {
return xnet, true, nil
}
if va >= 0 && vb <= 0 {
return xnet, false, nil
}
return fitNetworkBetween(a, b, mask+1)
}

func maskMax(ip net.IP) int {
if EffectiveVersion(ip) == 4 {
return 32
}
return 128
}
24 changes: 19 additions & 5 deletions net6.go
Original file line number Diff line number Diff line change
@@ -111,6 +111,10 @@ func (n Net6) Count() uint128.Uint128 {
moreOnes, _ := n.Hostmask.Size()
exp -= moreOnes

if exp == 128 {
return uint128.Max
}

z := uint128.New(2, 0)
return z.Lsh(uint(exp - 1))
}
@@ -183,11 +187,7 @@ func (n Net6) FirstAddress() net.IP {

// LastAddress returns the last usable address for the represented network
func (n Net6) LastAddress() net.IP {
xip := make([]byte, len(n.IPNet.IP))
wc := n.wildcard()
for pos := range n.IP() {
xip[pos] = n.IP()[pos] + (wc[pos] - n.Hostmask[pos])
}
xip, _ := n.finalAddress()
return xip
}

@@ -336,6 +336,20 @@ func (n Net6) contained(ip net.IP) bool {
return true
}

// finalAddress is here mostly because it also exists in Net4, but there
// are cases where it is valuable to have a method for both Net
// implementations that returns the last address in the netblock
func (n Net6) finalAddress() (net.IP, int) {
xip := make([]byte, len(n.IPNet.IP))
ones, _ := n.Mask().Size()

wc := n.wildcard()
for pos := range n.IP() {
xip[pos] = n.IP()[pos] + (wc[pos] - n.Hostmask[pos])
}
return xip, ones
}

func (n Net6) wildcard() net.IPMask {
wc := make([]byte, len(n.Mask()))
for i, b := range n.Mask() {
15 changes: 9 additions & 6 deletions net6_test.go
Original file line number Diff line number Diff line change
@@ -4,8 +4,6 @@ import (
"net"
"sort"
"testing"

"lukechampine.com/uint128"
)

var NewNet6Tests = []struct {
@@ -220,7 +218,13 @@ var Net6Tests = []struct {
"2001:0db8::",
"::",
"ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
0, -1, 0, "340282366920938463463374607431768211456",
0, -1, 0, "340282366920938463463374607431768211455",
},
{
"::",
"::",
"ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
0, -1, 0, "340282366920938463463374607431768211455",
},
}

@@ -236,7 +240,6 @@ func TestNet6_Version(t *testing.T) {
func TestNet6_Count(t *testing.T) {
for i, tt := range Net6Tests {
ipn := NewNet6(net.ParseIP(tt.ip), tt.netmasklen, tt.hostmask)
ttcount, _ := uint128.FromString(tt.count)

if ipn.IPNet.IP == nil {
if tt.count != "0" {
@@ -245,8 +248,8 @@ func TestNet6_Count(t *testing.T) {
continue
}

if v := ttcount.Cmp(ipn.Count()); v != 0 {
t.Errorf("[%d] count: want %s got %d", i, tt.count, ipn.Count())
if tt.count != ipn.Count().String() {
t.Errorf("[%d] count: want %s got %s", i, tt.count, ipn.Count().String())
}
}
}
94 changes: 58 additions & 36 deletions net_test.go
Original file line number Diff line number Diff line change
@@ -42,71 +42,76 @@ func TestNewNet(t *testing.T) {
}

var NewNetBetweenTests = []struct {
start net.IP
end net.IP
xnet string
exact bool
err error
start net.IP
end net.IP
xnet string
exact bool
err error
netslen int
}{
{
net.ParseIP("192.168.0.255"), net.ParseIP("192.168.2.0"),
"192.168.1.0/24", false, nil,
},
{
{ // 0
net.ParseIP("192.168.0.255"), net.ParseIP("10.0.0.0"),
"", false, ErrNoValidRange,
"", false, ErrNoValidRange, 0,
},
{
net.ParseIP("192.168.0.255"), net.ParseIP("2001:db8:0:1::"),
"", false, ErrNoValidRange,
"", false, ErrNoValidRange, 0,
},
{
net.ParseIP("2001:db8:0:1::"), net.ParseIP("192.168.0.255"),
"", false, ErrNoValidRange,
"", false, ErrNoValidRange, 0,
},
{
net.ParseIP("192.168.0.255"), net.ParseIP("192.168.0.255"),
"", false, ErrNoValidRange,
net.ParseIP("2001:db8:0:1::"), net.ParseIP("2001:db8::"),
"", false, ErrNoValidRange, 0,
},
{
net.ParseIP("192.168.0.255"), net.ParseIP("192.168.1.1"),
"192.168.1.0/32", true, nil,
net.ParseIP("192.168.0.255"), net.ParseIP("192.168.0.255"),
"192.168.0.255/32", true, nil, 1,
},
{
net.ParseIP("192.168.1.0"), net.ParseIP("192.168.1.2"),
"192.168.1.1/32", true, nil,
{ // 5
net.ParseIP("2001:db8:0:1::"), net.ParseIP("2001:db8:0:1::"),
"2001:db8:0:1::/128", true, nil, 1,
},
{
net.ParseIP("192.168.0.255"), net.ParseIP("192.168.1.2"),
"192.168.1.0/31", true, nil,
net.ParseIP("192.168.1.0"), net.ParseIP("192.168.2.0"),
"192.168.1.0/24", false, nil, 2,
},
{
net.ParseIP("192.168.0.255"), net.ParseIP("192.168.1.3"),
"192.168.1.0/31", false, nil,
net.ParseIP("2001:db8:1::"), net.ParseIP("2001:db8:2::"),
"2001:db8:1::/48", false, nil, 2,
},
{
net.ParseIP("192.168.1.0"), net.ParseIP("192.168.1.3"),
"192.168.1.0/30", true, nil,
net.ParseIP("192.168.1.0"), net.ParseIP("192.168.1.255"),
"192.168.1.0/24", true, nil, 1,
},
{
net.ParseIP("192.168.0.255"), net.ParseIP("192.168.1.4"),
"192.168.1.0/30", false, nil,
net.ParseIP("2001:db8:1::"), net.ParseIP("2001:db8:1:ffff:ffff:ffff:ffff:ffff"),
"2001:db8:1::/48", true, nil, 1,
},
{ // 10
net.ParseIP("192.168.1.0"), net.ParseIP("192.168.1.1"),
"192.168.1.0/31", true, nil, 1,
},
{
net.ParseIP("192.168.0.255"), net.ParseIP("192.168.1.5"),
"192.168.1.0/30", false, nil,
net.ParseIP("2001:db8:1::"), net.ParseIP("2001:db8:1::1"),
"2001:db8:1::/127", true, nil, 1,
},
{
net.ParseIP("192.168.0.254"), net.ParseIP("192.168.2.0"),
"192.168.0.255/32", false, nil,
net.ParseIP("192.168.0.255"), net.ParseIP("192.168.1.2"),
"192.168.0.255/32", false, nil, 3,
},
{
net.ParseIP("192.168.0.255"), net.ParseIP("192.168.2.0"),
"192.168.1.0/24", false, nil,
net.ParseIP("2001:db8:0:ffff:ffff:ffff:ffff:ffff"), net.ParseIP("2001:db8:1::1"),
"2001:db8:0:ffff:ffff:ffff:ffff:ffff/128", false, nil, 2,
},
{
net.ParseIP("2001:db7:ffff:ffff:ffff:ffff:ffff:ffff"), net.ParseIP("2001:db8:0:1::"),
"2001:db8::/64", true, nil,
net.ParseIP("10.0.0.0"), net.ParseIP("255.0.0.0"),
"10.0.0.0/7", false, nil, 13,
},
{ // 15
net.ParseIP("2001:db8::"), net.ParseIP("2001:db8:ffff:ffff:ffff:ffff:ffff::"),
"2001:db8::/33", false, nil, 81,
},
}

@@ -129,6 +134,23 @@ func TestNewNetBetween(t *testing.T) {
}
}

func TestAllNetsBetween(t *testing.T) {
for i, tt := range NewNetBetweenTests {
//t.Logf("[%d] nets between %s and %s", i, tt.start, tt.end)
xnets, err := AllNetsBetween(tt.start, tt.end)
//t.Logf("[%d] got %+v, '%v'", i, xnets, err)
if e := compareErrors(err, tt.err); len(e) > 0 {
t.Errorf("[%d] expected error '%v', got '%v'", i, tt.err, err)
}
if tt.err == nil {
if len(xnets) != tt.netslen {
t.Logf("[%d] AllNetsBetween(%s, %s) [%+v]", i, tt.start, tt.end, xnets)
t.Errorf("[%d] expected %d networks, got %d", i, tt.netslen, len(xnets))
}
}
}
}

var ParseCIDRTests = []struct {
s string
xnet string