Tüm eğitimler
TEKNİK REHBER GÖMÜLÜ LİNUX AF_PACKET 2026

AF_PACKET & TPACKET
Ham Paket Yakalama

Linux raw socket ile sıfır kopyalı paket yakalama — TPACKET_V3 ring buffer, donanım zaman damgası ve RT ağ uygulamaları.

00 AF_PACKET nedir

AF_PACKET, Linux'ta Ethernet katmanından itibaren ham (raw) paket alıp göndermeye izin veren bir adres ailesidir. IP, TCP veya UDP başlıkları kernel tarafından işlenmez; uygulama tüm çerçeveyi kendisi yönetir.

Temel kavram

Normal bir TCP/UDP soketi, kernel ağ yığınını (IP defrag, TCP state machine, checksum) kullanır. AF_PACKET soketi ise NIC sürücüsünden hemen sonra devreye girer — Layer 2 Ethernet çerçevesi uygulamaya olduğu gibi teslim edilir.

NIC donanımı → sürücü RX kuyruğu → AF_PACKET ring buffer → uygulama belleği
                                 ↕
                    (normal yol) → IP/TCP yığını → socket recv buffer

Kullanım alanları

tcpdump / Wiresharklibpcap, AF_PACKET + BPF filtresi üzerine inşa edilmiştir; her paket yakalamak için ham sokete ihtiyaç duyar
IDS / IPSSnort, Suricata gibi araçlar yüksek hızlı trafik analizini AF_PACKET TPACKET_V3 ile gerçekleştirir
Endüstriyel RT ağPROFINET, EtherCAT gibi protokoller standart TCP/IP yığınını devre dışı bırakarak ham Ethernet kullanır
PTP / IEEE 1588Hassas zaman senkronizasyonu için donanım zaman damgası (HW timestamping) ile ham paket gereklidir
Fuzzing & testAğ protokol testlerinde geçersiz veya özel paketler göndermek için raw socket kullanılır

Gerekli yetkiler

bash
# AF_PACKET soketi açmak için CAP_NET_RAW yetkisi gerekir
# Root olarak çalıştır VEYA binary'e capability ver:
sudo setcap cap_net_raw+ep ./my_app

# Mevcut capability'leri kontrol et
getcap ./my_app
# ./my_app cap_net_raw=ep

# systemd servis olarak çalışıyorsa unit'e ekle:
# AmbientCapabilities=CAP_NET_RAW
# CapabilityBoundingSet=CAP_NET_RAW

Kernel konfigürasyonu

bash
# AF_PACKET her modern Linux kernel'de varsayılan açıktır
# Gerekli seçenekler (zaten =y olmalı):
# CONFIG_PACKET=y           — AF_PACKET soket desteği
# CONFIG_PACKET_DIAG=y      — ss/netstat teşhis arayüzü
# CONFIG_BPF=y              — klasik BPF (cBPF) filtre motoru

grep CONFIG_PACKET /boot/config-$(uname -r)

Bu bölümde

  • AF_PACKET = kernel ağ yığınını atlayan Layer 2 ham soket
  • tcpdump, Snort, PROFINET, PTP uygulamalarının temel primitifi
  • CAP_NET_RAW yetkisi gerektirir — root veya setcap ile verilebilir
  • CONFIG_PACKET=y ile kernel desteği standart olarak açık gelir

01 SOCK_RAW vs SOCK_DGRAM

AF_PACKET soketi iki tipte açılabilir: SOCK_RAW tam Ethernet çerçevesini (başlık dahil) sunarken, SOCK_DGRAM yalnızca yük (payload) kısmını döndürür ve Ethernet başlığını kernel soyutlamasına bırakır.

socket() çağrısı

c
#include <sys/socket.h>
#include <linux/if_packet.h>
#include <net/ethernet.h>   /* ETH_P_ALL, ETH_P_IP ... */
#include <arpa/inet.h>      /* htons() */

/* SOCK_RAW: Ethernet başlığı dahil tüm çerçeve */
int fd_raw = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));

/* SOCK_DGRAM: kernel Ethernet başlığını soyar, sadece L3+ gelir */
int fd_dgram = socket(AF_PACKET, SOCK_DGRAM, htons(ETH_P_IP));

if (fd_raw < 0) {
    perror("socket");   /* EPERM: CAP_NET_RAW eksik */
    return -1;
}

ETH_P_* protokol sabitleri

SabitDeğerYakalanan trafik
ETH_P_ALL0x0003Tüm Ethernet çerçeveleri (promiscuous gerektirir)
ETH_P_IP0x0800Yalnızca IPv4 paketleri
ETH_P_IPV60x86DDYalnızca IPv6 paketleri
ETH_P_ARP0x0806Yalnızca ARP çerçeveleri
ETH_P_8021Q0x8100VLAN etiketli çerçeveler
ETH_P_LOOP0x0060Loopback çerçeveleri

