mirror of
https://github.com/kemko/nomad.git
synced 2026-01-01 16:05:42 +03:00
Compare commits
5 Commits
df4e97dc94
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4f1e7f56d1 | ||
|
|
b9bda8f2c1 | ||
|
|
c588527ae4 | ||
|
|
1136fd342c | ||
|
|
d058761dc7 |
3
.changelog/26910.txt
Normal file
3
.changelog/26910.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
```release-note:bug
|
||||
networking: Fixed network interface detection failure with bridge or CNI mode on IPv6-only interfaces
|
||||
```
|
||||
@@ -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,12 +501,19 @@ 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")
|
||||
|
||||
}
|
||||
|
||||
// Fallback: if no IPv4 address but we have IPv6, copy it to Address field
|
||||
// for backward compatibility with code that only checks Address field
|
||||
// (e.g. service registration with address_mode="alloc")
|
||||
if netStatus.Address == "" && netStatus.AddressIPv6 != "" {
|
||||
netStatus.Address = netStatus.AddressIPv6
|
||||
}
|
||||
|
||||
// Use the first DNS results, if non-empty
|
||||
if len(res.DNS) > 0 {
|
||||
cniDNS := res.DNS[0]
|
||||
|
||||
@@ -498,6 +498,162 @@ 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, "fd00:a110:c8::b", allocNet.Address) // fallback to IPv6
|
||||
test.Eq(t, "fd00:a110:c8::b", allocNet.AddressIPv6)
|
||||
test.Eq(t, "eth0", allocNet.InterfaceName)
|
||||
}
|
||||
|
||||
// TestCNI_cniToAllocNet_IPv6Only_MultipleAddresses asserts that when a CNI result
|
||||
// contains multiple IPv6 addresses on a single interface, the first address is selected.
|
||||
// This ensures consistent behavior with the IPv4 case.
|
||||
func TestCNI_cniToAllocNet_IPv6Only_MultipleAddresses(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::1")}, // first IPv6 - should be selected
|
||||
{IP: net.ParseIP("fd00:a110:c8::2")}, // second IPv6
|
||||
{IP: net.ParseIP("fd00:a110:c8::3")}, // third IPv6
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
c := &cniNetworkConfigurator{
|
||||
logger: testlog.HCLogger(t),
|
||||
}
|
||||
allocNet, err := c.cniToAllocNet(cniResult)
|
||||
must.NoError(t, err)
|
||||
must.NotNil(t, allocNet)
|
||||
test.Eq(t, "fd00:a110:c8::1", allocNet.Address) // fallback to IPv6
|
||||
test.Eq(t, "fd00:a110:c8::1", allocNet.AddressIPv6) // should select first IPv6 address
|
||||
test.Eq(t, "eth0", allocNet.InterfaceName)
|
||||
}
|
||||
|
||||
// TestCNI_cniToAllocNet_Dualstack_MultipleAddresses asserts that when a CNI result
|
||||
// contains multiple IPv4 and IPv6 addresses, the first address of each type is selected.
|
||||
func TestCNI_cniToAllocNet_Dualstack_MultipleAddresses(t *testing.T) {
|
||||
ci.Parallel(t)
|
||||
|
||||
cniResult := &cni.Result{
|
||||
Interfaces: map[string]*cni.Config{
|
||||
"eth0": {
|
||||
Sandbox: "nomad-sandbox",
|
||||
IPConfigs: []*cni.IPConfig{
|
||||
{IP: net.IPv4(192, 168, 1, 10)}, // first IPv4 - should be selected
|
||||
{IP: net.ParseIP("fd00:a110:c8::1")}, // first IPv6 - should be selected
|
||||
{IP: net.IPv4(192, 168, 1, 11)}, // second IPv4
|
||||
{IP: net.ParseIP("fd00:a110:c8::2")}, // second IPv6
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
c := &cniNetworkConfigurator{
|
||||
logger: testlog.HCLogger(t),
|
||||
}
|
||||
allocNet, err := c.cniToAllocNet(cniResult)
|
||||
must.NoError(t, err)
|
||||
must.NotNil(t, allocNet)
|
||||
test.Eq(t, "192.168.1.10", allocNet.Address) // should select first IPv4 address
|
||||
test.Eq(t, "fd00:a110:c8::1", allocNet.AddressIPv6) // should select first IPv6 address
|
||||
test.Eq(t, "eth0", allocNet.InterfaceName)
|
||||
}
|
||||
|
||||
// TestCNI_cniToAllocNet_MultipleInterfaces_IPv6First asserts that when multiple
|
||||
// interfaces exist, the first interface (lexicographically) with an address is selected,
|
||||
// even if it only has IPv6 and a later interface has IPv4.
|
||||
func TestCNI_cniToAllocNet_MultipleInterfaces_IPv6First(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::1")}, // IPv6 only on first interface
|
||||
},
|
||||
},
|
||||
"eth1": {
|
||||
Sandbox: "nomad-sandbox",
|
||||
IPConfigs: []*cni.IPConfig{
|
||||
{IP: net.IPv4(192, 168, 1, 10)}, // IPv4 on second interface
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
c := &cniNetworkConfigurator{
|
||||
logger: testlog.HCLogger(t),
|
||||
}
|
||||
allocNet, err := c.cniToAllocNet(cniResult)
|
||||
must.NoError(t, err)
|
||||
must.NotNil(t, allocNet)
|
||||
test.Eq(t, "fd00:a110:c8::1", allocNet.Address) // fallback to IPv6
|
||||
test.Eq(t, "fd00:a110:c8::1", allocNet.AddressIPv6) // IPv6 from first interface
|
||||
test.Eq(t, "eth0", allocNet.InterfaceName) // first interface should be selected
|
||||
}
|
||||
|
||||
// TestCNI_cniToAllocNet_MultipleInterfaces_IPv6OnMultiple asserts that when multiple
|
||||
// interfaces each have IPv6 addresses, the first interface is selected.
|
||||
func TestCNI_cniToAllocNet_MultipleInterfaces_IPv6OnMultiple(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::1")},
|
||||
{IP: net.ParseIP("fd00:a110:c8::2")}, // multiple on first interface
|
||||
},
|
||||
},
|
||||
"eth1": {
|
||||
Sandbox: "nomad-sandbox",
|
||||
IPConfigs: []*cni.IPConfig{
|
||||
{IP: net.ParseIP("fd00:b220:c8::1")}, // different IPv6 on second interface
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
c := &cniNetworkConfigurator{
|
||||
logger: testlog.HCLogger(t),
|
||||
}
|
||||
allocNet, err := c.cniToAllocNet(cniResult)
|
||||
must.NoError(t, err)
|
||||
must.NotNil(t, allocNet)
|
||||
test.Eq(t, "fd00:a110:c8::1", allocNet.Address) // fallback to IPv6
|
||||
test.Eq(t, "fd00:a110:c8::1", allocNet.AddressIPv6) // first address from first interface
|
||||
test.Eq(t, "eth0", allocNet.InterfaceName) // first interface
|
||||
}
|
||||
|
||||
func TestCNI_addCustomCNIArgs(t *testing.T) {
|
||||
ci.Parallel(t)
|
||||
cniArgs := map[string]string{
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user