minikube/pkg/network/network.go

254 lines
8.0 KiB
Go

/*
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 network
import (
"encoding/binary"
"fmt"
"net"
"time"
"github.com/juju/mutex/v2"
"k8s.io/klog/v2"
"k8s.io/minikube/pkg/util/lock"
)
const defaultReservationPeriod = 1 * time.Minute
// Parameters contains main network parameters.
type Parameters struct {
IP string // IP address of network
Netmask string // dotted-decimal format ('a.b.c.d')
Prefix int // network prefix length (number of leading ones in network mask)
CIDR string // CIDR format ('a.b.c.d/n')
Gateway string // taken from network interface address or assumed as first network IP address from given addr
ClientMin string // second IP address
ClientMax string // last IP address before broadcast
Broadcast string // last IP address
IsPrivate bool // whether the IP is private or not
Interface
reservation mutex.Releaser // subnet reservation has lifespan of the process: "If a process dies while the mutex is held, the mutex is automatically released."
}
// Interface contains main network interface parameters.
type Interface struct {
IfaceName string
IfaceIPv4 string
IfaceMTU int
IfaceMAC string
}
// lookupInInterfaces iterates over all local network interfaces
// and tries to match "ip" with associated networks
// returns (network parameters, ip network, nil) if found
//
// (nil, nil, nil) it nof
// (nil, nil, error) if any error happened
func lookupInInterfaces(ip net.IP) (*Parameters, *net.IPNet, error) {
// check local network interfaces
ifaces, err := net.Interfaces()
if err != nil {
return nil, nil, fmt.Errorf("failed listing network interfaces: %w", err)
}
for _, iface := range ifaces {
ifAddrs, err := iface.Addrs()
if err != nil {
return nil, nil, fmt.Errorf("failed listing addresses of network interface %+v: %w", iface, err)
}
for _, ifAddr := range ifAddrs {
ifip, lan, err := net.ParseCIDR(ifAddr.String())
if err != nil {
return nil, nil, fmt.Errorf("failed parsing network interface address %+v: %w", ifAddr, err)
}
if lan.Contains(ip) {
ip4 := ifip.To4().String()
rt := Parameters{
Interface: Interface{
IfaceName: iface.Name,
IfaceIPv4: ip4,
IfaceMTU: iface.MTU,
IfaceMAC: iface.HardwareAddr.String(),
},
Gateway: ip4,
}
return &rt, lan, nil
}
}
}
return nil, nil, nil
}
// 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.
var inspect = func(addr string) (*Parameters, error) {
// extract ip from addr
ip, network, err := ParseAddr(addr)
if err != nil {
return nil, err
}
n := &Parameters{}
ifParams, ifNet, err := lookupInInterfaces(ip)
if err != nil {
return nil, err
}
if ifNet != nil {
network = ifNet
n = ifParams
}
// couldn't determine network parameters from addr nor from network interfaces
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, fmt.Errorf("failed determining address of network from %s: %w", addr, err)
}
}
n.IP = network.IP.String()
n.Netmask = net.IP(network.Mask).String() // dotted-decimal format ('a.b.c.d')
n.Prefix, _ = network.Mask.Size()
n.CIDR = network.String()
n.IsPrivate = network.IP.IsPrivate()
networkIP := binary.BigEndian.Uint32(network.IP) // IP address of 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
}
// isSubnetTaken returns if local network subnet exists and any error occurred.
// If will return false in case of an 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)
}
for _, ifAddr := range ifAddrs {
_, lan, err := net.ParseCIDR(ifAddr.String())
if err != nil {
return false, fmt.Errorf("failed parsing network interface address %+v: %w", ifAddr, err)
}
if lan.Contains(net.ParseIP(subnet)) {
return true, nil
}
}
return false, nil
}
// IsBuiltinQEMU returns if network is builtin or the legacy value user.
func IsBuiltinQEMU(network string) bool {
if network == "user" {
return true
}
return network == "builtin"
}
// 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(currSubnet)
if err != nil {
return nil, err
}
subnet := n.IP
if n.IsPrivate {
taken, err := isSubnetTaken(subnet)
if err != nil {
return nil, err
}
if !taken {
if reservation, err := reserveSubnet(subnet, defaultReservationPeriod); err == nil {
n.reservation = reservation
klog.Infof("using free private subnet %s: %+v", n.CIDR, n)
return n, nil
}
klog.Infof("skipping subnet %s that is reserved: %+v", n.CIDR, n)
} else {
klog.Infof("skipping subnet %s that is taken: %+v", n.CIDR, n)
}
} else {
klog.Infof("skipping subnet %s that is not private", n.CIDR)
}
prefix, _ := net.ParseIP(n.IP).DefaultMask().Size()
nextSubnet := net.ParseIP(currSubnet).To4()
if prefix <= 16 {
nextSubnet[1] += byte(step)
} else {
nextSubnet[2] += byte(step)
}
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)
}
// ParseAddr will try to parse an ip or a cidr address
func ParseAddr(addr string) (net.IP, *net.IPNet, error) {
ip, network, err := net.ParseCIDR(addr)
if err != nil {
ip = net.ParseIP(addr)
if ip == nil {
return nil, nil, fmt.Errorf("failed parsing address %s: %w", addr, err)
}
err = nil
}
return ip, network, err
}
// reserveSubnet returns releaser if subnet was successfully reserved for given period, creating lock for subnet to avoid race condition between multiple minikube instances (especially while testing in parallel).
var reserveSubnet = func(subnet string, period time.Duration) (mutex.Releaser, error) {
spec := lock.PathMutexSpec(subnet)
spec.Timeout = 1 * time.Millisecond // practically: just check, don't wait
reservation, err := mutex.Acquire(spec)
if err != nil {
return nil, err
}
return reservation, nil
}