Arayüz bağlama (bind)

c
#include <net/if.h>
#include <string.h>

struct sockaddr_ll addr = {0};
addr.sll_family   = AF_PACKET;
addr.sll_protocol = htons(ETH_P_ALL);
addr.sll_ifindex  = if_nametoindex("eth0");  /* arayüz indeksi */

if (bind(fd_raw, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
    perror("bind");
    return -1;
}

/* Promiscuous mod: tüm MAC adreslerine gelen çerçeveleri al */
struct packet_mreq mreq = {0};
mreq.mr_ifindex = addr.sll_ifindex;
mreq.mr_type    = PACKET_MR_PROMISC;
setsockopt(fd_raw, SOL_PACKET, PACKET_ADD_MEMBERSHIP,
           &mreq, sizeof(mreq));

SOCK_RAW vs SOCK_DGRAM karşılaştırması

ÖzellikSOCK_RAWSOCK_DGRAM
Ethernet başlığıDahil — 14 bayt (dst+src MAC + ethertype)Kernel soyutlar, yoktur
recvfrom() sonucuHam Ethernet çerçevesiL3 yük (IP başlığından itibaren)
sendto() içinEthernet başlığını uygulama yazarKernel başlık ekler
Kullanımtcpdump, TPACKET, özel protokolBasit L3 sniffing

Bu bölümde

  • SOCK_RAW: tüm Ethernet çerçevesi — başlık yönetimi uygulamada
  • SOCK_DGRAM: kernel başlığı soyar, sadece yük gelir
  • ETH_P_ALL ile tüm trafik; spesifik protokol için ETH_P_IP vb.
  • bind() ile belirli arayüze bağla; PACKET_MR_PROMISC ile promiscuous mod aç

02 Paket gönderme

AF_PACKET SOCK_RAW ile ham Ethernet çerçevesi oluşturmak ve sendto() ile göndermek — başlık alanlarının elle doldurulması dahil eksiksiz bir örnek.

Ethernet çerçevesi yapısı

[ 6 B dst MAC ][ 6 B src MAC ][ 2 B EtherType ][ 0–1500 B payload ][ 4 B FCS (NIC ekler) ]

Çerçeve oluşturma ve gönderme

c
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <linux/if_packet.h>
#include <net/ethernet.h>
#include <net/if.h>
#include <arpa/inet.h>
#include <unistd.h>

#define IFACE  "eth0"
#define BUFLEN 1514   /* max Ethernet frame (eksik FCS) */

int main(void)
{
    /* 1) Ham soket aç */
    int fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
    if (fd < 0) { perror("socket"); return 1; }

    /* 2) Arayüz indeksi al */
    int ifindex = if_nametoindex(IFACE);

    /* 3) Hedef adresi doldur */
    struct sockaddr_ll dst = {0};
    dst.sll_family   = AF_PACKET;
    dst.sll_ifindex  = ifindex;
    dst.sll_halen    = ETH_ALEN;
    /* broadcast */
    memset(dst.sll_addr, 0xFF, ETH_ALEN);

    /* 4) Ethernet başlığı + yük oluştur */
    unsigned char frame[BUFLEN];
    struct ethhdr *eth = (struct ethhdr *)frame;

    /* Hedef MAC: broadcast */
    memset(eth->h_dest, 0xFF, ETH_ALEN);
    /* Kaynak MAC: gerçek uygulamada SIOCGIFHWADDR ile al */
    unsigned char src_mac[ETH_ALEN] = {0x02,0xAA,0xBB,0xCC,0xDD,0xEE};
    memcpy(eth->h_source, src_mac, ETH_ALEN);
    /* EtherType: 0x88B5 = IEEE 802 test protokolü */
    eth->h_proto = htons(0x88B5);

    /* 5) Yük yaz */
    const char *msg = "Merhaba, AF_PACKET!";
    size_t msg_len  = strlen(msg);
    memcpy(frame + sizeof(struct ethhdr), msg, msg_len);

    size_t frame_len = sizeof(struct ethhdr) + msg_len;
    /* Minimum Ethernet çerçevesi 60 bayt (FCS hariç) */
    if (frame_len < 60) {
        memset(frame + frame_len, 0, 60 - frame_len);
        frame_len = 60;
    }

    /* 6) Gönder */
    ssize_t sent = sendto(fd, frame, frame_len, 0,
                          (struct sockaddr *)&dst, sizeof(dst));
    if (sent < 0) { perror("sendto"); }
    else           { printf("Gonderildi: %zd bayt\n", sent); }

    close(fd);
    return 0;
}

Kaynak MAC adresini SIOCGIFHWADDR ile okuma

c
#include <sys/ioctl.h>
#include <net/if.h>

/* Arayüzün MAC adresini al */
int get_src_mac(int fd, const char *iface, unsigned char *mac_out)
{
    struct ifreq ifr;
    memset(&ifr, 0, sizeof(ifr));
    strncpy(ifr.ifr_name, iface, IFNAMSIZ - 1);

    if (ioctl(fd, SIOCGIFHWADDR, &ifr) < 0) {
        perror("ioctl SIOCGIFHWADDR");
        return -1;
    }
    memcpy(mac_out, ifr.ifr_hwaddr.sa_data, ETH_ALEN);
    return 0;
}

Gönderim hataları ve MTU

EMSGSIZEFrame boyutu arayüz MTU'sunu aşıyor — varsayılan 1500 B payload; jumbo frame için ethtool ile MTU artırılabilir
ENOBUFSKernel TX kuyruğu dolu — setsockopt SO_SNDBUF ile tampon büyütün veya gönderim hızını azaltın
ENETDOWNArayüz kapalı — ip link set eth0 up ile açın
ENXIOif_nametoindex() başarısız — arayüz adı yanlış

Bu bölümde

  • SOCK_RAW ile Ethernet başlığını (14 B) uygulamanın kendisi doldurur
  • Minimum çerçeve boyutu 60 B (FCS hariç) — kısa yükü padding ile doldurun
  • Kaynak MAC: SIOCGIFHWADDR ioctl ile arayüzden okunur
  • Jumbo frame için ip link set eth0 mtu 9000 + SO_SNDBUF büyütme

03 TPACKET_V1/V2/V3 ring buffer modları

TPACKET, AF_PACKET'in mmap tabanlı halka arabellek (ring buffer) uzantısıdır. Kernel ile kullanıcı alanı arasında zero-copy paket aktarımı sağlar ve üç nesil halinde gelişmiştir.

Sürüm geçmişi

SürümKernelTemel özellikKısıtlama
TPACKET_V12.4İlk mmap ring; gettimeofday() zaman damgası32-bit zaman damgası, her paket için ayrı başlık yönetimi
TPACKET_V22.6.27VLAN bilgisi, ns çözünürlüklü zaman damgasıHer paket hâlâ ayrı frame, blok döngüsü yok
TPACKET_V33.2Blok tabanlı ring; birden fazla paket tek blokta, esnek frame boyutuTX tarafı sınırlı (V2 önerilir TX için)

Neden TPACKET_V3?

V1 ve V2'de kernel her paket için bir ring frame kullanır ve poll() çağrısı tek paket geldikten sonra döner. V3'te kernel birden fazla paketi tek bloğa toplar (block coalescing) ve yalnızca blok dolduğunda veya timeout geçtiğinde bildirim gönderir — bu durum CPU kullanımını önemli ölçüde azaltır.

TPACKET_V2:  [pkt1][pkt2][pkt3][pkt4] ...  her paket ayrı poll() uyandırması

TPACKET_V3:  [BLOK0: pkt1,pkt2,pkt3]  [BLOK1: pkt4,pkt5,...] ...
             poll() sadece blok bitişinde uyandırılır (veya tp_retire_blk_tov timeout)

Performans karşılaştırması

MetrikTPACKET_V2TPACKET_V3
CPU kullanımı (1 Gbit/s)%30–50%5–10
Paket başına syscallHer paket için poll()Blok başına tek poll()
Frame boyutuSabit (tp_frame_size)Esnek (blok içinde değişken)
Zaman damgası çözünürlüğüNanosaniyeNanosaniye
TX ring desteğiTamKısmi — RX için tercih edin

Sürüm seçimi rehberi

V3 RXYüksek hızlı paket yakalama (100 Mbit/s ve üzeri) — IDS, libpcap modern modu, trafik analizi
V2 TXHam paket gönderimi için hâlâ V2 kullanın; V3 TX desteği sınırlıdır
V1Eski kernel uyumluluğu gerekiyorsa; yeni geliştirmelerde kullanmayın

Bu bölümde

  • TPACKET_V3: blok tabanlı ring, çoklu paket birleştirme, düşük CPU
  • V2: sabit frame, paket başına poll — orta hız veya TX için hâlâ geçerli
  • V1: 2.4 kernel mirası, yeni projede kullanmayın
  • Yüksek hız RX → V3; ham TX → V2

04 TPACKET_V3 kurulumu

TPACKET_V3 ring buffer'ı oluşturmak için tp_block_size, tp_block_nr, tp_frame_size parametrelerinin doğru ayarlanması ve mmap() ile belleğe eşlenmesi gerekir.

Ring boyutu parametreleri

tp_block_sizeHer bloğun bayt cinsinden boyutu — sayfa boyutunun katı olmalı (ör. 4096 * 64 = 262144). Büyük blok = daha az poll() uyandırması.
tp_block_nrRing'deki toplam blok sayısı. Toplam ring boyutu = tp_block_size * tp_block_nr.
tp_frame_sizeV3'te minimum frame boyutu (TPACKET_ALIGNMENT katı). V3'te çerçeveler esnek olduğundan bu daha az kritiktir.
tp_retire_blk_tovBlok zaman aşımı (ms). Bu süre dolduğunda blok dolmamış olsa bile kullanıcıya teslim edilir. Düşük gecikme için küçük tutun.
tp_sizeof_privBlok başlığındaki özel alan boyutu — genellikle 0.
tp_feature_req_wordİsteğe bağlı özellikler — TP_FT_REQ_FILL_RXHASH gibi.

TPACKET_V3 ring oluşturma

c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/mman.h>
#include <linux/if_packet.h>
#include <net/ethernet.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <poll.h>

#define IFACE           "eth0"
#define BLOCK_SIZE      (1 << 22)   /* 4 MiB blok */
#define BLOCK_NR        64           /* 64 blok = 256 MiB ring */
#define FRAME_SIZE      2048         /* her paket için minimum alan */
#define BLOCK_TIMEOUT   10           /* ms — düşük gecikme için */

struct ring {
    void        *map;       /* mmap başlangıç adresi */
    size_t       map_size;  /* toplam mmap boyutu */
    unsigned int block_nr;
    unsigned int block_size;
};

int setup_tpacket_v3(const char *iface, struct ring *r)
{
    /* 1) Ham soket aç */
    int fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
    if (fd < 0) { perror("socket"); return -1; }

    /* 2) TPACKET_V3 sürümünü seç */
    int version = TPACKET_V3;
    if (setsockopt(fd, SOL_PACKET, PACKET_VERSION,
                   &version, sizeof(version)) < 0) {
        perror("setsockopt PACKET_VERSION");
        return -1;
    }

    /* 3) Ring parametrelerini ayarla */
    struct tpacket_req3 req = {0};
    req.tp_block_size      = BLOCK_SIZE;
    req.tp_block_nr        = BLOCK_NR;
    req.tp_frame_size      = FRAME_SIZE;
    req.tp_frame_nr        = (BLOCK_SIZE / FRAME_SIZE) * BLOCK_NR;
    req.tp_retire_blk_tov  = BLOCK_TIMEOUT;
    req.tp_sizeof_priv     = 0;
    req.tp_feature_req_word = TP_FT_REQ_FILL_RXHASH;

    if (setsockopt(fd, SOL_PACKET, PACKET_RX_RING,
                   &req, sizeof(req)) < 0) {
        perror("setsockopt PACKET_RX_RING");
        return -1;
    }

    /* 4) mmap: tüm ring'i kullanıcı belleğine eşle */
    size_t map_size = (size_t)BLOCK_SIZE * BLOCK_NR;
    void *map = mmap(NULL, map_size,
                     PROT_READ | PROT_WRITE,
                     MAP_SHARED | MAP_LOCKED,
                     fd, 0);
    if (map == MAP_FAILED) { perror("mmap"); return -1; }

    /* 5) Arayüze bağla */
    struct sockaddr_ll addr = {0};
    addr.sll_family   = AF_PACKET;
    addr.sll_protocol = htons(ETH_P_ALL);
    addr.sll_ifindex  = if_nametoindex(iface);
    if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
        perror("bind"); return -1;
    }

    r->map        = map;
    r->map_size   = map_size;
    r->block_nr   = BLOCK_NR;
    r->block_size = BLOCK_SIZE;
    return fd;
}

