automation-tests/common/libnetwork/netavark/network.go

344 lines
10 KiB
Go

//go:build linux || freebsd
// +build linux freebsd
package netavark
import (
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"time"
"github.com/containers/common/libnetwork/internal/util"
"github.com/containers/common/libnetwork/types"
"github.com/containers/common/pkg/config"
"github.com/containers/storage/pkg/lockfile"
"github.com/containers/storage/pkg/unshare"
"github.com/sirupsen/logrus"
)
type netavarkNetwork struct {
// networkConfigDir is directory where the network config files are stored.
networkConfigDir string
// networkRunDir is where temporary files are stored, i.e.the ipam db, aardvark config etc
networkRunDir string
// tells netavark whether this is rootless mode or rootful, "true" or "false"
networkRootless bool
// netavarkBinary is the path to the netavark binary.
netavarkBinary string
// aardvarkBinary is the path to the aardvark binary.
aardvarkBinary string
// defaultNetwork is the name for the default network.
defaultNetwork string
// defaultSubnet is the default subnet for the default network.
defaultSubnet types.IPNet
// defaultsubnetPools contains the subnets which must be used to allocate a free subnet by network create
defaultsubnetPools []config.SubnetPool
// dnsBindPort is set the the port to pass to netavark for aardvark
dnsBindPort uint16
// pluginDirs list of directories were netavark plugins are located
pluginDirs []string
// ipamDBPath is the path to the ip allocation bolt db
ipamDBPath string
// syslog describes whenever the netavark debbug output should be log to the syslog as well.
// This will use logrus to do so, make sure logrus is set up to log to the syslog.
syslog bool
// lock is a internal lock for critical operations
lock *lockfile.LockFile
// modTime is the timestamp when the config dir was modified
modTime time.Time
// networks is a map with loaded networks, the key is the network name
networks map[string]*types.Network
}
type InitConfig struct {
// NetworkConfigDir is directory where the network config files are stored.
NetworkConfigDir string
// NetavarkBinary is the path to the netavark binary.
NetavarkBinary string
// AardvarkBinary is the path to the aardvark binary.
AardvarkBinary string
// NetworkRunDir is where temporary files are stored, i.e.the ipam db, aardvark config
NetworkRunDir string
// DefaultNetwork is the name for the default network.
DefaultNetwork string
// DefaultSubnet is the default subnet for the default network.
DefaultSubnet string
// DefaultsubnetPools contains the subnets which must be used to allocate a free subnet by network create
DefaultsubnetPools []config.SubnetPool
// DNSBindPort is set the the port to pass to netavark for aardvark
DNSBindPort uint16
// PluginDirs list of directories were netavark plugins are located
PluginDirs []string
// Syslog describes whenever the netavark debbug output should be log to the syslog as well.
// This will use logrus to do so, make sure logrus is set up to log to the syslog.
Syslog bool
}
// NewNetworkInterface creates the ContainerNetwork interface for the netavark backend.
// Note: The networks are not loaded from disk until a method is called.
func NewNetworkInterface(conf *InitConfig) (types.ContainerNetwork, error) {
// root needs to use a globally unique lock because there is only one host netns
lockPath := defaultRootLockPath
if unshare.IsRootless() {
lockPath = filepath.Join(conf.NetworkConfigDir, "netavark.lock")
}
lock, err := lockfile.GetLockFile(lockPath)
if err != nil {
return nil, err
}
defaultNetworkName := conf.DefaultNetwork
if defaultNetworkName == "" {
defaultNetworkName = types.DefaultNetworkName
}
defaultSubnet := conf.DefaultSubnet
if defaultSubnet == "" {
defaultSubnet = types.DefaultSubnet
}
defaultNet, err := types.ParseCIDR(defaultSubnet)
if err != nil {
return nil, fmt.Errorf("failed to parse default subnet: %w", err)
}
if err := os.MkdirAll(conf.NetworkConfigDir, 0o755); err != nil {
return nil, err
}
if err := os.MkdirAll(conf.NetworkRunDir, 0o755); err != nil {
return nil, err
}
defaultSubnetPools := conf.DefaultsubnetPools
if defaultSubnetPools == nil {
defaultSubnetPools = config.DefaultSubnetPools
}
n := &netavarkNetwork{
networkConfigDir: conf.NetworkConfigDir,
networkRunDir: conf.NetworkRunDir,
netavarkBinary: conf.NetavarkBinary,
aardvarkBinary: conf.AardvarkBinary,
networkRootless: unshare.IsRootless(),
ipamDBPath: filepath.Join(conf.NetworkRunDir, "ipam.db"),
defaultNetwork: defaultNetworkName,
defaultSubnet: defaultNet,
defaultsubnetPools: defaultSubnetPools,
dnsBindPort: conf.DNSBindPort,
pluginDirs: conf.PluginDirs,
lock: lock,
syslog: conf.Syslog,
}
return n, nil
}
// Drivers will return the list of supported network drivers
// for this interface.
func (n *netavarkNetwork) Drivers() []string {
paths := getAllPlugins(n.pluginDirs)
return append([]string{types.BridgeNetworkDriver, types.MacVLANNetworkDriver}, paths...)
}
// DefaultNetworkName will return the default netavark network name.
func (n *netavarkNetwork) DefaultNetworkName() string {
return n.defaultNetwork
}
func (n *netavarkNetwork) loadNetworks() error {
// check the mod time of the config dir
f, err := os.Stat(n.networkConfigDir)
if err != nil {
return err
}
modTime := f.ModTime()
// skip loading networks if they are already loaded and
// if the config dir was not modified since the last call
if n.networks != nil && modTime.Equal(n.modTime) {
return nil
}
// make sure the remove all networks before we reload them
n.networks = nil
n.modTime = modTime
files, err := os.ReadDir(n.networkConfigDir)
if err != nil && !errors.Is(err, os.ErrNotExist) {
return err
}
networks := make(map[string]*types.Network, len(files))
for _, f := range files {
if f.IsDir() {
continue
}
if filepath.Ext(f.Name()) != ".json" {
continue
}
path := filepath.Join(n.networkConfigDir, f.Name())
file, err := os.Open(path)
if err != nil {
// do not log ENOENT errors
if !errors.Is(err, os.ErrNotExist) {
logrus.Warnf("Error loading network config file %q: %v", path, err)
}
continue
}
network := new(types.Network)
err = json.NewDecoder(file).Decode(network)
if err != nil {
logrus.Warnf("Error reading network config file %q: %v", path, err)
continue
}
// check that the filename matches the network name
if network.Name+".json" != f.Name() {
logrus.Warnf("Network config name %q does not match file name %q, skipping", network.Name, f.Name())
continue
}
if !types.NameRegex.MatchString(network.Name) {
logrus.Warnf("Network config %q has invalid name: %q, skipping: %v", path, network.Name, types.RegexError)
continue
}
err = parseNetwork(network)
if err != nil {
logrus.Warnf("Network config %q could not be parsed, skipping: %v", path, err)
continue
}
logrus.Debugf("Successfully loaded network %s: %v", network.Name, network)
networks[network.Name] = network
}
// create the default network in memory if it did not exists on disk
if networks[n.defaultNetwork] == nil {
networkInfo, err := n.createDefaultNetwork()
if err != nil {
return fmt.Errorf("failed to create default network %s: %w", n.defaultNetwork, err)
}
networks[n.defaultNetwork] = networkInfo
}
logrus.Debugf("Successfully loaded %d networks", len(networks))
n.networks = networks
return nil
}
func parseNetwork(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{}
}
if len(network.ID) != 64 {
return fmt.Errorf("invalid network ID %q", network.ID)
}
// add gateway when not internal or dns enabled
addGateway := !network.Internal || network.DNSEnabled
return util.ValidateSubnets(network, addGateway, nil)
}
func (n *netavarkNetwork) createDefaultNetwork() (*types.Network, error) {
net := types.Network{
Name: n.defaultNetwork,
NetworkInterface: defaultBridgeName + "0",
// Important do not change this ID
ID: "2f259bab93aaaaa2542ba43ef33eb990d0999ee1b9924b557b7be53c0b7a1bb9",
Driver: types.BridgeNetworkDriver,
Subnets: []types.Subnet{
{Subnet: n.defaultSubnet},
},
}
return n.networkCreate(&net, true)
}
// getNetwork will lookup a network by name or ID. It returns an
// error when no network was found or when more than one network
// with the given (partial) ID exists.
// getNetwork will read from the networks map, therefore the caller
// must ensure that n.lock is locked before using it.
func (n *netavarkNetwork) getNetwork(nameOrID string) (*types.Network, error) {
// fast path check the map key, this will only work for names
if val, ok := n.networks[nameOrID]; ok {
return val, nil
}
// If there was no match we might got a full or partial ID.
var net *types.Network
for _, val := range n.networks {
// This should not happen because we already looked up the map by name but check anyway.
if val.Name == nameOrID {
return val, nil
}
if strings.HasPrefix(val.ID, nameOrID) {
if net != nil {
return nil, fmt.Errorf("more than one result for network ID %s", nameOrID)
}
net = val
}
}
if net != nil {
return net, nil
}
return nil, fmt.Errorf("unable to find network with name or ID %s: %w", nameOrID, types.ErrNoSuchNetwork)
}
// Implement the NetUtil interface for easy code sharing with other network interfaces.
// ForEach call the given function for each network
func (n *netavarkNetwork) ForEach(run func(types.Network)) {
for _, val := range n.networks {
run(*val)
}
}
// Len return the number of networks
func (n *netavarkNetwork) Len() int {
return len(n.networks)
}
// DefaultInterfaceName return the default cni bridge name, must be suffixed with a number.
func (n *netavarkNetwork) DefaultInterfaceName() string {
return defaultBridgeName
}
func (n *netavarkNetwork) Network(nameOrID string) (*types.Network, error) {
network, err := n.getNetwork(nameOrID)
if err != nil {
return nil, err
}
return network, nil
}