//go:build !remote && (linux || freebsd)

package libpod

import (
	"errors"
	"fmt"
	"os"
	"slices"
	"sort"

	"github.com/sirupsen/logrus"
	"go.podman.io/common/libnetwork/etchosts"
	"go.podman.io/common/libnetwork/types"
	"go.podman.io/common/pkg/config"
	"go.podman.io/common/pkg/machine"
	"go.podman.io/podman/v6/libpod/define"
	"go.podman.io/podman/v6/libpod/events"
	"go.podman.io/podman/v6/pkg/namespaces"
	"go.podman.io/podman/v6/pkg/rootless"
	"go.podman.io/storage/pkg/lockfile"
)

// bindPorts ports to keep them open via conmon so no other process can use them and we can check if they are in use.
// Note in all cases it is important that we bind before setting up the network to avoid issues were we add firewall
// rules before we even "own" the port.
func (c *Container) bindPorts() ([]*os.File, error) {
	if !c.runtime.config.Engine.EnablePortReservation || rootless.IsRootless() || !c.config.NetMode.IsBridge() {
		return nil, nil
	}
	return bindPorts(c.convertPortMappings())
}

// convertPortMappings will remove the HostIP part from the ports when running inside podman machine.
// This is needed because a HostIP of 127.0.0.1 would now allow the gvproxy forwarder to reach to open ports.
// For machine the HostIP must only be used by gvproxy and never in the VM.
func (c *Container) convertPortMappings() []types.PortMapping {
	if !machine.IsGvProxyBased() || len(c.config.PortMappings) == 0 {
		return c.config.PortMappings
	}
	// if we run in a machine VM we have to ignore the host IP part
	newPorts := make([]types.PortMapping, 0, len(c.config.PortMappings))
	for _, port := range c.config.PortMappings {
		port.HostIP = ""
		newPorts = append(newPorts, port)
	}
	return newPorts
}

func (c *Container) getNetworkOptions(networkOpts []types.NamedPerNetworkOptions) types.NetworkOptions {
	nameservers := make([]string, 0, len(c.runtime.config.Containers.DNSServers.Get())+len(c.config.DNSServer))
	nameservers = append(nameservers, c.runtime.config.Containers.DNSServers.Get()...)
	for _, ip := range c.config.DNSServer {
		nameservers = append(nameservers, ip.String())
	}
	opts := types.NetworkOptions{
		ContainerID:       c.config.ID,
		ContainerName:     getNetworkPodName(c),
		DNSServers:        nameservers,
		ContainerHostname: c.NetworkHostname(),
		NetworkStatus:     c.getNetworkStatus(),
		NetworkOrder:      networkNamesFromOpts(networkOpts),
	}
	opts.PortMappings = c.convertPortMappings()

	// If the container requested special network options use this instead of the config.
	// This is the case for container restore or network reload.
	if c.perNetworkOpts != nil {
		opts.Networks = c.perNetworkOpts
	} else {
		opts.Networks = networkOpts
	}

	// Alias should only include user-provided aliases. Append the auto-generated
	// aliases (short ID, hostname) so the network backend creates DNS records
	// for them. Concat allocates a new slice, avoiding mutation of the stored config.
	for i := range opts.Networks {
		opts.Networks[i].Aliases = slices.Concat(opts.Networks[i].Aliases, getExtraNetworkAliases(c))
	}

	return opts
}

// networkNamesFromOpts extracts an ordered list of network names from
// the given NamedPerNetworkOptions slice.
func networkNamesFromOpts(nets []types.NamedPerNetworkOptions) []string {
	names := make([]string, 0, len(nets))
	for _, n := range nets {
		names = append(names, n.Name)
	}
	return names
}

// setUpNetwork will set up the networks, on error it will also tear down the
// networks. If rootless it will join/create the rootless network namespace.
func (r *Runtime) setUpNetwork(ns string, opts types.NetworkOptions) (map[string]types.StatusBlock, error) {
	return r.network.Setup(ns, types.SetupOptions{NetworkOptions: opts})
}

