メインコンテンツへスキップ

アップストリーム版のMPTCPを試してみた

NOTE
この記事は最終更新日から1年以上が経過しています。内容が古くなっている可能性があります。

はじめに
#

従来、LinuxでMPTCPを利用する場合「 Linux Kernel MultiPath TCP project」が公開しているLinuxカーネルを使う必要がありましたが、アップストリームのLinuxカーネル 5.6以降でMPTCPが利用可能になりました。

現在、MPTCPの仕様は「 RFC6824」で規定された"MPTCP v0"と「 RFC8684」で規定された"MPTCP v1"の2つのバージョンが存在します。従来版は"MPTCP v0"にのみ、アップストリーム版は"MPTCP v1"のみに対応しており、“MPTCP v1"は後方互換性を持たないので、現時点では従来版とアップストリーム版でMPTCP通信を行うことができません。

従来版および"MPTCP v0"については、本ブログでも過去に以下の投稿しています。

今回は、Ubuntuでアップストリーム版を使った複数サブフローでのMPTCP通信を試してみました。

参考サイト
#

今回、参考にしたサイトは次の通りです。

環境構築
#

VirtualBoxを使って、以下のような構成を作成しました。

“Client"と"Server"はアップストリーム版のMPTCP対応カーネルを導入した新規VMを作成 “Router"はMPTCP未対応カーネルの既設VMを利用 “Client"は複数サブフローを試したいのでネットワークインタフェースを2つ用意

新規VM作成
#

OSインストール
#

今回は次の理由から「Ubuntu Server 21.04 (Hirsute Hippo) Daily Build」としました。

複数サブフローでの同時送信はLinuxカーネル 5.10以降 後述の「SystemTap」にカーネルデバッグ情報が必要

注意点として、カーネルデバッグ情報のインストールに7GB程度とられるのでVMの仮想ハードディスク容量をデフォルトの10GBから最低でも15GBに変更しておく必要があります。

カーネル更新
#

インストール完了時点でLinuxカーネルは5.8であり、MPTCP通信自体は可能ですが複数サブフローで送信を行うには5.10以降に更新する必要があります。

  • /etc/apt/sources.list」に以下を追記します。

    deb http://archive.ubuntu.com/ubuntu hirsute-proposed main restricted universe multiverse
  • カーネルを5.10に更新します。

    $ sudo apt update
    $ sudo apt install linux-headers-5.10.0-14-generic linux-image-5.10.0-14-generic
    $ sudo reboot
    
    $ uname -a
    Linux mps 5.10.0-14-generic #15-Ubuntu SMP Fri Jan 29 15:10:03 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
    $ cat /proc/sys/net/mptcp/enabled
    1

なお、デフォルトでMPTCPは有効になっています。

SystemTap導入
#

このままMPTCP通信を試すこともできるのですが、アップストリーム版でMPTCP通信を行うには、従来版と違い次のようにソケット生成時に「 IPPROTO_MPTCP」を指定する必要があります。

fd = socket(AF_INET, SOCK_STREAM, IPPROTO_MPTCP);

今回はRedhatの記事を参考に「 SystemTap」を使った既存アプリをMPTCP通信に対応させてみます。

  • Systemtapの実行にカーネルデバッグ情報が必要なのでインストールします。

    $ sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys C8CAB6595FDFF622
    $ codename=$(lsb_release -c | awk  '{print $2}')
    $ sudo tee /etc/apt/sources.list.d/ddebs.list << EOF
    deb http://ddebs.ubuntu.com/ ${codename}      main restricted universe multiverse
    deb http://ddebs.ubuntu.com/ ${codename}-security main restricted universe multiverse
    deb http://ddebs.ubuntu.com/ ${codename}-updates  main restricted universe multiverse
    deb http://ddebs.ubuntu.com/ ${codename}-proposed main restricted universe multiverse
    EOF
    $ sudo apt-get update
    $ sudo apt-get install linux-image-$(uname -r)-dbgsym
  • Systemtapインストール

    $ sudo apt install build-essential
    $ sudo apt install systemtap
  • Systemtap実行時にgccが使われるのでbuild-essentialを入れてます。

  • スクリプト「mptcp.stap」を作成

    #! /usr/bin/env stap
    
    %{
    #include <linux/in.h>
    #include <linux/ip.h>
    %}
    
    function mptcpify () %{
        if (CONTEXT->kregs->si == SOCK_STREAM &&
            (CONTEXT->kregs->dx == IPPROTO_TCP ||
             CONTEXT->kregs->dx == 0)) {
                    CONTEXT->kregs->dx = IPPROTO_MPTCP;
                    STAP_RETVALUE = 1;
        } else {
               STAP_RETVALUE = 0;
        }
    %}
    
    probe kernel.function("__sys_socket") {
            if (mptcpify() == 1) {
                    printf("command %16s mptcpified\n", execname());
            }
    }
    • 本スクリプトを実行することで、socket()コール時に IPPROTO_TCPIPPROTO_MPTCP に入れ替えます。

