Slide 1

Slide 1 text


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


Slide 2

Slide 2 text

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


Slide 3

Slide 3 text

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


Slide 4

Slide 4 text

本日のセミナーのゴール
 4
 1. eBPFについて知ってもらう
 ○ eBPFで何ができるのか
 ○ eBPFのしくみ
 
 2. eBPFの使い方(はじめかた)を知ってもらう
 ○ eBPFプログラムの書き方


Slide 5

Slide 5 text

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. 


Slide 6

Slide 6 text

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.


Slide 7

Slide 7 text

BPFはカーネルモジュールなのか?
 7
 ● Linuxには,カーネルモジュール(Linuxの機能を拡張する仕組み)がある
 ○ なんでも出来る(自由度が高すぎる)ため,安全性を保証できない
 
 ● BPFはLinuxカーネル内で動作する独自のレジスタマシンと
 命令セットを持った仮想マシン
 ○ 仮想マシン内で動作するプログラムを記述して使用
 ○ カーネルモジュールと違って,使えるLinuxの機能やプログラムの文法に
 制限がある
 ○ ユーザ空間で作ったプログラムがカーネルをクラッシュさせたり,
 破壊しないように検証する機構を持つ
 
 
 


Slide 8

Slide 8 text

一番身近(!?)な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)
 


Slide 9

Slide 9 text

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/ 


Slide 10

Slide 10 text

eBPFで拡張された内容(一部)
 10
 ● 命令セットの一新
 ● 使用可能なレジスタの増加(2個->10個)
 ● レジスタ幅(32bit->64bit)
 ● スタックサイズ(16Byte -> 512Byte)
 ● 外部関数(カーネル内関数)呼び出し
 ● ヘルパー関数の実装
 ● map(ユーザ/カーネル空間,BPFプログラム間でのデータのやりとりに使用)の 追加
 ● 他のBPFプログラムへのジャンプ
 
 
 


Slide 11

Slide 11 text

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


Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

eBPFの全体像
 17
 ユーザ空間
 カーネル空間
 BPF プログラム バイト コード Verifier BPF bpf(2)
 map ユーザ プログラム 必要な場合はユーザ空間で 取得 sockets trace_points (カーネル空間の 静的トレース) uprobes (ユーザ空間の 動的トレース) kprobes (カーネル空間の 動的トレース)

Slide 18

Slide 18 text

eBPFの利用例
 18
 ● ネットワーク用途
 ○ パケットフィルタリング
 ○ トラフィック制御
 ○ トンネリング
 ● トレーシング用途
 ○ カーネルのトレーシング
 ○ ユーザ空間プログラムのトレーシング
 ● セキュリティ用途
 ○ システムコールフィルタリング
 
 
 


Slide 19

Slide 19 text

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 


Slide 20

Slide 20 text

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 


Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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


Slide 23

Slide 23 text

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


Slide 24

Slide 24 text

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


Slide 25

Slide 25 text

おしながき
 25
 1. Hello World!
 2. TCPコネクションをトレース
 3. コンテナのTCPコネクションをトレース
 4. XDPを使ってICMPパケット(ping)をドロップ
 
 


Slide 26

Slide 26 text

前提環境
 26
 ● Ubuntu 18.04LTS
 ● Kernel 4.15
 ● Python 3.6.9
 
 
 


Slide 27

Slide 27 text

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"


Slide 28

Slide 28 text

Hello World! 
 28
 ● 全体のイメージ
 ○ cloneシステムコール(子プロセスの作成)が
 呼ばれた時に,Hello Worldを出力
 
 
 
 
 ユーザ空間
 カーネル空間
 Application A Application B clone()システムコール
 呼び出し
 clone BPFプログラム int trace_clone() BPFプログラム フロントエンド(python)

Slide 29

Slide 29 text

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()


Slide 30

Slide 30 text

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システムコールが 呼ばれた際の処理を記述

Slide 31

Slide 31 text

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に出力 ※性能に影響があるので,デバッグ用途

Slide 32

Slide 32 text

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の検証器を許可しない コードが存在する場合,ロードできない

Slide 33

Slide 33 text

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システムコールにアタッチするために, カーネル内の関数名を取得する

Slide 34

Slide 34 text

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が呼び出すように関 連付けする

Slide 35

Slide 35 text

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で 受け取ったメッセージを出力

