1 of 169

XDP で作って学ぶ

ファイアウォールとロードバランサー

1

SECCON Fukuoka Workshop 2023

2 of 169

※この資料はセキュリティミニキャンプ 2023 in 宮崎で使用した資料をアップデートしたものです

2

3 of 169

タイムテーブル

  1. 準備編の準備
  2. 講義内容
  3. 概要編
    1. eBPFについて
    2. XDPについて
  4. 準備編
    • 開発環境セットアップ
    • terassyi/seccamp-xdpリポジトリの紹介
  5. 基礎編
    • eBPF(XDP)のコードに触れる
    • XDPを動かしてみる
  6. 実践編
    • scmlbの紹介
    • 作る!

3

4 of 169

自己紹介

  • 寺嶋 友哉(てらしま ともや)
  • お仕事
    • Kubernetes 基盤開発・運用
    • Cilium などのネットワーク機能を中心に担当
  • 趣味
    • ネットワーク関連ソフトウェア開発
      • BGP, TCP …
    • 将棋
    • アニメ/漫画
  • プチ情報
    • 葬送のフリーレンおもしろい

4

5 of 169

講義内容

  • 目的
    • 今ホットなLinuxカーネルの機能であるeBPF(XDP)を知ってほしい
    • XDP(eBPF)がどのようにセキュリティに応用されているか知ってほしい
    • XDPによるパケット処理を実際に動かして楽しさを知ってほしい
  • 内容
    • XDPにゼロから入門します
    • ファイアウォール機能を備えたネットワークロードバランサーをXDPを使って実装します
    • C言語で記述するXDPプログラムのみにフォーカスします
    • 時間があればコントロールプレーンのロジックにも触れるかもしれません
      • コントロールプレーンは Go 言語で記述しています

5

6 of 169

概要編

6

7 of 169

eBPFとは

  • Linuxカーネルの中に実装された仮想的なCPUで動的にプログラムを実行できる技術
  • カーネルを安全かつ動的、効率的に拡張できる

7

“eBPF is a revolutionary technology with origins in the Linux kernel that can run sandboxed programs in a privileged context such as the operating system kernel. It is used to safely and efficiently extend the capabilities of the kernel without requiring to change kernel source code or load kernel modules.”

- ebpf.io What is eBPF? -

8 of 169

eBPFとは

8

参考資料:(1)

eBPFプログラムはカーネル空間で動作する

カーネルにロードするeBPFプログラムはC言語で書く

ユーザー空間のプログラムは色々な言語で書ける

9 of 169

“e”BPF

  • Extended Berkeley Packet Filter
  • もとはパケットフィルタリングのための技術
    • 今はclassic BPFと呼んで区別している
    • tcpdump(パケットトレースツール)はcBPFでフィルタールールを書ける
  • 拡張されてより汎用的な‘e’BPF となった
    • パケットフィルタリング
    • カーネル内のイベントのトレース
  • 現在ではeBPFを単にBPFと呼ぶ人も

9

参考資料:(1)

この子は eBee

“BPF originally stood for Berkeley Packet Filter, but now that eBPF (extended BPF) can do so much more than packet filtering, the acronym no longer makes sense.”

- ebpf.io What do eBPF and BPF stand for? -

10 of 169

eBPF、流行ってる

  • eBPFがカーネルに追加されたのが2014年
  • 2021年に eBPF Foundation が設立された
    • Google/Microsoft/Meta/Huawei/Netflix
  • クラウドネイティブの発展とともに注目されてきている
    • コンテナ/マイクロサービスとの親和性
  • WindowsでもeBPFを動かそうというプロジェクトも進んでいる
  • eBPFを活用した色々なサービスが登場している

10

11 of 169

eBPF、流行ってる 基盤になってる

  • eBPFがカーネルに追加されたのが2014年
  • 2021年に eBPF Foundation が設立された
    • Google/Microsoft/Meta/Huawei/Netflix
  • クラウドネイティブの発展とともに注目されてきている
    • コンテナ/マイクロサービスとの親和性
  • WindowsでもeBPFを動かそうというプロジェクトも進んでいる
  • eBPFを活用した色々なサービスが登場している

11

12 of 169

eBPF のいいところ

  • Linuxカーネルの挙動を動的に拡張できる
  • 安全かつ迅速に実行が可能
  • カーネルモジュールやupstreamの変更と比較して
    • カーネルパニックしない
    • カーネルのソースコードを変更しなくて良い
    • eBPFプログラムの挿入にマシンの再起動なども必要ない
  • 既存のトレーシング技術と比べてより柔軟にイベントをトレースできる
  • カーネルの開発者じゃなくてもカーネルをカスタマイズできるようになった

12

13 of 169

eBPFができること

13

Tracing & Observability

Security

Networking

14 of 169

再び、eBPF とは?

  • カーネル内部で動作するサンドボックス化された仮想マシン
    • 非常にシンプルな64bitの命令セットをもつ仮想的なCPU
    • eBPFプログラムを実行することによってカーネルがパニックしないように予め安全性を検証して実行される
    • eBPF - 仮想マシン編 ←こちらの資料に簡潔にまとまっています
  • ユーザー空間からeBPFプログラムをカーネルに読み込んで実行できる
  • カーネル内部の様々なイベントをフックしてeBPFプログラムが実行される

14

参考資料:(5)

15 of 169

そもそも 、、Linux kernel?

  • カーネルの役割
    • ハードウェアの抽象化
    • プロセスの管理
    • メモリの管理
    • デバイスの管理
    • etc…
  • ユーザープログラムはシステムコールによりカーネルに処理を依頼する

15

16 of 169

フックポイント?

  • eBPFはイベントドリブン
  • カーネルにeBPFプログラムを実行するための場所(フックポイント)が用意されている
  • フックポイントでイベントが発生したらeBPFプログラムが実行される
  • 例えば...
    • システムコール呼び出し
    • カーネル関数呼び出し
    • NICにパケットが入ってきたとき
    • socketでデータを扱うとき

