From 6b3ab4fb353b65e1416980387666bd5a2775f759 Mon Sep 17 00:00:00 2001 From: Damyan Yordanov Date: Mon, 18 Nov 2024 14:57:24 +0100 Subject: [PATCH 1/3] Make prefix delegation length configurable --- internal/api/onmetal_config.go | 12 +++++++ plugins/onmetal/plugin.go | 52 +++++++++++++++++++++++---- plugins/onmetal/plugin_test.go | 64 ++++++++++++++++++++++++++++++---- 3 files changed, 115 insertions(+), 13 deletions(-) create mode 100644 internal/api/onmetal_config.go diff --git a/internal/api/onmetal_config.go b/internal/api/onmetal_config.go new file mode 100644 index 0000000..30de297 --- /dev/null +++ b/internal/api/onmetal_config.go @@ -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"` +} diff --git a/plugins/onmetal/plugin.go b/plugins/onmetal/plugin.go index 09fd29b..03d76ba 100644 --- a/plugins/onmetal/plugin.go +++ b/plugins/onmetal/plugin.go @@ -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" @@ -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 } @@ -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 { diff --git a/plugins/onmetal/plugin_test.go b/plugins/onmetal/plugin_test.go index 40f6fe6..a64dfe0 100644 --- a/plugins/onmetal/plugin_test.go +++ b/plugins/onmetal/plugin_test.go @@ -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" ) @@ -17,13 +21,26 @@ 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, err := yaml.Marshal(data) + + file, err := 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) } @@ -31,9 +48,19 @@ func Init6() { /* 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") } } @@ -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) } @@ -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) } @@ -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, err := yaml.Marshal(data) + + file, err := 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") + } +} From 32608e1883b1faafbb2c538083be171e4f572a4b Mon Sep 17 00:00:00 2001 From: Damyan Yordanov Date: Mon, 18 Nov 2024 15:13:21 +0100 Subject: [PATCH 2/3] Update readme --- README.md | 9 +++++++-- example/config.yaml | 2 +- example/onmetal_config.yaml | 2 ++ 3 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 example/onmetal_config.yaml diff --git a/README.md b/README.md index 7ce959a..c9bdbf9 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/example/config.yaml b/example/config.yaml index f4a41e6..4c0b16d 100644 --- a/example/config.yaml +++ b/example/config.yaml @@ -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 diff --git a/example/onmetal_config.yaml b/example/onmetal_config.yaml new file mode 100644 index 0000000..7f72aa1 --- /dev/null +++ b/example/onmetal_config.yaml @@ -0,0 +1,2 @@ +prefixDelegation: + length: 64 \ No newline at end of file From 798288d682fc0d7f9bd98fab084915a819612621 Mon Sep 17 00:00:00 2001 From: Damyan Yordanov Date: Mon, 18 Nov 2024 15:36:40 +0100 Subject: [PATCH 3/3] Fix some lint findings --- plugins/onmetal/plugin_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/onmetal/plugin_test.go b/plugins/onmetal/plugin_test.go index a64dfe0..9161276 100644 --- a/plugins/onmetal/plugin_test.go +++ b/plugins/onmetal/plugin_test.go @@ -31,16 +31,16 @@ func Init6() { }, } - configData, err := yaml.Marshal(data) + configData, _ := yaml.Marshal(data) - file, err := os.CreateTemp("", "config.yaml") + file, _ := os.CreateTemp("", "config.yaml") defer func() { _ = file.Close() _ = os.Remove(file.Name()) }() _ = os.WriteFile(file.Name(), configData, 0644) - _, err = setup6(file.Name()) + _, err := setup6(file.Name()) if err != nil { log.Fatal(err) } @@ -295,16 +295,16 @@ func TestPrefixDelegationNotRequested7(t *testing.T) { }, } - configData, err := yaml.Marshal(data) + configData, _ := yaml.Marshal(data) - file, err := os.CreateTemp("", "config.yaml") + file, _ := os.CreateTemp("", "config.yaml") defer func() { _ = file.Close() _ = os.Remove(file.Name()) }() _ = os.WriteFile(file.Name(), configData, 0644) - _, err = setup6(file.Name()) + _, err := setup6(file.Name()) if err == nil { t.Fatal("no error occurred when providing wrong prefix delegation length, but it should have") }