From cb62f29959be1825df633736e598b2d57b76316b Mon Sep 17 00:00:00 2001 From: Paul Holzinger Date: Thu, 17 Mar 2022 18:16:02 +0100 Subject: [PATCH] libnetwork/netavark: allow network create with no ipam driver Network create now uses the ipam driver. This allows the user to configure the ipam driver manually instead of choosing a fixed default. If the ipam driver is `none` no ips will be assigned to this container. This means that only the interfaces are created. This will require a patch in netavark since it rejects the config when no static ips are provided. Ref containers/podman#13521 Signed-off-by: Paul Holzinger --- libnetwork/netavark/config.go | 36 +++++++++++-- libnetwork/netavark/config_test.go | 65 ++++++++++++++++++++++- libnetwork/netavark/ipam_test.go | 4 +- libnetwork/netavark/run_test.go | 83 ++++++++++++++++++++++++++++++ 4 files changed, 182 insertions(+), 6 deletions(-) diff --git a/libnetwork/netavark/config.go b/libnetwork/netavark/config.go index e950e1bff..742506ebd 100644 --- a/libnetwork/netavark/config.go +++ b/libnetwork/netavark/config.go @@ -66,6 +66,11 @@ func (n *netavarkNetwork) networkCreate(newNetwork *types.Network, defaultNet bo return nil, err } + err = validateIPAMDriver(newNetwork) + if err != nil { + return nil, err + } + // Only get the used networks for validation if we do not create the default network. // The default network should not be validated against used subnets, we have to ensure // that this network can always be created even when a subnet is already used on the host. @@ -152,10 +157,19 @@ func createMacvlan(network *types.Network) error { return errors.Errorf("parent interface %s does not exist", network.NetworkInterface) } } - if len(network.Subnets) == 0 { - return errors.Errorf("macvlan driver needs at least one subnet specified, DHCP is not supported with netavark") + + // we already validated the drivers before so we just have to set the default here + switch network.IPAMOptions[types.Driver] { + case "": + if len(network.Subnets) == 0 { + return errors.Errorf("macvlan driver needs at least one subnet specified, DHCP is not yet supported with netavark") + } + network.IPAMOptions[types.Driver] = types.HostLocalIPAMDriver + case types.HostLocalIPAMDriver: + if len(network.Subnets) == 0 { + return errors.Errorf("macvlan driver needs at least one subnet specified, when the host-local ipam driver is set") + } } - network.IPAMOptions[types.Driver] = types.HostLocalIPAMDriver // validate the given options, we do not need them but just check to make sure they are valid for key, value := range network.Options { @@ -245,3 +259,19 @@ func (n *netavarkNetwork) NetworkInspect(nameOrID string) (types.Network, error) } return *network, nil } + +func validateIPAMDriver(n *types.Network) error { + ipamDriver := n.IPAMOptions[types.Driver] + switch ipamDriver { + case "", types.HostLocalIPAMDriver: + case types.NoneIPAMDriver: + if len(n.Subnets) > 0 { + return errors.New("none ipam driver is set but subnets are given") + } + case types.DHCPIPAMDriver: + return errors.New("dhcp ipam driver is not yet supported with netavark") + default: + return errors.Errorf("unsupported ipam driver %q", ipamDriver) + } + return nil +} diff --git a/libnetwork/netavark/config_test.go b/libnetwork/netavark/config_test.go index 946d372a4..c8f690ba3 100644 --- a/libnetwork/netavark/config_test.go +++ b/libnetwork/netavark/config_test.go @@ -793,7 +793,7 @@ var _ = Describe("Config", func() { network := types.Network{Driver: "macvlan"} _, err := libpodNet.NetworkCreate(network) Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("macvlan driver needs at least one subnet specified, DHCP is not supported with netavark")) + Expect(err.Error()).To(Equal("macvlan driver needs at least one subnet specified, DHCP is not yet supported with netavark")) }) It("create macvlan config with internal", func() { @@ -956,6 +956,69 @@ var _ = Describe("Config", func() { Expect(network1.Options).To(HaveKeyWithValue("mtu", "9000")) }) + It("create bridge config with none ipam driver", func() { + network := types.Network{ + Driver: "bridge", + IPAMOptions: map[string]string{ + "driver": "none", + }, + } + network1, err := libpodNet.NetworkCreate(network) + Expect(err).To(BeNil()) + Expect(network1.Driver).To(Equal("bridge")) + Expect(network1.IPAMOptions).ToNot(BeEmpty()) + Expect(network1.IPAMOptions).To(HaveKeyWithValue("driver", "none")) + Expect(network1.Subnets).To(HaveLen(0)) + + // reload configs from disk + libpodNet, err = getNetworkInterface(networkConfDir) + Expect(err).To(BeNil()) + + network2, err := libpodNet.NetworkInspect(network1.Name) + Expect(err).To(BeNil()) + EqualNetwork(network2, network1) + }) + + It("create bridge config with none ipam driver and subnets", func() { + subnet := "10.1.0.0/24" + n, _ := types.ParseCIDR(subnet) + network := types.Network{ + Driver: "bridge", + IPAMOptions: map[string]string{ + "driver": "none", + }, + Subnets: []types.Subnet{ + {Subnet: n}, + }, + } + _, err := libpodNet.NetworkCreate(network) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(Equal("none ipam driver is set but subnets are given")) + }) + + It("create macvlan config with none ipam driver", func() { + network := types.Network{ + Driver: "macvlan", + IPAMOptions: map[string]string{ + "driver": "none", + }, + } + network1, err := libpodNet.NetworkCreate(network) + Expect(err).To(BeNil()) + Expect(network1.Driver).To(Equal("macvlan")) + Expect(network1.IPAMOptions).ToNot(BeEmpty()) + Expect(network1.IPAMOptions).To(HaveKeyWithValue("driver", "none")) + Expect(network1.Subnets).To(HaveLen(0)) + + // reload configs from disk + libpodNet, err = getNetworkInterface(networkConfDir) + Expect(err).To(BeNil()) + + network2, err := libpodNet.NetworkInspect(network1.Name) + Expect(err).To(BeNil()) + EqualNetwork(network2, network1) + }) + }) Context("network load valid existing ones", func() { diff --git a/libnetwork/netavark/ipam_test.go b/libnetwork/netavark/ipam_test.go index 734b49eef..cc6c8ab79 100644 --- a/libnetwork/netavark/ipam_test.go +++ b/libnetwork/netavark/ipam_test.go @@ -398,10 +398,10 @@ var _ = Describe("IPAM", func() { } }) - It("ipam with dhcp driver should not set ips", func() { + It("ipam with none driver should not set ips", func() { network, err := networkInterface.NetworkCreate(types.Network{ IPAMOptions: map[string]string{ - "driver": types.DHCPIPAMDriver, + "driver": types.NoneIPAMDriver, }, }) Expect(err).ToNot(HaveOccurred()) diff --git a/libnetwork/netavark/run_test.go b/libnetwork/netavark/run_test.go index 5048101af..581079bf6 100644 --- a/libnetwork/netavark/run_test.go +++ b/libnetwork/netavark/run_test.go @@ -699,6 +699,89 @@ var _ = Describe("run netavark", func() { Expect(err.Error()).To(ContainSubstring("interface eth0 already exists on container namespace")) }) }) + + It("setup ipam driver none network", func() { + runTest(func() { + network := types.Network{ + IPAMOptions: map[string]string{ + types.Driver: types.NoneIPAMDriver, + }, + } + network1, err := libpodNet.NetworkCreate(network) + Expect(err).To(BeNil()) + + intName1 := "eth0" + netName1 := network1.Name + + setupOpts := types.SetupOptions{ + NetworkOptions: types.NetworkOptions{ + ContainerID: stringid.GenerateNonCryptoID(), + Networks: map[string]types.PerNetworkOptions{ + netName1: { + InterfaceName: intName1, + }, + }, + }, + } + + res, err := libpodNet.Setup(netNSContainer.Path(), setupOpts) + Expect(err).To(BeNil()) + Expect(res).To(HaveLen(1)) + + Expect(res).To(HaveKey(netName1)) + Expect(res[netName1].Interfaces).To(HaveKey(intName1)) + Expect(res[netName1].Interfaces[intName1].Subnets).To(HaveLen(0)) + macInt1 := res[netName1].Interfaces[intName1].MacAddress + Expect(macInt1).To(HaveLen(6)) + + // check in the container namespace if the settings are applied + err = netNSContainer.Do(func(_ ns.NetNS) error { + defer GinkgoRecover() + i, err := net.InterfaceByName(intName1) + Expect(err).To(BeNil()) + Expect(i.Name).To(Equal(intName1)) + Expect(i.HardwareAddr).To(Equal(net.HardwareAddr(macInt1))) + addrs, err := i.Addrs() + Expect(err).To(BeNil()) + // we still have the ipv6 link local address + Expect(addrs).To(HaveLen(1)) + addr, ok := addrs[0].(*net.IPNet) + Expect(ok).To(BeTrue(), "cast address to ipnet") + // make sure we are link local + Expect(addr.IP.IsLinkLocalUnicast()).To(BeTrue(), "ip is link local address") + + // check loopback adapter + i, err = net.InterfaceByName("lo") + Expect(err).To(BeNil()) + Expect(i.Name).To(Equal("lo")) + Expect(i.Flags & net.FlagLoopback).To(Equal(net.FlagLoopback)) + Expect(i.Flags&net.FlagUp).To(Equal(net.FlagUp), "Loopback adapter should be up") + return nil + }) + Expect(err).To(BeNil()) + + err = libpodNet.Teardown(netNSContainer.Path(), types.TeardownOptions(setupOpts)) + Expect(err).To(BeNil()) + + // check in the container namespace that the interface is removed + err = netNSContainer.Do(func(_ ns.NetNS) error { + defer GinkgoRecover() + _, err := net.InterfaceByName(intName1) + Expect(err).To(HaveOccurred()) + + // check that only the loopback adapter is left + ints, err := net.Interfaces() + Expect(err).To(BeNil()) + Expect(ints).To(HaveLen(1)) + Expect(ints[0].Name).To(Equal("lo")) + Expect(ints[0].Flags & net.FlagLoopback).To(Equal(net.FlagLoopback)) + Expect(ints[0].Flags&net.FlagUp).To(Equal(net.FlagUp), "Loopback adapter should be up") + + return nil + }) + Expect(err).To(BeNil()) + }) + }) }) func runNetListener(wg *sync.WaitGroup, protocol, ip string, port int, expectedData string) {