तेजी से कोशिश करें超XDP प्रारंभिक भाग 2 (eBPF मानचित्र अनुभाग)

संक्षेप

पिछले लेख में, हमने XDP प्रोग्राम का उपयोग करके सभी पैकेट को DROP करने वाला प्रोग्राम लिखा।

eBPF के मानचित्र का उपयोग करके, DROP किए गए पैकेट की गणना करने वाला प्रोग्राम लिखने का प्रयास करेंगे।

इस बार भी, XDP+eBPF मानचित्र के साथ तेजी से काम करने के लिए यह चर्चा है।
इसलिए, eBPF जैसे विवरण या उपयोग नहीं किए गए मानचित्र के प्रकारों पर विस्तार से चर्चा नहीं की जाएगी।

पर्यावरण निर्माण

Ubuntu 22.04 को आधार मान लिया गया है।
XDP के माध्यम से NIC संचार को नियंत्रित करने के लिए, VM जैसे आभासी मशीन पर प्रयास करने की सिफारिश की जाती है।

सेटअप के लिए, पिछले लेख को देखें।

eBPF मानचित्र

eBPF मानचित्र एक डेटा संरचना है जिसका उपयोग कर्नेल और उपयोगकर्ता स्थान के बीच डेटा साझा करने के लिए किया जाता है। इसके द्वारा,
XDP प्रोग्राम रियल-टाइम में डेटा को अपडेट कर सकता है और उपयोगकर्ता स्थान इसे पढ़ सकता है।

इस बार, हम XDP में DROP किए गए पैकेटों की संख्या को गिनने वाला प्रोग्राम लिखेंगे।

eBPF मानचित्र के प्रकार

eBPF मानचित्र के कई प्रकार होते हैं।
लिनक्स कर्नेल दस्तावेज़ BPF मानचित्र में विवरण दर्ज है।
इस बार, हम निम्नलिखित प्रकार का उपयोग करेंगे।

  • BPF_MAP_TYPE_HASH
    • यह एक हैश तालिका का प्रतिनिधित्व करने वाला मानचित्र है।
    • हैश कुंजी और मान की जोड़ी को संग्रहीत कर सकता है।

इस बार, कुंजी के रूप में पैकेट के स्रोत IP पते का उपयोग किया जाएगा और मान में DROP किए गए पैकेटों की संख्या संग्रहीत की जाएगी।

eBPF मानचित्र का निर्माण

eBPF मानचित्र को eBPF प्रोग्राम के भीतर इस तरह परिभाषित किया जाता है।

struct {
    __uint(type, BPF_MAP_TYPE_HASH);
    __type(key, uint32_t);
    __type(value, uint64_t);
    __uint(max_entries, 1024);
} icmp_count_map SEC(".maps");
  • type : मानचित्र के प्रकार को निर्दिष्ट करता है।
  • key : कुंजी के प्रकार को निर्दिष्ट करता है। इस बार IPv4 पते को कुंजी के रूप में उपयोग करने के लिए, uint32_t निर्दिष्ट किया गया है।
  • value : मान के प्रकार को निर्दिष्ट करता है। इस बार DROP किए गए पैकेटों की संख्या को संग्रहीत करने के लिए, uint64_t निर्दिष्ट किया गया है।
  • max_entries : मानचित्र में संग्रहीत की जा सकने वाली अधिकतम प्रविष्टियों की संख्या को निर्दिष्ट करता है।
    • BPF_MAP_TYPE_HASH में प्रविष्टियों की संख्या को पार करने पर एक त्रुटि उत्पन्न होती है।
  • icmp_count_map : मानचित्र का नाम निर्दिष्ट करता है।
  • SEC(".maps") : मानचित्र को परिभाषित करने के लिए खंड को निर्दिष्ट करता है।

eBPF मानचित्र का संचालन

