Compare commits

...

3 Commits

Author SHA1 Message Date
Dmitrii Andreev
4c2edb8ddf added changelog entry 2025-10-08 18:28:09 +03:00
Dmitrii Andreev
1136fd342c client: fix IPv6-only CNI interface support
Fix a regression introduced in Nomad 1.9.0 where CNI bridge networking with
IPv6-only interfaces would fail with 'no interface with an address' error.

The issue was that while the code correctly populated the AddressIPv6 field
for IPv6 addresses, several validation checks only examined the Address field
(IPv4), causing IPv6-only configurations to be rejected.

Changes:
- Update interface selection logic in cniToAllocNet to accept interfaces with
  either IPv4 or IPv6 addresses (not just IPv4)
- Update fallback logic to check both Address and AddressIPv6 fields
- Update error check to only fail when both IPv4 and IPv6 are missing
- Update AllocNetworkStatus.IsZero() to check AddressIPv6 field

This allows CNI configurations with IPv6-only interfaces to work correctly,
restoring functionality from Nomad 1.8.x.

Fixes #26905
2025-10-08 18:12:14 +03:00
Dmitrii Andreev
d058761dc7 client: add test for IPv6-only CNI interfaces
Add a test case to verify that CNI results containing only IPv6 addresses
are handled correctly. This is a regression test for a bug introduced in
GH-23882 where IPv6-only interfaces would fail with 'no interface with an
address' error.

The test verifies that when a CNI plugin returns only an IPv6 address
(without IPv4), the allocation network status should be properly populated
with the IPv6 address in the AddressIPv6 field.
2025-10-08 18:12:13 +03:00
4 changed files with 37 additions and 6 deletions

3
.changelog/26910.txt Normal file
View File

@@ -0,0 +1,3 @@
```release-note:bug
client: Fixed CNI bridge networking failure on IPv6-only interfaces
```

View File

@@ -480,8 +480,8 @@ func (c *cniNetworkConfigurator) cniToAllocNet(res *cni.Result) (*structs.AllocN
}
}
// found a good interface, so we're done
if netStatus.Address != "" {
// found a good interface (with either IPv4 or IPv6), so we're done
if netStatus.Address != "" || netStatus.AddressIPv6 != "" {
netStatus.InterfaceName = name
return
}
@@ -493,7 +493,7 @@ func (c *cniNetworkConfigurator) cniToAllocNet(res *cni.Result) (*structs.AllocN
// If no IP address was found, use the first interface with an address
// found as a fallback
if netStatus.Address == "" {
if netStatus.Address == "" && netStatus.AddressIPv6 == "" {
setStatus(false)
c.logger.Debug("no sandbox interface with an address found CNI result, using first available",
"interface", netStatus.InterfaceName,
@@ -501,8 +501,8 @@ func (c *cniNetworkConfigurator) cniToAllocNet(res *cni.Result) (*structs.AllocN
)
}
// If no IP address could be found, return an error
if netStatus.Address == "" {
// If no IP address (IPv4 or IPv6) could be found, return an error
if netStatus.Address == "" && netStatus.AddressIPv6 == "" {
return nil, fmt.Errorf("failed to configure network: no interface with an address")
}

View File

@@ -498,6 +498,34 @@ func TestCNI_cniToAllocNet_Dualstack(t *testing.T) {
test.Eq(t, "eth0", allocNet.InterfaceName)
}
// TestCNI_cniToAllocNet_IPv6Only asserts that CNI results containing only IPv6
// addresses work correctly. This is a regression test for a bug introduced in
// GH-23882 where IPv6-only interfaces would fail with "no interface with an address".
func TestCNI_cniToAllocNet_IPv6Only(t *testing.T) {
ci.Parallel(t)
cniResult := &cni.Result{
Interfaces: map[string]*cni.Config{
"eth0": {
Sandbox: "nomad-sandbox",
IPConfigs: []*cni.IPConfig{
{IP: net.ParseIP("fd00:a110:c8::b")}, // only IPv6
},
},
},
}
c := &cniNetworkConfigurator{
logger: testlog.HCLogger(t),
}
allocNet, err := c.cniToAllocNet(cniResult)
must.NoError(t, err)
must.NotNil(t, allocNet)
test.Eq(t, "", allocNet.Address)
test.Eq(t, "fd00:a110:c8::b", allocNet.AddressIPv6)
test.Eq(t, "eth0", allocNet.InterfaceName)
}
func TestCNI_addCustomCNIArgs(t *testing.T) {
ci.Parallel(t)
cniArgs := map[string]string{

View File

@@ -12292,7 +12292,7 @@ func (a *AllocNetworkStatus) IsZero() bool {
if a == nil {
return true
}
if a.InterfaceName != "" || a.Address != "" {
if a.InterfaceName != "" || a.Address != "" || a.AddressIPv6 != "" {
return false
}
if !a.DNS.IsZero() {