Skip to content

Commit 739f664

Browse files
mcanevetsmira
authored andcommitted
feat(machined): inherit IP6_METHOD from METHOD in OpenNebula driver
When ETH*_IP6_METHOD is unset, fall back to the value of ETH*_METHOD, matching the reference [ -z "$ip6_method" ] && ip6_method="${method}" logic in setup_iface_vars. This means a DHCP interface now also gets a DHCPv6 operator, a static interface stays static, and a skip interface remains fully skipped. Update golden testdata to include the DHCPv6 operator that ETH1_METHOD=dhcp now emits. Signed-off-by: Mickaël Canévet <mickael.canevet@proton.ch> Signed-off-by: Andrey Smirnov <andrey.smirnov@siderolabs.com> (cherry picked from commit 3bec5cc)
1 parent 93878c0 commit 739f664

File tree

4 files changed

+131
-40
lines changed

4 files changed

+131
-40
lines changed

internal/app/machined/pkg/runtime/v1alpha1/platform/opennebula/ipv6_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,47 @@ func TestParseIPv6(t *testing.T) {
144144
extra: "ETH0_IP6 = \"2001:db8::1\"",
145145
wantAddrs: []netip.Prefix{netip.MustParsePrefix("2001:db8::1/64")},
146146
},
147+
{
148+
name: "METHOD=dhcp with no IP6_METHOD inherits dhcp and emits OperatorDHCP6",
149+
extra: "ETH0_METHOD = \"dhcp\"",
150+
wantOperators: []network.OperatorSpecSpec{dhcp6Op(1)},
151+
},
152+
{
153+
name: "METHOD=dhcp with IP6_METHOD=static and IP6 set uses static IPv6",
154+
extra: "ETH0_METHOD = \"dhcp\"\nETH0_IP6_METHOD = \"static\"\nETH0_IP6 = \"2001:db8::1\"",
155+
wantAddrs: []netip.Prefix{netip.MustParsePrefix("2001:db8::1/64")},
156+
},
157+
{
158+
name: "METHOD=dhcp with IP6_METHOD=disable emits no IPv6 config",
159+
extra: "ETH0_METHOD = \"dhcp\"\nETH0_IP6_METHOD = \"disable\"",
160+
},
161+
{
162+
name: "METHOD=static with no IP6_METHOD and no IP6 emits no IPv6 config",
163+
extra: "ETH0_METHOD = \"static\"",
164+
},
165+
{
166+
name: "METRIC=200 with no IP6_METRIC cascades to IPv6 gateway metric",
167+
extra: "ETH0_IP6 = \"2001:db8::1\"\nETH0_IP6_GATEWAY = \"2001:db8::fffe\"\nETH0_METRIC = \"200\"",
168+
wantAddrs: []netip.Prefix{netip.MustParsePrefix("2001:db8::1/64")},
169+
wantRoutes: []network.RouteSpecSpec{gw6Route("2001:db8::fffe", 200)},
170+
},
171+
{
172+
name: "METRIC=200 with IP6_METRIC=50 uses explicit IP6_METRIC",
173+
extra: "ETH0_IP6 = \"2001:db8::1\"\nETH0_IP6_GATEWAY = \"2001:db8::fffe\"\nETH0_METRIC = \"200\"\nETH0_IP6_METRIC = \"50\"",
174+
wantAddrs: []netip.Prefix{netip.MustParsePrefix("2001:db8::1/64")},
175+
wantRoutes: []network.RouteSpecSpec{gw6Route("2001:db8::fffe", 50)},
176+
},
177+
{
178+
name: "METRIC=200 with IP6_METHOD=dhcp and no IP6_METRIC cascades to DHCPv6 metric",
179+
extra: "ETH0_IP6_METHOD = \"dhcp\"\nETH0_METRIC = \"200\"",
180+
wantOperators: []network.OperatorSpecSpec{dhcp6Op(200)},
181+
},
182+
{
183+
name: "no METRIC and no IP6_METRIC uses IPv6 default of 1",
184+
extra: "ETH0_IP6 = \"2001:db8::1\"\nETH0_IP6_GATEWAY = \"2001:db8::fffe\"",
185+
wantAddrs: []netip.Prefix{netip.MustParsePrefix("2001:db8::1/64")},
186+
wantRoutes: []network.RouteSpecSpec{gw6Route("2001:db8::fffe", 1)},
187+
},
147188
} {
148189
t.Run(tc.name, func(t *testing.T) {
149190
t.Parallel()

internal/app/machined/pkg/runtime/v1alpha1/platform/opennebula/opennebula.go

Lines changed: 76 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -413,22 +413,53 @@ func parseIPv4StaticConfig(
413413
return nil
414414
}
415415

416-
// parseInterfaceIPv4 configures the IPv4 stack for one interface.
417-
// Dispatches to DHCP4 operator or static config based on ETH*_METHOD.
418-
func parseInterfaceIPv4(oneContext map[string]string, ifaceName, ifaceNameLower string, networkConfig *runtime.PlatformNetworkConfig, allDNSIPs *[]netip.Addr, allSearchDomains *[]string) error {
419-
if oneContext[ifaceName+"_METHOD"] == methodSkip {
420-
return nil
416+
// parseIPv4Metric reads ETH*_METRIC and returns the parsed value, or 0 when
417+
// the variable is absent. Callers apply their own default (e.g.
418+
// network.DefaultRouteMetric for IPv4, 1 for IPv6 via parseIPv6Metric).
419+
func parseIPv4Metric(oneContext map[string]string, ifaceName string) (uint32, error) {
420+
if metricStr := oneContext[ifaceName+"_METRIC"]; metricStr != "" {
421+
m, err := strconv.ParseUint(metricStr, 10, 32)
422+
if err != nil {
423+
return 0, fmt.Errorf("interface %s: failed to parse metric: %w", ifaceName, err)
424+
}
425+
426+
return uint32(m), nil
421427
}
422428

423-
routeMetric := uint32(network.DefaultRouteMetric)
429+
return 0, nil
430+
}
424431

425-
if metricStr := oneContext[ifaceName+"_METRIC"]; metricStr != "" {
432+
// parseIPv6Metric reads ETH*_IP6_METRIC; falls back to ipv4Metric (when > 0),
433+
// then to 1, matching the reference [ -z "$ip6_metric" ] && ip6_metric="${metric}".
434+
func parseIPv6Metric(oneContext map[string]string, ifaceName string, ipv4Metric uint32) (uint32, error) {
435+
if metricStr := oneContext[ifaceName+"_IP6_METRIC"]; metricStr != "" {
426436
m, err := strconv.ParseUint(metricStr, 10, 32)
427437
if err != nil {
428-
return fmt.Errorf("interface %s: failed to parse metric: %w", ifaceName, err)
438+
return 0, fmt.Errorf("interface %s: failed to parse IPv6 metric: %w", ifaceName, err)
429439
}
430440

431-
routeMetric = uint32(m)
441+
return uint32(m), nil
442+
}
443+
444+
if ipv4Metric > 0 {
445+
return ipv4Metric, nil
446+
}
447+
448+
return 1, nil
449+
}
450+
451+
// parseInterfaceIPv4 configures the IPv4 stack for one interface.
452+
// Dispatches to DHCP4 operator or static config based on ETH*_METHOD.
453+
func parseInterfaceIPv4(
454+
oneContext map[string]string, ifaceName, ifaceNameLower string, routeMetric uint32,
455+
networkConfig *runtime.PlatformNetworkConfig, allDNSIPs *[]netip.Addr, allSearchDomains *[]string,
456+
) error {
457+
if oneContext[ifaceName+"_METHOD"] == methodSkip {
458+
return nil
459+
}
460+
461+
if routeMetric == 0 {
462+
routeMetric = uint32(network.DefaultRouteMetric)
432463
}
433464

434465
if oneContext[ifaceName+"_METHOD"] == "dhcp" {
@@ -475,8 +506,8 @@ func ip6PrefixFrom(ipStr, prefixLenStr string) (netip.Prefix, error) {
475506
}
476507

477508
// parseIPv6Gateway reads ETH*_IP6_GATEWAY (or legacy GATEWAY6) and emits the
478-
// default IPv6 route (::/0) with metric from ETH*_IP6_METRIC (default 1).
479-
func parseIPv6Gateway(oneContext map[string]string, ifaceName, ifaceNameLower string, networkConfig *runtime.PlatformNetworkConfig) error {
509+
// default IPv6 route (::/0) with metric from parseIPv6Metric.
510+
func parseIPv6Gateway(oneContext map[string]string, ifaceName, ifaceNameLower string, ipv4Metric uint32, networkConfig *runtime.PlatformNetworkConfig) error {
480511
gwStr := oneContext[ifaceName+"_IP6_GATEWAY"]
481512
if gwStr == "" {
482513
gwStr = oneContext[ifaceName+"_GATEWAY6"]
@@ -491,15 +522,9 @@ func parseIPv6Gateway(oneContext map[string]string, ifaceName, ifaceNameLower st
491522
return fmt.Errorf("interface %s: failed to parse IPv6 gateway %q: %w", ifaceName, gwStr, err)
492523
}
493524

494-
metric := uint32(1)
495-
496-
if metricStr := oneContext[ifaceName+"_IP6_METRIC"]; metricStr != "" {
497-
m, err := strconv.ParseUint(metricStr, 10, 32)
498-
if err != nil {
499-
return fmt.Errorf("interface %s: failed to parse IPv6 metric: %w", ifaceName, err)
500-
}
501-
502-
metric = uint32(m)
525+
metric, err := parseIPv6Metric(oneContext, ifaceName, ipv4Metric)
526+
if err != nil {
527+
return err
503528
}
504529

505530
route := network.RouteSpecSpec{
@@ -521,17 +546,11 @@ func parseIPv6Gateway(oneContext map[string]string, ifaceName, ifaceNameLower st
521546
}
522547

523548
// parseIPv6DHCP emits a DHCPv6 operator for an interface, with metric from
524-
// ETH*_IP6_METRIC (default 1).
525-
func parseIPv6DHCP(oneContext map[string]string, ifaceName, ifaceNameLower string, networkConfig *runtime.PlatformNetworkConfig) error {
526-
metric := uint32(1)
527-
528-
if metricStr := oneContext[ifaceName+"_IP6_METRIC"]; metricStr != "" {
529-
m, err := strconv.ParseUint(metricStr, 10, 32)
530-
if err != nil {
531-
return fmt.Errorf("interface %s: failed to parse IPv6 metric: %w", ifaceName, err)
532-
}
533-
534-
metric = uint32(m)
549+
// parseIPv6Metric.
550+
func parseIPv6DHCP(oneContext map[string]string, ifaceName, ifaceNameLower string, ipv4Metric uint32, networkConfig *runtime.PlatformNetworkConfig) error {
551+
metric, err := parseIPv6Metric(oneContext, ifaceName, ipv4Metric)
552+
if err != nil {
553+
return err
535554
}
536555

537556
networkConfig.Operators = append(networkConfig.Operators, network.OperatorSpecSpec{
@@ -549,18 +568,25 @@ func parseIPv6DHCP(oneContext map[string]string, ifaceName, ifaceNameLower strin
549568
}
550569

551570
// parseInterfaceIPv6 configures the IPv6 stack for one interface.
552-
// Dispatches on ETH*_IP6_METHOD: disable (skip), auto (SLAAC via kernel),
553-
// dhcp (DHCPv6 operator), or static/empty (Phase 2 static path).
554-
func parseInterfaceIPv6(oneContext map[string]string, ifaceName, ifaceNameLower string, networkConfig *runtime.PlatformNetworkConfig) error {
555-
switch strings.ToLower(oneContext[ifaceName+"_IP6_METHOD"]) {
571+
// Dispatches on the effective IP6_METHOD: disable/skip (no-op), auto (SLAAC),
572+
// dhcp (DHCPv6 operator), or static/empty (static address path).
573+
// When IP6_METHOD is unset, ipv4Method is used as fallback, matching the
574+
// reference: [ -z "$ip6_method" ] && ip6_method="${method}".
575+
func parseInterfaceIPv6(oneContext map[string]string, ifaceName, ifaceNameLower string, ipv4Method string, ipv4Metric uint32, networkConfig *runtime.PlatformNetworkConfig) error {
576+
ip6Method := strings.ToLower(oneContext[ifaceName+"_IP6_METHOD"])
577+
if ip6Method == "" {
578+
ip6Method = ipv4Method
579+
}
580+
581+
switch ip6Method {
556582
case "disable", methodSkip:
557583
return nil
558584
case "auto":
559585
// SLAAC: the kernel accepts Router Advertisements by default in Talos;
560586
// no operator or sysctl is required to enable address auto-configuration.
561587
return nil
562588
case "dhcp":
563-
return parseIPv6DHCP(oneContext, ifaceName, ifaceNameLower, networkConfig)
589+
return parseIPv6DHCP(oneContext, ifaceName, ifaceNameLower, ipv4Metric, networkConfig)
564590
}
565591

566592
ip6Str := oneContext[ifaceName+"_IP6"]
@@ -602,23 +628,33 @@ func parseInterfaceIPv6(oneContext map[string]string, ifaceName, ifaceNameLower
602628
})
603629
}
604630

605-
return parseIPv6Gateway(oneContext, ifaceName, ifaceNameLower, networkConfig)
631+
return parseIPv6Gateway(oneContext, ifaceName, ifaceNameLower, ipv4Metric, networkConfig)
606632
}
607633

608634
// parseInterface runs all per-interface configuration (IPv4, IPv6, aliases).
609635
func parseInterface(oneContext map[string]string, ifaceName string, networkConfig *runtime.PlatformNetworkConfig, allDNSIPs *[]netip.Addr, allSearchDomains *[]string) error {
610636
ifaceNameLower := strings.ToLower(ifaceName)
637+
ipv4Method := strings.ToLower(oneContext[ifaceName+"_METHOD"])
611638

612639
ip6Method := strings.ToLower(oneContext[ifaceName+"_IP6_METHOD"])
613-
if oneContext[ifaceName+"_METHOD"] == methodSkip && (ip6Method == "" || ip6Method == "disable" || ip6Method == methodSkip) {
640+
if ip6Method == "" {
641+
ip6Method = ipv4Method
642+
}
643+
644+
if ipv4Method == methodSkip && (ip6Method == "" || ip6Method == methodSkip || ip6Method == "disable") {
614645
return nil
615646
}
616647

617-
if err := parseInterfaceIPv4(oneContext, ifaceName, ifaceNameLower, networkConfig, allDNSIPs, allSearchDomains); err != nil {
648+
ipv4Metric, err := parseIPv4Metric(oneContext, ifaceName)
649+
if err != nil {
650+
return err
651+
}
652+
653+
if err := parseInterfaceIPv4(oneContext, ifaceName, ifaceNameLower, ipv4Metric, networkConfig, allDNSIPs, allSearchDomains); err != nil {
618654
return err
619655
}
620656

621-
if err := parseInterfaceIPv6(oneContext, ifaceName, ifaceNameLower, networkConfig); err != nil {
657+
if err := parseInterfaceIPv6(oneContext, ifaceName, ifaceNameLower, ipv4Method, ipv4Metric, networkConfig); err != nil {
622658
return err
623659
}
624660

internal/app/machined/pkg/runtime/v1alpha1/platform/opennebula/testdata/expected.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,13 @@ operators:
7979
routeMetric: 200
8080
skipHostnameRequest: true
8181
layer: platform
82+
- operator: dhcp6
83+
linkName: eth1
84+
requireUp: true
85+
dhcp6:
86+
routeMetric: 200
87+
skipHostnameRequest: true
88+
layer: platform
8289
externalIPs: []
8390
metadata:
8491
platform: opennebula

internal/app/machined/pkg/runtime/v1alpha1/platform/opennebula/testdata/expected_no_network_flag.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,13 @@ operators:
7979
routeMetric: 200
8080
skipHostnameRequest: true
8181
layer: platform
82+
- operator: dhcp6
83+
linkName: eth1
84+
requireUp: true
85+
dhcp6:
86+
routeMetric: 200
87+
skipHostnameRequest: true
88+
layer: platform
8289
externalIPs: []
8390
metadata:
8491
platform: opennebula

0 commit comments

Comments
 (0)