eBPF मानचित्र को eBPF प्रोग्राम से इस तरह संचालित किया जाता है।

  • bpf_map_lookup_elem : मानचित्र से कुंजी के अनुरूप मान प्राप्त करता है।
  • bpf_map_update_elem : मानचित्र में कुंजी और मान की जोड़ी जोड़ता है।
  • bpf_map_delete_elem : मानचित्र से कुंजी के अनुरूप मान को हटा देता है।
  • bpf_map_get_next_key : मानचित्र की अगली कुंजी प्राप्त करता है।

वास्तव में XDP प्रोग्राम लिखने का प्रयास करें

eBPF मानचित्र का उपयोग करके प्रोग्राम

eBPF मानचित्र का उपयोग करके प्रोग्राम निम्नलिखित है।

#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/icmp.h>
#include <arpa/inet.h>

// eBPF मानचित्र की परिभाषा
struct {
    __uint(type, BPF_MAP_TYPE_HASH);
    __type(key, uint32_t);
    __type(value, uint64_t);
    __uint(max_entries, 1024);
} icmp_count_map SEC(".maps");

SEC("xdp")
int xdp_drop_icmp(struct xdp_md *ctx) {
    void *data_end = (void *)(unsigned long)ctx->data_end;
    void *data = (void *)(unsigned long)ctx->data;

    // इथरनेट हैडर के पॉइंटर को प्राप्त करें
    struct ethhdr *eth = data;
    if ((void *)eth + sizeof(*eth) > data_end)
        return XDP_PASS;

    // जांचें कि इथरनेट फ़्रेम में IP पैकेट शामिल है या नहीं
    if (eth->h_proto != htons(ETH_P_IP))
        return XDP_PASS;

    // IP हैडर के पॉइंटर को प्राप्त करें
    struct iphdr *ip = data + sizeof(*eth);
    if ((void *)ip + sizeof(*ip) > data_end)
        return XDP_PASS;

    // जांचें कि प्रोटोकॉल ICMP है या नहीं
    if (ip->protocol != IPPROTO_ICMP)
        return XDP_PASS;

    // ICMP हैडर के पॉइंटर को प्राप्त करें
    struct icmphdr *icmp = (void *)ip + sizeof(*ip);
    if ((void *)icmp + sizeof(*icmp) > data_end)
        return XDP_PASS;

    // स्रोत IP पते को निकट करें
    uint32_t src_ip = ip->saddr;

    // स्रोत IP पते से icmp_count_map को खोजें
    uint64_t *count = bpf_map_lookup_elem(&icmp_count_map, &src_ip);

    // यदि u64 प्रकार का पॉइंटर NULL नहीं है, तो गणना में वृद्धि करें
    if (count) {
        __sync_fetch_and_add(count, 1);
    } else {
        // NULL होने पर, icmp_count_map में जोड़ें
        bpf_map_update_elem(&icmp_count_map, &src_ip, &(uint64_t){1}, BPF_ANY);
    }  

    // ICMP पैकेट को DROP करें
    return XDP_DROP;
}

char _license[] SEC("license") = "MIT";

निर्माण और लोडिंग

xdp.c को निर्माण करें और XDP प्रोग्राम को लोड करें।

$ clang -O2 -target bpf -g -c xdp.c -o xdp.o
$ sudo ip link set dev eth0 xdp obj xdp.o sec xdp

उपयोगकर्ता स्थान से मानचित्र को संदर्भित करें

मानचित्र को संदर्भित करने के लिए पिन सेट करें

उपयोगकर्ता स्थान से मानचित्र को संदर्भित करने के लिए, मानचित्र को पिन करें।
इससे, उपयोगकर्ता स्थान से मानचित्र को संदर्भित किया जा सकेगा।

  • BPF प्रोग्राम के मानचित्र id को प्राप्त करें

    $ sudo bpftool map
    16: hash  name icmp_count_map  flags 0x0
            key 4B  value 8B  max_entries 1024  memlock 86144B
            btf_id 25
    
    • 16 मानचित्र का id है।
  • मानचित्र में पिन सेट करें

    $ sudo bpftool map pin id 16 /sys/fs/bpf/xdp/icmp_count_map
    

