Skip to content

Commit

Permalink
singlepointipam can be configured based on IP ranges
Browse files Browse the repository at this point in the history
Related issue: cmd-nse-remote-vlan/110

Signed-off-by: Lugossy Zoltan <[email protected]>
  • Loading branch information
zolug committed Nov 29, 2022
1 parent 1d79bf8 commit 25b5e6b
Show file tree
Hide file tree
Showing 6 changed files with 703 additions and 17 deletions.
97 changes: 97 additions & 0 deletions pkg/networkservice/ipam/singlepointipam/range.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Copyright (c) 2022 Nordix and its affiliates.
//
// SPDX-License-Identifier: Apache-2.0
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package singlepointipam

import (
"net"

"github.com/pkg/errors"
)

// IpamRange - range of IP addresses
type IpamRange struct {
RangeStart net.IP
RangeEnd net.IP
}

// IpamNet - IP network with optional IP ranges to limit address scope
type IpamNet struct {
Network *net.IPNet
Ranges []*IpamRange // optional: limit IpamNet by specifying valid range(s) within Network
}

// NewIpamNet - creates a new IpamNet
func NewIpamNet() *IpamNet {
return &IpamNet{
Ranges: []*IpamRange{},
}
}

// SetOrCheckNetwork - sets network if not yet set, otherwise checks if the two networks are equal.
func (ipn *IpamNet) SetOrCheckNetwork(network *net.IPNet) error {
if ipn.Network == nil {
ipn.Network = network
} else {
if !ipn.Network.IP.Equal(network.IP) {
return errors.Errorf("network mismatch: %v != %v", ipn.Network, network)
}
ones, bits := network.Mask.Size()
iones, ibits := ipn.Network.Mask.Size()
if ones != iones || bits != ibits {
return errors.Errorf("network mask mismatch: %v != %v", ipn.Network.Mask, network.Mask)
}
}
return nil
}

// AddRange - adds IpamRange to IpamNet.
// Does not check for overlap or if range already exists.
// Does not verify if ips are of the same version.
func (ipn *IpamNet) AddRange(firstip, lastip net.IP) error {
if ipn.Network == nil {
return errors.Errorf("network not set")
}

if !ipn.Network.Contains(firstip) || !ipn.Network.Contains(lastip) {
return errors.Errorf("%s-%s invalid range for CIDR %s", firstip, lastip, ipn.Network.String())
}

// based on the previous check both IPs are in the same network, verify that firstip precedes lastip in the range
fip16 := firstip.To16()
lip16 := lastip.To16()
Loop:
for i := 0; i < len(fip16); i += 2 {
mask := 0x8000 // binary 1000000000000000
f := int(fip16[i])<<8 + int(fip16[i+1])
l := int(lip16[i])<<8 + int(lip16[i+1])
for j := 0; j < 16; j++ {
if (f & mask) > (l & mask) {
return errors.Errorf("%s-%s invalid range start", firstip, lastip)
}
if (f & mask) < (l & mask) {
break Loop
}
mask >>= 1
}
}

if ipn.Ranges == nil {
ipn.Ranges = []*IpamRange{}
}
ipn.Ranges = append(ipn.Ranges, &IpamRange{RangeStart: firstip, RangeEnd: lastip})
return nil
}
292 changes: 292 additions & 0 deletions pkg/networkservice/ipam/singlepointipam/range_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,292 @@
// Copyright (c) 2022 Nordix and its affiliates.
//
// SPDX-License-Identifier: Apache-2.0
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package singlepointipam_test

import (
"net"
"testing"

"github.com/stretchr/testify/require"

"github.com/networkservicemesh/sdk/pkg/networkservice/ipam/singlepointipam"
)

func TestRangeNetIPv4(t *testing.T) {
ipamNet := singlepointipam.NewIpamNet()
require.NotNil(t, ipamNet)
require.NotNil(t, ipamNet.Ranges)
require.Empty(t, ipamNet.Ranges)

_, ipNet1, err := net.ParseCIDR("192.2.3.4/16")
require.NoError(t, err)

err = ipamNet.SetOrCheckNetwork(ipNet1)
require.NoError(t, err)

err = ipamNet.SetOrCheckNetwork(ipNet1)
require.NoError(t, err)

_, ipNet2, err := net.ParseCIDR("192.2.0.10/24")
require.NoError(t, err)

err = ipamNet.SetOrCheckNetwork(ipNet2)
require.Error(t, err)

_, ipNet3, err := net.ParseCIDR("192.2.10.20/24")
require.NoError(t, err)

err = ipamNet.SetOrCheckNetwork(ipNet3)
require.Error(t, err)

firstip := net.ParseIP("192.2.3.4")
require.NotNil(t, firstip)
lastip := net.ParseIP("192.2.3.5")
require.NotNil(t, lastip)
err = ipamNet.AddRange(firstip, lastip)
require.NoError(t, err)

firstip = net.ParseIP("192.2.0.155")
require.NotNil(t, firstip)
lastip = net.ParseIP("192.2.128.4")
require.NotNil(t, lastip)
err = ipamNet.AddRange(firstip, lastip)
require.NoError(t, err)
}

