Skip to content

Commit

Permalink
Config parameter CidrPrefix accepts range of IPs
Browse files Browse the repository at this point in the history
Related issue: cmd-nse-remote-vlan/110
Related PR: sdk/1382

Signed-off-by: Lugossy Zoltan <[email protected]>
  • Loading branch information
zolug committed Nov 17, 2022
1 parent 298bb00 commit e9e7c6e
Show file tree
Hide file tree
Showing 3 changed files with 235 additions and 11 deletions.
83 changes: 83 additions & 0 deletions internal/pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,14 @@
package config

import (
"net"
"net/url"
"strconv"
"strings"
"time"

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

"github.com/kelseyhightower/envconfig"
"github.com/pkg/errors"
)
Expand Down Expand Up @@ -66,6 +69,86 @@ func validateConfig(cfg *Config) error {
if cfg.ListenOn.Scheme != tcpSchema {
return errors.New("only tcp schema is supported for this type of endpoint")
}
if _, err := ParseCidr(cfg.CidrPrefix); err != nil {
return err
}
return nil
}

// ParseCidr parses array of CIDR prefixes (and/or IP ranges)
// Valid "prefix" formats are "192.168.0.0/24", "192.168.0.1-192.168.0.100/24",
// or composite "192.168.0.1-192.168.0.10/24;192.168.0.20-192.168.0.30/24".
func ParseCidr(cIDRs []string) ([]*singlepointipam.IpamNet, error) {
if len(cIDRs) > 2 {
return nil, errors.Errorf("one IPv4 and/or one IPv6 config allowed")
}

ipamNets := []*singlepointipam.IpamNet{}
for _, cIDR := range cIDRs {
ipamNet := singlepointipam.NewIpamNet()
composite := strings.Split(cIDR, ";")
for _, cidrStr := range composite {
if r := strings.SplitN(cidrStr, "-", 2); len(r) == 2 {
if err := parseRange(ipamNet, r); err != nil {
return nil, err
}
} else {
// IP network
if len(composite) > 1 {
return nil, errors.Errorf("%s composite subnet config is invalid", composite)
}
if err := parseNetwork(ipamNet, cidrStr); err != nil {
return nil, err
}
}
}
ipamNets = append(ipamNets, ipamNet)
}

if err := validateNetworkFamily(ipamNets...); err != nil {
return nil, err
}

return ipamNets, nil
}

func parseNetwork(ipamNet *singlepointipam.IpamNet, network string) error {
network = strings.TrimSpace(network)
_, ipNet, err := net.ParseCIDR(network)
if err != nil {
return errors.Errorf("%s is invalid CIDR: %s", network, err)
}
return ipamNet.SetOrCheckNetwork(ipNet)
}

func parseRange(ipamNet *singlepointipam.IpamNet, r []string) error {
r[0] = strings.TrimSpace(r[0])
r[1] = strings.TrimSpace(r[1])
firstip := net.ParseIP(r[0])
if firstip == nil {
return errors.Errorf("%s is invalid range start", r[0])
}
lastip, ipNet, err := net.ParseCIDR(r[1])
if err != nil {
return errors.Errorf("%s is invalid CIDR: %s", r[1], err)
}
if !ipNet.Contains(firstip) {
return errors.Errorf("%s is invalid range start for CIDR %s", firstip, ipNet.String())
}
if err := ipamNet.SetOrCheckNetwork(ipNet); err != nil {
return err
}
return ipamNet.AddRange(firstip, lastip)
}

func validateNetworkFamily(ipamNets ...*singlepointipam.IpamNet) error {
if len(ipamNets) == 2 {
ip1 := ipamNets[0].Network.IP.To16()
ip2 := ipamNets[1].Network.IP.To16()
if ip1.To4() != nil && ip2.To4() != nil || ip1.To4() == nil && ip2.To4() == nil {
return errors.Errorf("one IPv4 and/or one IPv6 config allowed")
}
}
return nil
}

Expand Down
148 changes: 147 additions & 1 deletion internal/pkg/config/config_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2021 Nordix Foundation.
// Copyright (c) 2021-2022 Nordix Foundation.
//
// SPDX-License-Identifier: Apache-2.0
//
Expand Down Expand Up @@ -46,3 +46,149 @@ func TestServiceConfig_UnmarshalBinary(t *testing.T) {
VLANTag: 200,
}, cfg)
}

