Skip to content

Commit

Permalink
support advanced network configuration via cli
Browse files Browse the repository at this point in the history
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 containers#11534

Signed-off-by: Paul Holzinger <[email protected]>
  • Loading branch information
Luap99 committed Dec 14, 2021
1 parent d072167 commit 5358184
Show file tree
Hide file tree
Showing 17 changed files with 644 additions and 120 deletions.
9 changes: 1 addition & 8 deletions cmd/podman/common/create_opts.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,18 +156,11 @@ func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig, rtc *c
}

// netMode
nsmode, networks, err := specgen.ParseNetworkNamespace(string(cc.HostConfig.NetworkMode), true)
nsmode, networks, netOpts, err := specgen.ParseNetworkFlag([]string{string(cc.HostConfig.NetworkMode)})
if err != nil {
return nil, nil, err
}

var netOpts map[string][]string
parts := strings.SplitN(string(cc.HostConfig.NetworkMode), ":", 2)
if len(parts) > 1 {
netOpts = make(map[string][]string)
netOpts[parts[0]] = strings.Split(parts[1], ",")
}

// network
// Note: we cannot emulate compat exactly here. we only allow specifics of networks to be
// defined when there is only one network.
Expand Down
131 changes: 75 additions & 56 deletions cmd/podman/common/netflags.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ func DefineNetFlags(cmd *cobra.Command) {
_ = cmd.RegisterFlagCompletionFunc(macAddressFlagName, completion.AutocompleteNone)

networkFlagName := "network"
netFlags.String(
networkFlagName, containerConfig.NetNS(),
netFlags.StringArray(
networkFlagName, nil,
"Connect a container to a network",
)
_ = cmd.RegisterFlagCompletionFunc(networkFlagName, AutocompleteNetworkFlag)
Expand All @@ -88,9 +88,7 @@ func DefineNetFlags(cmd *cobra.Command) {
}

// NetFlagsToNetOptions parses the network flags for the given cmd.
// The netnsFromConfig bool is used to indicate if the --network flag
// should always be parsed regardless if it was set on the cli.
func NetFlagsToNetOptions(opts *entities.NetOptions, flags pflag.FlagSet, netnsFromConfig bool) (*entities.NetOptions, error) {
func NetFlagsToNetOptions(opts *entities.NetOptions, flags pflag.FlagSet) (*entities.NetOptions, error) {
var (
err error
)
Expand Down Expand Up @@ -168,79 +166,100 @@ func NetFlagsToNetOptions(opts *entities.NetOptions, flags pflag.FlagSet, netnsF
return nil, err
}

// parse the --network value only when the flag is set or we need to use
// the netns config value, e.g. when --pod is not used
if netnsFromConfig || flags.Changed("network") {
network, err := flags.GetString("network")
// parse the network only when network was changed
// otherwise we send default to server so that the server
// can pick the correct default instead of the client
if flags.Changed("network") {
network, err := flags.GetStringArray("network")
if err != nil {
return nil, err
}

ns, networks, options, err := specgen.ParseNetworkString(network)
ns, networks, options, err := specgen.ParseNetworkFlag(network)
if err != nil {
return nil, err
}

if len(options) > 0 {
opts.NetworkOptions = options
}
opts.NetworkOptions = options
opts.Network = ns
opts.Networks = networks
}

ip, err := flags.GetString("ip")
if err != nil {
return nil, err
}
if ip != "" {
staticIP := net.ParseIP(ip)
if staticIP == nil {
return nil, errors.Errorf("%s is not an ip address", ip)
}
if !opts.Network.IsBridge() {
return nil, errors.Wrap(define.ErrInvalidArg, "--ip can only be set when the network mode is bridge")
}
if len(opts.Networks) != 1 {
return nil, errors.Wrap(define.ErrInvalidArg, "--ip can only be set for a single network")
}
for name, netOpts := range opts.Networks {
netOpts.StaticIPs = append(netOpts.StaticIPs, staticIP)
opts.Networks[name] = netOpts
if flags.Changed("ip") || flags.Changed("mac-address") || flags.Changed("network-alias") {
// if there is no network we add the default
if len(opts.Networks) == 0 {
opts.Networks = map[string]types.PerNetworkOptions{
"default": {},
}
}
}

m, err := flags.GetString("mac-address")
if err != nil {
return nil, err
}
if len(m) > 0 {
mac, err := net.ParseMAC(m)
ip, err := flags.GetString("ip")
if err != nil {
return nil, err
}
if !opts.Network.IsBridge() {
return nil, errors.Wrap(define.ErrInvalidArg, "--mac-address can only be set when the network mode is bridge")
if ip != "" {
// if pod create --infra=false
if infra, err := flags.GetBool("infra"); err == nil && !infra {
return nil, errors.Wrap(define.ErrInvalidArg, "cannot set --ip without infra container")
}

staticIP := net.ParseIP(ip)
if staticIP == nil {
return nil, errors.Errorf("%s is not an ip address", ip)
}
if !opts.Network.IsBridge() && !opts.Network.IsDefault() {
return nil, errors.Wrap(define.ErrInvalidArg, "--ip can only be set when the network mode is bridge")
}
if len(opts.Networks) != 1 {
return nil, errors.Wrap(define.ErrInvalidArg, "--ip can only be set for a single network")
}
for name, netOpts := range opts.Networks {
netOpts.StaticIPs = append(netOpts.StaticIPs, staticIP)
opts.Networks[name] = netOpts
}
}
if len(opts.Networks) != 1 {
return nil, errors.Wrap(define.ErrInvalidArg, "--mac-address can only be set for a single network")

m, err := flags.GetString("mac-address")
if err != nil {
return nil, err
}
for name, netOpts := range opts.Networks {
netOpts.StaticMAC = types.HardwareAddr(mac)
opts.Networks[name] = netOpts
if len(m) > 0 {
// if pod create --infra=false
if infra, err := flags.GetBool("infra"); err == nil && !infra {
return nil, errors.Wrap(define.ErrInvalidArg, "cannot set --mac without infra container")
}
mac, err := net.ParseMAC(m)
if err != nil {
return nil, err
}
if !opts.Network.IsBridge() && !opts.Network.IsDefault() {
return nil, errors.Wrap(define.ErrInvalidArg, "--mac-address can only be set when the network mode is bridge")
}
if len(opts.Networks) != 1 {
return nil, errors.Wrap(define.ErrInvalidArg, "--mac-address can only be set for a single network")
}
for name, netOpts := range opts.Networks {
netOpts.StaticMAC = types.HardwareAddr(mac)
opts.Networks[name] = netOpts
}
}
}

aliases, err := flags.GetStringSlice("network-alias")
if err != nil {
return nil, err
}
if len(aliases) > 0 {
if !opts.Network.IsBridge() {
return nil, errors.Wrap(define.ErrInvalidArg, "--network-alias can only be set when the network mode is bridge")
aliases, err := flags.GetStringSlice("network-alias")
if err != nil {
return nil, err
}
for name, netOpts := range opts.Networks {
netOpts.Aliases = aliases
opts.Networks[name] = netOpts
if len(aliases) > 0 {
// if pod create --infra=false
if infra, err := flags.GetBool("infra"); err == nil && !infra {
return nil, errors.Wrap(define.ErrInvalidArg, "cannot set --network-alias without infra container")
}
if !opts.Network.IsBridge() && !opts.Network.IsDefault() {
return nil, errors.Wrap(define.ErrInvalidArg, "--network-alias can only be set when the network mode is bridge")
}
for name, netOpts := range opts.Networks {
netOpts.Aliases = aliases
opts.Networks[name] = netOpts
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion cmd/podman/containers/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ func create(cmd *cobra.Command, args []string) error {
err error
)
flags := cmd.Flags()
cliVals.Net, err = common.NetFlagsToNetOptions(nil, *flags, cliVals.Pod == "" && cliVals.PodIDFile == "")
cliVals.Net, err = common.NetFlagsToNetOptions(nil, *flags)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/podman/containers/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ func run(cmd *cobra.Command, args []string) error {
}

flags := cmd.Flags()
cliVals.Net, err = common.NetFlagsToNetOptions(nil, *flags, cliVals.Pod == "" && cliVals.PodIDFile == "")
cliVals.Net, err = common.NetFlagsToNetOptions(nil, *flags)
if err != nil {
return err
}
Expand Down
4 changes: 2 additions & 2 deletions cmd/podman/pods/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ func create(cmd *cobra.Command, args []string) error {
return fmt.Errorf("cannot specify no-hosts without an infra container")
}
flags := cmd.Flags()
createOptions.Net, err = common.NetFlagsToNetOptions(nil, *flags, createOptions.Infra)
createOptions.Net, err = common.NetFlagsToNetOptions(nil, *flags)
if err != nil {
return err
}
Expand All @@ -133,7 +133,7 @@ func create(cmd *cobra.Command, args []string) error {
} else {
// reassign certain options for lbpod api, these need to be populated in spec
flags := cmd.Flags()
infraOptions.Net, err = common.NetFlagsToNetOptions(nil, *flags, createOptions.Infra)
infraOptions.Net, err = common.NetFlagsToNetOptions(nil, *flags)
if err != nil {
return err
}
Expand Down
38 changes: 30 additions & 8 deletions docs/source/markdown/podman-create.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -476,9 +476,12 @@ Not implemented
#### **--ip**=*ip*

Specify a static IP address for the container, for example **10.88.64.128**.
This option can only be used if the container is joined to only a single network - i.e., `--network=_network-name_` is used at most once -
and if the container is not joining another container's network namespace via `--network=container:_id_`.
The address must be within the CNI network's IP address pool (default **10.88.0.0/16**).
This option can only be used if the container is joined to only a single network - i.e., **--network=network-name** is used at most once -
and if the container is not joining another container's network namespace via **--network=container:_id_**.
The address must be within the network's IP address pool (default **10.88.0.0/16**).

To specify multiple static IP addresses per container, set multiple networks using the **--network** option with a static IP address specified for each using the `ip` mode for that option.


#### **--ipc**=*ipc*

Expand Down Expand Up @@ -531,12 +534,16 @@ This option is currently supported only by the **journald** log driver.

#### **--mac-address**=*address*

Container MAC address (e.g. 92:d0:c6:0a:29:33)
Container network interface MAC address (e.g. 92:d0:c6:0a:29:33)
This option can only be used if the container is joined to only a single network - i.e., **--network=_network-name_** is used at most once -
and if the container is not joining another container's network namespace via **--network=container:_id_**.

Remember that the MAC address in an Ethernet network must be unique.
The IPv6 link-local address will be based on the device's MAC address
according to RFC4862.

To specify multiple static MAC addresses per container, set multiple networks using the **--network** option with a static MAC address specified for each using the `mac` mode for that option.

#### **--memory**, **-m**=*limit*

Memory limit (format: `<number>[<unit>]`, where unit = b (bytes), k (kilobytes), m (megabytes), or g (gigabytes))
Expand Down Expand Up @@ -668,15 +675,22 @@ This works for both background and foreground containers.

#### **--network**=*mode*, **--net**

Set the network mode for the container. Invalid if using **--dns**, **--dns-opt**, or **--dns-search** with **--network** that is set to **none** or **container:**_id_. If used together with **--pod**, the container will not join the pod's network namespace.
Set the network mode for the container. Invalid if using **--dns**, **--dns-opt**, or **--dns-search** with **--network** set to **none** or **container:**_id_. If used together with **--pod**, the container will not join the pod's network namespace.

Valid _mode_ values are:

- **bridge**: Create a network stack on the default bridge. This is the default for rootfull containers.
- **bridge[:OPTIONS,...]**: Create a network stack on the default bridge. This is the default for rootfull containers. It is possible to specify these additional options:
- **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.

For example to set a static ipv4 address and a static mac address, use `--network bridge:ip=10.88.0.10,mac=44:33:22:11:00:99`.
- \<network name or ID\>[:OPTIONS,...]: Connect to a user-defined network; this is the network name or ID from a network created by **[podman network create](podman-network-create.1.md)**. Using the network name implies the bridge network mode. It is possible to specify the same options described under the bridge mode above. You can use the **--network** option multiple times to specify additional networks.
- **none**: Create a network namespace for the container but do not configure network interfaces for it, thus the container has no network connectivity.
- **container:**_id_: Reuse another container's network stack.
- **host**: Do not create a network namespace, the container will use the host's network. Note: The host mode gives the container full access to local system services such as D-bus and is therefore considered insecure.
- **network**: Connect to a user-defined network, multiple networks should be comma-separated.
- **ns:**_path_: Path to a network namespace to join.
- **private**: Create a new namespace for the container. This will use the **bridge** mode for rootfull containers and **slirp4netns** for rootless ones.
- **slirp4netns[:OPTIONS,...]**: use **slirp4netns**(1) to create a user network stack. This is the default for rootless containers. It is possible to specify these additional options:
Expand All @@ -694,7 +708,9 @@ Valid _mode_ values are:

#### **--network-alias**=*alias*

Add network-scoped alias for the container. NOTE: A container will only have access to aliases on the first network that it joins. This is a limitation that will be removed in a later release.
Add a network-scoped alias for the container, setting the alias for all networks that the container joins. To set a name only for a specific network, use the alias option as described under the **--network** option.
Network aliases work only with the bridge networking mode. This option can be specified multiple times.
NOTE: A container will only have access to aliases on the first network that it joins. This is a limitation that will be removed in a later release.

#### **--no-healthcheck**

Expand Down Expand Up @@ -1492,6 +1508,12 @@ $ podman create --name container1 --personaity=LINUX32 fedora bash
$ podman create --name container1 --rootfs /path/to/rootfs:O bash
```

### Create a container connected to two networks (called net1 and net2) with a static ip

```
$ podman create --network net1:ip=10.89.1.5 --network net2:ip=10.89.10.10 alpine ip addr
```

### Rootless Containers

Podman runs as a non root user on most systems. This feature requires that a new enough version of shadow-utils
Expand Down
Loading

0 comments on commit 5358184

Please sign in to comment.