//go:build linux // +build linux package netavark import ( "encoding/json" "net" "os" "path/filepath" "time" internalutil "github.com/containers/common/libnetwork/internal/util" "github.com/containers/common/libnetwork/types" "github.com/containers/common/pkg/util" "github.com/containers/storage/pkg/stringid" "github.com/pkg/errors" ) // NetworkCreate will take a partial filled Network and fill the // missing fields. It creates the Network and returns the full Network. // nolint:gocritic func (n *netavarkNetwork) NetworkCreate(net types.Network) (types.Network, error) { n.lock.Lock() defer n.lock.Unlock() err := n.loadNetworks() if err != nil { return types.Network{}, err } network, err := n.networkCreate(&net, false) if err != nil { return types.Network{}, err } // add the new network to the map n.networks[network.Name] = network return *network, nil } func (n *netavarkNetwork) networkCreate(newNetwork *types.Network, defaultNet bool) (*types.Network, error) { // if no driver is set use the default one if newNetwork.Driver == "" { newNetwork.Driver = types.DefaultNetworkDriver } if !defaultNet { // 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 newNetwork.ID != "" { return nil, errors.Wrap(types.ErrInvalidArg, "ID can not be set for network create") } // generate random network ID var i int for i = 0; i < 1000; i++ { id := stringid.GenerateNonCryptoID() if _, err := n.getNetwork(id); err != nil { newNetwork.ID = id break } } if i == 1000 { return nil, errors.New("failed to create random network ID") } } err := internalutil.CommonNetworkCreate(n, newNetwork) if err != nil { 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. // 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 && newNetwork.Driver == types.BridgeNetworkDriver { usedNetworks, err = internalutil.GetUsedSubnets(n) if err != nil { return nil, err } } switch newNetwork.Driver { case types.BridgeNetworkDriver: err = internalutil.CreateBridge(n, newNetwork, usedNetworks, n.defaultsubnetPools) if err != nil { return nil, err } // validate the given options, we do not need them but just check to make sure they are valid for key, value := range newNetwork.Options { switch key { case "mtu": _, err = internalutil.ParseMTU(value) if err != nil { return nil, err } case "vlan": _, err = internalutil.ParseVlan(value) if err != nil { return nil, err } default: return nil, errors.Errorf("unsupported bridge network option %s", key) } } case types.MacVLANNetworkDriver: err = createMacvlan(newNetwork) if err != nil { return nil, err } default: return nil, errors.Wrapf(types.ErrInvalidArg, "unsupported driver %s", newNetwork.Driver) } // add gateway when not internal or dns enabled addGateway := !newNetwork.Internal || newNetwork.DNSEnabled err = internalutil.ValidateSubnets(newNetwork, addGateway, usedNetworks) if err != nil { return nil, err } newNetwork.Created = time.Now() if !defaultNet { confPath := filepath.Join(n.networkConfigDir, newNetwork.Name+".json") f, err := os.Create(confPath) if err != nil { return nil, err } defer f.Close() enc := json.NewEncoder(f) enc.SetIndent("", " ") err = enc.Encode(newNetwork) if err != nil { return nil, err } } return newNetwork, nil } func createMacvlan(network *types.Network) error { if network.NetworkInterface != "" { interfaceNames, err := internalutil.GetLiveNetworkNames() if err != nil { return err } if !util.StringInSlice(network.NetworkInterface, interfaceNames) { 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 { 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") } } // 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 "mode": if !util.StringInSlice(value, types.ValidMacVLANModes) { return errors.Errorf("unknown macvlan mode %q", value) } case "mtu": _, err := internalutil.ParseMTU(value) if err != nil { return err } default: return errors.Errorf("unsupported macvlan network option %s", key) } } return nil } // NetworkRemove will remove the Network with the given name or ID. // It does not ensure that the network is unused. func (n *netavarkNetwork) NetworkRemove(nameOrID string) error { n.lock.Lock() defer n.lock.Unlock() err := n.loadNetworks() if err != nil { return err } network, err := n.getNetwork(nameOrID) if err != nil { return err } // Removing the default network is not allowed. if network.Name == n.defaultNetwork { return errors.Errorf("default network %s cannot be removed", n.defaultNetwork) } file := filepath.Join(n.networkConfigDir, network.Name+".json") // make sure to not error for ErrNotExist if err := os.Remove(file); err != nil && !errors.Is(err, os.ErrNotExist) { return err } delete(n.networks, network.Name) return nil } // NetworkList will return all known Networks. Optionally you can // supply a list of filter functions. Only if a network matches all // functions it is returned. func (n *netavarkNetwork) NetworkList(filters ...types.FilterFunc) ([]types.Network, error) { n.lock.Lock() defer n.lock.Unlock() err := n.loadNetworks() if err != nil { return nil, err } networks := make([]types.Network, 0, len(n.networks)) outer: for _, net := range n.networks { for _, filter := range filters { // All filters have to match, if one does not match we can skip to the next network. if !filter(*net) { continue outer } } networks = append(networks, *net) } return networks, nil } // NetworkInspect will return the Network with the given name or ID. func (n *netavarkNetwork) NetworkInspect(nameOrID string) (types.Network, error) { n.lock.Lock() defer n.lock.Unlock() err := n.loadNetworks() if err != nil { return types.Network{}, err } network, err := n.getNetwork(nameOrID) if err != nil { return types.Network{}, err } 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 }