Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix subnet checking #15394

Merged
merged 2 commits into from
Dec 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 11 additions & 10 deletions pkg/network/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ func lookupInInterfaces(ip net.IP) (*Parameters, *net.IPNet, error) {
// inspect initialises IPv4 network parameters struct from given address addr.
// addr can be single address (like "192.168.17.42"), network address (like "192.168.17.0") or in CIDR form (like "192.168.17.42/24 or "192.168.17.0/24").
// If addr belongs to network of local network interface, parameters will also contain info about that network interface.
func inspect(addr string) (*Parameters, error) {
var inspect = func(addr string) (*Parameters, error) {

// extract ip from addr
ip, network, err := net.ParseCIDR(addr)
Expand Down Expand Up @@ -191,7 +191,7 @@ func inspect(addr string) (*Parameters, error) {

// isSubnetTaken returns if local network subnet exists and any error occurred.
// If will return false in case of an error.
func isSubnetTaken(subnet string) (bool, error) {
var isSubnetTaken = func(subnet string) (bool, error) {
ifAddrs, err := net.InterfaceAddrs()
if err != nil {
return false, fmt.Errorf("failed listing network interface addresses: %w", err)
Expand Down Expand Up @@ -225,19 +225,20 @@ func IsUser(network string) bool {

// FreeSubnet will try to find free private network beginning with startSubnet, incrementing it in steps up to number of tries.
func FreeSubnet(startSubnet string, step, tries int) (*Parameters, error) {
currSubnet := startSubnet
for try := 0; try < tries; try++ {
n, err := inspect(startSubnet)
n, err := inspect(currSubnet)
if err != nil {
return nil, err
}
startSubnet = n.IP
if isSubnetPrivate(startSubnet) {
taken, err := isSubnetTaken(startSubnet)
subnet := n.IP
if isSubnetPrivate(subnet) {
spowelljr marked this conversation as resolved.
Show resolved Hide resolved
taken, err := isSubnetTaken(subnet)
if err != nil {
return nil, err
}
if !taken {
if ok := reserveSubnet(startSubnet, defaultReservationPeriod); ok {
if ok := reserveSubnet(subnet, defaultReservationPeriod); ok {
klog.Infof("using free private subnet %s: %+v", n.CIDR, n)
return n, nil
}
Expand All @@ -249,13 +250,13 @@ func FreeSubnet(startSubnet string, step, tries int) (*Parameters, error) {
klog.Infof("skipping subnet %s that is not private", n.CIDR)
}
prefix, _ := net.ParseIP(n.IP).DefaultMask().Size()
nextSubnet := net.ParseIP(startSubnet).To4()
nextSubnet := net.ParseIP(currSubnet).To4()
if prefix <= 16 {
nextSubnet[1] += byte(step)
} else {
nextSubnet[2] += byte(step)
}
startSubnet = nextSubnet.String()
currSubnet = nextSubnet.String()
}
return nil, fmt.Errorf("no free private network subnets found with given parameters (start: %q, step: %d, tries: %d)", startSubnet, step, tries)
}
Expand All @@ -265,7 +266,7 @@ func FreeSubnet(startSubnet string, step, tries int) (*Parameters, error) {
// - true, if new reservation was created or expired one renewed
//
// uses sync.Map to manage reservations thread-safe
func reserveSubnet(subnet string, period time.Duration) bool {
var reserveSubnet = func(subnet string, period time.Duration) bool {
// put 'zero' reservation{} Map value for subnet Map key
// to block other processes from concurrently changing this subnet
zero := reservation{}
Expand Down
116 changes: 116 additions & 0 deletions pkg/network/network_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
Copyright 2022 The Kubernetes Authors All rights reserved.

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 network

import (
"strings"
"testing"
"time"
)

func TestIsSubnetPrivate(t *testing.T) {
tests := []struct {
subnet string
expected bool
}{
{"9.255.255.255", false},
{"10.0.0.0", true},
{"10.255.255.255", true},
{"11.0.0.0", false},
{"172.15.255.255", false},
{"172.16.0.0", true},
{"172.31.255.255", true},
{"172.32.0.0", false},
{"192.167.255.255", false},
{"192.168.0.0", true},
{"192.168.255.255", true},
{"192.169.0.0", false},
}
for _, test := range tests {
got := isSubnetPrivate(test.subnet)
if got != test.expected {
t.Errorf("isSubnetPrivate(%q) = %t; expected = %t", test.subnet, got, test.expected)
}
}
}

func TestFreeSubnet(t *testing.T) {
reserveSubnet = func(subnet string, period time.Duration) bool { return true }

t.Run("NoRetriesSuccess", func(t *testing.T) {
startingSubnet := "192.168.0.0"
subnet, err := FreeSubnet(startingSubnet, 0, 1)
if err != nil {
t.Fatal(err)
}
expectedIP := startingSubnet
if subnet.IP != expectedIP {
t.Errorf("expected IP = %q; got = %q", expectedIP, subnet.IP)
}
})

t.Run("FirstSubnetTaken", func(t *testing.T) {
count := 0
isSubnetTaken = func(subnet string) (bool, error) {
count++
return count == 1, nil
}

startingSubnet := "192.168.0.0"
subnet, err := FreeSubnet(startingSubnet, 9, 2)
if err != nil {
t.Fatal(err)
}
expectedIP := "192.168.9.0"
if subnet.IP != expectedIP {
t.Errorf("expected IP = %q; got = %q", expectedIP, subnet.IP)
}
})

t.Run("FirstSubnetIPV6NetworkFound", func(t *testing.T) {
count := 0
inspect = func(addr string) (*Parameters, error) {
count++
p := &Parameters{IP: addr}
if count == 1 {
p.IP = "0.0.0.0"
}
return p, nil
}

startingSubnet := "10.0.0.0"
subnet, err := FreeSubnet(startingSubnet, 9, 2)
if err != nil {
t.Fatal(err)
}
expectedIP := "10.9.0.0"
if subnet.IP != expectedIP {
t.Errorf("expepcted IP = %q; got = %q", expectedIP, subnet.IP)
}
})

t.Run("NonPrivateSubnet", func(t *testing.T) {
startingSubnet := "192.167.0.0"
_, err := FreeSubnet(startingSubnet, 9, 1)
if err == nil {
t.Fatalf("expected to fail since IP non-private but no error thrown")
}
if !strings.Contains(err.Error(), startingSubnet) {
t.Errorf("expected starting subnet of %q to be included in error, but intead got: %v", startingSubnet, err)
}
})
}