Skip to content

非公式本サイトは非公式の日本語ドキュメントであり、Cloudflare 公式サイトではありません。最新情報はdevelopers.cloudflare.comをご確認ください。

Programmable Flow Protection (Beta)

最終更新 Markdown で表示Agent セットアップ

Programmable Flow Protection は、ゲームプロトコル、金融サービスプロトコル、VoIP、通信、ストリーミングなど、カスタムまたは標準化されたレイヤー 7 の UDP ベースプロトコルに対する DDoS 攻撃を防ぐシステムです。トポロジは非対称構成と対称構成の両方に対応しますが、検査するのはイングレストラフィックだけです。

Programmable Flow Protection は現在クローズドベータで、Magic TransitBYOIP または Cloudflare リース IP)のアドオンとしてのみ利用できます。システムを有効にしたい場合は、アカウントチームに連絡するか、この フォーム に記入してください。

仕組み

Programmable Flow Protection では、Cloudflare のグローバル anycast ネットワーク上で、パケット層のステートフルなプログラムを C で書いて実行できます。プログラムはユーザー空間で動く extended Berkeley Packet Filter(eBPF)として動作します。eBPF プログラム は、開発者が高性能なカスタムネットワークロジックを書けるパケットフィルターです。

Programmable Flow Protection は、UDP ベースのアプリケーションプロトコルを検査・解析し(ディープパケットインスペクション)、プログラムに基づいてパケットの扱いを決めます。カスタムプログラムのロジックで、許可されたユーザーを通し、攻撃を能動的に遮断できます。

このシステムは、Cloudflare のステートフル緩和基盤である flowtrackd プラットフォーム上に構築されています。Programmable Flow Protection は、DDoS Advanced Protection システムの 一般設定 に依存して動作します。Advanced Protection システムへルーティングするよう選んだ プレフィックス と、許可リスト を尊重します。Programmable Flow Protection を動かすには、Advanced DDoS Protection システムを 有効化 してください。

ベータ期間中は、Cloudflare がユーザーのコード作成を支援し、ガイダンスを提供します。人気のゲームプロトコルや VoIP プロトコル向けのすぐに使えるコードスニペット(テンプレート)は、後から提供する可能性があります。


はじめに

アカウントで Programmable Flow Protection が有効になったら、Cloudflare ダッシュボードの Networking > L3/4 DDoS Protection > Advanced Protection に移動します。Programmable Flow Protection タブで次を行います。

  1. C で書いた eBPF プログラムをアップロードします。

    プログラムはシステムで検証され、アカウントに保存されます。API がプログラムをコンパイルし、コンパイル済みプログラムに対して verifier を実行して、メモリチェックとプログラムの終了を確認します。コンパイルまたは検証に失敗すると、Cloudflare ダッシュボードに詳細なエラーメッセージが表示されます。

  2. ルール を作成します。

  3. プログラムの動作を確認するには、Network Analytics ダッシュボードを開き、Programmable Flow Protection タブを選択します。

追加のルールを作成し、ルール設定 をさまざまなリージョンや Cloudflare ロケーションに スコープ できます。モード(Mitigation または Monitoring)を変えて、トラフィックパターンや業務要件に合わせられます。

Programmable Flow Protection は Data Localization suite に対応しています。

基本的なプログラムを書く

次の手順では、IPv6 ヘッダー付きの User Datagram Protocol(UDP)トラフィックをすべてドロップするサンプルプログラムを書きます。宛先ポート 66 のトラフィックと、UDP ペイロードに特定のカスタムアプリケーションヘッダー値がないトラフィックもドロップします。

  1. 使うバージョン付きヘルパー関数を指定する define ディレクティブを追加します。

    Cloudflare が Programmable Flow Protection API に機能を追加すると、API の新しいバージョンを公開します。バージョンは後方互換です。

    #define CF_EBPF_HELPER_V0
  2. Cloudflare の eBPF ヘッダーファイルをインクルードします。

    これらのファイルには、BPF プログラムへの入力パケットデータを解析する ヘルパー関数 があります。

    #include <cf_ebpf_defs.h>
    #include <cf_ebpf_helper.h>
  3. パケット処理のエントリ関数を定義します。

    Cloudflare のプログラム検証を通すには、次と完全に同じ関数シグネチャが必要です。

    戻り値の型 uint64_t は、Cloudflare がパケットを通すかドロップするかを示します。関数名 cf_ebpf_main はプログラムのエントリポイントです。引数 void *state は、Cloudflare が BPF プログラムへ渡す入力データです。

    uint64_t cf_ebpf_main(void *state)
  4. 入力引数を使える構造体へキャストします。

    入力データを cf_ebpf_generic_ctx に変換します。これにより、読み取るメモリ上のデータ境界を Cloudflare に伝えます。

    次に、データ解析用の変数を宣言します。cf_ebpf_parsed_headers には IPv4、IPv6、UDP ヘッダーが入ります。cf_ebpf_packet_data には、Cloudflare が受け取った元の IP パケット(最大 1,500 バイト)と、パケット長および IP ヘッダー長が入ります。

    struct cf_ebpf_generic_ctx *ctx = state;
    struct cf_ebpf_parsed_headers headers;
    struct cf_ebpf_packet_data *p;
  5. ヘルパー関数を呼び出して変数を埋めます。

    手順 2 でインクルードしたヘッダーファイルに含まれるヘルパー関数 parse_packet_data を呼び出して、変数を埋める必要があります。

    parse_packet_data は、verifier を通すために必要なメモリチェックを行います。成功時は 0 を返し、入力パラメーターが正しく埋まります。失敗時は 1 を返します。parse_packet_data が失敗した場合、verifier を通すにはプログラムが CF_EBPF_DROP を返してパケットをドロップする必要があります。

    if (parse_packet_data(ctx, &p, &headers) != 0) {
        return CF_EBPF_DROP;
    }

    解析が成功したあとで使える値は次のとおりです。

    struct cf_ebpf_packet_data {
         /* Total length of the packet. */
         size_t   total_packet_length;
         /* Size of the IP header. Supports IPv4 (including options) and IPv6. */
         size_t   ip_header_length;
         /* Bytes of the packet, starting with the IP header. */
         uint8_t  packet_buffer[1500];
    };
    
    struct cf_ebpf_parsed_headers {
         /* Pointer to the parsed IPv4 header, if present (otherwise null). */
         struct iphdr   *ipv4;
         /* Pointer to the parsed IPv6 header, if present (otherwise null). */
         struct ipv6hdr *ipv6;
         /* Pointer to the parsed UDP header. */
         struct udphdr  *udp;
         /* Raw pointer to the last valid byte of the packet context data. */
         uint8_t        *data_end;
    };

    ヘルパー関数と構造体の完全な定義は、BPF ヘルパー関数と構造体 を参照してください。

  6. カスタムロジックを書きます。

    ここまでの手順は、ロジックに関係なく、どのプログラムでも同じになるコードです。

    ここから、独自のカスタムロジックを書けます。

    次のスニペットでは、IPv6 ヘッダーがあるパケット、または UDP 宛先ポートが 66 のパケットをドロップします。

    次に、UDP ペイロード内のアプリケーションヘッダー値を確認し、最後のバイトが固定値 0xCF であることを検証します。

     struct ipv6hdr *ipv6_hdr;
     struct udphdr *udp_hdr;
     ipv6_hdr = (struct ipv6hdr *)headers.ipv6;
     if (ipv6_hdr != NULL) {
       return CF_EBPF_DROP;
     }
     udp_hdr = (struct udphdr *)headers.udp;
     if (ntohs(udp_hdr->dest) == 66) {
         return CF_EBPF_DROP;
     }
    
     struct apphdr *app = (struct apphdr *)(udp_hdr + 1);
     if ((uint8_t *)(app + 1) > headers.data_end) {
         return CF_EBPF_DROP;
     }
    
     // The verifier has a special limit that it will not allow offsets
     // beyond 65535. We need this check (token_len > 64000) in order
     // to satisfy that, even though it is not possible.
     uint16_t token_len = app->length;
     if (token_len > 64000) {
         return CF_EBPF_DROP;
     }
    
     if ((uint8_t *)(app->token + token_len) > headers.data_end) {
         return CF_EBPF_DROP;
     }
    
     uint8_t *last_byte = app->token + token_len - 1;
     if (*last_byte != 0xCF) {
         return CF_EBPF_DROP;
     }
  7. プログラムロジックでドロップしなかったパケットは、CF_EBPF_PASS を返して通します。

    現在サポートしている戻り値は次のとおりです。

    • CF_EBPF_PASS = return value 0
    • CF_EBPF_DROP = return value 1

    プログラムを API にアップロードすると verifier が動き、既知の値型だけを返すことを強制します。

    return CF_EBPF_PASS;

