Merge pull request #967 from Luap99/ipam-none

libnetwork: allow new none ipam driver
This commit is contained in:
OpenShift Merge Robot 2022-03-29 13:39:46 -04:00 committed by GitHub
commit 8c96512c34
12 changed files with 582 additions and 147 deletions

View File

@ -128,21 +128,12 @@ func findPluginByName(plugins []*libcni.NetworkConfig, name string) bool {
// convertIPAMConfToNetwork converts A cni IPAMConfig to libpod network subnets. // convertIPAMConfToNetwork converts A cni IPAMConfig to libpod network subnets.
// It returns an array of subnets and an extra bool if dhcp is configured. // It returns an array of subnets and an extra bool if dhcp is configured.
func convertIPAMConfToNetwork(network *types.Network, ipam *ipamConfig, confPath string) error { func convertIPAMConfToNetwork(network *types.Network, ipam *ipamConfig, confPath string) error {
if ipam.PluginType == types.DHCPIPAMDriver { switch ipam.PluginType {
case "":
network.IPAMOptions[types.Driver] = types.NoneIPAMDriver
case types.DHCPIPAMDriver:
network.IPAMOptions[types.Driver] = types.DHCPIPAMDriver network.IPAMOptions[types.Driver] = types.DHCPIPAMDriver
return nil case types.HostLocalIPAMDriver:
}
if ipam.PluginType != types.HostLocalIPAMDriver {
// This is not an error. While we only support certain ipam drivers, we
// cannot make it fail for unsupported ones. CNI is still able to use them,
// just our translation logic cannot convert this into a Network.
// For the same reason this is not warning, it would just be annoying for
// everyone using a unknown ipam driver.
logrus.Infof("unsupported ipam plugin %q in %s", ipam.PluginType, confPath)
return nil
}
network.IPAMOptions[types.Driver] = types.HostLocalIPAMDriver network.IPAMOptions[types.Driver] = types.HostLocalIPAMDriver
for _, r := range ipam.Ranges { for _, r := range ipam.Ranges {
for _, ipam := range r { for _, ipam := range r {
@ -199,6 +190,15 @@ func convertIPAMConfToNetwork(network *types.Network, ipam *ipamConfig, confPath
network.Subnets = append(network.Subnets, s) network.Subnets = append(network.Subnets, s)
} }
} }
default:
// This is not an error. While we only support certain ipam drivers, we
// cannot make it fail for unsupported ones. CNI is still able to use them,
// just our translation logic cannot convert this into a Network.
// For the same reason this is not warning, it would just be annoying for
// everyone using a unknown ipam driver.
logrus.Infof("unsupported ipam plugin %q in %s", ipam.PluginType, confPath)
network.IPAMOptions[types.Driver] = ipam.PluginType
}
return nil return nil
} }
@ -225,10 +225,13 @@ func (n *cniNetwork) createCNIConfigListFromNetwork(network *types.Network, writ
var ( var (
routes []ipamRoute routes []ipamRoute
ipamRanges [][]ipamLocalHostRangeConf ipamRanges [][]ipamLocalHostRangeConf
ipamConf ipamConfig ipamConf *ipamConfig
err error err error
) )
if len(network.Subnets) > 0 {
ipamDriver := network.IPAMOptions[types.Driver]
switch ipamDriver {
case types.HostLocalIPAMDriver:
defIpv4Route := false defIpv4Route := false
defIpv6Route := false defIpv6Route := false
for _, subnet := range network.Subnets { for _, subnet := range network.Subnets {
@ -257,48 +260,22 @@ func (n *cniNetwork) createCNIConfigListFromNetwork(network *types.Network, writ
routes = append(routes, route) routes = append(routes, route)
} }
} }
ipamConf = newIPAMHostLocalConf(routes, ipamRanges) conf := newIPAMHostLocalConf(routes, ipamRanges)
} else { ipamConf = &conf
ipamConf = ipamConfig{PluginType: "dhcp"} case types.DHCPIPAMDriver:
ipamConf = &ipamConfig{PluginType: "dhcp"}
case types.NoneIPAMDriver:
// do nothing
default:
return nil, "", errors.Errorf("unsupported ipam driver %q", ipamDriver)
} }
vlan := 0 opts, err := parseOptions(network.Options, network.Driver)
mtu := 0
vlanPluginMode := ""
for k, v := range network.Options {
switch k {
case "mtu":
mtu, err = internalutil.ParseMTU(v)
if err != nil { if err != nil {
return nil, "", err return nil, "", err
} }
case "vlan":
vlan, err = internalutil.ParseVlan(v)
if err != nil {
return nil, "", err
}
case "mode":
switch network.Driver {
case types.MacVLANNetworkDriver:
if !pkgutil.StringInSlice(v, types.ValidMacVLANModes) {
return nil, "", errors.Errorf("unknown macvlan mode %q", v)
}
case types.IPVLANNetworkDriver:
if !pkgutil.StringInSlice(v, types.ValidIPVLANModes) {
return nil, "", errors.Errorf("unknown ipvlan mode %q", v)
}
default:
return nil, "", errors.Errorf("cannot set option \"mode\" with driver %q", network.Driver)
}
vlanPluginMode = v
default:
return nil, "", errors.Errorf("unsupported network option %s", k)
}
}
isGateway := true isGateway := true
ipMasq := true ipMasq := true
if network.Internal { if network.Internal {
@ -314,7 +291,7 @@ func (n *cniNetwork) createCNIConfigListFromNetwork(network *types.Network, writ
switch network.Driver { switch network.Driver {
case types.BridgeNetworkDriver: case types.BridgeNetworkDriver:
bridge := newHostLocalBridge(network.NetworkInterface, isGateway, ipMasq, mtu, vlan, &ipamConf) bridge := newHostLocalBridge(network.NetworkInterface, isGateway, ipMasq, opts.mtu, opts.vlan, ipamConf)
plugins = append(plugins, bridge, newPortMapPlugin(), newFirewallPlugin(), newTuningPlugin()) plugins = append(plugins, bridge, newPortMapPlugin(), newFirewallPlugin(), newTuningPlugin())
// if we find the dnsname plugin we add configuration for it // if we find the dnsname plugin we add configuration for it
if hasDNSNamePlugin(n.cniPluginDirs) && network.DNSEnabled { if hasDNSNamePlugin(n.cniPluginDirs) && network.DNSEnabled {
@ -323,10 +300,10 @@ func (n *cniNetwork) createCNIConfigListFromNetwork(network *types.Network, writ
} }
case types.MacVLANNetworkDriver: case types.MacVLANNetworkDriver:
plugins = append(plugins, newVLANPlugin(types.MacVLANNetworkDriver, network.NetworkInterface, vlanPluginMode, mtu, &ipamConf)) plugins = append(plugins, newVLANPlugin(types.MacVLANNetworkDriver, network.NetworkInterface, opts.vlanPluginMode, opts.mtu, ipamConf))
case types.IPVLANNetworkDriver: case types.IPVLANNetworkDriver:
plugins = append(plugins, newVLANPlugin(types.IPVLANNetworkDriver, network.NetworkInterface, vlanPluginMode, mtu, &ipamConf)) plugins = append(plugins, newVLANPlugin(types.IPVLANNetworkDriver, network.NetworkInterface, opts.vlanPluginMode, opts.mtu, ipamConf))
default: default:
return nil, "", errors.Errorf("driver %q is not supported by cni", network.Driver) return nil, "", errors.Errorf("driver %q is not supported by cni", network.Driver)
@ -402,3 +379,48 @@ func removeMachinePlugin(conf *libcni.NetworkConfigList) *libcni.NetworkConfigLi
conf.Plugins = plugins conf.Plugins = plugins
return conf return conf
} }
type options struct {
vlan int
mtu int
vlanPluginMode string
}
func parseOptions(networkOptions map[string]string, networkDriver string) (*options, error) {
opt := &options{}
var err error
for k, v := range networkOptions {
switch k {
case "mtu":
opt.mtu, err = internalutil.ParseMTU(v)
if err != nil {
return nil, err
}
case "vlan":
opt.vlan, err = internalutil.ParseVlan(v)
if err != nil {
return nil, err
}
case "mode":
switch networkDriver {
case types.MacVLANNetworkDriver:
if !pkgutil.StringInSlice(v, types.ValidMacVLANModes) {
return nil, errors.Errorf("unknown macvlan mode %q", v)
}
case types.IPVLANNetworkDriver:
if !pkgutil.StringInSlice(v, types.ValidIPVLANModes) {
return nil, errors.Errorf("unknown ipvlan mode %q", v)
}
default:
return nil, errors.Errorf("cannot set option \"mode\" with driver %q", networkDriver)
}
opt.vlanPluginMode = v
default:
return nil, errors.Errorf("unsupported network option %s", k)
}
}
return opt, nil
}

View File

@ -145,12 +145,14 @@ func newHostLocalBridge(name string, isGateWay, ipMasq bool, mtu, vlan int, ipam
MTU: mtu, MTU: mtu,
HairpinMode: true, HairpinMode: true,
Vlan: vlan, Vlan: vlan,
IPAM: *ipamConf,
} }
if ipamConf != nil {
bridge.IPAM = *ipamConf
// if we use host-local set the ips cap to ensure we can set static ips via runtime config // if we use host-local set the ips cap to ensure we can set static ips via runtime config
if ipamConf.PluginType == types.HostLocalIPAMDriver { if ipamConf.PluginType == types.HostLocalIPAMDriver {
bridge.Capabilities = caps bridge.Capabilities = caps
} }
}
return &bridge return &bridge
} }
@ -259,7 +261,9 @@ func hasDNSNamePlugin(paths []string) bool {
func newVLANPlugin(pluginType, device, mode string, mtu int, ipam *ipamConfig) VLANConfig { func newVLANPlugin(pluginType, device, mode string, mtu int, ipam *ipamConfig) VLANConfig {
m := VLANConfig{ m := VLANConfig{
PluginType: pluginType, PluginType: pluginType,
IPAM: *ipam, }
if ipam != nil {
m.IPAM = *ipam
} }
if mtu > 0 { if mtu > 0 {
m.MTU = mtu m.MTU = mtu

View File

@ -53,6 +53,11 @@ func (n *cniNetwork) networkCreate(newNetwork *types.Network, defaultNet bool) (
return nil, err 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. // 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 // 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. // that this network can always be created even when a subnet is already used on the host.
@ -197,13 +202,38 @@ func createIPMACVLAN(network *types.Network) error {
return errors.Errorf("parent interface %s does not exist", network.NetworkInterface) return errors.Errorf("parent interface %s does not exist", network.NetworkInterface)
} }
} }
switch network.IPAMOptions[types.Driver] {
// set default
case "":
if len(network.Subnets) == 0 { if len(network.Subnets) == 0 {
// if no subnets and no driver choose dhcp
network.IPAMOptions[types.Driver] = types.DHCPIPAMDriver network.IPAMOptions[types.Driver] = types.DHCPIPAMDriver
if network.Internal {
return errors.New("internal is not supported with macvlan and dhcp ipam driver")
}
} else { } else {
network.IPAMOptions[types.Driver] = types.HostLocalIPAMDriver network.IPAMOptions[types.Driver] = types.HostLocalIPAMDriver
} }
case types.HostLocalIPAMDriver:
if len(network.Subnets) == 0 {
return errors.New("host-local ipam driver set but no subnets are given")
}
}
if network.IPAMOptions[types.Driver] == types.DHCPIPAMDriver && network.Internal {
return errors.New("internal is not supported with macvlan and dhcp ipam driver")
}
return nil
}
func validateIPAMDriver(n *types.Network) error {
ipamDriver := n.IPAMOptions[types.Driver]
switch ipamDriver {
case "", types.HostLocalIPAMDriver:
case types.DHCPIPAMDriver, types.NoneIPAMDriver:
if len(n.Subnets) > 0 {
return errors.Errorf("%s ipam driver is set but subnets are given", ipamDriver)
}
default:
return errors.Errorf("unsupported ipam driver %q", ipamDriver)
}
return nil return nil
} }

View File

@ -167,6 +167,86 @@ var _ = Describe("Config", func() {
Expect(network1.Internal).To(BeFalse()) Expect(network1.Internal).To(BeFalse())
}) })
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(cniConfDir)
Expect(err).To(BeNil())
network2, err := libpodNet.NetworkInspect(network1.Name)
Expect(err).To(BeNil())
Expect(network2).To(Equal(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 bridge config with dhcp ipam driver", func() {
network := types.Network{
Driver: "bridge",
IPAMOptions: map[string]string{
"driver": "dhcp",
},
}
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", "dhcp"))
Expect(network1.Subnets).To(HaveLen(0))
// reload configs from disk
libpodNet, err = getNetworkInterface(cniConfDir)
Expect(err).To(BeNil())
network2, err := libpodNet.NetworkInspect(network1.Name)
Expect(err).To(BeNil())
Expect(network2).To(Equal(network1))
})
It("create bridge config with none hdcp driver and subnets", func() {
subnet := "10.1.0.0/24"
n, _ := types.ParseCIDR(subnet)
network := types.Network{
Driver: "bridge",
IPAMOptions: map[string]string{
"driver": "dhcp",
},
Subnets: []types.Subnet{
{Subnet: n},
},
}
_, err := libpodNet.NetworkCreate(network)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(Equal("dhcp ipam driver is set but subnets are given"))
})
It("create bridge with same name should fail", func() { It("create bridge with same name should fail", func() {
network := types.Network{ network := types.Network{
Driver: "bridge", Driver: "bridge",
@ -1070,6 +1150,18 @@ var _ = Describe("Config", func() {
Expect(err).To(HaveOccurred()) Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("subnet 10.10.0.0/24 is already used on the host or by another config")) Expect(err.Error()).To(ContainSubstring("subnet 10.10.0.0/24 is already used on the host or by another config"))
}) })
It("create network with invalid ipam driver", func() {
network := types.Network{
Driver: "bridge",
IPAMOptions: map[string]string{
"driver": "blah",
},
}
_, err := libpodNet.NetworkCreate(network)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(Equal("unsupported ipam driver \"blah\""))
})
}) })
Context("network load valid existing ones", func() { Context("network load valid existing ones", func() {
@ -1114,8 +1206,6 @@ var _ = Describe("Config", func() {
// check for the unsupported ipam plugin message // check for the unsupported ipam plugin message
logString := logBuffer.String() logString := logBuffer.String()
Expect(logString).ToNot(BeEmpty()) Expect(logString).ToNot(BeEmpty())
Expect(logString).To(ContainSubstring("unsupported ipam plugin \\\"\\\" in %s", cniConfDir+"/ipam-none.conflist"))
Expect(logString).To(ContainSubstring("unsupported ipam plugin \\\"\\\" in %s", cniConfDir+"/ipam-empty.conflist"))
Expect(logString).To(ContainSubstring("unsupported ipam plugin \\\"static\\\" in %s", cniConfDir+"/ipam-static.conflist")) Expect(logString).To(ContainSubstring("unsupported ipam plugin \\\"static\\\" in %s", cniConfDir+"/ipam-static.conflist"))
}) })
@ -1258,6 +1348,27 @@ var _ = Describe("Config", func() {
Expect(network.ID).To(HaveLen(64)) Expect(network.ID).To(HaveLen(64))
Expect(network.Driver).To(Equal("bridge")) Expect(network.Driver).To(Equal("bridge"))
Expect(network.Subnets).To(HaveLen(0)) Expect(network.Subnets).To(HaveLen(0))
Expect(network.IPAMOptions).To(HaveKeyWithValue("driver", "static"))
})
It("ipam none network", func() {
network, err := libpodNet.NetworkInspect("ipam-none")
Expect(err).To(BeNil())
Expect(network.Name).To(Equal("ipam-none"))
Expect(network.ID).To(HaveLen(64))
Expect(network.Driver).To(Equal("bridge"))
Expect(network.Subnets).To(HaveLen(0))
Expect(network.IPAMOptions).To(HaveKeyWithValue("driver", "none"))
})
It("ipam empty network", func() {
network, err := libpodNet.NetworkInspect("ipam-empty")
Expect(err).To(BeNil())
Expect(network.Name).To(Equal("ipam-empty"))
Expect(network.ID).To(HaveLen(64))
Expect(network.Driver).To(Equal("bridge"))
Expect(network.Subnets).To(HaveLen(0))
Expect(network.IPAMOptions).To(HaveKeyWithValue("driver", "none"))
}) })
It("network list with filters (name)", func() { It("network list with filters (name)", func() {

View File

@ -125,6 +125,17 @@ func CNIResultToStatus(res cnitypes.Result) (types.StatusBlock, error) {
result.DNSSearchDomains = cniResult.DNS.Search result.DNSSearchDomains = cniResult.DNS.Search
interfaces := make(map[string]types.NetInterface) interfaces := make(map[string]types.NetInterface)
for intIndex, netInterface := range cniResult.Interfaces {
// we are only interested about interfaces in the container namespace
if netInterface.Sandbox == "" {
continue
}
mac, err := net.ParseMAC(netInterface.Mac)
if err != nil {
return result, err
}
subnets := make([]types.NetAddress, 0, len(cniResult.IPs))
for _, ip := range cniResult.IPs { for _, ip := range cniResult.IPs {
if ip.Interface == nil { if ip.Interface == nil {
// we do no expect ips without an interface // we do no expect ips without an interface
@ -133,26 +144,18 @@ func CNIResultToStatus(res cnitypes.Result) (types.StatusBlock, error) {
if len(cniResult.Interfaces) <= *ip.Interface { if len(cniResult.Interfaces) <= *ip.Interface {
return result, errors.Errorf("invalid cni result, interface index %d out of range", *ip.Interface) return result, errors.Errorf("invalid cni result, interface index %d out of range", *ip.Interface)
} }
cniInt := cniResult.Interfaces[*ip.Interface]
netInt, ok := interfaces[cniInt.Name] // when we have a ip for this interface add it to the subnets
if ok { if *ip.Interface == intIndex {
netInt.Subnets = append(netInt.Subnets, types.NetAddress{ subnets = append(subnets, types.NetAddress{
IPNet: types.IPNet{IPNet: ip.Address}, IPNet: types.IPNet{IPNet: ip.Address},
Gateway: ip.Gateway, Gateway: ip.Gateway,
}) })
interfaces[cniInt.Name] = netInt
} else {
mac, err := net.ParseMAC(cniInt.Mac)
if err != nil {
return result, err
} }
interfaces[cniInt.Name] = types.NetInterface{ }
interfaces[netInterface.Name] = types.NetInterface{
MacAddress: types.HardwareAddr(mac), MacAddress: types.HardwareAddr(mac),
Subnets: []types.NetAddress{{ Subnets: subnets,
IPNet: types.IPNet{IPNet: ip.Address},
Gateway: ip.Gateway,
}},
}
} }
} }
result.Interfaces = interfaces result.Interfaces = interfaces

View File

@ -830,6 +830,91 @@ var _ = Describe("run CNI", func() {
Expect(err).To(BeNil()) Expect(err).To(BeNil())
}) })
}) })
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())
logString := logBuffer.String()
Expect(logString).To(BeEmpty())
// 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())
})
})
}) })
Context("network setup test with networks from disk", func() { Context("network setup test with networks from disk", func() {

View File

@ -27,7 +27,9 @@ func CreateBridge(n NetUtil, network *types.Network, usedNetworks []*net.IPNet,
} }
} }
if network.IPAMOptions[types.Driver] != types.DHCPIPAMDriver { ipamDriver := network.IPAMOptions[types.Driver]
// also do this when the driver is unset
if ipamDriver == "" || ipamDriver == types.HostLocalIPAMDriver {
if len(network.Subnets) == 0 { if len(network.Subnets) == 0 {
freeSubnet, err := GetFreeIPv4NetworkSubnet(usedNetworks, subnetPools) freeSubnet, err := GetFreeIPv4NetworkSubnet(usedNetworks, subnetPools)
if err != nil { if err != nil {

View File

@ -67,6 +67,11 @@ func (n *netavarkNetwork) networkCreate(newNetwork *types.Network, defaultNet bo
return nil, err 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. // 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 // 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. // that this network can always be created even when a subnet is already used on the host.
@ -153,10 +158,19 @@ func createMacvlan(network *types.Network) error {
return errors.Errorf("parent interface %s does not exist", network.NetworkInterface) return errors.Errorf("parent interface %s does not exist", network.NetworkInterface)
} }
} }
// 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 { if len(network.Subnets) == 0 {
return errors.Errorf("macvlan driver needs at least one subnet specified, DHCP is not supported with netavark") return errors.Errorf("macvlan driver needs at least one subnet specified, DHCP is not yet supported with netavark")
} }
network.IPAMOptions[types.Driver] = types.HostLocalIPAMDriver 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")
}
}
// validate the given options, we do not need them but just check to make sure they are valid // validate the given options, we do not need them but just check to make sure they are valid
for key, value := range network.Options { for key, value := range network.Options {
@ -246,3 +260,19 @@ func (n *netavarkNetwork) NetworkInspect(nameOrID string) (types.Network, error)
} }
return *network, nil 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
}