उपयोगकर्ता स्थान प्रोग्राम लिखें जो eBPF मानचित्र को प्राप्त करे

चूंकि हमने eBPF मानचित्र में डेटा संग्रहीत किया है, अब हम उपयोगकर्ता स्थान से मानचित्र को संदर्भित करेंगे।

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <linux/bpf.h>
#include <linux/if_link.h>
#include <bpf/libbpf.h>
#include <bpf/bpf.h>

int main(int argc, char **argv) {
    int map_fd = bpf_obj_get("/sys/fs/bpf/xdp/icmp_count_map");
    if (map_fd < 0) {
        perror("bpf_obj_get");
        return 1;
    }

    struct bpf_map_info map_info = {};
    uint32_t info_len = sizeof(map_info);
    if (bpf_obj_get_info_by_fd(map_fd, &map_info, &info_len)) {
        perror("bpf_obj_get_info_by_fd");
        return 1;
    }

    // मानचित्र की जानकारी प्रदर्शित करें
    printf("मानचित्र का नाम: %s\n", map_info.name);
    printf("मानचित्र का प्रकार: %d\n", map_info.type);
    printf("मानचित्र की कुंजी का आकार: %d\n", map_info.key_size);
    printf("मानचित्र के मान का आकार: %d\n", map_info.value_size);
    printf("मानचित्र अधिकतम प्रविष्टियाँ: %d\n", map_info.max_entries);

    // सभी प्रविष्टियाँ प्राप्त करें
    uint32_t key, next_key;
    uint64_t value;
    key = next_key = 0;
    while (bpf_map_get_next_key(map_fd, &key, &next_key) == 0) {
        key = next_key; // अद्यतन की गई कुंजी का उपयोग करें
        if (bpf_map_lookup_elem(map_fd, &key, &value) == 0) {
            // IP पते को स्ट्रिंग में परिवर्तित करें
            char ip_str[INET_ADDRSTRLEN];
            inet_ntop(AF_INET, &key, ip_str, sizeof(ip_str));
            printf("कुंजी: %s, मान: %lu\n", ip_str, value);
        }
        key = next_key; // अगले लूप की तैयारी
    }

    return 0;
}
  • bpf_obj_get : पिन किए गए मानचित्र का फ़ाइल फ़ाइल निर्देशांक प्राप्त करता है।

    • /sys/fs/bpf/xdp/icmp_count_map के पिन किए गए मानचित्र को निर्दिष्ट करता है।
  • bpf_obj_get_info_by_fd : मानचित्र की जानकारी प्राप्त करता है।

    • मानचित्र का नाम, प्रकार, कुंजी का आकार, मान का आकार, और अधिकतम प्रविष्टियाँ प्राप्त करता है।
  • bpf_map_get_next_key : मानचित्र की अगली कुंजी प्राप्त करता है।

    • key : वर्तमान कुंजी
    • next_key : अगली कुंजी
  • bpf_map_lookup_elem : मानचित्र से कुंजी के अनुरूप मान प्राप्त करता है।

निर्माण और चलाना

$ gcc -o map_info map_info.c -lbpf
$ sudo ./map_info
मानचित्र का नाम: icmp_count_map
मानचित्र का प्रकार: 1
मानचित्र की कुंजी का आकार: 4
मानचित्र के मान का आकार: 8
मानचित्र अधिकतम प्रविष्टियाँ: 1024
कुंजी: 192.168.XX.10, मान: 320
कुंजी: 192.168.XX.11, मान: 2307

इससे, XDP प्रोग्राम द्वारा DROP किए गए ICMP पैकेट के स्रोत IP पते और गणना की संख्या प्रदर्शित होगी।

सफाई

  • मानचित्र को अनपिन करें

    $ sudo rm -iv /sys/fs/bpf/xdp/icmp_count_map
    
  • XDP प्रोग्राम को अनलोड करें

    $ sudo ip link set dev eth0 xdp off
    