Ring boyutlandırma hesabı

bash
# Örnek: 1 Gbit/s hat, paket başı ortalama 512 B, 100 ms tampon süresi
# Hat kapasitesi: 1e9 / 8 = 125 MB/s
# 100 ms tampon: 125 MB/s * 0.1 = 12.5 MB minimum
# Güvenlik katsayısı x4 = 50 MB → 64 blok x 1 MiB = 64 MiB seç

# Gerçek ring kullanımını izle:
cat /proc/net/packet
# refcnt sk  type proto  iface   R  rmem   wmem   user   inode

Bu bölümde

  • PACKET_VERSION = TPACKET_V3 → PACKET_RX_RING → mmap() → bind() sırası
  • tp_block_size: sayfa boyutu katı; tp_retire_blk_tov: düşük gecikme için küçük tutun
  • Toplam ring = tp_block_size * tp_block_nr; hat hızına göre hesaplayın
  • MAP_LOCKED: belleği swap'tan korur — RT uygulamalarda zorunlu

05 Zero-copy yakalama döngüsü

TPACKET_V3 ring buffer kurulduktan sonra poll() ile blok bildirimlerini beklemek ve blok başlığını (tpacket_block_desc) tarayarak her paketi işlemek gerekir.

Blok ve paket başlık yapıları

