diff --git a/.cirrus.yml b/.cirrus.yml index b2079e0049..3edd5510c0 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -36,7 +36,7 @@ env: # package is not available in earlier releases; once we update to a future # Fedora release (or if the package is backported), switch back from Rawhide # to the latest Fedora release. - IMAGE_SUFFIX: "c20260415t185141z-f43f42d14" + IMAGE_SUFFIX: "c20260425t010036z-f43f42d14" RAWHIDE_CACHE_IMAGE_NAME: "rawhide-${IMAGE_SUFFIX}" FEDORA_CACHE_IMAGE_NAME: "fedora-${IMAGE_SUFFIX}" diff --git a/common/libnetwork/internal/util/create.go b/common/libnetwork/internal/util/create.go deleted file mode 100644 index 5096987dd0..0000000000 --- a/common/libnetwork/internal/util/create.go +++ /dev/null @@ -1,57 +0,0 @@ -package util - -import ( - "fmt" - - "github.com/sirupsen/logrus" - "go.podman.io/common/libnetwork/types" -) - -func CommonNetworkCreate(n NetUtil, network *types.Network) error { - if network.Labels == nil { - network.Labels = map[string]string{} - } - if network.Options == nil { - network.Options = map[string]string{} - } - if network.IPAMOptions == nil { - network.IPAMOptions = map[string]string{} - } - - var name string - var err error - // validate the name when given - if network.Name != "" { - if !types.NameRegex.MatchString(network.Name) { - return fmt.Errorf("network name %s invalid: %w", network.Name, types.ErrInvalidName) - } - if _, err := n.Network(network.Name); err == nil { - return fmt.Errorf("network name %s already used: %w", network.Name, types.ErrNetworkExists) - } - } else { - name, err = GetFreeDeviceName(n) - if err != nil { - return err - } - network.Name = name - // also use the name as interface name when we create a bridge network - if network.Driver == types.BridgeNetworkDriver && network.NetworkInterface == "" { - network.NetworkInterface = name - } - } - - // Validate interface name if specified - if network.NetworkInterface != "" { - if err := ValidateInterfaceName(network.NetworkInterface); err != nil { - return fmt.Errorf("network interface name %s invalid: %w", network.NetworkInterface, err) - } - } - return nil -} - -func IpamNoneDisableDNS(network *types.Network) { - if network.IPAMOptions[types.Driver] == types.NoneIPAMDriver { - logrus.Debugf("dns disabled for network %q because ipam driver is set to none", network.Name) - network.DNSEnabled = false - } -} diff --git a/common/libnetwork/netavark/config.go b/common/libnetwork/netavark/config.go index f8631d2895..0ccf39e39f 100644 --- a/common/libnetwork/netavark/config.go +++ b/common/libnetwork/netavark/config.go @@ -10,11 +10,11 @@ import ( "os" "path/filepath" "slices" - "strconv" - "time" + "strings" internalutil "go.podman.io/common/libnetwork/internal/util" "go.podman.io/common/libnetwork/types" + "go.podman.io/common/pkg/config" "go.podman.io/storage/pkg/stringid" ) @@ -100,13 +100,14 @@ func (n *netavarkNetwork) NetworkCreate(net types.Network, options *types.Networ if err != nil { return types.Network{}, err } + + if options != nil && options.IgnoreIfExists { + if network, ok := n.networks[net.Name]; ok { + return *network, nil + } + } network, err := n.networkCreate(&net, false) if err != nil { - if options != nil && options.IgnoreIfExists && errors.Is(err, types.ErrNetworkExists) { - if network, ok := n.networks[net.Name]; ok { - return *network, nil - } - } return types.Network{}, err } // add the new network to the map @@ -139,251 +140,86 @@ func (n *netavarkNetwork) networkCreate(newNetwork *types.Network, defaultNet bo return nil, errors.New("failed to create random network ID") } } - - err := internalutil.CommonNetworkCreate(n, newNetwork) - if err != nil { - return nil, err + if newNetwork.Name == "" { + name, err := internalutil.GetFreeDeviceName(n) + if err != nil { + return nil, err + } + newNetwork.Name = name } - err = validateIPAMDriver(newNetwork) + usedSubnets, err := internalutil.GetUsedSubnets(n) 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. - // This could happen if you run a container on this net, then the network 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 && newNetwork.Driver == types.BridgeNetworkDriver { - usedNetworks, err = internalutil.GetUsedSubnets(n) - if err != nil { - return nil, err - } + usedNames := make(map[string]string, len(n.networks)) + for name, network := range n.networks { + usedNames[name] = network.ID } - switch newNetwork.Driver { - case types.BridgeNetworkDriver: - internalutil.MapDockerBridgeDriverOptions(newNetwork) - - checkBridgeConflict := true - // validate the given options, - for key, value := range newNetwork.Options { - switch key { - case types.MTUOption: - _, err = internalutil.ParseMTU(value) - if err != nil { - return nil, err - } - - case types.VLANOption: - _, err = internalutil.ParseVlan(value) - if err != nil { - return nil, err - } - // Unset used networks here to ensure that when using vlan networks - // we do not error if the subnet is already in use on the host. - // https://github.com/containers/podman/issues/25736 - usedNetworks = nil - // If there is no vlan there should be no other config with the same bridge. - // However with vlan we want to allow that so that you can have different - // configs on the same bridge but different vlans - // https://github.com/containers/common/issues/2095 - checkBridgeConflict = false - - case types.IsolateOption: - val, err := internalutil.ParseIsolate(value) - if err != nil { - return nil, err - } - newNetwork.Options[types.IsolateOption] = val - case types.MetricOption: - _, err := strconv.ParseUint(value, 10, 32) - if err != nil { - return nil, err - } - case types.NoDefaultRoute: - val, err := strconv.ParseBool(value) - if err != nil { - return nil, err - } - // rust only support "true" or "false" while go can parse 1 and 0 as well so we need to change it - newNetwork.Options[types.NoDefaultRoute] = strconv.FormatBool(val) - case types.VRFOption: - if len(value) == 0 { - return nil, errors.New("invalid vrf name") - } - case types.ModeOption: - switch value { - case types.BridgeModeManaged: - case types.BridgeModeUnmanaged: - // Unset used networks here to ensure that when using unmanaged networks - // we do not error if the subnet is already in use on the host. - // https://github.com/containers/common/issues/2322 - usedNetworks = nil - // Also make sure we don't error if the bridge name is already used as well. - checkBridgeConflict = false - default: - return nil, fmt.Errorf("unknown bridge mode %q", value) - } - default: - return nil, fmt.Errorf("unsupported bridge network option %s", key) - } - } + type Used struct { + Interfaces []string `json:"interfaces"` + Names map[string]string `json:"names"` + Subnets []types.IPNet `json:"subnets"` + } - err = internalutil.CreateBridge(n, newNetwork, usedNetworks, n.defaultsubnetPools, checkBridgeConflict) - if err != nil { - return nil, err - } + type ConfigOpts struct { + SubnetPools []config.SubnetPool `json:"subnet_pools"` + DefaultInterfaceName string `json:"default_interface_name"` + CheckUsedSubnets bool `json:"check_used_subnets"` + } - case types.MacVLANNetworkDriver, types.IPVLANNetworkDriver: - err = createIpvlanOrMacvlan(newNetwork) - if err != nil { - return nil, err - } - default: - net, err := n.createPlugin(newNetwork) - if err != nil { - return nil, err - } - newNetwork = net + type CreateConfigOptions struct { + Network types.Network `json:"network"` + Used Used `json:"used"` + Options ConfigOpts `json:"options"` } - // when we do not have ipam we must disable dns - internalutil.IpamNoneDisableDNS(newNetwork) + subnets := make([]types.IPNet, len(usedSubnets)) + for i, subnet := range usedSubnets { + subnets[i] = types.IPNet{IPNet: *subnet} + } - // process NetworkDNSServers - if len(newNetwork.NetworkDNSServers) > 0 && !newNetwork.DNSEnabled { - return nil, fmt.Errorf("cannot set NetworkDNSServers if DNS is not enabled for the network: %w", types.ErrInvalidArg) + opts := CreateConfigOptions{ + Network: *newNetwork, + Used: Used{ + Interfaces: internalutil.GetBridgeInterfaceNames(n), + Names: usedNames, + Subnets: subnets, + }, + Options: ConfigOpts{ + SubnetPools: n.defaultsubnetPools, + DefaultInterfaceName: n.DefaultInterfaceName(), + CheckUsedSubnets: !defaultNet, + }, } - // validate ip address - for _, dnsServer := range newNetwork.NetworkDNSServers { - if net.ParseIP(dnsServer) == nil { - return nil, fmt.Errorf("unable to parse ip %s specified in NetworkDNSServers: %w", dnsServer, types.ErrInvalidArg) - } + var needsPlugin bool + if !slices.Contains(builtinDrivers, newNetwork.Driver) { + needsPlugin = true } - - // add gateway when not internal or dns enabled - addGateway := !newNetwork.Internal || newNetwork.DNSEnabled - err = internalutil.ValidateSubnets(newNetwork, addGateway, usedNetworks) + var result *types.Network + err = n.execNetavark([]string{"create"}, needsPlugin, &opts, &result) if err != nil { + if strings.Contains(err.Error(), "network already exists") { + return nil, fmt.Errorf("%w %.0w", err, types.ErrNetworkExists) + } return nil, err } - // validate routes - err = internalutil.ValidateRoutes(newNetwork.Routes) - if err != nil { + // normalize network fields + if err := parseNetwork(result); err != nil { return nil, err } - newNetwork.Created = time.Now() - if !defaultNet { - err = n.commitNetwork(newNetwork) + err := n.commitNetwork(result) if err != nil { return nil, err } } - return newNetwork, nil -} - -// ipvlan shares the same mac address so supporting DHCP is not really possible. -var errIpvlanNoDHCP = errors.New("ipam driver dhcp is not supported with ipvlan") - -func createIpvlanOrMacvlan(network *types.Network) error { - if network.NetworkInterface != "" { - interfaceNames, err := internalutil.GetLiveNetworkNames() - if err != nil { - return err - } - if !slices.Contains(interfaceNames, network.NetworkInterface) { - return fmt.Errorf("parent interface %s does not exist", network.NetworkInterface) - } - } - - driver := network.Driver - isMacVlan := driver != types.IPVLANNetworkDriver - - // always turn dns off with macvlan, it is not implemented in netavark - // and makes little sense to support with macvlan - // see https://github.com/containers/netavark/pull/467 - network.DNSEnabled = false - - // 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 no subnets and no driver choose dhcp - network.IPAMOptions[types.Driver] = types.DHCPIPAMDriver - if !isMacVlan { - return errIpvlanNoDHCP - } - } else { - network.IPAMOptions[types.Driver] = types.HostLocalIPAMDriver - } - case types.HostLocalIPAMDriver: - if len(network.Subnets) == 0 { - return fmt.Errorf("%s driver needs at least one subnet specified when the host-local ipam driver is set", driver) - } - case types.DHCPIPAMDriver: - if !isMacVlan { - return errIpvlanNoDHCP - } - if len(network.Subnets) > 0 { - return errors.New("ipam driver dhcp set but subnets are set") - } - } - - // validate the given options, we do not need them but just check to make sure they are valid - for key, value := range network.Options { - switch key { - case types.ModeOption: - if isMacVlan { - if !slices.Contains(types.ValidMacVLANModes, value) { - return fmt.Errorf("unknown macvlan mode %q", value) - } - } else { - if !slices.Contains(types.ValidIPVLANModes, value) { - return fmt.Errorf("unknown ipvlan mode %q", value) - } - } - case types.MetricOption: - _, err := strconv.ParseUint(value, 10, 32) - if err != nil { - return err - } - case types.MTUOption: - _, err := internalutil.ParseMTU(value) - if err != nil { - return err - } - case types.NoDefaultRoute: - val, err := strconv.ParseBool(value) - if err != nil { - return err - } - // rust only support "true" or "false" while go can parse 1 and 0 as well so we need to change it - network.Options[types.NoDefaultRoute] = strconv.FormatBool(val) - case types.BclimOption: - if isMacVlan { - _, err := strconv.ParseInt(value, 10, 32) - if err != nil { - return fmt.Errorf("failed to parse %q option: %w", key, err) - } - // do not fallthrough for macvlan - break - } - // bclim is only valid for macvlan not ipvlan so fallthrough to error case - fallthrough - default: - return fmt.Errorf("unsupported %s network option %s", driver, key) - } - } - return nil + return result, nil } // NetworkRemove will remove the Network with the given name or ID. @@ -461,45 +297,6 @@ 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, types.DHCPIPAMDriver: - case types.NoneIPAMDriver: - if len(n.Subnets) > 0 { - return errors.New("none ipam driver is set but subnets are given") - } - default: - return fmt.Errorf("unsupported ipam driver %q", ipamDriver) - } - return nil -} - -var errInvalidPluginResult = errors.New("invalid plugin result") - -func (n *netavarkNetwork) createPlugin(net *types.Network) (*types.Network, error) { - path, err := getPlugin(net.Driver, n.pluginDirs) - if err != nil { - return nil, err - } - result := new(types.Network) - err = n.execPlugin(path, []string{"create"}, net, result) - if err != nil { - return nil, fmt.Errorf("plugin %s failed: %w", path, err) - } - // now make sure that neither the name, ID, driver were changed by the plugin - if net.Name != result.Name { - return nil, fmt.Errorf("%w: changed network name", errInvalidPluginResult) - } - if net.ID != result.ID { - return nil, fmt.Errorf("%w: changed network ID", errInvalidPluginResult) - } - if net.Driver != result.Driver { - return nil, fmt.Errorf("%w: changed network driver", errInvalidPluginResult) - } - return result, nil -} - func getAllPlugins(dirs []string) []string { var plugins []string for _, dir := range dirs { @@ -515,14 +312,3 @@ func getAllPlugins(dirs []string) []string { } return plugins } - -func getPlugin(name string, dirs []string) (string, error) { - for _, dir := range dirs { - fullpath := filepath.Join(dir, name) - st, err := os.Stat(fullpath) - if err == nil && st.Mode().IsRegular() { - return fullpath, nil - } - } - return "", fmt.Errorf("failed to find driver or plugin %q", name) -} diff --git a/common/libnetwork/netavark/config_test.go b/common/libnetwork/netavark/config_test.go index cfb143836e..45e301ccda 100644 --- a/common/libnetwork/netavark/config_test.go +++ b/common/libnetwork/netavark/config_test.go @@ -14,10 +14,8 @@ import ( . "github.com/onsi/gomega" gomegaTypes "github.com/onsi/gomega/types" "github.com/sirupsen/logrus" - "go.podman.io/common/libnetwork/netavark" "go.podman.io/common/libnetwork/types" "go.podman.io/common/libnetwork/util" - "go.podman.io/common/pkg/config" ) var _ = Describe("Config", func() { @@ -146,6 +144,7 @@ var _ = Describe("Config", func() { network = types.Network{Name: network1.Name} _, err = libpodNet.NetworkCreate(network, nil) + Expect(err).To(HaveOccurred()) Expect(err).To(MatchError(types.ErrNetworkExists)) }) @@ -560,7 +559,6 @@ var _ = Describe("Config", func() { } _, err := libpodNet.NetworkCreate(network, nil) Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("subnet ip is nil")) }) DescribeTable("Subnet validation", @@ -583,13 +581,13 @@ var _ = Describe("Config", func() { // Config file defines overlapping subnets: // - 10.1.2.0/23 covers 10.1.2.0-10.1.3.255 // - 10.1.3.248/30 covers 10.1.3.248-10.1.3.251 (subset of the /23) - Entry("overlapping ipv4 subnets", []string{"10.1.2.0/23", "10.1.3.248/30"}, "overlapping subnets detected"), + Entry("overlapping ipv4 subnets", []string{"10.1.2.0/23", "10.1.3.248/30"}, "overlapping subnets"), // Config file defines overlapping subnets: // - fd00:1:2::/48 covers fd00:1:2:0:0:0:0:0 - fd00:1:2:ffff:ffff:ffff:ffff:ffff // - fd00:1:2:3:f8::/124 covers fd00:1:2:3:f8:0:0:0 - fd00:1:2:3:f8:0:0:f (subset of the /48) - Entry("overlapping ipv6 subnets", []string{"fd00:1:2::/48", "fd00:1:2:3:f8::/124"}, "overlapping subnets detected"), - Entry("duplicate ipv4 subnets", []string{"10.89.0.0/16", "10.89.0.0/16"}, "duplicate subnets detected"), - Entry("duplicate ipv6 subnets", []string{"fd11::/64", "fd11::/64"}, "duplicate subnets detected"), + Entry("overlapping ipv6 subnets", []string{"fd00:1:2::/48", "fd00:1:2:3:f8::/124"}, "overlapping subnets"), + Entry("duplicate ipv4 subnets", []string{"10.89.0.0/16", "10.89.0.0/16"}, "duplicate subnet"), + Entry("duplicate ipv6 subnets", []string{"fd11::/64", "fd11::/64"}, "duplicate subnet"), Entry("two non-overlapping ipv4 subnets", []string{"10.12.0.0/16", "10.13.0.0/16"}, ""), Entry("two non-overlapping ipv6 subnets", []string{"fd12::/64", "fd13::/64"}, ""), ) @@ -670,7 +668,7 @@ var _ = Describe("Config", func() { }) It("create network with more than max interface name size", func() { - name := strings.Repeat("a", types.MaxInterfaceNameLength+1) + name := strings.Repeat("a", types.MaxInterfaceNameLength+2) network := types.Network{ NetworkInterface: name, } @@ -737,12 +735,7 @@ var _ = Describe("Config", func() { }) It("update NetworkDNSServers AddDNSServers", func() { - libpodNet, err := netavark.NewNetworkInterface(&netavark.InitConfig{ - Config: &config.Config{}, - NetworkConfigDir: networkConfDir, - NetworkRunDir: networkConfDir, - NetavarkBinary: "true", - }) + libpodNet, err := getNetworkInterface(networkConfDir) if err != nil { Fail("Failed to create NewNetavarkNetworkInterface") } @@ -764,12 +757,7 @@ var _ = Describe("Config", func() { }) It("update NetworkDNSServers RemoveDNSServers", func() { - libpodNet, err := netavark.NewNetworkInterface(&netavark.InitConfig{ - Config: &config.Config{}, - NetworkConfigDir: networkConfDir, - NetworkRunDir: networkConfDir, - NetavarkBinary: "true", - }) + libpodNet, err := getNetworkInterface(networkConfDir) if err != nil { Fail("Failed to create NewNetavarkNetworkInterface") } @@ -791,12 +779,7 @@ var _ = Describe("Config", func() { }) It("update NetworkDNSServers Add and Remove DNSServers", func() { - libpodNet, err := netavark.NewNetworkInterface(&netavark.InitConfig{ - Config: &config.Config{}, - NetworkConfigDir: networkConfDir, - NetworkRunDir: networkConfDir, - NetavarkBinary: "true", - }) + libpodNet, err := getNetworkInterface(networkConfDir) if err != nil { Fail("Failed to create NewNetavarkNetworkInterface") } @@ -832,7 +815,7 @@ var _ = Describe("Config", func() { } _, err := libpodNet.NetworkCreate(network, nil) Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring(`unable to parse ip a.b.c.d`)) + Expect(err.Error()).To(ContainSubstring("invalid IP address syntax")) }) It("create network with NetworDNSServers with DNSEnabled=false", func() { @@ -882,7 +865,7 @@ var _ = Describe("Config", func() { } _, err := libpodNet.NetworkCreate(network, nil) Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring(`parsing "abc": invalid syntax`)) + Expect(err.Error()).To(ContainSubstring("invalid digit found in string")) network = types.Network{ Options: map[string]string{ @@ -891,7 +874,7 @@ var _ = Describe("Config", func() { } _, err = libpodNet.NetworkCreate(network, nil) Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring(`mtu -1 is less than zero`)) + Expect(err.Error()).To(ContainSubstring(`invalid digit found in string`)) }) It("create network with com.docker.network.driver.mtu option", func() { @@ -919,7 +902,7 @@ var _ = Describe("Config", func() { } _, err := libpodNet.NetworkCreate(network, nil) Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring(`parsing "abc": invalid syntax`)) + Expect(err.Error()).To(ContainSubstring(`invalid digit found in string`)) network = types.Network{ Options: map[string]string{ @@ -928,7 +911,7 @@ var _ = Describe("Config", func() { } _, err = libpodNet.NetworkCreate(network, nil) Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring(`mtu -1 is less than zero`)) + Expect(err.Error()).To(ContainSubstring(`invalid digit found in string`)) }) It("create network with vlan option", func() { @@ -955,7 +938,7 @@ var _ = Describe("Config", func() { } _, err := libpodNet.NetworkCreate(network, nil) Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring(`parsing "abc": invalid syntax`)) + Expect(err.Error()).To(ContainSubstring(`invalid digit found in string`)) network = types.Network{ Options: map[string]string{ @@ -964,7 +947,7 @@ var _ = Describe("Config", func() { } _, err = libpodNet.NetworkCreate(network, nil) Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring(`vlan ID -1 must be between 0 and 4094`)) + Expect(err.Error()).To(ContainSubstring(`invalid digit found in string`)) }) It("create two networks with vlan option and same bridge", func() { @@ -1032,7 +1015,7 @@ var _ = Describe("Config", func() { }} _, err := libpodNet.NetworkCreate(network, nil) Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("unsupported bridge network option someopt")) + Expect(err.Error()).To(ContainSubstring("unsupported bridge network option")) }) It("network create unsupported driver", func() { @@ -1041,7 +1024,7 @@ var _ = Describe("Config", func() { } _, err := libpodNet.NetworkCreate(network, nil) Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring(`failed to find driver or plugin "someDriver"`)) + Expect(err.Error()).To(ContainSubstring(`plugin directory not provided`)) }) It("network create internal and dns", func() { @@ -1076,7 +1059,7 @@ var _ = Describe("Config", func() { network = types.Network{Name: "net"} _, err = libpodNet.NetworkCreate(network, nil) Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("network name net already used")) + Expect(err.Error()).To(ContainSubstring("network already exists")) }) It("remove default network config should fail", func() { @@ -1315,7 +1298,7 @@ var _ = Describe("Config", func() { } _, err = libpodNet.NetworkCreate(network, nil) Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("failed to parse \"bclim\" option: strconv.ParseInt: parsing \"abc\": invalid syntax")) + Expect(err.Error()).To(ContainSubstring("invalid digit found in string")) }) It("create ipvlan config with bclim should fail", func() { @@ -1332,7 +1315,7 @@ var _ = Describe("Config", func() { } _, err := libpodNet.NetworkCreate(network, nil) Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("unsupported ipvlan network option bclim")) + Expect(err.Error()).To(ContainSubstring("unsupported ipvlan network option bclim")) }) It("create macvlan config with mode", func() { @@ -1367,7 +1350,7 @@ var _ = Describe("Config", func() { } _, err := libpodNet.NetworkCreate(network, nil) Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("unknown macvlan mode \"abc\"")) + Expect(err.Error()).To(ContainSubstring("invalid macvlan mode \"abc\"")) }) It("create macvlan config with invalid option", func() { @@ -1384,7 +1367,7 @@ var _ = Describe("Config", func() { } _, err := libpodNet.NetworkCreate(network, nil) Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("unsupported macvlan network option abc")) + Expect(err.Error()).To(ContainSubstring("unsupported vlan network option: abc")) }) It("create macvlan config with mtu", func() { @@ -1442,7 +1425,7 @@ var _ = Describe("Config", func() { } _, err := libpodNet.NetworkCreate(network, nil) Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("none ipam driver is set but subnets are given")) + Expect(err.Error()).To(ContainSubstring("None ipam driver is set but subnets are given")) }) It("create macvlan config with none ipam driver", func() { @@ -1474,7 +1457,7 @@ var _ = Describe("Config", func() { network := types.Network{Driver: "ipvlan"} _, err := libpodNet.NetworkCreate(network, nil) Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("ipam driver dhcp is not supported with ipvlan")) + Expect(err.Error()).To(ContainSubstring("ipam driver dhcp is not supported with ipvlan")) }) It("create ipvlan config without subnet and host-local", func() { @@ -1522,7 +1505,7 @@ var _ = Describe("Config", func() { } _, err := libpodNet.NetworkCreate(network, nil) Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("ipam driver dhcp is not supported with ipvlan")) + Expect(err.Error()).To(ContainSubstring("ipam driver dhcp is not supported with ipvlan")) }) It("create ipvlan config with mode", func() { @@ -1557,25 +1540,23 @@ var _ = Describe("Config", func() { } _, err := libpodNet.NetworkCreate(network, nil) Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("unknown ipvlan mode \"abc\"")) + Expect(err.Error()).To(ContainSubstring("invalid ipvlan mode \"abc\"")) }) It("create network with isolate option 'true'", func() { - for _, val := range []string{"true", "1"} { - network := types.Network{ - Options: map[string]string{ - types.IsolateOption: val, - }, - } - network1, err := libpodNet.NetworkCreate(network, nil) - Expect(err).ToNot(HaveOccurred()) - Expect(network1.Driver).To(Equal("bridge")) - Expect(network1.Options).ToNot(BeNil()) - path := filepath.Join(networkConfDir, network1.Name+".json") - Expect(path).To(BeARegularFile()) - grepInFile(path, `"isolate": "true"`) - Expect(network1.Options).To(HaveKeyWithValue("isolate", "true")) + network := types.Network{ + Options: map[string]string{ + types.IsolateOption: "true", + }, } + network1, err := libpodNet.NetworkCreate(network, nil) + Expect(err).ToNot(HaveOccurred()) + Expect(network1.Driver).To(Equal("bridge")) + Expect(network1.Options).ToNot(BeNil()) + path := filepath.Join(networkConfDir, network1.Name+".json") + Expect(path).To(BeARegularFile()) + grepInFile(path, `"isolate": "true"`) + Expect(network1.Options).To(HaveKeyWithValue("isolate", "true")) }) It("create network with isolate option 'strict'", func() { @@ -1743,7 +1724,7 @@ var _ = Describe("Config", func() { } _, err := libpodNet.NetworkCreate(network, nil) Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("route destination invalid")) + Expect(err.Error()).To(ContainSubstring("route destination invalid")) }) It("create macvlan config with invalid static route (destination is address)", func() { @@ -1759,7 +1740,7 @@ var _ = Describe("Config", func() { } _, err := libpodNet.NetworkCreate(network, nil) Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("route destination invalid")) + Expect(err.Error()).To(ContainSubstring("route destination invalid")) }) It("create ipvlan config with invalid static route (destination is address)", func() { @@ -1780,7 +1761,7 @@ var _ = Describe("Config", func() { } _, err := libpodNet.NetworkCreate(network, nil) Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("route destination invalid")) + Expect(err.Error()).To(ContainSubstring("route destination invalid")) }) It("create bridge config with invalid static route (dest = \"foo\")", func() { @@ -1796,7 +1777,7 @@ var _ = Describe("Config", func() { } _, err := libpodNet.NetworkCreate(network, nil) Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("route destination ip nil")) + Expect(err.Error()).To(ContainSubstring("failed to load network create: IO error: invalid IP address syntax")) }) It("create macvlan config with invalid static route (dest = \"foo\")", func() { @@ -1812,7 +1793,7 @@ var _ = Describe("Config", func() { } _, err := libpodNet.NetworkCreate(network, nil) Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("route destination ip nil")) + Expect(err.Error()).To(ContainSubstring("failed to load network create: IO error: invalid IP address syntax")) }) It("create ipvlan config with invalid static route (dest = \"foo\")", func() { @@ -1833,7 +1814,7 @@ var _ = Describe("Config", func() { } _, err := libpodNet.NetworkCreate(network, nil) Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("route destination ip nil")) + Expect(err.Error()).To(ContainSubstring("failed to load network create: IO error: invalid IP address syntax")) }) It("create bridge config with invalid static route (gw = \"foo\")", func() { @@ -1849,7 +1830,7 @@ var _ = Describe("Config", func() { } _, err := libpodNet.NetworkCreate(network, nil) Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("route gateway nil")) + Expect(err.Error()).To(ContainSubstring("invalid IP address syntax")) }) It("create macvlan config with invalid static route (gw = \"foo\")", func() { @@ -1865,7 +1846,7 @@ var _ = Describe("Config", func() { } _, err := libpodNet.NetworkCreate(network, nil) Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("route gateway nil")) + Expect(err.Error()).To(ContainSubstring("invalid IP address syntax")) }) It("create ipvlan config with invalid static route (gw = \"foo\")", func() { @@ -1886,7 +1867,7 @@ var _ = Describe("Config", func() { } _, err := libpodNet.NetworkCreate(network, nil) Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("route gateway nil")) + Expect(err.Error()).To(ContainSubstring("invalid IP address syntax")) }) It("create macvlan config with metric option", func() { @@ -1906,7 +1887,7 @@ var _ = Describe("Config", func() { network := types.Network{ Driver: "bridge", Options: map[string]string{ - "no_default_route": "1", + "no_default_route": "true", }, } network1, err := libpodNet.NetworkCreate(network, nil) @@ -1919,7 +1900,7 @@ var _ = Describe("Config", func() { network := types.Network{ Driver: "macvlan", Options: map[string]string{ - "no_default_route": "1", + "no_default_route": "true", }, } network1, err := libpodNet.NetworkCreate(network, nil) @@ -1937,7 +1918,7 @@ var _ = Describe("Config", func() { {Subnet: n}, }, Options: map[string]string{ - "no_default_route": "1", + "no_default_route": "true", }, } network1, err := libpodNet.NetworkCreate(network, nil) @@ -1955,7 +1936,7 @@ var _ = Describe("Config", func() { } _, err := libpodNet.NetworkCreate(network, nil) Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("parsing \"foo\": invalid syntax")) + Expect(err.Error()).To(ContainSubstring("unable to parse \"no_default_route\"")) }) It("create macvlan config with invalid no_default_route", func() { @@ -1967,7 +1948,7 @@ var _ = Describe("Config", func() { } _, err := libpodNet.NetworkCreate(network, nil) Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("parsing \"foo\": invalid syntax")) + Expect(err.Error()).To(ContainSubstring("unable to parse \"no_default_route\"")) }) It("create ipvlan config with invalid no_default_route", func() { @@ -1984,7 +1965,7 @@ var _ = Describe("Config", func() { } _, err := libpodNet.NetworkCreate(network, nil) Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("parsing \"foo\": invalid syntax")) + Expect(err.Error()).To(ContainSubstring("unable to parse \"no_default_route\"")) }) Context("network load valid existing ones", func() { diff --git a/common/libnetwork/netavark/exec.go b/common/libnetwork/netavark/exec.go index 88b40bbf98..27ae896db6 100644 --- a/common/libnetwork/netavark/exec.go +++ b/common/libnetwork/netavark/exec.go @@ -101,10 +101,6 @@ func (n *netavarkNetwork) execNetavark(args []string, needPlugin bool, stdin, re return n.execBinary(n.netavarkBinary, append(n.getCommonNetavarkOptions(needPlugin), args...), stdin, result, env) } -func (n *netavarkNetwork) execPlugin(path string, args []string, stdin, result any) error { - return n.execBinary(path, args, stdin, result, nil) -} - func (n *netavarkNetwork) execBinary(path string, args []string, stdin, result any, env []string) error { stdinR, stdinW, err := os.Pipe() if err != nil { diff --git a/common/libnetwork/netavark/ipam_test.go b/common/libnetwork/netavark/ipam_test.go index 0a457a9084..e9eb1f3506 100644 --- a/common/libnetwork/netavark/ipam_test.go +++ b/common/libnetwork/netavark/ipam_test.go @@ -6,6 +6,7 @@ import ( "bytes" "fmt" "net" + "os" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -29,10 +30,12 @@ var _ = Describe("IPAM", func() { }) JustBeforeEach(func() { + netavarkBinary := os.Getenv("NETAVARK_BINARY") libpodNet, err := NewNetworkInterface(&InitConfig{ Config: &config.Config{}, NetworkConfigDir: networkConfDir, NetworkRunDir: networkConfDir, + NetavarkBinary: netavarkBinary, }) if err != nil { Fail("Failed to create NewNetworkInterface") diff --git a/common/libnetwork/netavark/network.go b/common/libnetwork/netavark/network.go index 3a35b2af82..e1021309a2 100644 --- a/common/libnetwork/netavark/network.go +++ b/common/libnetwork/netavark/network.go @@ -296,17 +296,27 @@ func parseNetwork(network *types.Network) error { } func (n *netavarkNetwork) createDefaultNetwork() (*types.Network, error) { - net := types.Network{ + network := &types.Network{ Name: n.defaultNetwork, NetworkInterface: defaultBridgeName + "0", // Important do not change this ID - ID: "2f259bab93aaaaa2542ba43ef33eb990d0999ee1b9924b557b7be53c0b7a1bb9", - Driver: types.BridgeNetworkDriver, + ID: "2f259bab93aaaaa2542ba43ef33eb990d0999ee1b9924b557b7be53c0b7a1bb9", + Driver: types.BridgeNetworkDriver, + Created: time.Now(), Subnets: []types.Subnet{ {Subnet: n.defaultSubnet}, }, + IPAMOptions: map[string]string{ + "driver": types.HostLocalIPAMDriver, + }, + } + + // Normalize network fields (initializes nil maps, adds gateway, validates, etc.) + if err := parseNetwork(network); err != nil { + return nil, err } - return n.networkCreate(&net, true) + + return network, nil } // getNetwork will lookup a network by name or ID. It returns an diff --git a/common/libnetwork/netavark/plugin_test.go b/common/libnetwork/netavark/plugin_test.go index d6b1d77263..a58ff662b5 100644 --- a/common/libnetwork/netavark/plugin_test.go +++ b/common/libnetwork/netavark/plugin_test.go @@ -62,7 +62,7 @@ var _ = Describe("Plugins", func() { } _, err := libpodNet.NetworkCreate(network, nil) Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("plugin ../../bin/netavark-testplugin failed: netavark (exit code 1): my custom error")) + Expect(err.Error()).To(Equal("netavark (exit code 1): exit code 1, message: my custom error")) }) It("create plugin change name error", func() { @@ -72,7 +72,7 @@ var _ = Describe("Plugins", func() { } _, err := libpodNet.NetworkCreate(network, nil) Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("invalid plugin result: changed network name")) + Expect(err.Error()).To(Equal("netavark (exit code 1): netavark-testplugin plugin invalid result: changed network name")) }) It("create plugin change id error", func() { @@ -82,7 +82,7 @@ var _ = Describe("Plugins", func() { } _, err := libpodNet.NetworkCreate(network, nil) Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("invalid plugin result: changed network ID")) + Expect(err.Error()).To(Equal("netavark (exit code 1): netavark-testplugin plugin invalid result: changed network ID")) }) It("create plugin change driver error", func() { @@ -92,6 +92,6 @@ var _ = Describe("Plugins", func() { } _, err := libpodNet.NetworkCreate(network, nil) Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("invalid plugin result: changed network driver")) + Expect(err.Error()).To(Equal("netavark (exit code 1): netavark-testplugin plugin invalid result: changed network driver")) }) }) diff --git a/common/pkg/config/config.go b/common/pkg/config/config.go index 2ab2613495..eb21a3895a 100644 --- a/common/pkg/config/config.go +++ b/common/pkg/config/config.go @@ -635,10 +635,10 @@ type NetworkConfig struct { type SubnetPool struct { // Base is a bigger subnet which will be used to allocate a subnet with // the given size. - Base *types.IPNet `toml:"base,omitempty"` + Base *types.IPNet `toml:"base,omitempty" json:"base,omitempty"` // Size is the CIDR for the new subnet. It must be equal or small // than the CIDR from the base subnet. - Size int `toml:"size,omitempty"` + Size int `toml:"size,omitempty" json:"size,omitempty"` } // SecretConfig represents the "secret" TOML config table.