16

17 of 169

どうしてeBPFは安全?

  • eBPFプログラムが実行されるまで
  • C言語でeBPFプログラムを記述
  • ClangやGCCでeBPFバイトコードにコンパイル
  • bpfシステムコールでカーネル空間にロード
  • Verifierが安全性を検証
  • アーキテクチャに合わせてネイティブコードにJITコンパイル
  • フックポイントで実行

17

18 of 169

eBPF Verifier

  • Verifierがチェックしていること
    • 命令数が制限を超えていない
    • 無限ループがない
    • 到達不可能な命令がない
    • 有効なメモリにのみアクセスしている
    • 未初期化のメモリにアクセスしない
    • 不正な型変換がない
  • コンパイルはできてもロードするときにVerifierに怒られることもしばしば
    • eBPFの開発はここが一番つらい
  • Verifierの検査をパスするためにC言語のコードにも様々な制約がある

18

参考資料:(6)(7)

具体的な制約は基礎編で

19 of 169

BPF Map

  • eBPFプログラムが使えるストレージ
  • ユーザー空間のプロセスとカーネル空間のeBPFプログラム双方の間で情報を共有できる
  • いろいろなデータ構造が用意されている
    • HASH
    • ARRAY
    • LPM_TRIE
    • etc…

19

20 of 169

ヘルパー関数

  • BPFプログラム内で呼び出せる関数
  • BPFプログラム内で任意のカーネル関数を呼び出せると困る
    • カーネルのバージョンに依存してしまう
  • ヘルパー関数でできること
    • 乱数生成
    • 現在の日時の取得
    • eBPFマップの操作(値の取得/更新/削除)
    • プロセスやcgroupのコンテキストの取得
    • ネットワークパケットや転送ロジックの操作

20

ヘルパー関数の使用例は基礎編で

21 of 169

XDPとは?

  • eXpress Data Path の略
  • eBPFプログラムをネットワークドライバなどにアタッチして動かせる
  • NICから受信したデータをカーネルのネットワークスタックより手前で処理する
  • 高速なパケット処理を実現

21

参考資料:(8)

22 of 169

XDP のいいところ

  • eBPFのメリットを享受して安全に実行できる
  • よりNICに近い場所で高速にパケットを処理できる
  • Linuxカーネルのネットワークスタックと協調した動作が可能

22

23 of 169

XDP のセキュリティ分野への活用

  • DDoS攻撃に対する保護ツール
  • ファイアウォール
  • ロードバランサー
  • NAT

23

24 of 169

XDP in production

  • Cilium(Isovalent)
    • Kubernetes のネットワーク機能をeBPF/XDPで高速化する
    • What is Cilium?(9)
  • Katran(Meta/facebook)
    • ネットワークロードバランサー
    • facebookincubator/katran(10)
  • Unimog(Cloudflare)
  • Gatebot(Cloudflare)

24

参考資料:(9)(10)(11)(12)

25 of 169

XDP のいいところ(再掲)

  • eBPFのメリットを享受して安全に実行できる
  • よりNICに近い場所で高速にパケットを処理できる
  • Linuxカーネルのネットワークスタックと協調した動作が可能

25

柔軟で高速なパケット処理

26 of 169

パケット処理

26

参考資料:(13)

27 of 169

プロトコルとヘッダ

  • パケットはヘッダとデータ(ペイロード)から構成される
  • 上位層のデータの中に次のプロトコルのヘッダとデータが続く

27

28 of 169

ヘッダ

  • IPv4ヘッダの定義

28

参考資料:(14)

ビット単位で意味がある

29 of 169

TCP/IP

29

30 of 169

XDP が有効な領域

  • 大体トランスポート層の解析まで
  • 複雑な処理は向いてない
    • TCPの再実装
    • アプリケーション層の処理

30

大体このあたりまでが守備範囲

31 of 169

概要編終わり

31

32 of 169

準備編

32

33 of 169

terassyi/seccamp-xdp

  • github.com/terassyi/seccamp-xdp
    • 本講義のハンズオン用リポジトリ
    • /app
      • ハンズオンで使うテスト用アプリのコード
    • /scmlb
      • 実践編で作るロードバランサーのコード
    • /topology
      • ハンズオン用のネットワークを作成するスクリプト
    • /tutorial
      • 基礎編で使うXDPコード

33

34 of 169

使用するツールたち

  • ビルド関連
    • make
    • clang
  • ネットワーク操作関連
    • ipコマンド
      • ip netns
  • eBPF関連
    • bpftool
  • デモ関連
    • curl
    • ping
    • nmap

34

35 of 169

環境のセットアップ

  • seccamp-xdp/README.mdに沿ってセットアップを実行してください

35

36 of 169

IPコマンド

  • 本講義ではLinuxのNetwork Namespace(netns)という機能をつかってハンズオン用のネットワークを構築する
  • netnsとは
    • ネットワーク関連リソースのみを隔離した環境を作ることができる機能
    • ip netns というサブコマンドを使って操作する
    • ip netns exec <netns name> <command> という感じで指定のnetnsで任意のコマンドを実行できる
    • 詳細は ip netnsコマンドで学ぶNetwork Namespace(15) を参照
    • 使用例

36

参考資料:(15)

37 of 169

準備編終わり

37

38 of 169

基礎編

38

※基礎編はseccamp-xdp/tutorialのコードを使用します

39 of 169

はじめての XDP プログラム

  • パケットを受信するたびに hello from xdp! を出力するだけのプログラム

39

見慣れない記法がいくつかある...?

40 of 169

はじめての XDP プログラム

  • パケットを受信するたびに hello from xdp! を出力するだけのプログラム

40

41 of 169

vmlinux.h

  • eBPFプログラムが扱うカーネルのデータ構造がまとまったヘッダファイル

41

参考資料:(16)

42 of 169

vmlinux.h

  • eBPFプログラムが扱うカーネルのデータ構造がまとまったヘッダファイル
  • bpftool で自動生成できる
  • マシンに合わせたヘッダファイルを生成してくれる