View File

@ -794,7 +794,7 @@ var _ = Describe("Config", func() {
network := types.Network{Driver: "macvlan"} network := types.Network{Driver: "macvlan"}
_, err := libpodNet.NetworkCreate(network) _, err := libpodNet.NetworkCreate(network)
Expect(err).To(HaveOccurred()) 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() { It("create macvlan config with internal", func() {
@ -957,6 +957,69 @@ var _ = Describe("Config", func() {
Expect(network1.Options).To(HaveKeyWithValue("mtu", "9000")) 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() { Context("network load valid existing ones", func() {

View File

@ -399,10 +399,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{ network, err := networkInterface.NetworkCreate(types.Network{
IPAMOptions: map[string]string{ IPAMOptions: map[string]string{
"driver": types.DHCPIPAMDriver, "driver": types.NoneIPAMDriver,
}, },
}) })
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())

View File

@ -700,6 +700,89 @@ var _ = Describe("run netavark", func() {
Expect(err.Error()).To(ContainSubstring("interface eth0 already exists on container namespace")) 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) { func runNetListener(wg *sync.WaitGroup, protocol, ip string, port int, expectedData string) {

View File

@ -12,10 +12,12 @@ const (
// IPAM drivers // IPAM drivers
Driver = "driver" Driver = "driver"
// HostLocalIPAMDriver store the ip // HostLocalIPAMDriver store the ip locally in a db
HostLocalIPAMDriver = "host-local" HostLocalIPAMDriver = "host-local"
// DHCPIPAMDriver get subnet and ip from dhcp server // DHCPIPAMDriver get subnet and ip from dhcp server
DHCPIPAMDriver = "dhcp" DHCPIPAMDriver = "dhcp"
// NoneIPAMDriver do not provide ipam management
NoneIPAMDriver = "none"
// DefaultSubnet is the name that will be used for the default CNI network. // DefaultSubnet is the name that will be used for the default CNI network.
DefaultNetworkName = "podman" DefaultNetworkName = "podman"