From df439562801b870155a9ada5e0969c148bf11c28 Mon Sep 17 00:00:00 2001 From: Lugossy Zoltan Date: Wed, 16 Nov 2022 00:48:40 +0100 Subject: [PATCH] singlepointipam can be configured based on IP ranges Related issue: cmd-nse-remote-vlan/110 Signed-off-by: Lugossy Zoltan --- .../ipam/singlepointipam/range.go | 86 ++++++ .../ipam/singlepointipam/range_test.go | 276 ++++++++++++++++++ .../ipam/singlepointipam/server.go | 56 +++- .../ipam/singlepointipam/server_test.go | 154 +++++++++- pkg/tools/ippool/ippool.go | 8 + pkg/tools/ippool/ippool_test.go | 113 +++++++ 6 files changed, 676 insertions(+), 17 deletions(-) create mode 100644 pkg/networkservice/ipam/singlepointipam/range.go create mode 100644 pkg/networkservice/ipam/singlepointipam/range_test.go diff --git a/pkg/networkservice/ipam/singlepointipam/range.go b/pkg/networkservice/ipam/singlepointipam/range.go new file mode 100644 index 0000000000..beccc8d62f --- /dev/null +++ b/pkg/networkservice/ipam/singlepointipam/range.go @@ -0,0 +1,86 @@ +// 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()) + } + + fip16 := firstip.To16() + lip16 := lastip.To16() + for i := 0; i < len(fip16); i += 2 { + if (uint(fip16[i])<<8 + uint(fip16[i+1])) > (uint(lip16[i])<<8 + uint(lip16[i+1])) { + return errors.Errorf("%s-%s invalid range start", firstip, lastip) + } + } + + if ipn.Ranges == nil { + ipn.Ranges = []*IpamRange{} + } + ipn.Ranges = append(ipn.Ranges, &IpamRange{RangeStart: firstip, RangeEnd: lastip}) + return nil +} diff --git a/pkg/networkservice/ipam/singlepointipam/range_test.go b/pkg/networkservice/ipam/singlepointipam/range_test.go new file mode 100644 index 0000000000..929d208932 --- /dev/null +++ b/pkg/networkservice/ipam/singlepointipam/range_test.go @@ -0,0 +1,276 @@ +// 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) +} + +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) +} + +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) +} diff --git a/pkg/networkservice/ipam/singlepointipam/server.go b/pkg/networkservice/ipam/singlepointipam/server.go index ce288fb3e4..5617872e00 100644 --- a/pkg/networkservice/ipam/singlepointipam/server.go +++ b/pkg/networkservice/ipam/singlepointipam/server.go @@ -33,12 +33,12 @@ import ( type singlePIpam struct { Map - ipPools []*ippool.IPPool - prefixes []*net.IPNet - myIPs []string - masks []string - once sync.Once - initErr error + ipPools []*ippool.IPPool + ipamNetworks []*IpamNet + myIPs []string + masks []string + once sync.Once + initErr error } type connectionInfo struct { @@ -55,25 +55,51 @@ func (i *connectionInfo) shouldUpdate(exclude *ippool.IPPool) bool { } // NewServer - creates a new NetworkServiceServer chain element that implements IPAM service. -func NewServer(prefixes ...*net.IPNet) networkservice.NetworkServiceServer { +func NewServer(networks ...*IpamNet) networkservice.NetworkServiceServer { return &singlePIpam{ - prefixes: prefixes, + ipamNetworks: networks, } } + func (sipam *singlePIpam) init() { - if len(sipam.prefixes) == 0 { - sipam.initErr = errors.New("required one or more prefixes") + if len(sipam.ipamNetworks) == 0 { + sipam.initErr = errors.New("required one or more prefixes/ranges") return } - for _, prefix := range sipam.prefixes { - if prefix == nil { - sipam.initErr = errors.Errorf("prefix must not be nil: %+v", sipam.prefixes) + for _, ipamNetwork := range sipam.ipamNetworks { + if ipamNetwork.Network == nil { + sipam.initErr = errors.Errorf("prefix must not be nil: %+v", sipam.ipamNetworks) return } + if len(ipamNetwork.Ranges) > 0 { + for _, iprange := range ipamNetwork.Ranges { + if iprange.RangeStart == nil || iprange.RangeEnd == nil { + sipam.initErr = errors.Errorf("IP range start/end must be set: %+v", iprange) + return + } + if ipNet := ipamNetwork.Network; !ipNet.Contains(iprange.RangeStart) || !ipNet.Contains(iprange.RangeEnd) { + sipam.initErr = errors.Errorf("IP range start/end must be in network: %+v, %+v", iprange, ipNet) + return + } + } + } + prefix := ipamNetwork.Network ones, _ := prefix.Mask.Size() mask := fmt.Sprintf("/%d", ones) sipam.masks = append(sipam.masks, mask) - ipPool := ippool.NewWithNet(prefix) + var ipPool *ippool.IPPool + + if len(ipamNetwork.Ranges) == 0 { + // simple cidr prefix + ipPool = ippool.NewWithNet(prefix) + } else { + // IP range(s) + ipPool = ippool.New(len(prefix.IP)) + for _, iprange := range ipamNetwork.Ranges { + ipPool.AddRange(iprange.RangeStart, iprange.RangeEnd) + } + } + sipam.ipPools = append(sipam.ipPools, ipPool) } } @@ -168,7 +194,7 @@ func (sipam *singlePIpam) setMyIP(i int) error { func (sipam *singlePIpam) getAddrs(excludeIP4, excludeIP6 *ippool.IPPool) (connInfo *connectionInfo, err error) { var dstAddr, srcAddr net.IP - for i := 0; i < len(sipam.prefixes); i++ { + for i := 0; i < len(sipam.ipamNetworks); i++ { // The NSE needs only one src address dstSet := false if i >= len(sipam.myIPs) { diff --git a/pkg/networkservice/ipam/singlepointipam/server_test.go b/pkg/networkservice/ipam/singlepointipam/server_test.go index 581a578a3e..ab53f25fdf 100644 --- a/pkg/networkservice/ipam/singlepointipam/server_test.go +++ b/pkg/networkservice/ipam/singlepointipam/server_test.go @@ -34,14 +34,29 @@ import ( "github.com/networkservicemesh/sdk/pkg/networkservice/utils/metadata" ) -func newIpamServer(prefixes ...*net.IPNet) networkservice.NetworkServiceServer { +func newIpamServer(networks ...*net.IPNet) networkservice.NetworkServiceServer { + ipamNetworks := []*singlepointipam.IpamNet{} + for _, n := range networks { + ipamNetworks = append(ipamNetworks, &singlepointipam.IpamNet{Network: n}) + } + return newIpamNetServer(ipamNetworks...) +} + +func newIpamNetServer(networks ...*singlepointipam.IpamNet) networkservice.NetworkServiceServer { return next.NewNetworkServiceServer( updatepath.NewServer("ipam"), metadata.NewServer(), - singlepointipam.NewServer(prefixes...), + singlepointipam.NewServer(networks...), ) } +func newIpamNet(ipNet *net.IPNet, ranges ...*singlepointipam.IpamRange) *singlepointipam.IpamNet { + return &singlepointipam.IpamNet{ + Network: ipNet, + Ranges: ranges, + } +} + func newRequest() *networkservice.NetworkServiceRequest { return &networkservice.NetworkServiceRequest{ Connection: &networkservice.Connection{ @@ -407,3 +422,138 @@ func TestRefreshRequestMultiServer(t *testing.T) { require.NoError(t, err) validateConns(t, conn, []string{"192.168.0.5/16", "fe80::5/64"}) } + +//nolint:dupl +func TestIPv4Range(t *testing.T) { + sip, ipNet, err := net.ParseCIDR("192.168.3.4/16") + require.NoError(t, err) + eip := net.ParseIP("192.168.4.1") + require.NotNil(t, eip) + + ipamNet := newIpamNet(ipNet, &singlepointipam.IpamRange{RangeStart: sip, RangeEnd: eip}) + srv := newIpamNetServer(ipamNet) + + conn1, err := srv.Request(context.Background(), newRequest()) + require.NoError(t, err) + validateConn(t, conn1, "192.168.3.5/16") + + conn2, err := srv.Request(context.Background(), newRequest()) + require.NoError(t, err) + validateConn(t, conn2, "192.168.3.6/16") + + _, err = srv.Close(context.Background(), conn1) + require.NoError(t, err) + + conn3, err := srv.Request(context.Background(), newRequest()) + require.NoError(t, err) + validateConn(t, conn3, "192.168.3.5/16") + + conn4, err := srv.Request(context.Background(), newRequest()) + require.NoError(t, err) + validateConn(t, conn4, "192.168.3.7/16") +} + +//nolint:dupl +func TestIPv6Range(t *testing.T) { + sip, ipNet, err := net.ParseCIDR("fe80::/64") + require.NoError(t, err) + eip := net.ParseIP("fe80::ff") + require.NotNil(t, eip) + + ipamNet := newIpamNet(ipNet, &singlepointipam.IpamRange{RangeStart: sip, RangeEnd: eip}) + srv := newIpamNetServer(ipamNet) + + conn1, err := srv.Request(context.Background(), newRequest()) + require.NoError(t, err) + validateConn(t, conn1, "fe80::1/64") + + conn2, err := srv.Request(context.Background(), newRequest()) + require.NoError(t, err) + validateConn(t, conn2, "fe80::2/64") + + _, err = srv.Close(context.Background(), conn1) + require.NoError(t, err) + + conn3, err := srv.Request(context.Background(), newRequest()) + require.NoError(t, err) + validateConn(t, conn3, "fe80::1/64") + + conn4, err := srv.Request(context.Background(), newRequest()) + require.NoError(t, err) + validateConn(t, conn4, "fe80::3/64") +} + +//nolint:dupl +func TestIPv4MissingRange(t *testing.T) { + sip, ipNet, err := net.ParseCIDR("192.168.3.4/16") + require.NoError(t, err) + eip := net.ParseIP("192.168.4.1") + require.NotNil(t, eip) + + ipamNet1 := newIpamNet(ipNet, &singlepointipam.IpamRange{RangeStart: sip}) + srv1 := newIpamNetServer(ipamNet1) + _, err = srv1.Request(context.Background(), newRequest()) + require.Error(t, err) + + ipamNet2 := newIpamNet(ipNet, &singlepointipam.IpamRange{RangeEnd: eip}) + srv2 := newIpamNetServer(ipamNet2) + _, err = srv2.Request(context.Background(), newRequest()) + require.Error(t, err) +} + +//nolint:dupl +func TestIPv6MissingRange(t *testing.T) { + sip, ipNet, err := net.ParseCIDR("2dea::1/64") + require.NoError(t, err) + require.Equal(t, sip, net.ParseIP("2dea::1")) + eip := net.ParseIP("2dea::1000:1") + require.NotNil(t, eip) + + ipamNet1 := newIpamNet(ipNet, &singlepointipam.IpamRange{RangeStart: sip}) + srv1 := newIpamNetServer(ipamNet1) + _, err = srv1.Request(context.Background(), newRequest()) + require.Error(t, err) + + ipamNet2 := newIpamNet(ipNet, &singlepointipam.IpamRange{RangeEnd: eip}) + srv2 := newIpamNetServer(ipamNet2) + _, err = srv2.Request(context.Background(), newRequest()) + require.Error(t, err) +} + +//nolint:dupl +func TestIPv4RangeOutOfBounds(t *testing.T) { + sip, ipNet, err := net.ParseCIDR("192.168.3.4/16") + require.NoError(t, err) + require.Equal(t, sip, net.ParseIP("192.168.3.4")) + eip := net.ParseIP("192.200.0.0") + require.NotNil(t, eip) + + ipamNet1 := newIpamNet(ipNet, &singlepointipam.IpamRange{RangeStart: sip, RangeEnd: eip}) + srv1 := newIpamNetServer(ipamNet1) + _, err = srv1.Request(context.Background(), newRequest()) + require.Error(t, err) + + ipamNet2 := newIpamNet(ipNet, &singlepointipam.IpamRange{RangeStart: eip, RangeEnd: sip}) + srv2 := newIpamNetServer(ipamNet2) + _, err = srv2.Request(context.Background(), newRequest()) + require.Error(t, err) +} + +//nolint:dupl +func TestIPv6RangeOutOfBounds(t *testing.T) { + sip, ipNet, err := net.ParseCIDR("2dea::1/64") + require.NoError(t, err) + require.Equal(t, sip, net.ParseIP("2dea::1")) + eip := net.ParseIP("2dea:1000::f") + require.NotNil(t, eip) + + ipamNet1 := newIpamNet(ipNet, &singlepointipam.IpamRange{RangeStart: sip, RangeEnd: eip}) + srv1 := newIpamNetServer(ipamNet1) + _, err = srv1.Request(context.Background(), newRequest()) + require.Error(t, err) + + ipamNet2 := newIpamNet(ipNet, &singlepointipam.IpamRange{RangeStart: eip, RangeEnd: sip}) + srv2 := newIpamNetServer(ipamNet2) + _, err = srv2.Request(context.Background(), newRequest()) + require.Error(t, err) +} diff --git a/pkg/tools/ippool/ippool.go b/pkg/tools/ippool/ippool.go index c1ec44a507..868794795a 100644 --- a/pkg/tools/ippool/ippool.go +++ b/pkg/tools/ippool/ippool.go @@ -147,6 +147,14 @@ func (tree *IPPool) AddNetString(ipNetString string) { tree.AddNet(ipNet) } +// AddRange - adds ip addresses from ip range to the pool +func (tree *IPPool) AddRange(start, end net.IP) { + tree.lock.Lock() + defer tree.lock.Unlock() + + tree.addRange(&ipRange{start: ipAddressFromIP(start), end: ipAddressFromIP(end)}) +} + // ContainsNetString parses ipNetRaw string and checks that pool contains whole ipNet func (tree *IPPool) ContainsNetString(ipNetRaw string) bool { _, ipNet, err := net.ParseCIDR(ipNetRaw) diff --git a/pkg/tools/ippool/ippool_test.go b/pkg/tools/ippool/ippool_test.go index 6f6b6791f7..f80aa50528 100644 --- a/pkg/tools/ippool/ippool_test.go +++ b/pkg/tools/ippool/ippool_test.go @@ -126,6 +126,119 @@ func Test_IPPoolContains(t *testing.T) { } } +func TestIPPoolRange_Contains(t *testing.T) { + firstip := net.ParseIP("192.10.254.255") + lastip := net.ParseIP("192.168.0.150") + require.NotNil(t, firstip) + require.NotNil(t, lastip) + + ipPool := New(len(firstip)) + require.NotNil(t, ipPool) + ipPool.AddRange(firstip, lastip) + + require.True(t, ipPool.ContainsNetString("192.10.255.0/32")) + require.True(t, ipPool.ContainsNetString("192.10.254.255/32")) + require.True(t, ipPool.ContainsNetString("192.168.0.5/32")) + require.False(t, ipPool.ContainsNetString("193.169.0.1/32")) + require.True(t, ipPool.ContainsString("192.168.0.2")) + require.True(t, ipPool.ContainsString("::ffff:192.168.0.100")) + require.False(t, ipPool.ContainsNetString("192.0.0.1/32")) + + for i := 24; i < 32; i++ { + ipNet := "192.10.255.0/" + fmt.Sprint(i) + require.True(t, ipPool.ContainsNetString(ipNet), ipNet) + } +} + +func TestIPPoolRange_IPv6Contains(t *testing.T) { + firstip := net.ParseIP("1000:2000:1000::0") + lastip := net.ParseIP("1000:2000:2000:3000::1") + require.NotNil(t, firstip) + require.NotNil(t, lastip) + + ipPool := New(len(firstip)) + require.NotNil(t, ipPool) + ipPool.AddRange(firstip, lastip) + + require.True(t, ipPool.ContainsNetString("1000:2000:2000:1000::0/128")) + require.True(t, ipPool.ContainsNetString("1000:2000:2000:1000::ffff/128")) + require.False(t, ipPool.ContainsNetString("1000:2000:2000:5000::ffff/128")) + require.True(t, ipPool.ContainsString("1000:2000:2000:3000::1")) + require.False(t, ipPool.ContainsString("::ffff:192.168.0.100")) + require.False(t, ipPool.ContainsString("192.167.0.100")) + + for i := 36; i < 128; i++ { + ipNet := "1000:2000:1000::0/" + fmt.Sprint(i) + require.True(t, ipPool.ContainsNetString(ipNet), ipNet) + } + for i := 35; i > 0; i-- { + ipNet := "1000:2000:1000::0/" + fmt.Sprint(i) + require.False(t, ipPool.ContainsNetString(ipNet), ipNet) + } +} + +func TestIPPoolRange_WorksCorrect(t *testing.T) { + firstip := net.ParseIP("192.168.0.0") + lastip := net.ParseIP("192.168.2.150") + require.NotNil(t, firstip) + require.NotNil(t, lastip) + + ipPool := New(len(firstip)) + require.NotNil(t, ipPool) + + ipPool.AddRange(firstip, lastip) + p, err := ipPool.Pull() + require.NoError(t, err) + require.Equal(t, p, firstip) + + prefix := p.String() + "/24" + fmt.Println(prefix) + ipPool.ExcludeString(prefix) + p, err = ipPool.Pull() + require.NoError(t, err) + require.Equal(t, p, net.ParseIP("192.168.1.0")) + + ipPool.ExcludeString("192.168.1.0/25") + p, err = ipPool.Pull() + require.NoError(t, err) + require.Equal(t, p, net.ParseIP("192.168.1.128")) + + ipPool.ExcludeString("192.168.1.0/24") + p, err = ipPool.Pull() + require.NoError(t, err) + require.Equal(t, p, net.ParseIP("192.168.2.0")) + + ipPool.ExcludeString("192.168.2.0/24") + _, err = ipPool.Pull() + require.Error(t, err) +} + +func TestIPPoolRange_IPv6WorksCorrect(t *testing.T) { + firstip := net.ParseIP("1000:2000:ff00::0") + lastip := net.ParseIP("1000:2000:ffff::0") + require.NotNil(t, firstip) + require.NotNil(t, lastip) + + ipPool := New(len(firstip)) + require.NotNil(t, ipPool) + + ipPool.AddRange(firstip, lastip) + p, err := ipPool.Pull() + require.NoError(t, err) + require.Equal(t, p, firstip) + + prefix := p.String() + "/48" + fmt.Println(prefix) + ipPool.ExcludeString(prefix) + p, err = ipPool.Pull() + require.NoError(t, err) + require.Equal(t, p, net.ParseIP("1000:2000:ff01::0")) + + ipPool.ExcludeString("1000:2000:ff00::0/38") + _, err = ipPool.Pull() + require.Error(t, err) +} + func TestIPPoolTool_Contains(t *testing.T) { ipPool := NewWithNetString("192.168.0.0/16")