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

従来、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通信を試してみました。
 
 

参考サイト

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

  • Linux MPTCP Upstream Project
  • Using Upstream MPTCP Linux Systems
  • Multipath TCP on Red Hat Enterprise Linux 8.3: From 0 to 1 subflows
  • CHAPTER 25. GETTING STARTED WITH MULTIPATH TCP
  •  
     

    環境構築

    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を試してみてはいかがでしょうか。

    コメントを残す

    メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

    *