42

生成されたファイルの中を探すと右のような iphdr の定義が見つかるはず

43 of 169

SEC()と関数名

  • SEC() でeBPFプログラムのプログラムタイプ/アタッチタイプを指定する
    • SEC(“xdp”)で今回対象の BPF_PROG_TYPE_XDP/BPF_XDP に向けたコードを書ける
  • SEC()がついた関数がエントリーポイントとなるので関数名は自由
  • プログラムタイプ/アタッチタイプに応じて関数の引数が変わってくる
    • XDPの場合は xdp_md構造体

43

参考資料:(17)

44 of 169

プログラムタイプ/アタッチタイプ

  • フックする場所に応じてプログラムタイプとアタッチタイプを指定する
  • 詳しくはカーネルのドキュメントを参照

44

参考資料:(17)

45 of 169

XDP Actions

  • 関数の返り値(XDP Actions)によってパケットのその後の処理を決定する

  • XDP_ABORTED(0)
    • eBPFプログラムがエラー終了
    • パケットはドロップ
  • XDP_DROP(1)
    • 単にパケットをドロップ
  • XDP_PASS(2)
    • カーネルのネットワークスタックに引き渡す
  • XDP_TX(3)
    • パケットを受信したNICから送出
  • XDP_REDIRECT(4)
    • 受信したパケットを別のNICから送出

45

46 of 169

ライセンス

  • eBPFのプログラムはGPLv2ライセンスでなければならない
  • licenseセクションがないとVerifierに怒られる

46

参考資料:(18)(19)

47 of 169

ふたつめの XDP プログラム

  • プロトコル毎の受信したパケット数をカウントするプログラム
  1. 情報を保存するマップ(配列)の定義
  2. xdp_md構造体からパケットのデータ構造へのキャスト
  3. マップからの値の取得と情報の更新

47

48 of 169

Mapの宣言

  • .maps section に構造体を定義することでBPFマップを作成できる
  • 必要なフィールドは以下
    • type
      • BPF Mapのタイプ
      • BPF maps(20)
    • key_size
      • マップのキーのサイズ(byte単位)
    • value_size
      • マップの値のサイズ(byte単位)
    • max_entries
      • マップに格納できる最大数
    • その他にも指定が必要な場合もある

48

参考資料:(20)

最初は形で覚えると良い

49 of 169

xdp_md 構造体とパケットへの変換

49

パケットデータの始まりへのポインタ

パケットデータの終わりへのポインタ

受信した/送信するNICのインデックス

XDPプログラムのエントリポイント関数の引数

Ethernetヘッダの構造体にキャスト

キャストしたEthernetヘッダ構造体の大きさがデータの大きさを越して無効なポインタへのアクセスをしていないか検査している

パケットのデータの始まりと終わりのポインタを記録しておく

消費したEthernetヘッダの構造体の大きさのぶんだけポインタをインクリメントしてIPv4ヘッダの先頭にポインタを合わせる

各種プロトコルのヘッダの構造体へのキャストは定型句として覚えてしまうと良い

50 of 169

ヘルパー関数の利用

  • bpf/bpf_helper_defs.hに定義されている
    • bpf/bpf_helpers.hを読み込めば使える
  • 詳しくはbpf-helpersのmanページを参照
  • bpf_map_lookup_elem()はeBPFマップに格納した値をキーをもとに取得する
    • 第一引数はマップへのポインタ
    • 第2引数はキーへのポインタ

50

参考資料:(21)

マップから取り出した値はNULL(無効なポインタ)かもしれないので必ず検査しないといけない

格納している値へのポインタが直接返ってくるので直に書き換え可能

値が取れなかったときは新たに値を挿入する

51 of 169

eBPF Verifier のためのC言語の制約

  • 命令数の制限
    • eBPFバイトコードにコンパイルしたときに命令数が1Mを超えてはいけない
  • 無限ループの禁止
    • 有限ループは可能
    • 実際は有限で終わるがVerifierに終了が保証できなければ怒られる
    • eBPFプログラム内では不必要に繰り返し処理をしないのが吉
  • 使用する変数は必ず初期化する
  • 使用するポインタ変数がNULLでないことを必ず検査する
  • 自作関数はinline展開する
    • 関数宣言する際は必ず(always) static inlineにする
  • 関数の引数は5つ以上取れない
  • 詳しくは→ XDP入門 - BPF(XDP)用C言語のお作法(22)

51

参考資料:(22)

※この資料は少し情報が古く、現在はグローバル変数は使えるようになっています

52 of 169

改めて、XDP プログラムを見てみる

   書ける気がしてきた!!

52

53 of 169

動かしてみる

  1. テスト用ネットワーク作成
  2. ビルド
  3. ロード
  4. アタッチ

53

テスト用ネットワークの構成

54 of 169

テスト用ネットワーク作成

  • Network Namespace を使ってネットワークを作成します

54

55 of 169

ビルド

  • Clangを使ってC言語をeBPFバイトコードにコンパイルする
    • -target bpf オプションでeBPFをターゲットにできる
  • -g オプションをつけることでデバッグ情報(BTF)をつけれる

55

make <file name>.bpf.o でもビルドできる

56 of 169

ロード

  • ビルド成果物(*.bpf.o)をカーネル空間にロードする
  • bpftoolを使う

56

ビルドして生成されたオブジェクトファイル名

/sys/fs/bpf/<object name> という感じで指定する

アンロードするときは /sys/fs/bpf/<object name> を削除する

57 of 169

アタッチ

  • カーネル空間にロードしたeBPFプログラムをデバイス(NIC)にアタッチする
  • bpftoolを使う
    • まずはロードしたプログラムのidをみつける

  • idとデバイス名をつかってアタッチ

57

プログラムのid

デタッチはデバイス名だけ指定で良い

58 of 169

ipコマンドでのアタッチ

  • XDPプログラムはipコマンドでもアタッチ/デタッチできる
    • bpftoolと異なり明示的なロードの作業が必要ない

  • makeターゲットを用意している