tcpdump更新
#

必須ではありませんが、OSインストールで含まれる「 tcpdump」はMPTCP v1に未対応なので対応している最新版に更新します。

  • 最新版ソースをビルドしてインストール

    $ sudo apt install libpcap-dev
    $ wget https://www.tcpdump.org/release/tcpdump-4.99.0.tar.gz
    $ tar xf tcpdump-4.99.0.tar.gz
    $ cd tcpdump-4.99.0/
    $ ./configure
    $ make
    $ sudo make install
    $ sudo tcpdump --version
    tcpdump version 4.99.0
    libpcap version 1.9.1 (with TPACKET_V3)

通信アプリインストール
#

Redhatの記事に倣い「ncat」、そして複数サブフロー送信の確認用に「iperf」を導入します。

$ sudo apt install ncat iperf

ここまででMPTCP通信に必要な一式をVMに導入できました。このVMイメージをクローンして"Client"と"Server"とします。

動作確認
#

さて、それでは”Client"と"Server"で通信させてみましょう。

単一サブフロー
#

まずはRedhatの記事の手順に従って、単一サブフローを試してみます。

Server
#

  • 予めSystemTapで「mptcp.stap」を実行しておきます。

    $ sudo stap -vg mptcp.stap
    Pass 1: parsed user script and 480 library scripts using 115216virt/101484res/7724shr/93660data kb, in 300usr/10sys/314real ms.
    Pass 2: analyzed script: 1 probe, 2 functions, 1 embed, 0 globals using 185872virt/172964res/8824shr/164316data kb, in 1300usr/100sys/1388real ms.
    Pass 3: using cached /root/.systemtap/cache/7e/stap_7e56f03f2ab5129c2608bd38944e60b3_1608.c
    Pass 4: using cached /root/.systemtap/cache/7e/stap_7e56f03f2ab5129c2608bd38944e60b3_1608.ko
    Pass 5: starting run.
  • 次にncatをサーバとして起動します。

    $ ncat -4lk 4321
  • SystemTapを実行したコンソール上に以下の出力が追加されます。

    command             ncat mptcpified

Client
#

  • Server同様に予めSystemTapで「mptcp.stap」を実行しておきます。

    $ sudo stap -vg mptcp.stap
    Pass 1: parsed user script and 480 library scripts using 115220virt/101760res/8000shr/93664data kb, in 290usr/20sys/313real ms.
    Pass 2: analyzed script: 1 probe, 2 functions, 1 embed, 0 globals using 185876virt/173188res/9052shr/164320data kb, in 1270usr/90sys/1359real ms.
    Pass 3: using cached /root/.systemtap/cache/7e/stap_7e56f03f2ab5129c2608bd38944e60b3_1608.c
    Pass 4: using cached /root/.systemtap/cache/7e/stap_7e56f03f2ab5129c2608bd38944e60b3_1608.ko
    Pass 5: starting run.
  • 実際にMPTCP通信しているかの確認用にtcpdumpを実行します。

    $ sudo tcpdump -i any port 4321 -n -nn
    tcpdump: data link type LINUX_SLL2
    tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
    listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes
  • 次にncatをクライアントとして起動します。

    $ ncat 192.168.30.20 4321
  • SystemTapを実行したコンソール上に以下の出力が追加されます。

    command             ncat mptcpified
  • tcpdumpを実行したコンソール上にmptcpオプション付のパケットが出力されます。

    06:17:53.978527 enp0s3 Out IP 192.168.10.20.35644 > 192.168.30.20.4321: Flags [S], seq 804167068, win 64240, options [mss 1460,sackOK,TS val 1521023865 ecr 0,nop,wscale 7,mptcp capable v1], length 0
    06:17:53.979072 enp0s3 In  IP 192.168.30.20.4321 > 192.168.10.20.35644: Flags [S.], seq 2369976460, ack 804167069, win 65160, options [mss 1460,sackOK,TS val 251220121 ecr 1521023865,nop,wscale 7,mptcp capable v1 {0x7948c13f4e14a250}], length 0
    06:17:53.979103 enp0s3 Out IP 192.168.10.20.35644 > 192.168.30.20.4321: Flags [.], ack 1, win 502, options [nop,nop,TS val 1521023865 ecr 251220121,mptcp capable v1 {0xcc3b1bc6232e7271,0x7948c13f4e14a250}], length 0

これで単一サブフローでMPTCP通信ができることが確認できました。

複数サブフロー
#

いよいよ、複数サブフロー(ネットワークインタフェース)を同時に使って”Client"と"Server"で通信させてみます。

