Podman Multi-network

Podman Multi-network

Posted on March 20, 2024

Podman Multi-network

With the release of podman 4.0 the networking backend has be rewritten in rust. Netavark and Ardavark are two tools that have been written to replace the CNI based networking stack in older versions of podman.

One of the wonderful things that netavark supports is multiple networks. This means we can map multiple networks into a container. This is exterememly useful in telecommunications and data center infrastructure where the need to connect to multiple networks becomes a nessesity.

Lets explore how this works.

I have podman 4.4 installed in a RHEL 9 virtual machgine with two networks connected.

[root@localhost ~] ip addr sho
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 52:54:00:32:dc:af brd ff:ff:ff:ff:ff:ff
    inet 10.10.100.50/24 brd 10.10.100.255 scope global dynamic noprefixroute enp1s0
       valid_lft 599sec preferred_lft 599sec
    inet6 fe80::5054:ff:fe32:dcaf/64 scope link noprefixroute
       valid_lft forever preferred_lft forever
3: enp7s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 52:54:00:d1:b4:52 brd ff:ff:ff:ff:ff:ff
    inet 10.10.110.80/24 brd 10.10.110.255 scope global dynamic noprefixroute enp7s0
       valid_lft 572sec preferred_lft 572sec
    inet6 fe80::418:4b71:25f:9b90/64 scope link noprefixroute
       valid_lft forever preferred_lft forever
5: veth2b2dc630@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master podman0 state UP group default qlen 1000
    link/ether ee:67:39:7a:ee:44 brd ff:ff:ff:ff:ff:ff link-netns netns-cb6c78e6-ae7d-0d5b-deca-ca98142c5342
    inet6 fe80::ec67:39ff:fe7a:ee44/64 scope link
       valid_lft forever preferred_lft forever
7: podman0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether ee:67:39:7a:ee:44 brd ff:ff:ff:ff:ff:ff
    inet 10.88.0.1/16 brd 10.88.255.255 scope global podman0
       valid_lft forever preferred_lft forever
    inet6 fe80::8a1:b6ff:fef7:6c16/64 scope link
       valid_lft forever preferred_lft forever

enp1s0 and enp7s0 are the interfaces connected to each different network. The podman0 network is a bridge that is installed by deafult when podman is installed.

First lets see what networks podman can see.

[root@localhost ~] podman network ls
NETWORK ID    NAME        DRIVER
2f259bab93aa  podman      bridge

Let's create two networks in podman and link them to the interfaces the server has.

podman network create -d macvlan -o parent=enp1s0 --subnet 10.10.100.0/24 netone
podman network create -d macvlan -o parent=enp7s0 --subnet 10.10.110.0/24 nettwo

We can see the networks have been created with podman network ls

[root@localhost ~]# podman network ls
NETWORK ID    NAME        DRIVER
fdd5ce0ff2be  netone      macvlan
4deff6eacc13  nettwo      macvlan
2f259bab93aa  podman      bridge

So let's get a container up and running and connect the networks.

[root@localhost ~] podman run -dt ubi9
[root@localhost ~] podman ps
CONTAINER ID  IMAGE                  COMMAND     CREATED        STATUS            PORTS       NAMES
0a05716b8f5c  localhost/ubi9:latest  /bin/bash   4 seconds ago  Up 3 seconds ago              hungry_chaplygin
[root@localhost ~] podman network connect --ip 10.10.110.81 nettwo hungry_chaplygin
[root@localhost ~] podman network connect --ip 10.10.100.81 netone hungry_chaplygin

If we inspect the conatiner we can see it has three network attachments. One for podman, one for netone, and one for nettwo ( I have trunkcated the output to show only the network componants. )