58

オブジェクトファイルを直接指定する

デバイス名を指定する

TARGETにオブジェクトの名前を指定

59 of 169

アタッチの確認

  • ipコマンドでの確認

  • bpftoolでの確認

59

60 of 169

Generic XDP

  • XDPはNICから直接呼び出される
    • NIC側でXDPをサポートしていないと使えない
  • Generic XDPはカーネル側で動かすことができるモード
    • その分パフォーマンスは落ちる
    • NICのサポートが不要
    • テストで使いやすい!!
  • 詳細は以下を参照

60

参考資料:(23)

今回はこの Generic XDP を使います

61 of 169

動作確認

  • 再度プログラムを確認

61

62 of 169

動作確認

  • bpf_printk()の出力が期待通りにでていることで動作を確認する
  • bpf_printk()の出力先
    • /sys/kernel/debug/tracing/trace_pipe

  • pingでパケットを送ってみて出力されるか試してみる‼

62

63 of 169

動作確認

  • ping でパケットを受信するとともに出力が流れるはず...!!

63

64 of 169

パケットカウンタも動かしてみる

64

65 of 169

基礎編終わり

65

66 of 169

実践編

66

67 of 169

パケットの気持ちになってコードを書いてみる

パケット処理プログラムのコツ

67

68 of 169

eBPFパケット処理プログラムのコツ

  • eBPFはイベント駆動でプログラムが実行される
  • 1イベント == 1パケット
  • 個々のパケットがどのような挙動をしてほしいかを意識するとよい
  • パケットをまたいで状態を管理したいときはプロトコルの気持ちになる
    • TCPコネクションの管理など(後でやります)
  • 情報の保存先はBPFマップのみ

68

69 of 169

eBPFパケット処理プログラムのコツ

  1. コードを書く
  2. コンパイルが通るかを確かめる
  3. Verifierに怒られるかを確かめる
    1. ログがわかりにくいので大変
    2. 最近はBTFが有効になっていればだいぶわかりやすくなった(なってない)
  4. bpf_printk()を使ってデバッグする
  5. パケットをキャプチャして期待した挙動になっているか確かめる
    • XDPの場合はtcpdump(wireshark)でパケットがとれない
    • 対向の通信相手でキャプチャして頑張る

69

70 of 169

つくるもの

  • scmlb
    • seccamp-xdp/scmlb (実践編のハンズオンはこのディレクトリ直下で実施します)
    • ハンズオン用のコードはhandsonブランチにあります
    • 完成版のコードはmainブランチにあります
    • XDPの部分のみを実装します
  • XDPを使ったシンプルなネットワークロードバランサー
  • 今回実装する機能
    • パケットカウンタ
    • ファイアウォール
    • 簡単なDoS保護機能
    • ロードバランス機能

70

ハンズオン用インスタンスに入ってすぐはmainブランチにいます

71 of 169

ロードバランサー

  • Load (負荷)を Balance (分散) する装置
  • 負荷分散の手法もたくさん
    • L4(Layer4/Network LB)
    • L7(Layer7/Application LB)
    • DNS round robin
    • etc..

71

L7

L4

処理するプロトコルによって特性や役割が異なる

72 of 169

ロードバランサーの利用例

72

  • TLS終端
  • URLによる高度なルーティング
  • Nginx とか
  • Globalアドレスの変換
  • 高速なパケット処理
  • 今回作るのはこれ

73 of 169

scmlbの概要

  • scmlbd
    • ロードバランサーの制御を行うデーモン型のプログラム
    • XDPプログラムはこちらで起動時にロード
  • scmlb
    • scmlbdを操作するためのCLIプログラム

73

74 of 169

動作イメージを掴む

  • まずは完成版を動かしてイメージを掴んでみましょう

74

mainブランチにいることを確認

75 of 169

動作イメージを掴む

  • バックエンドの追加とラウンドロビンで負荷分散されていることを確認する

75

76 of 169

動作イメージを掴む

  • バックエンドを適切に外すことができることを確認する

76

77 of 169

動作イメージを掴む

  • バックエンドを適切に追加できることを確認する

77

78 of 169

動作イメージを掴む

  • ファイアウォールを動かしてみる

78

10.0.2.0/24からのICMPを拒否する

10.0.3.0/24からの

8000〜9000番ポート宛のTCPを拒否する

79 of 169

動作確認終わり

79

handsonブランチにcheckoutしてコードを書いていきましょう

80 of 169

scmlbのプロジェクト構成

  • bpf/
    • XDPプログラム関連のコード(詳細は次のページ)
    • 今回はこのディレクトリに注目
  • bin/
    • ビルド成果物のディレクトリ(make buildすると作成される)
  • cmd/, pkg/, protocol/
    • scmlbd/scmlbコマンドのためのGo言語のコード
  • Makefile
    • ビルドしたり、検証用ネットワーク作成のためのターゲットを定義している

80

81 of 169

bpfディレクトリの構成

  • include/
    • csum.h - チェックサム関連の関数を定義
    • maps.h - 利用する各種eBPFマップを定義
    • scmlb.h - 利用する構造体を定義
    • tail_call.h - tail callのためのシンボルを定義
    • vmlinux.h - 自動生成されるカーネルの構造体の集合
  • xdp.c
    • 各種ロジックを記述
    • ここを編集します!!

81

82 of 169

bpf/xdp.c

  • entrypoint()
    • パケット受信の度に呼び出されるエントリーポイントとなる関数
    • この関数から各種機能の関数を呼び出す
  • count()
    • パケットカウンタ機能の本体
  • firewall()
    • ファイアウォール機能の本体
  • lb_ingress()
    • ロードバランサーのupstreamから受信したパケットを処理する関数
  • lb_egress()
    • ロードバランサーの各backendから受信したパケットを処理する関数

82

各機能の関数にtail callして処理をつなげていきます

83 of 169

