mirror of https://github.com/containers/podman.git
netavark network interface
Implement a new network interface for netavark. For now only bridge networking is supported. The interface can create/list/inspect/remove networks. For setup and teardown netavark will be invoked. Signed-off-by: Paul Holzinger <pholzing@redhat.com>
This commit is contained in:
parent
12c62b92ff
commit
eaae294628
|
@ -14,6 +14,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/containernetworking/cni/libcni"
|
"github.com/containernetworking/cni/libcni"
|
||||||
|
internalutil "github.com/containers/podman/v3/libpod/network/internal/util"
|
||||||
"github.com/containers/podman/v3/libpod/network/types"
|
"github.com/containers/podman/v3/libpod/network/types"
|
||||||
"github.com/containers/podman/v3/libpod/network/util"
|
"github.com/containers/podman/v3/libpod/network/util"
|
||||||
pkgutil "github.com/containers/podman/v3/pkg/util"
|
pkgutil "github.com/containers/podman/v3/pkg/util"
|
||||||
|
@ -156,10 +157,7 @@ func convertIPAMConfToNetwork(network *types.Network, ipam ipamConfig, confPath
|
||||||
return errors.Errorf("failed to parse gateway ip %s", ipam.Gateway)
|
return errors.Errorf("failed to parse gateway ip %s", ipam.Gateway)
|
||||||
}
|
}
|
||||||
// convert to 4 byte if ipv4
|
// convert to 4 byte if ipv4
|
||||||
ipv4 := gateway.To4()
|
internalutil.NormalizeIP(&gateway)
|
||||||
if ipv4 != nil {
|
|
||||||
gateway = ipv4
|
|
||||||
}
|
|
||||||
} else if !network.Internal {
|
} else if !network.Internal {
|
||||||
// only add a gateway address if the network is not internal
|
// only add a gateway address if the network is not internal
|
||||||
gateway, err = util.FirstIPInSubnet(sub)
|
gateway, err = util.FirstIPInSubnet(sub)
|
||||||
|
@ -244,13 +242,13 @@ func (n *cniNetwork) createCNIConfigListFromNetwork(network *types.Network, writ
|
||||||
for k, v := range network.Options {
|
for k, v := range network.Options {
|
||||||
switch k {
|
switch k {
|
||||||
case "mtu":
|
case "mtu":
|
||||||
mtu, err = parseMTU(v)
|
mtu, err = internalutil.ParseMTU(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
case "vlan":
|
case "vlan":
|
||||||
vlan, err = parseVlan(v)
|
vlan, err = internalutil.ParseVlan(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
|
@ -339,36 +337,6 @@ func (n *cniNetwork) createCNIConfigListFromNetwork(network *types.Network, writ
|
||||||
return config, cniPathName, nil
|
return config, cniPathName, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseMTU parses the mtu option
|
|
||||||
func parseMTU(mtu string) (int, error) {
|
|
||||||
if mtu == "" {
|
|
||||||
return 0, nil // default
|
|
||||||
}
|
|
||||||
m, err := strconv.Atoi(mtu)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
if m < 0 {
|
|
||||||
return 0, errors.Errorf("mtu %d is less than zero", m)
|
|
||||||
}
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseVlan parses the vlan option
|
|
||||||
func parseVlan(vlan string) (int, error) {
|
|
||||||
if vlan == "" {
|
|
||||||
return 0, nil // default
|
|
||||||
}
|
|
||||||
v, err := strconv.Atoi(vlan)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
if v < 0 || v > 4094 {
|
|
||||||
return 0, errors.Errorf("vlan ID %d must be between 0 and 4094", v)
|
|
||||||
}
|
|
||||||
return v, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertSpecgenPortsToCNIPorts(ports []types.PortMapping) ([]cniPortMapEntry, error) {
|
func convertSpecgenPortsToCNIPorts(ports []types.PortMapping) ([]cniPortMapEntry, error) {
|
||||||
cniPorts := make([]cniPortMapEntry, 0, len(ports))
|
cniPorts := make([]cniPortMapEntry, 0, len(ports))
|
||||||
for _, port := range ports {
|
for _, port := range ports {
|
||||||
|
|
|
@ -9,7 +9,6 @@ import (
|
||||||
"github.com/containers/podman/v3/libpod/define"
|
"github.com/containers/podman/v3/libpod/define"
|
||||||
internalutil "github.com/containers/podman/v3/libpod/network/internal/util"
|
internalutil "github.com/containers/podman/v3/libpod/network/internal/util"
|
||||||
"github.com/containers/podman/v3/libpod/network/types"
|
"github.com/containers/podman/v3/libpod/network/types"
|
||||||
"github.com/containers/podman/v3/libpod/network/util"
|
|
||||||
pkgutil "github.com/containers/podman/v3/pkg/util"
|
pkgutil "github.com/containers/podman/v3/pkg/util"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
@ -42,6 +41,12 @@ func (n *cniNetwork) networkCreate(newNetwork types.Network, defaultNet bool) (*
|
||||||
newNetwork.Driver = types.DefaultNetworkDriver
|
newNetwork.Driver = types.DefaultNetworkDriver
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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(define.ErrInvalidArg, "ID can not be set for network create")
|
||||||
|
}
|
||||||
|
|
||||||
err := internalutil.CommonNetworkCreate(n, &newNetwork)
|
err := internalutil.CommonNetworkCreate(n, &newNetwork)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -77,14 +82,9 @@ func (n *cniNetwork) networkCreate(newNetwork types.Network, defaultNet bool) (*
|
||||||
return nil, errors.Wrapf(define.ErrInvalidArg, "unsupported driver %s", newNetwork.Driver)
|
return nil, errors.Wrapf(define.ErrInvalidArg, "unsupported driver %s", newNetwork.Driver)
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := range newNetwork.Subnets {
|
err = internalutil.ValidateSubnets(&newNetwork, usedNetworks)
|
||||||
err := internalutil.ValidateSubnet(&newNetwork.Subnets[i], !newNetwork.Internal, usedNetworks)
|
if err != nil {
|
||||||
if err != nil {
|
return nil, err
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if util.IsIPv6(newNetwork.Subnets[i].Subnet.IP) {
|
|
||||||
newNetwork.IPv6Enabled = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// generate the network ID
|
// generate the network ID
|
||||||
|
|
|
@ -7,12 +7,6 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func CommonNetworkCreate(n NetUtil, network *types.Network) error {
|
func CommonNetworkCreate(n NetUtil, network *types.Network) error {
|
||||||
// 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 network.ID != "" {
|
|
||||||
return errors.Wrap(define.ErrInvalidArg, "ID can not be set for network create")
|
|
||||||
}
|
|
||||||
|
|
||||||
if network.Labels == nil {
|
if network.Labels == nil {
|
||||||
network.Labels = map[string]string{}
|
network.Labels = map[string]string{}
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,3 +68,11 @@ func getRandomIPv6Subnet() (net.IPNet, error) {
|
||||||
ip = append(ip, make([]byte, 8)...)
|
ip = append(ip, make([]byte, 8)...)
|
||||||
return net.IPNet{IP: ip, Mask: net.CIDRMask(64, 128)}, nil
|
return net.IPNet{IP: ip, Mask: net.CIDRMask(64, 128)}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NormalizeIP will transform the given ip to the 4 byte len ipv4 if possible
|
||||||
|
func NormalizeIP(ip *net.IP) {
|
||||||
|
ipv4 := ip.To4()
|
||||||
|
if ipv4 != nil {
|
||||||
|
*ip = ipv4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParseMTU parses the mtu option
|
||||||
|
func ParseMTU(mtu string) (int, error) {
|
||||||
|
if mtu == "" {
|
||||||
|
return 0, nil // default
|
||||||
|
}
|
||||||
|
m, err := strconv.Atoi(mtu)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if m < 0 {
|
||||||
|
return 0, errors.Errorf("mtu %d is less than zero", m)
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseVlan parses the vlan option
|
||||||
|
func ParseVlan(vlan string) (int, error) {
|
||||||
|
if vlan == "" {
|
||||||
|
return 0, nil // default
|
||||||
|
}
|
||||||
|
v, err := strconv.Atoi(vlan)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if v < 0 || v > 4094 {
|
||||||
|
return 0, errors.Errorf("vlan ID %d must be between 0 and 4094", v)
|
||||||
|
}
|
||||||
|
return v, nil
|
||||||
|
}
|
|
@ -38,6 +38,7 @@ func ValidateSubnet(s *types.Subnet, addGateway bool, usedNetworks []*net.IPNet)
|
||||||
if !s.Subnet.Contains(s.Gateway) {
|
if !s.Subnet.Contains(s.Gateway) {
|
||||||
return errors.Errorf("gateway %s not in subnet %s", s.Gateway, &s.Subnet)
|
return errors.Errorf("gateway %s not in subnet %s", s.Gateway, &s.Subnet)
|
||||||
}
|
}
|
||||||
|
NormalizeIP(&s.Gateway)
|
||||||
} else if addGateway {
|
} else if addGateway {
|
||||||
ip, err := util.FirstIPInSubnet(net)
|
ip, err := util.FirstIPInSubnet(net)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -45,12 +46,35 @@ func ValidateSubnet(s *types.Subnet, addGateway bool, usedNetworks []*net.IPNet)
|
||||||
}
|
}
|
||||||
s.Gateway = ip
|
s.Gateway = ip
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.LeaseRange != nil {
|
if s.LeaseRange != nil {
|
||||||
if s.LeaseRange.StartIP != nil && !s.Subnet.Contains(s.LeaseRange.StartIP) {
|
if s.LeaseRange.StartIP != nil {
|
||||||
return errors.Errorf("lease range start ip %s not in subnet %s", s.LeaseRange.StartIP, &s.Subnet)
|
if !s.Subnet.Contains(s.LeaseRange.StartIP) {
|
||||||
|
return errors.Errorf("lease range start ip %s not in subnet %s", s.LeaseRange.StartIP, &s.Subnet)
|
||||||
|
}
|
||||||
|
NormalizeIP(&s.LeaseRange.StartIP)
|
||||||
}
|
}
|
||||||
if s.LeaseRange.EndIP != nil && !s.Subnet.Contains(s.LeaseRange.EndIP) {
|
if s.LeaseRange.EndIP != nil {
|
||||||
return errors.Errorf("lease range end ip %s not in subnet %s", s.LeaseRange.EndIP, &s.Subnet)
|
if !s.Subnet.Contains(s.LeaseRange.EndIP) {
|
||||||
|
return errors.Errorf("lease range end ip %s not in subnet %s", s.LeaseRange.EndIP, &s.Subnet)
|
||||||
|
}
|
||||||
|
NormalizeIP(&s.LeaseRange.EndIP)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateSubnets will validate the subnets for this network.
|
||||||
|
// It also sets the gateway if the gateway is empty and it sets
|
||||||
|
// IPv6Enabled to true if at least one subnet is ipv6.
|
||||||
|
func ValidateSubnets(network *types.Network, usedNetworks []*net.IPNet) error {
|
||||||
|
for i := range network.Subnets {
|
||||||
|
err := ValidateSubnet(&network.Subnets[i], !network.Internal, usedNetworks)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if util.IsIPv6(network.Subnets[i].Subnet.IP) {
|
||||||
|
network.IPv6Enabled = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -0,0 +1,210 @@
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package netavark
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/containers/podman/v3/libpod/define"
|
||||||
|
internalutil "github.com/containers/podman/v3/libpod/network/internal/util"
|
||||||
|
"github.com/containers/podman/v3/libpod/network/types"
|
||||||
|
"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.
|
||||||
|
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(define.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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
usedNetworks, err = internalutil.GetUsedSubnets(n)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch newNetwork.Driver {
|
||||||
|
case types.BridgeNetworkDriver:
|
||||||
|
err = internalutil.CreateBridge(n, &newNetwork, usedNetworks)
|
||||||
|
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 network option %s", key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, errors.Wrapf(define.ErrInvalidArg, "unsupported driver %s", newNetwork.Driver)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = internalutil.ValidateSubnets(&newNetwork, usedNetworks)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: If we have a working solution for internal networks with dns this check should be removed.
|
||||||
|
if newNetwork.DNSEnabled && newNetwork.Internal {
|
||||||
|
return nil, errors.New("cannot set internal and dns enabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
enc := json.NewEncoder(f)
|
||||||
|
enc.SetIndent("", " ")
|
||||||
|
err = enc.Encode(newNetwork)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &newNetwork, 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
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,5 @@
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package netavark
|
||||||
|
|
||||||
|
const defaultBridgeName = "podman"
|
|
@ -0,0 +1,116 @@
|
||||||
|
package netavark
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
type netavarkError struct {
|
||||||
|
exitCode int
|
||||||
|
// Set the json key to "error" so we can directly unmarshal into this struct
|
||||||
|
Msg string `json:"error"`
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *netavarkError) Error() string {
|
||||||
|
ec := ""
|
||||||
|
// only add the exit code the the error message if we have at least info log level
|
||||||
|
// the normal user does not need to care about the number
|
||||||
|
if e.exitCode > 0 && logrus.IsLevelEnabled(logrus.InfoLevel) {
|
||||||
|
ec = " (exit code " + strconv.Itoa(e.exitCode) + ")"
|
||||||
|
}
|
||||||
|
msg := "netavark" + ec
|
||||||
|
if len(msg) > 0 {
|
||||||
|
msg += ": " + e.Msg
|
||||||
|
}
|
||||||
|
if e.err != nil {
|
||||||
|
msg += ": " + e.err.Error()
|
||||||
|
}
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *netavarkError) Unwrap() error {
|
||||||
|
return e.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func newNetavarkError(msg string, err error) error {
|
||||||
|
return &netavarkError{
|
||||||
|
Msg: msg,
|
||||||
|
err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// execNetavark will execute netavark with the following arguments
|
||||||
|
// It takes the path to the binary, the list of args and an interface which is
|
||||||
|
// marshaled to json and send via stdin to netavark. The result interface is
|
||||||
|
// used to marshal the netavark output into it. This can be nil.
|
||||||
|
// All errors return by this function should be of the type netavarkError
|
||||||
|
// to provide a helpful error message.
|
||||||
|
func execNetavark(binary string, args []string, stdin, result interface{}) error {
|
||||||
|
stdinR, stdinW, err := os.Pipe()
|
||||||
|
if err != nil {
|
||||||
|
return newNetavarkError("failed to create stdin pipe", err)
|
||||||
|
}
|
||||||
|
defer stdinR.Close()
|
||||||
|
|
||||||
|
stdoutR, stdoutW, err := os.Pipe()
|
||||||
|
if err != nil {
|
||||||
|
return newNetavarkError("failed to create stdout pipe", err)
|
||||||
|
}
|
||||||
|
defer stdoutR.Close()
|
||||||
|
defer stdoutW.Close()
|
||||||
|
|
||||||
|
cmd := exec.Command(binary, args...)
|
||||||
|
// connect the pipes to stdin and stdout
|
||||||
|
cmd.Stdin = stdinR
|
||||||
|
cmd.Stdout = stdoutW
|
||||||
|
// connect stderr to the podman stderr for logging
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
// set the netavark log level to the same as the podman
|
||||||
|
cmd.Env = append(os.Environ(), "RUST_LOG="+logrus.GetLevel().String())
|
||||||
|
// if we run with debug log level lets also set RUST_BACKTRACE=1 so we can get the full stack trace in case of panics
|
||||||
|
if logrus.IsLevelEnabled(logrus.DebugLevel) {
|
||||||
|
cmd.Env = append(cmd.Env, "RUST_BACKTRACE=1")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = cmd.Start()
|
||||||
|
if err != nil {
|
||||||
|
return newNetavarkError("failed to start process", err)
|
||||||
|
}
|
||||||
|
err = json.NewEncoder(stdinW).Encode(stdin)
|
||||||
|
stdinW.Close()
|
||||||
|
if err != nil {
|
||||||
|
return newNetavarkError("failed to encode stdin data", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dec := json.NewDecoder(stdoutR)
|
||||||
|
|
||||||
|
err = cmd.Wait()
|
||||||
|
stdoutW.Close()
|
||||||
|
if err != nil {
|
||||||
|
exitError := &exec.ExitError{}
|
||||||
|
if errors.As(err, &exitError) {
|
||||||
|
ne := &netavarkError{}
|
||||||
|
// lets disallow unknown fields to make sure we do not get some unexpected stuff
|
||||||
|
dec.DisallowUnknownFields()
|
||||||
|
// this will unmarshal the error message into the error struct
|
||||||
|
ne.err = dec.Decode(ne)
|
||||||
|
ne.exitCode = exitError.ExitCode()
|
||||||
|
return ne
|
||||||
|
}
|
||||||
|
return newNetavarkError("unexpected failure during execution", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if result != nil {
|
||||||
|
err = dec.Decode(result)
|
||||||
|
if err != nil {
|
||||||
|
return newNetavarkError("failed to decode result", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package netavark_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/containers/podman/v3/libpod/network/netavark"
|
||||||
|
"github.com/containers/podman/v3/libpod/network/types"
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNetavark(t *testing.T) {
|
||||||
|
RegisterFailHandler(Fail)
|
||||||
|
RunSpecs(t, "Netavark Suite")
|
||||||
|
}
|
||||||
|
|
||||||
|
var netavarkBinary string
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
netavarkBinary = os.Getenv("NETAVARK_BINARY")
|
||||||
|
if netavarkBinary == "" {
|
||||||
|
netavarkBinary = "/usr/libexec/podman/netavark"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNetworkInterface(confDir string, machine bool) (types.ContainerNetwork, error) {
|
||||||
|
return netavark.NewNetworkInterface(netavark.InitConfig{
|
||||||
|
NetworkConfigDir: confDir,
|
||||||
|
IsMachine: machine,
|
||||||
|
NetavarkBinary: netavarkBinary,
|
||||||
|
LockFile: filepath.Join(confDir, "netavark.lock"),
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,275 @@
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package netavark
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/containers/podman/v3/libpod/define"
|
||||||
|
"github.com/containers/podman/v3/libpod/network/internal/util"
|
||||||
|
"github.com/containers/podman/v3/libpod/network/types"
|
||||||
|
"github.com/containers/storage/pkg/lockfile"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
type netavarkNetwork struct {
|
||||||
|
// networkConfigDir is directory where the network config files are stored.
|
||||||
|
networkConfigDir string
|
||||||
|
|
||||||
|
// netavarkBinary is the path to the netavark binary.
|
||||||
|
netavarkBinary string
|
||||||
|
|
||||||
|
// defaultNetwork is the name for the default network.
|
||||||
|
defaultNetwork string
|
||||||
|
// defaultSubnet is the default subnet for the default network.
|
||||||
|
defaultSubnet types.IPNet
|
||||||
|
|
||||||
|
// isMachine describes whenever podman runs in a podman machine environment.
|
||||||
|
isMachine bool
|
||||||
|
|
||||||
|
// lock is a internal lock for critical operations
|
||||||
|
lock lockfile.Locker
|
||||||
|
|
||||||
|
// 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
|
||||||
|
|
||||||
|
// DefaultNetwork is the name for the default network.
|
||||||
|
DefaultNetwork string
|
||||||
|
// DefaultSubnet is the default subnet for the default network.
|
||||||
|
DefaultSubnet string
|
||||||
|
|
||||||
|
// IsMachine describes whenever podman runs in a podman machine environment.
|
||||||
|
IsMachine bool
|
||||||
|
|
||||||
|
// LockFile is the path to lock file.
|
||||||
|
LockFile string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
// TODO: consider using a shared memory lock
|
||||||
|
lock, err := lockfile.GetLockfile(conf.LockFile)
|
||||||
|
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, errors.Wrap(err, "failed to parse default subnet")
|
||||||
|
}
|
||||||
|
|
||||||
|
n := &netavarkNetwork{
|
||||||
|
networkConfigDir: conf.NetworkConfigDir,
|
||||||
|
netavarkBinary: conf.NetavarkBinary,
|
||||||
|
defaultNetwork: defaultNetworkName,
|
||||||
|
defaultSubnet: defaultNet,
|
||||||
|
isMachine: conf.IsMachine,
|
||||||
|
lock: lock,
|
||||||
|
}
|
||||||
|
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drivers will return the list of supported network drivers
|
||||||
|
// for this interface.
|
||||||
|
func (n *netavarkNetwork) Drivers() []string {
|
||||||
|
return []string{types.BridgeNetworkDriver}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 := ioutil.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 !define.NameRegex.MatchString(network.Name) {
|
||||||
|
logrus.Warnf("Network config %q has invalid name: %q, skipping: %v", path, network.Name, define.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 errors.Wrapf(err, "failed to create default network %s", n.defaultNetwork)
|
||||||
|
}
|
||||||
|
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 errors.Errorf("invalid network ID %q", network.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return util.ValidateSubnets(network, 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, errors.Errorf("more than one result for network ID %s", nameOrID)
|
||||||
|
}
|
||||||
|
net = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if net != nil {
|
||||||
|
return net, nil
|
||||||
|
}
|
||||||
|
return nil, errors.Wrapf(define.ErrNoSuchNetwork, "unable to find network with name or ID %s", nameOrID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package netavark
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/containers/podman/v3/libpod/network/internal/util"
|
||||||
|
"github.com/containers/podman/v3/libpod/network/types"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
type netavarkOptions struct {
|
||||||
|
types.NetworkOptions
|
||||||
|
Networks map[string]*types.Network `json:"network_info"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup will setup the container network namespace. It returns
|
||||||
|
// a map of StatusBlocks, the key is the network name.
|
||||||
|
func (n *netavarkNetwork) Setup(namespacePath string, options types.SetupOptions) (map[string]types.StatusBlock, error) {
|
||||||
|
n.lock.Lock()
|
||||||
|
defer n.lock.Unlock()
|
||||||
|
err := n.loadNetworks()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = util.ValidateSetupOptions(n, namespacePath, options)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO IP address assignment
|
||||||
|
|
||||||
|
netavarkOpts, err := n.convertNetOpts(options.NetworkOptions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to convert net opts")
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := json.Marshal(&netavarkOpts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
fmt.Println(string(b))
|
||||||
|
|
||||||
|
result := map[string]types.StatusBlock{}
|
||||||
|
err = execNetavark(n.netavarkBinary, []string{"setup", namespacePath}, netavarkOpts, result)
|
||||||
|
|
||||||
|
if len(result) != len(options.Networks) {
|
||||||
|
logrus.Errorf("unexpected netavark result: %v", result)
|
||||||
|
return nil, fmt.Errorf("unexpected netavark result length, want (%d), got (%d) networks", len(options.Networks), len(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Teardown will teardown the container network namespace.
|
||||||
|
func (n *netavarkNetwork) Teardown(namespacePath string, options types.TeardownOptions) error {
|
||||||
|
n.lock.Lock()
|
||||||
|
defer n.lock.Unlock()
|
||||||
|
err := n.loadNetworks()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
netavarkOpts, err := n.convertNetOpts(options.NetworkOptions)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed to convert net opts")
|
||||||
|
}
|
||||||
|
|
||||||
|
return execNetavark(n.netavarkBinary, []string{"teardown", namespacePath}, netavarkOpts, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *netavarkNetwork) convertNetOpts(opts types.NetworkOptions) (*netavarkOptions, error) {
|
||||||
|
netavarkOptions := netavarkOptions{
|
||||||
|
NetworkOptions: opts,
|
||||||
|
Networks: make(map[string]*types.Network, len(opts.Networks)),
|
||||||
|
}
|
||||||
|
|
||||||
|
for network := range opts.Networks {
|
||||||
|
net, err := n.getNetwork(network)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
netavarkOptions.Networks[network] = net
|
||||||
|
}
|
||||||
|
return &netavarkOptions, nil
|
||||||
|
}
|
|
@ -0,0 +1,363 @@
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package netavark_test
|
||||||
|
|
||||||
|
// The tests have to be run as root.
|
||||||
|
// For each test there will be two network namespaces created,
|
||||||
|
// netNSTest and netNSContainer. Each test must be run inside
|
||||||
|
// netNSTest to prevent leakage in the host netns, therefore
|
||||||
|
// it should use the following structure:
|
||||||
|
// It("test name", func() {
|
||||||
|
// runTest(func() {
|
||||||
|
// // add test logic here
|
||||||
|
// })
|
||||||
|
// })
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/containernetworking/plugins/pkg/ns"
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/vishvananda/netlink"
|
||||||
|
|
||||||
|
"github.com/containers/podman/v3/libpod/network/types"
|
||||||
|
"github.com/containers/podman/v3/pkg/netns"
|
||||||
|
"github.com/containers/podman/v3/pkg/rootless"
|
||||||
|
"github.com/containers/storage/pkg/stringid"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("run netavark", func() {
|
||||||
|
var (
|
||||||
|
libpodNet types.ContainerNetwork
|
||||||
|
confDir string
|
||||||
|
logBuffer bytes.Buffer
|
||||||
|
netNSTest ns.NetNS
|
||||||
|
netNSContainer ns.NetNS
|
||||||
|
)
|
||||||
|
|
||||||
|
// runTest is a helper function to run a test. It ensures that each test
|
||||||
|
// is run in its own netns. It also creates a mountns to mount a tmpfs to /var/lib/cni.
|
||||||
|
runTest := func(run func()) {
|
||||||
|
netNSTest.Do(func(_ ns.NetNS) error {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
// we have to setup the loopback adapter in this netns to use port forwarding
|
||||||
|
link, err := netlink.LinkByName("lo")
|
||||||
|
Expect(err).To(BeNil(), "Failed to get loopback adapter")
|
||||||
|
err = netlink.LinkSetUp(link)
|
||||||
|
Expect(err).To(BeNil(), "Failed to set loopback adapter up")
|
||||||
|
run()
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
logrus.SetLevel(logrus.TraceLevel)
|
||||||
|
logrus.SetFormatter(&logrus.TextFormatter{DisableQuote: true})
|
||||||
|
// The tests need root privileges.
|
||||||
|
// Technically we could work around that by using user namespaces and
|
||||||
|
// the rootless cni code but this is to much work to get it right for a unit test.
|
||||||
|
if rootless.IsRootless() {
|
||||||
|
Skip("this test needs to be run as root")
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
confDir, err = ioutil.TempDir("", "podman_netavark_test")
|
||||||
|
if err != nil {
|
||||||
|
Fail("Failed to create tmpdir")
|
||||||
|
}
|
||||||
|
logBuffer = bytes.Buffer{}
|
||||||
|
logrus.SetOutput(&logBuffer)
|
||||||
|
|
||||||
|
netNSTest, err = netns.NewNS()
|
||||||
|
if err != nil {
|
||||||
|
Fail("Failed to create netns")
|
||||||
|
}
|
||||||
|
|
||||||
|
netNSContainer, err = netns.NewNS()
|
||||||
|
if err != nil {
|
||||||
|
Fail("Failed to create netns")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
JustBeforeEach(func() {
|
||||||
|
var err error
|
||||||
|
libpodNet, err = getNetworkInterface(confDir, false)
|
||||||
|
if err != nil {
|
||||||
|
Fail("Failed to create NewCNINetworkInterface")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
AfterEach(func() {
|
||||||
|
logrus.SetFormatter(&logrus.TextFormatter{})
|
||||||
|
logrus.SetLevel(logrus.InfoLevel)
|
||||||
|
os.RemoveAll(confDir)
|
||||||
|
|
||||||
|
netns.UnmountNS(netNSTest)
|
||||||
|
netNSTest.Close()
|
||||||
|
|
||||||
|
netns.UnmountNS(netNSContainer)
|
||||||
|
netNSContainer.Close()
|
||||||
|
|
||||||
|
fmt.Println(logBuffer.String())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("test basic setup", func() {
|
||||||
|
runTest(func() {
|
||||||
|
defNet := types.DefaultNetworkName
|
||||||
|
intName := "eth0"
|
||||||
|
opts := types.SetupOptions{
|
||||||
|
NetworkOptions: types.NetworkOptions{
|
||||||
|
ContainerID: "someID",
|
||||||
|
ContainerName: "someName",
|
||||||
|
Networks: map[string]types.PerNetworkOptions{
|
||||||
|
defNet: {
|
||||||
|
InterfaceName: intName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
res, err := libpodNet.Setup(netNSContainer.Path(), opts)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(res).To(HaveLen(1))
|
||||||
|
Expect(res).To(HaveKey(defNet))
|
||||||
|
Expect(res[defNet].Interfaces).To(HaveKey(intName))
|
||||||
|
Expect(res[defNet].Interfaces[intName].Networks).To(HaveLen(1))
|
||||||
|
ip := res[defNet].Interfaces[intName].Networks[0].Subnet.IP
|
||||||
|
Expect(ip.String()).To(ContainSubstring("10.88.0."))
|
||||||
|
gw := res[defNet].Interfaces[intName].Networks[0].Gateway
|
||||||
|
Expect(gw.String()).To(Equal("10.88.0.1"))
|
||||||
|
macAddress := res[defNet].Interfaces[intName].MacAddress
|
||||||
|
Expect(macAddress).To(HaveLen(6))
|
||||||
|
// default network has no dns
|
||||||
|
Expect(res[defNet].DNSServerIPs).To(BeEmpty())
|
||||||
|
Expect(res[defNet].DNSSearchDomains).To(BeEmpty())
|
||||||
|
|
||||||
|
// check in the container namespace if the settings are applied
|
||||||
|
err = netNSContainer.Do(func(_ ns.NetNS) error {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
i, err := net.InterfaceByName(intName)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
Expect(i.Name).To(Equal(intName))
|
||||||
|
Expect(i.HardwareAddr).To(Equal(macAddress))
|
||||||
|
addrs, err := i.Addrs()
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
subnet := &net.IPNet{
|
||||||
|
IP: ip,
|
||||||
|
Mask: net.CIDRMask(16, 32),
|
||||||
|
}
|
||||||
|
Expect(addrs).To(ContainElements(subnet))
|
||||||
|
|
||||||
|
// check loopback adapter
|
||||||
|
i, err = net.InterfaceByName("lo")
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
Expect(i.Name).To(Equal("lo"))
|
||||||
|
Expect(i.Flags & net.FlagLoopback).To(Equal(net.FlagLoopback))
|
||||||
|
Expect(i.Flags&net.FlagUp).To(Equal(net.FlagUp), "Loopback adapter should be up")
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
|
||||||
|
// default bridge name
|
||||||
|
bridgeName := "podman0"
|
||||||
|
// check settings on the host side
|
||||||
|
i, err := net.InterfaceByName(bridgeName)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(i.Name).To(Equal(bridgeName))
|
||||||
|
addrs, err := i.Addrs()
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
// test that the gateway ip is assigned to the interface
|
||||||
|
subnet := &net.IPNet{
|
||||||
|
IP: gw,
|
||||||
|
Mask: net.CIDRMask(16, 32),
|
||||||
|
}
|
||||||
|
Expect(addrs).To(ContainElements(subnet))
|
||||||
|
|
||||||
|
wg := &sync.WaitGroup{}
|
||||||
|
expected := stringid.GenerateNonCryptoID()
|
||||||
|
// now check ip connectivity
|
||||||
|
err = netNSContainer.Do(func(_ ns.NetNS) error {
|
||||||
|
runNetListener(wg, "tcp", "0.0.0.0", 5000, expected)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
conn, err := net.Dial("tcp", ip.String()+":5000")
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
_, err = conn.Write([]byte(expected))
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
conn.Close()
|
||||||
|
|
||||||
|
err = libpodNet.Teardown(netNSContainer.Path(), types.TeardownOptions(opts))
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, proto := range []string{"tcp", "udp"} {
|
||||||
|
// copy proto to extra var to keep correct references in the goroutines
|
||||||
|
protocol := proto
|
||||||
|
It("run with exposed ports protocol "+protocol, func() {
|
||||||
|
runTest(func() {
|
||||||
|
testdata := stringid.GenerateNonCryptoID()
|
||||||
|
defNet := types.DefaultNetworkName
|
||||||
|
intName := "eth0"
|
||||||
|
setupOpts := types.SetupOptions{
|
||||||
|
NetworkOptions: types.NetworkOptions{
|
||||||
|
ContainerID: stringid.GenerateNonCryptoID(),
|
||||||
|
PortMappings: []types.PortMapping{{
|
||||||
|
Protocol: protocol,
|
||||||
|
HostIP: "127.0.0.1",
|
||||||
|
HostPort: 5000,
|
||||||
|
ContainerPort: 5000,
|
||||||
|
}},
|
||||||
|
Networks: map[string]types.PerNetworkOptions{
|
||||||
|
defNet: {InterfaceName: intName},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
res, err := libpodNet.Setup(netNSContainer.Path(), setupOpts)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
Expect(res).To(HaveLen(1))
|
||||||
|
Expect(res).To(HaveKey(defNet))
|
||||||
|
Expect(res[defNet].Interfaces).To(HaveKey(intName))
|
||||||
|
Expect(res[defNet].Interfaces[intName].Networks).To(HaveLen(1))
|
||||||
|
Expect(res[defNet].Interfaces[intName].Networks[0].Subnet.IP.String()).To(ContainSubstring("10.88.0."))
|
||||||
|
Expect(res[defNet].Interfaces[intName].MacAddress).To(HaveLen(6))
|
||||||
|
// default network has no dns
|
||||||
|
Expect(res[defNet].DNSServerIPs).To(BeEmpty())
|
||||||
|
Expect(res[defNet].DNSSearchDomains).To(BeEmpty())
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(1)
|
||||||
|
// start a listener in the container ns
|
||||||
|
err = netNSContainer.Do(func(_ ns.NetNS) error {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
runNetListener(&wg, protocol, "0.0.0.0", 5000, testdata)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
|
||||||
|
conn, err := net.Dial(protocol, "127.0.0.1:5000")
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
_, err = conn.Write([]byte(testdata))
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
conn.Close()
|
||||||
|
|
||||||
|
// wait for the listener to finish
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
err = libpodNet.Teardown(netNSContainer.Path(), types.TeardownOptions(setupOpts))
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
It("run with range ports protocol "+protocol, func() {
|
||||||
|
runTest(func() {
|
||||||
|
defNet := types.DefaultNetworkName
|
||||||
|
intName := "eth0"
|
||||||
|
setupOpts := types.SetupOptions{
|
||||||
|
NetworkOptions: types.NetworkOptions{
|
||||||
|
ContainerID: stringid.GenerateNonCryptoID(),
|
||||||
|
PortMappings: []types.PortMapping{{
|
||||||
|
Protocol: protocol,
|
||||||
|
HostIP: "127.0.0.1",
|
||||||
|
HostPort: 5001,
|
||||||
|
ContainerPort: 5000,
|
||||||
|
Range: 3,
|
||||||
|
}},
|
||||||
|
Networks: map[string]types.PerNetworkOptions{
|
||||||
|
defNet: {InterfaceName: intName},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
res, err := libpodNet.Setup(netNSContainer.Path(), setupOpts)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
Expect(res).To(HaveLen(1))
|
||||||
|
Expect(res).To(HaveKey(defNet))
|
||||||
|
Expect(res[defNet].Interfaces).To(HaveKey(intName))
|
||||||
|
Expect(res[defNet].Interfaces[intName].Networks).To(HaveLen(1))
|
||||||
|
containerIP := res[defNet].Interfaces[intName].Networks[0].Subnet.IP.String()
|
||||||
|
Expect(containerIP).To(ContainSubstring("10.88.0."))
|
||||||
|
Expect(res[defNet].Interfaces[intName].MacAddress).To(HaveLen(6))
|
||||||
|
// default network has no dns
|
||||||
|
Expect(res[defNet].DNSServerIPs).To(BeEmpty())
|
||||||
|
Expect(res[defNet].DNSSearchDomains).To(BeEmpty())
|
||||||
|
|
||||||
|
// loop over all ports
|
||||||
|
for p := 5001; p < 5004; p++ {
|
||||||
|
port := p
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(1)
|
||||||
|
testdata := stringid.GenerateNonCryptoID()
|
||||||
|
// start a listener in the container ns
|
||||||
|
err = netNSContainer.Do(func(_ ns.NetNS) error {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
runNetListener(&wg, protocol, containerIP, port-1, testdata)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
|
||||||
|
conn, err := net.Dial(protocol, net.JoinHostPort("127.0.0.1", strconv.Itoa(port)))
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
_, err = conn.Write([]byte(testdata))
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
conn.Close()
|
||||||
|
|
||||||
|
// wait for the listener to finish
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
err = libpodNet.Teardown(netNSContainer.Path(), types.TeardownOptions(setupOpts))
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
func runNetListener(wg *sync.WaitGroup, protocol, ip string, port int, expectedData string) {
|
||||||
|
switch protocol {
|
||||||
|
case "tcp":
|
||||||
|
ln, err := net.Listen(protocol, net.JoinHostPort(ip, strconv.Itoa(port)))
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
// make sure to read in a separate goroutine to not block
|
||||||
|
go func() {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
defer wg.Done()
|
||||||
|
defer ln.Close()
|
||||||
|
conn, err := ln.Accept()
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
defer conn.Close()
|
||||||
|
conn.SetDeadline(time.Now().Add(1 * time.Second))
|
||||||
|
data, err := ioutil.ReadAll(conn)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
Expect(string(data)).To(Equal(expectedData))
|
||||||
|
}()
|
||||||
|
case "udp":
|
||||||
|
conn, err := net.ListenUDP("udp", &net.UDPAddr{
|
||||||
|
IP: net.ParseIP(ip),
|
||||||
|
Port: port,
|
||||||
|
})
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
conn.SetDeadline(time.Now().Add(1 * time.Second))
|
||||||
|
go func() {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
defer wg.Done()
|
||||||
|
defer conn.Close()
|
||||||
|
data := make([]byte, len(expectedData))
|
||||||
|
i, err := conn.Read(data)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
Expect(i).To(Equal(len(expectedData)))
|
||||||
|
Expect(string(data)).To(Equal(expectedData))
|
||||||
|
}()
|
||||||
|
default:
|
||||||
|
Fail("unsupported protocol")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"name": "bridge",
|
||||||
|
"id": "17f29b073143d8cd97b5bbe492bdeffec1c5fee55cc1fe2112c8b9335f8b6121",
|
||||||
|
"driver": "bridge",
|
||||||
|
"network_interface": "podman9",
|
||||||
|
"created": "2021-10-06T18:50:54.25770461+02:00",
|
||||||
|
"subnets": [
|
||||||
|
{
|
||||||
|
"subnet": "10.89.8.0/24",
|
||||||
|
"gateway": "10.89.8.1",
|
||||||
|
"lease_range": {
|
||||||
|
"start_ip": "10.89.8.20",
|
||||||
|
"end_ip": "10.89.8.50"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"name": "invalid name",
|
||||||
|
"id": "6839f44f0fd01c5c5830856b66a1d7ce46842dd8798be0addf96f7255ce9f889",
|
||||||
|
"driver": "bridge",
|
||||||
|
"network_interface": "podman9",
|
||||||
|
"created": "2021-10-06T18:50:54.25770461+02:00",
|
||||||
|
"subnets": [
|
||||||
|
{
|
||||||
|
"subnet": "10.89.8.0/24",
|
||||||
|
"gateway": "10.89.8.1"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ipv6_enabled": false,
|
||||||
|
"internal": false,
|
||||||
|
"dns_enabled": true,
|
||||||
|
"ipam_options": {
|
||||||
|
"driver": "host-local"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"name": "invalid_gateway",
|
||||||
|
"id": "49be6e401e7f8b9844afb969dcbc96e78205ed86ec1e5a46150bd4ab4fdd5686",
|
||||||
|
"driver": "bridge",
|
||||||
|
"network_interface": "podman9",
|
||||||
|
"created": "2021-10-06T18:50:54.25770461+02:00",
|
||||||
|
"subnets": [
|
||||||
|
{
|
||||||
|
"subnet": "10.89.9.0/24",
|
||||||
|
"gateway": "10.89.100.1"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ipv6_enabled": false,
|
||||||
|
"internal": false,
|
||||||
|
"dns_enabled": true,
|
||||||
|
"ipam_options": {
|
||||||
|
"driver": "host-local"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"name": "name_miss",
|
||||||
|
"id": "3bed2cb3a3acf7b6a8ef408420cc682d5520e26976d354254f528c965612054f",
|
||||||
|
"driver": "bridge",
|
||||||
|
"network_interface": "podman8",
|
||||||
|
"created": "2021-10-06T18:50:54.25770461+02:00",
|
||||||
|
"subnets": [
|
||||||
|
{
|
||||||
|
"subnet": "10.89.7.0/24",
|
||||||
|
"gateway": "10.89.7.1"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ipv6_enabled": false,
|
||||||
|
"internal": true,
|
||||||
|
"dns_enabled": false,
|
||||||
|
"ipam_options": {
|
||||||
|
"driver": "host-local"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"name": "wrongID",
|
||||||
|
"id": "someID",
|
||||||
|
"driver": "bridge",
|
||||||
|
"network_interface": "podman1",
|
||||||
|
"created": "2021-10-06T18:50:54.25770461+02:00",
|
||||||
|
"subnets": [
|
||||||
|
{
|
||||||
|
"subnet": "10.89.0.0/24",
|
||||||
|
"gateway": "10.89.0.1"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ipv6_enabled": false,
|
||||||
|
"internal": false,
|
||||||
|
"dns_enabled": false,
|
||||||
|
"ipam_options": {
|
||||||
|
"driver": "host-local"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
{
|
||||||
|
"name": "bridge",
|
||||||
|
"id": "17f29b073143d8cd97b5bbe492bdeffec1c5fee55cc1fe2112c8b9335f8b6121",
|
||||||
|
"driver": "bridge",
|
||||||
|
"network_interface": "podman9",
|
||||||
|
"created": "2021-10-06T18:50:54.25770461+02:00",
|
||||||
|
"subnets": [
|
||||||
|
{
|
||||||
|
"subnet": "10.89.8.0/24",
|
||||||
|
"gateway": "10.89.8.1",
|
||||||
|
"lease_range": {
|
||||||
|
"start_ip": "10.89.8.20",
|
||||||
|
"end_ip": "10.89.8.50"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ipv6_enabled": false,
|
||||||
|
"internal": false,
|
||||||
|
"dns_enabled": true,
|
||||||
|
"ipam_options": {
|
||||||
|
"driver": "host-local"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
{
|
||||||
|
"name": "dualstack",
|
||||||
|
"id": "6839f44f0fd01c5c5830856b66a1d7ce46842dd8798be0addf96f7255ce9f889",
|
||||||
|
"driver": "bridge",
|
||||||
|
"network_interface": "podman21",
|
||||||
|
"created": "2021-10-06T18:50:54.25770461+02:00",
|
||||||
|
"subnets": [
|
||||||
|
{
|
||||||
|
"subnet": "fd10:88:a::/64",
|
||||||
|
"gateway": "fd10:88:a::1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"subnet": "10.89.19.0/24",
|
||||||
|
"gateway": "10.89.19.10"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ipv6_enabled": true,
|
||||||
|
"internal": false,
|
||||||
|
"dns_enabled": true,
|
||||||
|
"ipam_options": {
|
||||||
|
"driver": "host-local"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"name": "internal",
|
||||||
|
"id": "3bed2cb3a3acf7b6a8ef408420cc682d5520e26976d354254f528c965612054f",
|
||||||
|
"driver": "bridge",
|
||||||
|
"network_interface": "podman8",
|
||||||
|
"created": "2021-10-06T18:50:54.25770461+02:00",
|
||||||
|
"subnets": [
|
||||||
|
{
|
||||||
|
"subnet": "10.89.7.0/24"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ipv6_enabled": false,
|
||||||
|
"internal": true,
|
||||||
|
"dns_enabled": false,
|
||||||
|
"ipam_options": {
|
||||||
|
"driver": "host-local"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"name": "label",
|
||||||
|
"id": "1aca80e8b55c802f7b43740da2990e1b5735bbb323d93eb5ebda8395b04025e2",
|
||||||
|
"driver": "bridge",
|
||||||
|
"network_interface": "podman15",
|
||||||
|
"created": "2021-10-06T18:50:54.25770461+02:00",
|
||||||
|
"subnets": [
|
||||||
|
{
|
||||||
|
"subnet": "10.89.13.0/24",
|
||||||
|
"gateway": "10.89.13.1"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ipv6_enabled": false,
|
||||||
|
"internal": false,
|
||||||
|
"dns_enabled": true,
|
||||||
|
"labels": {
|
||||||
|
"mykey": "value"
|
||||||
|
},
|
||||||
|
"ipam_options": {
|
||||||
|
"driver": "host-local"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"name": "mtu",
|
||||||
|
"id": "49be6e401e7f8b9844afb969dcbc96e78205ed86ec1e5a46150bd4ab4fdd5686",
|
||||||
|
"driver": "bridge",
|
||||||
|
"network_interface": "podman13",
|
||||||
|
"created": "2021-10-06T18:50:54.25770461+02:00",
|
||||||
|
"subnets": [
|
||||||
|
{
|
||||||
|
"subnet": "10.89.11.0/24",
|
||||||
|
"gateway": "10.89.11.1"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ipv6_enabled": false,
|
||||||
|
"internal": false,
|
||||||
|
"dns_enabled": true,
|
||||||
|
"options": {
|
||||||
|
"mtu": "1500"
|
||||||
|
},
|
||||||
|
"ipam_options": {
|
||||||
|
"driver": "host-local"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"name": "podman",
|
||||||
|
"id": "2f259bab93aaaaa2542ba43ef33eb990d0999ee1b9924b557b7be53c0b7a1bb9",
|
||||||
|
"driver": "bridge",
|
||||||
|
"network_interface": "podman0",
|
||||||
|
"created": "2021-10-06T18:50:54.25770461+02:00",
|
||||||
|
"subnets": [
|
||||||
|
{
|
||||||
|
"subnet": "10.88.0.0/16",
|
||||||
|
"gateway": "10.88.0.1"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ipv6_enabled": false,
|
||||||
|
"internal": false,
|
||||||
|
"dns_enabled": false,
|
||||||
|
"ipam_options": {
|
||||||
|
"driver": "host-local"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"name": "vlan",
|
||||||
|
"id": "c3b258168c41c0bce97616716bef315eeed33eb1142904bfe7f32eb392c7cf80",
|
||||||
|
"driver": "bridge",
|
||||||
|
"network_interface": "podman14",
|
||||||
|
"created": "2021-10-06T18:50:54.25770461+02:00",
|
||||||
|
"subnets": [
|
||||||
|
{
|
||||||
|
"subnet": "10.89.12.0/24",
|
||||||
|
"gateway": "10.89.12.1"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ipv6_enabled": false,
|
||||||
|
"internal": false,
|
||||||
|
"dns_enabled": true,
|
||||||
|
"options": {
|
||||||
|
"vlan": "5"
|
||||||
|
},
|
||||||
|
"ipam_options": {
|
||||||
|
"driver": "host-local"
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue