Python ile IPv6 ağ sağlığını izleyen küçük bir araç yazmak

Selamlar arkadaşlar, bu yazımızda Python ile küçük ama gerçekten faydalı bir IPv6 izleme aracı yazacağız. Adres dağıtımı, prefix delegation falan değil derdimiz; konumuz daha sıradan ama bir o kadar can sıkıcı bir mesele: IPv6 tarafı ne durumda, kimse fark etmeden çökmüş olabilir mi? Hadi başlayalım.

İşin aslı şu: çoğu şirkette IPv4 izlemesi yıllardır oturmuş, IPv6 ise ya yarı kurulu durumda ya da ' çalışıyor herhalde' modunda. Tecrübeyle sabittir, IPv6 routing'i bir gece sessizce düşer ve bunu fark eden ilk kişi genellikle bir kullanıcı olur. Bu yüzden ufak bir Python script'i bile büyük fark yaratıyor.

Neden Python ve neden asyncio?

ping6 veya ping -6 zaten her Linux kutusunda var; biz onun üzerine ince bir katman yazıyoruz. Tek bir host için subprocess yeter, ama gerçekçi bir senaryoda 20-30 host paralel kontrol edilmek istenir. Sıralı ping yapsanız her hedef için 2-3 saniye bekler, toplam tur 60 saniyeye dayanır. Asyncio ile bunu birkaç saniyeye indiriyoruz.

Şahsi kanaatim, bu tür araçlarda framework kullanmaya gerek yok; standart kütüphane fazlasıyla yetiyor.

Tek host için ping fonksiyonu

Hadi minimum bir örnekle başlayalım. Aşağıdaki fonksiyon ping6 çıktısını parse edip yapılandırılmış bir sonuç döndürüyor:

import asyncio
import re
from dataclasses import dataclass

@dataclass
class PingSonuc:
    host: str
    ulasilabilir: bool
    rtt_ms: float | None
    paket_kaybi_yuzde: float

async def ping6_async(host: str, sayi: int = 3) -> PingSonuc:
    proc = await asyncio.create_subprocess_exec(
        'ping', '-6', '-c', str(sayi), '-W', '2', host,
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.DEVNULL,
    )
    stdout, _ = await proc.communicate()
    cikti = stdout.decode()

    kayip = re.search(r'(\d+(?:\.\d+)?)% packet loss', cikti)
    paket_kaybi = float(kayip.group(1)) if kayip else 100.0

    rtt = re.search(r'min/avg/max.*= [\d.]+/([\d.]+)/', cikti)
    rtt_ms = float(rtt.group(1)) if rtt else None

    return PingSonuc(host, proc.returncode == 0, rtt_ms, paket_kaybi)

Burada create_subprocess_exec ile ping komutunu non-blocking şekilde çağırıyoruz. -W 2 parametresi her bir paket için en fazla 2 saniye bekleyeceğimizi söylüyor; donmuş hedefler bütün döngüyü tıkamasın diye bu önemli.

Paralel toplu kontrol

Tek host'a bakmak ısınma turu. Şimdi gerçek senaryoya geçelim. Bir hedef listesini paralel pingleyip sonuçları toplayalım:

async def topla(hedefler: list[str]) -> list[PingSonuc]:
    gorevler = [ping6_async(h) for h in hedefler]
    return await asyncio.gather(*gorevler, return_exceptions=False)

async def main():
    hedefler = [
        '2001:4860:4860::8888',   # Google DNS
        '2606:4700:4700::1111',   # Cloudflare DNS
        '2001:db8:cafe::1',       # ic gateway
    ]
    sonuclar = await topla(hedefler)
    for s in sonuclar:
        durum = 'UP' if s.ulasilabilir else 'DOWN'
        rtt = f'{s.rtt_ms:.1f}ms' if s.rtt_ms else 'N/A'
        print(f'[{durum}] {s.host} rtt={rtt} kayip={s.paket_kaybi_yuzde:.0f}%')

asyncio.run(main())

20 hedef için bu kod yaklaşık 3 saniyede toplam sonucu veriyor; sıralı versiyonun yanından bile geçmiyor. Tabii burada ICMP rate limit'lere de dikkat etmek lazım, ağ ekibi sizi düşman zannetmesin.

SLO bütçesi: sadece UP/DOWN değil

Bana sorarsanız, gerçek değer UP/DOWN'da değil, gecikme dağılımında saklı. Bir host hâlâ ping'e cevap veriyor olabilir ama RTT 12 ms'den 180 ms'ye fırladıysa kullanıcı zaten yandı. Basit bir SLO sınıfı:

def slo_durumu(s: PingSonuc, esik_ms: float = 50.0) -> str:
    if not s.ulasilabilir:
        return 'KRITIK'
    if s.paket_kaybi_yuzde > 0:
        return 'BOZULDU'
    if s.rtt_ms and s.rtt_ms > esik_ms:
        return 'YAVAS'
    return 'SAGLIKLI'

Bu üç-dört durumlu çıktı, alert kuralları yazmayı çok rahatlatıyor. Tek bir gauge yerine bir histogram tutarsanız Grafana'da p95'i takip etmek de kolay olur ama bu yazının kapsamı dışına taşar.

Sık karşılaşılan hatalar

  • -W parametresini unutmak: Hedef sessizce paket düşürüyorsa varsayılan timeout dakikalara çıkabilir. Tüm async döngünüz tek bir DOWN host yüzünden kilitlenir. -W 2 veya -W 3 koymak zorunlu.
  • Sadece DNS adı denemek: Hostname AAAA kaydı dönmüyorsa Linux sessizce IPv4'e fallback yapabilir; siz IPv6'yı izlediğinizi sanırken aslında IPv4 ölçüyorsunuzdur. Mümkünse hedefi doğrudan IPv6 literali olarak verin, requests kullanıyorsanız URL'de köşeli parantezle (https://[2001:db8::1]/health).
  • Tek thread'de senkron subprocess.run: 30 hedef için döngü kuruyorsanız asyncio'ya geçmek üç katı performans değil, on katı performans demek. asyncio.gather burada hayat kurtarıyor.
  • Loopback üzerinde test edip iyi sandığınız kod: ::1 her zaman ulaşılabilir; gerçek bir test ortamı için harici bir IPv6 hedefi şart, yoksa script'iniz sahada ilk ICMP filtresinde patlar.

Doğrulama

Local'de bir tail komutuyla manuel doğrulamak en hızlısı:

python ipv6_monitor.py | grep -E 'DOWN|YAVAS'

Beklediğiniz çıktı yoksa, yani her şey UP ve hızlı görünüyorsa, bilerek olmayan bir adres ekleyin (2001:db8:dead::1 gibi). DOWN satırı görüyor musunuz? Görüyorsanız hata yolu çalışıyor demektir.

Kapanış

Bu yazıda Python ve asyncio ile basit ama işine yarayan bir IPv6 sağlık izleme aracı yazdık; UP/DOWN'un ötesine geçip RTT ve paket kaybı eşikleriyle SLO mantığı ekledik. Bence bu küçük script kurumsal izleme yığınınızın yerine geçmez ama IPv6 tarafının gerçekten ne durumda olduğunu öğrenmenin en hızlı yolu hâlâ böyle 80 satırlık bir Python dosyası. Umarım faydalı olur, bir sonraki yazıda görüşmek üzere.