diff --git a/common/libnetwork/netavark/config.go b/common/libnetwork/netavark/config.go index 3b78544352..6321356865 100644 --- a/common/libnetwork/netavark/config.go +++ b/common/libnetwork/netavark/config.go @@ -166,6 +166,7 @@ func (n *netavarkNetwork) networkCreate(newNetwork *types.Network, defaultNet bo type ConfigOpts struct { SubnetPools []config.SubnetPool `json:"subnet_pools"` + SubnetPoolsV6 []config.SubnetPool `json:"subnet_pools_v6,omitempty"` DefaultInterfaceName string `json:"default_interface_name"` CheckUsedSubnets bool `json:"check_used_subnets"` } @@ -190,6 +191,7 @@ func (n *netavarkNetwork) networkCreate(newNetwork *types.Network, defaultNet bo }, Options: ConfigOpts{ SubnetPools: n.defaultsubnetPools, + SubnetPoolsV6: n.defaultSubnetPoolsV6, DefaultInterfaceName: n.DefaultInterfaceName(), CheckUsedSubnets: !defaultNet, }, diff --git a/common/libnetwork/netavark/network.go b/common/libnetwork/netavark/network.go index c06d0486f2..cf765f3e83 100644 --- a/common/libnetwork/netavark/network.go +++ b/common/libnetwork/netavark/network.go @@ -47,6 +47,12 @@ type netavarkNetwork struct { // defaultsubnetPools contains the subnets which must be used to allocate a free subnet by network create defaultsubnetPools []config.SubnetPool + // defaultSubnetV6 is the default ipv6 subnet for the default network (may be zero value). + defaultSubnetV6 types.IPNet + + // defaultSubnetPoolsV6 contains ipv6 subnets for allocating free subnets by network create + defaultSubnetPoolsV6 []config.SubnetPool + // dnsBindPort is set the port to pass to netavark for aardvark dnsBindPort uint16 @@ -150,6 +156,16 @@ func NewNetworkInterface(conf *InitConfig) (types.ContainerNetwork, error) { defaultSubnetPools = config.DefaultSubnetPools } + var defaultSubnetV6 types.IPNet + if v6 := conf.Config.Network.DefaultSubnetV6; v6 != "" { + defaultSubnetV6, err = types.ParseCIDR(v6) + if err != nil { + return nil, fmt.Errorf("failed to parse default ipv6 subnet: %w", err) + } + } + + defaultSubnetPoolsV6 := conf.Config.Network.DefaultSubnetPoolsV6 + n := &netavarkNetwork{ networkConfigDir: conf.NetworkConfigDir, networkRunDir: conf.NetworkRunDir, @@ -161,6 +177,8 @@ func NewNetworkInterface(conf *InitConfig) (types.ContainerNetwork, error) { defaultNetwork: defaultNetworkName, defaultSubnet: defaultNet, defaultsubnetPools: defaultSubnetPools, + defaultSubnetV6: defaultSubnetV6, + defaultSubnetPoolsV6: defaultSubnetPoolsV6, dnsBindPort: conf.Config.Network.DNSBindPort, pluginDirs: conf.Config.Network.NetavarkPluginDirs.Get(), lock: lock, @@ -303,16 +321,25 @@ func parseNetwork(network *types.Network) error { } func (n *netavarkNetwork) createDefaultNetwork() (*types.Network, error) { + subnets := []types.Subnet{ + {Subnet: n.defaultSubnet}, + } + + ipv6Enabled := false + if n.defaultSubnetV6.IP != nil { + subnets = append(subnets, types.Subnet{Subnet: n.defaultSubnetV6}) + ipv6Enabled = true + } + network := &types.Network{ Name: n.defaultNetwork, NetworkInterface: defaultBridgeName + "0", // Important do not change this ID - ID: "2f259bab93aaaaa2542ba43ef33eb990d0999ee1b9924b557b7be53c0b7a1bb9", - Driver: types.BridgeNetworkDriver, - Created: time.Now(), - Subnets: []types.Subnet{ - {Subnet: n.defaultSubnet}, - }, + ID: "2f259bab93aaaaa2542ba43ef33eb990d0999ee1b9924b557b7be53c0b7a1bb9", + Driver: types.BridgeNetworkDriver, + Created: time.Now(), + Subnets: subnets, + IPv6Enabled: ipv6Enabled, IPAMOptions: map[string]string{ "driver": types.HostLocalIPAMDriver, }, diff --git a/common/pkg/config/config.go b/common/pkg/config/config.go index 22d161e27f..2038f7618f 100644 --- a/common/pkg/config/config.go +++ b/common/pkg/config/config.go @@ -604,10 +604,19 @@ type NetworkConfig struct { // DefaultSubnetPools is a list of subnets and size which are used to // allocate subnets automatically for podman network create. // It will iterate through the list and will pick the first free subnet - // with the given size. This is only used for ipv4 subnets, ipv6 subnets - // are always assigned randomly. + // with the given size. This is only used for ipv4 subnets. DefaultSubnetPools []SubnetPool `toml:"default_subnet_pools,omitempty"` + // DefaultSubnetV6 is the IPv6 subnet for the default network. + // If empty, no IPv6 subnet is added to the default network. + // Must be a valid IPv6 CIDR block. + DefaultSubnetV6 string `toml:"default_subnet_v6,omitempty"` + + // DefaultSubnetPoolsV6 is a list of IPv6 subnets and prefix lengths used to + // allocate subnets automatically for podman network create with --ipv6. + // If empty, IPv6 subnets are assigned randomly from fd00::/8 (RFC 4193). + DefaultSubnetPoolsV6 []SubnetPool `toml:"default_subnet_pools_v6,omitempty"` + // DefaultRootlessNetworkCmd is used to set the default rootless network // program, either "slirp4nents" (default) or "pasta". DefaultRootlessNetworkCmd string `toml:"default_rootless_network_cmd,omitempty"` @@ -948,6 +957,19 @@ func (c *NetworkConfig) Validate() error { } } + for _, pool := range c.DefaultSubnetPoolsV6 { + if pool.Base.IP.To4() != nil { + return fmt.Errorf("invalid ipv6 subnet pool ip %q, must be ipv6", pool.Base.IP) + } + ones, _ := pool.Base.IPNet.Mask.Size() + if ones > pool.Size { + return fmt.Errorf("invalid ipv6 subnet pool, size is bigger than subnet %q", &pool.Base.IPNet) + } + if pool.Size > 128 { + return errors.New("invalid ipv6 subnet pool size, must be between 0-128") + } + } + return nil } diff --git a/common/pkg/config/config_local_test.go b/common/pkg/config/config_local_test.go index 833a1b60de..1b70cb6470 100644 --- a/common/pkg/config/config_local_test.go +++ b/common/pkg/config/config_local_test.go @@ -85,6 +85,61 @@ var _ = Describe("Config Local", func() { )) }) + It("should fail on invalid ipv6 subnet pool with ipv4 address", func() { + defConf, err := defaultConfig() + gomega.Expect(err).ToNot(gomega.HaveOccurred()) + + net, _ := types.ParseCIDR("10.0.0.0/8") + defConf.Network.DefaultSubnetPoolsV6 = []SubnetPool{ + {Base: &net, Size: 24}, + } + + err = defConf.Network.Validate() + gomega.Expect(err).To(gomega.HaveOccurred()) + gomega.Expect(err.Error()).To(gomega.ContainSubstring("must be ipv6")) + }) + + It("should fail on invalid ipv6 subnet pool size bigger than subnet", func() { + defConf, err := defaultConfig() + gomega.Expect(err).ToNot(gomega.HaveOccurred()) + + net, _ := types.ParseCIDR("fd00::/48") + defConf.Network.DefaultSubnetPoolsV6 = []SubnetPool{ + {Base: &net, Size: 32}, + } + + err = defConf.Network.Validate() + gomega.Expect(err).To(gomega.HaveOccurred()) + gomega.Expect(err.Error()).To(gomega.ContainSubstring("size is bigger than subnet")) + }) + + It("should fail on invalid ipv6 subnet pool size exceeding 128", func() { + defConf, err := defaultConfig() + gomega.Expect(err).ToNot(gomega.HaveOccurred()) + + net, _ := types.ParseCIDR("fd00::/8") + defConf.Network.DefaultSubnetPoolsV6 = []SubnetPool{ + {Base: &net, Size: 129}, + } + + err = defConf.Network.Validate() + gomega.Expect(err).To(gomega.HaveOccurred()) + gomega.Expect(err.Error()).To(gomega.ContainSubstring("must be between 0-128")) + }) + + It("should pass on valid ipv6 subnet pool", func() { + defConf, err := defaultConfig() + gomega.Expect(err).ToNot(gomega.HaveOccurred()) + + net, _ := types.ParseCIDR("fd00::/8") + defConf.Network.DefaultSubnetPoolsV6 = []SubnetPool{ + {Base: &net, Size: 64}, + } + + err = defConf.Network.Validate() + gomega.Expect(err).ToNot(gomega.HaveOccurred()) + }) + It("parse dns port", func() { // Given config, err := newLocked(&Options{}, testConfigPath("")) diff --git a/common/pkg/config/containers.conf b/common/pkg/config/containers.conf index 9c998dde09..3f2d311d3c 100644 --- a/common/pkg/config/containers.conf +++ b/common/pkg/config/containers.conf @@ -392,8 +392,7 @@ default_sysctls = [ # DefaultSubnetPools is a list of subnets and size which are used to # allocate subnets automatically for podman network create. # It will iterate through the list and will pick the first free subnet -# with the given size. This is only used for ipv4 subnets, ipv6 subnets -# are always assigned randomly. +# with the given size. This is only used for ipv4 subnets. # #default_subnet_pools = [ # {"base" = "10.89.0.0/16", "size" = 24}, @@ -403,6 +402,22 @@ default_sysctls = [ # {"base" = "10.128.0.0/9", "size" = 24}, #] +# The IPv6 subnet for the default network. +# If empty, no IPv6 subnet is added to the default network. +# Must be a valid IPv6 CIDR block. +# +#default_subnet_v6 = "" + +# DefaultSubnetPoolsV6 is a list of IPv6 subnets and prefix lengths used to +# allocate subnets automatically for podman network create with --ipv6. +# It will iterate through the list and will pick the first free subnet +# with the given size. If empty, IPv6 subnets are assigned randomly +# from fd00::/8 (RFC 4193). +# +#default_subnet_pools_v6 = [ +# {"base" = "fd00::/48", "size" = 64}, +#] + # Configure which rootless network program to use by default.