diff options
Diffstat (limited to 'libpod/network/cni')
-rw-r--r-- | libpod/network/cni/cni_conversion.go | 3 | ||||
-rw-r--r-- | libpod/network/cni/config.go | 103 | ||||
-rw-r--r-- | libpod/network/cni/config_test.go | 35 | ||||
-rw-r--r-- | libpod/network/cni/network.go | 32 | ||||
-rw-r--r-- | libpod/network/cni/run.go | 7 | ||||
-rw-r--r-- | libpod/network/cni/run_test.go | 4 |
6 files changed, 124 insertions, 60 deletions
diff --git a/libpod/network/cni/cni_conversion.go b/libpod/network/cni/cni_conversion.go index 09943948b..060794ebe 100644 --- a/libpod/network/cni/cni_conversion.go +++ b/libpod/network/cni/cni_conversion.go @@ -185,6 +185,9 @@ func convertIPAMConfToNetwork(network *types.Network, ipam ipamConfig, confPath s.LeaseRange.StartIP = rangeStart s.LeaseRange.EndIP = rangeEnd } + if util.IsIPv6(s.Subnet.IP) { + network.IPv6Enabled = true + } network.Subnets = append(network.Subnets, s) } } diff --git a/libpod/network/cni/config.go b/libpod/network/cni/config.go index ee203f80d..d31cd3002 100644 --- a/libpod/network/cni/config.go +++ b/libpod/network/cni/config.go @@ -24,7 +24,7 @@ func (n *cniNetwork) NetworkCreate(net types.Network) (types.Network, error) { if err != nil { return types.Network{}, err } - network, err := n.networkCreate(net, true) + network, err := n.networkCreate(net, false) if err != nil { return types.Network{}, err } @@ -33,89 +33,106 @@ func (n *cniNetwork) NetworkCreate(net types.Network) (types.Network, error) { return *network.libpodNet, nil } -func (n *cniNetwork) networkCreate(net types.Network, writeToDisk bool) (*network, error) { +// networkCreate will fill out the given network struct and return the new network entry. +// If defaultNet is true it will not validate against used subnets and it will not write the cni config to disk. +func (n *cniNetwork) networkCreate(newNetwork types.Network, defaultNet bool) (*network, error) { // if no driver is set use the default one - if net.Driver == "" { - net.Driver = types.DefaultNetworkDriver + if newNetwork.Driver == "" { + newNetwork.Driver = types.DefaultNetworkDriver } // FIXME: Should we use a different type for network create without the ID field? // the caller is not allowed to set a specific ID - if net.ID != "" { + if newNetwork.ID != "" { return nil, errors.Wrap(define.ErrInvalidArg, "ID can not be set for network create") } - if net.Labels == nil { - net.Labels = map[string]string{} + if newNetwork.Labels == nil { + newNetwork.Labels = map[string]string{} } - if net.Options == nil { - net.Options = map[string]string{} + if newNetwork.Options == nil { + newNetwork.Options = map[string]string{} } - if net.IPAMOptions == nil { - net.IPAMOptions = map[string]string{} + if newNetwork.IPAMOptions == nil { + newNetwork.IPAMOptions = map[string]string{} } var name string var err error // validate the name when given - if net.Name != "" { - if !define.NameRegex.MatchString(net.Name) { - return nil, errors.Wrapf(define.RegexError, "network name %s invalid", net.Name) + if newNetwork.Name != "" { + if !define.NameRegex.MatchString(newNetwork.Name) { + return nil, errors.Wrapf(define.RegexError, "network name %s invalid", newNetwork.Name) } - if _, ok := n.networks[net.Name]; ok { - return nil, errors.Wrapf(define.ErrNetworkExists, "network name %s already used", net.Name) + if _, ok := n.networks[newNetwork.Name]; ok { + return nil, errors.Wrapf(define.ErrNetworkExists, "network name %s already used", newNetwork.Name) } } else { name, err = n.getFreeDeviceName() if err != nil { return nil, err } - net.Name = name + newNetwork.Name = name } - switch net.Driver { + // 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. + // This could happen if you run a container on this net, then the cni interface will be + // created on the host and "block" this subnet from being used again. + // Therefore the next podman command tries to create the default net again and it would + // fail because it thinks the network is used on the host. + var usedNetworks []*net.IPNet + if !defaultNet { + usedNetworks, err = n.getUsedSubnets() + if err != nil { + return nil, err + } + } + + switch newNetwork.Driver { case types.BridgeNetworkDriver: // if the name was created with getFreeDeviceName set the interface to it as well - if name != "" && net.NetworkInterface == "" { - net.NetworkInterface = name + if name != "" && newNetwork.NetworkInterface == "" { + newNetwork.NetworkInterface = name } - err = n.createBridge(&net) + err = n.createBridge(&newNetwork, usedNetworks) if err != nil { return nil, err } case types.MacVLANNetworkDriver: - err = createMacVLAN(&net) + err = createMacVLAN(&newNetwork) if err != nil { return nil, err } default: - return nil, errors.Wrapf(define.ErrInvalidArg, "unsupported driver %s", net.Driver) + return nil, errors.Wrapf(define.ErrInvalidArg, "unsupported driver %s", newNetwork.Driver) } - for i := range net.Subnets { - err := validateSubnet(&net.Subnets[i], !net.Internal) + for i := range newNetwork.Subnets { + err := validateSubnet(&newNetwork.Subnets[i], !newNetwork.Internal, usedNetworks) if err != nil { return nil, err } - if util.IsIPv6(net.Subnets[i].Subnet.IP) { - net.IPv6Enabled = true + if util.IsIPv6(newNetwork.Subnets[i].Subnet.IP) { + newNetwork.IPv6Enabled = true } } // generate the network ID - net.ID = getNetworkIDFromName(net.Name) + newNetwork.ID = getNetworkIDFromName(newNetwork.Name) // FIXME: Should this be a hard error? - if net.DNSEnabled && net.Internal && hasDNSNamePlugin(n.cniPluginDirs) { - logrus.Warnf("dnsname and internal networks are incompatible. dnsname plugin not configured for network %s", net.Name) - net.DNSEnabled = false + if newNetwork.DNSEnabled && newNetwork.Internal && hasDNSNamePlugin(n.cniPluginDirs) { + logrus.Warnf("dnsname and internal networks are incompatible. dnsname plugin not configured for network %s", newNetwork.Name) + newNetwork.DNSEnabled = false } - cniConf, path, err := n.createCNIConfigListFromNetwork(&net, writeToDisk) + cniConf, path, err := n.createCNIConfigListFromNetwork(&newNetwork, !defaultNet) if err != nil { return nil, err } - return &network{cniNet: cniConf, libpodNet: &net, filename: path}, nil + return &network{cniNet: cniConf, libpodNet: &newNetwork, filename: path}, nil } // NetworkRemove will remove the Network with the given name or ID. @@ -218,7 +235,7 @@ func createMacVLAN(network *types.Network) error { return nil } -func (n *cniNetwork) createBridge(network *types.Network) error { +func (n *cniNetwork) createBridge(network *types.Network, usedNetworks []*net.IPNet) error { if network.NetworkInterface != "" { bridges := n.getBridgeInterfaceNames() if pkgutil.StringInSlice(network.NetworkInterface, bridges) { @@ -236,7 +253,7 @@ func (n *cniNetwork) createBridge(network *types.Network) error { } if len(network.Subnets) == 0 { - freeSubnet, err := n.getFreeIPv4NetworkSubnet() + freeSubnet, err := n.getFreeIPv4NetworkSubnet(usedNetworks) if err != nil { return err } @@ -256,14 +273,14 @@ func (n *cniNetwork) createBridge(network *types.Network) error { } } if !ipv4 { - freeSubnet, err := n.getFreeIPv4NetworkSubnet() + freeSubnet, err := n.getFreeIPv4NetworkSubnet(usedNetworks) if err != nil { return err } network.Subnets = append(network.Subnets, *freeSubnet) } if !ipv6 { - freeSubnet, err := n.getFreeIPv6NetworkSubnet() + freeSubnet, err := n.getFreeIPv6NetworkSubnet(usedNetworks) if err != nil { return err } @@ -278,10 +295,14 @@ func (n *cniNetwork) createBridge(network *types.Network) error { // given gateway and lease range are part of this subnet. If the // gateway is empty and addGateway is true it will get the first // available ip in the subnet assigned. -func validateSubnet(s *types.Subnet, addGateway bool) error { +func validateSubnet(s *types.Subnet, addGateway bool, usedNetworks []*net.IPNet) error { if s == nil { return errors.New("subnet is nil") } + if s.Subnet.IP == nil { + return errors.New("subnet ip is nil") + } + // Reparse to ensure subnet is valid. // Do not use types.ParseCIDR() because we want the ip to be // the network address and not a random ip in the subnet. @@ -289,6 +310,12 @@ func validateSubnet(s *types.Subnet, addGateway bool) error { if err != nil { return errors.Wrap(err, "subnet invalid") } + + // check that the new subnet does not conflict with existing ones + if util.NetworkIntersectsWithNetworks(net, usedNetworks) { + return errors.Errorf("subnet %s is already used on the host or by another config", net.String()) + } + s.Subnet = types.IPNet{IPNet: *net} if s.Gateway != nil { if !s.Subnet.Contains(s.Gateway) { diff --git a/libpod/network/cni/config_test.go b/libpod/network/cni/config_test.go index f67402657..11ad71870 100644 --- a/libpod/network/cni/config_test.go +++ b/libpod/network/cni/config_test.go @@ -313,6 +313,14 @@ var _ = Describe("Config", func() { Expect(network1.Subnets[0].Subnet.String()).To(Equal(subnet)) Expect(network1.Subnets[0].Gateway.String()).To(Equal("fdcc::1")) Expect(network1.Subnets[0].LeaseRange).To(BeNil()) + + // reload configs from disk + libpodNet, err = getNetworkInterface(cniConfDir, false) + Expect(err).To(BeNil()) + // check the the networks are identical + network2, err := libpodNet.NetworkInspect(network1.Name) + Expect(err).To(BeNil()) + Expect(network1).To(Equal(network2)) }) It("create bridge with ipv6 enabled", func() { @@ -508,6 +516,9 @@ var _ = Describe("Config", func() { Expect(network1.Subnets[0].Gateway.String()).To(Equal("10.0.0.1")) Expect(network1.Subnets[0].LeaseRange.StartIP.String()).To(Equal(startIP)) + err = libpodNet.NetworkRemove(network1.Name) + Expect(err).To(BeNil()) + endIP := "10.0.0.10" network = types.Network{ Driver: "bridge", @@ -529,6 +540,9 @@ var _ = Describe("Config", func() { Expect(network1.Subnets[0].Gateway.String()).To(Equal("10.0.0.1")) Expect(network1.Subnets[0].LeaseRange.EndIP.String()).To(Equal(endIP)) + err = libpodNet.NetworkRemove(network1.Name) + Expect(err).To(BeNil()) + network = types.Network{ Driver: "bridge", Subnets: []types.Subnet{ @@ -590,7 +604,7 @@ var _ = Describe("Config", func() { } _, err := libpodNet.NetworkCreate(network) Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("subnet invalid")) + Expect(err.Error()).To(ContainSubstring("subnet ip is nil")) }) It("create network with name", func() { @@ -886,6 +900,25 @@ var _ = Describe("Config", func() { Expect(err.Error()).To(Equal("default network podman cannot be removed")) }) + It("network create with same subnet", func() { + subnet := "10.0.0.0/24" + n, _ := types.ParseCIDR(subnet) + subnet2 := "10.10.0.0/24" + n2, _ := types.ParseCIDR(subnet2) + network := types.Network{Subnets: []types.Subnet{{Subnet: n}, {Subnet: n2}}} + network1, err := libpodNet.NetworkCreate(network) + Expect(err).To(BeNil()) + Expect(network1.Subnets).To(HaveLen(2)) + network = types.Network{Subnets: []types.Subnet{{Subnet: n}}} + _, err = libpodNet.NetworkCreate(network) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("subnet 10.0.0.0/24 is already used on the host or by another config")) + network = types.Network{Subnets: []types.Subnet{{Subnet: n2}}} + _, err = libpodNet.NetworkCreate(network) + 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")) + }) + }) Context("network load valid existing ones", func() { diff --git a/libpod/network/cni/network.go b/libpod/network/cni/network.go index fde08a0c6..46e07f780 100644 --- a/libpod/network/cni/network.go +++ b/libpod/network/cni/network.go @@ -106,6 +106,12 @@ func NewCNINetworkInterface(conf InitConfig) (types.ContainerNetwork, error) { return n, nil } +// Drivers will return the list of supported network drivers +// for this interface. +func (n *cniNetwork) Drivers() []string { + return []string{types.BridgeNetworkDriver, types.MacVLANNetworkDriver} +} + func (n *cniNetwork) loadNetworks() error { // skip loading networks if they are already loaded if n.networks != nil { @@ -179,7 +185,7 @@ func (n *cniNetwork) createDefaultNetwork() (*network, error) { {Subnet: n.defaultSubnet}, }, } - return n.networkCreate(net, false) + return n.networkCreate(net, true) } // getNetwork will lookup a network by name or ID. It returns an @@ -221,12 +227,7 @@ func getNetworkIDFromName(name string) string { } // getFreeIPv6NetworkSubnet returns a unused ipv4 subnet -func (n *cniNetwork) getFreeIPv4NetworkSubnet() (*types.Subnet, error) { - networks, err := n.getUsedSubnets() - if err != nil { - return nil, err - } - +func (n *cniNetwork) getFreeIPv4NetworkSubnet(usedNetworks []*net.IPNet) (*types.Subnet, error) { // the default podman network is 10.88.0.0/16 // start locking for free /24 networks network := &net.IPNet{ @@ -236,12 +237,13 @@ func (n *cniNetwork) getFreeIPv4NetworkSubnet() (*types.Subnet, error) { // TODO: make sure to not use public subnets for { - if intersectsConfig := util.NetworkIntersectsWithNetworks(network, networks); !intersectsConfig { + if intersectsConfig := util.NetworkIntersectsWithNetworks(network, usedNetworks); !intersectsConfig { logrus.Debugf("found free ipv4 network subnet %s", network.String()) return &types.Subnet{ Subnet: types.IPNet{IPNet: *network}, }, nil } + var err error network, err = util.NextSubnet(network) if err != nil { return nil, err @@ -250,12 +252,7 @@ func (n *cniNetwork) getFreeIPv4NetworkSubnet() (*types.Subnet, error) { } // getFreeIPv6NetworkSubnet returns a unused ipv6 subnet -func (n *cniNetwork) getFreeIPv6NetworkSubnet() (*types.Subnet, error) { - networks, err := n.getUsedSubnets() - if err != nil { - return nil, err - } - +func (n *cniNetwork) getFreeIPv6NetworkSubnet(usedNetworks []*net.IPNet) (*types.Subnet, error) { // FIXME: Is 10000 fine as limit? We should prevent an endless loop. for i := 0; i < 10000; i++ { // RFC4193: Choose the ipv6 subnet random and NOT sequentially. @@ -263,7 +260,7 @@ func (n *cniNetwork) getFreeIPv6NetworkSubnet() (*types.Subnet, error) { if err != nil { return nil, err } - if intersectsConfig := util.NetworkIntersectsWithNetworks(&network, networks); !intersectsConfig { + if intersectsConfig := util.NetworkIntersectsWithNetworks(&network, usedNetworks); !intersectsConfig { logrus.Debugf("found free ipv6 network subnet %s", network.String()) return &types.Subnet{ Subnet: types.IPNet{IPNet: network}, @@ -279,9 +276,8 @@ func (n *cniNetwork) getUsedSubnets() ([]*net.IPNet, error) { // first, load all used subnets from network configs subnets := make([]*net.IPNet, 0, len(n.networks)) for _, val := range n.networks { - for _, subnet := range val.libpodNet.Subnets { - // nolint:exportloopref - subnets = append(subnets, &subnet.Subnet.IPNet) + for i := range val.libpodNet.Subnets { + subnets = append(subnets, &val.libpodNet.Subnets[i].Subnet.IPNet) } } // second, load networks from the current system diff --git a/libpod/network/cni/run.go b/libpod/network/cni/run.go index 14634262c..b69953c4b 100644 --- a/libpod/network/cni/run.go +++ b/libpod/network/cni/run.go @@ -114,7 +114,7 @@ func (n *cniNetwork) Setup(namespacePath string, options types.SetupOptions) (ma } logrus.Debugf("cni result for container %s network %s: %v", options.ContainerID, name, cnires) var status types.StatusBlock - status, retErr = cniResultToStatus(cnires) + status, retErr = CNIResultToStatus(cnires) if retErr != nil { return nil, retErr } @@ -123,8 +123,9 @@ func (n *cniNetwork) Setup(namespacePath string, options types.SetupOptions) (ma return results, nil } -// cniResultToStatus convert the cni result to status block -func cniResultToStatus(cniResult *current.Result) (types.StatusBlock, error) { +// CNIResultToStatus convert the cni result to status block +// nolint:golint +func CNIResultToStatus(cniResult *current.Result) (types.StatusBlock, error) { result := types.StatusBlock{} nameservers := make([]net.IP, 0, len(cniResult.DNS.Nameservers)) for _, nameserver := range cniResult.DNS.Nameservers { diff --git a/libpod/network/cni/run_test.go b/libpod/network/cni/run_test.go index 32e88ca61..f6da22a76 100644 --- a/libpod/network/cni/run_test.go +++ b/libpod/network/cni/run_test.go @@ -140,6 +140,10 @@ var _ = Describe("run CNI", func() { Expect(res[defNet].DNSServerIPs).To(BeEmpty()) Expect(res[defNet].DNSSearchDomains).To(BeEmpty()) + // reload the interface so the networks are reload from disk + libpodNet, err := getNetworkInterface(cniConfDir, false) + Expect(err).To(BeNil()) + err = libpodNet.Teardown(netNSContainer.Path(), types.TeardownOptions(setupOpts)) Expect(err).To(BeNil()) }) |