Server
#

  • 予めSystemTapで「mptcp.stap」を実行しておきます。

  • 許容するサブフロー数を設定します。

    $ sudo ip mptcp limits set subflow 2
    $ sudo ip mptcp limits show
    add_addr_accepted 0 subflows 2
  • iperfをサーバとして起動します。

    $ iperf -s
  • SystemTapを実行したコンソール上に以下の出力が追加されます。

    command             iperf mptcpified

Client
#

  • 予めSystemTapで「mptcp.stap」を実行しておきます。

  • それぞれのネットワークインタフェース指定でtcpdumpを実行します。

    • enp0s3

      $ sudo tcpdump -i enp0s3 dst port 5001 -n -nn
      tcpdump: data link type LINUX_SLL2
      tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
      listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes
    • enp0s8

      $ sudo tcpdump -i enp0s8 dst port 5001 -n -nn | more
      tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
      listening on enp0s8, link-type EN10MB (Ethernet), snapshot length 262144 bytes
  • サブフローの設定をします。

    • 今回試した時のClientの経路設定は次のとおりです。

      $ ip route
      default via 192.168.10.1 dev enp0s3 proto static metric 100
      default via 192.168.20.1 dev enp0s8 proto static metric 200
      192.168.10.0/24 dev enp0s3 proto kernel scope link src 192.168.10.20
      192.168.20.0/24 dev enp0s8 proto kernel scope link src 192.168.20.20
    • enp0s3のデフォルトゲートウェイ設定の優先度が高いので、先程は何も設定しなくてもenp0s3がMPTCP通信に使われていました。

    • enp0s8(192.168.20.20)をサブフローとして使うように設定します。

      $ sudo ip mptcp limits set subflow 2
      $ sudo ip mptcp limits show
      add_addr_accepted 0 subflows 2
      $ sudo ip mptcp endpoint add 192.168.20.20 subflow
      $ sudo ip mptcp endpoint show
      192.168.20.20 id 1 subflow
    • 複数サブフローからパケットが送信されるようにポリシールーティングを設定します。

      $ sudo ip rule add from 192.168.10.20 table 1
      $ sudo ip route add 192.168.10.0/24 dev enp0s3 scope link table 1
      $ sudo ip route add default via 192.168.10.1 dev enp0s3 table 1
      
      $ sudo ip rule add from 192.168.20.20 table 2
      $ sudo ip route add 192.168.20.0/24 dev enp0s8 scope link table 2
      $ sudo ip route add default via 192.168.20.1 dev enp0s8 table 2
      
      $ ip route show table 1
      default via 192.168.10.1 dev enp0s3
      192.168.10.0/24 dev enp0s3 scope link
      $ ip route show table 2
      default via 192.168.20.1 dev enp0s8
      192.168.20.0/24 dev enp0s8 scope link
    • この設定を行わない場合は、送信元が192.168.20.20のパケットもenp0s3から送信されます。

      • 公式wikiでさりげなく書いてあるのですが、割と見落としがちですね。

      Configure the routing rules to use multiple subflows from multiple interfaces: Configure Routing

  • iperfをクライアントとして起動します。

    $ iperf -c 192.168.30.20
  • SystemTapを実行したコンソール上に以下の出力が追加されます。

    command             iperf mptcpified
  • tcpdumpを実行したそれぞれのネットワークインタフェースからmptcpオプション付のパケットが出力されるのが確認できます。

おわりに
#

いくつかハマりポイントがありましたが、なんとかUbuntuで複数サブフローを使ったMPTCP通信を行うことができました。みなさんもぜひこれを機会にMPTCPを試してみてはいかがでしょうか。

関連記事

MPTCPのパケットを眺めてみる(その2)

前回は、実際に送信されるMPTCPのMP_CAPABLEのパケットを眺めてみました。 今回は、ADD_ADDR、DSS、DSS FIN、MP_JOINのフォーマットと実際のパケットを見比べてみます。 MPTCPのオプションは、前回の内容を参考にしてください。

MPTCPのパケットを眺めてみる(その1)

前回は、 RFC 6824 で定義されているMPTCPについて紹介しました。 今回は、TCPのオプション領域でやり取りされるMPTCPのシグナリングパケットを簡単に説明し、RFCで定義されているオプションのフォーマットとMPTCPのMP_CAPABLEが付いた実際のパケットを見比べてみようと思います。

Multipath TCP(MPTCP)

Multipath TCP(MPTCP)は、複数のIPアドレス/インタフェースを同時に使用して通信することで、通信のスループットを向上させるという機能です。また、あるIPアドレス/インタフェースが使用できない状態になっても別のIPアドレス/インタフェースを使うことで、通信を継続できるという耐障害性も兼ね備えています。 そんな夢のようなMPTCPに触れる機会があったので、簡単にご紹介します。