概要
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
を用いた方法を説明している。
SystemTap
を用いる方法は少し面倒なので、
今回は、 SystemTap
を利用する代わりにTCPプロキシで通常のTCPの接続をMPTCPに中継する方法を取った。
MPTCP-Proxy
TCP の接続を終端し、MPTCPで接続を張り中継するだけなので、Go言語で簡易的に実装した。 (すでに既存実装があるような気がするが、探すのも面倒だったので)
動作としては、単純な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
実験にあたり、以下の資料を参考にした。
実験1(単純な2パス)
図のような環境で様子を観察した。本実験はMPTCP-Proxy
のtest/simple
にあるスクリプト一式で行った。
$ cd test/simple $ docker-compose up -d
で動かすことができる。
MPTCP 関連の設定
クライアント側
クライアント側では、MPTCP-Proxy
が5555/tcp
でTCP接続を待ち受け、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-Proxy
が4444/tcp
でMPTCP接続を待ち受け、localhost:5201
へTCPで接続する。
$ 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.2
へ MPTCP Join Connection
オプション付きで 3-way handshake が行われていることが分かる。
実験2(クライアントがシングルホーム)
図のような環境で様子を観察した。本実験はMPTCP-Proxy
のtest/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.2
へ MPTCP Join Connection
オプション付きで 3-way handshake が行われていることが分かる。
また、クライアント(10.123.202.3) <-> サーバー(10.123.201.2)
の通信のみが行われていることが分かる。
LinuxのECMPにおいて送信元アドレスがどのような挙動(必ずアドレスが割り当てられたインタフェースから送信されるか)を示すかはさらなる調査が必要ではあるものの、2つのインタフェースに分配して通信が行われていることが分かる。
実験3(サーバーがシングルホーム)
図のような環境で様子を観察した。本実験はMPTCP-Proxy
のtest/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 のルーティングよく分からん。