// getNetworkPodName return the pod name (hostname) used by dns backend.
// If we are in the pod network namespace use the pod name otherwise the container name
func getNetworkPodName(c *Container) string {
	if c.config.NetMode.IsPod() || c.IsInfra() {
		pod, err := c.runtime.state.Pod(c.PodID())
		if err == nil {
			return pod.Name()
		}
	}
	return c.Name()
}

// Tear down a container's network configuration and joins the
// rootless net ns as rootless user
func (r *Runtime) teardownNetworkBackend(ns string, opts types.NetworkOptions) error {
	return r.network.Teardown(ns, types.TeardownOptions{NetworkOptions: opts})
}

// Tear down a container's network backend configuration, but do not tear down the
// namespace itself.
func (r *Runtime) teardownNetwork(ctr *Container) error {
	if ctr.state.NetNS == "" {
		// The container has no network namespace, we're set
		return nil
	}

	logrus.Debugf("Tearing down network namespace at %s for container %s", ctr.state.NetNS, ctr.ID())

	networks, err := ctr.networks()
	if err != nil {
		return err
	}

	if len(networks) == 0 {
		return nil
	}

	// --net=pasta: per-container pasta cleans up when it exits, nothing to tear down.
	if ctr.config.NetMode.IsPasta() {
		return nil
	}

	// Note: pasta/pesto port teardown is handled inside container-libs
	// netavark Teardown(), so no explicit pesto cleanup is needed here.

	netOpts := ctr.getNetworkOptions(networks)
	return r.teardownNetworkBackend(ctr.state.NetNS, netOpts)
}

// isBridgeNetMode checks if the given network mode is bridge.
// It returns nil when it is set to bridge and an error otherwise.
func isBridgeNetMode(n namespaces.NetworkMode) error {
	if !n.IsBridge() {
		return fmt.Errorf("%q is not supported: %w", n, define.ErrNetworkModeInvalid)
	}
	return nil
}

// Reload only works with containers with a configured network.
// It will tear down, and then reconfigure, the network of the container.
// This is mainly used when a reload of firewall rules wipes out existing
// firewall configuration.
// Efforts will be made to preserve MAC and IP addresses.
// Only works on containers with bridge networking.
func (r *Runtime) reloadContainerNetwork(ctr *Container) (map[string]types.StatusBlock, error) {
	if ctr.state.NetNS == "" {
		return nil, fmt.Errorf("container %s network is not configured, refusing to reload: %w", ctr.ID(), define.ErrCtrStateInvalid)
	}
	if err := isBridgeNetMode(ctr.config.NetMode); err != nil {
		return nil, err
	}
	logrus.Infof("Going to reload container %s network", ctr.ID())

	// store the old status before unsetting it
	netStatus := ctr.getNetworkStatus()
	err := r.teardownNetwork(ctr)
	if err != nil {
		logrus.Error(err)
	}
	// We must unset the network status here so
	ctr.state.NetworkStatus = nil

	// always save even when there was an error
	err = ctr.save()
	if err != nil {
		return nil, fmt.Errorf("failed to save container status after network teardown: %w", err)
	}

	networkOpts, err := ctr.networks()
	if err != nil {
		return nil, err
	}

	// Set the same network settings as before..
	newNetworkOpts := make([]types.NamedPerNetworkOptions, 0, len(networkOpts))
	for _, network := range networkOpts {
		for name, netInt := range netStatus[network.Name].Interfaces {
			network.InterfaceName = name
			network.StaticMAC = netInt.MacAddress
			for _, netAddress := range netInt.Subnets {
				network.StaticIPs = append(network.StaticIPs, netAddress.IPNet.IP)
			}
			// Normally interfaces have a length of 1, only for some special network configs we could get more.
			// For now just use the first interface to get the ips this should be good enough for most cases.
			break
		}
		newNetworkOpts = append(newNetworkOpts, network)
	}
	ctr.perNetworkOpts = newNetworkOpts

	return r.configureNetNS(ctr, ctr.state.NetNS, true)
}

