..

BPF Günlükleri: Sıfırdan Packet Counter Uygulaması

eBPF XDP

Bu yazıda, eBPF yapısını kullanarak sıfırdan bir paket sayacı uygulaması geliştireceğiz. Uygulamamızı C dili ile yazdıktan sonra Cilium’un bpf2go aracı ile derleyeceğiz. Sonrasında uygulamamızı kernel’a atacağımız bir Go kodu ile yazımızı tamamlayacağız. Şimdi malzeme listesiyle başlayalım.

Gereksinimler

  • Linux (BPF desteği olan bir kernel)
  • Clang ve LLVM (eBPF programlarını derlemek için)
  • Go (eBPF programlarını yüklemek ve kontrol etmek için)

Uygulama

  • Öncelikle eBPF ile çalışacak programımızı yazalım. Bu program, gelen paketleri sayacak ve bir sayaç değerini arttıracak.
//go:build ignore

#include <linux/bpf.h> // Bu header dosyası her kernel sürümü için farklılık gösterir. "apt-get install linux-headers-$(uname -r)" komutu ile kernelinize uygun header dosyalarını yükleyebilirsiniz

#include <bpf/bpf_helpers.h>

struct {
    __uint(type, BPF_MAP_TYPE_ARRAY);
    __type(key, __u32);
    __type(value, __u64);
    __uint(max_entries, 1);
} pkt_count SEC(".maps");

SEC("xdp") // XDP hook'unu kullanarak çalışacak
int count_packets() {
    __u32 key    = 0;
    __u64 *count = bpf_map_lookup_elem(&pkt_count, &key);
    if (count) {
        __sync_fetch_and_add(count, 1);
    }

    return XDP_PASS;
}
  • Ardından, bu programı bpf2go aracı ile derleyebilmemizi sağlayacak Go programını yazalım.
package main

//go:generate go run github.com/cilium/ebpf/cmd/bpf2go counter counter.c
  • Şimdi, bu Go kodunu çalıştırarak C kodumuzu derleyelim.
go mod init ebpf-test && go mod tidy

go generate
  • Derleme işlemi başarılı bir şekilde tamamlandıysa birkaç tane dosya oluşmuş olmalı.
    • counter_bpfeb.go
    • counter_bpfel.go
    • counter_bpfeb.o
    • counter_bpfel.o
  • Not: Go kodlarını incelediğimizde kernel’a yükleyeceğimiz programdan okuma işlemleri yapmak için gerekli yapıları oluşturduğunu görebiliriz.

  • Son olarak, bu programı kernele yükleyecek ve kontrol edecek Go kodunu yazalım ve çalıştıralım.
package main

import (
    "log"
    "net"
    "os"
    "os/signal"
    "time"

    "github.com/cilium/ebpf/link"
    "github.com/cilium/ebpf/rlimit"
)

func main() {
    // Remove resource limits for kernels <5.11.
    if err := rlimit.RemoveMemlock(); err != nil { 
        log.Fatal("Removing memlock:", err)
    }

    // Load the compiled eBPF ELF and load it into the kernel.
    var objs counterObjects 
    if err := loadCounterObjects(&objs, nil); err != nil {
        log.Fatal("Loading eBPF objects:", err)
    }
    defer objs.Close() 

    ifname := "eth0" // Change this to an interface on your machine.
    iface, err := net.InterfaceByName(ifname)
    if err != nil {
        log.Fatalf("Getting interface %s: %s", ifname, err)
    }

    // Attach count_packets to the network interface.
    link, err := link.AttachXDP(link.XDPOptions{ 
        Program:   objs.CountPackets,
        Interface: iface.Index,
    })
    if err != nil {
        log.Fatal("Attaching XDP:", err)
    }
    defer link.Close() 

    log.Printf("Counting incoming packets on %s..", ifname)

    // Periodically fetch the packet counter from PktCount,
    // exit the program when interrupted.
    tick := time.Tick(time.Second)
    stop := make(chan os.Signal, 5)
    signal.Notify(stop, os.Interrupt)
    for {
        select {
        case <-tick:
            var count uint64
            err := objs.PktCount.Lookup(uint32(0), &count) 
            if err != nil {
                log.Fatal("Map lookup:", err)
            }
            log.Printf("Received %d packets", count)
        case <-stop:
            log.Print("Received signal, exiting..")
            return
        }
    }
}
go run main.go
  • Terminalde şunu gördüyseniz ilk eBPF uygulamanızı başarıyla geliştirdiniz demektir.
msrexe@ubuntu:~/ebpf-test$ sudo go run .
2024/05/29 23:41:44 Counting incoming packets on eth0..
2024/05/29 23:41:45 Received 0 packets
2024/05/29 23:41:50 Received 0 packets
2024/05/29 23:41:51 Received 1 packets
2024/05/29 23:41:54 Received 3 packets
2024/05/29 23:41:55 Received 4 packets

Sonuç

Bu yazıda, eBPF yapısını kullanarak sıfırdan bir paket sayacı uygulaması geliştirdik. Bu uygulamada eğer linux/bpf.h sebebiyle hata aldıysanız ve kernel headerları ile çok uğraştıysanız bir sonraki yazıda anlatacağım portable bpf uygulamaları geliştirmek için kullanılan BPF CO-RE yapısını inceleyebilirsiniz.

Kaynaklar