外部記憶装置

脳みそ小さすぎるからメモしとく

MPTCP を触ってみる(Ubuntu 22.04 LTS)

概要

Ubuntu 22.04 LTS (Linux Kernel 5.15) がリリースされ、通常用途のサーバーでも Linux Kernel 5.6 で実装された MPTCP の利用が容易になった。 せっかくなので、3つのシナリオで MPTCP を用いたアプリケーションを動かし、その動作の様子を観察した。 (※通学に利用している電車のWiFiが不安定で何とかしたいという理由もある)

MPTCP について

datatracker.ietf.org www.multipath-tcp.org Multipath TCPは複数パスを用いてコネクションを張ることで、TCPスループットや通信品質を改善することを目的としている。 利用用途としては、モバイル回線と固定回線の併用や複数のモバイル回線の併用といった、通信が不安定になりがちな回線におけるTCPの品質改善に利用されることを想定しているようだ。

MPTCP の使い方

MPTCP を利用するには、 socket(2)IPPROT_MPTCP フラグを指定してソケットを作成する必要がある。

int fd = socket(AF_INET, SOCK_STREAM, IPPROTO_MPTCP);

そのため、既存のアプリケーションでは、socket(2) の引数を適切に書き換える必要がある。 RedHat では、既存のアプリケーションをMPTCPに対応させるために、SystemTapを用いた方法を説明している。

access.redhat.com

SystemTap を用いる方法は少し面倒なので、 今回は、 SystemTap を利用する代わりにTCPプロキシで通常のTCPの接続をMPTCPに中継する方法を取った。

MPTCP-Proxy

TCP の接続を終端し、MPTCPで接続を張り中継するだけなので、Go言語で簡易的に実装した。 (すでに既存実装があるような気がするが、探すのも面倒だったので)

github.com

動作としては、単純なTCPのプロキシである。 mode = "client" では、TCP -> MPTCP の中継を行い、mode = "server" では、 MPTCP -> TCP の中継を行う。

実験環境

Docker と docker-compose を用いて実験環境を用意した。 ホストは Ubuntu 22.04 LTS で、以下の設定となっている。

$ sudo sysctl -a | grep mptcp
net.ipv4.tcp_available_ulp = espintcp mptcp tls
net.mptcp.add_addr_timeout = 120
net.mptcp.allow_join_initial_addr_port = 1
net.mptcp.checksum_enabled = 0
net.mptcp.enabled = 1
net.mptcp.stale_loss_cnt = 4

$ sudo sysctl net.ipv4.ip_forward
net.ipv4.ip_forward = 1

実験にあたり、以下の資料を参考にした。

access.redhat.com

実験1(単純な2パス)

図のような環境で様子を観察した。本実験はMPTCP-Proxytest/simpleにあるスクリプト一式で行った。

$ cd test/simple
$ docker-compose up -d

で動かすことができる。

MPTCP 関連の設定

クライアント側

クライアント側では、MPTCP-Proxy5555/tcpTCP接続を待ち受け、10.123.200.2:4444 へMPTCPで接続する。

$ mptcp-proxy -m client -p 5555 -r 10.123.200.2:4444

MPTCP関連の設定として、サブフローを1つまで受け入れる設定をしている。

$ ip mptcp limits set subflow 1 add_addr_accepted 1

サーバー側

サーバー側では iperf3 が接続を待ち受け、 MPTCP-Proxy4444/tcp でMPTCP接続を待ち受け、localhost:5201TCPで接続する。

$ iperf3 -s &
$ mptcp-proxy -m server -p 4444 -r localhost:5201

MPTCP関連の設定として、サブフローを1つまで受け入れる設定をしている。 また、10.123.201.2(eth1) をMPTCPのエンドポイントとして利用する設定も行っている。

$ ip mptcp limits set subflow 1
$ ip mptcp endpoint add 10.123.201.2 dev eth1 signal

実験

iproute2 でMPTCPの接続状況を監視することができる。

$ docker-compose exec client ip mptcp monitor

別コンソールで iperf3 を動作させる。

$ docker-compose exec client iperf3 -c localhost -p 5555
Connecting to host localhost, port 5555
[  5] local 127.0.0.1 port 35948 connected to 127.0.0.1 port 5555
[ ID] Interval           Transfer     Bitrate         Retr  Cwnd
[  5]   0.00-1.00   sec  1.10 GBytes  9.46 Gbits/sec    0   1.37 MBytes       
[  5]   1.00-2.00   sec  1.10 GBytes  9.43 Gbits/sec    0   1.37 MBytes       

mptcp monitor を用いた動作状況の確認

iproute2 mptcp monitor でも接続状況が反映される。