参考として、基本プログラムの全体は次のとおりです。

#define CF_EBPF_HELPER_V0

#include <cf_ebpf_defs.h>
#include <cf_ebpf_helper.h>

struct apphdr {
    uint8_t       version;
    uint16_t      length;   // Length of the variable-length token
    unsigned char token[0]; // Variable-length token
} __attribute__((packed));

uint64_t
cf_ebpf_main(void *state)
{
    struct cf_ebpf_generic_ctx *ctx = state;
    struct cf_ebpf_parsed_headers headers;
    struct cf_ebpf_packet_data *p;

    if (parse_packet_data(ctx, &p, &headers) != 0) {
        return CF_EBPF_DROP;
    }
    struct ipv6hdr *ipv6_hdr;
    struct udphdr *udp_hdr;
    ipv6_hdr = (struct ipv6hdr *)headers.ipv6;
    if (ipv6_hdr != NULL) {
        return CF_EBPF_DROP;
    }

    udp_hdr = (struct udphdr *)headers.udp;
    if (ntohs(udp_hdr->dest) == 66) {
        return CF_EBPF_DROP;
    }

    struct apphdr *app = (struct apphdr *)(udp_hdr + 1);
    if ((uint8_t *)(app + 1) > headers.data_end) {
        return CF_EBPF_DROP;
    }

    // The verifier has a special limit that it will not allow offsets
    // beyond 65535. We need this check (token_len > 64000) in order
    // to satisfy that, even though it is not possible.
    uint16_t token_len = app->length;
    if (token_len > 64000) {
        return CF_EBPF_DROP;
    }

    if ((uint8_t *)(app->token + token_len) > headers.data_end) {
        return CF_EBPF_DROP;
    }

    uint8_t *last_byte = app->token + token_len - 1;
    if (*last_byte != 0xCF) {
        return CF_EBPF_DROP;
    }
    return CF_EBPF_PASS;
}

複雑なプログラムを書く: チャレンジベースの応答

次のサンプルプログラムは、同じ送信元 IP からのパケット間で状態を保持するヘルパー関数を使い、UDP ベースのチャレンジ応答を実装します。クライアントがトラフィックを通す前に、チャレンジを受信して応答できることを証明させることで、DDoS 攻撃の緩和に役立ちます。

チャレンジの仕組みは次のとおりです。

未知の送信元 IP からパケットが来ると、プログラムはランダムな nonce を含むチャレンジパケットを生成し、ステートテーブルでその送信元 IP を「challenged」としてマークします。元のパケットはドロップします。

すでにチャレンジ済みの送信元 IP からパケットが来ると、プログラムは正しいチャレンジ応答(nonce と秘密値の XOR)が含まれているかを確認します。応答が正しければ、送信元 IP を「verified」としてマークします。誤っていれば、送信元 IP を直ちにブロックリストに入れます。

