Save 37% off PRO during our Black Friday Sale! »

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

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

9fdb3aa57e5fb99b3772976b0b903e53?s=128

Yuki Nakata chikuwait

June 27, 2020
Tweet

Transcript

  1. 
 「おいしくてつよくなる」
 eBPFのはじめかた
 オープンソースカンファレンス2020 Online/Hokkaido
 2020/6/27
 公立はこだて未来大学大学院 高度ICT領域
 システムソフトウェア研究室
 中田 裕貴


  2. 自己紹介
 2
 中田 裕貴 / chikuwait
 Twitter: chiku_wait GitHub: chikuwait
 


    • 公立はこだて未来大学大学院システム情報科学研究科
 高度ICT領域 M1
 • システムソフトウェア研究室
 • 仮想化技術(ハイパーバイザ,コンテナ)に関する研究
 • ハイパーバイザからOS,L2〜L4ネットワーク,
 Kubernetesまで広く浅く触ってます
 • 最近パケットが個人的にアツい
 
 
 

  3. 本日のセミナーの内容
 3
 ```
 ネットワークやシステム監視,トレースといった場面でeBPFの名前を
 聞くことが多くなってきました.このセミナーでは,
 日頃ハイパーバイザやコンテナをいじっている低レイヤ大好き学生が,Hello World からパケット処理までゆるく解説していきます.
 ```
 


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

  4. 本日のセミナーのゴール
 4
 1. eBPFについて知ってもらう
 ◦ eBPFで何ができるのか
 ◦ eBPFのしくみ
 
 2.

    eBPFの使い方(はじめかた)を知ってもらう
 ◦ eBPFプログラムの書き方

  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. 

  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.

  7. BPFはカーネルモジュールなのか?
 7
 • Linuxには,カーネルモジュール(Linuxの機能を拡張する仕組み)がある
 ◦ なんでも出来る(自由度が高すぎる)ため,安全性を保証できない
 
 • BPFはLinuxカーネル内で動作する独自のレジスタマシンと
 命令セットを持った仮想マシン


    ◦ 仮想マシン内で動作するプログラムを記述して使用
 ◦ カーネルモジュールと違って,使えるLinuxの機能やプログラムの文法に
 制限がある
 ◦ ユーザ空間で作ったプログラムがカーネルをクラッシュさせたり,
 破壊しないように検証する機構を持つ
 
 
 

  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)
 

  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/1395404418-25376-1-git-send-email-dborkman@redhat.com/T/ 

  10. eBPFで拡張された内容(一部)
 10
 • 命令セットの一新
 • 使用可能なレジスタの増加(2個->10個)
 • レジスタ幅(32bit->64bit)
 • スタックサイズ(16Byte

    -> 512Byte)
 • 外部関数(カーネル内関数)呼び出し
 • ヘルパー関数の実装
 • map(ユーザ/カーネル空間,BPFプログラム間でのデータのやりとりに使用)の 追加
 • 他のBPFプログラムへのジャンプ
 
 
 

  11. eBPFの全体像
 11
 ユーザ空間
 カーネル空間
 BPF プログラム バイト コード Verifier BPF

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

  12. eBPFの全体像
 12
 ユーザ空間
 カーネル空間
 BPF プログラム バイト コード Verifier BPF

    bpf(2)
 map ユーザ プログラム bpfシステムコールを 利用してカーネルに ロード sockets trace_points (カーネル空間の 静的トレース) uprobes (ユーザ空間の 動的トレース) kprobes (カーネル空間の 動的トレース)
  13. eBPFの全体像
 13
 ユーザ空間
 カーネル空間
 BPF プログラム バイト コード Verifier BPF

    bpf(2)
 map ユーザ プログラム Verifier(検証器)で プログラムの安全性を 確認してロードを完了 sockets trace_points (カーネル空間の 静的トレース) uprobes (ユーザ空間の 動的トレース) kprobes (カーネル空間の 動的トレース)
  14. eBPFの全体像
 14
 ユーザ空間
 カーネル空間
 BPF プログラム バイト コード Verifier BPF

    bpf(2)
 map ユーザ プログラム BPFプログラムを, これらのイベントソースを 使ってアタッチする sockets trace_points (カーネル空間の 静的トレース) uprobes (ユーザ空間の 動的トレース) kprobes (カーネル空間の 動的トレース)
  15. eBPFの全体像
 15
 ユーザ空間
 カーネル空間
 BPF プログラム バイト コード Verifier BPF

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

    bpf(2)
 map ユーザ プログラム BPFマップにアクセスして データを読み書きして保存 sockets trace_points (カーネル空間の 静的トレース) uprobes (ユーザ空間の 動的トレース) kprobes (カーネル空間の 動的トレース)
  17. eBPFの全体像
 17
 ユーザ空間
 カーネル空間
 BPF プログラム バイト コード Verifier BPF

    bpf(2)
 map ユーザ プログラム 必要な場合はユーザ空間で 取得 sockets trace_points (カーネル空間の 静的トレース) uprobes (ユーザ空間の 動的トレース) kprobes (カーネル空間の 動的トレース)
  18. eBPFの利用例
 18
 • ネットワーク用途
 ◦ パケットフィルタリング
 ◦ トラフィック制御
 ◦ トンネリング


    • トレーシング用途
 ◦ カーネルのトレーシング
 ◦ ユーザ空間プログラムのトレーシング
 • セキュリティ用途
 ◦ システムコールフィルタリング
 
 
 

  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 

  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 

  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
  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を置き換え,
 高パフォーマンスな
 ネットワーキングを実現
 

  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などで書ける
 

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


  25. おしながき
 25
 1. Hello World!
 2. TCPコネクションをトレース
 3. コンテナのTCPコネクションをトレース
 4.

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

  26. 前提環境
 26
 • Ubuntu 18.04LTS
 • Kernel 4.15
 • Python

    3.6.9
 
 
 

  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"

  28. Hello World! 
 28
 • 全体のイメージ
 ◦ cloneシステムコール(子プロセスの作成)が
 呼ばれた時に,Hello Worldを出力


    
 
 
 
 ユーザ空間
 カーネル空間
 Application A Application B clone()システムコール
 呼び出し
 clone BPFプログラム int trace_clone() BPFプログラム フロントエンド(python)
  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()

  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システムコールが 呼ばれた際の処理を記述
  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に出力 ※性能に影響があるので,デバッグ用途
  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の検証器を許可しない コードが存在する場合,ロードできない
  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システムコールにアタッチするために, カーネル内の関数名を取得する
  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が呼び出すように関 連付けする
  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で 受け取ったメッセージを出力
  36. Hello World! 
 36


  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() リングバッファ

  38. TCPのコネクションをトレース
 38
 • tcp_v4_connectが呼ばれた時のフック
 
 
 
 
 #include <uapi/linux/ptrace.h>


    #include <net/sock.h>
 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;
 }
 
 

  39. TCPのコネクションをトレース
 39
 • tcp_v4_connectが呼ばれた時のフック
 
 
 
 
 #include <uapi/linux/ptrace.h>


    #include <net/sock.h>
 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構造体のアドレス を格納する連想配列を定義
  40. TCPのコネクションをトレース
 40
 • tcp_v4_connectが呼ばれた時のフック
 
 
 
 
 #include <uapi/linux/ptrace.h>


    #include <net/sock.h>
 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を取得
  41. TCPのコネクションをトレース
 41
 • tcp_v4_connectが呼ばれた時のフック
 
 
 
 
 #include <uapi/linux/ptrace.h>


    #include <net/sock.h>
 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構造体のアドレスを格納
  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を使って ユーザ空間との任意のデータ構造 の受け渡しをリングバッファでおこなう
  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構造体を定義
  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;
 }

  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を取得
  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構造体(プロセスに関する 情報が詰まっている )を取得
  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構造体のアドレスを取得
  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構造体の中に格納
  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構造体の中に格納
  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構造体の中に格納
  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構造体の中に格納
  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構造体のアド レスをリングバッファに送信
  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;
 }
 連想配列から削除
  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() 
 

  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プログラム読み込み
  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が 呼び出すように関連付けする
  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が 呼び出すように関連付けする
  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() 
 
 リングバッファを監視し, エントリがあった場合に 呼び出すコールバック関数を登録
  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() 
 
 リングバッファをポーリングして エントリを監視
  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() 
 
 リングバッファから構造体を 取り出して出力
  61. TCPのコネクションをトレース
 61


  62. LinuxコンテナのTCPのコネクションをトレース
 62
 • 説明したTCPのコネクショントレースを拡張して,
 Linuxコンテナ(Dockerなど)のTCPコネクションのみを
 トレースするように拡張
 ◦ Linuxコンテナか否かを識別する必要
 


  63. 1分で分かった気になるLinuxコンテナの仕組み
 63
 • Linuxコンテナは,Linux上に独立したLinux環境をつくる仕組み
 ◦ Linuxカーネルの機能でプロセス単位でリソースを隔離・制限
 ◦ あくまでも,コンテナはプロセスに過ぎない(VMではない)
 • コンテナを実現するための技術として,Linux

    NamespacesやCgroup,chrootな どがある
 ◦ Linux Namespaces:プロセスIDや,ホスト名,
 ネットワークインターフェースなどを他のプロセスと分離
 ◦ Chroot:ルートファイルシステムを隔離
 ◦ Cgroup:CPUやメモリなどのリソースの制限

  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 ...
  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構造体(プロセスに関する情報が詰まっている)の
 情報をもとに名前空間を確認

  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); 
 …(省略)
 

  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構造体を取得
  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名前空間が分離されている
  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 (ホスト名などを分離する名前空間の構造体 )を取得
  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に設定する)
  71. LinuxコンテナのTCPのコネクションをトレース
 71


  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; 
 }
 """

  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構造体はパケットに関する コンテキスト情報を持った構造体
  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)
  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型(<linux/if_ether.h>で定義) にキャストする data data_end Ethernetフレーム(64Byte〜1518Byte) 宛先 MAC 送信元 MAC Ether Type Ethernet ヘッダ
  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
  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
  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パケット)
  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パケット)
  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 ...
  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")
 
 

  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プログラムをロード
  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(ローカルループバック )デバイスにロード
  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プログラムを削除する
  85. XDPを使ってICMPパケット(ping)をドロップ
 85


  86. 「おいしくてつよくなる」eBPF
 86
 • おいしい?
 ◦ カーネル空間の中から様々なLinuxに関する情報を
 フック/トレースできる
 ◦ 
 •

    つよくなる?
 ◦ 効率的にシステムの可観測性(モニタリング,トレーシング)を
 向上できる
 ◦ (XDPを使うことで)Linuxカーネルの機能を活用しながら,
 高速パケット処理できる
 今日からeBPFはじめて圧倒的トレーシング