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ı
Gerekli yetkiler
# 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
# 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ı
#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
| Sabit | Değer | Yakalanan trafik |
|---|---|---|
ETH_P_ALL | 0x0003 | Tüm Ethernet çerçeveleri (promiscuous gerektirir) |
ETH_P_IP | 0x0800 | Yalnızca IPv4 paketleri |
ETH_P_IPV6 | 0x86DD | Yalnızca IPv6 paketleri |
ETH_P_ARP | 0x0806 | Yalnızca ARP çerçeveleri |
ETH_P_8021Q | 0x8100 | VLAN etiketli çerçeveler |
ETH_P_LOOP | 0x0060 | Loopback çerçeveleri |
Arayüz bağlama (bind)
#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ı
| Özellik | SOCK_RAW | SOCK_DGRAM |
|---|---|---|
| Ethernet başlığı | Dahil — 14 bayt (dst+src MAC + ethertype) | Kernel soyutlar, yoktur |
| recvfrom() sonucu | Ham Ethernet çerçevesi | L3 yük (IP başlığından itibaren) |
| sendto() için | Ethernet başlığını uygulama yazar | Kernel başlık ekler |
| Kullanım | tcpdump, TPACKET, özel protokol | Basit 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
#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
#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
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üm | Kernel | Temel özellik | Kısıtlama |
|---|---|---|---|
| TPACKET_V1 | 2.4 | İlk mmap ring; gettimeofday() zaman damgası | 32-bit zaman damgası, her paket için ayrı başlık yönetimi |
| TPACKET_V2 | 2.6.27 | VLAN bilgisi, ns çözünürlüklü zaman damgası | Her paket hâlâ ayrı frame, blok döngüsü yok |
| TPACKET_V3 | 3.2 | Blok tabanlı ring; birden fazla paket tek blokta, esnek frame boyutu | TX 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ı
| Metrik | TPACKET_V2 | TPACKET_V3 |
|---|---|---|
| CPU kullanımı (1 Gbit/s) | %30–50 | %5–10 |
| Paket başına syscall | Her paket için poll() | Blok başına tek poll() |
| Frame boyutu | Sabit (tp_frame_size) | Esnek (blok içinde değişken) |
| Zaman damgası çözünürlüğü | Nanosaniye | Nanosaniye |
| TX ring desteği | Tam | Kısmi — RX için tercih edin |
Sürüm seçimi rehberi
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
TPACKET_V3 ring oluşturma
#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ı
# Ö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ı
/* 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ü
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);
}
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
| Seviye | Nerede alınır | Doğruluk | Gereksinim |
|---|---|---|---|
| Software (SW) | Kernel ağ yığınında, genellikle softirq | 1–100 µs | Her NIC, ekstra yapılandırma yok |
| Hardware (HW) | NIC PHY/MAC katmanında, RX/TX anında | 1–100 ns | HW timestamp destekli NIC + sürücü |
| Crossstamp | HW + SW eşleştirme (PTP_SYS_OFFSET) | Alt µs | Kernel 5.0+, destekli NIC |
NIC HW timestamp desteğini sorgulama
# 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
#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
#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);
}
}
}
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
# 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
#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
/* 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 */
};
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
#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
# 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)
/* 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
# 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ım | Komut / Ayar | Etki |
|---|---|---|
| 1. Kernel | CONFIG_PREEMPT_RT veya PREEMPT_FULL | Kernel preemption gecikmeyi azaltır |
| 2. isolcpus | isolcpus=2,3 nohz_full=2,3 | CPU'yu kernel zamanlayıcıdan izole eder |
| 3. IRQ affinity | /proc/irq/N/smp_affinity | NIC kesmesini izole CPU'ya yönlendir |
| 4. SCHED_FIFO | sched_setscheduler(SCHED_FIFO, 90) | Preemption olmayan RT çalışma |
| 5. mlockall | MCL_CURRENT | MCL_FUTURE | Page fault latency ortadan kalkar |
| 6. TPACKET_V3 | tp_retire_blk_tov=1 ms | Düşük timeout = düşük gecikme |
| 7. SO_BUSY_POLL | SO_BUSY_POLL=50 µs | Poll latency azalır, CPU artar |
| 8. BPF filtre | SO_ATTACH_FILTER | Gereksiz 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