Updated:

This page contains notes from my various tests and experiments. It is a raw record of what I did, without correction for errors, or later update for things that I learn. Use at your own risk.

Experiments reusing the docker bridge with openvpn

I believe the fully using Docker and OpenVPN together will require that I have a single bridge that is used for both OpenVPN as well as docker. Here I’ll document my experiments.

Create a docker bridge with a name

Since I will be needing to manipulate the docker bridge, it should be created with a name. Fortunately Docker supports this. Create a named docker bridge:

docker network create \
  --subnet 10.231.101.0/24 \
  --gateway 10.231.101.10 \
  --ip-range 10.231.101.128/26 \
  --opt com.docker.network.bridge.name=docker_openvpn \
ovpn

The bridge starts down as ip a shows. If I start a simple container though it goes up.

docker --t --rm --net ovpn busybox

It goes down when the container is stopped.

I want to add my ethernet connection eno1 to this bridge. I’ll need to use the brctl command, which comes from bridge-utils

sudo apt install bridge-utils

I know from earlier experiments that routing is going to be an issue. Here’s how routing starts:

kent@ubutower:~$ route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         192.168.0.11    0.0.0.0         UG    100    0        0 eno1
10.231.101.0    0.0.0.0         255.255.255.0   U     0      0        0 docker_openvpn
169.254.0.0     0.0.0.0         255.255.0.0     U     1000   0        0 docker0
172.17.0.0      0.0.0.0         255.255.0.0     U     0      0        0 docker0
192.168.0.0     0.0.0.0         255.255.255.0   U     100    0        0 eno1

I want my ethernet interface eno1 to be in the docker_openvpn bridge.

sudo brctl addif docker_openvpn eno1

Start openvpn on the server, and login locally. I’m using the script from adventures-in-ros2-networking-2

Login to the remote server with ssh ros-openvpn and start openvpn with sudo ovpn_run.

On the desktop, connect to openvpn with sudo openvpn ros_desktop.ovpn That creates a tap0 interface on the desktop. I want to add this to the docker bridge:

sudo brctl addif docker_openvpn tap0

That left issues in my routing table with duplicate routes:

kent@ubutower:~/openvpn$ route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         192.168.0.11    0.0.0.0         UG    20100  0        0 eno1
10.231.101.0    0.0.0.0         255.255.255.0   U     0      0        0 docker_openvpn
10.231.101.0    0.0.0.0         255.255.255.0   U     0      0        0 tap0
169.254.0.0     0.0.0.0         255.255.0.0     U     1000   0        0 docker0
172.17.0.0      0.0.0.0         255.255.0.0     U     0      0        0 docker0
192.168.0.0     0.0.0.0         255.255.255.0   U     100    0        0 eno1

This does not cause any issues yet, but it will. I am going to want to add a route to a robot at some point, and I do not want that to go through tap0 to the cloud, but let the bridge manage routing. So I need to remove the 10.231.101.0 route to tap0.

kent@ubutower:~$ sudo ip route del 10.231.101.0/24 dev tap0
kent@ubutower:~$ route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         192.168.0.11    0.0.0.0         UG    20100  0        0 eno1
10.231.101.0    0.0.0.0         255.255.255.0   U     0      0        0 docker_openvpn
169.254.0.0     0.0.0.0         255.255.0.0     U     1000   0        0 docker0
172.17.0.0      0.0.0.0         255.255.0.0     U     0      0        0 docker0
192.168.0.0     0.0.0.0         255.255.255.0   U     100    0        0 eno1

OK that did it, I can successfully ping 10.231.101.1 from the desktop. But I need to figure out later if there is an openvpn or linux option to prevent that route from being automatically added.

How about ROS2? On the desktop, I created a dds config file bridge-dds to use the bridge:

<?xml version="1.0" encoding="UTF-8" ?>
  <CycloneDDS xmlns="https://cdds.io/config" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:s>
    <Domain id="any">
        <General>
            <NetworkInterfaceAddress>docker_openvpn</NetworkInterfaceAddress>
        </General>
    </Domain>
  </CycloneDDS>

I started a talker on the remote:

$ CYCLONEDDS_URI=file://$PWD/cyclonedds.xml ros2 run demo_nodes_cpp talker

I started a listener on the desktop:

$ CYCLONEDDS_URI=file://$PWD/bridge-dds.xml ros2 run demo_nodes_cpp listener

That worked:

kent@ubutower:~/openvpn$ CYCLONEDDS_URI=file://$PWD/bridge-dds.xml ros2 run demo_nodes_cpp listener
[INFO] [1635277839.214475042] [listener]: I heard: [Hello World: 1]
[INFO] [1635277840.213716849] [listener]: I heard: [Hello World: 2]
[INFO] [1635277841.213704470] [listener]: I heard: [Hello World: 3]

