The husarnet/ros2router
Docker image is designed to effortlessly bridge local ROS 2 nodes, even those with standard DDS settings, to nodes on different machines across various networks. It runs seamlessly with the Husarnet VPN, ensuring that neither distance nor network differences become obstacles in your ROS 2 projects.
Compatible with both natively-executed ROS 2 nodes and those operating within Docker.
Based on DDS Router project by eProsima.
-
Run ROS 2 Nodes on Two Machines:
Whether they're in the same LAN or different ones, execute the following on your first machine:
ROS_LOCALHOST_ONLY=1 ros2 run demo_nodes_cpp listener
And on your second machine:
ROS_LOCALHOST_ONLY=1 ros2 run demo_nodes_cpp talker
-
Husarnet Account Setup
- Register for a free account on app.husarnet.com.
- Establish a new Husarnet network in the Online Dashboard.
- Click the [Add element] button and copy the code under the Join Code tab.
-
Connect Devices to Husarnet:
If you're on Ubuntu, follow these steps:
- Install Husarnet:
curl https://install.husarnet.com/install.sh | sudo bash
- Connect to the Husarnet network:
sudo husarnet join <paste-join-code-here>
-
Start the DDS Router Docker Image:
Launch the following command on each host:
docker run \ --detach \ --restart=unless-stopped \ --network host \ husarnet/ros2router:1.4.0
-
Verify Connection:
Open the terminal for the
listener
. You should start seeing incoming messages.
This guide provides a straightforward setup. Dive deeper and explore additional options and examples in the sections below.
The all-in-one example, that should be fine in most cases (both with ROS 2 nodes running on host and in Docker containers) is in the demo/auto/general
directory.
services:
ros2router:
image: husarnet/ros2router:1.8.0
network_mode: host
ipc: shareable
volumes:
- ./filter.yaml:/filter.yaml
environment:
- DISCOVERY_SERVER_ID=2
- DISCOVERY_SERVER_LISTENING_PORT=8888
- PARTICIPANTS=husarnet,shm
- |
CONFIG_BASE=
version: v4.0
specs:
discovery-trigger: writer
participants:
- name: LocalParticipant
kind: local
domain: 0
transport: builtin
- ROS_DOMAIN_ID=123
talker:
image: husarion/ros2-demo-nodes:humble
ipc: service:ros2router
network_mode: service:ros2router
volumes:
- ./shm-only.xml:/shm-only.xml
environment:
- FASTRTPS_DEFAULT_PROFILES_FILE=/shm-only.xml
- ROS_DOMAIN_ID=123
command: ros2 run demo_nodes_cpp talker
# On the same host to connect to LocalParticipant, just execute:
# export ROS_LOCAHOST_ONLY=1
# ros2 run demo_nodes_cpp talker
# On the other host, to listen to /chatter topic:
#
# Option 1: ROS 2 Iron (assuming "laptop" is the husarnet hostname of the host running ros2router):
# export ROS_DISCOVERY_SERVER=;;laptop:8888
# ros2 run demo_nodes_cpp listener
#
# Option 2: ROS 2 Humble:
# Modify superclient.xml to point to the host with ros2router (line 31)
# export FASTRTPS_DEFAULT_PROFILES_FILE=${PWD}/superclient.xml
# ros2 run demo_nodes_cpp listener
env | default value | description |
---|---|---|
PARTICIPANTS |
husarnet,shm,lo |
The coma separated list of paticipants in the ROS 2 Router configuration. Possible values are: husarnet , shm , lo , lan , if-<if-name-optional-wildcard> . If unset of empty you need to use a custom DDS router configuration file |
ROS_DISCOVERY_SERVER |
If set the HusarnetParticipant will work in the Disocovery Server setup. Set it to one of the following formats: <husarnet-ipv6-addr>:<discovery-server-port> or <husarnet-hostname>:<discovery-server-port> to connect as the Client to the device acting as a Discovery Server. To specify multiple addresses, use semicolons as separators. The server's ID is determined by its position in the list (starting from 0 ). If there's an empty space between semicolons, it indicates that the respective ID is available. Eg. ROS_DISCOVERY_SERVER=";;abc:123;;;def:456" means that the ID of abc:123 is 2 and ID of def is 5 . If one of the addresses is the same as the hosts Husarnet IPv6 address, then the Discovery Server - Server config is launch on the host. |
|
DISCOVERY_SERVER_ID |
-1 |
The ID of the local Discovery Server. If the server config is launched (one of the addresses specified in ROS_DISCOVERY_SERVER is the same as host's Husarnet address), the value of DISCOVERY_SERVER_ID env is ignored and the ID from ROS_DISCOVERY_SERVER env is used. If the value is outside of the 0-255 range, then the random value (from 10-255 range) is used instead |
ROS_DOMAIN_ID |
If set it changes the default domain: 0 for all participants with kind: local (basically all instead of HusarnetParticipant working in the Discovery Server config) |
|
EXIT_IF_HOST_TABLE_CHANGED |
FALSE |
Valid only if ROS_DISCOVERY_SERVER envs is unset and thus starting the Initial Peers config. This env is useful in connection with restart: always Docker policy - it restarts the DDS Router with a new Initial Peers list applied (the Initial Peers list is not updated by the DDS Router in runtime) |
USER |
root |
Allowing you to run the DDS Router as a different user (useful to enable SHM communication between host and Docker container) |
HUSARNET_API_HOST |
127.0.0.1 |
The IPv4 address where Husarnet Daemon is running (If using a different address than 127.0.0.1 remember also to run the Husarnet Daemon with a HUSARNET_DAEMON_API_INTERFACE env setup ) |
CONFIG_BASE |
providing some optional, extra config to the ROS 2 Router | |
FILTER |
It's alternative to providing /filter.yaml as a volume |
The Docker image for the Husarnet ROS 2 Router can accept a portion of the typical ROS 2 Router configuration *.yaml
file. This segment only includes the allowlist
, blocklist
, and builtin-topics
sections. The provided configuration is then integrated with the automatically generated config file content.
Here's a sample of the filter.yaml
file:
Example of the filter.yaml
file:
allowlist:
- name: "rt/camera/color/image_raw/theora"
type: "theora_image_transport::msg::dds_::Packet_"
- name: "rt/camera/color/image_raw/compressed"
type: "sensor_msgs::msg::dds_::CompressedImage_"
- name: "rt/cmd_vel"
type: "geometry_msgs::msg::dds_::Twist_"
blocklist: []
builtin-topics: []
Note that each ROS 2 topic name is preceded with rX/
prefix (more here):
ROS Subsystem | Prefix |
---|---|
ROS Topics | rt |
ROS Service Request | rq |
ROS Service Response | rr |
ROS Service | rs |
ROS Parameter | rp |
ROS Action | ra |
The repository includes the create_filter.sh
script, which facilitates the automated creation of DDS Router filter rules:
curl -s https://raw.githubusercontent.com/husarnet/husarnet-ros2router/main/create_filter.sh > create_filter.sh
chmod +x create_filter.sh
./create_filter.sh /chatter /cmd_vel > filter.yaml
Modify the filter.yaml
file if needed and assign it as a bind mount volume:
docker run \
--detach \
--restart=unless-stopped \
--network host \
-e ROS_DOMAIN_ID \
-v $(pwd)/filter.yaml:/filter.yaml \
husarnet/ros2router:1.2.0
-
Connect both hosts to the same Husarnet network (eg. named
host_a
andhost_b
). -
On both
host_a
andhost_b
execute:
docker run \
--detach \
--restart=unless-stopped \
--network host \
-e ROS_DOMAIN_ID \
husarnet/ros2router:1.2.0
- Start a chatter demo:
- on the
host_a
:
export ROS_LOCALHOST_ONLY=1 # is required to connect with a LocalUdpParticipant
ros2 run demo_nodes_cpp talker
- on the
host_b
:
export ROS_LOCALHOST_ONLY=1 # is required to connect with a LocalUdpParticipant
ros2 run demo_nodes_cpp listener
-
Connect both hosts to the same Husarnet network (eg. named
host_a
andhost_b
). -
Execute on
host_a
:
docker run \
--detach \
--restart=unless-stopped \
--network host \
-e DISCOVERY_SERVER_PORT="11888" \
husarnet/ros2router:1.2.0
- Execute on
host_b
:
docker run \
--detach \
--restart=unless-stopped \
--network host \
-e ROS_DISCOVERY_SERVER="host_a:11888" \
husarnet/ros2router:1.2.0
- Start a chatter demo:
- on the
host_a
:
export ROS_LOCALHOST_ONLY=1 # is required to connect with a LocalUdpParticipant
ros2 run demo_nodes_cpp talker
- on the
host_b
:
export ROS_LOCALHOST_ONLY=1 # is required to connect with a LocalUdpParticipant
ros2 run demo_nodes_cpp listener
-
Connect both hosts to the same Husarnet network (eg. named
host_a
andhost_b
). -
Create a DDS Router config file on the
host_a
:
user@host_a:~$ vim config.yaml
with the following content:
version: v3.0
allowlist:
- name: "rt/chatter"
type: "std_msgs::msg::dds_::String_"
participants:
- name: SimpleParticipant
kind: local
domain: 0
- name: ServerDSParticipant
kind: local-discovery-server
discovery-server-guid:
id: 200
listening-addresses:
- domain: host_a
port: 11811
transport: udp
- Create a DDS Router config file on the
host_b
:
user@host_b:~$ vim config.yaml
with the following content:
version: v3.0
allowlist:
- name: "rt/chatter"
type: "std_msgs::msg::dds_::String_"
participants:
- name: SimpleParticipant
kind: local
domain: 0
- name: ClientDSParticipant
kind: local-discovery-server
discovery-server-guid:
id: 202
connection-addresses:
- discovery-server-guid:
id: 200
addresses:
- domain: host_a
port: 11811
transport: udp
- On both
host_a
andhost_b
execute (in the same folder asconfig.yaml
file):
docker run --name ros2router \
--restart=unless-stopped \
--network host \
--ipc host \
--user $(id -u):$(id -g) \
-v $(pwd)/config.yaml:/config.yaml \
-v /etc/group:/etc/group:ro \
-v /etc/passwd:/etc/passwd:ro \
-v /etc/shadow:/etc/shadow:ro \
-e AUTO_CONFIG=FALSE \
husarnet/ros2router:1.2.0 bash -c "ddsrouter -c /config.yaml -r 10"
- Start a chatter demo:
- on the
host_a
:
export ROS_DOMAIN_ID=0
ros2 run demo_nodes_cpp talker
- on the
host_b
:
export ROS_DOMAIN_ID=0
ros2 run demo_nodes_cpp listener