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.
Read bytes from kernel space into kernel buffer
- Args:
void* dstkernel buffer to read intou32 sizebytes to readconst void* srckernel space pointer
Read string from kernel space into kernel buffer
- Args:
void* dstkernel buffer to read intou32 sizemaximum bytes to readconst void* srckernel space pointer to string