but not the first time that I tried it. When it failed, ping was also failing, so I must have made some error. Need to get this all automated.

ROS2 tests under Docker

The standard ROS2 desktop image osrf/ros:galactic-desktop does not have all of the networking utilities installed, so let me create a basic Dockerfile with this.

The Dockerfile:

FROM osrf/ros:galactic-desktop

# install ros packages
RUN apt-get update

# extra stuff
RUN apt-get install -y --no-install-recommends \
  iproute2 \
  bridge-utils \
  iputils-ping \
  dnsutils \
  nano \
  less

On the desktop, build it, and start it:

docker build -t 'rosnet' .
docker run -it --rm --net ovpn rosnet bash

Now I am going to start two listeners. One is run on the desktop directly:

kent@ubutower:~/openvpn$ CYCLONEDDS_URI=file://$PWD/bridge-dds.xml ros2 run demo_nodes_cpp listener

A second is run on the openvpn gateway:

ubuntu@openvpn:~$ CYCLONEDDS_URI=file://$PWD/cyclonedds.xml ros2 run demo_nodes_cpp listener

Now I’ll start a docker container with ROS2 on the desktop

kent@ubutower:~/openvpn$ dr --net ovpn -h rosnet rosnet bash

We’ll run a talker here. The docker container only has a single interface, so we don’t need to configure DDS.

root@rosnet:/# ros2 run demo_nodes_cpp talker
[INFO] [1635280872.456529455] [talker]: Publishing: 'Hello World: 1'
[INFO] [1635280873.456487733] [talker]: Publishing: 'Hello World: 2'
[INFO] [1635280874.456404643] [talker]: Publishing: 'Hello World: 3'

Hmmm, it worked the first time I tried it, but not this time when I tried to document. I see that the link seems to be restarting due to inactivity. Perhaps that is causing some issues. OK the desktop has lost its tap0 interface, and the terminal where I was running the openvpn server is dead. Let me instead run that as a daemon using systemctl. On the remote:

sudo systemctl start openvpn@openvpn
sudo journalctl -f --unit openvpn@openvpn

Connect to openvpn on the desktop:

sudo openvpn ros_desktop.ovpn 

Now I have to repeat the routing and bridging fixes on the desktop

sudo brctl addif docker_openvpn tap0
sudo ip route del 10.231.101.0/24 dev tap0

Now if I run listeners on the desktop and on the remote, they hear the talker that is being run from the desktop Docker.

I installed openssh-server on the ros Docker. Tried to start with systemctl, it complained. I guess that image does not use it! Swell. Started from init.d, worked. Could connect but not login as root. I probably need to setup another user with keys.

I’m going to redo the Docker file as follows:

FROM ros:galactic-ros-base-focal

# install ros packages
RUN apt-get update

# ROS for demo
RUN apt-get install -y ros-galactic-demo-nodes-cpp

# extra stuff
RUN apt-get install -y --no-install-recommends \
  iproute2 \
  bridge-utils \
  iputils-ping \
  dnsutils \
  nano \
  less

I tried that, it also does not seem to use systemd. Well I see all of the systemd stuff, but no systemctl. I googled a bit, and I guess that is by design.

Support Mechanisms to do my Network Munging

Get Network Manager to leave ethernet connection in bridge

I confirmed through logging that a script at kent@ubutower:/etc/NetworkManager/dispatcher.d/pre-up.d runs when the ethernet interface comes up. How about openvpn? … Yes!

So what runs when openvpn starts and stops on the client? Checking the manual, here are some interesting options:

  • –up cmd (Run command cmd after successful TUN/TAP device open (pre –user UID change).)
  • –route-pre-down (Executed right before the routes are removed.)

”–ifconfig-noexec” prevents an IP address for tun0, and stops the route being added, but the route is started down. I will need a script to add it to thebridge, and set ip up. When I do that, ros2 demo nodes communicate. Let me see if I can automate all that through the config.

I modified the .ovpn script for the client, and it does all that is needed (does not add route, adds tun0 to bridge) with this preamble prior to the key:

client
nobind
dev tap
remote-cert-tls server
ifconfig-noexec
persist-tun
script-security 2
route-up "/bin/bash -c 'brctl addif docker_openvpn tap0 && ip link set tap0 up'"
remote 44.232.53.80 1194 udp

I need to modify the server config instructions to make this work automatic.


References:

https://blog.oddbit.com/post/2018-03-12-using-docker-macvlan-networks/