c
/* Blok başlığı — her bloğun başında yer alır */
struct tpacket_block_desc {
    uint32_t version;
    uint32_t offset_to_priv;
    union tpacket_bd_header_u {
        struct tpacket_hdr_v1 {
            uint32_t block_status;  /* TP_STATUS_* bayrakları */
            uint32_t num_pkts;      /* bu bloktaki paket sayısı */
            uint32_t offset_to_first_pkt; /* ilk paket başlığına ofset */
            uint32_t blk_len;
            uint64_t seq_num;
            struct tpacket_bd_ts ts_first_pkt;  /* ilk paketin zaman damgası */
            struct tpacket_bd_ts ts_last_pkt;   /* son paketin zaman damgası */
        } v1;
    } hdr;
};

/* V3 paket başlığı */
struct tpacket3_hdr {
    uint32_t tp_next_offset;  /* sonraki pakete ofset (bayt) */
    uint32_t tp_sec;          /* saniye (zaman damgası) */
    uint32_t tp_nsec;         /* nanosaniye (zaman damgası) */
    uint32_t tp_snaplen;      /* yakalanan uzunluk */
    uint32_t tp_len;          /* gerçek paket uzunluğu */
    uint32_t tp_status;       /* TP_STATUS_* */
    uint16_t tp_mac;          /* Ethernet başlığına ofset */
    uint16_t tp_net;          /* ağ katmanına ofset */
    /* ... VLAN, hash alanları */
};