XDP के लोड और मानचित्र को संदर्भित करने वाला प्रोग्राम

हर बार ip कमांड के साथ XDP को लोड करना और पिन सेट करना और मानचित्र तक पहुँच प्राप्त करना कठिन होता है।
एक स्थायी प्रोग्राम के मामले में, जब हम XDP प्रोग्राम को लोड करते हैं, तो हम मानचित्र के फ़ाइल फ़ाइल निर्देशांक को
प्राप्त कर सकते हैं, इसलिए पिन सेट किए बिना मानचित्र तक पहुँच प्राप्त करना संभव है।

इसके अलावा, जब हम प्रोग्राम को समाप्त करते हैं, तो XDP प्रोग्राम को अनलोड करने के लिए,
अनलोड करना या पिन को अनियंत्रित करना भूलने से बचा जा सकता है।

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <linux/bpf.h>
#include <linux/if_link.h>
#include <bpf/libbpf.h>
#include <bpf/bpf.h>
#include <unistd.h>
#include <net/if.h>

int main(int argc, char **argv) {
    struct bpf_object *obj;
    struct bpf_map *map;
    struct bpf_program *prog;
    struct bpf_link *link = NULL;
    int map_fd, prog_fd;
    int err;

    if (argc != 2) {
        fprintf(stderr, "उपयोग: %s <इंटरफ़ेस>\n", argv[0]);
        return 1;
    }

    // xdp प्रोग्राम को लोड करें
    obj = bpf_object__open_file("./xdp.o", NULL);
    if (libbpf_get_error(obj)) {
        perror("BPF ऑब्जेक्ट को खोलने में विफल");
        return 1;
    }

    if (bpf_object__load(obj)) {
        perror("BPF ऑब्जेक्ट को लोड करने में विफल");
        return 1;
    }

    // प्रोग्राम को प्राप्त करें
    prog = bpf_object__find_program_by_name(obj, "xdp_drop_icmp");
    if (libbpf_get_error(prog)) {
        perror("नाम के द्वारा BPF प्रोग्राम को खोजने में विफल");
        return 1;
    }

    // मानचित्र को प्राप्त करें
    map = bpf_object__find_map_by_name(obj, "icmp_count_map");
    if (!map) {
        perror("नाम से BPF मानचित्र को खोजने में विफल");
        return 1;
    }

    map_fd = bpf_map__fd(map);
    if (map_fd < 0) {
        perror("मानचित्र fd प्राप्त करने में विफल");
        return 1;
    }

    // eth0 पर XDP प्रोग्राम को अटैच करें
    const char* if_name = argv[1];
    int ifindex = if_nametoindex(if_name);
    if (ifindex == 0) {
        perror("इंटरफ़ेस अनुक्रमांक प्राप्त करने में विफल");
        return 1;
    }

    link = bpf_program__attach_xdp(prog, ifindex);
    if (libbpf_get_error(link)) {
        perror("XDP प्रोग्राम को अटैच करने में विफल");
        return 1;
    }

    // अनंत लूप में 1 सेकंड में एक बार मानचित्र की स्थिति प्रदर्शित करें
    while (1) {
        // रेखा का विभाजन
        printf("==============================\n");
        // मानचित्र की जानकारी प्रदर्शित करें
        struct bpf_map_info map_info = {};
        uint32_t info_len = sizeof(map_info);
        if (bpf_obj_get_info_by_fd(map_fd, &map_info, &info_len)) {
            perror("मानचित्र जानकारी प्राप्त करने में विफल");
            return 1;
        }

        printf("मानचित्र का नाम: %s\n", map_info.name);
        printf("मानचित्र का प्रकार: %d\n", map_info.type);
        printf("मानचित्र की कुंजी का आकार: %d\n", map_info.key_size);
        printf("मानचित्र के मान का आकार: %d\n", map_info.value_size);
        printf("मानचित्र अधिकतम प्रविष्टियाँ: %d\n", map_info.max_entries);

        // सभी प्रविष्टियाँ प्राप्त करें
        uint32_t key, next_key;
        uint64_t value;
        key = next_key = 0;
        while (bpf_map_get_next_key(map_fd, &key, &next_key) == 0) {
            key = next_key; // अद्यतन की गई कुंजी का उपयोग करें
            if (bpf_map_lookup_elem(map_fd, &key, &value) == 0) {
                // IP पते को स्ट्रिंग में परिवर्तित करें
                char ip_str[INET_ADDRSTRLEN];
                inet_ntop(AF_INET, &key, ip_str, sizeof(ip_str));
                printf("कुंजी: %s, मान: %lu\n", ip_str, value);
            }
            key = next_key; // अगले लूप की तैयारी
        }
        sleep(1);  // 1 सेकंड के अंतराल पर लूप
    }

    return 0;
}
  • bpf_object__open_file : एक फाइल से BPF ऑब्जेक्ट को खोलता है।
    • XDP के बन चुके ऑब्जेक्ट फ़ाइल को निर्दिष्ट करता है।
  • bpf_object__load : BPF ऑब्जेक्ट को लोड करता है।
    • BPF ऑब्जेक्ट को लोड करने से प्रोग्राम या मानचित्र को प्राप्त किया जा सकता है।
  • bpf_object__find_program_by_name : नाम द्वारा BPF प्रोग्राम को प्राप्त करता है।
    • XDP प्रोग्राम का नाम निर्दिष्ट करें। sec(“xdp”) के उपयोग से निर्दिष्ट की गई फ़ंक्शन का नाम प्रदान करें।
  • bpf_object__find_map_by_name : नाम द्वारा BPF मानचित्र को प्राप्त करता है।
    • मानचित्र का नाम निर्दिष्ट करें।
  • bpf_map__fd : मानचित्र का फ़ाइल फ़ाइल निर्देशांक प्राप्त करता है।
  • bpf_program__attach_xdp : XDP प्रोग्राम को निर्दिष्ट इंटरफ़ेस पर अटैच करता है।