検証済みの送信元 IP からのパケットは、追加チェックなしで通します。

  1. Cloudflare の eBPF ヘッダーファイルをインクルードし、ヘルパーのバージョンを定義します。

    #define CF_EBPF_HELPER_V0
    
    #include <cf_ebpf_defs.h>
    #include <cf_ebpf_helper.h>
  2. チャレンジ応答プロトコル用の定数を定義します。

    チャレンジ応答は、nonce と秘密値の XOR で計算します。有効期限は、challenged または verified の状態が有効な時間を決めます。

    #define CHALLENGE_SECRET 0xDEADBEEFCAFEBABEULL
    #define CHALLENGE_EXPIRY_SECS 60
    #define VERIFIED_EXPIRY_SECS 3600
  3. チャレンジパケット用の構造体を定義します。

    チャレンジパケットには、クライアントが応答すべき nonce と、クライアント応答用の領域があります。

    struct challenge_packet {
        uint64_t nonce;        // Random nonce for this challenge
        uint64_t response;     // Expected: nonce XOR CHALLENGE_SECRET
    };
  4. エントリ関数を定義し、パケットを解析します。

    uint64_t cf_ebpf_main(void *state)
    {
        struct cf_ebpf_generic_ctx *ctx = state;
        struct cf_ebpf_parsed_headers headers;
        struct cf_ebpf_packet_data *p;
    
        if (parse_packet_data(ctx, &p, &headers) != 0) {
            return CF_EBPF_DROP;
        }
    
        struct udphdr *udp_hdr = headers.udp;
  5. get_src_ip_status で送信元 IP の状態を確認します。

    ステータスは、この送信元 IP が新規、challenged、verified、blocklisted のいずれかを示します。expiry タイムスタンプは、ステータスの期限切れ時刻です。

        uint8_t status;
        uint64_t expiry;
        int ret = get_src_ip_status(&status, &expiry);
    
        // Check if status has expired
        int64_t now = timestamp();
        if (ret == 0 && expiry > 0 && (uint64_t)now > expiry) {
            // Status expired, treat as new connection
            ret = -1;
        }
  6. 検証済みの送信元 IP を処理します。

    Programmable Flow Protection プラットフォームは、ブロックリスト済み IP からのパケットを、プログラム呼び出し前にドロップします。ブロックリストのケースを明示的に扱う必要はありません。

    送信元 IP が検証済み(以前のチャレンジに合格)なら、パケットを通します。

        if (ret == 0 && status == CF_EBPF_SRC_IP_STATUS_VERIFIED) {
            return CF_EBPF_PASS;
        }
  7. チャレンジ済み送信元 IP からのチャレンジ応答かどうかを確認します。

    送信元 IP が以前にチャレンジ済みなら、現在のパケットに有効なチャレンジ応答があるかを確認します。応答が正しければ、送信元 IP を verified にします。誤っていれば、直ちにブロックリストに入れます。

        if (ret == 0 && status == CF_EBPF_SRC_IP_STATUS_CHALLENGED) {
            // Get the stored nonce from user data
            uint64_t stored_nonce;
            if (get_src_ip_data(&stored_nonce) != 0) {
                return CF_EBPF_DROP;
            }
    
            // Parse the challenge response from the packet payload
            struct challenge_packet *resp = (struct challenge_packet *)(udp_hdr + 1);
            if ((uint8_t *)(resp + 1) > headers.data_end) {
                return CF_EBPF_DROP;
            }
    
            // Verify the response: should be nonce XOR secret
            uint64_t expected_response = stored_nonce ^ CHALLENGE_SECRET;
            if (resp->response == expected_response) {
                // Correct response - mark as verified
                set_src_ip_status(CF_EBPF_SRC_IP_STATUS_VERIFIED, VERIFIED_EXPIRY_SECS);
                set_src_ip_data(0);  // Clear the nonce
                return CF_EBPF_PASS;
            }
    
            // Wrong response - blocklist immediately
            set_src_ip_status(CF_EBPF_SRC_IP_STATUS_BLOCKLISTED, 0);
            return CF_EBPF_DROP;
        }
  8. 新しい送信元 IP にチャレンジを発行します。

    ランダムな nonce を生成し、ステートテーブルに保存し、チャレンジパケットを作って set_challenge で送ります。

        // Generate a new challenge for this source IP
        uint64_t nonce = rand();
    
        // Store the nonce and mark as challenged
        set_src_ip_status(CF_EBPF_SRC_IP_STATUS_CHALLENGED, CHALLENGE_EXPIRY_SECS);
        set_src_ip_data(nonce);
    
        // Build the challenge packet to send back
        struct challenge_packet challenge;
        challenge.nonce = nonce;
        challenge.response = 0;  // Client will fill this in
    
        // Set the challenge packet buffer
        set_challenge((uint8_t *)&challenge, sizeof(challenge));
    
        // Drop the original packet until client responds to challenge
        return CF_EBPF_DROP;
    }

参考として、複雑なプログラムの全体は次のとおりです。

#define CF_EBPF_HELPER_V0

#include <cf_ebpf_defs.h>
#include <cf_ebpf_helper.h>

// Challenge-response protocol constants
#define CHALLENGE_SECRET 0xDEADBEEFCAFEBABEULL
#define CHALLENGE_EXPIRY_SECS 60
#define VERIFIED_EXPIRY_SECS 3600

// Challenge packet structure
struct challenge_packet {
    uint64_t nonce;
    uint64_t response;
};

uint64_t cf_ebpf_main(void *state)
{
    struct cf_ebpf_generic_ctx *ctx = state;
    struct cf_ebpf_parsed_headers headers;
    struct cf_ebpf_packet_data *p;

    if (parse_packet_data(ctx, &p, &headers) != 0) {
        return CF_EBPF_DROP;
    }

    struct udphdr *udp_hdr = headers.udp;

    // Check source IP status
    uint8_t status;
    uint64_t expiry;
    int ret = get_src_ip_status(&status, &expiry);

    // Check if status has expired
    int64_t now = timestamp();
    if (ret == 0 && expiry > 0 && (uint64_t)now > expiry) {
        ret = -1;  // Treat as new connection
    }

    // Handle verified source IPs - allow through
    if (ret == 0 && status == CF_EBPF_SRC_IP_STATUS_VERIFIED) {
        return CF_EBPF_PASS;
    }

    // Handle challenged source IPs - check for valid response
    if (ret == 0 && status == CF_EBPF_SRC_IP_STATUS_CHALLENGED) {
        uint64_t stored_nonce;
        if (get_src_ip_data(&stored_nonce) != 0) {
            return CF_EBPF_DROP;
        }

        // Parse challenge response from packet payload
        struct challenge_packet *resp = (struct challenge_packet *)(udp_hdr + 1);
        if ((uint8_t *)(resp + 1) > headers.data_end) {
            return CF_EBPF_DROP;
        }

        // Check response using XOR
        uint64_t expected_response = stored_nonce ^ CHALLENGE_SECRET;
        if (resp->response == expected_response) {
            // Correct response - mark as verified
            set_src_ip_status(CF_EBPF_SRC_IP_STATUS_VERIFIED, VERIFIED_EXPIRY_SECS);
            set_src_ip_data(0);
            return CF_EBPF_PASS;
        }

        // Wrong response - blocklist immediately
        set_src_ip_status(CF_EBPF_SRC_IP_STATUS_BLOCKLISTED, 0);
        return CF_EBPF_DROP;
    }

    // New source IP - issue initial challenge
    uint64_t nonce = rand();
    set_src_ip_status(CF_EBPF_SRC_IP_STATUS_CHALLENGED, CHALLENGE_EXPIRY_SECS);
    set_src_ip_data(nonce);

    struct challenge_packet challenge;
    challenge.nonce = nonce;
    challenge.response = 0;
    set_challenge((uint8_t *)&challenge, sizeof(challenge));

    return CF_EBPF_DROP;
}

このプログラムは、次の重要な考え方を示しています。

  • 状態管理: get_src_ip_statusset_src_ip_statusget_src_ip_dataset_src_ip_data を使い、送信元 IP ごとのチャレンジ状態を追跡します。
  • チャレンジの送信: set_challenge でチャレンジパケットをクライアントへ返します。
  • 暗号的な検証: 共有秘密を使い、クライアントがチャレンジに正しく応答したことを検証します。
  • 期限切れの処理: タイムスタンプを使い、古い状態エントリを期限切れにします。

複雑なプログラムを書く: レート制限

次のサンプルプログラムは、固定ウィンドウアルゴリズムで送信元 IP ごとのレート制限を実装します。時間ウィンドウ内に 1 つの送信元 IP が送れるパケット数を制限することで、大規模な DDoS 攻撃の緩和に役立ちます。