Ana yakalama döngüsü

c
typedef void (*pkt_handler_t)(const uint8_t *data, uint32_t len,
                               uint32_t sec, uint32_t nsec);

void capture_loop(int fd, struct ring *r, pkt_handler_t handler)
{
    struct pollfd pfd = { .fd = fd, .events = POLLIN | POLLERR };
    unsigned int cur_block = 0;

    while (1) {
        /* Mevcut bloğun durumunu kontrol et */
        struct tpacket_block_desc *pbd =
            (struct tpacket_block_desc *)
            ((uint8_t *)r->map + (size_t)cur_block * r->block_size);

        /* Blok hazır değilse poll() ile bekle */
        while (!(pbd->hdr.v1.block_status & TP_STATUS_USER)) {
            int ret = poll(&pfd, 1, -1);
            if (ret < 0 && errno != EINTR) { perror("poll"); return; }
        }

        /* Bloktaki paketleri işle */
        uint32_t num_pkts = pbd->hdr.v1.num_pkts;
        uint8_t  *pkt_ptr = (uint8_t *)pbd
                          + pbd->hdr.v1.offset_to_first_pkt;

        for (uint32_t i = 0; i < num_pkts; i++) {
            struct tpacket3_hdr *hdr = (struct tpacket3_hdr *)pkt_ptr;

            /* Ethernet çerçevesi pointer'ı */
            const uint8_t *eth = pkt_ptr + hdr->tp_mac;

            /* İşleyiciye gönder */
            handler(eth, hdr->tp_snaplen,
                    hdr->tp_sec, hdr->tp_nsec);

            /* Sonraki pakete geç */
            if (hdr->tp_next_offset == 0) break;
            pkt_ptr += hdr->tp_next_offset;
        }

        /* Bloğu kernel'e iade et */
        pbd->hdr.v1.block_status = TP_STATUS_KERNEL;
        /* Bellek bariyeri: compiler/CPU yeniden sıralama önlemi */
        __sync_synchronize();

        cur_block = (cur_block + 1) % r->block_nr;
    }
}

/* Örnek işleyici */
void print_pkt(const uint8_t *data, uint32_t len,
               uint32_t sec, uint32_t nsec)
{
    const struct ethhdr *eth = (const struct ethhdr *)data;
    printf("[%u.%09u] %02x:%02x:%02x:%02x:%02x:%02x -> "
           "%02x:%02x:%02x:%02x:%02x:%02x len=%u\n",
           sec, nsec,
           eth->h_source[0], eth->h_source[1], eth->h_source[2],
           eth->h_source[3], eth->h_source[4], eth->h_source[5],
           eth->h_dest[0],   eth->h_dest[1],   eth->h_dest[2],
           eth->h_dest[3],   eth->h_dest[4],   eth->h_dest[5],
           len);
}
ÖNEMLİ

Blok işlendikten sonra block_status = TP_STATUS_KERNEL ile kernel'e iade etmek zorunludur. İade edilmeyen bloklar ring'i doldurur ve kernel paket düşürmeye (drop) başlar. __sync_synchronize() veya __atomic_store_n() ile bellek bariyeri koyun; aksi takdirde CPU durum bayrağını yeniden sıralayabilir.

Bu bölümde

  • tpacket_block_desc.block_status == TP_STATUS_USER: blok hazır
  • offset_to_first_pkt + tp_next_offset ile paketleri zincir gibi gezin
  • tp_mac → Ethernet başlığı; tp_net → IP başlığı offset'i
  • İşlem sonrası block_status = TP_STATUS_KERNEL + bellek bariyeri zorunlu

06 Hardware timestamping

Yazılım zaman damgası (software timestamp) mikrosaniye düzeyinde hata içerir. Donanım zaman damgası (hardware timestamp) NIC'in kendisinde PHY katmanında alınır ve genellikle <100 ns doğruluk sağlar — PTP/IEEE 1588 senkronizasyonunun temelidir.