tail call

  • eBPFプログラムを関数呼び出しのように数珠つなぎに実行する手法
  • 関数呼び出しと異なり呼び出し元に返ってくることはない
  • scmlbのtail callの遷移図→

83

受信したNICによって分岐

参考資料:(24)

84 of 169

STEP1: パケットカウンタ

  • bpf/xdp.cのcounter()関数のロジックを記述
  • tutorial/counter_map.bpf.cの実装を書き写す
    • 情報を格納するマップの名前が異なるので注意

84

今回利用するマップの定義

ここに書く!!

count()関数です

85 of 169

実装してみましょう

85

まずは写経でXDPプログラムを書くことに慣れましょう

86 of 169

STEP1: パケットカウンタ

  • 動かしてみる
    • scmlbdの起動とテスト用ネットワーク作成

86

テスト用ネットワーク

87 of 169

STEP1: パケットカウンタ

  • 動かしてみる
    • scmlbに向けてパケットを送信してみる(ping, curl, etc…)

87

scmlb statコマンドで受信したパケット数を確認できる!!

88 of 169

STEP2: ファイアウォール

  • アプリケーションサーバーは必要なポートのみ公開したい
  • ファイアウォールが意図しないポートが公開されているかも...
  • テスト環境をつくる

88

step2のテスト用ネットワーク

8080番にWebサーバーが公開されてる

host3とhost4からhost2にアクセスしてみる

89 of 169

STEP2: ファイアウォール

  • 10.0.1.2:8080で公開されているサーバーにアクセスしてみる

89

90 of 169

STEP2: ファイアウォール

  • テスト環境にnmapコマンドでポートスキャンしてみる
  • netcat(ncコマンド)で7070番で動くサーバーにつないでみる

90

7070ポートも開いてる...

参考資料:(25)(26)

91 of 169

STEP2: ファイアウォール

  • テスト環境にnmapコマンドでポートスキャンしてみる
  • netcat(ncコマンド)で7070番で動くサーバーにつないでみる

91

7070ポートも開いてる...

7070番ポートをファイアウォールを実装して塞いでみましょう!!

92 of 169

STEP2: ファイアウォール

  • 仕様
    • デフォルトはすべての通信を許可
    • 特定の送信元ネットワークからの通信を以下の要素でフィルタリングして拒否するルールを追加していく
      • プロトコル(TCP/UDP/ICMP)
      • 送信元ポート(例: 8000-9000 のように範囲で指定できる)
      • 宛先ポート(例: 8000-9000 のように範囲で指定できる)
    • 登録されたルールにマッチして拒否したパケットの数をカウントする

92

93 of 169

STEP2: ファイアウォール

  • bpf/xdp.cのfirewall()関数にロジックを記述
  • ファイアウォールに利用するeBPFマップは以下

93

94 of 169

STEP2: ファイアウォール

  • LPM Trie
    • Longest Prefix Match Trie
    • BPF_MAP_TYPE_LPM_TRIE
    • ロンゲストプレフィックスマッチを利用したマップ

94

参考資料:(27)(28)(29)

ID

PREFIX

1

192.168.2.0/24

2

192.0.2.128/25

3

198.51.100.0/24

4

198.51.100.0/28

5

0.0.0.0/0

203.0.113.1はここにマッチ

192.0.2.252はここにマッチ

例えば...

95 of 169

STEP2: ファイアウォール

  • bpf/xdp.cのfirewall()関数にロジックを記述
  • ファイアウォールに利用するeBPFマップは以下

95

96 of 169

STEP2: ファイアウォール

96

PREFIX

IDs

10.0.2.0/24

1, 3

10.0.3.0/24

2

192.168.0.0/24

4

0.0.0.0/0

5

ID

PROTOCOL

SRC PORT

DST PORT

1

ICMP

0

0

2

TCP

0

8000-9000

3

TCP

30000-60000

0

4

UDP

0

1024-65535

5

TCP

0

1024-65535

adv_rulematcher(LPM_TRIE)

adv_rules(HASH)

受信したパケットの送信元アドレスからマッチするID一覧を取得する

取得したIDをもとにルールの中身を参照する

97 of 169

STEP2: ファイアウォール

  • bpf/xdp.cのfirewall()関数を編集
  • bpf/xdp.cのfw_match()関数を利用
    • 引数で対象ルールとパケットから取得したプロトコル・ポートでそのルールにマッチしているかを判断

97

ここに書く!!

98 of 169

STEP2: ファイアウォール

  1. Ethernetヘッダ、IPv4ヘッダ解析
  2. 送信元アドレスをもとにadv_rulematcherを引く
  3. idの配列が取れたらその配列に対してループ
    1. 取得したidをキーとしてadv_rulesを引く
    2. adv_rulesからルールが取れたら受信したパケットのL4プロトコルに応じてヘッダを解析(TCP/UDPのみ)
    3. fw_match()関数でパケットを拒否するか判断
    4. 拒否する場合はルールの構造体のカウンタをインクリメント
    5. ドロップ
  4. どのidにもマッチしなければ次の機能へ

98

seccamp-xdp/scmlb/README.mdにも図を記載しているのでそちらも参照してください

99 of 169

実装してみましょう

99

フローチャートを見ながらコードを記述しましょう

fw_match()関数は既に実装されています

難しければmainブランチを見ながらでOK

100 of 169

STEP2: ファイアウォール

  • 動かしてみる
    • テスト用ネットワーク作成とテストアプリの起動

  • ビルドとscmlbdの起動

100

$ make step2

$ make build

$ sudo ip netns exec host2 bin/scmlbd start —upstream h2-h0 —vip 203.0.113.11 —gc

101 of 169

STEP2: ファイアウォール

  • なにもルールを入れてないときは7070番につながることを確かめる

101

102 of 169

STEP2: ファイアウォール

  • host3(10.0.2.0/24)から7070番ポートへのパケットを拒否するルールを追加

  • ルールが追加されていることを確認

102

103 of 169

STEP2: ファイアウォール

  • 7070番ポートへのTCPパケットが拒否されていることを確認

  • 8080番ポートや他のネットワークからは通信できることを確認

