Browse code

api/types/swarm: use netip types as appropriate

Change the types for IP address and prefix struct fields to netip.Addr
and netip.Prefix for convenience. Fields such as
swarm.InitRequest.ListenAddr which may encode non-numeric values such as
a network interface name have not been modified.

Signed-off-by: Cory Snider <csnider@mirantis.com>

Cory Snider authored on 2025/09/11 01:07:54
Showing 17 changed files
... ...
@@ -1,6 +1,7 @@
1 1
 package swarm
2 2
 
3 3
 import (
4
+	"net/netip"
4 5
 	"time"
5 6
 
6 7
 	"github.com/moby/moby/api/types/container"
... ...
@@ -14,7 +15,7 @@ import (
14 14
 // TODO: `domain` is not supported yet.
15 15
 type DNSConfig struct {
16 16
 	// Nameservers specifies the IP addresses of the name servers
17
-	Nameservers []string `json:",omitempty"`
17
+	Nameservers []netip.Addr `json:",omitempty"`
18 18
 	// Search specifies the search list for host-name lookup
19 19
 	Search []string `json:",omitempty"`
20 20
 	// Options allows certain internal resolver variables to be modified
... ...
@@ -1,6 +1,8 @@
1 1
 package swarm
2 2
 
3 3
 import (
4
+	"net/netip"
5
+
4 6
 	"github.com/moby/moby/api/types/network"
5 7
 )
6 8
 
... ...
@@ -68,8 +70,8 @@ const (
68 68
 
69 69
 // EndpointVirtualIP represents the virtual ip of a port.
70 70
 type EndpointVirtualIP struct {
71
-	NetworkID string `json:",omitempty"`
72
-	Addr      string `json:",omitempty"`
71
+	NetworkID string     `json:",omitempty"`
72
+	Addr      netip.Addr `json:",omitempty"`
73 73
 }
74 74
 
75 75
 // Network represents a network.
... ...
@@ -103,8 +105,8 @@ type NetworkAttachmentConfig struct {
103 103
 
104 104
 // NetworkAttachment represents a network attachment.
105 105
 type NetworkAttachment struct {
106
-	Network   Network  `json:",omitempty"`
107
-	Addresses []string `json:",omitempty"`
106
+	Network   Network      `json:",omitempty"`
107
+	Addresses []netip.Addr `json:",omitempty"`
108 108
 }
109 109
 
110 110
 // IPAMOptions represents ipam options.
... ...
@@ -115,7 +117,7 @@ type IPAMOptions struct {
115 115
 
116 116
 // IPAMConfig represents ipam configuration.
117 117
 type IPAMConfig struct {
118
-	Subnet  string `json:",omitempty"`
119
-	Range   string `json:",omitempty"`
120
-	Gateway string `json:",omitempty"`
118
+	Subnet  netip.Prefix `json:",omitempty"`
119
+	Range   netip.Prefix `json:",omitempty"`
120
+	Gateway netip.Addr   `json:",omitempty"`
121 121
 }
... ...
@@ -1,6 +1,7 @@
1 1
 package swarm
2 2
 
3 3
 import (
4
+	"net/netip"
4 5
 	"time"
5 6
 )
6 7
 
... ...
@@ -12,7 +13,7 @@ type ClusterInfo struct {
12 12
 	Spec                   Spec
13 13
 	TLSInfo                TLSInfo
14 14
 	RootRotationInProgress bool
15
-	DefaultAddrPool        []string
15
+	DefaultAddrPool        []netip.Prefix
16 16
 	SubnetSize             uint32
17 17
 	DataPathPort           uint32
18 18
 }
... ...
@@ -159,7 +160,7 @@ type InitRequest struct {
159 159
 	Spec             Spec
160 160
 	AutoLockManagers bool
161 161
 	Availability     NodeAvailability
162
-	DefaultAddrPool  []string
162
+	DefaultAddrPool  []netip.Prefix
163 163
 	SubnetSize       uint32
164 164
 }
165 165
 
... ...
@@ -4,6 +4,7 @@ import (
4 4
 	"context"
5 5
 	"encoding/json"
6 6
 	"fmt"
7
+	"net/netip"
7 8
 	"strings"
8 9
 
9 10
 	"github.com/containerd/log"
... ...
@@ -11,6 +12,7 @@ import (
11 11
 	"github.com/moby/moby/api/types/container"
12 12
 	mounttypes "github.com/moby/moby/api/types/mount"
13 13
 	types "github.com/moby/moby/api/types/swarm"
14
+	"github.com/moby/moby/v2/daemon/internal/sliceutil"
14 15
 	swarmapi "github.com/moby/swarmkit/v2/api"
15 16
 	"github.com/pkg/errors"
16 17
 )
... ...
@@ -47,7 +49,7 @@ func containerSpecFromGRPC(c *swarmapi.ContainerSpec) *types.ContainerSpec {
47 47
 
48 48
 	if c.DNSConfig != nil {
49 49
 		containerSpec.DNSConfig = &types.DNSConfig{
50
-			Nameservers: c.DNSConfig.Nameservers,
50
+			Nameservers: sliceutil.Map(c.DNSConfig.Nameservers, func(s string) netip.Addr { a, _ := netip.ParseAddr(s); return a }),
51 51
 			Search:      c.DNSConfig.Search,
52 52
 			Options:     c.DNSConfig.Options,
53 53
 		}
... ...
@@ -309,7 +311,7 @@ func containerToGRPC(c *types.ContainerSpec) (*swarmapi.ContainerSpec, error) {
309 309
 
310 310
 	if c.DNSConfig != nil {
311 311
 		containerSpec.DNSConfig = &swarmapi.ContainerSpec_DNSConfig{
312
-			Nameservers: c.DNSConfig.Nameservers,
312
+			Nameservers: sliceutil.Map(c.DNSConfig.Nameservers, (netip.Addr).String),
313 313
 			Search:      c.DNSConfig.Search,
314 314
 			Options:     c.DNSConfig.Options,
315 315
 		}
... ...
@@ -1,6 +1,7 @@
1 1
 package convert
2 2
 
3 3
 import (
4
+	"net/netip"
4 5
 	"strings"
5 6
 	"time"
6 7
 
... ...
@@ -8,6 +9,8 @@ import (
8 8
 	"github.com/moby/moby/api/types/network"
9 9
 	types "github.com/moby/moby/api/types/swarm"
10 10
 	"github.com/moby/moby/v2/daemon/cluster/convert/netextra"
11
+	"github.com/moby/moby/v2/daemon/internal/netiputil"
12
+	"github.com/moby/moby/v2/daemon/internal/sliceutil"
11 13
 	"github.com/moby/moby/v2/daemon/libnetwork/scope"
12 14
 	swarmapi "github.com/moby/swarmkit/v2/api"
13 15
 )
... ...
@@ -16,7 +19,7 @@ func networkAttachmentFromGRPC(na *swarmapi.NetworkAttachment) types.NetworkAtta
16 16
 	if na != nil {
17 17
 		return types.NetworkAttachment{
18 18
 			Network:   networkFromGRPC(na.Network),
19
-			Addresses: na.Addresses,
19
+			Addresses: sliceutil.Map(na.Addresses, func(s string) netip.Addr { a, _ := netip.ParseAddr(s); return a }),
20 20
 		}
21 21
 	}
22 22
 	return types.NetworkAttachment{}
... ...
@@ -82,10 +85,16 @@ func ipamFromGRPC(i *swarmapi.IPAMOptions) *types.IPAMOptions {
82 82
 		}
83 83
 
84 84
 		for _, config := range i.Configs {
85
+			// Best-effort parse of user suppplied values that have
86
+			// been round-tripped through Swarm's Raft store. It is
87
+			// far too late to reject bogus values.
88
+			subnet, _ := netiputil.ParseCIDR(config.Subnet)
89
+			iprange, _ := netiputil.ParseCIDR(config.Range)
90
+			gw, _ := netip.ParseAddr(config.Gateway)
85 91
 			ipam.Configs = append(ipam.Configs, types.IPAMConfig{
86
-				Subnet:  config.Subnet,
87
-				Range:   config.Range,
88
-				Gateway: config.Gateway,
92
+				Subnet:  subnet.Masked(),
93
+				Range:   iprange.Masked(),
94
+				Gateway: gw.Unmap(),
89 95
 			})
90 96
 		}
91 97
 	}
... ...
@@ -118,9 +127,10 @@ func endpointFromGRPC(e *swarmapi.Endpoint) types.Endpoint {
118 118
 		}
119 119
 
120 120
 		for _, v := range e.VirtualIPs {
121
+			vip, _ := netip.ParseAddr(v.Addr)
121 122
 			endpoint.VirtualIPs = append(endpoint.VirtualIPs, types.EndpointVirtualIP{
122 123
 				NetworkID: v.NetworkID,
123
-				Addr:      v.Addr,
124
+				Addr:      vip,
124 125
 			})
125 126
 		}
126 127
 	}
... ...
@@ -2,10 +2,12 @@ package convert
2 2
 
3 3
 import (
4 4
 	"fmt"
5
+	"net/netip"
5 6
 	"strings"
6 7
 
7 8
 	gogotypes "github.com/gogo/protobuf/types"
8 9
 	types "github.com/moby/moby/api/types/swarm"
10
+	"github.com/moby/moby/v2/daemon/internal/sliceutil"
9 11
 	swarmapi "github.com/moby/swarmkit/v2/api"
10 12
 	"github.com/moby/swarmkit/v2/ca"
11 13
 )
... ...
@@ -40,7 +42,7 @@ func SwarmFromGRPC(c swarmapi.Cluster) types.Swarm {
40 40
 				TrustRoot: string(c.RootCA.CACert),
41 41
 			},
42 42
 			RootRotationInProgress: c.RootCA.RootRotation != nil,
43
-			DefaultAddrPool:        c.DefaultAddressPool,
43
+			DefaultAddrPool:        sliceutil.Map(c.DefaultAddressPool, func(s string) netip.Prefix { pfx, _ := netip.ParsePrefix(s); return pfx }),
44 44
 			SubnetSize:             c.SubnetSize,
45 45
 			DataPathPort:           c.VXLANUDPPort,
46 46
 		},
... ...
@@ -4,7 +4,7 @@ import (
4 4
 	"errors"
5 5
 	"fmt"
6 6
 	"net"
7
-	"strings"
7
+	"net/netip"
8 8
 )
9 9
 
10 10
 const (
... ...
@@ -91,7 +91,7 @@ func (c *Cluster) resolveAdvertiseAddr(advertiseAddr, listenAddrPort string) (st
91 91
 
92 92
 // validateDefaultAddrPool validates default address pool
93 93
 // it also strips white space from the string before validation
94
-func validateDefaultAddrPool(defaultAddrPool []string, size uint32) error {
94
+func validateDefaultAddrPool(defaultAddrPool []netip.Prefix, size uint32) error {
95 95
 	if defaultAddrPool == nil {
96 96
 		// defaultAddrPool is not defined
97 97
 		return nil
... ...
@@ -108,16 +108,9 @@ func validateDefaultAddrPool(defaultAddrPool []string, size uint32) error {
108 108
 	if size > 29 {
109 109
 		return fmt.Errorf("subnet size is out of range: %d", size)
110 110
 	}
111
-	for i := range defaultAddrPool {
112
-		// trim leading and trailing white spaces
113
-		defaultAddrPool[i] = strings.TrimSpace(defaultAddrPool[i])
114
-		_, b, err := net.ParseCIDR(defaultAddrPool[i])
115
-		if err != nil {
116
-			return fmt.Errorf("invalid base pool %s: %v", defaultAddrPool[i], err)
117
-		}
118
-		ones, _ := b.Mask.Size()
119
-		if size < uint32(ones) {
120
-			return fmt.Errorf("invalid CIDR: %q. Subnet size is too small for pool: %d", defaultAddrPool[i], size)
111
+	for _, a := range defaultAddrPool {
112
+		if size < uint32(a.Bits()) {
113
+			return fmt.Errorf("invalid CIDR: %q. Subnet size is too small for pool: %d", a, size)
121 114
 		}
122 115
 	}
123 116
 
... ...
@@ -3,6 +3,7 @@ package cluster
3 3
 import (
4 4
 	"context"
5 5
 	"fmt"
6
+	"net/netip"
6 7
 	"path/filepath"
7 8
 	"strings"
8 9
 	"sync"
... ...
@@ -12,6 +13,7 @@ import (
12 12
 	types "github.com/moby/moby/api/types/swarm"
13 13
 	"github.com/moby/moby/v2/daemon/cluster/convert"
14 14
 	"github.com/moby/moby/v2/daemon/cluster/executor/container"
15
+	"github.com/moby/moby/v2/daemon/internal/sliceutil"
15 16
 	lncluster "github.com/moby/moby/v2/daemon/libnetwork/cluster"
16 17
 	"github.com/moby/moby/v2/daemon/libnetwork/cnmallocator"
17 18
 	swarmapi "github.com/moby/swarmkit/v2/api"
... ...
@@ -55,7 +57,7 @@ type nodeStartConfig struct {
55 55
 	// DataPathAddr is the address that has to be used for the data path
56 56
 	DataPathAddr string
57 57
 	// DefaultAddressPool contains list of subnets
58
-	DefaultAddressPool []string
58
+	DefaultAddressPool []netip.Prefix
59 59
 	// SubnetSize contains subnet size of DefaultAddressPool
60 60
 	SubnetSize uint32
61 61
 	// DataPathPort contains Data path port (VXLAN UDP port) number that is used for data traffic.
... ...
@@ -126,7 +128,7 @@ func (n *nodeRunner) start(conf nodeStartConfig) error {
126 126
 		ListenRemoteAPI:    conf.ListenAddr,
127 127
 		AdvertiseRemoteAPI: conf.AdvertiseAddr,
128 128
 		NetworkConfig: &networkallocator.Config{
129
-			DefaultAddrPool: conf.DefaultAddressPool,
129
+			DefaultAddrPool: sliceutil.Map(conf.DefaultAddressPool, (netip.Prefix).String),
130 130
 			SubnetSize:      conf.SubnetSize,
131 131
 			VXLANUDPPort:    conf.DataPathPort,
132 132
 		},
... ...
@@ -21,6 +21,9 @@ func ToIPNet(p netip.Prefix) *net.IPNet {
21 21
 // ToPrefix converts n into a netip.Prefix. If n is not a valid IPv4 or IPV6
22 22
 // address, ToPrefix returns netip.Prefix{}, false.
23 23
 func ToPrefix(n *net.IPNet) (netip.Prefix, bool) {
24
+	if n == nil {
25
+		return netip.Prefix{}, false
26
+	}
24 27
 	if ll := len(n.Mask); ll != net.IPv4len && ll != net.IPv6len {
25 28
 		return netip.Prefix{}, false
26 29
 	}
... ...
@@ -88,3 +91,21 @@ func PrefixAfter(prev netip.Prefix, sz int) netip.Prefix {
88 88
 	}
89 89
 	return netip.PrefixFrom(addr, sz).Masked()
90 90
 }
91
+
92
+// Unmap returns p with any IPv4-mapped IPv6 address prefix removed, per the
93
+// quirky semantics that have been Hyrum's Law'ed into our Engine API contract:
94
+// the subnet mask is truncated to the length of the unmapped address.
95
+// For example:
96
+//   - "::ffff:1.2.3.4/24" -> 1.2.3.4/0
97
+//   - "::ffff:1.2.3.4/120" -> 1.2.3.4/24
98
+func Unmap(p netip.Prefix) netip.Prefix {
99
+	bits := p.Addr().Unmap().BitLen() - p.Addr().BitLen() + p.Bits()
100
+	return netip.PrefixFrom(p.Addr().Unmap(), max(0, bits))
101
+}
102
+
103
+// ParseCIDR parses s as an IP address prefix, like [netip.ParsePrefix],
104
+// unmapping any IPv4-mapped IPv6 address prefixes using [Unmap].
105
+func ParseCIDR(s string) (netip.Prefix, error) {
106
+	prefix, err := netip.ParsePrefix(s)
107
+	return Unmap(prefix), err
108
+}
... ...
@@ -1,6 +1,7 @@
1 1
 package netiputil
2 2
 
3 3
 import (
4
+	"net"
4 5
 	"net/netip"
5 6
 	"testing"
6 7
 
... ...
@@ -44,3 +45,28 @@ func TestPrefixAfter(t *testing.T) {
44 44
 		assert.Check(t, next == tc.want, "PrefixAfter(%q, %d) = %s; want: %s", tc.prev, tc.sz, next, tc.want)
45 45
 	}
46 46
 }
47
+
48
+func TestUnmap(t *testing.T) {
49
+	assert.Check(t, !Unmap(netip.Prefix{}).IsValid())
50
+}
51
+
52
+func TestParseCIDR(t *testing.T) {
53
+	tests := []string{"::ffff:1.2.3.4/24", "::ffff:1.2.3.4/1", "::ffff:1.2.3.4/120", "1.2.3.4/24", "::1/128", "2001:db8::/32"}
54
+	for _, s := range tests {
55
+		// From "github.com/moby/moby/v2/daemon/libnetwork/types".ParseCIDR
56
+		ip, net, err := net.ParseCIDR(s)
57
+		assert.NilError(t, err)
58
+		net.IP = ip
59
+		want := netip.MustParsePrefix(net.String())
60
+
61
+		got, err := ParseCIDR(s)
62
+		assert.Check(t, err)
63
+		if got != want {
64
+			t.Errorf("ParseCIDR(%q) = %v, want %v", s, got, want)
65
+		}
66
+	}
67
+
68
+	got, err := ParseCIDR("invalid")
69
+	assert.Check(t, err != nil, "expected error for invalid input")
70
+	assert.Check(t, !got.IsValid(), "expected invalid result for invalid input")
71
+}
... ...
@@ -2,6 +2,7 @@ package network
2 2
 
3 3
 import (
4 4
 	"context"
5
+	"net/netip"
5 6
 	"strings"
6 7
 	"testing"
7 8
 	"time"
... ...
@@ -417,7 +418,7 @@ func TestServiceWithDefaultAddressPoolInit(t *testing.T) {
417 417
 	ctx := setupTest(t)
418 418
 
419 419
 	d := swarm.NewSwarm(ctx, t, testEnv,
420
-		daemon.WithSwarmDefaultAddrPool([]string{"20.20.0.0/16"}),
420
+		daemon.WithSwarmDefaultAddrPool(netip.MustParsePrefix("20.20.0.0/16")),
421 421
 		daemon.WithSwarmDefaultAddrPoolSubnetSize(24))
422 422
 	defer d.Stop(t)
423 423
 	cli := d.NewClientT(t)
... ...
@@ -1,10 +1,12 @@
1 1
 package service
2 2
 
3 3
 import (
4
+	"net/netip"
4 5
 	"testing"
5 6
 	"time"
6 7
 
7 8
 	"github.com/google/go-cmp/cmp"
9
+	"github.com/google/go-cmp/cmp/cmpopts"
8 10
 	"github.com/moby/moby/api/types/container"
9 11
 	swarmtypes "github.com/moby/moby/api/types/swarm"
10 12
 	"github.com/moby/moby/client"
... ...
@@ -67,7 +69,10 @@ func cmpServiceOpts() cmp.Option {
67 67
 		return delta < threshold && delta > -threshold
68 68
 	})
69 69
 
70
-	return cmp.FilterPath(metaTimeFields, withinThreshold)
70
+	return cmp.Options{
71
+		cmp.FilterPath(metaTimeFields, withinThreshold),
72
+		cmpopts.EquateComparable(netip.Addr{}, netip.Prefix{}),
73
+	}
71 74
 }
72 75
 
73 76
 func fullSwarmServiceSpec(name string, replicas uint64) swarmtypes.ServiceSpec {
... ...
@@ -95,7 +100,7 @@ func fullSwarmServiceSpec(name string, replicas uint64) swarmtypes.ServiceSpec {
95 95
 				StopGracePeriod: &restartDelay,
96 96
 				Hosts:           []string{"8.8.8.8  google"},
97 97
 				DNSConfig: &swarmtypes.DNSConfig{
98
-					Nameservers: []string{"8.8.8.8"},
98
+					Nameservers: []netip.Addr{netip.MustParseAddr("8.8.8.8")},
99 99
 					Search:      []string{"somedomain"},
100 100
 				},
101 101
 				Isolation: container.IsolationDefault,
... ...
@@ -7,6 +7,7 @@ import (
7 7
 	"fmt"
8 8
 	"io"
9 9
 	"net/http"
10
+	"net/netip"
10 11
 	"os"
11 12
 	"os/exec"
12 13
 	"os/user"
... ...
@@ -96,7 +97,7 @@ type Daemon struct {
96 96
 	swarmListenAddr   string
97 97
 	swarmWithIptables bool
98 98
 	SwarmPort         int // FIXME(vdemeester) should probably not be exported
99
-	DefaultAddrPool   []string
99
+	DefaultAddrPool   []netip.Prefix
100 100
 	SubnetSize        uint32
101 101
 	DataPathPort      uint32
102 102
 	OOMScoreAdjust    int
... ...
@@ -1,6 +1,7 @@
1 1
 package daemon
2 2
 
3 3
 import (
4
+	"net/netip"
4 5
 	"os/user"
5 6
 
6 7
 	"github.com/moby/moby/v2/internal/testutil/environment"
... ...
@@ -82,7 +83,7 @@ func WithSwarmIptables(useIptables bool) Option {
82 82
 }
83 83
 
84 84
 // WithSwarmDefaultAddrPool sets the swarm default address pool to use for swarm mode
85
-func WithSwarmDefaultAddrPool(defaultAddrPool []string) Option {
85
+func WithSwarmDefaultAddrPool(defaultAddrPool ...netip.Prefix) Option {
86 86
 	return func(d *Daemon) {
87 87
 		d.DefaultAddrPool = defaultAddrPool
88 88
 	}
... ...
@@ -1,6 +1,7 @@
1 1
 package swarm
2 2
 
3 3
 import (
4
+	"net/netip"
4 5
 	"time"
5 6
 
6 7
 	"github.com/moby/moby/api/types/container"
... ...
@@ -14,7 +15,7 @@ import (
14 14
 // TODO: `domain` is not supported yet.
15 15
 type DNSConfig struct {
16 16
 	// Nameservers specifies the IP addresses of the name servers
17
-	Nameservers []string `json:",omitempty"`
17
+	Nameservers []netip.Addr `json:",omitempty"`
18 18
 	// Search specifies the search list for host-name lookup
19 19
 	Search []string `json:",omitempty"`
20 20
 	// Options allows certain internal resolver variables to be modified
... ...
@@ -1,6 +1,8 @@
1 1
 package swarm
2 2
 
3 3
 import (
4
+	"net/netip"
5
+
4 6
 	"github.com/moby/moby/api/types/network"
5 7
 )
6 8
 
... ...
@@ -68,8 +70,8 @@ const (
68 68
 
69 69
 // EndpointVirtualIP represents the virtual ip of a port.
70 70
 type EndpointVirtualIP struct {
71
-	NetworkID string `json:",omitempty"`
72
-	Addr      string `json:",omitempty"`
71
+	NetworkID string     `json:",omitempty"`
72
+	Addr      netip.Addr `json:",omitempty"`
73 73
 }
74 74
 
75 75
 // Network represents a network.
... ...
@@ -103,8 +105,8 @@ type NetworkAttachmentConfig struct {
103 103
 
104 104
 // NetworkAttachment represents a network attachment.
105 105
 type NetworkAttachment struct {
106
-	Network   Network  `json:",omitempty"`
107
-	Addresses []string `json:",omitempty"`
106
+	Network   Network      `json:",omitempty"`
107
+	Addresses []netip.Addr `json:",omitempty"`
108 108
 }
109 109
 
110 110
 // IPAMOptions represents ipam options.
... ...
@@ -115,7 +117,7 @@ type IPAMOptions struct {
115 115
 
116 116
 // IPAMConfig represents ipam configuration.
117 117
 type IPAMConfig struct {
118
-	Subnet  string `json:",omitempty"`
119
-	Range   string `json:",omitempty"`
120
-	Gateway string `json:",omitempty"`
118
+	Subnet  netip.Prefix `json:",omitempty"`
119
+	Range   netip.Prefix `json:",omitempty"`
120
+	Gateway netip.Addr   `json:",omitempty"`
121 121
 }
... ...
@@ -1,6 +1,7 @@
1 1
 package swarm
2 2
 
3 3
 import (
4
+	"net/netip"
4 5
 	"time"
5 6
 )
6 7
 
... ...
@@ -12,7 +13,7 @@ type ClusterInfo struct {
12 12
 	Spec                   Spec
13 13
 	TLSInfo                TLSInfo
14 14
 	RootRotationInProgress bool
15
-	DefaultAddrPool        []string
15
+	DefaultAddrPool        []netip.Prefix
16 16
 	SubnetSize             uint32
17 17
 	DataPathPort           uint32
18 18
 }
... ...
@@ -159,7 +160,7 @@ type InitRequest struct {
159 159
 	Spec             Spec
160 160
 	AutoLockManagers bool
161 161
 	Availability     NodeAvailability
162
-	DefaultAddrPool  []string
162
+	DefaultAddrPool  []netip.Prefix
163 163
 	SubnetSize       uint32
164 164
 }
165 165