Timestamp seviyeleri

SeviyeNerede alınırDoğrulukGereksinim
Software (SW)Kernel ağ yığınında, genellikle softirq1–100 µsHer NIC, ekstra yapılandırma yok
Hardware (HW)NIC PHY/MAC katmanında, RX/TX anında1–100 nsHW timestamp destekli NIC + sürücü
CrossstampHW + SW eşleştirme (PTP_SYS_OFFSET)Alt µsKernel 5.0+, destekli NIC

NIC HW timestamp desteğini sorgulama

bash
# ethtool ile donanım timestamp yeteneklerini sorgula
ethtool -T eth0

# Örnek çıktı:
# Time stamping parameters for eth0:
# Capabilities:
#   hardware-transmit     (SOF_TIMESTAMPING_TX_HARDWARE)
#   software-transmit     (SOF_TIMESTAMPING_TX_SOFTWARE)
#   hardware-receive      (SOF_TIMESTAMPING_RX_HARDWARE)
#   software-receive      (SOF_TIMESTAMPING_RX_SOFTWARE)
#   hardware-raw-clock    (SOF_TIMESTAMPING_RAW_HARDWARE)
# PTP Hardware Clock: 0    ← /dev/ptp0 mevcut
# Hardware Transmit Timestamp Modes: off one-at-a-time
# Hardware Receive Filter Modes: none all

SIOCSHWTSTAMP ile HW timestamp etkinleştirme

c
#include <linux/net_tstamp.h>
#include <sys/ioctl.h>
#include <net/if.h>

int enable_hw_timestamp(int fd, const char *iface)
{
    struct ifreq ifr;
    struct hwtstamp_config cfg = {0};

    /* Tüm RX paketleri için HW timestamp iste */
    cfg.tx_type   = HWTSTAMP_TX_OFF;    /* TX timestamp istemiyoruz */
    cfg.rx_filter = HWTSTAMP_FILTER_ALL; /* tüm RX paketleri */

    memset(&ifr, 0, sizeof(ifr));
    strncpy(ifr.ifr_name, iface, IFNAMSIZ - 1);
    ifr.ifr_data = (void *)&cfg;

    if (ioctl(fd, SIOCSHWTSTAMP, &ifr) < 0) {
        perror("ioctl SIOCSHWTSTAMP");
        /* HW timestamp desteklenmiyor — SW'ye dön */
        return -1;
    }
    return 0;
}

/* SO_TIMESTAMPING soket seçeneği — tüm timestamp türlerini iste */
int req_timestamps(int fd)
{
    int flags = SOF_TIMESTAMPING_RX_HARDWARE  /* HW RX stamp */
              | SOF_TIMESTAMPING_RAW_HARDWARE /* ham HW saat */
              | SOF_TIMESTAMPING_RX_SOFTWARE  /* SW fallback */
              | SOF_TIMESTAMPING_SOFTWARE;    /* SW stamp aktif */

    return setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING,
                      &flags, sizeof(flags));
}

Timestamp'i recvmsg() ile okuma

c
#include <sys/socket.h>
#include <time.h>

void recv_with_timestamp(int fd)
{
    char buf[2048];
    char ctrl[512];
    struct iovec iov  = { buf, sizeof(buf) };
    struct msghdr msg = {0};
    msg.msg_iov        = &iov;
    msg.msg_iovlen     = 1;
    msg.msg_control    = ctrl;
    msg.msg_controllen = sizeof(ctrl);

    ssize_t n = recvmsg(fd, &msg, 0);
    if (n < 0) { perror("recvmsg"); return; }

    /* Kontrol mesajlarını tara */
    struct cmsghdr *cmsg;
    for (cmsg = CMSG_FIRSTHDR(&msg); cmsg;
         cmsg = CMSG_NXTHDR(&msg, cmsg)) {

        if (cmsg->cmsg_level == SOL_SOCKET &&
            cmsg->cmsg_type  == SCM_TIMESTAMPING) {

            /* 3 adet timespec: SW, SW deprecated, HW */
            struct timespec *ts = (struct timespec *)CMSG_DATA(cmsg);
            /* ts[0] = software timestamp */
            /* ts[2] = hardware timestamp (PHC saat) */
            printf("HW stamp: %ld.%09ld\n",
                   ts[2].tv_sec, ts[2].tv_nsec);
        }
    }
}
NOT

TPACKET_V3 ile kullanıldığında donanım zaman damgası doğrudan tpacket3_hdr.tp_sec / tp_nsec alanlarına yazılır — ayrı recvmsg() gerektirmez. ethtool -T eth0 çıktısında SOF_TIMESTAMPING_RX_HARDWARE yoksa NIC sürücüsü HW stamp desteklemiyor demektir; SW stamp ile devam edin.