// Produce an InspectNetworkSettings containing information on the container
// network.
func (c *Container) getContainerNetworkInfo() (*define.InspectNetworkSettings, error) {
	if c.config.NetNsCtr != "" {
		netNsCtr, err := c.runtime.GetContainer(c.config.NetNsCtr)
		if err != nil {
			return nil, err
		}
		// see https://github.com/containers/podman/issues/10090
		// the container has to be locked for syncContainer()
		netNsCtr.lock.Lock()
		defer netNsCtr.lock.Unlock()
		// Have to sync to ensure that state is populated
		if err := netNsCtr.syncContainer(); err != nil {
			return nil, err
		}
		logrus.Debugf("Container %s shares network namespace, retrieving network info of container %s", c.ID(), c.config.NetNsCtr)

		return netNsCtr.getContainerNetworkInfo()
	}

	settings := new(define.InspectNetworkSettings)
	settings.Ports = makeInspectPortBindings(c.config.PortMappings)

	networks, err := c.networks()
	if err != nil {
		return nil, err
	}

	getNetworkID := func(nameOrID string) string {
		network, err := c.runtime.network.NetworkInspect(nameOrID)
		if err == nil && network.ID != "" {
			return network.ID
		}
		return nameOrID
	}

	setDefaultNetworks := func() {
		settings.Networks = make(map[string]*define.InspectAdditionalNetwork, 1)
		name := c.NetworkMode()
		addedNet := new(define.InspectAdditionalNetwork)
		addedNet.NetworkID = getNetworkID(name)
		settings.Networks[name] = addedNet
	}

	if c.state.NetNS == "" {
		if networkNSPath, set := c.joinedNetworkNSPath(); networkNSPath != "" {
			if result, err := c.inspectJoinedNetworkNS(networkNSPath); err == nil {
				// fallback to dummy configuration
				settings.InspectBasicNetworkConfig = resultToBasicNetworkConfig(result)
			} else {
				// do not propagate error inspecting a joined network ns
				logrus.Errorf("Inspecting network namespace: %s of container %s: %v", networkNSPath, c.ID(), err)
			}
			return settings, nil
		} else if set {
			// network none case, if running allow user to join netns via sandbox key
			// https://github.com/containers/podman/issues/16716
			if c.state.PID > 0 {
				settings.SandboxKey = fmt.Sprintf("/proc/%d/ns/net", c.state.PID)
			}
		}
		// We can't do more if the network is down.
		// We still want to make dummy configurations for each network
		// the container joined.
		if len(networks) > 0 {
			settings.Networks = make(map[string]*define.InspectAdditionalNetwork, len(networks))
			for _, net := range networks {
				netInfo := new(define.InspectAdditionalNetwork)
				netInfo.NetworkID = getNetworkID(net.Name)
				netInfo.Aliases = net.Aliases
				netInfo.DNSNames = c.dnsNamesForNetwork(net.Aliases)
				settings.Networks[net.Name] = netInfo
			}
		} else {
			setDefaultNetworks()
		}

		return settings, nil
	}

	// Set network namespace path
	settings.SandboxKey = c.state.NetNS

	netStatus := c.getNetworkStatus()
	// If this is empty, we're probably using pasta
	if len(netStatus) == 0 {
		return settings, nil
	}

	// If we have networks - handle that here
	if len(networks) > 0 {
		if len(networks) != len(netStatus) {
			return nil, fmt.Errorf("network inspection mismatch: asked to join %d network(s) %v, but have information on %d network(s): %w", len(networks), networks, len(netStatus), define.ErrInternal)
		}

		settings.Networks = make(map[string]*define.InspectAdditionalNetwork, len(networks))

		isDefaultNet := false
		for _, network := range networks {
			result := netStatus[network.Name]
			addedNet := new(define.InspectAdditionalNetwork)
			addedNet.NetworkID = getNetworkID(network.Name)
			addedNet.Aliases = network.Aliases
			addedNet.DNSNames = c.dnsNamesForNetwork(network.Aliases)
			addedNet.InspectBasicNetworkConfig = resultToBasicNetworkConfig(result)

			settings.Networks[network.Name] = addedNet

			if network.Name == c.runtime.config.Network.DefaultNetwork {
				isDefaultNet = true
			}
		}

		// if not only the default network is connected we can return here
		// otherwise we have to populate the InspectBasicNetworkConfig settings
		if len(networks) != 1 || !isDefaultNet {
			return settings, nil
		}
	} else {
		setDefaultNetworks()
	}

	// If not joining networks, we should have at most 1 result
	if len(netStatus) > 1 {
		return nil, fmt.Errorf("should have at most 1 network status result if not joining networks, instead got %d: %w", len(netStatus), define.ErrInternal)
	}

	if len(netStatus) == 1 {
		for _, status := range netStatus {
			settings.InspectBasicNetworkConfig = resultToBasicNetworkConfig(status)
		}
	}
	return settings, nil
}

