Skip to content

Commit

Permalink
Implement OOB DHCPv4 (#145)
Browse files Browse the repository at this point in the history
  • Loading branch information
damyan authored May 15, 2024
1 parent de0954c commit 709188a
Show file tree
Hide file tree
Showing 2 changed files with 112 additions and 21 deletions.
59 changes: 41 additions & 18 deletions plugins/oob/k8s.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ func NewK8sClient(namespace string, oobLabel string) (*K8sClient, error) {
return &k8sClient, nil
}

func (k K8sClient) getIp(ipaddr net.IP, mac net.HardwareAddr) (net.IP, error) {
func (k K8sClient) getIp(ipaddr net.IP, mac net.HardwareAddr, exactIP bool) (net.IP, error) {
var ipamIP *ipamv1alpha1.IP
macKey := strings.ReplaceAll(mac.String(), ":", "")

Expand All @@ -110,7 +110,7 @@ func (k K8sClient) getIp(ipaddr net.IP, mac net.HardwareAddr) (net.IP, error) {
return nil, err
}
if ipamIP == nil {
ipamIP, err = k.doCreateIpamIP(subnetName, macKey)
ipamIP, err = k.doCreateIpamIP(subnetName, macKey, ipaddr, exactIP)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -183,25 +183,48 @@ func (k K8sClient) prepareCreateIpamIP(subnetName string, macKey string) (*ipamv
return nil, nil
}

func (k K8sClient) doCreateIpamIP(subnetName string, macKey string) (*ipamv1alpha1.IP, error) {
func (k K8sClient) doCreateIpamIP(subnetName string, macKey string, ipaddr net.IP, exactIP bool) (*ipamv1alpha1.IP, error) {
oobLabelKey := strings.Split(k.OobLabel, "=")[0]
oobLabelValue := strings.Split(k.OobLabel, "=")[1]
ipamIP := &ipamv1alpha1.IP{
ObjectMeta: metav1.ObjectMeta{
GenerateName: macKey + "-" + origin + "-",
Namespace: k.Namespace,
Labels: map[string]string{
"mac": macKey,
"origin": origin,
oobLabelKey: oobLabelValue,
var ipamIP *ipamv1alpha1.IP
if ipaddr.String() == UNKNOWN_IP || !exactIP {
ipamIP = &ipamv1alpha1.IP{
ObjectMeta: metav1.ObjectMeta{
GenerateName: macKey + "-" + origin + "-",
Namespace: k.Namespace,
Labels: map[string]string{
"mac": macKey,
"origin": origin,
oobLabelKey: oobLabelValue,
},
},
},
Spec: ipamv1alpha1.IPSpec{
Subnet: corev1.LocalObjectReference{
Name: subnetName,
Spec: ipamv1alpha1.IPSpec{
Subnet: corev1.LocalObjectReference{
Name: subnetName,
},
},
},
}
} else {
ip, _ := ipamv1alpha1.IPAddrFromString(ipaddr.String())
ipamIP = &ipamv1alpha1.IP{
ObjectMeta: metav1.ObjectMeta{
GenerateName: macKey + "-" + origin + "-",
Namespace: k.Namespace,
Labels: map[string]string{
"mac": macKey,
"origin": origin,
oobLabelKey: oobLabelValue,
},
},
Spec: ipamv1alpha1.IPSpec{
IP: ip,
Subnet: corev1.LocalObjectReference{
Name: subnetName,
},
},
}
}

err := k.Client.Create(k.Ctx, ipamIP)
if err != nil && !apierrors.IsAlreadyExists(err) {
return nil, fmt.Errorf("failed to create IP %s/%s: %w", ipamIP.Namespace, ipamIP.Name, err)
Expand Down Expand Up @@ -328,7 +351,7 @@ func (k K8sClient) getMatchingSubnet(subnetName string, ipaddr net.IP) (*ipamv1a
log.Debugf("Cannot select subnet %s/%s, does not exist", k.Namespace, subnetName)
return nil, nil
}
if !checkIPv6InCIDR(ipaddr, existingSubnet.Status.Reserved.String()) {
if !checkIPInCIDR(ipaddr, existingSubnet.Status.Reserved.String()) && ipaddr.String() != UNKNOWN_IP {
log.Debugf("Cannot select subnet %s/%s, CIDR mismatch", k.Namespace, subnetName)
return nil, nil
}
Expand Down Expand Up @@ -378,7 +401,7 @@ func prettyFormat(ipSpec interface{}) string {
return string(jsonBytes)
}

func checkIPv6InCIDR(ip net.IP, cidrStr string) bool {
func checkIPInCIDR(ip net.IP, cidrStr string) bool {
// Parse the CIDR string
_, cidrNet, err := net.ParseCIDR(cidrStr)
if err != nil {
Expand Down
74 changes: 71 additions & 3 deletions plugins/oob/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ package oob

import (
"fmt"
"net"
"time"

"github.com/coredhcp/coredhcp/handler"
"github.com/coredhcp/coredhcp/logger"
"github.com/coredhcp/coredhcp/plugins"
"github.com/insomniacslk/dhcp/dhcpv4"
"github.com/insomniacslk/dhcp/dhcpv6"
"net"
"time"

"github.com/mdlayher/netx/eui64"
)
Expand All @@ -19,13 +21,18 @@ var log = logger.GetLogger("plugins/oob")

var Plugin = plugins.Plugin{
Name: "oob",
Setup4: setup4,
Setup6: setup6,
}

var (
k8sClient *K8sClient
)

const (
UNKNOWN_IP = "0.0.0.0"
)

func parseArgs(args ...string) (string, string, error) {
if len(args) < 2 {
return "", "", fmt.Errorf("at least two arguments must be passed to ipam plugin, a namespace and a OOB subnet label, got %d", len(args))
Expand Down Expand Up @@ -72,7 +79,7 @@ func handler6(req, resp dhcpv6.DHCPv6) (dhcpv6.DHCPv6, bool) {
copy(ipaddr, relayMsg.LinkAddr)

log.Infof("Requested IP address from relay %s for mac %s", ipaddr.String(), mac.String())
leaseIP, err := k8sClient.getIp(ipaddr, mac)
leaseIP, err := k8sClient.getIp(ipaddr, mac, false)
if err != nil {
log.Errorf("Could not get IPAM IP: %s", err)
return nil, true
Expand Down Expand Up @@ -105,3 +112,64 @@ func handler6(req, resp dhcpv6.DHCPv6) (dhcpv6.DHCPv6, bool) {

return resp, false
}

func setup4(args ...string) (handler.Handler4, error) {
namespace, oobLabel, err := parseArgs(args...)
if err != nil {
return nil, err
}

k8sClient, err = NewK8sClient(namespace, oobLabel)
if err != nil {
return nil, fmt.Errorf("failed to create k8s client: %w", err)
}

log.Print("Loaded oob plugin for DHCPv4.")
return handler4, nil
}

func handler4(req, resp *dhcpv4.DHCPv4) (*dhcpv4.DHCPv4, bool) {
mac := req.ClientHWAddr

log.Debugf("received DHCPv4 packet: %s", req.Summary())
log.Tracef("Message type: %s", req.MessageType().String())

var ipaddr net.IP
var exactIP bool

serverIP := resp.ServerIPAddr
clientIP := req.ClientIPAddr
requestedIP := dhcpv4.GetIP(dhcpv4.OptionRequestedIPAddress, req.Options)
if clientIP != nil {
// ack requested address
exactIP = true
ipaddr = clientIP
log.Debugf("IP client: %v", ipaddr)
} else if requestedIP != nil {
// ack requested address
exactIP = true
ipaddr = requestedIP
log.Debugf("IP client: %v", ipaddr)
} else if serverIP != nil {
// no client information, use server address for subnet detection
exactIP = false
ipaddr = serverIP
log.Debugf("IP server: %v", ipaddr)
} else {
ipaddr = net.ParseIP(UNKNOWN_IP)
exactIP = false
}

log.Debugf("IP: %v", ipaddr)
leaseIP, err := k8sClient.getIp(ipaddr, mac, exactIP)
if err != nil {
log.Errorf("Could not get IPAM IP: %s", err)
return nil, true
}

resp.YourIPAddr = leaseIP

log.Debugf("Sent DHCPv4 response: %s", resp.Summary())

return resp, false
}

0 comments on commit 709188a

Please sign in to comment.