awk ile Ağ Log Verilerinden IP Adresi Bazlı Trafik Analizi ve Raporlama
Ağ loglarına bakan her sistem yöneticisinin bildiği o his: binlerce satır, ham veri, anlamsız görünen bir sel. Ama bu verinin içinde çok değerli bilgiler var. Kim ne zaman bağlandı, hangi IP ne kadar trafik üretiyor, anomali var mı, DDoS saldırısı başlıyor mu? İşte tam bu noktada awk devreye giriyor. Yıllardır bu işi yapıyorum ve awk olmadan ağ log analizini düşünemiyorum bile.
Bu yazıda gerçek dünya senaryoları üzerinden, nginx/apache access log ve iptables log verilerinden IP bazlı trafik analizi ve raporlama yapacağız. Scriptleri kopyalayıp çalıştırabilirsiniz, ama her satırın ne yaptığını anlamanızı da istiyorum.
awk’ı Neden Tercih Ediyoruz
grep bulur, sed değiştirir, awk ise analiz eder. Bu üçlünün içinde en güçlü olanı kesinlikle awk. Neden mi? Çünkü awk bir metin işleme aracı değil, aslında küçük bir programlama dili. Değişken tanımlayabilirsiniz, döngü kurabilirsiniz, koşullu ifade yazabilirsiniz ve en önemlisi, çıktınızı tam istediğiniz formatta alabilirsiniz.
Ağ log analizinde bizi ilgilendiren şeyler genellikle şunlar:
- Belirli bir IP’nin toplam istek sayısı
- Kaynak IP başına transfer edilen veri miktarı
- Zaman aralığına göre trafik yoğunluğu
- Hata kodu dağılımı (4xx, 5xx)
- En çok bağlantı kuran IP’lerin listesi
Tüm bunları awk ile halledebiliyoruz.
Log Formatını Anlamak
Çalışmadan önce hangi log formatıyla karşı karşıya olduğunuzu bilmeniz gerekiyor. Nginx’in varsayılan combined log formatı şöyle görünür:
192.168.1.45 - admin [10/Dec/2024:14:32:18 +0300] "GET /api/users HTTP/1.1" 200 4523 "-" "Mozilla/5.0"
Bu satırda alanlar şu şekilde:
- $1: IP adresi (192.168.1.45)
- $2: Ident (genellikle -)
- $3: Kullanıcı adı
- $4: Tarih
- $5: Saat dilimi
- $6: HTTP metodu ve path
- $9: HTTP durum kodu
- $10: Transfer edilen byte miktarı
awk varsayılan olarak boşluğu alan ayracı olarak kullanır, bu da combined log formatıyla mükemmel uyum sağlar.
Temel IP Sayım Analizi
En basit ama en sık kullandığım sorgu: hangi IP kaç istek atmış?
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20
Bu pipeline’ı adım adım açıklayayım: awk '{print $1}' sadece ilk alanı, yani IP adresini alır. sort alfabetik sıralar, uniq -c her benzersiz değerin önüne kaç kez geçtiğini yazar, sort -rn sayısal olarak büyükten küçüğe sıralar ve head -20 ilk 20’yi gösterir.
Ama bunu tamamen awk içinde de halledebiliriz ve bu çok daha esnek:
awk '{ip_count[$1]++} END {for (ip in ip_count) print ip_count[ip], ip}'
/var/log/nginx/access.log | sort -rn | head -20
Burada bir ilişkili dizi (associative array) kullanıyoruz. ip_count[$1]++ her satırda o IP için sayacı bir artırıyor. END bloğu tüm dosya işlendikten sonra çalışıyor ve birikmiş veriyi döküyor. Bu yaklaşımın avantajı: tek awk çağrısıyla her şeyi hallediyoruz.
Byte Bazlı Trafik Analizi
İstek sayısı bazen yanıltıcı olabilir. Küçük API çağrıları yapan bir client, büyük dosya indiren birine göre çok daha fazla istek atabilir ama gerçekte daha az trafik üretiyordur. Byte bazlı analiz bu yüzden kritik:
awk '{bytes[$1] += $10} END {
for (ip in bytes) {
mb = bytes[ip] / 1048576
printf "%.2f MBt%sn", mb, ip
}
}' /var/log/nginx/access.log | sort -rn | head -20
Burada $10 alanı byte miktarını tutuyor. Her IP için bu değerleri topluyoruz ve sonunda megabayta çevirip yazdırıyoruz. printf kullanmak çıktıyı formatlı hale getiriyor.
Bir sorunla karşılaşabilirsiniz: bazı log satırlarında $10 alanı - olabilir (transfer olmadığında). Bu durumu handle etmek için:
awk '$10 ~ /^[0-9]+$/ {bytes[$1] += $10} END {
for (ip in bytes) {
mb = bytes[ip] / 1048576
printf "%.2f MBt%sn", mb, ip
}
}' /var/log/nginx/access.log | sort -rn | head -20
$10 ~ /^[0-9]+$/ sadece rakamlardan oluşan alanları işleme alır, - gibi değerleri atlar.
HTTP Durum Kodu Filtresi ile Analiz
Sadece hata veren istekleri üreten IP’leri bulmak, güvenlik ve debugging açısından çok değerli. Örneğin 404 üreten IP’ler ya dizin taraması yapıyordur ya da kötü konfigüre edilmiş bir client’tır:
awk '$9 == 404 {count[$1]++} END {
for (ip in count) {
if (count[ip] > 10)
printf "%dt%sn", count[ip], ip
}
}' /var/log/nginx/access.log | sort -rn
Bu script sadece 404 dönen istekleri sayıyor ve 10’dan fazla 404 üreten IP’leri listeliyor. 5xx hatalarını da ekleyelim:
awk '$9 >= 400 {
errors[$1][$9]++
total[$1]++
} END {
for (ip in total) {
printf "%s - Toplam Hata: %dn", ip, total[ip]
for (code in errors[ip])
printf " HTTP %s: %d kezn", code, errors[ip][code]
}
}' /var/log/nginx/access.log | sort -t: -k3 -rn
Bu örnekte çok boyutlu dizi kullandık. errors[$1][$9] hem IP’ye hem de durum koduna göre gruplama yapıyor.
Zaman Bazlı Trafik Analizi
Günün hangi saatinde trafik yoğunlaşıyor? Saatlik istek dağılımını çıkarmak için log formatındaki zaman damgasını parse etmemiz gerekiyor:
awk '{
# [10/Dec/2024:14:32:18 formatından saat bilgisini cek
split($4, datetime, ":")
hour = substr(datetime[2], 1, 2)
hourly[hour]++
} END {
for (h = 0; h < 24; h++) {
printf "%02d:00 - %d istekn", h, hourly[h]+0
}
}' /var/log/nginx/access.log
split($4, datetime, ":") dördüncü alanı : karakterine göre böler. datetime[2] bize saat bilgisini verir. hourly[h]+0 ifadesi, o saat için hiç kayıt yoksa 0 yazdırmasını sağlar.
Belirli bir IP’nin hangi saatlerde aktif olduğunu görmek için:
TARGET_IP="192.168.1.45"
awk -v target="$TARGET_IP" '$1 == target {
split($4, dt, ":")
hour = substr(dt[2], 1, 2)
activity[hour]++
} END {
print "IP:", target, "- Saatlik Aktivite:"
for (h = 0; h < 24; h++) {
if (activity[h] > 0)
printf " Saat %02d: %d istekn", h, activity[h]
}
}' /var/log/nginx/access.log
iptables Log Analizi
Nginx loglarının yanı sıra iptables logları da çok değerli bilgi içerir. Tipik bir iptables log satırı şöyle görünür:
Dec 10 14:32:18 server kernel: [1234567.890] DROPPED IN=eth0 OUT= MAC=... SRC=203.0.113.42 DST=10.0.0.1 LEN=40 TOS=0x00 PREC=0x00 TTL=45 ID=0 DF PROTO=TCP SPT=54321 DPT=22 WINDOW=1024 RES=0x00 SYN URGP=0
Bu formattan SRC (kaynak IP) ve hedef port bilgilerini çıkaralım:
awk '/DROPPED/ || /BLOCKED/ {
for (i=1; i<=NF; i++) {
if ($i ~ /^SRC=/) {
split($i, src, "=")
blocked[src[2]]++
}
if ($i ~ /^DPT=/) {
split($i, dpt, "=")
ports[dpt[2]]++
}
}
} END {
print "=== En Cok Engellenen IP Adresleri ==="
for (ip in blocked)
printf "%dt%sn", blocked[ip], ip
print "n=== En Cok Hedef Alinan Portlar ==="
for (port in ports)
printf "%dt%sn", ports[port], port
}' /var/log/kern.log | sort -rn | head -20
Bu script hem engellenen IP’leri hem de hedef alınan portları aynı anda analiz ediyor. Güvenlik ekibi için altın değerinde bir çıktı.
Kapsamlı Raporlama Scripti
Şimdiye kadar öğrendiklerimizi bir araya getirip gerçekten kullanılabilir bir raporlama scripti yazalım. Bu scripti cron’a alabilir ve her gün sabah e-posta ile gönderebilirsiniz:
#!/bin/bash
# ip_traffic_report.sh - Gunluk IP Trafik Raporu
LOG_FILE="/var/log/nginx/access.log"
REPORT_DATE=$(date +%Y-%m-%d)
OUTPUT_FILE="/tmp/traffic_report_${REPORT_DATE}.txt"
# Sadece bugune ait satirlari filtrele
TODAY=$(date +%d/%b/%Y)
awk -v today="$TODAY" -v report_date="$REPORT_DATE" '
$0 ~ today {
# Istek sayisi
req_count[$1]++
# Byte toplami (gecerli sayi ise)
if ($10 ~ /^[0-9]+$/)
bytes[$1] += $10
# HTTP durum kodlari
status[$1][$9]++
# 4xx ve 5xx hatalar
if ($9 >= 400)
errors[$1]++
# Toplam istekler
total_req++
total_bytes += ($10 ~ /^[0-9]+$/) ? $10 : 0
}
END {
printf "================================================n"
printf " GUNLUK TRAFIK RAPORU - %sn", report_date
printf "================================================nn"
printf "OZET:n"
printf " Toplam Istek: %dn", total_req
printf " Toplam Transfer: %.2f MBnn", total_bytes/1048576
printf "TOP 10 IP - ISTEK SAYISINA GORE:n"
printf "%-20s %-12s %-12s %-10sn", "IP Adresi", "Istek", "Transfer MB", "Hata"
printf "%-20s %-12s %-12s %-10sn", "----------", "-----", "-----------", "----"
# req_count arayi icin gecici dosya kullanamayiz,
# for dongusuyle yaziyoruz
n = 0
for (ip in req_count) {
output[n] = sprintf("%-20s %-12d %-12.2f %-10d",
ip, req_count[ip], bytes[ip]/1048576, errors[ip]+0)
count_arr[n] = req_count[ip]
n++
}
# Basit bubble sort (kucuk veri setleri icin yeterli)
for (i=0; i<n-1; i++) {
for (j=0; j<n-i-1; j++) {
if (count_arr[j] < count_arr[j+1]) {
tmp = count_arr[j]; count_arr[j] = count_arr[j+1]; count_arr[j+1] = tmp
tmp = output[j]; output[j] = output[j+1]; output[j+1] = tmp
}
}
}
for (i=0; i<n && i<10; i++)
print output[i]
# Yuksek hata oranlilari bul
printf "nUYARI - YUKSEK HATA ORANLI IP ADRESLERI (>50 hata):n"
for (ip in errors) {
if (errors[ip] > 50)
printf " %s: %d hata / %d istek (%%%.1f)n",
ip, errors[ip], req_count[ip],
(errors[ip]/req_count[ip])*100
}
printf "n================================================n"
}' "$LOG_FILE" > "$OUTPUT_FILE"
cat "$OUTPUT_FILE"
# Opsiyonel: mail ile gonder
# mail -s "Gunluk Trafik Raporu - $REPORT_DATE" [email protected] < "$OUTPUT_FILE"
Gerçek Dünya Senaryosu: DDoS Tespiti
Bir gün production sunucunuz yavaşladı. İlk yapmanız gereken şey hızlıca hangi IP’lerin anormal miktarda istek attığını görmek. Bu scripti her zaman elinizin altında bulundurun:
#!/bin/bash
# Son 1000 satiri analiz et, esik degeri asan IP'leri bul
THRESHOLD=${1:-100} # Varsayilan: 100 istek
echo "Son 1000 satir icinde $THRESHOLD'dan fazla istek atan IP'ler:"
echo "--------------------------------------------------------------"
tail -1000 /var/log/nginx/access.log |
awk -v threshold="$THRESHOLD" '{
count[$1]++
last_seen[$1] = $4
} END {
for (ip in count) {
if (count[ip] >= threshold) {
gsub(/[/, "", last_seen[ip])
printf "IP: %-18s | Istek: %-6d | Son Gorulme: %sn",
ip, count[ip], last_seen[ip]
}
}
}' | sort -t: -k3 -rn
echo ""
echo "Bu IP'leri gecici olarak engellemek icin:"
echo " iptables -I INPUT -s <IP> -j DROP"
Şüpheli IP’yi tespit ettikten sonra anında şu şekilde engelleyebilirsiniz:
# Tespit edilen IP'leri otomatik engelle (dikkatli kullanin!)
tail -1000 /var/log/nginx/access.log |
awk '{count[$1]++} END {for (ip in count) if (count[ip] > 200) print ip}' |
while read suspicious_ip; do
echo "Engelleniyor: $suspicious_ip"
iptables -I INPUT -s "$suspicious_ip" -j DROP
echo "$suspicious_ip $(date)" >> /var/log/blocked_ips.log
done
Bu komutu otomatize etmeden önce iki kez düşünün. Yanlış bir eşik değeri, meşru kullanıcıları da engelleyebilir. Ben genellikle bunu yarı otomatik tutuyorum: script şüpheli IP’leri listeler, admin onayladıktan sonra engelleme yapılır.
Logrotate ile Arşiv Üzerinde Çalışmak
Loglar rotate olduğunda eski arşivleri de analiz etmek gerekebilir. .gz uzantılı sıkıştırılmış loglarla çalışmak için:
# Tum log arsivlerini dahil ederek haftalik ozet
zcat /var/log/nginx/access.log.*.gz |
awk '{
# Her gun icin istek sayisi
split($4, dt, "/")
day = substr(dt[1], 2) # Baştaki [ isaretini temizle
month = dt[2]
year = substr(dt[3], 1, 4)
date_key = day"-"month"-"year
daily[date_key]++
daily_ips[date_key][$1] = 1 # Benzersiz IP sayisi icin
} END {
print "TarihttIstektBenzersiz IP"
for (d in daily) {
ip_count = 0
for (ip in daily_ips[d]) ip_count++
printf "%st%dt%dn", d, daily[d], ip_count
}
}' | sort -t- -k3,3 -k2,2M -k1,1n
awk ile Field Separator Ayarlama
Apache ve nginx bazı konfigürasyonlarda farklı log formatları kullanabilir. Özel alan ayracı için -F parametresi:
# CSV formatli log icin virgul ayracı
awk -F',' '{print $1, $3}' /var/log/custom_access.csv
# Bircok ayrac karakteri icin
awk -F'[|,;]' '{print $1}' /var/log/mixed_format.log
# FS degiskenini kullanmak
awk 'BEGIN{FS="t"} {print $1, $5}' /var/log/tab_separated.log
BEGIN ve END Bloklarının Gücü
BEGIN ve END blokları awk‘ın en önemli özelliklerinden biri. Log analizinde başlık ve özet eklemek için ideal:
awk 'BEGIN {
print "Analiz basliyor..."
print "Tarih:", strftime("%Y-%m-%d %H:%M:%S")
print "================================"
total = 0
error_total = 0
}
{
total++
if ($9 >= 500) error_total++
bytes_total += ($10 ~ /^[0-9]+$/) ? $10 : 0
}
END {
print "nANALIZ SONUCLARI:"
print " Toplam Istek:", total
print " 5xx Hata Sayisi:", error_total
printf " Hata Orani: %.2f%%n", (error_total/total)*100
printf " Toplam Transfer: %.2f GBn", bytes_total/1073741824
print "================================"
print "Analiz tamamlandi."
}' /var/log/nginx/access.log
Performans İpuçları
Büyük log dosyaları üzerinde çalışırken birkaç önemli nokta:
- Dosyayı birden fazla kez okumaktan kaçının: Mümkünse tek
awkgeçişinde tüm analizleri yapın. Her okuma disk I/O demek. - Zaman filtresi önce uygulayın:
grep "10/Dec/2024" access.log | awk '...'yazmak, büyük dosyalardaawkiçinde filtreleme yapmaktan daha hızlı olabilir çünkügrepçok optimize edilmiştir. NRdeğişkenini kullanın: Test aşamasındaNR > 10000 {exit}eklemek scripti sadece ilk 10.000 satırda çalıştırır, hız testi için faydalıdır.- Pipe yerine process substitution:
awk '...' <(zcat file.gz)syntax’ı bazen daha temiz kod üretir.
Sonuç
awk ile ağ log analizi yapmak ilk başta karmaşık görünebilir ama bir kez mantığını kavradığınızda vazgeçilmez hale geliyor. İlişkili diziler, BEGIN/END blokları ve alan manipülasyonu ile pratik olarak her türlü log verisini işleyebilirsiniz.
Ben bu scriptlerin basit versiyonlarını yıllardır production ortamında kullanıyorum. Bir DDoS saldırısı başladığında ya da bir servis aniden yavaşladığında, birkaç satır awk komutu ile sorunu dakikalar içinde lokalize edebiliyorum.
Bir tavsiye: bu komutları düz syntax olarak ezberlemeye çalışmayın. Mantığı anlayın. awk‘ın her satırı nasıl işlediğini, dizileri nasıl kullandığını, BEGIN ve END‘in ne zaman devreye girdiğini kavradıktan sonra gerisi kendiliğinden geliyor.
Kendi log formatınıza göre bu scriptleri uyarlamak için awk '{print NF, $0}' logfile | head -5 komutunu çalıştırın: bu size her satırda kaç alan olduğunu ve içeriğini gösterir. Oradan hangi alan numarasının neye karşılık geldiğini kolayca çıkarabilirsiniz.
