【ROS2】同一ネットワーク内にService,Topic...通信が漏れるのを防ぐ方法

# はじめに
こんにちは。私は金沢工業大学の夢考房のRobocup@Homeプロジェクトに所属しています。
この前チーム内で開発を行っていたときに不意に発見した弱点でもあり、美点でもある、ROS2の素晴らしい機能を紹介したいと思います。
内容は一部ネットワーク系の話になるのですがネットワークがわからなくても問題ありません。
そしてタイトルからわかる通り、悪用できそうな感じがしますがそれはしないように！！！今回の記事の目的は悪用はできちゃうので対策しようということです。
それではやっていきましょう！

# 私の環境
- ROS2 Humble
- Ubuntu 22.04 LTS
- Ubuntu on Docker 22.04 tiryohさんのimage


### 本記事で使用するDocker imagesについて
本記事では[Tiryoh](https://github.com/Tiryoh)さんが作られた`ROS2`の`Docker`環境を利用します。
https://github.com/Tiryoh/docker-ros2-desktop-vnc
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/710848/a70034f2-c8ba-2b3b-9eb1-14b8b1f91c7d.png)


# ことの発端
プロジェクト内で二人が通路を挟んで背中を合わせて開発していました。それぞれウェブカメラや手元のものを認識するためにRealsense使ってYOLOの物体認識を検証しているときに、Realsenseを使っている人がカメラ映像を確認すると、本来手元が映っているはずが、**なぜか自分の背中が映っています**。振り向くと、後ろで開発していた人間はROS2で **カメラのトピック通信を行っていました。**
？？？？
それで確認すると二人とも同じトピック名で開発を行っていました。

これが大会会場になると同じトピック名がほかチームと被った場合とても両チームに大きな損害を与えかねません。その他にもROS2を使って製品を作っている会社さんなどは同製品内でトピック通信の競合が起きて損害を与えかねません。

# 本当にトピック通信は漏れる？検証しよう
今回の記事はトピック通信の通信周りについて説明します。

## メインのPCでトピック通信を書く
まずはROS2のカスタムトピックのパッケージを作成します

![Screenshot from 2023-12-15 08-34-29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/710848/8c994d12-1e63-3500-aac1-e55ebad62c38.png)

自動生成された`testtopic`のプログラムを以下のように書き換えます。どこにでもある一般的なコードです。

```:Python
import rclpy
from rclpy.node import Node
from std_msgs.msg import String


class TestTopic(Node):
    def __init__(self):
        super().__init__('testtopic')
        self.publisher_ = self.create_publisher(String, 'takuchan/testtopic', 10)
        self.timer_ = self.create_timer(0.5, self.timer_callback)
        self.i = 0

    def timer_callback(self):
        msg = String()
        msg.data = 'Hello World: %d' % self.i
        self.publisher_.publish(msg)
        self.get_logger().info('Publishing: "%s"' % msg.data)
        self.i += 1


def main():
    rclpy.init()
    node = TestTopic()
    rclpy.spin(node)
    node.destroy_node()
    rclpy.shutdown()


if __name__ == '__main__':
    main()
```
それではビルドしましょう！

![Screenshot from 2023-12-15 08-38-23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/710848/6d1ca42f-dc90-5c47-5f06-02312ebdbff9.png)

それで、`$ source ~/.bashrc`をしてから実行します。
実行をすると

![Screenshot from 2023-12-15 08-40-31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/710848/b9300628-d273-79e3-2457-7041c6f5618c.png)

ここまではROS2を開発している人にならよく見慣れた手順ですね。ついでにトピックリストも確認しておきましょう。

![Screenshot from 2023-12-15 08-41-37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/710848/16ec0f86-43d8-7e43-3ce6-36e6701ce7c3.png)

## サブPCでトピック通信を確認しよう

![Screenshot from 2023-12-15 08-45-52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/710848/e9fe579c-1f37-f88a-0a84-0fcaaf493b0c.png)

サブPCはTiryohさんのDockerimageを使用しています。画像をご覧の通りメインPCとは関係のないサブPCまでトピック通信の影響が及んでいます。
つまり、トピック通信を別PCで受け取ることができるということです。ではサブPCでトピック通信を受け取ってみましょう。
### かんたんにサブスクライバーのプログラム作ってみる
別パソコンでサブスクライバーのプログラムを書いてみましょう。
![Screenshot from 2023-12-15 08-52-18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/710848/88e7f1d1-151b-25ca-8a20-a6ca575e1273.png)