103

レスポンスが返ってこなくなっているはず!!

レスポンスが返ってくるはず!!

104 of 169

STEP3: ロードバランサー

  • Load (負荷)を Balance (分散) する装置
  • ロードバランサー宛に届いたパケットをアプリケーションサーバーに転送する
  • 転送先はコネクション単位で決定される
    • 同一のコネクションからのパケットは常に同じサーバーに転送しなければならない
    • →TCP接続が切れてしまう
  • 新しいコネクションの転送先の決定方法は様々
    • ラウンドロビン
    • ランダム

104

ロードバランサーはコネクションを追跡できないといけない

今回はこれ

105 of 169

STEP3: ロードバランサー

  • コネクショントラッキング
    • 5-tupleでコネクションを一意に判別して追跡
    • 5-tupleをキーとしてコネクションの状態を保存する
  • 5-tuple
    • 送信元アドレス/送信元ポート/宛先アドレス/宛先ポート/プロトコル
  • TCPコネクション
    • TCPパケットのフラグ(SYN/FIN/RST…)をみてコネクションの状態を遷移させる
    • TCPの状態遷移は複雑なので簡略化した状態遷移を実装して管理
  • UDP
    • 最後のパケットが届いてから一定期間はコネクションが維持される

105

参考資料:(30)(31)

106 of 169

STEP3: ロードバランサー

  • コネクショントラッキング(conntrack)
    • ロードバランサーはコネクションを追跡できないといけない
    • TCPコネクションは CLIENT1 <-> APP で確立される
    • CLIENTはVIPをもったサーバーと通信していると思っている
      • 実際はVIPはLBが持っていて後ろのAPPとコネクションを張っている

106

TCPコネクション

もしLBが適切なバックエンドにパケットを転送できなかったら...

107 of 169

STEP3: ロードバランサー

  • コネクショントラッキング(conntrack)
    • ロードバランサーはコネクションを追跡できないといけない
    • TCPコネクションは CLIENT1 <-> APP で確立される
    • CLIENTはVIPをもったサーバーと通信していると思っている
      • 実際はVIPはLBが持っていて後ろのAPPとコネクションを張っている

107

TCPコネクション

もしLBが適切なバックエンドにパケットを転送できなかったら...

108 of 169

STEP3: ロードバランサー

  • コネクショントラッキング(conntrack)
    • ロードバランサーはコネクションを追跡できないといけない
    • TCPコネクションは CLIENT1 <-> APP で確立される
    • CLIENTはVIPをもったサーバーと通信していると思っている
      • 実際はVIPはLBが持っていて後ろのAPPとコネクションを張っている

108

TCPコネクション

もしLBが適切なバックエンドにパケットを転送できなかったら...

TCPコネクションが切断されてしまう

109 of 169

STEP3: ロードバランサー

  • コネクショントラッキング(conntrack)

LBはコネクションを一意に判別して、かつその情報・状態を保存する必要がある

109

TCPコネクション

同一のコネクションのパケットは必ず同じバックエンドに転送する

110 of 169

STEP3: ロードバランサー

  • コネクショントラッキング(conntrack)
    • 個々のパケットからコネクションを判断しないといけない
    • 送信元アドレス/送信元ポート/宛先アドレス/宛先ポート/プロトコル で判断できる!

110

TCP/VIP:80

CLIENT1:30432

CLIENT2:59004

111 of 169

STEP3: ロードバランサー

  • コネクショントラッキング(conntrack)

LBはコネクションを一意に判別して、かつその情報・状態を保存する必要がある

111

保持する状態はプロトコルによる

  • TCP
    • TCPコネクションの状態
  • UDP
    • 最後に受信した時刻

112 of 169

STEP3: ロードバランサー

  • TCP(Transmission Control Protocol)
    • トランスポート層のプロトコル
    • コネクション型で高信頼性
    • 順序制御
    • 再送制御
    • 輻輳制御
    • 3 way handshake

112

※ Guojunzheng - Create some pictures and made it gif, CC 表示-継承 3.0, https://commons.wikimedia.org/w/index.php?curid=24426758による

113 of 169

STEP3: ロードバランサー

  • TCP(Transmission Control Protocol)
    • 内部ではパケットにセットされたフラグ(SYN/ACK…)に応じて状態を遷移してコネクションを管理している

113

複雑すぎるので簡略化

114 of 169

STEP3: ロードバランサー

  • コネクショントラッキング(conntrack)

114

5-tuple

状態

転送先のバックエンド

送信元のMACアドレス

パケット数

key

value

115 of 169

STEP3: ロードバランサー

  • パケット転送(NAT/Network Address Translation)
    • ロードバランサーのアドレス宛に届いたパケットを実サーバーのアドレス宛に書き換えて転送
    • 実サーバーから戻ってきたパケットもクライアント宛にアドレスを書き換えて転送
    • 各ヘッダのチェックサムも再計算

115

116 of 169

STEP3: ロードバランサー

  • ラウンドロビン
    • 分散対象のサーバーに順番にコネクションを割り当てる
    • 平均的な各サーバーの負荷が均等になる

116

117 of 169

STEP3: ロードバランサー

  • ロードバランサー用のテストネットワークを作成

117

118 of 169

(再掲)bpfディレクトリの構成

  • include/
    • csum.h - チェックサム関連の関数を定義
    • maps.h - 利用する各種eBPFマップを定義
    • scmlb.h - 利用する構造体を定義
    • tail_call.h - tail callのためのシンボルを定義
    • vmlinux.h - 自動生成されるカーネルの構造体の集合
  • xdp.c
    • 各種ロジックを記述
    • ここを編集します!!

118

119 of 169

STEP3: ロードバランサー

119

backend_ifindexマップにコントロールプレーンからバックエンドの情報を登録

受信したデバイスの番号はxdp_md構造体から取得できる

パケットカウンタ→ファイアウォール→ロードバランサーの順に処理

バックエンドサーバーと繋がっているデバイスを登録

