FastDDSが送出するIGMPv2マルチキャストパケットを追いかける

## ROS2ユーザー向けの要約
- FastDDSでデフォルトの`239.255.0.1`に対して、一般的に使われるであろう、239.0.0.0/8のプライベートスコープでMacアドレスが重複するマルチキャストグループは、`239.127.0.1`となります。ROS2以外のアプリケーション（動画配信システム、TV会議システムなど）の動きにも注意して下さい。
- ROS2のQoS周りがIPネットワーク的には変化の余地がありそう。SHMとかの単一ホスト向けの改良の方が優先度高そうですが。

## はじめに
先日、[ROSアドベントカレンダー](https://qiita.com/advent-calendar/2021/ros)として投稿した、[AWS\-VPC内でROS2\(FastDDS\-Multicast\-UDP\)を使ったPub/Subを実行する【相性良い/悪い\!?】](https://qiita.com/fudekun/items/d23ea7da97d5f9160824)の補足として、[FastDDS](https://fast-dds.docs.eprosima.com/en/latest/index.html)がどういったIGMPv2マルチキャストパケットを送出しているか！？
ROSアドカレではカットしたネタがちょっと勿体なかったので引っ張り出してきました。（かなり需要が低そうですが、一部、ROSクラスタの皆様にも役立ちそうなポイントがあったので。）


## パケットを見てみる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96992/73d0dabd-f57b-4fb8-6529-918b42169789.png)

まず、上記投稿時にAWS上でtcpdumpを実行し、Wiresharkを使って可視化しました。`-w ${YOUR_FILE_NAME}.cap`で保存したファイルをインポートするだけです。

## Pythonから使ってみて理解する
上記のパケットキャプチャ結果を、コードに落とし込んでみました。
ライブラリとして[Scapy](https://github.com/secdev/scapy)を使いました。大体のパケット操作が直感的に行えます。
IGMPについては、[該当プロトコルに対する実装部](https://github.com/secdev/scapy/blob/master/scapy/contrib/igmp.py#L34)にサンプルコードが記載されています。

```python
from scapy.all import conf, sendp, ARP, Ether, ETHER_BROADCAST, IP, IPOption_Router_Alert
from scapy.contrib.igmp import IGMP

ether_part = Ether(src="0e:a2:67:9e:56:bf", dst="01:00:5e:7f:00:01")
ip_part = IP(src="192.168.110.169", dst="239.255.0.1", tos=0xc0, ttl=1, flags=2, id=0x00, options=[IPOption_Router_Alert()])
igmp_part = IGMP(type=0x17, gaddr="239.255.0.1", mrcode=0)
x = ether_part / ip_part / igmp_part
x[IGMP].igmpize()
sendp(ether_part / ip_part / igmp_part)
```

### Etherヘッダ
[scapy/l2\.py](https://github.com/secdev/scapy/blob/master/scapy/layers/l2.py)で定義される通り、L2（データリンク層）の設定を実施します。
`dst=01:00:5e:7f:00:01`のMacアドレスは、マルチキャストグループ`239.255.0.1`から求めることが出来ます。詳しくは以下の記事が非常にわかりやすかったです。
> [マルチキャスト編　マルチキャストアドレッシングとは？ \| 日経クロステック（xTECH）](https://xtech.nikkei.com/it/article/COLUMN/20070704/276782/)
> IANA（Internet Assigned Numbers Authority）によって規定されているマルチキャスト用のMACアドレスは先頭の25ビットが固定値です。

#### ROS2で気をつけること
上記の引用元で指摘されていますが、このようなMacアドレスの採番方法を取っていますが、Macアドレス対マルチキャストグループとの関係性は**1対1ではありません**
今回の例では、
`01:00:5e`までがマルチキャスト（224.0.0.0/4）の場合固定になります。これは、2進数表現で **上位25ビット**分が固定である[RFC1112](https://datatracker.ietf.org/doc/html/rfc1112)の規定によります。
後半`7f:00:01`は、マルチキャストグループを示します。マルチキャストグループのIPアドレスの２進数表現で **下位23ビット**をミラーしたものです。
完全なマルチキャストグループの情報のうち、9ビット分（32-9=23ビット）の情報が消えています。（第1オクテッドと、第2オクテッドの先頭1ビット）
そのため、Macアドレス対マルチキャストグループは実際には、**32対1**の関係になります。もしかするとこれが、何か制約となることもある！？！？！？！？という雰囲気を頭の隅っこに入れておくと救われる日が来るかもしれません。
※例えば、FastDDSでデフォルトの`239.255.0.1`に対して、一般的に使われるであろう、239.0.0.0/8のプライベートスコープで重複するマルチキャストグループは、`239.127.0.1`となります。ROS2以外のアプリケーション（動画配信システム、TV会議システムなど）の動きにも注意して下さい。

```bash
# 固定
01　　　　　　　　　　　　　　00　　　　　　　　　　　　　　5e
00000001 00000000 01011110

# マルチキャストグループ（全32ビットのうち、下位の23ビットがマルチキャストグループのMacアドレスとして採用）
239      255       0        1
          |7f      00       01
11101111 1|1111111 00000000 00000001
#^^^^^^^^^情報落ちする
```

### IPヘッダ
[layers/inet.py](https://github.com/secdev/scapy/blob/master/scapy/layers/inet.py)で定義されており、L3（ネットワーク層）の設定を実施します。
基本的には、[IPv4 \- Wikipedia](https://ja.wikipedia.org/wiki/IPv4)の通りのヘッダ構造を埋めていく感じです。
指定されているオプションをざっくりと見てみます。

- src="192.168.110.169"
   - 送信元のIPv4アドレス。（AWS Transit Gatewayでは特に使っていないようにも見える、Macアドレスを使っている？？）
- tos
   - [Type of Service](https://ja.m.wikipedia.org/wiki/Type_of_Service)
   - 転送の優先順位とかを示す。0xc0はInternetwork controlとして上から2番目の優先度(制御用パケットであるため)
- ttl
   - [Time to live](https://ja.m.wikipedia.org/wiki/Time_to_live)
   - マルチキャストの場合、トラフィックが輻輳しないように最小限に設定しておくのが一般的。1であれば同一サブネット内利用を想定している。
- flags
   - フラグメント（断片化）の許可の有無。0x02として"禁止"設定されている。（近年は許可で使うことは稀）
- options(RouterAlert)
   - [RFC2113](https://datatracker.ietf.org/doc/html/rfc2113)で定義。ルーターによるスヌーピング（盗み見、検査）を許可するために使用します。[AWS Transit Gateway](https://aws.amazon.com/jp/transit-gateway/)や、近年発売されている中級機以上のスイッチングハブではこのフラグ有効にしておかないと期待した動作が得られない。
   - （なお、筆者はこのオプションの指定の必要性に気がつくことが出来ず半日ほど、悪戦苦闘した。いらないオプションなんてこの世にないのだ。）

#### ROS2で気をつけること
IGMPとは直接関係ありませんが、IPヘッダという点で気がついたことを挙げておきます。
ROS2にはQoSという、通信品質とリアルタイム性能をコントロールするためのパラメータがありますが、IPヘッダで定義するToSという「DDS」より低いネットワークレイヤの部分では特にコントロールされていないという点が意外でした。その辺については、[discourse](https://discourse.ros.org/t/transport-priority-qos-policy-to-solve-ip-flow-ambiguity-while-requesting-5g-network-qos/15332)でも議論されているみたいです。
試しに[demos/quality\_of\_service\_demo at master · ros2/demos](https://github.com/ros2/demos/tree/master/quality_of_service_demo)にあるデモを使って、各パラメータをいろいろな変えながら、FastDDSの通信をキャプチャしてみましたが、常に「ToS=0x00」と特に優先度の高い設定がせれている訳ではないことが分かりました。この辺が今後どう変わっていく可能性があるのか注目していきたいと思います。

```bash
ros2 run quality_of_service_demo_cpp interactive_publisher --delay 0.5 --deadline 1 --liveliness MANUAL_BY_TOPIC --lease 1
ros2 run quality_of_service_demo_cpp interactive_subscriber --deadline 1 --liveliness MANUAL_BY_TOPIC --lease 1
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96992/01e39308-d227-2bb4-6317-e9a75a871abb.png)




### IGMPヘッダ
[contrib/igmp.py](https://github.com/secdev/scapy/blob/master/scapy/contrib/igmp.py#L34)で定義されています。
指定されているオプションをざっくりと見てみます。

- type=0x17
   - IGMPのタイプ。ホストがマルチキャストグループの**参加**、**維持**、**離脱**を表明するために使用します。
      - 0x11：維持（メンバーシップ・クエリ）
      - 0x16：参加（メンバーシップ・レポート）
      - 0x17：離脱（リーブグループ・メッセージ）
- mrcode=0
   - IGMPメンバーシップ・クエリーに対する、最大応答待ち時間を指定します。
      - なお、今回は「参加表明」のメンバーシップ・レポートであるため、ここの数字は意味が無い模様。
      - スイッチングハブから送られてくるメンバーシップ・クエリの場合に使用される。

## おわりに
今回は、IGMPｖ2マルチキャストパケットを追いかけることで、マルチキャストへの理解を少しだけ深めることができました。
補足として、FastDDSで送出するIGMP以外の他のパケットについては、[4\. Understanding the RTPS Packet Format — Using Wireshark with RTI Connext DDS Systems documentation](https://community.rti.com/static/documentation/wireshark/2020-07/doc/understanding_rtps.html)に掲載されています。本記事と同様に、Wiresharkで可視化したものを題材として取り上げて、各メッセージがどういったことを表しているかを事細かに解説されています。マルチキャスト・ユニキャストを巧みに使い分けていて、すごい仕組みだなーと改めて感じました。（小並感）