निर्माण और चलाना

  • निर्माण

    $ gcc -o map_monitor map_monitor.c -lbpf
    
  • चलाना

    ./map_monitor eth0
    मानचित्र का नाम: icmp_count_map
    मानचित्र का प्रकार: 1
    मानचित्र की कुंजी का आकार: 4
    मानचित्र के मान का आकार: 8
    मानचित्र अधिकतम प्रविष्टियाँ: 1024
    कुंजी: 192.168.XX.2, मान: 4
    ==============================
    मानचित्र का नाम: icmp_count_map
    मानचित्र का प्रकार: 1
    मानचित्र की कुंजी का आकार: 4
    मानचित्र के मान का आकार: 8
    मानचित्र अधिकतम प्रविष्टियाँ: 1024
    कुंजी: 192.168.XX.2, मान: 5
    ^C
    

    Ctrl+C के साथ समाप्त करें।

समाप्त करने पर, XDP प्रोग्राम अनलोड हो जाएगा।

निष्कर्ष

हमने XDP प्रोग्राम के साथ DROP किए गए पैकेटों की संख्या को गिनने वाला प्रोग्राम लिखा है।
मानचित्र के उपयोग से, eBPF प्रोग्राम और उपयोगकर्ता स्थान प्रोग्राम के बीच डेटा का आदान-प्रदान करना संभव होता है,
जिससे XDP के अनुप्रयोगों की सीमा बढ़ती है।

अंत में एक नोट

वास्तव में, प्रदर्शन जैसे विचारों के लिए, BPF_MAP_TYPE_HASH का उपयोग करने के बजाय,
BPF_MAP_TYPE_PERCPU_HASH का उपयोग करना चाहिए, लेकिन इस बार सरलता के लिए BPF_MAP_TYPE_HASH का उपयोग किया गया है।