iptablesが大規模環境で限界を迎える理由
Linuxエンジニアは数十年にわたり<a href="https://itnotes.dev/ja/iptables-tee%e3%82%92%e4%bd%bf%e7%94%a8%e3%81%97%e3%81%9flinux%e3%81%a7%e3%81%ae%e3%83%88%e3%83%a9%e3%83%95%e3%82%a3%e3%83%83%e3%82%af%e3%83%9f%e3%83%a9%e3%83%bc%e3%83%aa%e3%83%b3%e3%82%b0%ef%bc%9a/">iptables</a>やnftablesを信頼してきました。これらは信頼性は高いものの、コストがゼロではありません。システムに入るすべてのパケットは、Netfilterフックの密集した森を通り抜けなければなりません。<a href="https://itnotes.dev/ja/10gbps%e3%83%aa%e3%83%b3%e3%82%af%e3%82%92%e6%9c%80%e5%a4%a7%e9%99%90%e3%81%ab%e6%b4%bb%e7%94%a8%e3%81%99%e3%82%8b%ef%bc%9a%e7%9c%9f%e3%81%ae%e3%83%91%e3%83%95%e3%82%a9%e3%83%bc%e3%83%9e%e3%83%b3/">10Gbps</a>や40Gbpsのリンクでは、このオーバーヘッドが膨大な<a href="https://itnotes.dev/ja/%e3%82%b7%e3%83%b3%e3%82%b0%e3%83%ab%e3%82%b3%e3%82%a2%e3%81%ae%e3%83%9c%e3%83%88%e3%83%ab%e3%83%8d%e3%83%83%e3%82%af%e3%82%92%e8%a7%a3%e6%b6%88%e3%81%99%e3%82%8b%ef%bc%9arss%e3%80%81rps%e3%80%81rfs/">CPU負荷</a>となります。実際、高密度な環境では、基本的なフィルタリングルールの管理だけでNetfilterがCPUサイクル全体の最大20%を消費することもあります。
eBPF (Extended Berkeley Packet Filter) は、このモデルを根本から覆します。TC (Traffic Control) レイヤーでeBPFを使用することで、JITコンパイルされたコードをカーネルのネットワークパス内で直接実行できます。ドライバーレベルでingress(受信)トラフィックに限定されるXDPとは異なり、TCはスタック의 조금 높은 위치에 존재합니다. 이 위치에는 큰 이점이 있습니다. 그것은 ingress와 egress(送信)の両方を処理できることです。また、sk_buff構造体へのフルアクセスが可能なため、複雑なパケットメタデータの処理も簡素化されます。
最近、あるチームがコンテナ間のレート制限に苦労していた際にサポートを行いました。標準的な<a href="https://itnotes.dev/ja/linux%e3%81%a7bufferbloat%e3%82%92%e8%a7%a3%e6%b6%88%e3%81%99%e3%82%8b%ef%bc%9a%e4%bd%8e%e9%81%85%e5%bb%b6%e3%83%8d%e3%83%83%e3%83%88%e3%83%af%e3%83%bc%e3%82%af%e3%81%ae%e3%81%9f%e3%82%81%e3%81%aesqm/">tcのqdisc</a>は柔軟性に欠け、iptablesによるパケット操作はピーク時に150ミリ秒のレイテンシスパイクを引き起こしていました。eBPF TCに移行したことで、パケットあたりの処理時間を15マイクロ秒短縮し、CPUオーバーヘッドを約35%削減できました。
TCの利点
- 双方向の制御: XDPは一方通行ですが、TCは入ってくるトラフィックと出ていくトラフィックの両方をフィルタリングできます.
- ハードウェアに依存しない: XDPは「ネイティブモード」のために特定のNICドライバーを必要とすることが多いですが、TCは物理NICから仮想ブリッジまで、Linuxカーネルが認識するあらゆるインターフェースで動作します。
- 豊富なコンテキスト: TCはカーネルがすでにパケットをパースした後に実行されます。バイトオフセットを手動で計算することなく、プロトコルフィールドに簡単にアクセスできます。
環境のセットアップ
これらのフィルタを構築するには、モダンなツールキットが必要です。C言語をBPFバイトコードにコンパイルするためにclangを使用し、そのコードをカーネルにロードするためにiproute2を使用します。最も安定したBPF機能を利用するために、Linuxカーネル 5.10以降を使用していることを確認してください。
UbuntuまたはDebianで依存関係をインストールします:
sudo apt update
sudo apt install -y clang llvm libelf-dev libbpf-dev gcc-multilib build-essential iproute2 bpftool
高速パケットフィルタの構築
特定のIPからのトラフィックを即座にドロップするクラシファイアを作成してみましょう。これは標準的なiptablesのDROPルールのプログラム可能な代替品として機能しますが、はるかに高速に動作します。
1. eBPF Cプログラムの記述
tc_filter.cという名前のファイルを作成します。SEC("classifier")マクロを使用してエントリポイントを定義します。
#include <linux/bpf.h>
#include <linux/pkt_cls.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/in.h>
#include <bpf/bpf_helpers.h>
#define TARGET_IP 0x0101A8C0 // 16進数表記の192.168.1.1 (リトルエンディアン)
SEC("classifier")
int handle_ingress(struct __sk_buff *skb) {
void *data_end = (void *)(long)skb->data_end;
void *data = (void *)(long)skb->data;
struct ethhdr *eth = data;
if ((void *)(eth + 1) > data_end)
return TC_ACT_OK;
if (eth->h_proto != __constant_htons(ETH_P_IP))
return TC_ACT_OK;
struct iphdr *ip = data + sizeof(struct ethhdr);
if ((void *)(ip + 1) > data_end)
return TC_ACT_OK;
if (ip->saddr == TARGET_IP) {
bpf_printk("次のIPからのトラフィックをブロック中: %pI4\n", &ip->saddr);
return TC_ACT_SHOT;
}
return TC_ACT_OK;
}
char _license[] SEC("license") = "GPL";
2. バイトコードへのコンパイル
標準のCコンパイラはここでは使用できません。bpfアーキテクチャを明示的にターゲットにする必要があります。
clang -O2 -target bpf -c tc_filter.c -o tc_filter.o
3. インターフェースへの適用
TC eBPFにはclsact (classifier action) qdiscが必要です。この仮想キューは、プログラムがトラフィックをインターセプトするために必要なフックを提供します。
# 1. eth0にclsact qdiscを作成
sudo tc qdisc add dev eth0 clsact
# 2. ingressフックにプログラムをロード
sudo tc filter add dev eth0 ingress bpf da obj tc_filter.o sec classifier
da (Direct Action) フラグは非常に重要です。これは、プログラムの戻り値(TC_ACT_SHOTなど)をパケットの最終的な判定として信頼するようカーネルに伝えます。
モニタリングとテスト
従来のファイアウォールとは異なり、eBPFはiptables -Lには表示されません。カーネル内部で何が起こっているかを確認するには、<a href="https://itnotes.dev/ja/%e3%83%8d%e3%83%83%e3%83%88%e3%83%af%e3%83%bc%e3%82%af%e9%81%85%e5%bb%b6%e3%81%ae%e5%8e%9f%e5%9b%a0%e3%82%92%e6%8e%a8%e6%b8%ac%e3%81%99%e3%82%8b%e3%81%ae%e3%81%af%e3%82%82%e3%81%86%e3%82%84%e3%82%81/">別の手段</a>が必要です。
フィルタの確認
次のコマンドを実行して、フィルタがインターフェースでアクティブであることを確認します:
tc filter show dev eth0 ingress
カーネルトレースの読み取り
bpf_printk関数はカーネルのトレースパイプにメッセージを送信します。これはデバッグにおいて最も強力な味方です。新しいターミナルを開いて実行してください:
sudo cat /sys/kernel/debug/tracing/trace_pipe
192.168.1.1からのパケットがインターフェースに到達すると、「次のIPからのトラフィックをブロック中」というメッセージが即座に表示されます。
パフォーマンス統計
フィルタが処理したパケット数を確認するには、統計フラグを使用します:
tc -s filter show dev eth0 ingress
さらなる活用:プログラム可能なQoS
パケットのドロップは始まりに過ぎません。eBPF TCを使用して、きめ細かなQuality of Service (QoS) を実現できます。BPF Mapを活用することで、数千の個別IPのバイト数を追跡できます。単なるドロップではなく、リアルタイムで帯域幅を計算するコードを記述することも可能です。
たとえば、あるIPが100MB/sのしきい値を超えた場合、プログラムはTC_ACT_SHOTを返し、範囲内であればTC_ACT_OKを返します。このような制御は、動的でカーディナリティの高いルールセットに苦労しがちな標準のtcクラスでは不可能です。このライフサイクルをマスターすることで、柔軟かつ驚異的に高速なネットワーキングロジックを構築できるようになります。