$ docker-compose exec client ip mptcp monitor
[       CREATED] token=c53b6fd4 remid=0 locid=0 saddr4=10.123.200.3 daddr4=10.123.200.2 sport=53360 dport=4444
[   ESTABLISHED] token=c53b6fd4 remid=0 locid=0 saddr4=10.123.200.3 daddr4=10.123.200.2 sport=53360 dport=4444
[     ANNOUNCED] token=c53b6fd4 remid=1 daddr4=10.123.201.2 dport=0
[SF_ESTABLISHED] token=c53b6fd4 remid=1 locid=0 saddr4=10.123.201.3 daddr4=10.123.201.2 sport=39957 dport=4444 backup=0
[       CREATED] token=c03b1893 remid=0 locid=0 saddr4=10.123.200.3 daddr4=10.123.200.2 sport=53362 dport=4444
[   ESTABLISHED] token=c03b1893 remid=0 locid=0 saddr4=10.123.200.3 daddr4=10.123.200.2 sport=53362 dport=4444
[     ANNOUNCED] token=c03b1893 remid=1 daddr4=10.123.201.2 dport=0
[SF_ESTABLISHED] token=c03b1893 remid=1 locid=0 saddr4=10.123.201.3 daddr4=10.123.201.2 sport=33487 dport=4444 backup=0

主フロー(Master flow) が 10.123.200.3 <-> 10.123.200.2 間で確立されている。 サーバー側から 10.123.201.2 へMPTCPによる接続が可能である通知が行われると、10.123.201.3 <-> 10.123.201.2 間でサブフロー(Sub flow) の接続が確立されていることが分かる。

クライアント(10.123.200.3/24, eth0) でのパケットキャプチャ

3-way handshake が MPTCP Multipath Capable フラグ付きで行われていることが分かる。

3-way handshake 直後に、サーバーから MPTCP Add Address オプションにより、 10.123.201.2 へのMPTCP接続をクライアントに対して通知していることが分かる。

クライアント(10.123.201.3/24, eth1) でのパケットキャプチャ

クライアント側のルーティングテーブルに従い、 10.123.201.2への接続は10.123.201.3/24のアドレスが割り当てられてインタフェース(ここではeth1)が利用される。 クライアント側から、10.123.201.2MPTCP Join Connection オプション付きで 3-way handshake が行われていることが分かる。

実験2(クライアントがシングルホーム)

図のような環境で様子を観察した。本実験はMPTCP-Proxytest/routingにあるスクリプト一式で行った。

$ cd test/routing
$ docker-compose up -d

で動かすことができる。

MPTCP 関連の設定

実験1 と同じである。

ルーティング

スタティックルーティングの設定を行っている。

クライアント側

ip route add 10.123.200.0/24 via 10.123.202.2
ip route add 10.123.201.0/24 via 10.123.202.2

サーバー側

ip route add 10.123.202.0/24 nexthop via 10.123.200.3 weight 1 nexthop via 10.123.201.3 weight 1

サーバー側については、ECMP(Equal Cost Multipath)で設定を行っている。 本来は、ip rule でエンドポイントのアドレスが割り当てられたインタフェースからパケットが送信されるように設定すべきな気がしている。

実験

実験1と同様の手順で実験を行った。

mptcp monitor を用いた動作状況の確認

iproute2 mptcp monitor でも接続状況が反映される。

$ docker-compose exec client ip mptcp monitor
[       CREATED] token=67e9a281 remid=0 locid=0 saddr4=10.123.202.3 daddr4=10.123.200.2 sport=46050 dport=4444
[   ESTABLISHED] token=67e9a281 remid=0 locid=0 saddr4=10.123.202.3 daddr4=10.123.200.2 sport=46050 dport=4444
[     ANNOUNCED] token=67e9a281 remid=1 daddr4=10.123.201.2 dport=0
[SF_ESTABLISHED] token=67e9a281 remid=1 locid=0 saddr4=10.123.202.3 daddr4=10.123.201.2 sport=47341 dport=4444 backup=0
[       CREATED] token=ab7a4b72 remid=0 locid=0 saddr4=10.123.202.3 daddr4=10.123.200.2 sport=46052 dport=4444
[   ESTABLISHED] token=ab7a4b72 remid=0 locid=0 saddr4=10.123.202.3 daddr4=10.123.200.2 sport=46052 dport=4444
[     ANNOUNCED] token=ab7a4b72 remid=1 daddr4=10.123.201.2 dport=0
[SF_ESTABLISHED] token=ab7a4b72 remid=1 locid=0 saddr4=10.123.202.3 daddr4=10.123.201.2 sport=59891 dport=4444 backup=0

主フロー(Master flow) が 10.123.202.3 <-> 10.123.200.2 間で確立されている。 サーバー側から 10.123.201.2 へMPTCPによる接続が可能である通知が行われると、10.123.202.3 <-> 10.123.201.2 間でサブフロー(Sub flow) の接続が確立されていることが分かる。