// resultToBasicNetworkConfig produces an InspectBasicNetworkConfig from a
// network result
func resultToBasicNetworkConfig(result types.StatusBlock) define.InspectBasicNetworkConfig {
	config := define.InspectBasicNetworkConfig{}
	interfaceNames := make([]string, 0, len(result.Interfaces))
	for interfaceName := range result.Interfaces {
		interfaceNames = append(interfaceNames, interfaceName)
	}
	// ensure consistent inspect results by sorting
	sort.Strings(interfaceNames)
	for _, interfaceName := range interfaceNames {
		netInt := result.Interfaces[interfaceName]
		for _, netAddress := range netInt.Subnets {
			size, _ := netAddress.IPNet.Mask.Size()
			if netAddress.IPNet.IP.To4() != nil {
				// ipv4
				if config.IPAddress == "" {
					config.IPAddress = netAddress.IPNet.IP.String()
					config.IPPrefixLen = size
					if netAddress.Gateway != nil {
						config.Gateway = netAddress.Gateway.String()
					}
				} else {
					config.SecondaryIPAddresses = append(config.SecondaryIPAddresses, define.Address{Addr: netAddress.IPNet.IP.String(), PrefixLength: size})
				}
			} else {
				// ipv6
				if config.GlobalIPv6Address == "" {
					config.GlobalIPv6Address = netAddress.IPNet.IP.String()
					config.GlobalIPv6PrefixLen = size
					if netAddress.Gateway != nil {
						config.IPv6Gateway = netAddress.Gateway.String()
					}
				} else {
					config.SecondaryIPv6Addresses = append(config.SecondaryIPv6Addresses, define.Address{Addr: netAddress.IPNet.IP.String(), PrefixLength: size})
				}
			}
		}
		if config.MacAddress == "" {
			config.MacAddress = netInt.MacAddress.String()
		} else {
			config.AdditionalMacAddresses = append(config.AdditionalMacAddresses, netInt.MacAddress.String())
		}
	}
	return config
}