自動生成された`testtopicsub.py`に以下のコードを書いて実行してみましょう。

```:Python
import rclpy
from rclpy.node import Node
from std_msgs.msg import String

class MySubscriber(Node):

    def __init__(self):
        super().__init__('my_subscriber')
        self.subscription = self.create_subscription(
            String,
            'takuchan/testtopic',
            self.listener_callback,
            10)
        self.subscription

    def listener_callback(self, msg):
        self.get_logger().info('I heard: "%s"' % msg.data)

def main(args=None):
    rclpy.init(args=args)

    my_subscriber = MySubscriber()

    rclpy.spin(my_subscriber)

    my_subscriber.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()

```


![Screenshot from 2023-12-15 08-58-23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/710848/6191586e-5c36-e6dd-a1a0-202d0db7b76a.png)

なんと、別PCでトピック通信を受け取ることができていました。

# でも、これって逆に強みではないでしょうか。
ROS2が搭載されているPCでは他ROS2搭載PCが発信した通信を取得できるということは、同一ネットワーク内で遠隔で他の端末情報を取得＆制御できるということです。

**例えば２つのロボットを一緒に制御したい場合などにとても有効な手段と言えそうです。**
個人的に夢がある話でかんたんに実装できてしまうこの機能はとても興味深いです！！！
僕は複数のロボットを同時に動かしたり、２つのロボットのカメラ映像を比較して制御したりするときにとても有効な手段だと思いました。
またロボット開発目的以外の用途としても利用できそうな点がとても魅力的に僕は写りました。
ネットワークの知識がいらなくてもROS2の知識さえあれば開発できる、魅力です！

# 大会会場ではちょっとまずいこの機能
Robocup@Home Japan Openなどの大会では一斉にそれぞれのチームがロボットを動かします。
そのときに
- ROS2 Humble
- 同一ネットワーク
- トピック通信やサービス通信などの名前が一致

この条件が揃うことで、自分とは別のロボットの情報も持ってきてしまいます！それではせっかく事前に開発したプログラムがROS2の仕様のせいで水の泡となってしまいます。。。
![大会フィールド.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/710848/e24add51-2414-3047-0c89-12e8c929b98f.jpeg)
大会会場　RCJ 2023 ダイハツアリーナ

# じゃあ対策しよう！
## 解決策①　環境変数PATHに登録しよう
`$ export ROS_LOCALHOST_ONLY=1 `
この設定を行った後にROS2のノードを起動すれば、そのノードは他のPCからは見えなくなります。ただし、この設定は現在のシェルセッションにのみ適用されます。PC全体でこの設定を適用するには、設定をbashrcなどのシェルの設定ファイルに追加する必要があります。
もとに戻して、ネットワーク全体で通信を行いたい場合は、この設定を解除（unset ROS_LOCALHOST_ONLY）すると直ります。

## 解決策②　ROS2ドメインIDを設定
`$ export ROS_DOMAIN_ID=1`
この設定方法は参考文献の中にある *同一ネットワーク内で複数人がROS2を使用する場合*から引用しています。
何も設定しない場合は`DOMAIN_ID`は`0`に設定されています。
開発チーム内でドメインIDを決めておきましょう！
ドメインIDは0～65532まで対応しています。

## .bashrcに書いちゃおう。
私の場合は基本的に外部PCと通信することはあまり考えていないので` ~/.bashrc` に書いちゃいます。
必要なときに ` $ source ~/.bashrc`をすればかんたんにそのシェル領域にバッシュが適用されますからね！


# なぜROS2でこんなことができるのか
ROS2からはROS2の実行方法がインターネットのプロトコルに依存しています。そのため、実行品質が保たれています。
ROS2のネットワーク全体でノードを受け取ることができる理由は、ROS2がData Distribution Service (DDS)を基に構築されているからです。DDSは分散ネットワーク全体で複数のノードを接続することを可能にします。
そのためです。

# 参考文献
https://jp.mathworks.com/help/ros/ug/connect-to-a-ros-2-network.html
https://qiita.com/NeK/items/6163d5a307665a3c9c1c