2021-02-15 00:13:20 +00:00
/ *
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 .
* /
2021-02-15 16:00:50 +00:00
package network
2021-02-15 00:13:20 +00:00
import (
"encoding/binary"
"fmt"
"net"
2021-03-17 14:32:39 +00:00
"sync"
"time"
2021-02-15 00:13:20 +00:00
"k8s.io/klog/v2"
)
2021-03-17 14:32:39 +00:00
const defaultReservationPeriod = 1 * time . Minute
2021-02-15 00:13:20 +00:00
var (
2021-03-17 14:32:39 +00:00
reservedSubnets = sync . Map { }
2021-02-15 16:00:50 +00:00
// valid private network subnets (RFC1918)
privateSubnets = [ ] net . IPNet {
2021-02-15 00:13:20 +00:00
// 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 } ,
} ,
}
)
2021-03-17 14:32:39 +00:00
// reservation of free private subnet is held for defined reservation period from createdAt time.
type reservation struct {
createdAt time . Time
}
2021-02-15 16:00:50 +00:00
// Parameters contains main network parameters.
type Parameters struct {
2021-03-22 23:32:50 +00:00
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
2021-02-15 00:13:20 +00:00
ClientMin string // second IP address
2021-03-22 23:32:50 +00:00
ClientMax string // last IP address before broadcast
2021-02-15 00:13:20 +00:00
Broadcast string // last IP address
Interface
}
// Interface contains main network interface parameters.
type Interface struct {
IfaceName string
IfaceIPv4 string
IfaceMTU int
IfaceMAC string
}
2021-06-25 21:26:26 +00:00
// lookupInInterfaces iterates over all local network interfaces
// and tries to match "ip" with associated networks
// returns (network parameters, ip network, nil) if found
2022-08-08 16:29:19 +00:00
//
// (nil, nil, nil) it nof
// (nil, nil, error) if any error happened
2021-06-25 21:26:26 +00:00
func lookupInInterfaces ( ip net . IP ) ( * Parameters , * net . IPNet , error ) {
2021-03-22 23:32:50 +00:00
// check local network interfaces
ifaces , err := net . Interfaces ( )
if err != nil {
2021-06-25 21:26:26 +00:00
return nil , nil , fmt . Errorf ( "failed listing network interfaces: %w" , err )
2021-03-22 23:32:50 +00:00
}
2021-06-24 02:09:14 +00:00
2021-02-15 00:13:20 +00:00
for _ , iface := range ifaces {
2021-06-25 21:26:26 +00:00
2021-02-15 00:13:20 +00:00
ifAddrs , err := iface . Addrs ( )
if err != nil {
2021-06-25 21:26:26 +00:00
return nil , nil , fmt . Errorf ( "failed listing addresses of network interface %+v: %w" , iface , err )
2021-02-15 00:13:20 +00:00
}
2021-06-25 21:26:26 +00:00
2021-02-15 00:13:20 +00:00
for _ , ifAddr := range ifAddrs {
ifip , lan , err := net . ParseCIDR ( ifAddr . String ( ) )
if err != nil {
2021-06-25 21:26:26 +00:00
return nil , nil , fmt . Errorf ( "failed parsing network interface address %+v: %w" , ifAddr , err )
2021-02-15 00:13:20 +00:00
}
if lan . Contains ( ip ) {
2021-06-25 21:26:26 +00:00
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
2021-02-15 00:13:20 +00:00
}
}
}
2021-06-25 21:26:26 +00:00
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.
func inspect ( addr string ) ( * Parameters , error ) {
// extract ip from addr
ip , network , err := net . ParseCIDR ( addr )
if err != nil {
ip = net . ParseIP ( addr )
if ip == nil {
return nil , fmt . Errorf ( "failed parsing address %s: %w" , addr , err )
}
}
n := & Parameters { }
ifParams , ifNet , err := lookupInInterfaces ( ip )
if err != nil {
return nil , err
}
if ifNet != nil {
network = ifNet
n = ifParams
}
2021-02-15 00:13:20 +00:00
2021-03-22 23:32:50 +00:00
// couldn't determine network parameters from addr nor from network interfaces
2021-02-15 00:13:20 +00:00
if network == nil {
ipnet := & net . IPNet {
IP : ip ,
Mask : ip . DefaultMask ( ) , // assume default network mask
}
_ , network , err = net . ParseCIDR ( ipnet . String ( ) )
if err != nil {
2021-03-22 23:32:50 +00:00
return nil , fmt . Errorf ( "failed determining address of network from %s: %w" , addr , err )
2021-02-15 00:13:20 +00:00
}
}
n . IP = network . IP . String ( )
2021-03-22 23:32:50 +00:00
n . Netmask = net . IP ( network . Mask ) . String ( ) // dotted-decimal format ('a.b.c.d')
n . Prefix , _ = network . Mask . Size ( )
2021-02-15 00:13:20 +00:00
n . CIDR = network . String ( )
2021-03-22 23:32:50 +00:00
networkIP := binary . BigEndian . Uint32 ( network . IP ) // IP address of network
2021-02-15 00:13:20 +00:00
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
}
2021-02-17 03:03:41 +00:00
// isSubnetTaken returns if local network subnet exists and any error occurred.
2021-02-15 00:13:20 +00:00
// If will return false in case of an error.
2021-02-17 03:03:41 +00:00
func isSubnetTaken ( subnet string ) ( bool , error ) {
2021-03-22 23:32:50 +00:00
ifAddrs , err := net . InterfaceAddrs ( )
2021-02-15 00:13:20 +00:00
if err != nil {
2021-03-22 23:32:50 +00:00
return false , fmt . Errorf ( "failed listing network interface addresses: %w" , err )
2021-02-15 00:13:20 +00:00
}
2021-03-22 23:32:50 +00:00
for _ , ifAddr := range ifAddrs {
_ , lan , err := net . ParseCIDR ( ifAddr . String ( ) )
2021-02-15 00:13:20 +00:00
if err != nil {
2021-03-22 23:32:50 +00:00
return false , fmt . Errorf ( "failed parsing network interface address %+v: %w" , ifAddr , err )
2021-02-15 00:13:20 +00:00
}
if lan . Contains ( net . ParseIP ( subnet ) ) {
return true , nil
}
}
return false , nil
}
2021-03-22 23:32:50 +00:00
// isSubnetPrivate returns if subnet is private network.
2021-02-17 03:03:41 +00:00
func isSubnetPrivate ( subnet string ) bool {
2021-02-15 16:00:50 +00:00
for _ , ipnet := range privateSubnets {
2021-02-15 00:13:20 +00:00
if ipnet . Contains ( net . ParseIP ( subnet ) ) {
return true
}
}
return false
}
2022-09-26 20:58:48 +00:00
// IsUser returns if network is user.
func IsUser ( network string ) bool {
return network == "user"
}
2021-02-15 16:00:50 +00:00
// 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 ) {
2021-02-15 00:13:20 +00:00
for try := 0 ; try < tries ; try ++ {
2021-02-17 03:03:41 +00:00
n , err := inspect ( startSubnet )
2021-02-15 00:13:20 +00:00
if err != nil {
return nil , err
}
2021-02-15 16:00:50 +00:00
startSubnet = n . IP
2021-02-17 03:03:41 +00:00
if isSubnetPrivate ( startSubnet ) {
taken , err := isSubnetTaken ( startSubnet )
2021-02-15 00:13:20 +00:00
if err != nil {
return nil , err
}
if ! taken {
2021-03-17 14:32:39 +00:00
if ok := reserveSubnet ( startSubnet , defaultReservationPeriod ) ; ok {
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 )
2021-02-15 00:13:20 +00:00
}
} else {
klog . Infof ( "skipping subnet %s that is not private" , n . CIDR )
}
2021-03-22 23:32:50 +00:00
prefix , _ := net . ParseIP ( n . IP ) . DefaultMask ( ) . Size ( )
2021-02-15 16:00:50 +00:00
nextSubnet := net . ParseIP ( startSubnet ) . To4 ( )
2021-03-22 23:32:50 +00:00
if prefix <= 16 {
2021-02-15 16:00:50 +00:00
nextSubnet [ 1 ] += byte ( step )
2021-02-15 00:13:20 +00:00
} else {
2021-02-15 16:00:50 +00:00
nextSubnet [ 2 ] += byte ( step )
2021-02-15 00:13:20 +00:00
}
2021-02-15 16:00:50 +00:00
startSubnet = nextSubnet . String ( )
2021-02-15 00:13:20 +00:00
}
2021-02-15 16:43:37 +00:00
return nil , fmt . Errorf ( "no free private network subnets found with given parameters (start: %q, step: %d, tries: %d)" , startSubnet , step , tries )
2021-02-15 00:13:20 +00:00
}
2021-03-17 14:32:39 +00:00
// reserveSubnet returns if subnet was successfully reserved for given period:
2022-08-08 16:29:19 +00:00
// - false, if it already has unexpired reservation
// - true, if new reservation was created or expired one renewed
//
2021-03-17 14:32:39 +00:00
// uses sync.Map to manage reservations thread-safe
func reserveSubnet ( subnet string , period time . Duration ) bool {
// put 'zero' reservation{} Map value for subnet Map key
2021-04-06 19:27:11 +00:00
// to block other processes from concurrently changing this subnet
2021-03-17 14:32:39 +00:00
zero := reservation { }
r , loaded := reservedSubnets . LoadOrStore ( subnet , zero )
// check if there was previously issued reservation
if loaded {
// back off if previous reservation was already set to 'zero'
2021-04-06 19:27:11 +00:00
// as then other process is already managing this subnet concurrently
2021-03-17 14:32:39 +00:00
if r == zero {
klog . Infof ( "backing off reserving subnet %s (other process is managing it!): %+v" , subnet , & reservedSubnets )
return false
}
// check if previous reservation expired
createdAt := r . ( reservation ) . createdAt
if time . Since ( createdAt ) < period {
// unexpired reservation: restore original createdAt value
reservedSubnets . Store ( subnet , reservation { createdAt : createdAt } )
klog . Infof ( "skipping subnet %s that has unexpired reservation: %+v" , subnet , & reservedSubnets )
return false
}
// expired reservation: renew setting createdAt to now
reservedSubnets . Store ( subnet , reservation { createdAt : time . Now ( ) } )
klog . Infof ( "reusing subnet %s that has expired reservation: %+v" , subnet , & reservedSubnets )
return true
}
// new reservation
klog . Infof ( "reserving subnet %s for %v: %+v" , subnet , period , & reservedSubnets )
reservedSubnets . Store ( subnet , reservation { createdAt : time . Now ( ) } )
return true
}