Bu bölümde

  • HW timestamp: NIC PHY katmanında, <100 ns doğruluk — PTP için zorunlu
  • SIOCSHWTSTAMP ioctl ile HWTSTAMP_FILTER_ALL → tüm RX paketleri etiketlenir
  • SO_TIMESTAMPING soket seçeneği; recvmsg() SCM_TIMESTAMPING ile üç timespec gelir
  • TPACKET_V3: tp_sec/tp_nsec alanları doğrudan HW stamp taşır

07 BPF filtre ekleme

AF_PACKET soketine klasik BPF (cBPF) filtresi ekleyerek kernel'de erken filtreleme yapılır — kullanıcı alanına yalnızca ilgili paketler çıkar, gereksiz kopyalama önlenir.

Klasik BPF (cBPF) mimarisi

cBPF, sanal bir yığıt makinesidir. Her talimat bir opcode + jt (true atla) + jf (false atla) + k (sabit) içerir. Filtre, paketi kabul etmek için sıfır olmayan değer, reddetmek için 0 döner.

paket geldi → kernel BPF sanal makinesi kodu çalıştırır → 0: düşür | >0: kullanıcıya ilet

tcpdump ile BPF kodu üretme

bash
# tcpdump'ın BPF komutlarını yazdırmasını sağla (-d: decode)
tcpdump -d 'tcp port 80'
# (000) ldh  [12]        — EtherType yükle
# (001) jeq  #0x86dd     — IPv6 mı?
# (002) ldb  [20]        — protocol byte
# ... (devam eder)

# -dd: C dizisi formatında yaz
tcpdump -dd 'udp port 53'
# { 0x28, 0, 0, 0x0000000c },
# { 0x15, 0, 8, 0x00000800 },
# ...

SO_ATTACH_FILTER ile filtre uygulama

c
#include <linux/filter.h>
#include <sys/socket.h>

/*
 * Örnek: yalnızca ARP paketlerini geçiren filtre
 * tcpdump -dd 'arp' çıktısından alındı
 * EtherType 0x0806 kontrolü
 */
static struct sock_filter arp_filter[] = {
    /* ldh  [12]: EtherType alanını yükle */
    { 0x28, 0, 0, 0x0000000c },
    /* jeq  #0x806: ARP ise kabul (ret 0xffff), değilse düşür (ret 0) */
    { 0x15, 0, 1, 0x00000806 },
    /* ret  #0xffff: paketi kabul et (65535 bayt) */
    { 0x06, 0, 0, 0x0000ffff },
    /* ret  #0: paketi düşür */
    { 0x06, 0, 0, 0x00000000 },
};

int attach_bpf_filter(int fd)
{
    struct sock_fprog prog = {
        .len    = sizeof(arp_filter) / sizeof(arp_filter[0]),
        .filter = arp_filter,
    };

    if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER,
                   &prog, sizeof(prog)) < 0) {
        perror("setsockopt SO_ATTACH_FILTER");
        return -1;
    }
    return 0;
}

/* Filtreyi kaldır */
int detach_bpf_filter(int fd)
{
    int dummy = 0;
    return setsockopt(fd, SOL_SOCKET, SO_DETACH_FILTER,
                      &dummy, sizeof(dummy));
}

TCP port 80 filtresi — tam örnek

c
/* tcpdump -dd 'tcp and port 80' (IPv4 only) */
static struct sock_filter tcp80_filter[] = {
    { 0x28, 0, 0, 0x0000000c }, /* ldh [12]  EtherType    */
    { 0x15, 0, 6, 0x00000800 }, /* jeq #0x800 IPv4?       */
    { 0x30, 0, 0, 0x00000017 }, /* ldb [23]  proto        */
    { 0x15, 0, 4, 0x00000006 }, /* jeq #6    TCP?         */
    { 0x28, 0, 0, 0x00000014 }, /* ldh [20]  frag offset  */
    { 0x45, 2, 0, 0x00001fff }, /* jset      fragment?    */
    { 0xb1, 0, 0, 0x0000000e }, /* ldxb 4*([14]&0xf)  IHL */
    { 0x48, 0, 0, 0x0000000e }, /* ldh [x+14] src port    */
    { 0x15, 2, 0, 0x00000050 }, /* jeq #80                */
    { 0x48, 0, 0, 0x00000010 }, /* ldh [x+16] dst port    */
    { 0x15, 0, 1, 0x00000050 }, /* jeq #80                */
    { 0x06, 0, 0, 0x00040000 }, /* ret #262144 KABUL      */
    { 0x06, 0, 0, 0x00000000 }, /* ret #0      DÜŞÜR      */
};
DİKKAT

cBPF filtreleri TPACKET ile birlikte kullanılabilir ancak ring buffer oluşturulmadan önce uygulanmalıdır. Ring kurulduktan sonra eklenen filtreler ring'e giren paketleri etkilemez; sıra önemlidir: socket() → SO_ATTACH_FILTER → PACKET_VERSION → PACKET_RX_RING → mmap() → bind().