120 of 169

(再掲)tail call

  • eBPFプログラムを関数呼び出しのように数珠つなぎに実行する手法
  • 関数呼び出しと異なり呼び出し元に返ってくることはない
  • scmlbのtail callの遷移図→

120

受信したNICによって分岐

参考資料:(24)

121 of 169

(再掲)bpf/xdp.c

  • entrypoint()
    • パケット受信の度に呼び出されるエントリーポイントとなる関数
    • この関数から各種機能の関数を呼び出す
  • count()
    • パケットカウンタ機能の本体
  • firewall()
    • ファイアウォール機能の本体
  • lb_ingress()
    • ロードバランサーのupstreamから受信したパケットを処理する関数
  • lb_egress()
    • ロードバランサーの各backendから受信したパケットを処理する関数

121

各機能の関数にtail callして処理をつなげていきます

122 of 169

STEP3: ロードバランサー

  • 大まかな処理概要
  • lb_ingress()関数
    • クライアント側(upstream)から受信したパケットを処理
  • lb_egress()関数
    • 各バックエンドから受信したパケットを処理

122

123 of 169

STEP3: ロードバランサー

  • lb_ingress()関数
    • TCP/UDPのプロトコルごとに処理を分岐

123

backend構造体にリダイレクト先の情報を格納

リダイレクト

パケットをプロトコルに分けて処理

124 of 169

STEP3: ロードバランサー

  • lb_egress()関数
    • TCP/UDPのプロトコルごとに処理を分岐

124

upstreamの情報は起動時にコントロールプレーンから格納される

パケットをプロトコルに分けて処理

125 of 169

STEP3: ロードバランサー

125

126 of 169

STEP3: ロードバランサー

  • 今回のハンズオンで実装する関数
    • handle_tcp_ingress()
    • handle_tcp_egress()
    • handle_udp_ingress()
    • handle_udp_egress()

126

127 of 169

STEP3: ロードバランサー

  • handle_tcp_ingress()関数
    • upstream(クライアント側)から受信したTCPパケットを処理して転送先バックエンドを決定
    • 引数
      • tcph: tcphdr構造体へのポインタ
      • iph: iphdr構造体へのポインタ
      • src_macaddr: 受信したパケットの送信元MACアドレス(長さ6のu8の配列)
      • target: 転送先を格納する空のbackend構造体
    • 戻り値
      • 処理結果を判別するint型の値(0なら成功)

127

128 of 169

STEP3: ロードバランサー

  1. 引数のパケットデータからconnection構造体を作成
  2. connection構造体をキーにconntrackマップを探索
  3. connection_info構造体が取れた時(既存のコネクション)
    1. TCPフラグをみてコネクションの状態遷移
    2. connection_info構造体をキーにbackend_infoマップを探索して保存されていたbackend(転送先)を取得
    3. 転送先のアドレスにパケットを書き換える
  4. connection_info構造体が取れなかった時(新規)
    • 転送先バックエンドを選択
    • 選択したバックエンドの情報(backend構造体)を取得
    • connection_info構造体を作成してconntrackマップに保存
    • 選択したバックエンド宛てにパケットを書き換える

128

seccamp-xdp/scmlb/README.mdにも図を記載しているのでそちらも参照してください

129 of 169

STEP3: ロードバランサー

129

ここの処理を記述

ここの処理を記述

130 of 169

STEP3: ロードバランサー

  • handle_tcp_egress()関数
    • バックエンドから受信したTCPパケットを処理してupstreamに転送する準備をする関数
    • 引数
      • tcph: tcphdr構造体
      • iph: iphdr構造体
      • us: 転送先のupstreamの情報を持った構造体
      • target: conntrackを探索した結果を保持する空のconnection_info構造体
    • 戻り値
      • 処理結果を判別するint型の値(0なら成功)

130

131 of 169

STEP3: ロードバランサー

  1. TCP,IPv4ヘッダとupstreamのアドレスからconnection構造体を作成
  2. connection構造体をキーにconntrackマップを探索
  3. TCPフラグを基に取得したconnection_info構造体の状態を遷移
  4. upstreamを転送先としてパケットを書き換えてチェックサムを再計算

131

132 of 169

STEP3: ロードバランサー

132

ここの処理を記述

133 of 169

STEP3: ロードバランサー

  • conntrackマップとconnection/connection_info構造体

133

134 of 169

実装してみましょう

134

フローチャートを見ながらコードを記述しましょう

フローチャート内の各関数は既に実装されています

(猛者は一から実装してもOK)

ロジックがかなり複雑になっているのでmainブランチを見ながらでOK

135 of 169

STEP3: ロードバランサー(補足)

  • バックエンドの選択

135

136 of 169

STEP3: ロードバランサー(補足)

  • バックエンドの選択

136

INDEX

BACKEND_ID

0

1

1

3

2

4

rr_table

ID

BACKEND_INFO

1

省略

3

省略

4

省略

backend_info

※ selected_backend_index

137 of 169

STEP3: ロードバランサー(補足)

  • TCP/IP Checksum
    • 対象となるパケットに対し、16ビットごとの 1 の補数和を取り、さらにそれの 1 の補数を取る
    • IP → ヘッダ全体
    • TCP/UDP → パケット全体 + 疑似ヘッダ
      • 疑似ヘッダ
        • 送信元アドレス(4 byte)
        • 宛先アドレス(4 byte)
        • パディング(1 byte)
        • プロトコル番号(1 byte)
        • パケット長(2 byte)

137

bpf/include/csum.h に定義しています

実際は差分だけ計算してます

138 of 169

STEP3: ロードバランサー

  • 動かしてみる
    • テスト用ネットワーク作成とテスト用アプリケーション起動、scmlbdの起動

138

ClientからLBのVIP(203.0.113.11)に向けてリクエストを投げる!!

139 of 169

STEP3: ロードバランサー

  • リクエストが均等にバックエンドから返ってきていることを確認

139

リクエストが登録したバックエンドから均等に返ってきている!!