Slide 36

Slide 36 text

Hello World! 
 36


Slide 37

Slide 37 text

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


Slide 38

Slide 38 text

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


Slide 39

Slide 39 text

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構造体のアドレス を格納する連想配列を定義

Slide 40

Slide 40 text

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を取得

Slide 41

Slide 41 text

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構造体のアドレスを格納

Slide 42

Slide 42 text

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を使って ユーザ空間との任意のデータ構造 の受け渡しをリングバッファでおこなう

Slide 43

Slide 43 text

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構造体を定義

Slide 44

Slide 44 text

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


Slide 45

Slide 45 text

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を取得

Slide 46

Slide 46 text

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構造体(プロセスに関する 情報が詰まっている )を取得

Slide 47

Slide 47 text

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構造体のアドレスを取得

Slide 48

Slide 48 text

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構造体の中に格納

Slide 49

Slide 49 text

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構造体の中に格納

Slide 50

Slide 50 text

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構造体の中に格納

Slide 51

Slide 51 text

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構造体の中に格納

Slide 52

Slide 52 text

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構造体のアド レスをリングバッファに送信

Slide 53

Slide 53 text

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;
 }
 連想配列から削除

Slide 54

Slide 54 text

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() 
 


Slide 55

Slide 55 text

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プログラム読み込み

Slide 56

Slide 56 text

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が 呼び出すように関連付けする

Slide 57

Slide 57 text

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が 呼び出すように関連付けする

Slide 58

Slide 58 text

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() 
 
 リングバッファを監視し, エントリがあった場合に 呼び出すコールバック関数を登録

Slide 59

Slide 59 text

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() 
 
 リングバッファをポーリングして エントリを監視

Slide 60

Slide 60 text

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() 
 
 リングバッファから構造体を 取り出して出力

Slide 61

Slide 61 text

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


Slide 62

Slide 62 text

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


Slide 63

Slide 63 text

1分で分かった気になるLinuxコンテナの仕組み
 63
 ● Linuxコンテナは,Linux上に独立したLinux環境をつくる仕組み
 ○ Linuxカーネルの機能でプロセス単位でリソースを隔離・制限
 ○ あくまでも,コンテナはプロセスに過ぎない(VMではない)
 ● コンテナを実現するための技術として,Linux NamespacesやCgroup,chrootな どがある
 ○ Linux Namespaces:プロセスIDや,ホスト名,
 ネットワークインターフェースなどを他のプロセスと分離
 ○ Chroot:ルートファイルシステムを隔離
 ○ Cgroup:CPUやメモリなどのリソースの制限


Slide 64

Slide 64 text

コンテナ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 ...

Slide 65

Slide 65 text

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


Slide 66

Slide 66 text

コンテナ判定の実装
 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); 
 …(省略)
 


Slide 67

Slide 67 text

コンテナ判定の実装
 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構造体を取得

Slide 68

Slide 68 text

コンテナ判定の実装
 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名前空間が分離されている

Slide 69

Slide 69 text

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 (ホスト名などを分離する名前空間の構造体 )を取得

Slide 70

Slide 70 text

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に設定する)

Slide 71

Slide 71 text

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


Slide 72

Slide 72 text

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


Slide 73

Slide 73 text

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構造体はパケットに関する コンテキスト情報を持った構造体

Slide 74

Slide 74 text

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)

Slide 75

Slide 75 text

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 ヘッダ

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

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パケット)

Slide 79

Slide 79 text

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パケット)

Slide 80

Slide 80 text

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 ...

Slide 81

Slide 81 text

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")
 
 


Slide 82

Slide 82 text

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プログラムをロード

Slide 83

Slide 83 text

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(ローカルループバック )デバイスにロード

Slide 84

Slide 84 text

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プログラムを削除する

Slide 85

Slide 85 text

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


Slide 86

Slide 86 text

「おいしくてつよくなる」eBPF
 86
 ● おいしい?
 ○ カーネル空間の中から様々なLinuxに関する情報を
 フック/トレースできる
 ○ 
 ● つよくなる?
 ○ 効率的にシステムの可観測性(モニタリング,トレーシング)を
 向上できる
 ○ (XDPを使うことで)Linuxカーネルの機能を活用しながら,
 高速パケット処理できる
 今日からeBPFはじめて圧倒的トレーシング