[root@localhost ~]# podman inspect hungry_chaplygin
[
     {
          "Id": "0a05716b8f5c0b3e6959db1aa1f36aa7f12acb407199c1b1f9aba5e2184b078a",
          "Created": "2023-03-22T22:36:09.298901801-04:00",
          "Path": "/bin/bash",
          "Args": [
               "/bin/bash"
          ],
          "State": {
               "OciVersion": "1.0.2-dev",
               "Status": "running",
               "Running": true,
               "Paused": false,
               "Restarting": false,
               "OOMKilled": false,
               "Dead": false,
               "Pid": 4488,
               "ConmonPid": 4483,
               "ExitCode": 0,
               "Error": "",
               "StartedAt": "2023-03-22T22:36:10.040751789-04:00",
               "FinishedAt": "0001-01-01T00:00:00Z",
               "Health": {
                    "Status": "",
                    "FailingStreak": 0,
                    "Log": null
               },
               "CgroupPath": "/machine.slice/libpod-0a05716b8f5c0b3e6959db1aa1f36aa7f12acb407199c1b1f9aba5e2184b078a.scope",
               "CheckpointedAt": "0001-01-01T00:00:00Z",
               "RestoredAt": "0001-01-01T00:00:00Z"
               }
          },
          "Mounts": [],
          "Dependencies": [],
          "NetworkSettings": {
               "EndpointID": "",
               "Gateway": "",
               "IPAddress": "",
               "IPPrefixLen": 0,
               "IPv6Gateway": "",
               "GlobalIPv6Address": "",
               "GlobalIPv6PrefixLen": 0,
               "MacAddress": "",
               "Bridge": "",
               "SandboxID": "",
               "HairpinMode": false,
               "LinkLocalIPv6Address": "",
               "LinkLocalIPv6PrefixLen": 0,
               "Ports": {
                    "80/tcp": null
               },
               "SandboxKey": "/run/netns/netns-deaff2ea-53a0-013b-8749-d63d8947a135",
               "Networks": {
                    "netone": {
                         "EndpointID": "",
                         "Gateway": "10.10.100.1",
                         "IPAddress": "10.10.100.81",
                         "IPPrefixLen": 24,
                         "IPv6Gateway": "",
                         "GlobalIPv6Address": "",
                         "GlobalIPv6PrefixLen": 0,
                         "MacAddress": "12:b9:90:dd:5b:91",
                         "NetworkID": "netone",
                         "DriverOpts": null,
                         "IPAMConfig": null,
                         "Links": null,
                         "Aliases": [
                              "0a05716b8f5c"
                         ]
                    },
                    "newnet": {
                         "EndpointID": "",
                         "Gateway": "10.10.110.1",
                         "IPAddress": "10.10.110.81",
                         "IPPrefixLen": 24,
                         "IPv6Gateway": "",
                         "GlobalIPv6Address": "",
                         "GlobalIPv6PrefixLen": 0,
                         "MacAddress": "c2:85:6d:55:c5:d9",
                         "NetworkID": "newnet",
                         "DriverOpts": null,
                         "IPAMConfig": null,
                         "Links": null,
                         "Aliases": [
                              "0a05716b8f5c"
                         ]
                    },
                    "podman": {
                         "EndpointID": "",
                         "Gateway": "10.88.0.1",
                         "IPAddress": "10.88.0.3",
                         "IPPrefixLen": 16,
                         "IPv6Gateway": "",
                         "GlobalIPv6Address": "",
                         "GlobalIPv6PrefixLen": 0,
                         "MacAddress": "8a:69:f4:5d:61:57",
                         "NetworkID": "podman",
                         "DriverOpts": null,
                         "IPAMConfig": null,
                         "Links": null,
                         "Aliases": [
                              "0a05716b8f5c"
                         ]
                    }
               }
          },


So let's do some testing.

[root@localhost ~]# podman exec -it hungry_chaplygin bash

I have installed iputils so I can use ping to test some network connectivity.

[root@0a05716b8f5c /] dnf install iputils -y

[root@0a05716b8f5c /]# ping 10.10.100.1
PING 10.10.100.1 (10.10.100.1) 56(84) bytes of data.
64 bytes from 10.10.100.1: icmp_seq=1 ttl=64 time=0.872 ms
64 bytes from 10.10.100.1: icmp_seq=2 ttl=64 time=0.581 ms

[root@0a05716b8f5c /]# ping 10.10.110.1
PING 10.10.110.1 (10.10.110.1) 56(84) bytes of data.
64 bytes from 10.10.110.1: icmp_seq=1 ttl=64 time=0.575 ms

As you can see I can ping the gateway for both networks.

One thing to note here is if you look at iptables you will see a rule in for netavark to allow traffic through it. This is implemented when you create the network in podman and so far it only works with iptables.

[root@localhost ~]# iptables -L
Chain INPUT (policy ACCEPT)
target     prot opt source               destination

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination
NETAVARK_FORWARD  all  --  anywhere             anywhere             /* netavark firewall plugin rules */

So that is a pretty great feature that will help if you need to connect a container with podman to multiple networks.