Bu bölümde

  • cBPF: kernel içinde çalışan sanal makine — kullanıcı alanına kopyalamadan filtreler
  • tcpdump -dd ile herhangi bir BPF ifadesi için C dizisi üretilebilir
  • SO_ATTACH_FILTER ile soket'e filtre bağla; SO_DETACH_FILTER ile kaldır
  • TPACKET kullanımında filtre ring buffer'dan önce eklenmelidir

08 Performans ve RT kullanım

Yüksek hızlı paket yakalama veya gerçek zamanlı (RT) ağ uygulamaları için CPU affinite ayarı, SCHED_FIFO zamanlayıcı politikası ve busy-polling teknikleri kritik öneme sahiptir.

CPU affinite

c
#define _GNU_SOURCE
#include <sched.h>
#include <pthread.h>

/* Mevcut thread'i belirli CPU'ya sabitle */
int pin_to_cpu(int cpu)
{
    cpu_set_t cpuset;
    CPU_ZERO(&cpuset);
    CPU_SET(cpu, &cpuset);
    return sched_setaffinity(0, sizeof(cpuset), &cpuset);
}

/* SCHED_FIFO: preemption olmayan RT zamanlayıcı */
int set_rt_priority(int prio)  /* prio: 1–99 */
{
    struct sched_param sp = { .sched_priority = prio };
    return sched_setscheduler(0, SCHED_FIFO, &sp);
}

/* mlockall: tüm belleği RAM'de kilitle, page fault önle */
#include <sys/mman.h>
void lock_memory(void)
{
    mlockall(MCL_CURRENT | MCL_FUTURE);
}

IRQ affinite — NIC kesmesini izole etme

bash
# NIC IRQ numaralarını bul
grep eth0 /proc/interrupts
# 42:  0  0  0  1234  PCI-MSI eth0-TxRx-0
# 43:  0  0  0   567  PCI-MSI eth0-TxRx-1

# IRQ 42'yi CPU 2'ye sabitle
echo 4 > /proc/irq/42/smp_affinity   # bitmask: CPU 2 = bit 2 = 4

# irqbalance'ı devre dışı bırak (aksi takdirde değişikliği geri alır)
systemctl stop irqbalance
systemctl disable irqbalance

# isolcpus ile CPU'yu kernel zamanlayıcıdan çıkar (kernel cmdline)
# /etc/default/grub: GRUB_CMDLINE_LINUX="isolcpus=2,3 nohz_full=2,3 rcu_nocbs=2,3"

Busy-polling (SO_BUSY_POLL)

c
/* Busy-poll süresi: poll() timeout yerine aktif döngü (µs) */
int busy_poll_us = 50;  /* 50 µs busy-poll, sonra sleep */
setsockopt(fd, SOL_SOCKET, SO_BUSY_POLL,
           &busy_poll_us, sizeof(busy_poll_us));

/* Sistem geneli ayar (root gerekir) */
/* sysctl net.core.busy_poll=50    */
/* sysctl net.core.busy_read=50    */

Paket düşme (drop) izleme

bash
# AF_PACKET istatistikleri (düşen paketler dahil)
ss -p --packet
# veya getsockopt ile programatik:

# NIC RX düşme istatistikleri
ethtool -S eth0 | grep -i drop
ethtool -S eth0 | grep -i miss

# Kernel ağ istatistikleri
cat /proc/net/packet  # drops sütunu
netstat -s | grep -i overflow

RT uygulama için tam kurulum özeti

AdımKomut / AyarEtki
1. KernelCONFIG_PREEMPT_RT veya PREEMPT_FULLKernel preemption gecikmeyi azaltır
2. isolcpusisolcpus=2,3 nohz_full=2,3CPU'yu kernel zamanlayıcıdan izole eder
3. IRQ affinity/proc/irq/N/smp_affinityNIC kesmesini izole CPU'ya yönlendir
4. SCHED_FIFOsched_setscheduler(SCHED_FIFO, 90)Preemption olmayan RT çalışma
5. mlockallMCL_CURRENT | MCL_FUTUREPage fault latency ortadan kalkar
6. TPACKET_V3tp_retire_blk_tov=1 msDüşük timeout = düşük gecikme
7. SO_BUSY_POLLSO_BUSY_POLL=50 µsPoll latency azalır, CPU artar
8. BPF filtreSO_ATTACH_FILTERGereksiz paket kopyası önlenir

Bu bölümde

  • sched_setaffinity + isolcpus: yakalama thread'ini izole CPU'da tut
  • SCHED_FIFO prio 90 + mlockall: RT gecikmeyi minimize eder
  • IRQ affinity: NIC kesmesini yakalama CPU'suyla hizala
  • SO_BUSY_POLL: sub-millisecond gecikme için aktif polling — güç tüketimi artar