レート制限の仕組みは次のとおりです。

パケットが来ると、プログラムはその送信元 IP の保存済み状態を取得します。状態には、ウィンドウ開始タイムスタンプとパケットカウンターが、1 つの 64 ビット値にパックされています。現在時刻がまだウィンドウ内なら、カウンターを増やします。カウンターが設定した上限を超えると、パケットをドロップします。ウィンドウが期限切れになると、カウンターをリセットします。

  1. Cloudflare の eBPF ヘッダーファイルをインクルードし、ヘルパーのバージョンを定義します。

    #include <cf_ebpf_defs.h>
    #define CF_EBPF_HELPER_V0
    #include <cf_ebpf_helper.h>
  2. レート制限の設定用定数を定義します。

    RATE_LIMIT はウィンドウあたりの最大パケット数です。WINDOW_SECONDS は各時間ウィンドウの長さ(秒)です。

    #define RATE_LIMIT 100         // Maximum packets allowed per window
    #define WINDOW_SECONDS 60      // Time window in seconds
  3. 状態データをパック/アンパックするマクロを定義します。

    送信元 IP ステートテーブルは、送信元 IP ごとに 1 つの u64 値を保存します。タイムスタンプとカウンターの両方を追跡するため、上位 32 ビットにタイムスタンプ、下位 32 ビットにカウンターをパックします。

    #define PACK_STATE(ts, count) (((uint64_t)(ts) << 32) | ((uint64_t)(count) & 0xFFFFFFFF))
    #define UNPACK_TIMESTAMP(data) ((uint32_t)((data) >> 32))
    #define UNPACK_COUNTER(data) ((uint32_t)((data) & 0xFFFFFFFF))
  4. エントリ関数を定義し、現在のタイムスタンプを取得します。

    タイムスタンプヘルパーが失敗した場合は、誤検知を避けるためにパケットを通します。

    uint64_t cf_ebpf_main(void *state)
    {
        // Get current timestamp
        int64_t now = timestamp();
        if (now < 0) {
            return CF_EBPF_PASS; // If timestamp fails, allow the packet
        }
        uint32_t now_secs = (uint32_t)now;
  5. この送信元 IP の既存状態を取得します。

    get_src_ip_data で、この送信元 IP を以前に見たことがあるかを調べます。

        // Try to get existing state for this source IP
        uint64_t data;
        int ret = get_src_ip_data(&data);
        uint32_t window_start;
        uint32_t counter;
  6. 新しい送信元 IP の場合を処理します。

    エントリがない(戻り値が -1)場合は、この送信元 IP からの最初のパケットです。ウィンドウを現在時刻から開始し、カウンターを 1 に初期化します。

        if (ret == -1) {
            // No existing entry - first packet from this IP
            // Initialize: window starts now, counter = 1
            window_start = now_secs;
            counter = 1;
        }
  7. 既存の送信元 IP を処理し、時間ウィンドウを確認します。

    エントリがある場合は、保存済みのタイムスタンプとカウンターをアンパックします。ウィンドウが期限切れなら、両方の値をリセットします。そうでなければカウンターを増やし、レート制限を超えていないかを確認します。

        } else if (ret != 0) {
            // If there's other unknown error with getting src_ip_data, pass packet
            return CF_EBPF_PASS;
        } else {
            // Entry exists - unpack the state
            window_start = UNPACK_TIMESTAMP(data);
            counter = UNPACK_COUNTER(data);
            // Check if we're still in the same time window
            if (now_secs - window_start >= WINDOW_SECONDS) {
                // Window expired - reset counter and start new window
                window_start = now_secs;
                counter = 1;
            } else {
                // Still in same window - increment counter
                counter++;
                // Check if rate limit exceeded
                if (counter > RATE_LIMIT) {
                    // Drop packet without updating state
                    return CF_EBPF_DROP;
                }
            }
        }
  8. 更新した状態を保存し、パケットを通します。

    ウィンドウ開始タイムスタンプとカウンターを 1 つの値にパックし、送信元 IP ステートテーブルに保存します。

        // Store updated state
        uint64_t new_data = PACK_STATE(window_start, counter);
        set_src_ip_data(new_data);
        return CF_EBPF_PASS;
    }

参考として、レート制限プログラムの全体は次のとおりです。

#include <cf_ebpf_defs.h>
#define CF_EBPF_HELPER_V0
#include <cf_ebpf_helper.h>

// Rate limit configuration
// This program implements a fixed (not sliding) window ratelimit.
#define RATE_LIMIT 100         // Maximum packets allowed per window
#define WINDOW_SECONDS 60      // Time window in seconds

// The source IP table holds a mapping from source IP -> custom u64. We will make the custom u64 value in the 
// table hold a timestamp and a counter to accomplish a ratelimit.
// 
// NOTE: the source IP table is effectively a LRU cache. If it is full, old values will be evicted. 
// Values are also garbage collected from the table every 1hr.
// 
// The macros below pack the timestamp (upper 32 bits) and counter (lower 32 bits) into 64-bit data
// into a value that we can store into the source IP table.
#define PACK_STATE(ts, count) (((uint64_t)(ts) << 32) | ((uint64_t)(count) & 0xFFFFFFFF))
#define UNPACK_TIMESTAMP(data) ((uint32_t)((data) >> 32))
#define UNPACK_COUNTER(data) ((uint32_t)((data) & 0xFFFFFFFF))

uint64_t cf_ebpf_main(void *state)
{
    // Get current timestamp
    int64_t now = timestamp();
    if (now < 0) {
        return CF_EBPF_PASS; // If timestamp fails, allow the packet
    }
    uint32_t now_secs = (uint32_t)now;

    // Try to get existing state for this source IP
    uint64_t data;
    int ret = get_src_ip_data(&data);
    uint32_t window_start;
    uint32_t counter;

    if (ret == -1) {
        // No existing entry - first packet from this IP
        // Initialize: window starts now, counter = 1
        window_start = now_secs;
        counter = 1;
    } else if (ret != 0) {
        // If there's other unknown error with getting src_ip_data, pass packet
        return CF_EBPF_PASS;
    } else {
        // Entry exists - unpack the state
        window_start = UNPACK_TIMESTAMP(data);
        counter = UNPACK_COUNTER(data);
        // Check if we're still in the same time window
        if (now_secs - window_start >= WINDOW_SECONDS) {
            // Window expired - reset counter and start new window
            window_start = now_secs;
            counter = 1;
        } else {
            // Still in same window - increment counter
            counter++;
            // Check if rate limit exceeded
            if (counter > RATE_LIMIT) {
                // Drop packet without updating state
                // Here is where the actual ratelimit occurs.
                return CF_EBPF_DROP;
            }
        }
    }
    // Store updated state
    uint64_t new_data = PACK_STATE(window_start, counter);
    set_src_ip_data(new_data);
    return CF_EBPF_PASS;
}

