Network Tracing

Kprobe Temelleri ve TCP

Syscall’lara hook olmak için tracepoint’leri kullandık. Şimdi kprobe’ları öğrenelim, yani dahili kernel fonksiyonlarına bağlanmayı.

Kprobe ve tracepoint karşılaştırması

Tracepoint’ler belirli kernel event’lerinde (syscall entry/exit, scheduling vb.) bulunan stabil ve dokümante edilmiş hook’lardır. Yapılandırılmış context sağlarlar:

SEC("tracepoint/syscalls/sys_enter_connect")
int trace(struct trace_event_raw_sys_enter *ctx) {
    u32 fd = ctx->args[0];  // Predefined fields
}

Kprobe’lar ise herhangi bir kernel fonksiyonuna ismiyle bağlanır.

Bu mekanizma daha esnek olmakla birlikte, kprobe’lar kernel’ın stabil API’sinin bir parçası değildir, bu yüzden kernel versiyonları arasında değişebilirler. Üstelik tanımlı bir ‘context’ sağlamazlar, argument türlerini ve sıralamasını kernel kaynak kodlarından bulmamız gerekir.

Neden kprobe?

Tracepoint’ler belirli event’leri sunar. Kprobe’lar ise kernel’ın iç yapısına daha derinden hook olmamızı sağlar. Örneğin, connect() syscall’ı non-blocking socket’ler için hemen return eder, ancak tcp_finish_connect() TCP handshake gerçekten tamamlandığında çağrılır.

Fonksiyon argument’lerine erişim

Kprobe’lar struct pt_regs *ctx alır, yani ham CPU register’ları. Argument’leri çıkarmak için macro’ları kullanın:

PT_REGS_PARM1(ctx)  // First parameter
PT_REGS_PARM2(ctx)  // Second parameter
PT_REGS_PARM3(ctx)  // Third parameter
// up to 8

tcp_finish_connect’in imzasını net/ipv4/tcp_input.c dosyasında bulabiliriz:

void tcp_finish_connect(struct sock *sk, struct sk_buff *skb);

struct sock*’a erişmek için birinci argument’i cast etmemiz gerekir:

struct sock *sk = (struct sock *)PT_REGS_PARM1(ctx);

Görev

Bir program uzak bir sunucuya bağlanıyor. connect() syscall’ı -EINPROGRESS döndürüyor (non-blocking), ancak bağlantı başarıyla tamamlanıyor.

Socket özellikleri

Socket’in özelliklerini bpf_probe_read_kernel ile okuyabilirsiniz. İlgili field’lar şunlardır:

struct sock {
    struct sock_common __sk_common;  // Common socket info
    // ...
};

struct sock_common {
    u16 skc_dport;   // Destination port
    u32 skc_daddr;   // Destination IPv4 address
    // ...
};

Port’ların big endian formatında olduğunu unutmayın, bu yüzden dönüştürmek için bpf_ntohs kullanmanız gerekecek.

Yapmanız gereken

Hedef port’u gönderin.

Quick reference
Read bytes from kernel space into kernel buffer
Function bpf_probe_read_kernel Full documentation
Args:
void* dstkernel buffer to read into
u32 sizebytes to read
const void* srckernel space pointer
On success, returns 0
On error, returns negative error code
Read string from kernel space into kernel buffer
Function bpf_probe_read_kernel_str Full documentation
Args:
void* dstkernel buffer to read into
u32 sizemaximum bytes to read
const void* srckernel space pointer to string
On success, returns number of bytes read (including null terminator)
On error, returns negative error code
Run your code to see execution events here