サーバー(10.123.200.2/24, eth0) でのパケットキャプチャ

3-way handshake 直後に、サーバーから MPTCP Add Address オプションにより、 10.123.201.2 へのMPTCP接続をクライアントに対して通知していることが分かる。 また、クライアント(10.123.202.3) <-> サーバー(10.123.200.2) の通信のみが行われていることが分かる。

サーバー(10.123.201.2/24, eth1) でのパケットキャプチャ

クライアント側から、10.123.201.2MPTCP Join Connection オプション付きで 3-way handshake が行われていることが分かる。 また、クライアント(10.123.202.3) <-> サーバー(10.123.201.2) の通信のみが行われていることが分かる。 LinuxのECMPにおいて送信元アドレスがどのような挙動(必ずアドレスが割り当てられたインタフェースから送信されるか)を示すかはさらなる調査が必要ではあるものの、2つのインタフェースに分配して通信が行われていることが分かる。

実験3(サーバーがシングルホーム)

図のような環境で様子を観察した。本実験はMPTCP-Proxytest/routing2にあるスクリプト一式で行った。

$ cd test/routing2
$ docker-compose up -d

で動かすことができる。

MPTCP 関連の設定

クライアント側

ip mptcp limits set subflow 1 add_addr_accepted 1
ip mptcp endpoint add 10.123.202.3 dev eth1 subflow

クライアント側からMPTCP subflow の接続を行う設定を行っている。

サーバー側

ip mptcp limits set subflow 1

ルーティング

スタティックルーティングの設定を行っている。

クライアント側

ip route add 10.123.200.0/24 via 10.123.201.2 src 10.123.201.3 metric 10
ip route add 10.123.200.0/24 via 10.123.202.2 src 10.123.202.3 metric 11

送信元アドレスが割り当てられたインタフェースから送信されるように経路を設定している。

ip rule で設定していたところ、クライアントのルーティングは利用できない様子?

サーバー側

ip route add 10.123.201.0/24 via 10.123.200.3
ip route add 10.123.202.0/24 via 10.123.200.3

実験

実験1と同様の手順で実験を行った。

mptcp monitor を用いた動作状況の確認

iproute2 mptcp monitor でも接続状況が反映される。

$ docker-compose exec client ip mptcp monitor
[       CREATED] token=e24736a9 remid=0 locid=0 saddr4=10.123.201.3 daddr4=10.123.200.2 sport=45618 dport=4444
[   ESTABLISHED] token=e24736a9 remid=0 locid=0 saddr4=10.123.201.3 daddr4=10.123.200.2 sport=45618 dport=4444
[SF_ESTABLISHED] token=e24736a9 remid=0 locid=1 saddr4=10.123.202.3 daddr4=10.123.200.2 sport=56857 dport=4444 backup=0 ifindex=533
[       CREATED] token=4d43f0e6 remid=0 locid=0 saddr4=10.123.201.3 daddr4=10.123.200.2 sport=45620 dport=4444
[   ESTABLISHED] token=4d43f0e6 remid=0 locid=0 saddr4=10.123.201.3 daddr4=10.123.200.2 sport=45620 dport=4444
[SF_ESTABLISHED] token=4d43f0e6 remid=0 locid=1 saddr4=10.123.202.3 daddr4=10.123.200.2 sport=45035 dport=4444 backup=0 ifindex=533

主フロー(Master flow) が 10.123.201.3 <-> 10.123.200.2 間で確立されている。 その後、10.123.202.3 <-> 10.123.200.2 間でサブフロー(Sub flow) の接続が確立されていることが分かる。

クライアント(10.123.201.3/24, eth0) でのパケットキャプチャ

3-way handshake が行われた後、サーバー、クライアント互いに新しいサブフローを張るために必要な情報を交換している様子は観察できなかった。 RFC8684 を読んだところ、2.2. Associating a New Subflow with an Existing MPTCP Connection に従っていそう。 通信はクライアント(10.123.201.3) <-> サーバー(10.123.200.2) の通信のみが行われていることが分かる。

クライアント(10.123.202.3/24, eth1) でのパケットキャプチャ

クライアントから MPTCP Join Connection オプション付きで 3-way handshake が行われていることが分かる。 また、クライアント(10.123.202.3) <-> サーバー(10.123.200.2) の通信のみが行われていることが分かる。

まとめ

3つのシナリオを取り上げて MPTCP の通信の様子を観察し、 通信がそれぞれのパスを利用して行われていることは確認できた。 実際に、4G(5G) と WiFi といった組み合わせの場合や、サブフローごとの重みの設定といった細かい制御が出来ると利用用途が広がりそうである。

P.S. Linux のルーティングよく分からん。