このプログラムは、次の重要な考え方を示しています。

  • ビットパッキング: ビットシフトで、複数の値(タイムスタンプとカウンター)を 1 つの u64 に保存します。
  • 固定ウィンドウのレート制限: 離散的な時間ウィンドウ内のパケット数を追跡し、ウィンドウが期限切れになるとリセットします。
  • 穏やかなエラー処理: ヘルパー関数が失敗したときはパケットを通し、エッジケースでの誤検知を避けます。
  • ステートテーブルの動作: 送信元 IP ステートテーブルは LRU キャッシュです。容量に達すると古いエントリが追い出されます。1 時間アクセスがないエントリもガベージコレクションされます。

状態

各プログラムは、自身のローカル状態にアクセスできます。状態はサーバーごとにローカルで、データセンター間では共有されません。

状態は特定のプログラムに紐づきます。ルールのモード(disabled、monitoring、enabled)を変更しても、ステートテーブルの内容は残ります。一方、ルールのプログラム自体やプログラムの内容を変更すると、ステートテーブルはクリアされます。

プログラムが使えるステートテーブルは 2 つです。

送信元 IP ステートテーブル

送信元 IP ステートテーブルは、送信元 IP アドレスをキーに状態を保存します。各エントリには次が含まれます。

フィールド 説明
Status Enum 送信元 IP のステータスです。None (0)、Challenged (1)、Verified (2)、Blocklisted (3)。
User data u64 任意の用途で設定できるユーザー定義値です。

デフォルトの最大容量は 1,000 エントリです。

このテーブルを操作するヘルパー関数は次のとおりです。

  • get_src_ip_status — 現在のパケットの送信元 IP のステータスを取得します。
  • set_src_ip_status — 現在のパケットの送信元 IP のステータスを設定します。
  • get_src_ip_data — 現在のパケットの送信元 IP のユーザーデータを取得します。
  • set_src_ip_data — 現在のパケットの送信元 IP のユーザーデータを保存します。

送信元 IP ステートテーブルにエントリが作られる条件は次のとおりです。

  1. プログラムが set_src_ip_status を呼び出し、送信元 IP を Challenged、Verified、または Blocklisted にします。
  2. プログラムが set_src_ip_data を呼び出し、送信元 IP のカスタム u64 データを保存します。
  3. プログラムが、テーブルに既存エントリのない新しい送信元 IP に対して set_challenge を呼び出します。

フローステートテーブル

フローステートテーブルは、送信元 IP、送信元ポート、宛先 IP、宛先ポートの 4 タプルをキーに状態を保存します。各エントリには、任意の用途で設定できる u64 値があります。

デフォルトの最大容量は 10,000 エントリです。

このテーブルを操作するヘルパー関数は次のとおりです。

  • get_flow_data — 現在のフローのユーザーデータを取得します。
  • set_flow_data — 現在のフローのユーザーデータを保存します。

フローステートテーブルにエントリが作られる条件は次のとおりです。

  1. プログラムが set_flow_data を呼び出し、フローのカスタム u64 データを保存します。

キャッシュの動作

どちらのステートテーブルも LRU(least recently used)キャッシュです。テーブルが最大容量に達すると、最も古いエントリが追い出されて新しいエントリ用の空きができます。1 時間アクセスがないエントリもガベージコレクションされます。


BPF ヘルパー関数と構造体

ヘルパー関数は、Cloudflare ランタイムが提供し、顧客プログラムが呼び出す関数です。

BPF Instruction Set Architecture(ISA)がサポートするシステムコールは限られているため、ヘルパー関数は重要です。安全のため、Cloudflare は、プログラム開発者が変更できない既知のライブラリの所定リストだけで BPF オブジェクトファイルをコンパイルします。

ヘルパー関数の定義と verifier ラッパーのソースは GitHub にあります。

ヘルパー関数

parse_packet_data

cf_ebpf_generic_ctxcf_ebpf_packet_data から cf_ebpf_parsed_headers を構築します。verifier を通すために必要なメモリチェックを行います。

static inline int parse_packet_data(
   struct cf_ebpf_generic_ctx *ctx,
   struct cf_ebpf_packet_data **out_p,
   struct cf_ebpf_parsed_headers *out_headers
);

引数:

  • ctx — BPF プログラムに渡される汎用コンテキストへのポインターです。
  • out_p — パケットデータ構造体を受け取るポインターです。
  • out_headers — 解析済みヘッダー構造体を受け取るポインターです。

戻り値: 成功時は 0、失敗時は 1(パケットが短すぎる、長さが不正など)。成功時、out_headers には有効な IP および UDP ヘッダーのポインターが入ります。

rand

ランダムな符号なし整数を生成します。

uint64_t rand(void);

戻り値: ランダムな uint64_t 値です。

timestamp

現在の UNIX タイムスタンプ(1970 年 1 月 1 日 0:00:00 UTC からの閏秒を除く秒数)を返します。

int64_t timestamp(void);

戻り値: 現在のタイムスタンプを int64_t で返します。

hash_md5

ソースバッファーの MD5 ハッシュを計算し、結果を宛先バッファーに保存します。

int hash_md5(uint8_t *src, size_t src_len, uint8_t *dest, size_t dest_len);

引数:

  • src — ソースバッファーへのポインターです。
  • src_len — ソースバッファーの長さ(バイト)です。
  • dest — 宛先バッファーへのポインターです(最低 16 バイト必要)。
  • dest_len — 宛先バッファーの長さ(バイト)です。

戻り値:

  • 成功時は正の値(書き込んだバイト数)です。
  • ソースバッファーが無効な場合は -1 です。
  • 宛先バッファーが null または小さすぎる場合は -2 です。

hash_sha256

ソースバッファーの SHA-256 ハッシュを計算し、結果を宛先バッファーに保存します。

int hash_sha256(uint8_t *src, size_t src_len, uint8_t *dest, size_t dest_len);

引数:

  • src — ソースバッファーへのポインターです。
  • src_len — ソースバッファーの長さ(バイト)です。
  • dest — 宛先バッファーへのポインターです(最低 32 バイト必要)。
  • dest_len — 宛先バッファーの長さ(バイト)です。

戻り値:

  • 成功時は正の値(書き込んだバイト数)です。
  • ソースバッファーが無効な場合は -1 です。
  • 宛先バッファーが null または小さすぎる場合は -2 です。

hash_sha512

ソースバッファーの SHA-512 ハッシュを計算し、結果を宛先バッファーに保存します。

int hash_sha512(uint8_t *src, size_t src_len, uint8_t *dest, size_t dest_len);

