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

pool: Randomize mac #447

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
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
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.22.0

require (
github.com/go-logr/logr v1.2.4
github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.1.2-0.20221215110210-ad3f3381681f
github.com/onsi/ginkgo/v2 v2.9.4
github.com/onsi/gomega v1.27.6
github.com/pkg/errors v0.9.1
Expand Down Expand Up @@ -52,7 +53,6 @@ require (
github.com/imdario/mergo v0.3.15 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.1.2-0.20221215110210-ad3f3381681f // indirect
github.com/kubernetes-csi/external-snapshotter/client/v4 v4.2.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect
Expand Down
10 changes: 6 additions & 4 deletions pkg/pool-manager/pod_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"

networkv1 "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1"
)

const tempPodName = "tempPodName"
Expand All @@ -40,7 +42,7 @@ func (p *PoolManager) AllocatePodMac(pod *corev1.Pod, isNotDryRun bool) error {
"macmap", p.macPoolMap,
"currentMac", p.currentMac.String())

networkValue, ok := pod.Annotations[NetworksAnnotation]
networkValue, ok := pod.Annotations[networkv1.NetworkAttachmentAnnot]
if !ok {
return nil
}
Expand All @@ -49,7 +51,7 @@ func (p *PoolManager) AllocatePodMac(pod *corev1.Pod, isNotDryRun bool) error {
// we want to connect the allocated mac from the webhook to a pod object in the podToMacPoolMap
// run it before multus added the status annotation
// this mean the pod is not ready
if _, ok := pod.Annotations[networksStatusAnnotation]; ok {
if _, ok := pod.Annotations[networkv1.NetworkStatusAnnot]; ok {
return nil
}

Expand Down Expand Up @@ -97,7 +99,7 @@ func (p *PoolManager) AllocatePodMac(pod *corev1.Pod, isNotDryRun bool) error {
if err != nil {
return err
}
pod.Annotations[NetworksAnnotation] = string(networkListJson)
pod.Annotations[networkv1.NetworkAttachmentAnnot] = string(networkListJson)

return nil
}
Expand Down Expand Up @@ -238,7 +240,7 @@ func (p *PoolManager) initPodMap() error {
continue
}

networkValue, ok := pod.Annotations[NetworksAnnotation]
networkValue, ok := pod.Annotations[networkv1.NetworkAttachmentAnnot]
if !ok {
continue
}
Expand Down
108 changes: 77 additions & 31 deletions pkg/pool-manager/pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ package pool_manager

import (
"context"
"crypto/rand"
"encoding/json"
"fmt"
"math/big"
"net"
"reflect"
"sync"
Expand All @@ -40,8 +42,6 @@ const (
RangeStartEnv = "RANGE_START"
RangeEndEnv = "RANGE_END"
RuntimeObjectFinalizerName = "k8s.v1.cni.cncf.io/kubeMacPool"
NetworksAnnotation = "k8s.v1.cni.cncf.io/networks"
networksStatusAnnotation = "k8s.v1.cni.cncf.io/networks-status"
TransactionTimestampAnnotation = "kubemacpool.io/transaction-timestamp"
mutatingWebhookConfigName = "kubemacpool-mutator"
virtualMachnesWebhookName = "mutatevirtualmachines.kubemacpool.io"
Expand Down Expand Up @@ -74,6 +74,8 @@ const (
OptOutMode OptMode = "Opt-out"
)

var ErrFull = errors.New("the range is full")

type macEntry struct {
instanceName string
macInstanceKey string // for vms, it holds the interface Name, for pods, it holds the network Name
Expand Down Expand Up @@ -179,41 +181,85 @@ func GetMacPoolSize(rangeStart, rangeEnd net.HardwareAddr) (int64, error) {
return endInt - startInt + 1, nil
}

// generateRandomMac generates a random MAC address within the specified range using GetMacPoolSize.
func generateRandomMac(rangeStart, rangeEnd net.HardwareAddr) (net.HardwareAddr, error) {
poolSize, err := GetMacPoolSize(rangeStart, rangeEnd)
if err != nil {
return nil, fmt.Errorf("failed to calculate MAC pool size: %w", err)
}
if poolSize <= 0 {
return nil, fmt.Errorf("invalid MAC pool size: %d", poolSize)
}

randomOffset, err := rand.Int(rand.Reader, big.NewInt(poolSize))
if err != nil {
return nil, fmt.Errorf("failed to generate random number: %w", err)
}

startInt, err := utils.ConvertHwAddrToInt64(rangeStart)
if err != nil {
return nil, fmt.Errorf("failed to convert rangeStart to int64: %w", err)
}

randomMacInt := startInt + randomOffset.Int64()

macString := fmt.Sprintf("%012X", randomMacInt)
macFormatted := fmt.Sprintf("%s:%s:%s:%s:%s:%s",
macString[0:2], macString[2:4], macString[4:6],
macString[6:8], macString[8:10], macString[10:12])

randomMac, err := net.ParseMAC(macFormatted)
if err != nil {
return nil, fmt.Errorf("failed to convert to MAC: %w", err)
}

return randomMac, nil
}
func (p *PoolManager) getFreeMac() (net.HardwareAddr, error) {
// this look will ensure that we check all the range
// first iteration from current mac to last mac in the range
// second iteration from first mac in the range to the latest one
for idx := 0; idx <= 1; idx++ {

// This loop runs from the current mac to the last one in the range
for {
if _, ok := p.macPoolMap[NewMacKey(p.currentMac.String())]; !ok {
log.V(1).Info("found unused mac", "mac", p.currentMac)
freeMac := make(net.HardwareAddr, len(p.currentMac))
copy(freeMac, p.currentMac)

// move to the next mac after we found a free one
// If we allocate a mac address then release it and immediately allocate the same one to another object
// we can have issues with dhcp and arp discovery
if p.currentMac.String() == p.rangeEnd.String() {
copy(p.currentMac, p.rangeStart)
} else {
p.currentMac = getNextMac(p.currentMac)
}

return freeMac, nil
}
if p.isMacPoolFull() {
return nil, ErrFull
}

if p.currentMac.String() == p.rangeEnd.String() {
break
}
p.currentMac = getNextMac(p.currentMac)
for {
randomMac, err := generateRandomMac(p.rangeStart, p.rangeEnd)
if err != nil {
return nil, err
}

copy(p.currentMac, p.rangeStart)
if err := checkCast(randomMac); err != nil {
continue
}
if _, used := p.macPoolMap[NewMacKey(randomMac.String())]; !used {
return randomMac, nil
}
}
}

// isMacPoolFull checks if the MAC pool is full based on the range and used MACs.
func (p *PoolManager) isMacPoolFull() bool {
usedMacsInRange := 0
for mac := range p.macPoolMap {
macAddr, _ := net.ParseMAC(mac.String())
if isWithinRange(macAddr, p.rangeStart, p.rangeEnd) {
usedMacsInRange++
}
}

poolSize, err := GetMacPoolSize(p.rangeStart, p.rangeEnd)
if err != nil {
return true
}

return int64(usedMacsInRange) >= poolSize
}

// isWithinRange checks if a MAC address is within the specified range.
func isWithinRange(mac, start, end net.HardwareAddr) bool {
macInt, _ := utils.ConvertHwAddrToInt64(mac)
startInt, _ := utils.ConvertHwAddrToInt64(start)
endInt, _ := utils.ConvertHwAddrToInt64(end)

return nil, fmt.Errorf("the range is full")
return macInt >= startInt && macInt <= endInt
}

func checkCast(mac net.HardwareAddr) error {
Expand Down
Loading
Loading