func TestCidrPrefix(t *testing.T) {
cidrs := []string{
"192.168.0.0/16",
"dead:beaf::/64",
}
_, err := config.ParseCidr(cidrs)
require.NoError(t, err)

cidrs = []string{
"192.168.0.1/32",
"dead:beaf::1/128",
}
_, err = config.ParseCidr(cidrs)
require.NoError(t, err)

cidrs = []string{
"192.168.100.0/24",
}
_, err = config.ParseCidr(cidrs)
require.NoError(t, err)

cidrs = []string{
"2dea:1000::/48",
}
_, err = config.ParseCidr(cidrs)
require.NoError(t, err)

cidrs = []string{
"2dea:1000::/48",
}
_, err = config.ParseCidr(cidrs)
require.NoError(t, err)

cidrs = []string{
"192.168.0.1-192.168.0.100/24",
"fe80:1000:2000::/64",
}
_, err = config.ParseCidr(cidrs)
require.NoError(t, err)

cidrs = []string{
"192.168.0.1-192.168.0.100/24",
"2dea:ffff:ffff:ffff:ffff:ffff:0000:0000-2dea:ffff:ffff:ffff:ffff:ffff:abcd:0000/96",
}
_, err = config.ParseCidr(cidrs)
require.NoError(t, err)

cidrs = []string{
"192.168.0.0-192.168.0.0/16;192.168.1.1-192.168.1.1/16;192.168.100.100-192.168.200.200/16",
"1000::a:2-1000::a:ffff/64;1000::d:2-1000::e:ffff/64;1000::f:f:2-1000::f:f:2/64",
}
_, err = config.ParseCidr(cidrs)
require.NoError(t, err)

cidrs = []string{
"2000::1-2000::2000/64",
}
_, err = config.ParseCidr(cidrs)
require.NoError(t, err)
}

func TestCidrPrefixInvalid(t *testing.T) {
cidrs := []string{
"192.168.0.1-192.168.0.100/24",
"10.0.0.1-10.0.200/16",
"100::1-100::ffff/64",
}
_, err := config.ParseCidr(cidrs)
require.Error(t, err)

cidrs = []string{
"192.168.0.1-192.168.0.100/24",
"10.0.0.1-10.0.200/16",
}
_, err = config.ParseCidr(cidrs)
require.Error(t, err)

cidrs = []string{
"100::1-100::ffff/64",
"2dea::1-2dea::ffff/64",
}
_, err = config.ParseCidr(cidrs)
require.Error(t, err)

cidrs = []string{
"192.168.0.1-192.168.0.100/24;192.168.32.1-192.168.32.100/25",
}
_, err = config.ParseCidr(cidrs)
require.Error(t, err)

cidrs = []string{
"2dea::af0-2dea::aff/96;2dea::ef0-2dea::eff/64;2dea::cf0-2dea::cff/96",
}
_, err = config.ParseCidr(cidrs)
require.Error(t, err)

cidrs = []string{
"192.168.0.1-192.168.0.100/24;192.168.0.0/24",
}
_, err = config.ParseCidr(cidrs)
require.Error(t, err)

cidrs = []string{
"192.168.0.64/28",
"2000::/64;2000::1-2000::2000/64",
}
_, err = config.ParseCidr(cidrs)
require.Error(t, err)

cidrs = []string{
"192.168.0.0/28-192.168.0.2/28",
}
_, err = config.ParseCidr(cidrs)
require.Error(t, err)

cidrs = []string{
"2000::2000-2000::1/64",
}
_, err = config.ParseCidr(cidrs)
require.Error(t, err)

cidrs = []string{
"192.168.0.0-192.168.0.2/36",
}
_, err = config.ParseCidr(cidrs)
require.Error(t, err)

cidrs = []string{
"abba::baba/140",
}
_, err = config.ParseCidr(cidrs)
require.Error(t, err)

cidrs = []string{
"192.168.11.0-192.168.10.2/30",
}
_, err = config.ParseCidr(cidrs)
require.Error(t, err)

cidrs = []string{
"2000:b::1-2000:a::2000/64",
}
_, err = config.ParseCidr(cidrs)
require.Error(t, err)
}
15 changes: 5 additions & 10 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import (
"net/url"
"os"
"os/signal"
"strings"
"syscall"
"time"

Expand Down Expand Up @@ -155,7 +154,7 @@ func main() {
log.FromContext(ctx).Infof("executing phase 3: parsing network prefixes for ipam")
// ********************************************************************************

ipamChain := getIPAMChain(ctx, cfg.CidrPrefix)
ipamChain := getIPAMChain(cfg.CidrPrefix)

log.FromContext(ctx).Infof("network prefixes parsed successfully")

Expand Down Expand Up @@ -305,15 +304,11 @@ func genPublishableURL(listenOn *url.URL, logger log.Logger) *url.URL {
return listenonurl.GetPublicURL(addrs, listenOn)
}

func getIPAMChain(ctx context.Context, cIDRs []string) networkservice.NetworkServiceServer {
func getIPAMChain(cIDRs []string) networkservice.NetworkServiceServer {
var ipamchain []networkservice.NetworkServiceServer
for _, cidr := range cIDRs {
var parseErr error
_, ipNet, parseErr := net.ParseCIDR(strings.TrimSpace(cidr))
if parseErr != nil {
log.FromContext(ctx).Fatalf("Could not parse CIDR %s; %+v", cidr, parseErr)
}
ipamchain = append(ipamchain, singlepointipam.NewServer(ipNet))
ipamNets, _ := config.ParseCidr(cIDRs)
for _, ipamNet := range ipamNets {
ipamchain = append(ipamchain, singlepointipam.NewServer(ipamNet))
}
return chain.NewNetworkServiceServer(ipamchain...)
}

0 comments on commit e9e7c6e

Please sign in to comment.