引数:

  • src — ソースバッファーへのポインターです。
  • src_len — ソースバッファーの長さ(バイト)です。
  • dest — 宛先バッファーへのポインターです(最低 64 バイト必要)。
  • dest_len — 宛先バッファーの長さ(バイト)です。

戻り値:

  • 成功時は正の値(書き込んだバイト数)です。
  • ソースバッファーが無効な場合は -1 です。
  • 宛先バッファーが null または小さすぎる場合は -2 です。

hash_crc32

ソースバッファーの CRC32 ハッシュを計算し、結果を 64 ビット整数として保存します。バイトから整数への変換は内部で行う便利なラッパーです。

int hash_crc32(uint8_t *src, size_t src_len, uint64_t *dest);

引数:

  • src — ソースバッファーへのポインターです。
  • src_len — ソースバッファーの長さ(バイト)です。
  • dest — CRC32 結果を受け取る uint64_t へのポインターです。

戻り値:

  • 成功時は正の値(内部で書き込んだバイト数。常に 8)です。
  • ソースバッファーが無効な場合は -1 です。
  • 宛先バッファーが null の場合は -2 です。

hash_blake2b512

ソースバッファーの BLAKE2B-512 ハッシュを計算し、結果を宛先バッファーに保存します。

int hash_blake2b512(const uint8_t *src, size_t src_len, uint8_t *dest, size_t dest_len);

引数:

  • src — ソースバッファーへのポインターです。
  • src_len — ソースバッファーの長さ(バイト)です。
  • dest — 宛先バッファーへのポインターです(最低 64 バイト必要)。
  • dest_len — 宛先バッファーの長さ(バイト)です。

戻り値:

  • 成功時は正の値(書き込んだバイト数)です。
  • ソースバッファーが無効な場合は -1 です。
  • 宛先バッファーが null または小さすぎる場合は -2 です。

hmac_sha256

ソースバッファーの HMAC-SHA256 を計算し、結果を宛先バッファーに保存します。秘密鍵はプラットフォーム側で設定され、BPF プログラムには直接公開されません。鍵はサーバーごと、顧客ごとに一意です。

int hmac_sha256(uint8_t *src, size_t src_len, uint8_t *dest, size_t dest_len);

引数:

  • src — ソースバッファーへのポインターです。
  • src_len — ソースバッファーの長さ(バイト)です。
  • dest — 宛先バッファーへのポインターです(最低 32 バイト必要)。
  • dest_len — 宛先バッファーの長さ(バイト)です。

戻り値:

  • 成功時は正の値(書き込んだバイト数)です。
  • ソースバッファーが無効な場合は -1 です。
  • 宛先バッファーが null または小さすぎる場合は -2 です。

hmac_sha512

ソースバッファーの HMAC-SHA512 を計算し、結果を宛先バッファーに保存します。秘密鍵はプラットフォーム側で設定され、BPF プログラムには直接公開されません。鍵はサーバーごと、顧客ごとに一意です。

int hmac_sha512(uint8_t *src, size_t src_len, uint8_t *dest, size_t dest_len);

引数:

  • src — ソースバッファーへのポインターです。
  • src_len — ソースバッファーの長さ(バイト)です。
  • dest — 宛先バッファーへのポインターです(最低 64 バイト必要)。
  • dest_len — 宛先バッファーの長さ(バイト)です。

戻り値:

  • 成功時は正の値(書き込んだバイト数)です。
  • ソースバッファーが無効な場合は -1 です。
  • 宛先バッファーが null または小さすぎる場合は -2 です。

hmac_blake2b512

ソースバッファーの BLAKE2B-512 HMAC を計算し、結果を宛先バッファーに保存します。秘密鍵はプラットフォーム側で設定され、BPF プログラムには直接公開されません。鍵はサーバーごと、顧客ごとに一意です。

int hmac_blake2b512(const uint8_t *src, size_t src_len, uint8_t *dest, size_t dest_len);

引数:

  • src — ソースバッファーへのポインターです。
  • src_len — ソースバッファーの長さ(バイト)です。
  • dest — 宛先バッファーへのポインターです(最低 64 バイト必要)。
  • dest_len — 宛先バッファーの長さ(バイト)です。

戻り値:

  • 成功時は正の値(書き込んだバイト数)です。
  • ソースバッファーが無効な場合は -1 です。
  • 宛先バッファーが null または小さすぎる場合は -2 です。

set_challenge

現在のパケットにチャレンジデータを設定します。チャレンジパケットをクライアントへ返すときに使います。

int set_challenge(uint8_t *src, size_t src_len);

引数:

  • src — チャレンジデータバッファーへのポインターです。
  • src_len — チャレンジデータの長さ(バイト)です。0 の場合、チャレンジバッファーはリセットされます。

戻り値:

  • 成功時は 0 です。
  • ソースバッファーが無効、または許可された最大サイズを超える場合は -4 です。
  • チャレンジが有効でない場合は -5 です。
  • この送信元 IP へ最近チャレンジを送った、またはグローバルレート制限を超えた場合は -6 です。

get_src_ip_status

ステートテーブルから、送信元 IP アドレスに紐づくステータス値を取得します。

int get_src_ip_status(uint8_t *status, uint64_t *expiry);

引数:

  • status — ステータス値を受け取るポインターです(CF_EBPF_SRC_IP_STATUS_CHALLENGEDCF_EBPF_SRC_IP_STATUS_VERIFIED、または CF_EBPF_SRC_IP_STATUS_BLOCKLISTED)。expiry だけが必要な場合は null にできます。
  • expiry — 期限切れタイムスタンプを受け取るポインターです。ステータスだけが必要な場合は null にできます。

戻り値:

  • 成功時は 0 です。
  • 送信元 IP のエントリがない場合は -1 です。
  • 現在のパケットに送信元 IP コンテキストが設定されていない場合は -2 です。
  • 指定したバッファーが小さすぎる場合は -3 です。
  • statusexpiry の両方が null の場合は -4 です。
  • 送信元 IP ステートテーブルが有効でない場合は -5 です。

set_src_ip_status

ステートテーブルで、送信元 IP アドレスに紐づくステータス値を設定します。

int set_src_ip_status(uint8_t status, uint64_t expiry_secs);

引数:

  • status — 設定するステータス値です(CF_EBPF_SRC_IP_STATUS_CHALLENGEDCF_EBPF_SRC_IP_STATUS_VERIFIED、または CF_EBPF_SRC_IP_STATUS_BLOCKLISTED)。
  • expiry_secs — ステータスが期限切れになるまでの秒数です。0 の場合、ステータスは期限切れになりません。

戻り値:

  • 成功時は 0 です。
  • 現在のパケットに送信元 IP コンテキストが設定されていない場合は -2 です。
  • 送信元 IP ステートテーブルが有効でない場合は -5 です。

get_src_ip_data

ステートテーブルから、送信元 IP アドレスに紐づくカスタムデータを取得します。

