diff --git a/pkg/drivers/kic/oci/network_create.go b/pkg/drivers/kic/oci/network_create.go index 9386bd14dd..1fd256c115 100644 --- a/pkg/drivers/kic/oci/network_create.go +++ b/pkg/drivers/kic/oci/network_create.go @@ -28,6 +28,7 @@ import ( "github.com/pkg/errors" "k8s.io/klog/v2" + "k8s.io/minikube/pkg/util" ) // firstSubnetAddr subnet to be used on first kic cluster @@ -74,28 +75,12 @@ func CreateNetwork(ociBin string, networkName string) (net.IP, error) { subnetAddr := firstSubnetAddr // Rather than iterate through all of the valid subnets, give up at 20 to avoid a lengthy user delay for something that is unlikely to work. // will be like 192.168.49.0/24 ,...,192.168.239.0/24 - for attempts < 20 { - // check if subnetAddr overlaps with *any* existing local networks - free := true - ips, err := net.InterfaceAddrs() + for attempts < 2 { + subnet, err := util.GetFreePrivateNetwork(subnetAddr, 10, 10) if err != nil { - klog.Errorf("failed to get list of local network addresses: %v", err) - } else { - for _, ip := range ips { - _, lan, err := net.ParseCIDR(ip.String()) - if err != nil { - klog.Errorf("failed to parse local network address %q: %v", ip, err) - continue - } - if lan.Contains(net.ParseIP(subnetAddr)) { - free = false - break - } - } - } - if !free { - continue + klog.Warningf("failed to find free private network subnet starting with %q, step %d, tries %d: %v", subnetAddr, 10, 10, err) } + subnetAddr = subnet.IP info.gateway, err = tryCreateDockerNetwork(ociBin, subnetAddr, defaultSubnetMask, info.mtu, networkName) if err == nil { @@ -108,14 +93,6 @@ func CreateNetwork(ociBin string, networkName string) (net.IP, error) { return nil, errors.Wrap(err, "un-retryable") } attempts++ - // Find an open subnet by incrementing the 3rd octet by 10 for each try - // 13 times adding 10 firstSubnetAddr "192.168.49.0/24" - // at most it will add up to 169 which is still less than max allowed 255 - // this is large enough to try more and not too small to not try enough - // can be tuned in the next iterations - newSubnet := net.ParseIP(subnetAddr).To4() - newSubnet[2] += byte(9 + attempts) - subnetAddr = newSubnet.String() } return info.gateway, fmt.Errorf("failed to create network after 20 attempts") } diff --git a/pkg/drivers/kvm/network.go b/pkg/drivers/kvm/network.go index d60ae20fd3..ebc2e6c648 100644 --- a/pkg/drivers/kvm/network.go +++ b/pkg/drivers/kvm/network.go @@ -31,6 +31,7 @@ import ( "github.com/docker/machine/libmachine/log" libvirt "github.com/libvirt/libvirt-go" "github.com/pkg/errors" + "k8s.io/minikube/pkg/util" "k8s.io/minikube/pkg/util/retry" ) @@ -38,16 +39,25 @@ import ( // https://play.golang.org/p/m8TNTtygK0 const networkTmpl = ` - {{.PrivateNetwork}} + {{.Name}} - + - + ` +type kvmNetwork struct { + Name string + util.Network +} + +// firstSubnetAddr is starting subnet to try for new KVM cluster, +// avoiding possible conflict with other local networks by further incrementing it up to 20 times by 10. +const firstSubnetAddr = "192.168.39.0" + // setupNetwork ensures that the network with `name` is started (active) // and has the autostart feature set. func setupNetwork(conn *libvirt.Connect, name string) error { @@ -145,10 +155,19 @@ func (d *Driver) createNetwork() error { // Only create the private network if it does not already exist netp, err := conn.LookupNetworkByName(d.PrivateNetwork) if err != nil { + subnet, err := util.GetFreePrivateNetwork(firstSubnetAddr, 10, 20) + if err != nil { + return errors.Wrapf(err, "failed to find free private network subnet starting with %q, step: %d, tries:%d", firstSubnetAddr, 10, 20) + } + tryNet := kvmNetwork{ + Name: d.PrivateNetwork, + Network: *subnet, + } + // create the XML for the private network from our networkTmpl tmpl := template.Must(template.New("network").Parse(networkTmpl)) var networkXML bytes.Buffer - if err := tmpl.Execute(&networkXML, d); err != nil { + if err := tmpl.Execute(&networkXML, tryNet); err != nil { return errors.Wrap(err, "executing network template") } @@ -173,6 +192,7 @@ func (d *Driver) createNetwork() error { if err := retry.Local(create, 10*time.Second); err != nil { return errors.Wrapf(err, "creating network %s", d.PrivateNetwork) } + log.Debugf("Network %s created", d.PrivateNetwork) } defer func() { if netp != nil { diff --git a/pkg/minikube/cluster/ip.go b/pkg/minikube/cluster/ip.go index 54a715c3bd..fc5adeb267 100644 --- a/pkg/minikube/cluster/ip.go +++ b/pkg/minikube/cluster/ip.go @@ -47,7 +47,11 @@ func HostIP(host *host.Host, clusterName string) (net.IP, error) { } return net.ParseIP(ip), nil case driver.KVM2: - return net.ParseIP("192.168.39.1"), nil + ip, err := host.Driver.GetIP() + if err != nil { + return []byte{}, errors.Wrap(err, "Error getting VM/Host IP address") + } + return net.ParseIP(ip), nil case driver.HyperV: v := reflect.ValueOf(host.Driver).Elem() var hypervVirtualSwitch string diff --git a/pkg/util/network.go b/pkg/util/network.go new file mode 100644 index 0000000000..8a9c5a98b8 --- /dev/null +++ b/pkg/util/network.go @@ -0,0 +1,210 @@ +/* +Copyright 2021 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 util + +import ( + "encoding/binary" + "fmt" + "net" + + "github.com/pkg/errors" + "k8s.io/klog/v2" +) + +var ( + // valid private networks (RFC1918) + privateNetworks = []net.IPNet{ + // 10.0.0.0/8 + { + IP: []byte{10, 0, 0, 0}, + Mask: []byte{255, 0, 0, 0}, + }, + // 172.16.0.0/12 + { + IP: []byte{172, 16, 0, 0}, + Mask: []byte{255, 240, 0, 0}, + }, + // 192.168.0.0/16 + { + IP: []byte{192, 168, 0, 0}, + Mask: []byte{255, 255, 0, 0}, + }, + } +) + +// Network contains main network parameters. +type Network struct { + IP string // IP address of the network + Netmask string // form: 4-byte ('a.b.c.d') + CIDR string // form: CIDR + Gateway string // first IP address (assumed, not checked !) + ClientMin string // second IP address + ClientMax string // last IP address before broadcastS + Broadcast string // last IP address + Interface +} + +// Interface contains main network interface parameters. +type Interface struct { + IfaceName string + IfaceIPv4 string + IfaceMTU int + IfaceMAC string +} + +// GetNetworkFrom initialises IPv4 network struct from given address. +// address 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 is valid existsing interface address, network struct will also contain info about the respective interface. +func GetNetworkFrom(addr string) (*Network, error) { + n := &Network{} + + // extract ip from addr + ip, network, err := net.ParseCIDR(addr) + if err != nil { + ip = net.ParseIP(addr) + if ip == nil { + return nil, errors.Wrapf(err, "parsing address %q", addr) + } + } + + // check local interfaces + ifaces, _ := net.Interfaces() + for _, iface := range ifaces { + ifAddrs, err := iface.Addrs() + if err != nil { + return nil, errors.Wrapf(err, "listing addresses of network interface %+v", iface) + } + for _, ifAddr := range ifAddrs { + ifip, lan, err := net.ParseCIDR(ifAddr.String()) + if err != nil { + return nil, errors.Wrapf(err, "parsing address of network iface %+v", ifAddr) + } + if lan.Contains(ip) { + n.IfaceName = iface.Name + n.IfaceIPv4 = ifip.To4().String() + n.IfaceMTU = iface.MTU + n.IfaceMAC = iface.HardwareAddr.String() + n.Gateway = n.IfaceIPv4 + network = lan + break + } + } + } + + if network == nil { + ipnet := &net.IPNet{ + IP: ip, + Mask: ip.DefaultMask(), // assume default network mask + } + _, network, err = net.ParseCIDR(ipnet.String()) + if err != nil { + return nil, errors.Wrapf(err, "determining network address from %q", addr) + } + } + + n.IP = network.IP.String() + n.Netmask = net.IP(network.Mask).String() // form: 4-byte ('a.b.c.d') + n.CIDR = network.String() + + networkIP := binary.BigEndian.Uint32(network.IP) // IP address of the network + networkMask := binary.BigEndian.Uint32(network.Mask) // network mask + broadcastIP := (networkIP & networkMask) | (networkMask ^ 0xffffffff) // last network IP address + + broadcast := make(net.IP, 4) + binary.BigEndian.PutUint32(broadcast, broadcastIP) + n.Broadcast = broadcast.String() + + gateway := net.ParseIP(n.Gateway).To4() // has to be converted to 4-byte representation! + if gateway == nil { + gateway = make(net.IP, 4) + binary.BigEndian.PutUint32(gateway, networkIP+1) // assume first network IP address + n.Gateway = gateway.String() + } + gatewayIP := binary.BigEndian.Uint32(gateway) + + min := make(net.IP, 4) + binary.BigEndian.PutUint32(min, gatewayIP+1) // clients-from: first network IP address after gateway + n.ClientMin = min.String() + + max := make(net.IP, 4) + binary.BigEndian.PutUint32(max, broadcastIP-1) // clients-from: last network IP address before broadcast + n.ClientMax = max.String() + + return n, nil +} + +// IsNetworkTaken returns if local network subnet exists and any error occurred. +// If will return false in case of an error. +func IsNetworkTaken(subnet string) (bool, error) { + ips, err := net.InterfaceAddrs() + if err != nil { + return false, errors.Wrap(err, "listing local networks") + } + for _, ip := range ips { + _, lan, err := net.ParseCIDR(ip.String()) + if err != nil { + return false, errors.Wrapf(err, "parsing network iface address %q", ip) + } + if lan.Contains(net.ParseIP(subnet)) { + return true, nil + } + } + return false, nil +} + +// IsNetworkPrivate returns if subnet is a private network. +func IsNetworkPrivate(subnet string) bool { + for _, ipnet := range privateNetworks { + if ipnet.Contains(net.ParseIP(subnet)) { + return true + } + } + return false +} + +// GetFreePrivateNetwork will try to find an free private network starting with subnet, incrementing it in steps up to number of tries. +func GetFreePrivateNetwork(subnet string, step, tries int) (*Network, error) { + for try := 0; try < tries; try++ { + n, err := GetNetworkFrom(subnet) + if err != nil { + return nil, err + } + subnet = n.IP + if IsNetworkPrivate(subnet) { + taken, err := IsNetworkTaken(subnet) + if err != nil { + return nil, err + } + if !taken { + klog.Infof("using free private subnet %s: %+v", n.CIDR, n) + return n, nil + } + klog.Infof("skipping subnet %s that is taken: %+v", n.CIDR, n) + } else { + klog.Infof("skipping subnet %s that is not private", n.CIDR) + } + ones, _ := net.ParseIP(n.IP).DefaultMask().Size() + newSubnet := net.ParseIP(subnet).To4() + if ones <= 16 { + newSubnet[1] += byte(step) + } else { + newSubnet[2] += byte(step) + } + subnet = newSubnet.String() + } + return nil, fmt.Errorf("no free private network subnets found with given parameters") +}