//go:build linux || freebsd // +build linux freebsd package network import ( "errors" "fmt" "os" "path/filepath" "github.com/containers/common/libnetwork/cni" "github.com/containers/common/libnetwork/netavark" "github.com/containers/common/libnetwork/types" "github.com/containers/common/pkg/config" "github.com/containers/common/pkg/machine" "github.com/containers/storage" "github.com/containers/storage/pkg/homedir" "github.com/containers/storage/pkg/ioutils" "github.com/containers/storage/pkg/unshare" "github.com/sirupsen/logrus" ) const ( // defaultNetworkBackendFileName is the file name for sentinel file to store the backend defaultNetworkBackendFileName = "defaultNetworkBackend" // cniConfigDirRootless is the directory in XDG_CONFIG_HOME for cni plugins cniConfigDirRootless = "cni/net.d/" // netavarkBinary is the name of the netavark binary netavarkBinary = "netavark" // aardvarkBinary is the name of the aardvark binary aardvarkBinary = "aardvark-dns" ) // NetworkBackend returns the network backend name and interface // It returns either the CNI or netavark backend depending on what is set in the config. // If the backend is set to "" we will automatically assign the backend on the following conditions: // 1. read ${graphroot}/defaultNetworkBackend // 2. find netavark binary (if not installed use CNI) // 3. check containers, images and CNI networks and if there are some we have an existing install and should continue to use CNI // // revive does not like the name because the package is already called network // //nolint:revive func NetworkBackend(store storage.Store, conf *config.Config, syslog bool) (types.NetworkBackend, types.ContainerNetwork, error) { backend := types.NetworkBackend(conf.Network.NetworkBackend) if backend == "" { var err error backend, err = defaultNetworkBackend(store, conf) if err != nil { return "", nil, fmt.Errorf("failed to get default network backend: %w", err) } } switch backend { case types.Netavark: netavarkBin, err := conf.FindHelperBinary(netavarkBinary, false) if err != nil { return "", nil, err } aardvarkBin, _ := conf.FindHelperBinary(aardvarkBinary, false) confDir := conf.Network.NetworkConfigDir if confDir == "" { confDir = getDefaultNetavarkConfigDir(store) } // We cannot use the runroot for rootful since the network namespace is shared for all // libpod instances they also have to share the same ipam db. // For rootless we have our own network namespace per libpod instances, // so this is not a problem there. runDir := netavarkRunDir if unshare.IsRootless() { runDir = filepath.Join(store.RunRoot(), "networks") } netInt, err := netavark.NewNetworkInterface(&netavark.InitConfig{ NetworkConfigDir: confDir, NetworkRunDir: runDir, NetavarkBinary: netavarkBin, AardvarkBinary: aardvarkBin, PluginDirs: conf.Network.NetavarkPluginDirs.Get(), FirewallDriver: conf.Network.FirewallDriver, DefaultNetwork: conf.Network.DefaultNetwork, DefaultSubnet: conf.Network.DefaultSubnet, DefaultsubnetPools: conf.Network.DefaultSubnetPools, DNSBindPort: conf.Network.DNSBindPort, Syslog: syslog, }) return types.Netavark, netInt, err case types.CNI: netInt, err := getCniInterface(conf) return types.CNI, netInt, err default: return "", nil, fmt.Errorf("unsupported network backend %q, check network_backend in containers.conf", backend) } } func defaultNetworkBackend(store storage.Store, conf *config.Config) (backend types.NetworkBackend, err error) { // read defaultNetworkBackend file file := filepath.Join(store.GraphRoot(), defaultNetworkBackendFileName) b, err := os.ReadFile(file) if err == nil { val := string(b) if val == string(types.Netavark) { return types.Netavark, nil } if val == string(types.CNI) { return types.CNI, nil } return "", fmt.Errorf("unknown network backend value %q in %q", val, file) } // fail for all errors except ENOENT if !errors.Is(err, os.ErrNotExist) { return "", fmt.Errorf("could not read network backend value: %w", err) } // cache the network backend to make sure always the same one will be used defer func() { // only write when there is no error if err == nil { if err := ioutils.AtomicWriteFile(file, []byte(backend), 0o644); err != nil { logrus.Errorf("could not write network backend to file: %v", err) } } }() _, err = conf.FindHelperBinary("netavark", false) if err != nil { // if we cannot find netavark use CNI return types.CNI, nil } // If there are any containers then return CNI cons, err := store.Containers() if err != nil { return "", err } if len(cons) != 0 { return types.CNI, nil } // If there are any non ReadOnly images then return CNI imgs, err := store.Images() if err != nil { return "", err } for _, i := range imgs { if !i.ReadOnly { return types.CNI, nil } } // If there are CNI Networks then return CNI cniInterface, err := getCniInterface(conf) if err == nil { nets, err := cniInterface.NetworkList() // there is always a default network so check > 1 if err != nil && !errors.Is(err, os.ErrNotExist) { return "", err } if len(nets) > 1 { // we do not have a fresh system so use CNI return types.CNI, nil } } return types.Netavark, nil } func getCniInterface(conf *config.Config) (types.ContainerNetwork, error) { confDir := conf.Network.NetworkConfigDir if confDir == "" { var err error confDir, err = getDefaultCNIConfigDir() if err != nil { return nil, err } } return cni.NewCNINetworkInterface(&cni.InitConfig{ CNIConfigDir: confDir, CNIPluginDirs: conf.Network.CNIPluginDirs.Get(), RunDir: conf.Engine.TmpDir, DefaultNetwork: conf.Network.DefaultNetwork, DefaultSubnet: conf.Network.DefaultSubnet, DefaultsubnetPools: conf.Network.DefaultSubnetPools, IsMachine: machine.IsGvProxyBased(), }) } func getDefaultCNIConfigDir() (string, error) { if !unshare.IsRootless() { return cniConfigDir, nil } configHome, err := homedir.GetConfigHome() if err != nil { return "", err } return filepath.Join(configHome, cniConfigDirRootless), nil } // getDefaultNetavarkConfigDir return the netavark config dir. For rootful it will // use "/etc/containers/networks" and for rootless "$graphroot/networks". We cannot // use the graphroot for rootful since the network namespace is shared for all // libpod instances. func getDefaultNetavarkConfigDir(store storage.Store) string { if !unshare.IsRootless() { return netavarkConfigDir } return filepath.Join(store.GraphRoot(), "networks") }