int get_src_ip_data(uint64_t *data);

引数:

  • data — 保存済みデータ値を受け取るポインターです。

戻り値:

  • 成功時は 0 です。
  • 送信元 IP のエントリがない場合は -1 です。
  • 現在のパケットに送信元 IP コンテキストが設定されていない場合は -2 です。
  • 指定したバッファーが小さすぎる場合は -3 です。
  • data が null の場合は -4 です。
  • 送信元 IP ステートテーブルが有効でない場合は -5 です。

set_src_ip_data

ステートテーブルに、送信元 IP アドレスに紐づくカスタムデータを保存します。

int set_src_ip_data(uint64_t data);

引数:

  • data — 保存するデータ値です。

戻り値:

  • 成功時は 0 です。
  • 現在のパケットに送信元 IP コンテキストが設定されていない場合は -2 です。
  • 送信元 IP ステートテーブルが有効でない場合は -5 です。

get_flow_data

ステートテーブルから、現在のフローに紐づくカスタムデータを取得します。

int get_flow_data(uint64_t *data);

引数:

  • data — 保存済みデータ値を受け取るポインターです。

戻り値:

  • 成功時は 0 です。
  • フローのエントリがない場合は -1 です。
  • 現在のパケットにフローコンテキストが設定されていない場合は -2 です。
  • 指定したバッファーが小さすぎる場合は -3 です。
  • data が null またはアライメントされていない場合は -4 です。
  • フローステートテーブルが有効でない場合は -5 です。

set_flow_data

ステートテーブルに、現在のフローに紐づくカスタムデータを保存します。

int set_flow_data(uint64_t data);

引数:

  • data — 保存するデータ値です。

戻り値:

  • 成功時は 0 です。
  • 現在のパケットにフローコンテキストが設定されていない場合は -2 です。
  • フローステートテーブルが有効でない場合は -5 です。

entropy

ソースバッファーのシャノンエントロピーを計算します。結果はミリビットで返り、0(すべて同じバイト)から 8000(256 種類のバイト値が均等に分布)までです。

int64_t entropy(uint8_t *src, size_t src_len);

引数:

  • src — ソースバッファーへのポインターです。
  • src_len — ソースバッファーの長さ(バイト)です。

戻り値:

  • 成功時はエントロピー値(ミリビット、0〜8000)です。
  • ソースバッファーが無効な場合は -1 です。

set_network_analytics_tag

ネットワーク分析レポート用のカスタムタグを設定します。タグは、Network Analytics ダッシュボードのパケットサンプルと一緒に表示されます。デフォルトのサンプリングレートは 1/10,000 です。 プログラム実行あたり設定できるタグは 1 つです。set_network_analytics_tag を複数回呼び出した場合、最後のタグ値がパケットサンプルに適用されます。

int set_network_analytics_tag(uint64_t tag);

引数:

  • tag — 設定するタグ値です。未設定の場合のデフォルトは 0 です。

戻り値: 成功時は 0 です。

ntohs

16 ビット整数をネットワークバイトオーダーからホストバイトオーダーへ変換します。

uint16_t ntohs(uint16_t netshort);

引数:

  • netshort — ネットワークバイトオーダーの 16 ビット値です。

戻り値: ホストバイトオーダーの値です。

htons

16 ビット整数をホストバイトオーダーからネットワークバイトオーダーへ変換します。

uint16_t htons(uint16_t hostshort);

引数:

  • hostshort — ホストバイトオーダーの 16 ビット値です。

戻り値: ネットワークバイトオーダーの値です。

ntohl

32 ビット整数をネットワークバイトオーダーからホストバイトオーダーへ変換します。

uint32_t ntohl(uint32_t netlong);

引数:

  • netlong — ネットワークバイトオーダーの 32 ビット値です。

戻り値: ホストバイトオーダーの値です。

htonl

32 ビット整数をホストバイトオーダーからネットワークバイトオーダーへ変換します。

uint32_t htonl(uint32_t hostlong);

引数:

  • hostlong — ホストバイトオーダーの 32 ビット値です。

戻り値: ネットワークバイトオーダーの値です。

ntohll

64 ビット整数をネットワークバイトオーダーからホストバイトオーダーへ変換します。

uint64_t ntohll(uint64_t netlonglong);

引数:

  • netlonglong — ネットワークバイトオーダーの 64 ビット値です。

戻り値: ホストバイトオーダーの値です。

htonll

64 ビット整数をホストバイトオーダーからネットワークバイトオーダーへ変換します。

uint64_t htonll(uint64_t hostlonglong);

引数:

  • hostlonglong — ホストバイトオーダーの 64 ビット値です。

戻り値: ネットワークバイトオーダーの値です。

構造体

cf_ebpf_generic_ctx

BPF プログラムに渡される汎用コンテキスト構造体です。

struct cf_ebpf_generic_ctx {
   /* Pointer to the beginning of the context data. */
   uint64_t data;
   /* Pointer to the end of the context data. */
   uint64_t data_end;
   /* Space for the program to store metadata. */
   uint64_t meta_data;
};

cf_ebpf_packet_data

BPF プログラムに渡される生のパケットデータを保持します。

struct cf_ebpf_packet_data {
   /* Total length of the packet. */
   size_t   total_packet_length;
   /* Size of the IP header. Supports IPv4 (including options) and IPv6. */
   size_t   ip_header_length;
   /* Bytes of the packet, starting with the IP header. */
   uint8_t  packet_buffer[1500];
};

cf_ebpf_parsed_headers

解析済みの IP および UDP ヘッダーへのポインターを保持します。parse_packet_data を呼び出して埋めます。

struct cf_ebpf_parsed_headers {
   /* Pointer to the parsed IPv4 header, if present (otherwise null). */
   struct iphdr   *ipv4;
   /* Pointer to the parsed IPv6 header, if present (otherwise null). */
   struct ipv6hdr *ipv6;
   /* Pointer to the parsed UDP header. */
   struct udphdr  *udp;
   /* Raw pointer to the last valid byte of the packet context data. */
   uint8_t        *data_end;
};

iphdr

IPv4 ヘッダー構造体です。出典: Linux kernel

struct iphdr {
#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
    uint8_t  version:4,
             ihl:4;
#else
    uint8_t  ihl:4,
             version:4;
#endif
    uint8_t  tos;
    uint16_t tot_len;
    uint16_t id;
    uint16_t frag_off;
    uint8_t  ttl;
    uint8_t  protocol;
    uint16_t check;
    uint32_t saddr;
    uint32_t daddr;
};

ipv6hdr

IPv6 ヘッダー構造体です。出典: Linux kernel

struct ipv6hdr {
#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
    uint8_t  version:4,
             priority:4;
#else
    uint8_t  priority:4,
             version:4;
#endif
    uint8_t  flow_lbl[3];
    uint16_t payload_len;
    uint8_t  nexthdr;
    uint8_t  hop_limit;
    uint8_t  saddr[16];
    uint8_t  daddr[16];
};