func TestRangeNetIPv6(t *testing.T) {
ipamNet := singlepointipam.NewIpamNet()
require.NotNil(t, ipamNet)
require.NotNil(t, ipamNet.Ranges)
require.Empty(t, ipamNet.Ranges)

_, ipNet1, err := net.ParseCIDR("100:100::1/64")
require.NoError(t, err)

err = ipamNet.SetOrCheckNetwork(ipNet1)
require.NoError(t, err)

err = ipamNet.SetOrCheckNetwork(ipNet1)
require.NoError(t, err)

_, ipNet2, err := net.ParseCIDR("100:100::1/48")
require.NoError(t, err)

err = ipamNet.SetOrCheckNetwork(ipNet2)
require.Error(t, err)

_, ipNet3, err := net.ParseCIDR("dead::beaf/64")
require.NoError(t, err)

err = ipamNet.SetOrCheckNetwork(ipNet3)
require.Error(t, err)

firstip := net.ParseIP("100:100::1")
require.NotNil(t, firstip)
lastip := net.ParseIP("100:100::2")
require.NotNil(t, lastip)
err = ipamNet.AddRange(firstip, lastip)
require.NoError(t, err)
require.NotEmpty(t, ipamNet.Ranges)
require.Equal(t, len(ipamNet.Ranges), 1)

firstip = net.ParseIP("100:100::d:1")
require.NotNil(t, firstip)
lastip = net.ParseIP("100:100::f:2")
require.NotNil(t, lastip)
err = ipamNet.AddRange(firstip, lastip)
require.NoError(t, err)
require.NotEmpty(t, ipamNet.Ranges)
require.Equal(t, len(ipamNet.Ranges), 2)

firstip = net.ParseIP("100:100:0:0::ff00")
require.NotNil(t, firstip)
lastip = net.ParseIP("100:100:0:0:1::1")
require.NotNil(t, lastip)
err = ipamNet.AddRange(firstip, lastip)
require.NoError(t, err)
require.NotEmpty(t, ipamNet.Ranges)
require.Equal(t, len(ipamNet.Ranges), 3)
}

func TestRangeIPv4(t *testing.T) {
ipamNet := singlepointipam.NewIpamNet()
require.NotNil(t, ipamNet)
require.Empty(t, ipamNet.Ranges)

firstip := net.ParseIP("192.2.3.4")
require.NotNil(t, firstip)
lastip := net.ParseIP("192.2.3.5")
require.NotNil(t, lastip)
err := ipamNet.AddRange(firstip, lastip)
require.Error(t, err)
require.Empty(t, ipamNet.Ranges)

_, ipNet1, err := net.ParseCIDR("192.2.0.0/16")
require.NoError(t, err)

err = ipamNet.SetOrCheckNetwork(ipNet1)
require.NoError(t, err)

err = ipamNet.AddRange(firstip, lastip)
require.NoError(t, err)

firstip = net.ParseIP("192.2.0.1")
require.NotNil(t, firstip)
err = ipamNet.AddRange(firstip, firstip)
require.NoError(t, err)

firstip = net.ParseIP("192.2.1.128")
require.NotNil(t, firstip)
lastip = net.ParseIP("192.2.64.1")
require.NotNil(t, lastip)
err = ipamNet.AddRange(firstip.To16(), lastip.To16())
require.NoError(t, err)

require.NotEmpty(t, ipamNet.Ranges)
require.Equal(t, len(ipamNet.Ranges), 3)
}

