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

Add prefix delegation configuration for onmetal #213

Merged
merged 3 commits into from
Nov 18, 2024
Merged
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
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,14 @@ Further, as a second parameter, a comma-separated list of subnet names shall be
- depends on [IPAM operator](https://github.com/ironcore-dev/ipam)

## OnMetal
The OnMetal plugin leases a [non temporary IPv6 address](https://datatracker.ietf.org/doc/html/rfc8415#section-6.2) to an in-band client, based on the algorithm described above.
The OnMetal plugin leases a [non temporary IPv6 address](https://datatracker.ietf.org/doc/html/rfc8415#section-6.2) to an in-band client, based on the algorithm described above. Additionally, when requested from the client, a prefix delegation with preconfigured length is leased. Currently multiple prefix delegations are not supported, client prefix delegation length proposals are ignored completely. The prefix delegation length should be in the range 1 <= length <= 127.
### Configuration
No configuration is needed
The onmetal configuration consists of the prefix delegation length only.
Providing the length in `onmetal_config.yaml` goes as follows:
```yaml
prefixDelegation:
length: 64
```
### Notes
- supports only IPv6
- IPv6 relays are mandatory
Expand Down
2 changes: 1 addition & 1 deletion example/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ server6:
# add leased IPs to ironcore's IPAM
- ipam: ipam-ns ipam-subnet1,ipam-subnet2,some-other-subnet
# lease IPs based on /127 subnets coming from relays running on the switches
- onmetal:
- onmetal: onmetal_config.yaml
# announce DNS servers per DHCP
- dns: 2001:4860:4860::6464 2001:4860:4860::64
# implement (i)PXE boot
Expand Down
2 changes: 2 additions & 0 deletions example/onmetal_config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
prefixDelegation:
length: 64
12 changes: 12 additions & 0 deletions internal/api/onmetal_config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and IronCore contributors
// SPDX-License-Identifier: MIT

package api

type PrefixDelegation struct {
Length int `yaml:"length"`
}

type OnMetalConfig struct {
PrefixDelegation PrefixDelegation `yaml:"prefixDelegation"`
}
52 changes: 45 additions & 7 deletions plugins/onmetal/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@ package onmetal
import (
"fmt"
"net"
"os"
"time"

"github.com/ironcore-dev/fedhcp/internal/api"
"gopkg.in/yaml.v3"

"github.com/coredhcp/coredhcp/handler"
"github.com/coredhcp/coredhcp/logger"
"github.com/coredhcp/coredhcp/plugins"
Expand All @@ -21,19 +25,52 @@ var Plugin = plugins.Plugin{
Setup6: setup6,
}

var mask80 = net.CIDRMask(prefixLength, 128)
var prefixLength int

const (
preferredLifeTime = 24 * time.Hour
validLifeTime = 24 * time.Hour
prefixLength = 80
preferredLifeTime = 24 * time.Hour
validLifeTime = 24 * time.Hour
prefixDelegationLengthMin = 1
prefixDelegationLengthMax = 127
)

// args[0] = path to config file
func parseArgs(args ...string) (string, error) {
if len(args) != 1 {
return "", fmt.Errorf("exactly one argument must be passed to the onmetal plugin, got %d", len(args))
}
return args[0], nil
}

func loadConfig(args ...string) (*api.OnMetalConfig, error) {
path, err := parseArgs(args...)
if err != nil {
return nil, fmt.Errorf("invalid configuration: %v", err)
}

log.Debugf("Reading metal config file %s", path)
configData, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("failed to read config file: %v", err)
}

config := &api.OnMetalConfig{}
if err = yaml.Unmarshal(configData, config); err != nil {
return nil, fmt.Errorf("failed to parse config file: %v", err)
}
return config, nil
}

func setup6(args ...string) (handler.Handler6, error) {
if len(args) != 0 {
return nil, fmt.Errorf("no arguments expected, got %d", len(args))
onMetalConfig, err := loadConfig(args...)
if err != nil {
return nil, err
}

prefixLength = onMetalConfig.PrefixDelegation.Length
if prefixLength < prefixDelegationLengthMin || prefixLength > prefixDelegationLengthMax {
return nil, fmt.Errorf("invalid prefix length: %d", prefixLength)
}
log.Printf("loaded onmetal plugin for DHCPv6.")

return handler6, nil
}
Expand Down Expand Up @@ -79,6 +116,7 @@ func handler6(req, resp dhcpv6.DHCPv6) (dhcpv6.DHCPv6, bool) {
optIAPD := m.Options.OneIAPD()
T1 := preferredLifeTime
T2 := validLifeTime
var mask80 = net.CIDRMask(prefixLength, 128)

if optIAPD != nil {
if optIAPD.T1 != 0 {
Expand Down
64 changes: 58 additions & 6 deletions plugins/onmetal/plugin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@ package onmetal

import (
"net"
"os"
"testing"

"github.com/ironcore-dev/fedhcp/internal/api"
"gopkg.in/yaml.v3"

"github.com/insomniacslk/dhcp/dhcpv6"
)

Expand All @@ -17,23 +21,46 @@ const (
)

var (
expectedIPAddress6 = net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2}
expectedIAID = [4]byte{1, 2, 3, 4}
expectedPrefix = "2001:db8:1111:2222:3333::/80"
expectedIAID = [4]byte{1, 2, 3, 4}
)

func Init6() {
_, err := setup6()
data := api.OnMetalConfig{
PrefixDelegation: api.PrefixDelegation{
Length: 80,
},
}

configData, _ := yaml.Marshal(data)

file, _ := os.CreateTemp("", "config.yaml")
defer func() {
_ = file.Close()
_ = os.Remove(file.Name())
}()
_ = os.WriteFile(file.Name(), configData, 0644)

_, err := setup6(file.Name())
if err != nil {
log.Fatal(err)
}
}

/* parametrization */
func TestWrongNumberArgs(t *testing.T) {
_, err := setup6("not-needed-arg")
_, err := setup6()
if err == nil {
t.Fatal("no error occurred when providing wrong number of args (1), but it should have")
t.Fatal("no error occurred when not providing a configuration file path, but it should have")
}

_, err = setup6("non-existing.yaml")
if err == nil {
t.Fatal("no error occurred when providing non existing configuration path, but it should have")
}

_, err = setup6("foo", "bar")
if err == nil {
t.Fatal("no error occurred when providing wrong number of args (2), but it should have")
}
}

Expand Down Expand Up @@ -91,6 +118,7 @@ func TestIPAddressRequested6(t *testing.T) {
t.Errorf("expected IAID %d, got %d", expectedIAID, iana.IaId)
}

expectedIPAddress6 := net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2}
if !addr.Equal(expectedIPAddress6) {
t.Errorf("expected IPv6 address %v, got %v", expectedIPAddress6, iana.Options.OneAddress().IPv6Addr)
}
Expand Down Expand Up @@ -217,6 +245,7 @@ func TestPrefixDelegationRequested6(t *testing.T) {
t.Errorf("expected IAID %d, got %d", expectedIAID, iapd.IaId)
}

expectedPrefix := "2001:db8:1111:2222:3333::/80"
if pref.String() != expectedPrefix {
t.Errorf("expected prefix %v, got %v", expectedPrefix, pref)
}
Expand Down Expand Up @@ -257,3 +286,26 @@ func TestPrefixDelegationNotRequested6(t *testing.T) {
t.Fatalf("Expected %d IAPD option, got %d: %v", optionDisabled, len(opts), opts)
}
}

func TestPrefixDelegationNotRequested7(t *testing.T) {
prefixDelegationLengthOutOfBounds := 128
data := api.OnMetalConfig{
PrefixDelegation: api.PrefixDelegation{
Length: prefixDelegationLengthOutOfBounds,
},
}

configData, _ := yaml.Marshal(data)

file, _ := os.CreateTemp("", "config.yaml")
defer func() {
_ = file.Close()
_ = os.Remove(file.Name())
}()
_ = os.WriteFile(file.Name(), configData, 0644)

_, err := setup6(file.Name())
if err == nil {
t.Fatal("no error occurred when providing wrong prefix delegation length, but it should have")
}
}
Loading