下準備
この文書ではVPNとしてtailscaleを想定しています。
tailscaleをまだインストールしていない場合は、下のコマンドでインストールします。
$ curl -fsSL https://tailscale.com/install.sh | sh
$ sudo tailscale up
以降、sudo
なしでtailscaleコマンドが使えるように、以下のコマンドも実行してください。
$ sudo tailscale set --operator=$USER
ROS通信の仕組みとROS over VPN
VPN上でROSを理解した上で使うために、まずはROS通信の仕組みを説明します。
talker
とlistener
の2ノードからなる通信サンプルを立ち上げます。
$ roslaunch roscpp_tutorials talker_listener.launch
普段は意識しないで使っていますが、roslaunch
を起動すると、rosmaster
というプロセスがポート11311上で自動で立ち上がります。
$ lsof -sTCP:LISTEN -i:11311
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
rosmaster 99 yosuke 4u IPv4 24080870 0t0 TCP *:11311 (LISTEN)
通信の中身を確認するために、xmlrpc
コマンドをインストールします。
$ sudo apt install libxmlrpc-core-c3-dev
rosmaster
が使用するxmlrpc APIは、以下に定義されています。
ここでは、rosmaster
からノードtalker
の情報を取得してみましょう。
$ xmlrpc localhost:11311 lookupNode client1 talker
Result:
Array of 3 items:
Index 0 Integer: 1
Index 1 String: 'node api'
Index 2 String: 'http://myhostname:45405/'
ホストmyhostname
のポート45405でノード用のサーバ(ROS slave)が立ち上がっているようです。
slaveが使うxmlrpcを呼び出すことで、ノードの詳細を見ることもできます。
$ xmlrpc myhostname:45405 getBusInfo client1
Result:
Array of 3 items:
Index 0 Integer: 1
Index 1 String: ''
Index 2 Array of 2 items:
Index 0 Array of 7 items:
Index 0 Integer: 0
Index 1 String: '/rosout'
Index 2 String: 'o'
Index 3 String: 'TCPROS'
Index 4 String: '/rosout'
Index 5 Boolean: TRUE
Index 6 String: 'TCPROS connection on port 38369 to [127.0.0.1:36152 on socket 12]'
Index 1 Array of 7 items:
Index 0 Integer: 1
Index 1 String: '/listener'
Index 2 String: 'o'
Index 3 String: 'TCPROS'
Index 4 String: '/chatter'
Index 5 Boolean: TRUE
Index 6 String: 'TCPROS connection on port 38369 to [127.0.0.1:36166 on socket 10]'
各ROSノードは、rosmasterにホスト名とポート番号を問い合わせた後で、以降はノード間で直接TCP/IP接続することで効率的に通信します。各ポート番号を追いかけていくことで、ノード間通信の詳細も確認できます。
ここで問題は、myhostname
というホスト名です。このホスト名には127.0.1.1
というIPアドレスが割り振られており、VPN内での通信に使うことはできません。
$ ping myhostname
PING myhostname (127.0.1.1): 56 data bytes
64 bytes from 127.0.1.1: icmp_seq=0 ttl=64 time=0.044 ms
64 bytes from 127.0.1.1: icmp_seq=1 ttl=64 time=0.047 ms
64 bytes from 127.0.1.1: icmp_seq=2 ttl=64 time=0.036 ms
64 bytes from 127.0.1.1: icmp_seq=3 ttl=64 time=0.047 ms
VPN内での通信には、以下のコマンドで得られるtailscale内で割り振られたIPアドレスを使う必要があります。
$ tailscale ip -4
100.121.86.85
ここで、環境変数ROS_IP
を使って、上記のIPアドレスを設定してみます。
$ ROS_IP=$(tailscale ip -4) roslaunch roscpp_tutorials talker_listener.launch
無事に、ROSが使用するIPアドレスをVPN通信可能なものに変更することができました。
$ xmlrpc localhost:11311 lookupNode client1 talker
Result:
Array of 3 items:
Index 0 Integer: 1
Index 1 String: 'node api'
Index 2 String: 'http://100.121.86.85:36595/'
ROS1の場合
ROSノードを立ち上げる前に、環境変数ROS_IP
とROS_MASTER_URI
を設定してください。
export ROS_MASTER_URI=http://$(tailscale ip -4 rosmasterを立ち上げたホスト名):11311
export ROS_IP=$(tailscale ip -4)
ROS2の場合
ROS2ではブロードキャスト通信を使って各ノードのdiscoveryを行いますが、多くのVPNではブロードキャスト通信が利用できません。
ここでは、ブロードキャスト通信を使う代わりに、ROS1のrosmaster
に相当するfastdds discovery
を使用します。
$ sudo apt install fastdds-tools
fastdds discovery
を以下のオプションで起動します。
$ fastdds discovery -i 0 -l $(tailscale ip -4) -p 11811
毎回の起動が面倒な場合は、supervisordを使って自動起動させるのがおすすめです。
[program:fastdds-discovery]
command=bash -c "fastdds discovery -i 0 -l $(tailscale ip -4) -p 11811"
autostart=true
autorestart=true
ROS2ノードを起動させる各ホスト上に、以下の内容のスクリプトを作成します。
ここで、DS_IP
にはdiscoveryサーバを立ち上げたホストのIPアドレスを設定してください。
DS_IP=$(tailscale ip -4 discoveryサーバを立ち上げたホスト名)
DS_PORT=11811
HOST_IP=$(tailscale ip -4)
cat > /tmp/fastdds_tailscale_configuration.xml <<EOF
<?xml version="1.0" encoding="UTF-8" ?>
<dds>
<profiles xmlns="http://www.eprosima.com/XMLSchemas/fastRTPS_Profiles">
<transport_descriptors>
<transport_descriptor>
<transport_id>TailscaleTransport</transport_id>
<type>UDPv4</type>
</transport_descriptor>
</transport_descriptors>
<participant profile_name="client_profile" is_default_profile="true">
<rtps>
<userTransports>
<transport_id>TailscaleTransport</transport_id>
</userTransports>
<useBuiltinTransports>true</useBuiltinTransports>
<defaultUnicastLocatorList>
<locator>
<udpv4>
<address>${HOST_IP}</address>
</udpv4>
</locator>
</defaultUnicastLocatorList>
<builtin>
<discovery_config>
<discoveryProtocol>SUPER_CLIENT</discoveryProtocol>
<discoveryServersList>
<RemoteServer prefix="44.53.00.5f.45.50.52.4f.53.49.4d.41">
<metatrafficUnicastLocatorList>
<locator>
<udpv4>
<address>${DS_IP}</address>
<port>${DS_PORT}</port>
</udpv4>
</locator>
</metatrafficUnicastLocatorList>
</RemoteServer>
</discoveryServersList>
</discovery_config>
<metatrafficUnicastLocatorList>
<locator>
<udpv4>
<address>${HOST_IP}</address>
</udpv4>
</locator>
</metatrafficUnicastLocatorList>
</builtin>
</rtps>
</participant>
</profiles>
</dds>
EOF
export RMW_IMPLEMENTATION=rmw_fastrtps_cpp
export FASTRTPS_DEFAULT_PROFILES_FILE=/tmp/fastdds_tailscale_configuration.xml
ros2 daemon stop
ros2 daemon start
ROSノードを立ち上げる前に、以下のコマンドでROS2環境をセットアップします。
$ source /opt/ros/jazzy/setup.bash
$ source ~/fastdds_tailscale.sh
余談
ROS2のDDS(FastDDS)は、XML形式の設定ファイルを使うことでプロトコルの詳細までカスタマイズできる。
VPNは本来UDP通信はあまり得意でなく、下のURLで記述された設定によってUDPからTCPに切り替えることもできるが、ROS2ならではのQoS制御を有効化するため、今回はUDPを使う設定にした。
https://fast-dds.docs.eprosima.com/en/latest/fastdds/transport/tcp/tcp.html
tailscaleでは、IPv6アドレスも利用できるのだが、VPN上でのIPv6はまだ実装が枯れていない部分も多いので、今回はIPv4のユニキャスト通信を使う設定にした。