Skip to content
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
26 changes: 25 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ The IP address to lease shall be passed as a string in `bluefield_config.yaml` g
```yaml
bluefieldIP: 2001:db8::1
```
The leased address preferred and valid lifetimes default to 24 hours and are optionally configurable:
```yaml
leaseTimes:
preferredLifetime: 24h
validLifetime: 24h
```


### Notes
Expand Down Expand Up @@ -111,6 +117,12 @@ Providing the length in `onmetal_config.yaml` goes as follows:
prefixDelegation:
length: 64
```
The leased address preferred and valid lifetimes default to 24 hours and are optionally configurable:
```yaml
leaseTimes:
preferredLifetime: 24h
validLifetime: 24h
```
Comment on lines +120 to +125

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Update the OnMetal configuration description.

Line 114 still says that the configuration contains only the prefix delegation length. The new leaseTimes settings make that statement incorrect. Update the description to mention both settings.

Proposed wording
- The onmetal configuration consists of the prefix delegation length only.
+ The OnMetal configuration consists of the prefix delegation length and optional lease times.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 120 - 125, Update the OnMetal configuration
description near the leaseTimes example so it no longer says the configuration
contains only the prefix delegation length. Mention both configurable leaseTimes
settings—preferred and valid lifetimes—alongside the prefix delegation length,
while preserving the existing YAML example.

### Notes
- supports only IPv6
- IPv6 relays are mandatory
Expand All @@ -131,6 +143,12 @@ subnetLabels:
- key: foo
value: bar
```
The leased address preferred and valid lifetimes default to 24 hours and are optionally configurable:
```yaml
leaseTimes:
preferredLifetime: 24h
validLifetime: 24h
```
### Notes
- supports both IPv4 and IPv6
- IPv6 relays are supported, IPv4 are not
Expand Down Expand Up @@ -258,7 +276,13 @@ The Stateless plugin derives deterministic IPv6 addresses from DHCPv6 relay mess

For example, given a link address of `2001:db8:1111:2222:3333::` and a MAC of `aa:bb:cc:dd:ee:ff`, the resulting address is `2001:db8:1111:2222:3333:aabb:ccdd:eeff`.