scmlbdにバックエンドを登録

140 of 169

STEP3: ロードバランサー

  • コネクションが維持されることを確認
    • 7070番ポートに接続してTCPコネクションを張る

  • conntrackにコネクションが保存されていることを確認

140

Establishedなコネクションが見れる

コネクションを繋いだままにしておく

141 of 169

STEP3: ロードバランサー

  • バックエンドを削除するテスト

141

142 of 169

STEP3: ロードバランサー

  • バックエンドを追加するテスト

142

143 of 169

まとめ

  • eBPF(XDP)に入門しました
  • 実際に動くものを作ってパケット処理のおもしろさを実感してもらえたら
  • ファイアウォール/ロードバランサーといったセキュリティ上重要なネットワークコンポーネントを実装することでその仕組みや扱い方を学んでいただけたら
  • eBPFは今後もホットトピックであり続けると思われるのでこれを機会に他の分野への応用にも手を伸ばしてみてほしい

143

144 of 169

参考資料

144

145 of 169

参考資料

145

146 of 169

参考資料

  1. TCPの状態遷移
  2. 負荷分散入門 - 第3回 リクエストの分散機能 (1/2)

146

147 of 169

はまりどころ

  • マップから値をとったときNULLチェックを忘れてる

  • 変数宣言の位置によってVerifierに怒られる

147

これは時と場合によりますが,例として

左のコードは動かなくて,右は動きます

変数宣言の場所がよくなかった

148 of 169

はまりどころ

  • 構造体を利用する前にメモリを初期化する

148

149 of 169

scmlb コントロールプレーン

149

Go言語の知識があったほうがよいです

Go言語入門はA Tour of Goがおすすめ

150 of 169

コントロールプレーン

  • 実はコントロールプレーンの方が圧倒的にコード量が多い

150

151 of 169

コントロールプレーンの役割

  • CLI(scmlb)
    • ユーザーからの設定をdaemonプログラムに反映する
      • fw set
      • lb set/drain/delete
    • daemonプログラムの情報を取得する
  • Daemon(scmlbd)
    • XDPプログラムのアタッチ/デタッチ
    • BPFマップの操作(値の挿入/更新/削除)
      • ファイアウォールのルール
      • ロードバランサーのバックエンド
    • ロードバランサーのconntrackのガベージコレクション

151

152 of 169

使用技術(ライブラリ)

  • cilium/ebpf
    • eBPF関連の操作全て
  • gRPC
    • CLI ↔ Daemon 間の通信
  • spf13/cobra
    • CLIツールを作成するライブラリ

152

153 of 169

Daemonプログラム

153

154 of 169

XDPプログラムのアタッチ/デタッチ

  • Go言語のビルド時にXDPプログラムもビルドする
    • go generate という機能を使ってビルド時に外部コマンドを実行する
  • cilium/ebpfのbpf2goコマンドでeBPFプログラムをビルドしてGoのバイナリに埋め込むことができる
    • eBPFプログラムをロード/アンロードするユーティリティ関数も自動生成してくれる

154

155 of 169

XDPプログラムのアタッチ/デタッチ

  • XDPプログラムをロードする

155

bpf2goで自動生成される

ロードする際のオプションを色々指定できる

156 of 169

XDPプログラムのアタッチ/デタッチ

  • デバイスにアタッチする

156

ロードしたプログラム・デバイス名を指定してアタッチ

明示的にGenericXDPを指定している

157 of 169

ロードバランサーコントロールプレーン

  • conntrackの管理(同期とガベージコレクション)

157

gc()はなにもやってませんでした...

sync()がGCもやってます

158 of 169

ロードバランサーコントロールプレーン

  • conntrackの同期

158

conntrakマップを走査

取得した各エントリを同期していく

conntrackの情報は常にデータプレーンの方が正しい前提で同期する

159 of 169

ロードバランサーコントロールプレーン

  • conntrackのGC

159

状態がClosedなエントリを削除する

最後のパケットから一定時間経ったエントリを削除する

conntrackマップからも削除

160 of 169

ロードバランサーコントロールプレーン

  • バックエンドの追加

160

バックエンドと繋がっているデバイスにプログラムをアタッチする

BPFマップに登録するバックエンド情報を作成する

161 of 169

ロードバランサーコントロールプレーン

  • バックエンドの安全な切り離し
    • scmlb lb drain –id <backend>
    • 既存のコネクションは処理できるが新規コネクションは受け付けない状態にバックエンドを遷移させる

161

バックエンド1は新規コネクションを受け付けない

バックエンド1がUnavailableになってる

162 of 169

ロードバランサーコントロールプレーン

  • バックエンドの安全な切り離し

162

バックエンドの状態をUnavailableにする

BPFマップを更新して状態を同期する

adjustRrTable()でラウンドロビンの対象からそのバックエンドを除外する

163 of 169

ロードバランサーコントロールプレーン

  • バックエンドの削除

163

バックエンドがUnavailableであることを確認

各種BPFマップから情報を削除する

164 of 169

機能拡張

  • データプレーン(eBPFプログラム側)
    • ラウンドロビン以外のバックエンド選択の実装
    • デフォルト拒否のファイアウォールの実装
    • 優先度付きのファイアウォールの実装
  • コントロールプレーン
    • バックエンドのヘルスチェックとunhealthyなバックエンドの除外

164

165 of 169

おすすめの本

  • Learning eBPF

165

早くも日本語訳本が出版されます!!

166 of 169

おすすめの本

  • 入門 eBPF

166

167 of 169

おすすめの本

  • マスタリング TCP/IP 入門編

167

168 of 169

おすすめのコンテンツ

  • pandax381/microps
    • もっとパケット処理をやってみたい方は自作TCP/IPがおすすめ
    • 学生向けにはインターンシップという形で講義が開催されています

168

169 of 169

ロゴの使用について

※この資料は、eBPF財団と提携しておらず、またその他のスポンサーでもありません。

 https://ebpf.foundation/brand-guidelines/

169