udphdr

UDP ヘッダー構造体です。出典: Linux kernel

struct udphdr {
    uint16_t source;
    uint16_t dest;
    uint16_t len;
    uint16_t check;
};

プログラムのエンドポイント

プログラムをアップロードする

プログラムをアップロードするには、Cloudflare ダッシュボードで Networking > L3/4 DDoS protection > Advanced Protection に移動します。次に、Programmable Flow Protection というタブを選択します。

Programs で、「Upload new program」ボタンをクリックします。C ソースコードのファイルを選ぶよう求められます。

Cloudflare API は C ファイルのソースコードを受け取り、BPF バイトコードへコンパイルし、verifier を実行します。

コンパイルまたは検証に失敗すると、API は詳細なエラーメッセージを返します。

コンパイルと検証が成功すると、Cloudflare はソースコードとオブジェクトファイルをアカウントに保存し、プログラム ID を返します。

プログラムを更新する

開発中は、新しいプログラムを何度も作るのではなく、同じプログラム ID のプログラムを更新すると便利なことがあります。

プログラムを更新するには、プログラムの横にある 3 点メニューを選び、Overwrite を選択します。C ソースコードとしてアップロードするファイルを選ぶよう求められます。

すべてのプログラムを表示する

アップロードしたすべてのプログラムと成功状態を確認するには、Programs セクションのテーブルを表示します。

プログラム名の横のリンクアイコンは、そのプログラムが現在アクティブなルールで使われており、削除できないことを示します。

プログラムを削除する

プログラムを削除するには、削除したいプログラムの横にある 3 点メニューを選び、Delete を選択します。

アクティブな Rule から参照されているプログラムは削除できません。

「failed」ステータスのプログラム(コンパイルまたは検証に失敗したもの)は、30 日間使われないと自動で永続削除されます。


ルール

パケットごとに実行されるルールは 1 つだけです。アカウントに複数のルールがある場合、最も具体的な スコープ のルールが実行されます。たとえば、特定の colo にスコープしたルールは、リージョンにスコープしたルールより優先され、リージョンルールはグローバルルールより優先されます。そのため、グローバルルールは 1 つまでしか作成できません。

すべてのルールを一覧表示する

ルールと関連するルール ID を確認するには、Cloudflare ダッシュボードで Networking > L3/4 DDoS protection > Advanced Protection に移動します。次に Programmable Flow Protection を選択します。

ルールを作成する

ルールを作成するには、Cloudflare ダッシュボードで Networking > L3/4 DDoS protection > Advanced Protection に移動します。次に Programmable Flow Protection を選択します。

RulesCreate rule を選択します。新しいルールの各フィールドを入力します。プログラム、モード、スコープの選択を求められます。

ルールを更新する

既存のルールを更新するには、Rules セクションに移動します。ルールの横の 3 点メニューをクリックし、Edit を選択します。

ルールのモードとスコープを編集するよう求められます。プログラムの変更は安全でないロールアウトになるため、ルールのプログラムは編集できません。

ルールを削除する

既存のルールを削除するには、Rules セクションに移動します。ルールの横の 3 点メニューをクリックし、Delete を選択します。


Debug Packet CAPture (PCAP)

この API エンドポイントは、次を入力としてプログラムをデバッグします。

  • リクエストデータとしてバイナリ形式で渡す、入力 PCAP ファイルのローカルパスです。入力 PCAP ファイルの最大サイズは 5 MB で、大きすぎる場合は拒否されます。
  • リクエストパスで指定するプログラム ID です。
  • 任意のクエリパラメーター ip_offset=<value> で IP オフセットを指定します。これは、入力 PCAP ファイルの各パケットで IP ヘッダーが何バイトずれているかです。 ip offset クエリパラメーターを省略すると、API は正しいオフセット値を推定します。 たとえば、PCAP が Ethernet パケットをキャプチャしている場合、検出される IP オフセットは 14 です。このエンドポイントは、PCAP 内のすべてのパケットが同じ IP オフセット値を持つと仮定します。そうでない場合、パケットの解析は正しくありません。

このエンドポイントは、参照された BPF プログラムを入力 PCAP に対して実行し、注釈付きの新しい PCAP ファイルを出力します。出力 PCAP は入力 PCAP とまったく同じパケットを含み、各パケットの Packet Comment セクションにプログラムの判定が注釈されます。

Requestbash
curl "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/magic/programmable_flow_protection/configs/programs/$PROGRAM_ID/pcap" \
--header "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
--header "Content-Type: application/vnd.tcpdump.pcap" \
--data-binary "@<PATH_TO_INPUT_PCAP_FILE>" \
--output output.pcap

Packet Comment の注釈には次が含まれることがあります。

  • プログラムの戻り値: CF_EBPF_PASS または CF_EBPF_DROP
  • Ignored: 受信パケットが UDP でない場合
  • Analytics tag: プログラムがこのパケットに設定したカスタムのネットワーク分析タグ(ある場合)

出力 PCAP ファイルには次も含まれることがあります。

  • Challenge packet: プログラムからクライアントへ送られたチャレンジパケット(ある場合)

プログラムとルールを安全にデプロイするためのベストプラクティス

既存の本番トラフィックに影響を与えずに、プログラムを安全にデプロイしてテストしてください。最初のデプロイでは、グローバルスコープのルールを disabled にし、colo またはリージョンスコープのルールを monitoring にして、一部の IP トラフィックだけに作用するフィルター式を付ける方法があります。

各 Cloudflare リージョンまたは colo は、最も細かいルールを適用します。上記のシナリオでは、monitoring ルールで指定した colo またはリージョンは monitoring モードで開発者プログラムを実行し、それ以外の Cloudflare ロケーションではプログラムを実行しません。monitoring ルールは、フィルター式に一致するトラフィックだけを対象に実行されます。

Network Analytics で正しい動作を確認したあと、monitoring ルールのスコープとフィルター式を広げられます。最終的に disabledmonitoring のルールを削除し、グローバルな enabled ルールを適用できます。

Expression フィールドでプログラムを一部の IP またはプレフィックスに限定し、Mode フィールドで実際にパケットをドロップするかを決めると、ロールアウト時の安全性と粒度を確保できます。


Network Analytics

Programmable Flow Protection を通るトラフィックは、Network Analytics ダッシュボードで確認できます。

Network Analytics ダッシュボードで Programmable Flow Protection タブを選ぶと、この機能に基づいてトラフィックを絞り込めます。プログラム ID、カスタムのネットワーク分析タグ、アクション、IP、ポートでフィルターできます。デフォルトのサンプリングレートは 1/10,000 です。

役に立ちましたか?