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"
|
||||
|
||||
"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/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)
|
||||
}
|
||||
// convert to 4 byte if ipv4
|
||||
ipv4 := gateway.To4()
|
||||
if ipv4 != nil {
|
||||
gateway = ipv4
|
||||
}
|
||||
internalutil.NormalizeIP(&gateway)
|
||||
} else if !network.Internal {
|
||||
// only add a gateway address if the network is not internal
|
||||
gateway, err = util.FirstIPInSubnet(sub)
|
||||
|
@ -244,13 +242,13 @@ func (n *cniNetwork) createCNIConfigListFromNetwork(network *types.Network, writ
|
|||
for k, v := range network.Options {
|
||||
switch k {
|
||||
case "mtu":
|
||||
mtu, err = parseMTU(v)
|
||||
mtu, err = internalutil.ParseMTU(v)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
case "vlan":
|
||||
vlan, err = parseVlan(v)
|
||||
vlan, err = internalutil.ParseVlan(v)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
@ -339,36 +337,6 @@ func (n *cniNetwork) createCNIConfigListFromNetwork(network *types.Network, writ
|
|||
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) {
|
||||
cniPorts := make([]cniPortMapEntry, 0, len(ports))
|
||||
for _, port := range ports {
|
||||
|
|
|
@ -9,7 +9,6 @@ import (
|
|||
"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/podman/v3/libpod/network/util"
|
||||
pkgutil "github.com/containers/podman/v3/pkg/util"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
@ -42,6 +41,12 @@ func (n *cniNetwork) networkCreate(newNetwork types.Network, defaultNet bool) (*
|
|||
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)
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
for i := range newNetwork.Subnets {
|
||||
err := internalutil.ValidateSubnet(&newNetwork.Subnets[i], !newNetwork.Internal, usedNetworks)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if util.IsIPv6(newNetwork.Subnets[i].Subnet.IP) {
|
||||
newNetwork.IPv6Enabled = true
|
||||
}
|
||||
err = internalutil.ValidateSubnets(&newNetwork, usedNetworks)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// generate the network ID
|
||||
|
|
|
@ -7,12 +7,6 @@ import (
|
|||
)
|
||||
|
||||
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 {
|
||||
network.Labels = map[string]string{}
|
||||
}
|
||||
|
|
|
@ -68,3 +68,11 @@ func getRandomIPv6Subnet() (net.IPNet, error) {
|
|||
ip = append(ip, make([]byte, 8)...)
|
||||
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) {
|
||||
return errors.Errorf("gateway %s not in subnet %s", s.Gateway, &s.Subnet)
|
||||
}
|
||||
NormalizeIP(&s.Gateway)
|
||||
} else if addGateway {
|
||||
ip, err := util.FirstIPInSubnet(net)
|
||||
if err != nil {
|
||||
|
@ -45,12 +46,35 @@ func ValidateSubnet(s *types.Subnet, addGateway bool, usedNetworks []*net.IPNet)
|
|||
}
|
||||
s.Gateway = ip
|
||||
}
|
||||
|
||||
if s.LeaseRange != nil {
|
||||
if s.LeaseRange.StartIP != nil && !s.Subnet.Contains(s.LeaseRange.StartIP) {
|
||||
return errors.Errorf("lease range start ip %s not in subnet %s", s.LeaseRange.StartIP, &s.Subnet)
|
||||
if s.LeaseRange.StartIP != nil {
|
||||
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) {
|
||||
return errors.Errorf("lease range end ip %s not in subnet %s", s.LeaseRange.EndIP, &s.Subnet)
|
||||
if s.LeaseRange.EndIP != nil {
|
||||
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
|
||||
|
|
|
@ -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