// NetworkDisconnect removes a container from the network
func (c *Container) NetworkDisconnect(nameOrID, netName string, _ bool) error {
	// only the bridge mode supports networks
	if err := isBridgeNetMode(c.config.NetMode); err != nil {
		return err
	}

	c.lock.Lock()
	defer c.lock.Unlock()

	networks, err := c.networks()
	if err != nil {
		return err
	}

	// check if network exists and if the input is an ID we get the name
	// The libpod db only uses names so it is important that we only use the name
	netName, _, err = c.runtime.normalizeNetworkName(netName)
	if err != nil {
		return err
	}

	nameExists := false
	var network types.NamedPerNetworkOptions
	for _, net := range networks {
		if net.Name == netName {
			nameExists = true
			network = net
			break
		}
	}
	if !nameExists && len(networks) > 0 {
		return fmt.Errorf("container %s is not connected to network %s", nameOrID, netName)
	}

	if err := c.syncContainer(); err != nil {
		return err
	}
	// get network status before we disconnect
	networkStatus := c.getNetworkStatus()

	if err := c.runtime.state.NetworkDisconnect(c, netName); err != nil {
		return err
	}

	// Since we removed the new network from the container db we must have to add it back during partial setup errors
	addContainerNetworkToDB := func() {
		if err := c.runtime.state.NetworkConnect(c, network); err != nil {
			logrus.Errorf("Failed to add network %s for container %s to DB after failed network disconnect", netName, nameOrID)
		}
	}

	c.newNetworkEvent(events.NetworkDisconnect, netName)
	if !c.ensureState(define.ContainerStateRunning, define.ContainerStateCreated) {
		return nil
	}

	if c.state.NetNS == "" {
		addContainerNetworkToDB()
		return fmt.Errorf("unable to disconnect %s from %s: %w", nameOrID, netName, define.ErrNoNetwork)
	}

	opts := types.NetworkOptions{
		ContainerID:   c.config.ID,
		ContainerName: getNetworkPodName(c),
		NetworkStatus: networkStatus,
		NetworkOrder:  networkNamesFromOpts(networks),
	}
	opts.PortMappings = c.convertPortMappings()

	opts.Networks = []types.NamedPerNetworkOptions{network}

	if err := c.runtime.teardownNetworkBackend(c.state.NetNS, opts); err != nil {
		addContainerNetworkToDB()
		return err
	}

	// update network status if container is running
	oldStatus, statusExist := networkStatus[netName]
	delete(networkStatus, netName)
	c.state.NetworkStatus = networkStatus
	err = c.save()
	if err != nil {
		return err
	}

	// Reload ports when there are still connected networks, maybe we removed the network interface with the child ip.
	// Reloading without connected networks does not make sense, so we can skip this step.
	if rootless.IsRootless() && c.runtime.config.Network.RootlessPortForwarder == config.RootlessPortForwarderRootlessport && len(networkStatus) > 0 {
		if err := c.reloadRootlessRLKPortMapping(); err != nil {
			return err
		}
	}

	// Update resolv.conf if required
	if statusExist {
		stringIPs := make([]string, 0, len(oldStatus.DNSServerIPs))
		for _, ip := range oldStatus.DNSServerIPs {
			stringIPs = append(stringIPs, ip.String())
		}
		if len(stringIPs) > 0 {
			logrus.Debugf("Removing DNS Servers %v from resolv.conf", stringIPs)
			if err := c.removeNameserver(stringIPs); err != nil {
				return err
			}
		}

		// update /etc/hosts file
		if file, ok := c.state.BindMounts[config.DefaultHostsFile]; ok {
			// sync the names with c.getHostsEntries()
			names := []string{c.Hostname(), c.config.Name}
			rm := etchosts.GetNetworkHostEntries(map[string]types.StatusBlock{netName: oldStatus}, names...)
			if len(rm) > 0 {
				// make sure to lock this file to prevent concurrent writes when
				// this is used a net dependency container
				lock, err := lockfile.GetLockFile(file)
				if err != nil {
					return fmt.Errorf("failed to lock hosts file: %w", err)
				}
				logrus.Debugf("Remove /etc/hosts entries %v", rm)
				lock.Lock()
				err = etchosts.Remove(file, rm)
				lock.Unlock()
				if err != nil {
					return err
				}
			}
		}
	}
	return nil
}

// ConnectNetwork connects a container to a given network
func (c *Container) NetworkConnect(nameOrID, netName string, netOpts types.PerNetworkOptions) error {
	// only the bridge mode supports networks
	if err := isBridgeNetMode(c.config.NetMode); err != nil {
		return err
	}

	c.lock.Lock()
	defer c.lock.Unlock()

	networks, err := c.networks()
	if err != nil {
		return err
	}

	// check if network exists and if the input is an ID we get the name
	// The libpod db only uses names so it is important that we only use the name
	var nicName string
	netName, nicName, err = c.runtime.normalizeNetworkName(netName)
	if err != nil {
		return err
	}

	if err := c.syncContainer(); err != nil {
		return err
	}

	// get network status before we connect
	networkStatus := c.getNetworkStatus()

	// check whether interface is to be named as the network_interface
	// when name left unspecified
	if netOpts.InterfaceName == "" {
		netOpts.InterfaceName = nicName
	}

	// set default interface name
	if netOpts.InterfaceName == "" {
		netOpts.InterfaceName = getFreeInterfaceName(networks)
		if netOpts.InterfaceName == "" {
			return errors.New("could not find free network interface name")
		}
	}
	netOpts.Aliases = slices.Compact(slices.Sorted(slices.Values(netOpts.Aliases)))
	namedOpts := types.NamedPerNetworkOptions{
		Name:              netName,
		PerNetworkOptions: netOpts,
	}

	if err := c.runtime.state.NetworkConnect(c, namedOpts); err != nil {
		// Docker compat: treat requests to attach already attached networks as a no-op, ignoring opts
		if errors.Is(err, define.ErrNetworkConnected) && !c.ensureState(define.ContainerStateRunning, define.ContainerStateCreated) {
			return nil
		}

		return err
	}

	// Since we added the new network to the container db we must have to remove it from that during partial setup errors
	removeContainerNetworkFromDB := func() {
		if err := c.runtime.state.NetworkDisconnect(c, netName); err != nil {
			logrus.Errorf("Failed to remove network %s for container %s from DB after failed network connect", netName, nameOrID)
		}
	}

	c.newNetworkEvent(events.NetworkConnect, netName)
	if !c.ensureState(define.ContainerStateRunning, define.ContainerStateCreated) {
		return nil
	}
	if c.state.NetNS == "" {
		removeContainerNetworkFromDB()
		return fmt.Errorf("unable to connect %s to %s: %w", nameOrID, netName, define.ErrNoNetwork)
	}

	opts := types.NetworkOptions{
		ContainerID:   c.config.ID,
		ContainerName: getNetworkPodName(c),
		NetworkStatus: networkStatus,
		NetworkOrder:  append(networkNamesFromOpts(networks), netName),
	}
	opts.PortMappings = c.convertPortMappings()
	setupOpts := namedOpts
	setupOpts.Aliases = slices.Concat(setupOpts.Aliases, getExtraNetworkAliases(c))
	opts.Networks = []types.NamedPerNetworkOptions{setupOpts}

	results, err := c.runtime.setUpNetwork(c.state.NetNS, opts)
	if err != nil {
		removeContainerNetworkFromDB()
		return err
	}
	if len(results) != 1 {
		return errors.New("when adding aliases, results must be of length 1")
	}

	// we need to get the old host entries before we add the new one to the status
	// if we do not add do it here we will get the wrong existing entries which will throw of the logic
	// we could also copy the map but this does not seem worth it
	// sync the hostNames with c.getHostsEntries()
	hostNames := []string{c.Hostname(), c.config.Name}
	oldHostEntries := etchosts.GetNetworkHostEntries(networkStatus, hostNames...)

	// update network status
	if networkStatus == nil {
		networkStatus = make(map[string]types.StatusBlock, 1)
	}
	networkStatus[netName] = results[netName]
	c.state.NetworkStatus = networkStatus

	err = c.save()
	if err != nil {
		return err
	}

	// The first network needs a port reload to set the correct child ip for the rootlessport process.
	// Adding a second network does not require a port reload because the child ip is still valid.
	if rootless.IsRootless() && c.runtime.config.Network.RootlessPortForwarder == config.RootlessPortForwarderRootlessport && len(networks) == 0 {
		if err := c.reloadRootlessRLKPortMapping(); err != nil {
			return err
		}
	}

	ipv6 := c.checkForIPv6(networkStatus)

	// Update resolv.conf if required
	stringIPs := make([]string, 0, len(results[netName].DNSServerIPs))
	for _, ip := range results[netName].DNSServerIPs {
		if (ip.To4() == nil) && !ipv6 {
			continue
		}
		stringIPs = append(stringIPs, ip.String())
	}
	if len(stringIPs) > 0 {
		logrus.Debugf("Adding DNS Servers %v to resolv.conf", stringIPs)
		if err := c.addNameserver(stringIPs); err != nil {
			return err
		}
	}

	// update /etc/hosts file
	if file, ok := c.state.BindMounts[config.DefaultHostsFile]; ok {
		// make sure to lock this file to prevent concurrent writes when
		// this is used a net dependency container
		lock, err := lockfile.GetLockFile(file)
		if err != nil {
			return fmt.Errorf("failed to lock hosts file: %w", err)
		}
		new := etchosts.GetNetworkHostEntries(results, hostNames...)
		logrus.Debugf("Add /etc/hosts entries %v", new)
		// use special AddIfExists API to make sure we only add new entries if an old one exists
		// see the AddIfExists() comment for more information
		lock.Lock()
		err = etchosts.AddIfExists(file, oldHostEntries, new)
		lock.Unlock()
		if err != nil {
			return err
		}
	}

	return nil
}