Addresses are leased as [non temporary IPv6 addresses](https://datatracker.ietf.org/doc/html/rfc8415#section-6.2) with a 24-hour lifetime.
Addresses are leased as [non temporary IPv6 addresses](https://datatracker.ietf.org/doc/html/rfc8415#section-6.2). The preferred and valid lifetimes default to 24 hours and are configurable via an optional config file (`stateless_config.yaml`):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use “non-temporary” in the Stateless description.

Replace “non temporary IPv6 addresses” with “non-temporary IPv6 addresses”.

🧰 Tools
🪛 LanguageTool

[grammar] ~279-~279: Use a hyphen to join words.
Context: ...cdd:eeff`. Addresses are leased as [non temporary IPv6 addresses](https://datatr...

(QB_NEW_EN_HYPHEN)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` at line 279, Update the Stateless description to use the
hyphenated term “non-temporary IPv6 addresses” in place of “non temporary IPv6
addresses,” without changing the surrounding documentation.

Source: Linters/SAST tools

```yaml
leaseTimes:
preferredLifetime: 24h
validLifetime: 24h
```
When no config file is provided, the defaults (24h) are used.

### Notes
- supports IPv6 only
Expand Down
6 changes: 5 additions & 1 deletion example/bluefield_config.yaml
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
bluefieldIP: 2001:db8::1
bluefieldIP: 2001:db8::1
# leaseTimes is optional; both default to 24h when omitted.
leaseTimes:
preferredLifetime: 24h
validLifetime: 24h
6 changes: 5 additions & 1 deletion example/onmetal_config.yaml
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
prefixDelegation:
length: 64
length: 64
# leaseTimes is optional; both default to 24h when omitted.
leaseTimes:
preferredLifetime: 24h
validLifetime: 24h
6 changes: 5 additions & 1 deletion example/oob_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,8 @@ subnetLabels:
- key: dhcp
value: "true"
- key: key-2
value: value-2
value: value-2
# leaseTimes is optional; both default to 24h when omitted.
leaseTimes:
preferredLifetime: 24h
validLifetime: 24h
4 changes: 4 additions & 0 deletions example/stateless_config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Lease times are optional; both default to 24h when omitted.
leaseTimes:
preferredLifetime: 24h
validLifetime: 24h
3 changes: 2 additions & 1 deletion internal/api/bluefield_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
package api

type BluefieldConfig struct {
BluefieldIP string `yaml:"bluefieldIP"`
BluefieldIP string `yaml:"bluefieldIP"`
LeaseTimes LeaseTimes `yaml:"leaseTimes"`
}
52 changes: 52 additions & 0 deletions internal/api/leasetime_config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and IronCore contributors
// SPDX-License-Identifier: MIT

package api

import (
"fmt"
"time"
)

// DefaultLeaseTime is the default preferred and valid lifetime for DHCP leases.
const DefaultLeaseTime = 24 * time.Hour

// LeaseTimes holds the DHCPv6 preferred and valid lifetimes. Both are optional;
// when unset they default to DefaultLeaseTime (24h).
type LeaseTimes struct {
// PreferredLifetime is the time.Duration an address remains preferred.
// +optional
PreferredLifetime time.Duration `yaml:"preferredLifetime"`
// ValidLifetime is the time.Duration an address remains valid.
// +optional
ValidLifetime time.Duration `yaml:"validLifetime"`
}

// Resolve returns the preferred and valid lifetimes, defaulting any zero value
// to DefaultLeaseTime.
func (l LeaseTimes) Resolve() (preferred, valid time.Duration) {
preferred = l.PreferredLifetime
if preferred == 0 {
preferred = DefaultLeaseTime
}
valid = l.ValidLifetime
if valid == 0 {
valid = DefaultLeaseTime
}
return preferred, valid
}

// Validate checks the resolved lifetimes. Both must be positive, and per
// RFC 8415 the preferred lifetime must not exceed the valid lifetime.
func (l LeaseTimes) Validate() error {
preferred, valid := l.Resolve()
switch {
case preferred <= 0:
return fmt.Errorf("preferredLifetime must be positive, got %s", preferred)
case valid <= 0:
return fmt.Errorf("validLifetime must be positive, got %s", valid)
case preferred > valid:
return fmt.Errorf("preferredLifetime (%s) must not exceed validLifetime (%s)", preferred, valid)
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
return nil
}
1 change: 1 addition & 0 deletions internal/api/onmetal_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ type PrefixDelegation struct {

type OnMetalConfig struct {
PrefixDelegation PrefixDelegation `yaml:"prefixDelegation"`
LeaseTimes LeaseTimes `yaml:"leaseTimes"`
}
1 change: 1 addition & 0 deletions internal/api/oob_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ type SubnetLabel struct {
type OOBConfig struct {
Namespace string `yaml:"namespace"`
SubnetLabels []SubnetLabel `yaml:"subnetLabels"`
LeaseTimes LeaseTimes `yaml:"leaseTimes"`
}
9 changes: 9 additions & 0 deletions internal/api/stateless_config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and IronCore contributors
// SPDX-License-Identifier: MIT

package api

// StatelessConfig holds the configuration for the stateless plugin.
type StatelessConfig struct {
LeaseTimes LeaseTimes `yaml:"leaseTimes"`
}
16 changes: 12 additions & 4 deletions plugins/bluefield/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@ var Plugin = plugins.Plugin{
Name: "bluefield",
Setup6: setupPlugin,
}
var ipaddr net.IP
var (
ipaddr net.IP
preferredLifeTime time.Duration
validLifeTime time.Duration
)

// args[0] = path to config file
func parseArgs(args ...string) (string, error) {
Expand Down Expand Up @@ -66,7 +70,11 @@ func setupPlugin(args ...string) (handler.Handler6, error) {
if ipaddr == nil {
return nil, fmt.Errorf("invalid IPv6 address: %s", args[0])
}
log.Infof("Parsed IP %s", ipaddr)
if err := bluefieldIPConfig.LeaseTimes.Validate(); err != nil {
return nil, fmt.Errorf("invalid lease times: %v", err)
}
preferredLifeTime, validLifeTime = bluefieldIPConfig.LeaseTimes.Resolve()
log.Infof("Parsed IP %s, lease times (preferred %s, valid %s)", ipaddr, preferredLifeTime, validLifeTime)
return handleDHCPv6, nil
}

Expand Down Expand Up @@ -128,8 +136,8 @@ func addOptIANA(resp dhcpv6.DHCPv6, iaId [4]byte) {
Options: dhcpv6.IdentityOptions{Options: []dhcpv6.Option{
&dhcpv6.OptIAAddress{
IPv6Addr: ipaddr,
PreferredLifetime: 24 * time.Hour,
ValidLifetime: 48 * time.Hour,
Comment thread
damyan marked this conversation as resolved.
PreferredLifetime: preferredLifeTime,
ValidLifetime: validLifeTime,
},
}},
})
Expand Down
8 changes: 4 additions & 4 deletions plugins/bluefield/plugin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ var _ = Describe("Bluefield Plugin", func() {
Expect(respm.MessageType).To(Equal(dhcpv6.MessageTypeAdvertise))
Expect(respm.Options.OneIANA().Options.Options[0].(*dhcpv6.OptIAAddress).IPv6Addr.String()).To(Equal(testIP))
Expect(respm.Options.OneIANA().Options.Options[0].(*dhcpv6.OptIAAddress).PreferredLifetime).To(Equal(24 * time.Hour))
Expect(respm.Options.OneIANA().Options.Options[0].(*dhcpv6.OptIAAddress).ValidLifetime).To(Equal(48 * time.Hour))
Expect(respm.Options.OneIANA().Options.Options[0].(*dhcpv6.OptIAAddress).ValidLifetime).To(Equal(24 * time.Hour))
Expect(respm.Options.OneIANA().T1).To(Equal(1 * time.Hour))
Expect(respm.Options.OneIANA().T2).To(Equal(2 * time.Hour))
Expect(respm.Options.OneIANA().IaId).NotTo(BeNil())
Expand All @@ -112,7 +112,7 @@ var _ = Describe("Bluefield Plugin", func() {
Expect(respm.MessageType).To(Equal(dhcpv6.MessageTypeReply))
Expect(respm.Options.OneIANA().Options.Options[0].(*dhcpv6.OptIAAddress).IPv6Addr.String()).To(Equal(testIP))
Expect(respm.Options.OneIANA().Options.Options[0].(*dhcpv6.OptIAAddress).PreferredLifetime).To(Equal(24 * time.Hour))
Expect(respm.Options.OneIANA().Options.Options[0].(*dhcpv6.OptIAAddress).ValidLifetime).To(Equal(48 * time.Hour))
Expect(respm.Options.OneIANA().Options.Options[0].(*dhcpv6.OptIAAddress).ValidLifetime).To(Equal(24 * time.Hour))
Expect(respm.Options.OneIANA().T1).To(Equal(1 * time.Hour))
Expect(respm.Options.OneIANA().T2).To(Equal(2 * time.Hour))
Expect(respm.Options.OneIANA().IaId).NotTo(BeNil())
Expand All @@ -129,7 +129,7 @@ var _ = Describe("Bluefield Plugin", func() {
Expect(respm.MessageType).To(Equal(dhcpv6.MessageTypeReply))
Expect(respm.Options.OneIANA().Options.Options[0].(*dhcpv6.OptIAAddress).IPv6Addr.String()).To(Equal(testIP))
Expect(respm.Options.OneIANA().Options.Options[0].(*dhcpv6.OptIAAddress).PreferredLifetime).To(Equal(24 * time.Hour))
Expect(respm.Options.OneIANA().Options.Options[0].(*dhcpv6.OptIAAddress).ValidLifetime).To(Equal(48 * time.Hour))
Expect(respm.Options.OneIANA().Options.Options[0].(*dhcpv6.OptIAAddress).ValidLifetime).To(Equal(24 * time.Hour))
Expect(respm.Options.OneIANA().T1).To(Equal(1 * time.Hour))
Expect(respm.Options.OneIANA().T2).To(Equal(2 * time.Hour))
Expect(respm.Options.OneIANA().IaId).NotTo(BeNil())
Expand Down Expand Up @@ -159,7 +159,7 @@ var _ = Describe("Bluefield Plugin", func() {
Expect(respm.MessageType).To(Equal(dhcpv6.MessageTypeReply))
Expect(respm.Options.OneIANA().Options.Options[0].(*dhcpv6.OptIAAddress).IPv6Addr.String()).To(Equal(testIP))
Expect(respm.Options.OneIANA().Options.Options[0].(*dhcpv6.OptIAAddress).PreferredLifetime).To(Equal(24 * time.Hour))
Expect(respm.Options.OneIANA().Options.Options[0].(*dhcpv6.OptIAAddress).ValidLifetime).To(Equal(48 * time.Hour))
Expect(respm.Options.OneIANA().Options.Options[0].(*dhcpv6.OptIAAddress).ValidLifetime).To(Equal(24 * time.Hour))
Expect(respm.Options.OneIANA().T1).To(Equal(1 * time.Hour))
Expect(respm.Options.OneIANA().T2).To(Equal(2 * time.Hour))
Expect(respm.Options.OneIANA().IaId).NotTo(BeNil())
Expand Down
13 changes: 11 additions & 2 deletions plugins/onmetal/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,12 @@ var Plugin = plugins.Plugin{

var prefixLength int

var (
preferredLifeTime time.Duration
validLifeTime time.Duration
)

const (
preferredLifeTime = 24 * time.Hour
validLifeTime = 24 * time.Hour
prefixDelegationLengthMin = 1
prefixDelegationLengthMax = 127
)
Expand Down Expand Up @@ -77,6 +80,12 @@ func setup6(args ...string) (handler.Handler6, error) {
return nil, fmt.Errorf("invalid prefix length: %d", prefixLength)
}

if err := onMetalConfig.LeaseTimes.Validate(); err != nil {
return nil, fmt.Errorf("invalid lease times: %v", err)
}
preferredLifeTime, validLifeTime = onMetalConfig.LeaseTimes.Resolve()
log.Infof("Using lease times (preferred %s, valid %s)", preferredLifeTime, validLifeTime)

return handler6, nil
}

Expand Down
21 changes: 18 additions & 3 deletions plugins/oob/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,13 @@ var (
k8sClient *K8sClient
)

var (
preferredLifeTime time.Duration
validLifeTime time.Duration
)

const (
UNKNOWN_IP = "0.0.0.0"
preferredLifeTime = 24 * time.Hour
validLifeTime = 24 * time.Hour
UNKNOWN_IP = "0.0.0.0"
)

// args[0] = path to config file
Expand Down Expand Up @@ -85,6 +88,12 @@ func setup6(args ...string) (handler.Handler6, error) {
return nil, fmt.Errorf("failed to create k8s client: %w", err)
}

if err := oobConfig.LeaseTimes.Validate(); err != nil {
return nil, fmt.Errorf("invalid lease times: %v", err)
}
preferredLifeTime, validLifeTime = oobConfig.LeaseTimes.Resolve()
log.Infof("Using lease times (preferred %s, valid %s)", preferredLifeTime, validLifeTime)

log.Print("Loaded oob plugin for DHCPv6.")
return handler6, nil
}
Expand Down Expand Up @@ -171,6 +180,12 @@ func setup4(args ...string) (handler.Handler4, error) {
return nil, fmt.Errorf("failed to create k8s client: %w", err)
}

if err := oobConfig.LeaseTimes.Validate(); err != nil {
return nil, fmt.Errorf("invalid lease times: %v", err)
}
preferredLifeTime, validLifeTime = oobConfig.LeaseTimes.Resolve()
log.Infof("Using lease times (preferred %s, valid %s)", preferredLifeTime, validLifeTime)

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