func TestInvalidRangeIPv4(t *testing.T) {
ipamNet := singlepointipam.NewIpamNet()
require.NotNil(t, ipamNet)

_, ipNet1, err := net.ParseCIDR("192.2.0.0/16")
require.NoError(t, err)

err = ipamNet.SetOrCheckNetwork(ipNet1)
require.NoError(t, err)

firstip := net.ParseIP("192.1.3.4")
require.NotNil(t, firstip)
lastip := net.ParseIP("192.2.0.5")
require.NotNil(t, lastip)
err = ipamNet.AddRange(firstip, lastip)
require.Error(t, err)

firstip = net.ParseIP("192.2.0.4")
require.NotNil(t, firstip)
lastip = net.ParseIP("200.2.3.4")
require.NotNil(t, lastip)
err = ipamNet.AddRange(firstip, lastip)
require.Error(t, err)

firstip = net.ParseIP("1.2.3.4")
require.NotNil(t, firstip)
lastip = net.ParseIP("192.2.0.10")
require.NotNil(t, lastip)
err = ipamNet.AddRange(firstip, lastip)
require.Error(t, err)

firstip = net.ParseIP("192.2.0.100")
require.NotNil(t, firstip)
lastip = net.ParseIP("192.2.0.10")
require.NotNil(t, lastip)
err = ipamNet.AddRange(firstip, lastip)
require.Error(t, err)

firstip = net.ParseIP("100::1")
require.NotNil(t, firstip)
lastip = net.ParseIP("100::2")
require.NotNil(t, lastip)
err = ipamNet.AddRange(firstip, lastip)
require.Error(t, err)

require.Empty(t, ipamNet.Ranges)
}

func TestRangeIPv6(t *testing.T) {
ipamNet := &singlepointipam.IpamNet{}
require.NotNil(t, ipamNet)
require.Nil(t, ipamNet.Ranges)

_, ipNet1, err := net.ParseCIDR("a:b:c:d::/64")
require.NoError(t, err)

err = ipamNet.SetOrCheckNetwork(ipNet1)
require.NoError(t, err)

firstip := net.ParseIP("a:b:c:d::4")
require.NotNil(t, firstip)
lastip := net.ParseIP("a:b:c:d::5")
require.NotNil(t, lastip)
err = ipamNet.AddRange(firstip, lastip)
require.NoError(t, err)

firstip = net.ParseIP("a:b:c:d::1")
require.NotNil(t, firstip)
err = ipamNet.AddRange(firstip, firstip)
require.NoError(t, err)

firstip = net.ParseIP("a:b:c:d::1")
require.NotNil(t, firstip)
lastip = net.ParseIP("a:b:c:d::2000")
require.NotNil(t, firstip)
err = ipamNet.AddRange(firstip, lastip)
require.NoError(t, err)

require.Equal(t, len(ipamNet.Ranges), 3)
}

func TestInvalidRangeIPv6(t *testing.T) {
ipamNet := &singlepointipam.IpamNet{}
require.NotNil(t, ipamNet)
require.Nil(t, ipamNet.Ranges)
require.Empty(t, ipamNet.Ranges)

_, ipNet1, err := net.ParseCIDR("a:b:c:d::/64")
require.NoError(t, err)

err = ipamNet.SetOrCheckNetwork(ipNet1)
require.NoError(t, err)

firstip := net.ParseIP("a:b:c:e::4")
require.NotNil(t, firstip)
lastip := net.ParseIP("a:b:c:d::5")
require.NotNil(t, lastip)
err = ipamNet.AddRange(firstip, lastip)
require.Error(t, err)

firstip = net.ParseIP("a:b:c:d::4")
require.NotNil(t, firstip)
lastip = net.ParseIP("a:e:c:d::4")
require.NotNil(t, lastip)
err = ipamNet.AddRange(firstip, lastip)
require.Error(t, err)

firstip = net.ParseIP("1:2:3:4::4")
require.NotNil(t, firstip)
lastip = net.ParseIP("a:b:c:d::10")
require.NotNil(t, lastip)
err = ipamNet.AddRange(firstip, lastip)
require.Error(t, err)

firstip = net.ParseIP("a:b:c:d::100")
require.NotNil(t, firstip)
lastip = net.ParseIP("a:b:c:d::10")
require.NotNil(t, lastip)
err = ipamNet.AddRange(firstip, lastip)
require.Error(t, err)

firstip = net.ParseIP("192.168.100.1")
require.NotNil(t, firstip)
lastip = net.ParseIP("192.168.100.2")
require.NotNil(t, lastip)
err = ipamNet.AddRange(firstip, lastip)
require.Error(t, err)

require.Empty(t, ipamNet.Ranges)
}
Loading

0 comments on commit 25b5e6b

Please sign in to comment.