summaryrefslogtreecommitdiff
path: root/pkg/specgen
diff options
context:
space:
mode:
authorPaul Holzinger <pholzing@redhat.com>2021-12-10 15:22:09 +0100
committerPaul Holzinger <pholzing@redhat.com>2021-12-14 15:23:39 +0100
commit535818414c2a6bdcf6434e36c33775ea1a43f1cf (patch)
treebc7130eb922b7d2918527f13c3155506af4444f1 /pkg/specgen
parentd072167fe2f75db9648bf1be4181b42e9b7db9a4 (diff)
downloadpodman-535818414c2a6bdcf6434e36c33775ea1a43f1cf.tar.gz
podman-535818414c2a6bdcf6434e36c33775ea1a43f1cf.tar.bz2
podman-535818414c2a6bdcf6434e36c33775ea1a43f1cf.zip
support advanced network configuration via cli
Rework the --network parse logic to support multiple networks with specific network configuration settings. --network can now be set multiple times. For bridge network mode the following options have been added: - **alias=name**: Add network-scoped alias for the container. - **ip=IPv4**: Specify a static ipv4 address for this container. - **ip=IPv6**: Specify a static ipv6 address for this container. - **mac=MAC**: Specify a static mac address address for this container. - **interface_name**: Specify a name for the created network interface inside the container. So now you can set --network bridge:ip=10.88.0.10,mac=44:33:22:11:00:99 for the default bridge network as well as for network names. This is better than using --ip because we can set the ip per network without any confusion which network the ip address should be assigned to. The --ip, --mac-address and --network-alias options are still supported but --ip or --mac-address can only be set when only one network is set. This limitation already existed previously. The ability to specify a custom network interface name is new Fixes #11534 Signed-off-by: Paul Holzinger <pholzing@redhat.com>
Diffstat (limited to 'pkg/specgen')
-rw-r--r--pkg/specgen/generate/namespaces.go16
-rw-r--r--pkg/specgen/namespaces.go164
-rw-r--r--pkg/specgen/namespaces_test.go265
-rw-r--r--pkg/specgen/pod_validate.go5
4 files changed, 435 insertions, 15 deletions
diff --git a/pkg/specgen/generate/namespaces.go b/pkg/specgen/generate/namespaces.go
index 782156663..a2bc37e34 100644
--- a/pkg/specgen/generate/namespaces.go
+++ b/pkg/specgen/generate/namespaces.go
@@ -67,7 +67,7 @@ func GetDefaultNamespaceMode(nsType string, cfg *config.Config, pod *libpod.Pod)
case "cgroup":
return specgen.ParseCgroupNamespace(cfg.Containers.CgroupNS)
case "net":
- ns, _, err := specgen.ParseNetworkNamespace(cfg.Containers.NetNS, cfg.Containers.RootlessNetworking == "cni")
+ ns, _, _, err := specgen.ParseNetworkFlag(nil)
return ns, err
}
@@ -259,6 +259,11 @@ func namespaceOptions(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.
if err != nil {
return nil, err
}
+
+ rtConfig, err := rt.GetConfigNoCopy()
+ if err != nil {
+ return nil, err
+ }
// if no network was specified use add the default
if len(s.Networks) == 0 {
// backwards config still allow the old cni networks list and convert to new format
@@ -271,15 +276,16 @@ func namespaceOptions(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.
s.Networks = networks
} else {
// no networks given but bridge is set so use default network
- rtConfig, err := rt.GetConfigNoCopy()
- if err != nil {
- return nil, err
- }
s.Networks = map[string]types.PerNetworkOptions{
rtConfig.Network.DefaultNetwork: {},
}
}
}
+ // rename the "default" network to the correct default name
+ if opts, ok := s.Networks["default"]; ok {
+ s.Networks[rtConfig.Network.DefaultNetwork] = opts
+ delete(s.Networks, "default")
+ }
toReturn = append(toReturn, libpod.WithNetNS(portMappings, expose, postConfigureNetNS, "bridge", s.Networks))
}
diff --git a/pkg/specgen/namespaces.go b/pkg/specgen/namespaces.go
index 121e1ecf7..15a8ece17 100644
--- a/pkg/specgen/namespaces.go
+++ b/pkg/specgen/namespaces.go
@@ -2,10 +2,12 @@ package specgen
import (
"fmt"
+ "net"
"os"
"strings"
"github.com/containers/common/pkg/cgroups"
+ "github.com/containers/podman/v3/libpod/define"
"github.com/containers/podman/v3/libpod/network/types"
"github.com/containers/podman/v3/pkg/rootless"
"github.com/containers/podman/v3/pkg/util"
@@ -325,21 +327,163 @@ func ParseNetworkNamespace(ns string, rootlessDefaultCNI bool) (Namespace, map[s
return toReturn, networks, nil
}
-func ParseNetworkString(network string) (Namespace, map[string]types.PerNetworkOptions, map[string][]string, error) {
+// ParseNetworkFlag parses a network string slice into the network options
+// If the input is nil or empty it will use the default setting from containers.conf
+func ParseNetworkFlag(networks []string) (Namespace, map[string]types.PerNetworkOptions, map[string][]string, error) {
var networkOptions map[string][]string
- parts := strings.SplitN(network, ":", 2)
+ // by default we try to use the containers.conf setting
+ // if we get at least one value use this instead
+ ns := containerConfig.Containers.NetNS
+ if len(networks) > 0 {
+ ns = networks[0]
+ }
- ns, nets, err := ParseNetworkNamespace(network, containerConfig.Containers.RootlessNetworking == "cni")
- if err != nil {
- return Namespace{}, nil, nil, err
+ toReturn := Namespace{}
+ podmanNetworks := make(map[string]types.PerNetworkOptions)
+
+ switch {
+ case ns == string(Slirp), strings.HasPrefix(ns, string(Slirp)+":"):
+ parts := strings.SplitN(ns, ":", 2)
+ if len(parts) > 1 {
+ networkOptions = make(map[string][]string)
+ networkOptions[parts[0]] = strings.Split(parts[1], ",")
+ }
+ toReturn.NSMode = Slirp
+ case ns == string(FromPod):
+ toReturn.NSMode = FromPod
+ case ns == "" || ns == string(Default) || ns == string(Private):
+ // Net defaults to Slirp on rootless
+ if rootless.IsRootless() && containerConfig.Containers.RootlessNetworking != "cni" {
+ toReturn.NSMode = Slirp
+ break
+ }
+ // if not slirp we use bridge
+ fallthrough
+ case ns == string(Bridge), strings.HasPrefix(ns, string(Bridge)+":"):
+ toReturn.NSMode = Bridge
+ parts := strings.SplitN(ns, ":", 2)
+ netOpts := types.PerNetworkOptions{}
+ if len(parts) > 1 {
+ var err error
+ netOpts, err = parseBridgeNetworkOptions(parts[1])
+ if err != nil {
+ return toReturn, nil, nil, err
+ }
+ }
+ // we have to set the special default network name here
+ podmanNetworks["default"] = netOpts
+
+ case ns == string(NoNetwork):
+ toReturn.NSMode = NoNetwork
+ case ns == string(Host):
+ toReturn.NSMode = Host
+ case strings.HasPrefix(ns, "ns:"):
+ split := strings.SplitN(ns, ":", 2)
+ if len(split) != 2 {
+ return toReturn, nil, nil, errors.Errorf("must provide a path to a namespace when specifying ns:")
+ }
+ toReturn.NSMode = Path
+ toReturn.Value = split[1]
+ case strings.HasPrefix(ns, string(FromContainer)+":"):
+ split := strings.SplitN(ns, ":", 2)
+ if len(split) != 2 {
+ return toReturn, nil, nil, errors.Errorf("must provide name or ID or a container when specifying container:")
+ }
+ toReturn.NSMode = FromContainer
+ toReturn.Value = split[1]
+ default:
+ // we should have a normal network
+ parts := strings.SplitN(ns, ":", 2)
+ if len(parts) == 1 {
+ // Assume we have been given a comma separated list of networks for backwards compat.
+ networkList := strings.Split(ns, ",")
+ for _, net := range networkList {
+ podmanNetworks[net] = types.PerNetworkOptions{}
+ }
+ } else {
+ if parts[0] == "" {
+ return toReturn, nil, nil, errors.New("network name cannot be empty")
+ }
+ netOpts, err := parseBridgeNetworkOptions(parts[1])
+ if err != nil {
+ return toReturn, nil, nil, errors.Wrapf(err, "invalid option for network %s", parts[0])
+ }
+ podmanNetworks[parts[0]] = netOpts
+ }
+
+ // networks need bridge mode
+ toReturn.NSMode = Bridge
}
- if len(parts) > 1 {
- networkOptions = make(map[string][]string)
- networkOptions[parts[0]] = strings.Split(parts[1], ",")
- nets = nil
+ if len(networks) > 1 {
+ if !toReturn.IsBridge() {
+ return toReturn, nil, nil, errors.Wrapf(define.ErrInvalidArg, "cannot set multiple networks without bridge network mode, selected mode %s", toReturn.NSMode)
+ }
+
+ for _, network := range networks[1:] {
+ parts := strings.SplitN(network, ":", 2)
+ if parts[0] == "" {
+ return toReturn, nil, nil, errors.Wrapf(define.ErrInvalidArg, "network name cannot be empty")
+ }
+ if util.StringInSlice(parts[0], []string{string(Bridge), string(Slirp), string(FromPod), string(NoNetwork),
+ string(Default), string(Private), string(Path), string(FromContainer), string(Host)}) {
+ return toReturn, nil, nil, errors.Wrapf(define.ErrInvalidArg, "can only set extra network names, selected mode %s conflicts with bridge", parts[0])
+ }
+ netOpts := types.PerNetworkOptions{}
+ if len(parts) > 1 {
+ var err error
+ netOpts, err = parseBridgeNetworkOptions(parts[1])
+ if err != nil {
+ return toReturn, nil, nil, errors.Wrapf(err, "invalid option for network %s", parts[0])
+ }
+ }
+ podmanNetworks[parts[0]] = netOpts
+ }
+ }
+
+ return toReturn, podmanNetworks, networkOptions, nil
+}
+
+func parseBridgeNetworkOptions(opts string) (types.PerNetworkOptions, error) {
+ netOpts := types.PerNetworkOptions{}
+ if len(opts) == 0 {
+ return netOpts, nil
+ }
+ allopts := strings.Split(opts, ",")
+ for _, opt := range allopts {
+ split := strings.SplitN(opt, "=", 2)
+ switch split[0] {
+ case "ip", "ip6":
+ ip := net.ParseIP(split[1])
+ if ip == nil {
+ return netOpts, errors.Errorf("invalid ip address %q", split[1])
+ }
+ netOpts.StaticIPs = append(netOpts.StaticIPs, ip)
+
+ case "mac":
+ mac, err := net.ParseMAC(split[1])
+ if err != nil {
+ return netOpts, err
+ }
+ netOpts.StaticMAC = types.HardwareAddr(mac)
+
+ case "alias":
+ if split[1] == "" {
+ return netOpts, errors.New("alias cannot be empty")
+ }
+ netOpts.Aliases = append(netOpts.Aliases, split[1])
+
+ case "interface_name":
+ if split[1] == "" {
+ return netOpts, errors.New("interface_name cannot be empty")
+ }
+ netOpts.InterfaceName = split[1]
+
+ default:
+ return netOpts, errors.Errorf("unknown bridge network option: %s", split[0])
+ }
}
- return ns, nets, networkOptions, nil
+ return netOpts, nil
}
func SetupUserNS(idmappings *storage.IDMappingOptions, userns Namespace, g *generate.Generator) (string, error) {
diff --git a/pkg/specgen/namespaces_test.go b/pkg/specgen/namespaces_test.go
new file mode 100644
index 000000000..4f69e6b98
--- /dev/null
+++ b/pkg/specgen/namespaces_test.go
@@ -0,0 +1,265 @@
+package specgen
+
+import (
+ "net"
+ "testing"
+
+ "github.com/containers/podman/v3/libpod/network/types"
+ "github.com/containers/podman/v3/pkg/rootless"
+ "github.com/stretchr/testify/assert"
+)
+
+func parsMacNoErr(mac string) types.HardwareAddr {
+ m, _ := net.ParseMAC(mac)
+ return types.HardwareAddr(m)
+}
+
+func TestParseNetworkFlag(t *testing.T) {
+ // root and rootless have different defaults
+ defaultNetName := "default"
+ defaultNetworks := map[string]types.PerNetworkOptions{
+ defaultNetName: {},
+ }
+ defaultNsMode := Namespace{NSMode: Bridge}
+ if rootless.IsRootless() {
+ defaultNsMode = Namespace{NSMode: Slirp}
+ defaultNetworks = map[string]types.PerNetworkOptions{}
+ }
+
+ tests := []struct {
+ name string
+ args []string
+ nsmode Namespace
+ networks map[string]types.PerNetworkOptions
+ options map[string][]string
+ err string
+ }{
+ {
+ name: "empty input",
+ args: nil,
+ nsmode: defaultNsMode,
+ networks: defaultNetworks,
+ },
+ {
+ name: "empty string as input",
+ args: []string{},
+ nsmode: defaultNsMode,
+ networks: defaultNetworks,
+ },
+ {
+ name: "default mode",
+ args: []string{"default"},
+ nsmode: defaultNsMode,
+ networks: defaultNetworks,
+ },
+ {
+ name: "private mode",
+ args: []string{"private"},
+ nsmode: defaultNsMode,
+ networks: defaultNetworks,
+ },
+ {
+ name: "bridge mode",
+ args: []string{"bridge"},
+ nsmode: Namespace{NSMode: Bridge},
+ networks: map[string]types.PerNetworkOptions{
+ defaultNetName: {},
+ },
+ },
+ {
+ name: "slirp4netns mode",
+ args: []string{"slirp4netns"},
+ nsmode: Namespace{NSMode: Slirp},
+ networks: map[string]types.PerNetworkOptions{},
+ },
+ {
+ name: "from pod mode",
+ args: []string{"pod"},
+ nsmode: Namespace{NSMode: FromPod},
+ networks: map[string]types.PerNetworkOptions{},
+ },
+ {
+ name: "no network mode",
+ args: []string{"none"},
+ nsmode: Namespace{NSMode: NoNetwork},
+ networks: map[string]types.PerNetworkOptions{},
+ },
+ {
+ name: "container mode",
+ args: []string{"container:abc"},
+ nsmode: Namespace{NSMode: FromContainer, Value: "abc"},
+ networks: map[string]types.PerNetworkOptions{},
+ },
+ {
+ name: "ns path mode",
+ args: []string{"ns:/path"},
+ nsmode: Namespace{NSMode: Path, Value: "/path"},
+ networks: map[string]types.PerNetworkOptions{},
+ },
+ {
+ name: "slirp4netns mode with options",
+ args: []string{"slirp4netns:cidr=10.0.0.0/24"},
+ nsmode: Namespace{NSMode: Slirp},
+ networks: map[string]types.PerNetworkOptions{},
+ options: map[string][]string{
+ "slirp4netns": {"cidr=10.0.0.0/24"},
+ },
+ },
+ {
+ name: "bridge mode with options 1",
+ args: []string{"bridge:ip=10.0.0.1,mac=11:22:33:44:55:66"},
+ nsmode: Namespace{NSMode: Bridge},
+ networks: map[string]types.PerNetworkOptions{
+ defaultNetName: {
+ StaticIPs: []net.IP{net.ParseIP("10.0.0.1")},
+ StaticMAC: parsMacNoErr("11:22:33:44:55:66"),
+ },
+ },
+ },
+ {
+ name: "bridge mode with options 2",
+ args: []string{"bridge:ip=10.0.0.1,ip=10.0.0.5"},
+ nsmode: Namespace{NSMode: Bridge},
+ networks: map[string]types.PerNetworkOptions{
+ defaultNetName: {
+ StaticIPs: []net.IP{net.ParseIP("10.0.0.1"), net.ParseIP("10.0.0.5")},
+ },
+ },
+ },
+ {
+ name: "bridge mode with ip6 option",
+ args: []string{"bridge:ip6=fd10::"},
+ nsmode: Namespace{NSMode: Bridge},
+ networks: map[string]types.PerNetworkOptions{
+ defaultNetName: {
+ StaticIPs: []net.IP{net.ParseIP("fd10::")},
+ },
+ },
+ },
+ {
+ name: "bridge mode with alias option",
+ args: []string{"bridge:alias=myname,alias=myname2"},
+ nsmode: Namespace{NSMode: Bridge},
+ networks: map[string]types.PerNetworkOptions{
+ defaultNetName: {
+ Aliases: []string{"myname", "myname2"},
+ },
+ },
+ },
+ {
+ name: "bridge mode with alias option",
+ args: []string{"bridge:alias=myname,alias=myname2"},
+ nsmode: Namespace{NSMode: Bridge},
+ networks: map[string]types.PerNetworkOptions{
+ defaultNetName: {
+ Aliases: []string{"myname", "myname2"},
+ },
+ },
+ },
+ {
+ name: "bridge mode with interface option",
+ args: []string{"bridge:interface_name=eth123"},
+ nsmode: Namespace{NSMode: Bridge},
+ networks: map[string]types.PerNetworkOptions{
+ defaultNetName: {
+ InterfaceName: "eth123",
+ },
+ },
+ },
+ {
+ name: "bridge mode with invalid option",
+ args: []string{"bridge:abc=123"},
+ nsmode: Namespace{NSMode: Bridge},
+ err: "unknown bridge network option: abc",
+ },
+ {
+ name: "bridge mode with invalid ip",
+ args: []string{"bridge:ip=10..1"},
+ nsmode: Namespace{NSMode: Bridge},
+ err: "invalid ip address \"10..1\"",
+ },
+ {
+ name: "bridge mode with invalid mac",
+ args: []string{"bridge:mac=123"},
+ nsmode: Namespace{NSMode: Bridge},
+ err: "address 123: invalid MAC address",
+ },
+ {
+ name: "network name",
+ args: []string{"someName"},
+ nsmode: Namespace{NSMode: Bridge},
+ networks: map[string]types.PerNetworkOptions{
+ "someName": {},
+ },
+ },
+ {
+ name: "network name with options",
+ args: []string{"someName:ip=10.0.0.1"},
+ nsmode: Namespace{NSMode: Bridge},
+ networks: map[string]types.PerNetworkOptions{
+ "someName": {StaticIPs: []net.IP{net.ParseIP("10.0.0.1")}},
+ },
+ },
+ {
+ name: "multiple networks",
+ args: []string{"someName", "net2"},
+ nsmode: Namespace{NSMode: Bridge},
+ networks: map[string]types.PerNetworkOptions{
+ "someName": {},
+ "net2": {},
+ },
+ },
+ {
+ name: "multiple networks with options",
+ args: []string{"someName:ip=10.0.0.1", "net2:ip=10.10.0.1"},
+ nsmode: Namespace{NSMode: Bridge},
+ networks: map[string]types.PerNetworkOptions{
+ "someName": {StaticIPs: []net.IP{net.ParseIP("10.0.0.1")}},
+ "net2": {StaticIPs: []net.IP{net.ParseIP("10.10.0.1")}},
+ },
+ },
+ {
+ name: "multiple networks with bridge mode first should map to default net",
+ args: []string{"bridge", "net2"},
+ nsmode: Namespace{NSMode: Bridge},
+ networks: map[string]types.PerNetworkOptions{
+ defaultNetName: {},
+ "net2": {},
+ },
+ },
+ {
+ name: "conflicting network modes should error",
+ args: []string{"bridge", "host"},
+ nsmode: Namespace{NSMode: Bridge},
+ err: "can only set extra network names, selected mode host conflicts with bridge: invalid argument",
+ },
+ {
+ name: "multiple networks empty name should error",
+ args: []string{"someName", ""},
+ nsmode: Namespace{NSMode: Bridge},
+ err: "network name cannot be empty: invalid argument",
+ },
+ {
+ name: "multiple networks on invalid mode should error",
+ args: []string{"host", "net2"},
+ nsmode: Namespace{NSMode: Host},
+ err: "cannot set multiple networks without bridge network mode, selected mode host: invalid argument",
+ },
+ }
+
+ for _, tt := range tests {
+ tt := tt
+ t.Run(tt.name, func(t *testing.T) {
+ got, got1, got2, err := ParseNetworkFlag(tt.args)
+ if tt.err != "" {
+ assert.EqualError(t, err, tt.err, tt.name)
+ } else {
+ assert.NoError(t, err, tt.name)
+ }
+
+ assert.Equal(t, tt.nsmode, got, tt.name)
+ assert.Equal(t, tt.networks, got1, tt.name)
+ assert.Equal(t, tt.options, got2, tt.name)
+ })
+ }
+}
diff --git a/pkg/specgen/pod_validate.go b/pkg/specgen/pod_validate.go
index 32c1159c6..224a5b12d 100644
--- a/pkg/specgen/pod_validate.go
+++ b/pkg/specgen/pod_validate.go
@@ -42,6 +42,11 @@ func (p *PodSpecGenerator) Validate() error {
if p.NetNS.NSMode != Default && p.NetNS.NSMode != "" {
return errors.New("NoInfra and network modes cannot be used together")
}
+ // Note that networks might be set when --ip or --mac was set
+ // so we need to check that no networks are set without the infra
+ if len(p.Networks) > 0 {
+ return errors.New("cannot set networks options without infra container")
+ }
if len(p.DNSOption) > 0 {
return exclusivePodOptions("NoInfra", "DNSOption")
}