// get a free interface name for a new network
// return an empty string if no free name was found
func getFreeInterfaceName(networks []types.NamedPerNetworkOptions) string {
	ifNames := make([]string, 0, len(networks))
	for _, opts := range networks {
		ifNames = append(ifNames, opts.InterfaceName)
	}
	for i := range 100000 {
		ifName := fmt.Sprintf("eth%d", i)
		if !slices.Contains(ifNames, ifName) {
			return ifName
		}
	}
	return ""
}

func getExtraNetworkAliases(c *Container) []string {
	// always add the short id as alias for docker compat
	alias := []string{c.config.ID[:12]}
	// if an explicit hostname was set add it as well
	if c.config.Spec.Hostname != "" {
		alias = append(alias, c.config.Spec.Hostname)
	}
	return alias
}

func (c *Container) dnsNamesForNetwork(aliases []string) []string {
	all := slices.Concat([]string{c.Name()}, aliases, getExtraNetworkAliases(c))
	names := all[:0]
	for _, n := range all {
		if !slices.Contains(names, n) {
			names = append(names, n)
		}
	}
	return names
}

// DisconnectContainerFromNetwork removes a container from its network
func (r *Runtime) DisconnectContainerFromNetwork(nameOrID, netName string, force bool) error {
	ctr, err := r.LookupContainer(nameOrID)
	if err != nil {
		return err
	}
	return ctr.NetworkDisconnect(nameOrID, netName, force)
}

// ConnectContainerToNetwork connects a container to a network
func (r *Runtime) ConnectContainerToNetwork(nameOrID, netName string, netOpts types.PerNetworkOptions) error {
	ctr, err := r.LookupContainer(nameOrID)
	if err != nil {
		return err
	}
	return ctr.NetworkConnect(nameOrID, netName, netOpts)
}

// normalizeNetworkName takes a network name, a partial or a full network ID and
// returns: 1) the network name and 2) the network_interface name for macvlan
// and ipvlan drivers if the naming pattern is "device" defined in the
// containers.conf file. Else, "".
// If the network is not found an error is returned.
func (r *Runtime) normalizeNetworkName(nameOrID string) (string, string, error) {
	net, err := r.network.NetworkInspect(nameOrID)
	if err != nil {
		return "", "", err
	}

	netIface := ""
	namingPattern := r.config.Containers.InterfaceName
	if namingPattern == "device" && (net.Driver == types.MacVLANNetworkDriver || net.Driver == types.IPVLANNetworkDriver) {
		netIface = net.NetworkInterface
	}

	return net.Name, netIface, nil
}
