Skip to content

Commit

Permalink
F-93: custom mac prefix support
Browse files Browse the repository at this point in the history
Signed-off-by: Jaime <jconchello@opennebula.io>
  • Loading branch information
jaimecb committed Nov 26, 2024
1 parent 1e8ec97 commit a1de3d1
Show file tree
Hide file tree
Showing 2 changed files with 166 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
package onelease

import (
"errors"
"fmt"
"net"
"strconv"
"strings"

"github.com/coredhcp/coredhcp/handler"
"github.com/coredhcp/coredhcp/logger"
"github.com/coredhcp/coredhcp/plugins"
"github.com/insomniacslk/dhcp/dhcpv4"
)

var log = logger.GetLogger("plugins/onelease")

// Plugin wraps the information necessary to register a plugin.
// In the main package, you need to export a `plugins.Plugin` object called
// `Plugin`, so it can be registered into the plugin registry.
Expand Down Expand Up @@ -58,6 +59,12 @@ var Plugin = plugins.Plugin{
Setup4: setup4,
}

var (
log = logger.GetLogger("plugins/onelease")
macPrefix = [2]byte{0x02, 0x00}
excludedIpSet = make(map[string]struct{})
)

// setup6 is the setup function to initialize the handler for DHCPv6
// traffic. This function implements the `plugin.SetupFunc6` interface.
// This function returns a `handler.Handler6` function, and an error if any.
Expand All @@ -74,6 +81,32 @@ var Plugin = plugins.Plugin{
// implements the `plugin.SetupFunc4` interface.
func setup4(args ...string) (handler.Handler4, error) {
log.Printf("loaded plugin for DHCPv4.")
if len(args) > 1 {
log.Errorf("Expected one MAC prefix, but received %d", len(args))
return nil, errors.New("one_lease failed to initialize (at most one MAC prefix expected)")
}

if len(args) == 0 {
log.Printf("No MAC prefix provided. Using default: %02x:%02x", macPrefix[0], macPrefix[1])
return ipMACHandler4, nil
}

bytes := strings.Split(args[0], ":")
if len(bytes) != 2 {
log.Error("Invalid MAC prefix provided")
return nil, errors.New("one_lease failed to initialize (invalid MAC prefix format)")
}

tempMacPrefix := make([]byte, 2)
for index, macByte := range bytes {
value, err := strconv.ParseUint(macByte, 16, 8)
if err != nil {
log.Errorf("Invalid hexadecimal value '%s': %v", macByte, err)
return nil, errors.New("one_lease failed to initialize (invalid hexadecimal value)")
}
tempMacPrefix[index] = byte(value)
}
copy(macPrefix[:], tempMacPrefix)
return ipMACHandler4, nil
}

Expand Down Expand Up @@ -130,12 +163,19 @@ func getIPFromMAC(req *dhcpv4.DHCPv4) (net.IP, error) {
return nil, fmt.Errorf("invalid MAC address: %v", mac)
}

// verify that the two first bytes equal to "02:00"
if mac[0] != 0x02 || mac[1] != 0x00 {
// verify that the two first bytes equal to macPrefix
if mac[0] != macPrefix[0] || mac[1] != macPrefix[1] {
return nil, fmt.Errorf("MAC address %s is not from OpenNebula", mac)
}

// retrieve the IP address from the MAC address
// the IP address is the last 4 bytes of the MAC address
return net.IPv4(mac[2], mac[3], mac[4], mac[5]), nil
ip := net.IPv4(mac[2], mac[3], mac[4], mac[5])

if _, exists := excludedIpSet[ip.String()]; exists {
// TODO: IP excluded
return nil, nil
} else {
return ip, nil
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package onelease

import (
"net"
"testing"

"github.com/insomniacslk/dhcp/dhcpv4"
"github.com/stretchr/testify/assert"
)

func TestGetIPFromMAC(t *testing.T) {
// Test cases
testCases := []struct {
name string
macAddress net.HardwareAddr
expectedIP net.IP
shouldFail bool
expectedErrMsg string
}{
{
name: "Valid OpenNebula MAC",
macAddress: net.HardwareAddr{0x02, 0x00, 0x0A, 0x0B, 0x0C, 0x0D},
expectedIP: net.IPv4(0x0A, 0x0B, 0x0C, 0x0D),
shouldFail: false,
},
{
name: "Invalid MAC Prefix",
macAddress: net.HardwareAddr{0x00, 0x01, 0x0A, 0x0B, 0x0C, 0x0D},
shouldFail: true,
expectedErrMsg: "MAC address 00:01:0a:0b:0c:0d is not from OpenNebula",
},
{
name: "Invalid MAC Length",
macAddress: net.HardwareAddr{0x02, 0x00, 0x0A},
shouldFail: true,
expectedErrMsg: "invalid MAC address",
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Prepare a mock DHCP request
req := &dhcpv4.DHCPv4{
ClientHWAddr: tc.macAddress,
}

// Reset macPrefix
macPrefix = [2]byte{0x02, 0x00}

ip, err := getIPFromMAC(req)

if tc.shouldFail {
assert.Error(t, err)
if tc.expectedErrMsg != "" {
assert.Contains(t, err.Error(), tc.expectedErrMsg)
}
} else {
assert.NoError(t, err)
assert.Equal(t, tc.expectedIP, ip)
}
})
}
}

func TestSetup4(t *testing.T) {
testCases := []struct {
name string
args []string
shouldFail bool
expectedPrefix [2]byte
}{
{
name: "No Arguments (Default)",
args: []string{},
expectedPrefix: [2]byte{0x02, 0x00},
},
{
name: "Nil as argument (Default)",
args: []string{},
expectedPrefix: [2]byte{0x02, 0x00},
},
{
name: "Custom MAC Prefix",
args: []string{"AA:BB"},
expectedPrefix: [2]byte{0xAA, 0xBB},
},
{
name: "Invalid MAC Prefix Format",
args: []string{"AAA:BBB"},
shouldFail: true,
},
{
name: "Too Many Arguments",
args: []string{"AA:BB", "CC:DD"},
shouldFail: true,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Restore default prefix before each test
macPrefix = [2]byte{0x02, 0x00}

handler, err := setup4(tc.args...)

if tc.shouldFail {
assert.Error(t, err)
assert.Nil(t, handler)
} else {
assert.NoError(t, err)
assert.NotNil(t, handler)

// Explicitly check the MAC prefix was set correctly
assert.Equal(t, tc.expectedPrefix[0], macPrefix[0],
"First byte of MAC prefix should match")
assert.Equal(t, tc.expectedPrefix[1], macPrefix[1],
"Second byte of MAC prefix should match")
}
})
}
}

0 comments on commit a1de3d1

Please sign in to comment.