Upgrade to Pro — share decks privately, control downloads, hide ads and more …

「おいしくてつよくなる」eBPFのはじめかた/Learn eBPF

「おいしくてつよくなる」eBPFのはじめかた/Learn eBPF

Yuki Nakata chikuwait

June 27, 2020
Tweet

More Decks by Yuki Nakata chikuwait

Other Decks in Programming

Transcript


  1. 「おいしくてつよくなる」

    eBPFのはじめかた

    オープンソースカンファレンス2020 Online/Hokkaido

    2020/6/27

    公立はこだて未来大学大学院 高度ICT領域

    システムソフトウェア研究室

    中田 裕貴


    View Slide

  2. 自己紹介

    2

    中田 裕貴 / chikuwait

    Twitter: chiku_wait GitHub: chikuwait


    ● 公立はこだて未来大学大学院システム情報科学研究科

    高度ICT領域 M1

    ● システムソフトウェア研究室

    ● 仮想化技術(ハイパーバイザ,コンテナ)に関する研究

    ● ハイパーバイザからOS,L2〜L4ネットワーク,

    Kubernetesまで広く浅く触ってます

    ● 最近パケットが個人的にアツい




    View Slide

  3. 本日のセミナーの内容

    3

    ```

    ネットワークやシステム監視,トレースといった場面でeBPFの名前を

    聞くことが多くなってきました.このセミナーでは,

    日頃ハイパーバイザやコンテナをいじっている低レイヤ大好き学生が,Hello World
    からパケット処理までゆるく解説していきます.

    ```


    人々にeBPFの雰囲気を知ってもらって,低レイヤ沼に落ちてほしい!


    View Slide

  4. 本日のセミナーのゴール

    4

    1. eBPFについて知ってもらう

    ○ eBPFで何ができるのか

    ○ eBPFのしくみ


    2. eBPFの使い方(はじめかた)を知ってもらう

    ○ eBPFプログラムの書き方


    View Slide

  5. BPFとは?

    5

    BPF = Berkeley Packet Filter

    ● 1992年にUNIX(BSD)上でパケットキャプチャ・フィルタリングを

    効率的にするためのソフトウェアとして開発[1]

    ○ カーネル空間でパケットフィルタリングを実施

    ○ ユーザ/カーネル空間の切り替えによるオーバヘッドの削減

    ○ データコピーの削減


    ● 1997年に,Linuxカーネル2.1.75に移植




    [1]Steven McCanne and Van Jacobson. 1993. The BSD packet filter: a new architecture for user-level packet capture. In Proceedings of the USENIX Winter 1993
    Conference Proceedings on USENIX Winter 1993 Conference Proceedings (USENIX’93). USENIX Association, USA, 2.

    View Slide

  6. BPFの概要図[1]

    6

    [1]Steven McCanne and Van Jacobson. 1993. The BSD packet filter: a new architecture for user-level packet capture. In Proceedings of the USENIX Winter 1993 Conference
    Proceedings on USENIX Winter 1993 Conference Proceedings (USENIX’93). USENIX Association, USA, 2.


    View Slide

  7. BPFはカーネルモジュールなのか?

    7

    ● Linuxには,カーネルモジュール(Linuxの機能を拡張する仕組み)がある

    ○ なんでも出来る(自由度が高すぎる)ため,安全性を保証できない


    ● BPFはLinuxカーネル内で動作する独自のレジスタマシンと

    命令セットを持った仮想マシン

    ○ 仮想マシン内で動作するプログラムを記述して使用

    ○ カーネルモジュールと違って,使えるLinuxの機能やプログラムの文法に

    制限がある

    ○ ユーザ空間で作ったプログラムがカーネルをクラッシュさせたり,

    破壊しないように検証する機構を持つ




    View Slide

  8. 一番身近(!?)なBPFの例:tcpdump

    8

    ● tcpdumpは,-dオプションを使うとBPFプログラムを出力できる





    $ sudo tcpdump -d -i lo src 127.0.0.1 and port 80

    (000) ldh [12]                  

    (001) jeq #0x800 jt 2 jf 16 

    (002) ld [26] 

    (003) jeq #0x7f000001

    (004) ldb [23] 

    (005) jeq #0x84 jt 8 jf 6

    (006) jeq #0x6 jt 8 jf 7

    (007) jeq #0x11 jt 8 jf 16

    (008) ldh [20] 

    ...

    #Offset 12(EtherType)

    #Is IPv4?

    #Offset 26 (Destination IP Address)

    #Is 127.0.0.1?

    #Offset 23 (IPv4 Protocol Field)

    #Is SCTP?

    # Is TCP?

    # Is UDP?

    # Offset 20(IPv4 Flag+ Fragment)


    View Slide

  9. eBPFの登場

    9

    2014年に,BPFをより汎用的なカーネル内仮想マシン

    にするための拡張が登場(Linuxカーネル3.14)[2]

    ● 拡張されたBPFはeBPF(extended BPF)と呼ばれる

    ● 対して,従来のBPFはcBPF(classic BPF)と呼ばれることがある

    ● パケット以外にも,カーネル内のあらゆる操作をフックしてトレース

    できるようになった



    [2]Daniel Borkmann,https://lore.kernel.org/netdev/[email protected]/T/

    View Slide

  10. eBPFで拡張された内容(一部)

    10

    ● 命令セットの一新

    ● 使用可能なレジスタの増加(2個->10個)

    ● レジスタ幅(32bit->64bit)

    ● スタックサイズ(16Byte -> 512Byte)

    ● 外部関数(カーネル内関数)呼び出し

    ● ヘルパー関数の実装

    ● map(ユーザ/カーネル空間,BPFプログラム間でのデータのやりとりに使用)の
    追加

    ● 他のBPFプログラムへのジャンプ




    View Slide

  11. eBPFの全体像

    11

    ユーザ空間
 カーネル空間

    BPF
    プログラム
    バイト
    コード
    Verifier
    BPF
    map
    ユーザ
    プログラム
    C言語で書かれた
    BPFプログラムを
    LLVM/Clangでコンパイル
    sockets
    trace_points
    (カーネル空間の
    静的トレース)
    uprobes
    (ユーザ空間の
    動的トレース)
    kprobes
    (カーネル空間の
    動的トレース)
    bpf(2)


    View Slide

  12. eBPFの全体像

    12

    ユーザ空間
 カーネル空間

    BPF
    プログラム
    バイト
    コード
    Verifier
    BPF
    bpf(2)

    map
    ユーザ
    プログラム
    bpfシステムコールを
    利用してカーネルに
    ロード
    sockets
    trace_points
    (カーネル空間の
    静的トレース)
    uprobes
    (ユーザ空間の
    動的トレース)
    kprobes
    (カーネル空間の
    動的トレース)

    View Slide

  13. eBPFの全体像

    13

    ユーザ空間
 カーネル空間

    BPF
    プログラム
    バイト
    コード
    Verifier
    BPF
    bpf(2)

    map
    ユーザ
    プログラム
    Verifier(検証器)で
    プログラムの安全性を
    確認してロードを完了
    sockets
    trace_points
    (カーネル空間の
    静的トレース)
    uprobes
    (ユーザ空間の
    動的トレース)
    kprobes
    (カーネル空間の
    動的トレース)

    View Slide

  14. eBPFの全体像

    14

    ユーザ空間
 カーネル空間

    BPF
    プログラム
    バイト
    コード
    Verifier
    BPF
    bpf(2)

    map
    ユーザ
    プログラム
    BPFプログラムを,
    これらのイベントソースを
    使ってアタッチする
    sockets
    trace_points
    (カーネル空間の
    静的トレース)
    uprobes
    (ユーザ空間の
    動的トレース)
    kprobes
    (カーネル空間の
    動的トレース)

    View Slide

  15. eBPFの全体像

    15

    ユーザ空間
 カーネル空間

    BPF
    プログラム
    バイト
    コード
    Verifier
    BPF
    bpf(2)

    map
    ユーザ
    プログラム
    イベントが発生すると,
    BPFプログラムが呼び出され,
    処理を実行
    sockets
    trace_points
    (カーネル空間の
    静的トレース)
    uprobes
    (ユーザ空間の
    動的トレース)
    kprobes
    (カーネル空間の
    動的トレース)

    View Slide

  16. eBPFの全体像

    16

    ユーザ空間
 カーネル空間

    BPF
    プログラム
    バイト
    コード
    Verifier
    BPF
    bpf(2)

    map
    ユーザ
    プログラム
    BPFマップにアクセスして
    データを読み書きして保存
    sockets
    trace_points
    (カーネル空間の
    静的トレース)
    uprobes
    (ユーザ空間の
    動的トレース)
    kprobes
    (カーネル空間の
    動的トレース)

    View Slide

  17. eBPFの全体像

    17

    ユーザ空間
 カーネル空間

    BPF
    プログラム
    バイト
    コード
    Verifier
    BPF
    bpf(2)

    map
    ユーザ
    プログラム
    必要な場合はユーザ空間で
    取得
    sockets
    trace_points
    (カーネル空間の
    静的トレース)
    uprobes
    (ユーザ空間の
    動的トレース)
    kprobes
    (カーネル空間の
    動的トレース)

    View Slide

  18. eBPFの利用例

    18

    ● ネットワーク用途

    ○ パケットフィルタリング

    ○ トラフィック制御

    ○ トンネリング

    ● トレーシング用途

    ○ カーネルのトレーシング

    ○ ユーザ空間プログラムのトレーシング

    ● セキュリティ用途

    ○ システムコールフィルタリング




    View Slide

  19. eBPFを使ったOSS・プロジェクト例:BCC[3]

    19

    BCC(BPF Compiler Collection)

    ● eBPFのプログラム作成のためフレームワーク・ツール群

    ○ eBPFプログラムを簡単に記述するためのCの方言(eBPF C)を提供

    ○ eBPFプログラムのフロントエンドを書く

    PythonやC++,Luaでのバインディング

    を持つ

    ● eBPFを使ったパフォーマンス分析を同封


    ● セミナー後半で説明する

    eBPFプログラムの作成で使用します



    bcc tool bcc tool
    bcc
    python Lua
    Kernel
    BPF
    events
    [3]IO Visor Project, BCC - Tools for BPF-based Linux IO analysis, networking, monitoring, and more, https://github.com/iovisor/bcc

    View Slide

  20. BCC tools[3]

    20

    [3]IO Visor Project, BCC - Tools for BPF-based Linux IO analysis, networking, monitoring, and more, https://github.com/iovisor/bcc

    View Slide

  21. eBPFを使ったOSS・プロジェクト例: XDP[4]

    21

    XDP(eXpress Data Path)

    ● Linuxカーネル内に実装した高速パケット処理基盤

    ● NICドライバにeBPFプログラムをアタッチ

    ○ パケットがネットワークスタックに

    到達する前にフィルタリングや転送

    ○ XDP対応Smart NICを使うと,

    ハードウェアオフロードもできる

    ● 高速パケット処理とLinuxの機能の活用を

    両立できる



    [4]IO Visor Project, XDP eXpress Data Path, https://www.iovisor.org/technology/xdp

    Application
    Kernel
    Network
    Stack
    Driver
    XDP

    View Slide

  22. eBPFを使ったOSS・プロジェクト例: Cilium[5]

    22

    [5]Cilium, API-aware Networking and Security using eBPF and XDP, https://github.com/cilium/cilium

    Ciliumの概念図[5]
    KubernetesのネットワーキングにeBPF/XDPを使用

    ● コンテナ間のネットワーキングやロードバランシング,

    セキュリティポリシーを提供

    ● eBPFでiptablesを置き換え,

    高パフォーマンスな

    ネットワーキングを実現


    View Slide

  23. eBPFの仕組みや用途のまとめ

    23

    ● BPF = Berkeley Packet Filter

    ○ パケットキャプチャ・フィルタリングを効率的にするために開発

    ○ BPFはLinuxカーネル内で動作する独自のレジスタマシンと

    命令セットを持った仮想マシン

    ● eBPFはBPFを汎用的なカーネル内仮想マシンにするための拡張

    ○ パケット以外にも,カーネル内のあらゆる操作をフックして

    トレースできるようになった

    ○ eBPF mapを使ってユーザ/カーネル空間,BPFプログラム間で

    データのやりとりができるようになった

    ● BCC(BPF Compiler Collection)

    ○ eBPFプログラムを簡単に記述するためのCの方言(eBPF C)を使える

    ○ eBPFプログラムのフロントエンドをPythonやC++,Luaなどで書ける


    View Slide

  24. 24
    Hello worldからはじめるeBPF入門


    View Slide

  25. おしながき

    25

    1. Hello World!

    2. TCPコネクションをトレース

    3. コンテナのTCPコネクションをトレース

    4. XDPを使ってICMPパケット(ping)をドロップ



    View Slide

  26. 前提環境

    26

    ● Ubuntu 18.04LTS

    ● Kernel 4.15

    ● Python 3.6.9




    View Slide

  27. bccのインストール

    27

    ● IOVisorのaptリポジトリを使用








    ● インストールの確認




    $ sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 4052245BD4284CDD

    $ echo " deb https://repo.iovisor.org/apt/ $( lsb_release -cs ) $( lsb_release -cs ) \

    main " | sudo tee /etc/apt/sources.list.d/iovisor.list

    $ sudo apt update

    $ sudo apt install bcc-tools libbcc-examples linux-headers- $( uname -r ) python3-bcc

    $ python3 -c "import bcc"


    View Slide

  28. Hello World! 

    28

    ● 全体のイメージ

    ○ cloneシステムコール(子プロセスの作成)が

    呼ばれた時に,Hello Worldを出力





    ユーザ空間

    カーネル空間

    Application
    A
    Application
    B
    clone()システムコール

    呼び出し

    clone
    BPFプログラム
    int trace_clone()
    BPFプログラム
    フロントエンド(python)

    View Slide

  29. Hello World! 

    29

    ● cloneシステムコール(子プロセスの作成)が呼ばれた時に,

    Hello Worldを出力する





    from bcc import BPF


    bpf_code ="""

    int trace_clone(void *ctx){

    bpf_trace_printk("Hello, eBPF world!\\n");

    return 0;

    }

    """


    bpf = BPF(text=bpf_code)

    sysclone_name = bpf.get_syscall_fnname("clone")

    bpf.attach_kprobe(event = sysclone_name, fn_name = "trace_clone")

    bpf.trace_print()


    View Slide

  30. Hello World! 

    30

    ● cloneシステムコール(子プロセスの作成)が呼ばれた時に,

    Hello Worldを出力する





    from bcc import BPF


    bpf_code ="""

    int trace_clone(void *ctx){

    bpf_trace_printk("Hello, eBPF world!\\n");

    return 0;

    }

    """


    bpf = BPF(text=bpf_code)

    sysclone_name = bpf.get_syscall_fnname("clone")

    bpf.attach_kprobe(event = sysclone_name, fn_name = "trace_clone")

    bpf.trace_print()

    Pythonのbpf_code変数に
    cloneシステムコールが
    呼ばれた際の処理を記述

    View Slide

  31. Hello World! 

    31

    ● cloneシステムコール(子プロセスの作成)が呼ばれた時に,

    Hello Worldを出力する





    from bcc import BPF


    bpf_code ="""

    int trace_clone(void *ctx){

    bpf_trace_printk("Hello, eBPF world!\\n");

    return 0;

    }

    """


    bpf = BPF(text=bpf_code)

    sysclone_name = bpf.get_syscall_fnname("clone")

    bpf.attach_kprobe(event = sysclone_name, fn_name = "trace_clone")

    bpf.trace_print()

    bpf_trace_printk()はBPFプログラムから
    ユーザ空間にメッセージを渡す関数
    /sys/kernel/debug/tracing/trace_pipeに出力
    ※性能に影響があるので,デバッグ用途

    View Slide

  32. Hello World! 

    32

    ● cloneシステムコール(子プロセスの作成)が呼ばれた時に,

    Hello Worldを出力する





    from bcc import BPF


    bpf_code ="""

    int trace_clone(void *ctx){

    bpf_trace_printk("Hello, eBPF world!\\n");

    return 0;

    }

    """


    bpf = BPF(text=bpf_code)

    sysclone_name = bpf.get_syscall_fnname("clone")

    bpf.attach_kprobe(event = sysclone_name, fn_name = "trace_clone")

    bpf.trace_print()

    BPFプログラムをカーネルにロードする
    ※ループなどeBPFの検証器を許可しない
    コードが存在する場合,ロードできない

    View Slide

  33. Hello World! 

    33

    ● cloneシステムコール(子プロセスの作成)が呼ばれた時に,

    Hello Worldを出力する





    from bcc import BPF


    bpf_code ="""

    int trace_clone(void *ctx){

    bpf_trace_printk("Hello, eBPF world!\\n");

    return 0;

    }

    """


    bpf = BPF(text=bpf_code)

    sysclone_name = bpf.get_syscall_fnname("clone")

    bpf.attach_kprobe(event = sysclone_name, fn_name = "trace_clone")

    bpf.trace_print()

    cloneシステムコールにアタッチするために,
    カーネル内の関数名を取得する

    View Slide

  34. Hello World! 

    34

    ● cloneシステムコール(子プロセスの作成)が呼ばれた時に,

    Hello Worldを出力する





    from bcc import BPF


    bpf_code ="""

    int trace_clone(void *ctx){

    bpf_trace_printk("Hello, eBPF world!\\n");

    return 0;

    }

    """


    bpf = BPF(text=bpf_code)

    sysclone_name = bpf.get_syscall_fnname("clone")

    bpf.attach_kprobe(event = sysclone_name, fn_name = "trace_clone")

    bpf.trace_print()

    attach_kprobeを使って,
    cloneシステムコールが呼び出された時に
    BPFプログラムのtrace_cloneが呼び出すように関
    連付けする

    View Slide

  35. Hello World! 

    35

    ● cloneシステムコール(子プロセスの作成)が呼ばれた時に,

    Hello Worldを出力する





    from bcc import BPF


    bpf_code ="""

    int trace_clone(void *ctx){

    bpf_trace_printk("Hello, eBPF world!\\n");

    return 0;

    }

    """


    bpf = BPF(text=bpf_code)

    sysclone_name = bpf.get_syscall_fnname("clone")

    bpf.attach_kprobe(event = sysclone_name, fn_name = "trace_clone")

    bpf.trace_print()

    /sys/kernel/debug/tracing/trace_pipeで
    受け取ったメッセージを出力

    View Slide

  36. Hello World! 

    36


    View Slide

  37. TCPのコネクションをトレース

    37

    ● tcp_v4_connectにアタッチ

    ○ tcp_v4_connectが呼ばれた時と,

    returnした時の2段階でフック




    ユーザ空間

    カーネル空間

    Application
    A
    Application
    B
    TCPパケット送信

    tcp_v4_connect()
    BPFプログラム
    int tcp_connect()
    BPFプログラム
    フロントエンド(python)
    eBPF map
    BPFプログラム
    int tcp_connect_ret()
    リングバッファ


    View Slide

  38. TCPのコネクションをトレース

    38

    ● tcp_v4_connectが呼ばれた時のフック





    #include 

    #include 

    BPF_HASH(socklist, u32, struct sock *);


    int tcp_connect(struct pt_regs *ctx, struct sock *sock){

    u32 pid = bpf_get_current_pid_tgid();

    socklist.update(&pid, &sock);


    return 0;

    }



    View Slide

  39. TCPのコネクションをトレース

    39

    ● tcp_v4_connectが呼ばれた時のフック





    #include 

    #include 

    BPF_HASH(socklist, u32, struct sock *);


    int tcp_connect(struct pt_regs *ctx, struct sock *sock){

    u32 pid = bpf_get_current_pid_tgid();

    socklist.update(&pid, &sock);


    return 0;

    }



    eBPF mapを使って,
    sockistという名前でsock構造体のアドレス
    を格納する連想配列を定義

    View Slide

  40. TCPのコネクションをトレース

    40

    ● tcp_v4_connectが呼ばれた時のフック





    #include 

    #include 

    BPF_HASH(socklist, u32, struct sock *);


    int tcp_connect(struct pt_regs *ctx, struct sock *sock){

    u32 pid = bpf_get_current_pid_tgid();

    socklist.update(&pid, &sock);


    return 0;

    }



    bpf_get_current_pid_tgidという
    ヘルパー関数を使って,プロセス IDを取得

    View Slide

  41. TCPのコネクションをトレース

    41

    ● tcp_v4_connectが呼ばれた時のフック





    #include 

    #include 

    BPF_HASH(socklist, u32, struct sock *);


    int tcp_connect(struct pt_regs *ctx, struct sock *sock){

    u32 pid = bpf_get_current_pid_tgid();

    socklist.update(&pid, &sock);


    return 0;

    }



    プロセスIDをキーにして
    sock構造体のアドレスを格納

    View Slide

  42. TCPのコネクションをトレース

    42

    ● tcp_v4_connectがreturnした際にユーザ空間にデータを渡す定義






    struct data_t{

    u32 pid;

    char comm[TASK_COMM_LEN];

    u32 saddr;

    u32 daddr;

    u16 dport;

    };


    BPF_PERF_OUTPUT(events);

    BPF_PERF_OUTPUTを使って
    ユーザ空間との任意のデータ構造
    の受け渡しをリングバッファでおこなう

    View Slide

  43. TCPのコネクションをトレース

    43

    ● tcp_v4_connectがreturnした際にユーザ空間にデータを渡す定義





    struct data_t{

    u32 pid;

    char comm[TASK_COMM_LEN];

    u32 saddr;

    u32 daddr;

    u16 dport;

    };


    BPF_PERF_OUTPUT(events);

    プロセスID,コマンド名,送信元アドレス,
    宛先アドレス,宛先ポートをメンバに持つ
    data_t構造体を定義

    View Slide

  44. TCPのコネクションをトレース

    44

    ● tcp_v4_connectがreturnするときのフック





    int tcp_connect_ret(struct pt_regs *ctx){ 

    u32 pid = bpf_get_current_pid_tgid(), dport; 

    struct sock **sock, *sockp; 

    struct data_t data = {}; 

    struct task_struct *task = (struct task_struct *)bpf_get_current_task(); 

    sock = socklist.lookup(&pid); 

    if(sock == 0){ 

    return 0;

    }

    sockp = *sock; 

    data.pid = pid; 

    bpf_get_current_comm(&data.comm, sizeof(data.comm)); 

    data.saddr = sockp->__sk_common.skc_rcv_saddr; 

    data.daddr = sockp->__sk_common.skc_daddr; 

    dport = sockp->__sk_common.skc_dport; 

    data.dport = ntohs(dport); 

    events.perf_submit(ctx, &data, sizeof(data)); 

    socklist.delete(&pid); 

    return 0;

    }


    View Slide

  45. TCPのコネクションをトレース

    45

    ● tcp_v4_connectがreturnするときのフック





    int tcp_connect_ret(struct pt_regs *ctx){ 

    u32 pid = bpf_get_current_pid_tgid(), dport;

    struct sock **sock, *sockp; 

    struct data_t data = {}; 

    struct task_struct *task = (struct task_struct *)bpf_get_current_task(); 

    sock = socklist.lookup(&pid); 

    if(sock == 0){ 

    return 0;

    }

    sockp = *sock; 

    data.pid = pid; 

    bpf_get_current_comm(&data.comm, sizeof(data.comm)); 

    data.saddr = sockp->__sk_common.skc_rcv_saddr; 

    data.daddr = sockp->__sk_common.skc_daddr; 

    dport = sockp->__sk_common.skc_dport; 

    data.dport = ntohs(dport); 

    events.perf_submit(ctx, &data, sizeof(data)); 

    socklist.delete(&pid); 

    return 0;

    }

    プロセスIDを取得

    View Slide

  46. TCPのコネクションをトレース

    46

    ● tcp_v4_connectがreturnするときのフック





    int tcp_connect_ret(struct pt_regs *ctx){ 

    u32 pid = bpf_get_current_pid_tgid(), dport; 

    struct sock **sock, *sockp; 

    struct data_t data = {}; 

    struct task_struct *task = (struct task_struct *)bpf_get_current_task(); 

    sock = socklist.lookup(&pid); 

    if(sock == 0){ 

    return 0;

    }

    sockp = *sock; 

    data.pid = pid; 

    bpf_get_current_comm(&data.comm, sizeof(data.comm)); 

    data.saddr = sockp->__sk_common.skc_rcv_saddr; 

    data.daddr = sockp->__sk_common.skc_daddr; 

    dport = sockp->__sk_common.skc_dport; 

    data.dport = ntohs(dport); 

    events.perf_submit(ctx, &data, sizeof(data)); 

    socklist.delete(&pid); 

    return 0;

    }

    bpf_get_current_task()という
    ヘルパー関数を使って,
    task_struct構造体(プロセスに関する
    情報が詰まっている )を取得

    View Slide

  47. TCPのコネクションをトレース

    47

    ● tcp_v4_connectがreturnするときのフック





    int tcp_connect_ret(struct pt_regs *ctx){ 

    u32 pid = bpf_get_current_pid_tgid(), dport; 

    struct sock **sock, *sockp; 

    struct data_t data = {}; 

    struct task_struct *task = (struct task_struct *)bpf_get_current_task(); 

    sock = socklist.lookup(&pid); 

    if(sock == 0){ 

    return 0;

    }

    sockp = *sock; 

    data.pid = pid; 

    bpf_get_current_comm(&data.comm, sizeof(data.comm)); 

    data.saddr = sockp->__sk_common.skc_rcv_saddr; 

    data.daddr = sockp->__sk_common.skc_daddr; 

    dport = sockp->__sk_common.skc_dport; 

    data.dport = ntohs(dport); 

    events.perf_submit(ctx, &data, sizeof(data)); 

    socklist.delete(&pid); 

    return 0;

    }

    取得したプロセスIDを使って,
    連想配列からsock構造体のアドレスを取得

    View Slide

  48. TCPのコネクションをトレース

    48

    ● tcp_v4_connectがreturnするときのフック





    int tcp_connect_ret(struct pt_regs *ctx){ 

    u32 pid = bpf_get_current_pid_tgid(), dport; 

    struct sock **sock, *sockp; 

    struct data_t data = {}; 

    struct task_struct *task = (struct task_struct *)bpf_get_current_task(); 

    sock = socklist.lookup(&pid); 

    if(sock == 0){ 

    return 0;

    }

    sockp = *sock; 

    data.pid = pid; 

    bpf_get_current_comm(&data.comm, sizeof(data.comm)); 

    data.saddr = sockp->__sk_common.skc_rcv_saddr; 

    data.daddr = sockp->__sk_common.skc_daddr; 

    dport = sockp->__sk_common.skc_dport; 

    data.dport = ntohs(dport); 

    events.perf_submit(ctx, &data, sizeof(data)); 

    socklist.delete(&pid); 

    return 0;

    }

    プロセスIDを
    data_t構造体の中に格納

    View Slide

  49. TCPのコネクションをトレース

    49

    ● tcp_v4_connectがreturnするときのフック





    int tcp_connect_ret(struct pt_regs *ctx){ 

    u32 pid = bpf_get_current_pid_tgid(), dport; 

    struct sock **sock, *sockp; 

    struct data_t data = {}; 

    struct task_struct *task = (struct task_struct *)bpf_get_current_task(); 

    sock = socklist.lookup(&pid); 

    if(sock == 0){ 

    return 0;

    }

    sockp = *sock; 

    data.pid = pid; 

    bpf_get_current_comm(&data.comm, sizeof(data.comm)); 

    data.saddr = sockp->__sk_common.skc_rcv_saddr; 

    data.daddr = sockp->__sk_common.skc_daddr; 

    dport = sockp->__sk_common.skc_dport; 

    data.dport = ntohs(dport); 

    events.perf_submit(ctx, &data, sizeof(data)); 

    socklist.delete(&pid); 

    return 0;

    }

    bpf_get_current_commヘルパーを
    使ってコマンド名を取得し,
    data_t構造体の中に格納

    View Slide

  50. TCPのコネクションをトレース

    50

    ● tcp_v4_connectがreturnするときのフック





    int tcp_connect_ret(struct pt_regs *ctx){ 

    u32 pid = bpf_get_current_pid_tgid(), dport; 

    struct sock **sock, *sockp; 

    struct data_t data = {}; 

    struct task_struct *task = (struct task_struct *)bpf_get_current_task(); 

    sock = socklist.lookup(&pid); 

    if(sock == 0){ 

    return 0;

    }

    sockp = *sock; 

    data.pid = pid; 

    bpf_get_current_comm(&data.comm, sizeof(data.comm)); 

    data.saddr = sockp->__sk_common.skc_rcv_saddr; 

    data.daddr = sockp->__sk_common.skc_daddr; 

    dport = sockp->__sk_common.skc_dport; 

    data.dport = ntohs(dport); 

    events.perf_submit(ctx, &data, sizeof(data)); 

    socklist.delete(&pid); 

    return 0;

    }

    sock構造体を使って,
    送信元,宛先アドレスを
    data_t構造体の中に格納

    View Slide

  51. TCPのコネクションをトレース

    51

    ● tcp_v4_connectがreturnするときのフック





    int tcp_connect_ret(struct pt_regs *ctx){ 

    u32 pid = bpf_get_current_pid_tgid(), dport; 

    struct sock **sock, *sockp; 

    struct data_t data = {}; 

    struct task_struct *task = (struct task_struct *)bpf_get_current_task(); 

    sock = socklist.lookup(&pid); 

    if(sock == 0){ 

    return 0;

    }

    sockp = *sock; 

    data.pid = pid; 

    bpf_get_current_comm(&data.comm, sizeof(data.comm)); 

    data.saddr = sockp->__sk_common.skc_rcv_saddr; 

    data.daddr = sockp->__sk_common.skc_daddr; 

    dport = sockp->__sk_common.skc_dport; 

    data.dport = ntohs(dport); 

    events.perf_submit(ctx, &data, sizeof(data)); 

    socklist.delete(&pid); 

    return 0;

    }

    sock構造体を使って,
    宛先ポート番号を
    data_t構造体の中に格納

    View Slide

  52. TCPのコネクションをトレース

    52

    ● tcp_v4_connectがreturnするときのフック





    int tcp_connect_ret(struct pt_regs *ctx){ 

    u32 pid = bpf_get_current_pid_tgid(), dport; 

    struct sock **sock, *sockp; 

    struct data_t data = {}; 

    struct task_struct *task = (struct task_struct *)bpf_get_current_task(); 

    sock = socklist.lookup(&pid); 

    if(sock == 0){ 

    return 0;

    }

    sockp = *sock; 

    data.pid = pid; 

    bpf_get_current_comm(&data.comm, sizeof(data.comm)); 

    data.saddr = sockp->__sk_common.skc_rcv_saddr; 

    data.daddr = sockp->__sk_common.skc_daddr; 

    dport = sockp->__sk_common.skc_dport; 

    data.dport = ntohs(dport); 

    events.perf_submit(ctx, &data, sizeof(data)); 

    socklist.delete(&pid); 

    return 0;

    }

    perf_submitを使って,data_t構造体のアド
    レスをリングバッファに送信

    View Slide

  53. TCPのコネクションをトレース

    53

    ● tcp_v4_connectionがreturnするときのフック





    int tcp_connect_ret(struct pt_regs *ctx){ 

    u32 pid = bpf_get_current_pid_tgid(), dport; 

    struct sock **sock, *sockp; 

    struct data_t data = {}; 

    struct task_struct *task = (struct task_struct *)bpf_get_current_task(); 

    sock = socklist.lookup(&pid); 

    if(sock == 0){ 

    return 0;

    }

    sockp = *sock; 

    data.pid = pid; 

    bpf_get_current_comm(&data.comm, sizeof(data.comm)); 

    data.saddr = sockp->__sk_common.skc_rcv_saddr; 

    data.daddr = sockp->__sk_common.skc_daddr; 

    dport = sockp->__sk_common.skc_dport; 

    data.dport = ntohs(dport); 

    events.perf_submit(ctx, &data, sizeof(data)); 

    socklist.delete(&pid); 

    return 0;

    }

    連想配列から削除

    View Slide

  54. TCPのコネクションをトレース

    54

    ユーザ空間側




    from __future__ import print_function 

    from bcc import BPF 

    from bcc.utils import printb 

    def ntoa(addr):

    …(省略)


    def print_event(cpu, data, size): 

    event = bpf["events"].event(data) 

    printb(b"%-6d %-16s %-16s %-16s %-16d" % (event.pid, event.comm, ntoa(event.saddr), ntoa(event.daddr), event.dport)) 


    bpf = BPF(src_file = "trace.c"); 

    bpf.attach_kprobe(event = "tcp_v4_connect", fn_name = "tcp_connect") 

    bpf.attach_kretprobe(event = "tcp_v4_connect", fn_name = "tcp_connect_ret") 

    bpf["events"].open_perf_buffer(print_event) 



    print("%-6s %-16s %-16s %-16s %-16s" % ("PID", "COMMAND", "SOURCE-IPADDR", "DESTINATION-IPADDR", "DPORT")) 

    while 1:

    bpf.perf_buffer_poll() 


    View Slide

  55. TCPのコネクションをトレース

    55

    ユーザ空間側




    from __future__ import print_function 

    from bcc import BPF 

    from bcc.utils import printb 

    def ntoa(addr):

    …(省略)


    def print_event(cpu, data, size): 

    event = bpf["events"].event(data) 

    printb(b"%-6d %-16s %-16s %-16s %-16d" % (event.pid, event.comm, ntoa(event.saddr), ntoa(event.daddr), event.dport)) 


    bpf = BPF(src_file = "trace.c"); 

    bpf.attach_kprobe(event = "tcp_v4_connect", fn_name = "tcp_connect") 

    bpf.attach_kretprobe(event = "tcp_v4_connect", fn_name = "tcp_connect_ret") 

    bpf["events"].open_perf_buffer(print_event) 



    print("%-6s %-16s %-16s %-16s %-16s" % ("PID", "COMMAND", "SOURCE-IPADDR", "DESTINATION-IPADDR", "DPORT")) 

    while 1:

    bpf.perf_buffer_poll() 


    BPFプログラム読み込み

    View Slide

  56. TCPのコネクションをトレース

    56

    ユーザ空間側




    from __future__ import print_function 

    from bcc import BPF 

    from bcc.utils import printb 

    def ntoa(addr):

    …(省略)


    def print_event(cpu, data, size): 

    event = bpf["events"].event(data) 

    printb(b"%-6d %-16s %-16s %-16s %-16d" % (event.pid, event.comm, ntoa(event.saddr), ntoa(event.daddr), event.dport)) 


    bpf = BPF(src_file = "trace.c"); 

    bpf.attach_kprobe(event = "tcp_v4_connect", fn_name = "tcp_connect") 

    bpf.attach_kretprobe(event = "tcp_v4_connect", fn_name = "tcp_connect_ret") 

    bpf["events"].open_perf_buffer(print_event) 



    print("%-6s %-16s %-16s %-16s %-16s" % ("PID", "COMMAND", "SOURCE-IPADDR", "DESTINATION-IPADDR", "DPORT")) 

    while 1:

    bpf.perf_buffer_poll() 


    attach_kprobeを使って,
    tcp_v4_connectが呼び出された時に
    BPFプログラムのtcp_connectが
    呼び出すように関連付けする

    View Slide

  57. TCPのコネクションをトレース

    57

    ユーザ空間側




    from __future__ import print_function 

    from bcc import BPF 

    from bcc.utils import printb 

    def ntoa(addr):

    …(省略)


    def print_event(cpu, data, size): 

    event = bpf["events"].event(data) 

    printb(b"%-6d %-16s %-16s %-16s %-16d" % (event.pid, event.comm, ntoa(event.saddr), ntoa(event.daddr), event.dport)) 


    bpf = BPF(src_file = "trace.c"); 

    bpf.attach_kprobe(event = "tcp_v4_connect", fn_name = "tcp_connect") 

    bpf.attach_kretprobe(event = "tcp_v4_connect", fn_name = "tcp_connect_ret") 

    bpf["events"].open_perf_buffer(print_event) 



    print("%-6s %-16s %-16s %-16s %-16s" % ("PID", "COMMAND", "SOURCE-IPADDR", "DESTINATION-IPADDR", "DPORT")) 

    while 1:

    bpf.perf_buffer_poll() 


    attach_kprobeを使って,
    tcp_v4_connectがreturnされた時に
    BPFプログラムのtcp_connect_retが
    呼び出すように関連付けする

    View Slide

  58. TCPのコネクションをトレース

    58

    ユーザ空間側




    from __future__ import print_function 

    from bcc import BPF 

    from bcc.utils import printb 

    def ntoa(addr):

    …(省略)


    def print_event(cpu, data, size): 

    event = bpf["events"].event(data) 

    printb(b"%-6d %-16s %-16s %-16s %-16d" % (event.pid, event.comm, ntoa(event.saddr), ntoa(event.daddr), event.dport)) 


    bpf = BPF(src_file = "trace.c"); 

    bpf.attach_kprobe(event = "tcp_v4_connect", fn_name = "tcp_connect") 

    bpf.attach_kretprobe(event = "tcp_v4_connect", fn_name = "tcp_connect_ret") 

    bpf["events"].open_perf_buffer(print_event) 



    print("%-6s %-16s %-16s %-16s %-16s" % ("PID", "COMMAND", "SOURCE-IPADDR", "DESTINATION-IPADDR", "DPORT")) 

    while 1:

    bpf.perf_buffer_poll() 


    リングバッファを監視し,
    エントリがあった場合に
    呼び出すコールバック関数を登録

    View Slide

  59. TCPのコネクションをトレース

    59

    ユーザ空間側




    from __future__ import print_function 

    from bcc import BPF 

    from bcc.utils import printb 

    def ntoa(addr):

    …(省略)


    def print_event(cpu, data, size): 

    event = bpf["events"].event(data) 

    printb(b"%-6d %-16s %-16s %-16s %-16d" % (event.pid, event.comm, ntoa(event.saddr), ntoa(event.daddr), event.dport)) 


    bpf = BPF(src_file = "trace.c"); 

    bpf.attach_kprobe(event = "tcp_v4_connect", fn_name = "tcp_connect") 

    bpf.attach_kretprobe(event = "tcp_v4_connect", fn_name = "tcp_connect_ret") 

    bpf["events"].open_perf_buffer(print_event) 



    print("%-6s %-16s %-16s %-16s %-16s" % ("PID", "COMMAND", "SOURCE-IPADDR", "DESTINATION-IPADDR", "DPORT")) 

    while 1:

    bpf.perf_buffer_poll() 


    リングバッファをポーリングして
    エントリを監視

    View Slide

  60. TCPのコネクションをトレース

    60

    ユーザ空間側




    from __future__ import print_function 

    from bcc import BPF 

    from bcc.utils import printb 

    def ntoa(addr):

    …(省略)


    def print_event(cpu, data, size): 

    event = bpf["events"].event(data) 

    printb(b"%-6d %-16s %-16s %-16s %-16d" % (event.pid, event.comm, ntoa(event.saddr), ntoa(event.daddr), event.dport)) 


    bpf = BPF(src_file = "trace.c"); 

    bpf.attach_kprobe(event = "tcp_v4_connect", fn_name = "tcp_connect") 

    bpf.attach_kretprobe(event = "tcp_v4_connect", fn_name = "tcp_connect_ret") 

    bpf["events"].open_perf_buffer(print_event) 



    print("%-6s %-16s %-16s %-16s %-16s" % ("PID", "COMMAND", "SOURCE-IPADDR", "DESTINATION-IPADDR", "DPORT")) 

    while 1:

    bpf.perf_buffer_poll() 


    リングバッファから構造体を
    取り出して出力

    View Slide

  61. TCPのコネクションをトレース

    61


    View Slide

  62. LinuxコンテナのTCPのコネクションをトレース

    62

    ● 説明したTCPのコネクショントレースを拡張して,

    Linuxコンテナ(Dockerなど)のTCPコネクションのみを

    トレースするように拡張

    ○ Linuxコンテナか否かを識別する必要


    View Slide

  63. 1分で分かった気になるLinuxコンテナの仕組み

    63

    ● Linuxコンテナは,Linux上に独立したLinux環境をつくる仕組み

    ○ Linuxカーネルの機能でプロセス単位でリソースを隔離・制限

    ○ あくまでも,コンテナはプロセスに過ぎない(VMではない)

    ● コンテナを実現するための技術として,Linux NamespacesやCgroup,chrootな
    どがある

    ○ Linux Namespaces:プロセスIDや,ホスト名,

    ネットワークインターフェースなどを他のプロセスと分離

    ○ Chroot:ルートファイルシステムを隔離

    ○ Cgroup:CPUやメモリなどのリソースの制限


    View Slide

  64. コンテナB


    コンテナA


    1分で分かった気になるLinuxコンテナの仕組み

    64

    init プロセス
    PID 1
    プロセスA プロセスB
    PID 1
    プロセスA1 プロセスA2
    PID 2 PID 3
    プロセスB1 プロセスB2
    PID 2 PID 3
    PID 1
    /
    /bin /home
    ...
    /
    /bin /home
    ...

    View Slide

  65. コンテナB


    コンテナA


    コンテナ判定の方針

    65

    init プロセス
    PID 1
    プロセスA プロセスB
    PID 1
    プロセスA1 プロセスA2
    PID 2 PID 3
    プロセスB1 プロセスB2
    PID 2 PID 3
    PID 1
    ● TCP接続したプロセスが,Linux Namespacesで

    ホストから分離されているか,確認することでコンテナか判定する

    ○ task_struct構造体(プロセスに関する情報が詰まっている)の

    情報をもとに名前空間を確認


    View Slide

  66. コンテナ判定の実装

    66

    ● tcp_v4_connectがreturnするときのフックを拡張





    int tcp_connect_ret(struct pt_regs *ctx){ 

    u32 pid = bpf_get_current_pid_tgid(); 

    struct sock **sock, *sockp; 

    struct data_t data = {}; 

    struct task_struct *task = (struct task_struct *)bpf_get_current_task(); 

    struct pid_namespace *pidns = (struct pid_namespace * )task->nsproxy->pid_ns_for_children; 

    struct uts_namespace *uts = (struct uts_namespace * )task->nsproxy->uts_ns; 


    sock = socklist.lookup(&pid); 

    if(sock == 0 || pidns->level == 0){ 

    return 0;

    }


    sockp = *sock; 

    data.pid = pid; 

    bpf_get_current_comm(&data.comm, sizeof(data.comm)); 

    …(省略)

    bpf_probe_read(&data.nodename, sizeof(data.nodename), (void *)uts->name.nodename); 

    …(省略)


    View Slide

  67. コンテナ判定の実装

    67

    ● tcp_v4_connectがreturnするときのフックを拡張





    int tcp_connect_ret(struct pt_regs *ctx){ 

    u32 pid = bpf_get_current_pid_tgid(); 

    struct sock **sock, *sockp; 

    struct data_t data = {}; 

    struct task_struct *task = (struct task_struct *)bpf_get_current_task(); 

    struct pid_namespace *pidns = (struct pid_namespace * )task->nsproxy->pid_ns_for_children; 

    struct uts_namespace *uts = (struct uts_namespace * )task->nsproxy->uts_ns; 


    sock = socklist.lookup(&pid); 

    if(sock == 0 || pidns->level == 0){ 

    return 0;

    }


    sockp = *sock; 

    data.pid = pid; 

    bpf_get_current_comm(&data.comm, sizeof(data.comm)); 

    …(省略)

    bpf_probe_read(&data.nodename, sizeof(data.nodename), (void *)uts->name.nodename); 

    …(省略)


    bpf_get_current_task()で取得したtask_structから
    ,nsproxy(プロセスごとに名前空間の管理をする構造体 )を
    経由して,pid_namespace構造体を取得

    View Slide

  68. コンテナ判定の実装

    68

    ● tcp_v4_connectがreturnするときのフックを拡張





    int tcp_connect_ret(struct pt_regs *ctx){ 

    u32 pid = bpf_get_current_pid_tgid(); 

    struct sock **sock, *sockp; 

    struct data_t data = {}; 

    struct task_struct *task = (struct task_struct *)bpf_get_current_task(); 

    struct pid_namespace *pidns = (struct pid_namespace * )task->nsproxy->pid_ns_for_children; 

    struct uts_namespace *uts = (struct uts_namespace * )task->nsproxy->uts_ns; 


    sock = socklist.lookup(&pid); 

    if(sock == 0 || pidns->level == 0){ 

    return 0;

    }


    sockp = *sock; 

    data.pid = pid; 

    bpf_get_current_comm(&data.comm, sizeof(data.comm)); 

    …(省略)

    bpf_probe_read(&data.nodename, sizeof(data.nodename), (void *)uts->name.nodename); 

    …(省略)


    pid_namespace構造体のlevelは,
    PID名前空間の深さを表す.なので0より大きい場合は,
    PID名前空間が分離されている

    View Slide

  69. DockerのコンテナIDを取得する

    69

    ● tcp_v4_connectがreturnするときのフックを拡張





    int tcp_connect_ret(struct pt_regs *ctx){ 

    u32 pid = bpf_get_current_pid_tgid(); 

    struct sock **sock, *sockp; 

    struct data_t data = {}; 

    struct task_struct *task = (struct task_struct *)bpf_get_current_task(); 

    struct pid_namespace *pidns = (struct pid_namespace * )task->nsproxy->pid_ns_for_children; 

    struct uts_namespace *uts = (struct uts_namespace * )task->nsproxy->uts_ns; 


    sock = socklist.lookup(&pid); 

    if(sock == 0 || pidns->level == 0){ 

    return 0;

    }


    sockp = *sock; 

    data.pid = pid; 

    bpf_get_current_comm(&data.comm, sizeof(data.comm)); 

    …(省略)

    bpf_probe_read(&data.nodename, sizeof(data.nodename), (void *)uts->name.nodename); 

    …(省略)


    task_structからnsproxyを経由して,uts_namespace
    (ホスト名などを分離する名前空間の構造体 )を取得

    View Slide

  70. DockerのコンテナIDを取得する

    70

    ● tcp_v4_connectがreturnするときのフックを拡張





    int tcp_connect_ret(struct pt_regs *ctx){ 

    u32 pid = bpf_get_current_pid_tgid(); 

    struct sock **sock, *sockp; 

    struct data_t data = {}; 

    struct task_struct *task = (struct task_struct *)bpf_get_current_task(); 

    struct pid_namespace *pidns = (struct pid_namespace * )task->nsproxy->pid_ns_for_children; 

    struct uts_namespace *uts = (struct uts_namespace * )task->nsproxy->uts_ns; 


    sock = socklist.lookup(&pid); 

    if(sock == 0 || pidns->level == 0){ 

    return 0;

    }


    sockp = *sock; 

    data.pid = pid; 

    bpf_get_current_comm(&data.comm, sizeof(data.comm)); 

    …(省略)

    bpf_probe_read(&data.nodename, sizeof(data.nodename), (void *)uts->name.nodename); 

    …(省略)


    uts_namespacesからnodenameを取得する
    (DockerはコンテナIDをnodenameに設定する)

    View Slide

  71. LinuxコンテナのTCPのコネクションをトレース

    71


    View Slide

  72. XDPを使ってICMPパケット(ping)をドロップ

    72

    bpf_code = """

    int icmp_drop(struct xdp_md *ctx) { 

    void* data = (void *)(long)ctx->data; 

    void* data_end = (void *)(long)ctx->data_end; 

    struct ethhdr *eth = data; 

    int hdr_size = sizeof(*eth), proto; 


    if (data + hdr_size > data_end) 

    return XDP_PASS; 

    if (eth->h_proto == htons(ETH_P_IP)) { 

    struct iphdr *iph = data + hdr_size; 

    if ((void *)(iph + 1) > data_end) 

    return XDP_PASS; 


    if (iph->protocol == 1) { 

    return XDP_DROP; 

    }

    }

    return XDP_PASS; 

    }

    """


    View Slide

  73. XDPを使ってICMPパケット(ping)をドロップ

    73

    bpf_code = """

    int icmp_drop(struct xdp_md *ctx) { 

    void* data = (void *)(long)ctx->data; 

    void* data_end = (void *)(long)ctx->data_end; 

    struct ethhdr *eth = data; 

    int hdr_size = sizeof(*eth), proto; 


    if (data + hdr_size > data_end) 

    return XDP_PASS; 

    if (eth->h_proto == htons(ETH_P_IP)) { 

    struct iphdr *iph = data + hdr_size; 

    if ((void *)(iph + 1) > data_end) 

    return XDP_PASS; 


    if (iph->protocol == 1) { 

    return XDP_DROP; 

    }

    }

    return XDP_PASS; 

    }

    """

    xdp_md構造体はパケットに関する
    コンテキスト情報を持った構造体

    View Slide

  74. XDPを使ってICMPパケット(ping)をドロップ

    74

    bpf_code = """

    int icmp_drop(struct xdp_md *ctx) { 

    void* data = (void *)(long)ctx->data; 

    void* data_end = (void *)(long)ctx->data_end; 

    struct ethhdr *eth = data; 

    int hdr_size = sizeof(*eth), proto; 


    if (data + hdr_size > data_end) 

    return XDP_PASS; 

    if (eth->h_proto == htons(ETH_P_IP)) { 

    struct iphdr *iph = data + hdr_size; 

    if ((void *)(iph + 1) > data_end) 

    return XDP_PASS; 


    if (iph->protocol == 1) { 

    return XDP_DROP; 

    }

    }

    return XDP_PASS; 

    }

    """

    dataメンバはパケットの先頭,
    data_endはパケットの末尾を指す
    (u32に定義されているので,ポインタに変換する )
    data data_end
    Ethernetフレーム(64Byte〜1518Byte)

    View Slide

  75. XDPを使ってICMPパケット(ping)をドロップ

    75

    bpf_code = """

    int icmp_drop(struct xdp_md *ctx) { 

    void* data = (void *)(long)ctx->data; 

    void* data_end = (void *)(long)ctx->data_end; 

    struct ethhdr *eth = data; 

    int hdr_size = sizeof(*eth), proto; 


    if (data + hdr_size > data_end) 

    return XDP_PASS; 

    if (eth->h_proto == htons(ETH_P_IP)) { 

    struct iphdr *iph = data + hdr_size; 

    if ((void *)(iph + 1) > data_end) 

    return XDP_PASS; 


    if (iph->protocol == 1) { 

    return XDP_DROP; 

    }

    }

    return XDP_PASS; 

    }

    """

    パケットを解析する為に,
    dataをethhdr型(で定義)
    にキャストする
    data data_end
    Ethernetフレーム(64Byte〜1518Byte)
    宛先
    MAC
    送信元
    MAC
    Ether
    Type
    Ethernet ヘッダ

    View Slide

  76. XDPを使ってICMPパケット(ping)をドロップ

    76

    bpf_code = """

    int icmp_drop(struct xdp_md *ctx) { 

    void* data = (void *)(long)ctx->data; 

    void* data_end = (void *)(long)ctx->data_end; 

    struct ethhdr *eth = data; 

    int hdr_size = sizeof(*eth), proto; 


    if (data + hdr_size > data_end) 

    return XDP_PASS; 

    if (eth->h_proto == htons(ETH_P_IP)) { 

    struct iphdr *iph = data + hdr_size; 

    if ((void *)(iph + 1) > data_end) 

    return XDP_PASS; 


    if (iph->protocol == 1) { 

    return XDP_DROP; 

    }

    }

    return XDP_PASS; 

    }

    """

    ポインタの境界チェック (data + Ethernet Headerのサ
    イズが data_endを超えないかチェック )
    Ethernetフレーム(64Byte〜1518Byte)
    data data_end
    宛先
    MAC
    送信元
    MAC
    Ether
    Type
    Ethernet ヘッダ
    data + hdr_size

    View Slide

  77. XDPを使ってICMPパケット(ping)をドロップ

    77

    bpf_code = """

    int icmp_drop(struct xdp_md *ctx) { 

    void* data = (void *)(long)ctx->data; 

    void* data_end = (void *)(long)ctx->data_end; 

    struct ethhdr *eth = data; 

    int hdr_size = sizeof(*eth), proto; 


    if (data + hdr_size > data_end) 

    return XDP_PASS; 

    if (eth->h_proto == htons(ETH_P_IP)) { 

    struct iphdr *iph = data + hdr_size; 

    if ((void *)(iph + 1) > data_end) 

    return XDP_PASS; 


    if (iph->protocol == 1) { 

    return XDP_DROP; 

    }

    }

    return XDP_PASS; 

    }

    """

    Ethernetフレーム内にあるEtherTypeが
    IPv4であることを確認する
    Ethernetフレーム(64Byte〜1518Byte)
    data data_end
    宛先
    MAC
    送信元
    MAC
    Ether
    Type
    Ethernet ヘッダ
    eth->h_proto

    View Slide

  78. XDPを使ってICMPパケット(ping)をドロップ

    78

    bpf_code = """

    int icmp_drop(struct xdp_md *ctx) { 

    void* data = (void *)(long)ctx->data; 

    void* data_end = (void *)(long)ctx->data_end; 

    struct ethhdr *eth = data; 

    int hdr_size = sizeof(*eth), proto; 


    if (data + hdr_size > data_end) 

    return XDP_PASS; 

    if (eth->h_proto == htons(ETH_P_IP)) { 

    struct iphdr *iph = data + hdr_size; 

    if ((void *)(iph + 1) > data_end) 

    return XDP_PASS; 


    if (iph->protocol == 1) { 

    return XDP_DROP; 

    }

    }

    return XDP_PASS; 

    }

    """

    dataにEthernetヘッダ分オフセットを
    足したアドレスをiphdr構造体にキャスト
    Ethernetフレーム(64Byte〜1518Byte)
    data data_end
    宛先
    MAC
    送信元
    MAC
    Ether
    Type
    Ethernet ヘッダ
    data + hdr_size
    データ部(IPパケット)

    View Slide

  79. XDPを使ってICMPパケット(ping)をドロップ

    79

    bpf_code = """

    int icmp_drop(struct xdp_md *ctx) { 

    void* data = (void *)(long)ctx->data; 

    void* data_end = (void *)(long)ctx->data_end; 

    struct ethhdr *eth = data; 

    int hdr_size = sizeof(*eth), proto; 


    if (data + hdr_size > data_end) 

    return XDP_PASS; 

    if (eth->h_proto == htons(ETH_P_IP)) { 

    struct iphdr *iph = data + hdr_size; 

    if ((void *)(iph + 1) > data_end) 

    return XDP_PASS; 


    if (iph->protocol == 1) { 

    return XDP_DROP; 

    }

    }

    return XDP_PASS; 

    }

    """

    境界値チェック
    Ethernetフレーム(64Byte〜1518Byte)
    data data_end
    宛先
    MAC
    送信元
    MAC
    Ether
    Type
    Ethernet ヘッダ
    iph + 1
    データ部(IPパケット)

    View Slide

  80. XDPを使ってICMPパケット(ping)をドロップ

    80

    bpf_code = """

    int icmp_drop(struct xdp_md *ctx) { 

    void* data = (void *)(long)ctx->data; 

    void* data_end = (void *)(long)ctx->data_end; 

    struct ethhdr *eth = data; 

    int hdr_size = sizeof(*eth), proto; 


    if (data + hdr_size > data_end) 

    return XDP_PASS; 

    if (eth->h_proto == htons(ETH_P_IP)) { 

    struct iphdr *iph = data + hdr_size; 

    if ((void *)(iph + 1) > data_end) 

    return XDP_PASS; 


    if (iph->protocol == 1) { 

    return XDP_DROP; 

    }

    }

    return XDP_PASS; 

    }

    """

    IPパケットのプロトコル番号が
    1(ICMP)である場合,パケットをドロップ
    Ethernetフレーム(64Byte〜1518Byte)
    宛先
    MAC
    送信元
    MAC
    Ether
    Type
    Ethernet ヘッダ
    データ部(IPパケット)
    バー
    ジョン
    ... プロトコ
    ル番号
    iph->protocol
    ...

    View Slide

  81. XDPを使ってICMPパケット(ping)をドロップ

    81

    bpf = BPF(text=bpf_code)

    func = bpf.load_func("icmp_drop", BPF.XDP)


    bpf.attach_xdp("lo", func)

    print("Start ICMP Drop...")

    while True:

    try:

    time.sleep(1)

    except KeyboardInterrupt:

    break

    print("End ICMP Drop")

    bpf.remove_xdp("lo")



    View Slide

  82. XDPを使ってICMPパケット(ping)をドロップ

    82

    bpf = BPF(text=bpf_code)

    func = bpf.load_func("icmp_drop", BPF.XDP)


    bpf.attach_xdp("lo", func)

    print("Start ICMP Drop...")

    while True:

    try:

    time.sleep(1)

    except KeyboardInterrupt:

    break

    print("End ICMP Drop")

    bpf.remove_xdp("lo")



    BPFプログラムをロード

    View Slide

  83. XDPを使ってICMPパケット(ping)をドロップ

    83

    bpf = BPF(text=bpf_code)

    func = bpf.load_func("icmp_drop", BPF.XDP)


    bpf.attach_xdp("lo", func)

    print("Start ICMP Drop...")

    while True:

    try:

    time.sleep(1)

    except KeyboardInterrupt:

    break

    print("End ICMP Drop")

    bpf.remove_xdp("lo")


    BPFプログラムのicmp_drop関数を
    lo(ローカルループバック )デバイスにロード

    View Slide

  84. XDPを使ってICMPパケット(ping)をドロップ

    84

    bpf = BPF(text=bpf_code)

    func = bpf.load_func("icmp_drop", BPF.XDP)


    bpf.attach_xdp("lo", func)

    print("Start ICMP Drop...")

    while True:

    try:

    time.sleep(1)

    except KeyboardInterrupt:

    break

    print("End ICMP Drop")

    bpf.remove_xdp("lo")


    終了する前に,BPFプログラムを削除する

    View Slide

  85. XDPを使ってICMPパケット(ping)をドロップ

    85


    View Slide

  86. 「おいしくてつよくなる」eBPF

    86

    ● おいしい?

    ○ カーネル空間の中から様々なLinuxに関する情報を

    フック/トレースできる

    ○ 

    ● つよくなる?

    ○ 効率的にシステムの可観測性(モニタリング,トレーシング)を

    向上できる

    ○ (XDPを使うことで)Linuxカーネルの機能を活用しながら,

    高速パケット処理できる

    今日からeBPFはじめて圧倒的トレーシング


    View Slide