1.BCCの概要
1.BCCの概要
BCCは、eBPFアプリケーションの開発プロセスを簡素化し、パフォーマンス分析に関連する多数のeBPFアプリケーションを収集するPythonライブラリです。BCCは、PythonやLuaなどのBPF開発にさまざまなフロントエンドサポートを提供し、マップの作成、コードのコンパイル、解析、挿入などの操作を実現するため、開発者はC言語で挿入するカーネルコードの開発に集中するだけで済みます。
BCCツールセットのほとんどのツールはLinuxKernel 4.1以降でサポートされている必要があり、完全なツールサポートにはLinux Kernel4.15以降が必要です。
GitHub:https://github.com/iovisor/bcc
2.BCCのインストール
yum install bcc-tools
export PATH=$PATH:/usr/share/bcc/tools
2つの一般的に使用されるコマンドツール
1、opensnoop
Opensnoopは、open()システム呼び出しを追跡してファイルを開こうとするプロセスを表示します。これは、構成ファイルやログファイルを見つけたり、起動に失敗した失敗したアプリケーションのトラブルシューティングに使用できます。
Opensnoopは、sys_open()カーネル関数を動的に追跡し、関数の変更を更新します。OpensnoopにはLinuxカーネル4.5のサポートが必要です。BPFを使用するため、root権限が必要です。opensnoop [-h] [-T] [-U] [-x] [-p PID] [-t TID] [-u UID] [-d DURATION] [-n NAME] [-e] [-f FLAG_FILTER]
-h、-help:ヘルプ情報ビュー
-T、-timestamp:出力結果印刷タイムスタンプ
-U、-print-uid:印刷UID
-x、-failed:失敗したオープンシステム呼び出しのみを表示
-p PID、 --pid PID:追跡のみを行うプロセスのPID
-t TID、-tid TID:TIDスレッドのみを追跡
-u UID、-uid UID:UIDのみを追跡
-d DURATION、-duration DURATION:時間を追跡(秒単位)
- n NAME、-name NAME:名前
-e、
-extended_fieldsを含む印刷プロセスのみ:拡張フィールドを表示します-f FLAG_FILTER、-flag_filter FLAG_FILTER:O_WRONLYなどのフィルターフィールドを指定します
2、execsnoop
execsnoopは、execシステム呼び出しをトレースすることによって新しいプロセスを追跡します。execの代わりにforkを使用するプロセスは、表示される結果に含まれません。
execsnoopにはBPFサポートが必要なため、root権限が必要です。execsnoop [-h] [-T] [-t] [-x] [-q] [-n NAME] [-l LINE] [--max-args MAX_ARGS]
-h:ヘルプ情報の表示
-T:タイムスタンプの印刷、形式HH:MM:SS
-t:タイムスタンプの印刷
-x:失敗の実行exec
-n NAME:正規式が名前と一致するコマンドラインのみを印刷します
-l LINE:のみ印刷パラメータのLINEに一致するコマンドライン
--max-argsMAXARGS:パラメータの最大数を解析して表示します。デフォルトは20です。
3、生体潜時
Biolatencyは、ブロックデバイスのIOを追跡し、IOレイテンシ分布を記録して、ヒストグラムに表示します。Biolatencyは、blk_
ファミリの機能を動的に追跡し、機能の変更を記録します。
BiolatencyにはBPFサポートが必要なため、root権限が必要です。biolatency [-h] [-F] [-T] [-Q] [-m] [-D] [interval [count]]
-h使用状況メッセージを
出力します。-T:タイムスタンプを出力します。
-m:ミリ秒レベルのヒストグラムを出力します。-
D:各ディスクデバイスのヒストグラムを出力します。-
F:各IOセットのヒストグラムを
出力します。間隔:出力間隔
カウント:出力量
4、ext4slower
ext4slowerは、ext4ファイルシステムの読み取り、書き込み、オープン、および同期操作を追跡し、対応する操作に費やされた時間を測定し、しきい値を超える詳細情報を出力します。デフォルトのしきい値の最小値は10msです。しきい値が0の場合、すべてのイベントが出力されます。
ext4slowerにはBPFサポートが必要なため、root権限が必要です。
ext4slowerは、ファイルシステムを介して独立した遅いディスクIOを識別できます。ext4slower [-h] [-j] [-p PID] [min_ms]
-h、-help:ヘルプ情報の表示
-j、-csv:csv形式でフィールドを出力します
-p PID、-pid PID:PIDプロセスのみを追跡します
min_ms:IOしきい値を追跡します。デフォルトは10です。
5、biosnoop
biosnoopは、デバイスIOを追跡し、各IOデバイスの要約情報の行を出力できます。
biosnoopblk_
は、ファミリの機能を動的に追跡し、機能の変更を記録します。
BiosnoopにはBPFサポートが必要なため、root権限が必要です。
biosnoop [-hQ]
-h:ヘルプ情報を
表示します-Q:OSキューで費やされた時間を表示します
6、cachestat
Cachestatは、Linuxページのヒット率とミス率をカウントし、カーネルページのキャッシュ機能を動的に追跡し、キャッシュ機能の変更を更新するために使用されます。
CachestatにはBPFサポートが必要なため、root権限が必要です。cachestat [-h] [-T] [interval] [count]
-h:ヘルプ情報を表示します
-T、-timestamp:出力タイムスタンプ
間隔:出力間隔、秒単位
カウント:出力量
7、キャッシュトップ
Cachetopは、各プロセスのLinuxページキャッシュのヒット率とミス率をカウントし、カーネルページのキャッシュ機能を動的に追跡し、キャッシュ機能の変更を更新するために使用されます。
CachestatにはBPFサポートが必要なため、root権限が必要です。
c achetop [-h] [interval]
-h:ヘルプ情報の表示
間隔:出力間隔
PID:プロセスID
UID:プロセスユーザーID
HITS:ページキャッシュヒットの数
MISSES:ページキャッシュミスの数
DIRTIES:ページキャッシュに追加されたダーティページの数
READ_HIT%:ページキャッシュの読み取りヒットレート
WRITE_HIT%:ページキャッシュの書き込みヒット率
BUFFERS_MB:バッファサイズ、データソース/ proc / meminfo
CACHED_MB:現在のページのキャッシュサイズ、データソース/ proc / meminfo
8、tcpconnect
tcpconnectは、アクティブなTCP接続の数を追跡し、カーネルのtcp_v4_connectおよびtcp_v6_connect関数を動的に追跡し、関数内の変更を記録するために使用されます。
tcpconnectにはBPFサポートが必要なため、root権限が必要です。tcpconnect [-h] [-c] [-t] [-x] [-p PID] [-P PORT]
-h:ヘルプ情報を表示します
-t:タイムスタンプを出力します
-c:各送信元IPと宛先IP /ポートの接続数をカウントし
ます
-pPID:PIDプロセスのみを追跡します-P PORT:追跡する宛先ポートのリストをコンマで区切ります
9、トレース
トレースは、関数呼び出しをトレースし、関数パラメーターまたは戻り値を出力するために使用されます。BPFサポートが必要なため、root特権が必要です。trace [-h] [-b BUFFER_PAGES] [-p PID] [-L TID] [-v] [-Z STRING_SIZE] [-S] [-s SYM_FILE_LIST] [-M MAX_EVENTS] [-t] [-u] [-T] [-C] [-K] [-U] [-a] [-I header] probe [probe ...]
-h:ヘルプ情報を表示します
-p PID:PIDプロセスのみを追跡します
-L TID:TIDスレッドのみを追跡します
-v:生成されたBPFプログラムを表示し、デバッグに使用します
-z STRING_SIZE:文字列パラメーターの長さを収集します
-s SYM_FILE_LIST:スタックサイズを収集します
-M MAX_EVENTS:
印刷されるトレースメッセージの最大数-t:印刷時間(秒単位)。
-u:タイムスタンプを
印刷します-T:タイムカラムを印刷します
-C:CPU IDを印刷します
-K:
各イベントのカーネルスタックを印刷します-U:各イベントのユーザースタックを印刷します
-a:カーネルスタックと仮想ユーザースタックのシーケンスを印刷しますアドレス
-Iヘッダー:ヘッダーファイルをBPFプログラム
プローブに追加します[プローブ...]:オープンシステム呼び出しのすべての呼び出しメソッドをトレースし、malloc呼び出しをトレースして出力するために、関数
trace ':: do_sys_open "%s"、arg2'に接続されたプローブ割り当てられたメモリのサイズを申請して、pthread_create関数呼び出しを追跡し、スレッド開始関数アドレスを出力しますtrace ':c:malloc "size = %d", arg1'
trace 'u:pthread:pthread_create "start addr = %llx", arg3'
10、デッドロック
デッドロックは、実行中のプロセスで潜在的なデッドロックを見つけるために使用されます。デッドロックには、アップローブイベントをアタッチすることによるBPFサポートが必要なため、ルート権限が必要です。deadlock [-h] [--binary BINARY] [--dump-graph DUMP_GRAPH] [--verbose] [--lock-symbols LOCK_SYMBOLS] [--unlock-symbols UNLOCK_SYMBOLS] pid
-h、-help:ヘルプ情報を表示します
--binary BINARY:スレッドライブラリを指定します。動的リンカーには指定する必要があります。
--dump-graph DUMP_GRAPH:ミューテックスグラフを指定されたファイルにエクスポートします
--verbose:ミューテックス統計を
出力します--lock-symbols LOCK_SYMBOLS:追跡するロックのリストで、コンマで区切ります。デフォルトはpthread_mutex_lockです。
--unlock-symbols UNLOCK_SYMBOLS:追跡するロック解除のリストで、コンマで区切ります。デフォルトはpthread_mutex_unlockです。
pid:deadlock 181 --binary /lib/x86_64-linux-gnu/libpthread.so.0
プロセス181で潜在的なデッドロックを見つけるために追跡されるプロセスID 。プロセスが動的リンカーによって作成されている場合は、-binaryで指定されたスレッドライブラリを使用する必要があります。
11、memleak
memleakは、メモリ割り当てとリリースマッチングを追跡および検索するために使用され、Linux Kernel4.7以降のサポートが必要です。memleak [-h] [-p PID] [-t] [-a] [-o OLDER] [-c COMMAND] [--combined-only] [-s SAMPLE_RATE] [-T TOP] [-z MIN_SIZE] [-Z MAX_SIZE] [-O OBJ] [INTERVAL] [COUNT]
-h:ヘルプ情報の表示
-p PID:プロセスPIDの指定
-t:すべてのメモリ割り当てと解放要求と結果の追跡
-a:解放されていないメモリのリストの出力
-z MIN_SIZE:割り当てられたメモリの最小値の
キャプチャ-Z MAX_SIZE:割り当てのキャプチャメモリの最大値はmemleak -z 16 -Z 32
、16バイトから32バイトの間のメモリ割り当てのみをキャプチャして分析します。
3、BCCプログラミング開発
1.BCC実現の原則
BCCは、eBPFからデータを抽出するための上位レベルのパッケージであるeBPFのツールセットです。BCCツールのプログラミング形式は、PythonのネストされたBPFプログラムです。Pythonコードは、eBPFの使いやすい上位レベルのインターフェイスをユーザーに提供し、データ処理にも使用できます。BPFプログラムは、データを抽出するためにカーネルに挿入されます。BPFプログラムの実行中、BPFプログラムはLLVMによってコンパイルされてBPF命令セットのelfファイルを取得し、カーネルに注入できる部分がelfファイルから解析され、bpf_load_programメソッドを使用して注入が完了します。
bpf_load_programインジェクションプログラムメソッドは、複雑なベリファイアメカニズムを追加します。インジェクションプログラムを実行する前に、システムの安全性を最大限に確保するために一連の安全性チェックが実行されます。セキュリティチェックされたBPFバイトコードは、カーネルJITを使用してコンパイルされ、ネイティブアセンブリ命令が生成され、カーネル固有のフックプログラムにアタッチされます。最後に、カーネルモードとユーザーモードは効率的なマップメカニズムを介して通信し、BCCツールはユーザーモードでのデータ処理にPythonを使用します。
2.BCCの実装例
Pythonコーディングの一部では、使用するモジュールとパッケージを導入する必要があります。
BCCツールのPythonコードでは、BPF C言語プログラムコードは次のように使用されます
。hello_world.py:
#!/usr/bin/python3
from bcc import BPF
bpf_program = '''
int kprobe__sys_clone(void *ctx)
{
bpf_trace_printk("Hello, World!\\n");
return 0;
}'''
if __name__ == "__main__":
BPF(text=bpf_program).trace_print()
kprobe__sys_clone
これは、kprobesを介したカーネル動的追跡のショートカットです。C関数がで始まる場合kprobe__
、残りは検出されるカーネル関数の名前と見なされます。
bpf_trace_printk:出力python3 hello_world.py
3.DDOS防御の例
#!/usr/bin/python
from bcc import BPF
import pyroute2
import time
import sys
flags = 0
def usage():
print("Usage: {0} [-S] <ifdev>".format(sys.argv[0]))
print(" -S: use skb mode\n")
print("e.g.: {0} eth0\n".format(sys.argv[0]))
exit(1)
if len(sys.argv) < 2 or len(sys.argv) > 3:
usage()
if len(sys.argv) == 2:
device = sys.argv[1]
if len(sys.argv) == 3:
if "-S" in sys.argv:
# XDP_FLAGS_SKB_MODE
flags |= 2 << 0
if "-S" == sys.argv[1]:
device = sys.argv[2]
else:
device = sys.argv[1]
mode = BPF.XDP
ctxtype = "xdp_md"
# load BPF program
b = BPF(text = """
#define KBUILD_MODNAME "foo"
#include <uapi/linux/bpf.h>
#include <linux/in.h>
#include <linux/if_ether.h>
#include <linux/if_packet.h>
#include <linux/if_vlan.h>
#include <linux/ip.h>
#include <linux/ipv6.h>
// how to determin ddos
#define MAX_NB_PACKETS 1000
#define LEGAL_DIFF_TIMESTAMP_PACKETS 1000000
// store data, data can be accessd in kernel and user namespace
BPF_HASH(rcv_packets);
BPF_TABLE("percpu_array", uint32_t, long, dropcnt, 256);
static inline int parse_ipv4(void *data, u64 nh_off, void *data_end) {
struct iphdr *iph = data + nh_off;
if ((void*)&iph[1] > data_end)
return 0;
return iph->protocol;
}
static inline int parse_ipv6(void *data, u64 nh_off, void *data_end) {
struct ipv6hdr *ip6h = data + nh_off;
if ((void*)&ip6h[1] > data_end)
return 0;
return ip6h->nexthdr;
}
// determine ddos
static inline int detect_ddos(){
// Used to count number of received packets
u64 rcv_packets_nb_index = 0, rcv_packets_nb_inter=1, *rcv_packets_nb_ptr;
// Used to measure elapsed time between 2 successive received packets
u64 rcv_packets_ts_index = 1, rcv_packets_ts_inter=0, *rcv_packets_ts_ptr;
int ret = 0;
rcv_packets_nb_ptr = rcv_packets.lookup(&rcv_packets_nb_index);
rcv_packets_ts_ptr = rcv_packets.lookup(&rcv_packets_ts_index);
if(rcv_packets_nb_ptr != 0 && rcv_packets_ts_ptr != 0){
rcv_packets_nb_inter = *rcv_packets_nb_ptr;
rcv_packets_ts_inter = bpf_ktime_get_ns() - *rcv_packets_ts_ptr;
if(rcv_packets_ts_inter < LEGAL_DIFF_TIMESTAMP_PACKETS){
rcv_packets_nb_inter++;
} else {
rcv_packets_nb_inter = 0;
}
if(rcv_packets_nb_inter > MAX_NB_PACKETS){
ret = 1;
}
}
rcv_packets_ts_inter = bpf_ktime_get_ns();
rcv_packets.update(&rcv_packets_nb_index, &rcv_packets_nb_inter);
rcv_packets.update(&rcv_packets_ts_index, &rcv_packets_ts_inter);
return ret;
}
// determine and recode by proto
int xdp_prog1(struct CTXTYPE *ctx) {
void* data_end = (void*)(long)ctx->data_end;
void* data = (void*)(long)ctx->data;
struct ethhdr *eth = data;
// drop packets
int rc = XDP_PASS; // let pass XDP_PASS or redirect to tx via XDP_TX
long *value;
uint16_t h_proto;
uint64_t nh_off = 0;
uint32_t index;
nh_off = sizeof(*eth);
if (data + nh_off > data_end)
return rc;
h_proto = eth->h_proto;
// parse double vlans
if (detect_ddos() == 0){
return rc;
}
rc = XDP_DROP;
#pragma unroll
for (int i=0; i<2; i++) {
if (h_proto == htons(ETH_P_8021Q) || h_proto == htons(ETH_P_8021AD)) {
struct vlan_hdr *vhdr;
vhdr = data + nh_off;
nh_off += sizeof(struct vlan_hdr);
if (data + nh_off > data_end)
return rc;
h_proto = vhdr->h_vlan_encapsulated_proto;
}
}
if (h_proto == htons(ETH_P_IP))
index = parse_ipv4(data, nh_off, data_end);
else if (h_proto == htons(ETH_P_IPV6))
index = parse_ipv6(data, nh_off, data_end);
else
index = 0;
value = dropcnt.lookup(&index);
if (value)
*value += 1;
return rc;
}
""", cflags=["-w", "-DCTXTYPE=%s" % ctxtype])
fn = b.load_func("xdp_prog1", mode)
b.attach_xdp(device, fn, flags)
dropcnt = b.get_table("dropcnt")
prev = [0] * 256
print("Printing drops per IP protocol-number, hit CTRL+C to stop")
while 1:
try:
for k in dropcnt.keys():
val = dropcnt.sum(k).value
i = k.value
if val:
delta = val - prev[i]
prev[i] = val
print("{}: {} pkt/s".format(i, delta))
time.sleep(1)
except KeyboardInterrupt:
print("Removing filter from device")
break;
b.remove_xdp(device, flags)