TCP-BPF: Linuxはマイクロカーネルの夢を見るか

eBPFでcommit logを調べてみるといろいろと面白そうなものが出てくるな。例えば、TCP-BPF [netdev 2.2]。TCPコネクションのパラメータをBPFで操作できる。さらに最近(バージョン5.5以降)では、輻輳制御もeBPFで実装できるようになっているようだ。eBPFによりカーネルからどんどん機能を追い出してLinuxはマイクロカーネル化するのだという鼻息荒い発表も見かけるが(「eBPF - Rethinking the Linux Kernel」[QCon2020])、正直これが正しい方向性なのかよくわからない。面白いけど。

eBPFを使っているわけではないが、輻輳制御をユーザレベルで実装するという研究はいくつかある(「Restructuring Endpoint Congestion Control」 [SIGCOMM2018]、「Deploying Safe User-Level Network Services with icTCP」 [OSDI2004])。前者で提案しているPortusでは、Rust言語で書いた輻輳制御アルゴリズムをLinux kernel、mTCP/DPDK、QUICで動かすことができる。つまりwrite-once run-anywhereを実現している。

commit 40304b2a1567fecc321f640ee4239556dd0f3ee0
Author: Lawrence Brakmo <brakmo@fb.com>
Date:   Fri Jun 30 20:02:40 2017 -0700

   bpf: BPF support for sock_ops

さて話をTCP-BPFに戻す。パッチがマージされたのはバージョン4.12の頃。BPF_PROG_TYPE_SOCK_OPSというプログラムタイプが追加され、ソケットのフィールド、例えばIPアドレスとかポート番号にeBPFプログラムからアクセスできるようになった。この以前にもBPF_PROG_TYPE_CGROUP_SOCK型が存在したが、これはコネクションの生存期間に1度しか呼ばれないことを前提としていた。一方、BPF_PROG_TYPE_SOCK_OPS型は複数回呼び出されても大丈夫。下記にサンプルプログラムを示すが、opフィールドに指定されたイベントが発生したら、このeBPFプログラムがコールバックされる。コールバックの仕掛けはあらかじめTCPスタックに埋め込まれている。

samples/bpf/tcp_cong_kern.c

SEC("sockops")
int bpf_cong(struct bpf_sock_ops *skops)
{
       char cong[] = "dctcp";
       int rv = 0;
       int op;
<snip>
       op = (int) skops->op;
<snip>
        /* Check if both hosts are in the same datacenter. For this
        * example they are if the 1st 5.5 bytes in the IPv6 address
        * are the same.
        */
       if (skops->family == AF_INET6 &&
           skops->local_ip6[0] == skops->remote_ip6[0] &&
           (bpf_ntohl(skops->local_ip6[1]) & 0xfff00000) ==
           (bpf_ntohl(skops->remote_ip6[1]) & 0xfff00000)) {
               switch (op) {
               case BPF_SOCK_OPS_NEEDS_ECN:
                       rv = 1;
                       break;
               case BPF_SOCK_OPS_ACTIVE_ESTABLISHED_CB:
                       rv = bpf_setsockopt(skops, SOL_TCP, TCP_CONGESTION,
                                           cong, sizeof(cong));
                       break;
               case BPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB:
                       rv = bpf_setsockopt(skops, SOL_TCP, TCP_CONGESTION,
                                           cong, sizeof(cong));
                       break;
               default:
                       rv = -1;
               }
       } else {
               rv = -1;
       }

このサンプルでは、IPv6アドレスの先頭を見てデータセンタ間の通信であれば輻輳制御をDCTCPに設定するものだ。FacebookではDCTCP(RFC8257)を使っているのだろうか? 他にも初期輻輳ウィンドウサイズを制限するサンプルなんかもある。確かにこの手の制御を実装しようと思うと、setsockoptでも可能だけど、プログラムを修正する必要があるし、現実的じゃない。sysctlだとフロー毎に制御できない。ということでeBPFの出番になるわけだ。

commit 417759f7d4cf44a5fb526fbafcc9372e3dbfc0ae
Merge: 65726b5b7efa 09903869f69f
Author: Alexei Starovoitov <ast@kernel.org>
Date:   Thu Jan 9 08:46:19 2020 -0800

   Merge branch 'tcp-bpf-cc'

さらに、バージョン5.5の頃、eBPFでTCP輻輳制御を実装するためのパッチ群がコミットされている。これはbpf-nextツリーからのマージリクエストのようだ。tools/testing/selftests/bpf以下にDCTCPとCUBICの実装サンプルが存在する。

commit 0baf26b0fcd74bbfcef53c5d5e8bad2b99c8d0d2
Author: Martin KaFai Lau <kafai@fb.com>
Date:   Wed Jan 8 16:35:08 2020 -0800

   bpf: tcp: Support tcp_congestion_ops in bpf
   
   This patch makes "struct tcp_congestion_ops" to be the first user
   of BPF STRUCT_OPS.  It allows implementing a tcp_congestion_ops
   in bpf.

中途半端だけど、今日はここまで。

この記事が気に入ったらサポートをしてみませんか?