Post

Linux'ta Network Sorunlarını Syscall Perspektifiyle Okumak

🇹🇷 Ping çalışıyor ama uygulama bağlanamıyor mu? Connection Refused ile Timeout arasındaki farkı, connect, sendto ve accept çağrıları üzerinden, kernel'ın gözünden anlatıyoruz.

Linux'ta Network Sorunlarını Syscall Perspektifiyle Okumak

Önceki yazımızda Linux’ta Dosya ve Yetki Problemlerini Kernel Seviyesinde Anlamak başlığı altında, dosya sisteminin (VFS) bürokrasisini ve “Permission Denied” hatalarını incelemiştik.

Peki ya dosyaya erişebiliyorsunuz ama bu sefer de dış dünyaya, internete veya veritabanına erişemiyorsanız?

Bir sistem yöneticisi (SysAdmin) veya DevOps mühendisi için en gergin, en saç baş yolduran anlardan biri, Network (Ağ) ekibiyle veya veri merkezi yöneticileriyle yapılan o soğuk savaştır.

Siz: “Uygulama sunucuya erişemiyor. Connection Timeout alıyorum. Firewall’da bir engelleme var mı?” Networkçü: “Hayır, erişim açık. Benim ekranımda her şey yeşil. Router logları temiz. Ping atıyorum cevap dönüyor. Sorun senin sunucuda veya kodunda.”

O an derin bir nefes alırsınız. Terminalinizdeki nmap veya curl çıktısındaki o inatçı Connection timed out hatasına bakarsınız. Sonra ping atarsınız ve cevap gelir. “Allah Allah…” dersiniz. “Ping gidiyor, telnet gitmiyor. Acaba sorun gerçekten bende mi? Yoksa networkçü beni başından mı savıyor?”

Bu yazıda, Network ekibini suçlamadan önce (veya suçlayabilmek için elinizde reddedilemez teknik kanıtlar toplarken) kullanacağımız “cerrahi aletleri” masaya yatıracağız.

📌 Bu Yazıda Ne Öğreneceksin?

Bu yazı, ezbere komutlar listesi değil, ağın “ruhunu” anlama rehberidir. Okumayı bitirdiğinizde:

  • Telefon Santrali Metaforu: socket, bind, listen, accept ve connect syscall’larının aslında nasıl bir “Sandalye Kapmaca” veya “Restoran Sırası” yönettiğini göreceksiniz.
  • Connection Refused vs Timeout: İkisi arasındaki farkın, sorunun kaynağını (Server mı, Network mü?) şıp diye bulmanızı nasıl sağladığını anlayacaksınız.
  • DNS’in Sırları: strace çıktısında neden bazen UDP yerine UNIX Domain Socket (nscd) gördüğünüzü ve DNS’in TCP bağlantılarını başlamadan nasıl öldürdüğünü öğreneceksiniz.
  • Backlog & SYN Flood: Sunucunuz ayaktayken neden “Bağlanamıyorum” dendiğini, Backlog (Restoran Hostesi) analojisiyle beyninize kazıyacaksınız.
  • Gerçek Hayat Tuzakları: Kubernetes (ndots:5), Docker (localhost döngüsü) ve Java/Node.js/Python dünyasındaki hata mesajlarının Kernelca çevirisini cebinize koyacaksınız.

Kısacası; OSI Modelini ezbere saymak yerine, elinizi kirletip Kernel’ın ağ trafiğini nasıl yönettiğini ve paketlerin neden kaybolduğunu canlı izlemeyi öğreneceksiniz.

📞 1. Telefon Santrali Metaforu: Socket Dünyası

Linux’ta ağ iletişimi (Network I/O), dosya I/O’sına şaşırtıcı derecede benzer. UNIX felsefesini hatırlayın: “Linux’ta her şey bir dosyadır.” Ağ soketleri (sockets) de, üzerine yazıp okuyabileceğiniz özel birer dosyadır.

Bir TCP bağlantısının kurulma sürecini (Three-Way Handshake), eski usul bir telefon görüşmesine benzetirsek taşlar yerine oturacaktır:

sequenceDiagram
    participant Client as Arayan (Client)
    participant Cloud as Şebeke (Network)
    participant Server as Santral (Server)

    Note over Server: 1. socket() + bind() + listen()<br/>(Telefonu fişe tak, hattı aç)
    
    Note over Client: 2. socket() + connect()<br/>(Numarayı çevir)
    
    Client->>Cloud: [SYN] "Alo? Orada kimse var mı?"
    activate Cloud
    Cloud->>Server: (Telefon Çalar...)
    deactivate Cloud
    
    activate Server
    Server-->>Cloud: [SYN-ACK] "Evet, ben buradayım!"
    deactivate Server
    
    activate Cloud
    Cloud-->>Client: (Çalma sesi durur, hat açılır)
    deactivate Cloud
    
    Client->>Cloud: [ACK] "Tamam, seni duyuyorum."
    activate Cloud
    Cloud->>Server: (Bağlantı Onayı İletilir)
    deactivate Cloud
    
    Note right of Server: 3. accept() Döndü!<br/>(Ahizeyi kaldır)
    
    Note over Client, Server: Bağlantı Kuruldu (ESTABLISHED)
    
    Client->>Server: "GET /index.html" (Veri Gönderimi)

A. Sunucu (Server) Tarafı: “Santrali Kurmak”

  1. socket() (Telefonu Satın Alma): İşlem yapmak için önce fiziksel bir cihaza ihtiyacınız var. Kodunuzda socket() fonksiyonunu çağırdığınızda, Kernel’a şunu dersiniz: “Bana IPv4 dilini konuşan, TCP özellikli bir uç nokta ver.” Kernel size bir “File Descriptor” (Dosya Numarası, örneğin: 3) verir. Henüz bir numaranız yok, hattınız bağlı değil. Sadece elinizde bir telefon makinesi var.

    🧐 Teknik Detay: File Descriptor (FD) Nedir?

    Linux’ta açılan her dosya, soket veya pipe için Kernel bir tam sayı (integer) atar.

    • 0: Standard Input (stdin)
    • 1: Standard Output (stdout)
    • 2: Standard Error (stderr) Bu yüzden programınızın açtığı ilk soket veya dosya genellikle 3 numarasını alır. socket() = 3 çıktısının sırrı budur.
  2. bind() (Sandalye Kapmaca): Diğer insanların sizi bulabilmesi için sabit bir yere oturmanız lazım. “Ben 192.168.1.5 masasındaki 80 numaralı sandalyeye oturacağım” dersiniz. Ancak, eğer o sandalyede başka bir uygulama oturuyorsa (örn: Nginx çalışırken Apache açmaya çalışmak), Kernel “Oturamazsın!” der ve EADDRINUSE (Address already in use) hatasını fırlatır. Bu, Kernel’ın oynattığı bir sandalye kapmaca oyunudur; bir portu (sandalyeyi) sadece tek bir kişi (process) kapabilir.

  3. listen() (Telefonu Açık Bırakma): Tesisat hazır. Şimdi santrali aktif ediyorsunuz. “Artık aramaları kabul etmeye hazırım.” Burada çok kritik, genelde gözden kaçan bir detay vardır: Backlog Queue. Kernel’a şunu söylersiniz: “Ben telefondayken arkada kaç kişi bekleyebilir?”. Bu sayı önemlidir (daha sonra değineceğiz).

  4. accept() (Ahizeyi Kaldırma): Telefon çaldığında (Client bağlandığında) sunucu uygulamanız ahizeyi kaldırır. Bu işlem, yepyeni bir soket (yeni bir hat) oluşturur. Ana hat (Listening Socket) meşgul edilmez, görüşme bu yeni hat üzerine aktarılır. Böylece ana hat, yeni gelen aramaları beklemeye devam edebilir.

B. İstemci (Client) Tarafı: “Arama Yapmak”

  1. socket(): İstemci de önce bir telefon makinesi edinir.
  2. connect() (Numara Çevirme): Hedef numarayı (IP + Port) çevirir ve ahizeyi kulağına dayayıp beklemeye başlar. İşte bütün olay, bütün o timeout hataları, bütün o beklemeler burada, bu çağrı sırasında yaşanır.

Bu süreci anladığımızda, hataları yorumlamak çok daha kolaylaşır.

⛔ 2. Connection Refused vs Timeout: Hayati Fark

Yazılımcıların ve junior sistemcilerin en sık karıştırdığı, birbirinin yerine kullandığı iki kavram vardır. İkisi de “Bağlanamadım” demektir ama nedenleri birbirinden gece ile gündüz kadar farklıdır. Bu farkı anlamak, sorunun kaynağını (Server tarafı mı, yoksa aradaki Network mü?) bulmanın tek yoludur.

A. Connection Refused (ECONNREFUSED)

  • Senaryo: Arkadaşınızı aradınız. Telefon çaldı. Arkadaşınız (veya sekreteri) telefonu açtı ve “Seninle konuşmak istemiyorum” diyip suratınıza kapattı. Veya operatörden “Aradığınız numara kullanılmamaktadır” mesajı aldınız.
  • Teknik Anlamı: İstemci, sunucuya bir SYN paketi (Merhaba, konuşabilir miyiz?) gönderdi. Sunucu (ya da sunucunun hemen önündeki güvenlik duvarı), bir RST, ACK (Reset) paketiyle geri döndü. “HAYIR!” dedi.
  • Ne Demek İstiyor?
    1. Ulaşım Tamam: Tebrikler! Hedef sunucuya fiziksel olarak ulaştınız. Network kabloları sağlam, IP routing doğru. Network ekibini suçlamayın.
    2. Servis Yok: O IP adresinde, o Port numarasını (8080 diyelim) dinleyen canlı bir program YOK.
    3. Aktif Red: Veya sunucu üzerindeki yerel firewall (iptables/firewalld), “Bu paketi derhal reddet (REJECT)” kuralı işletiyor.

💡 Teşhis: Eğer Connection Refused alıyorsanız, Router/Switch yönetenleri rahatsız etmeyin. Sorun %99 ihtimalle hedef sunucuda. Ya servis çökmüş (crash), ya yanlış portta çalışıyor ya da servis 127.0.0.1‘i dinlerken siz dış IP’den gelmeye çalışıyorsunuz.

B. Connection Timed Out (ETIMEDOUT)

  • Senaryo: Arkadaşınızı aradınız… dıt… dıt… çalıyor… çalıyor… 30 saniye geçti… 1 dakika geçti… Açan yok. Telesekreter bile yok. Sadece derin bir sessizlik. Sonunda sıkılıp kapatıyorsunuz.
  • Teknik Anlamı: İstemci SYN paketini (bazen defalarca) gönderdi. Ama karşıdan “ÇIT” çıkmadı. Ne “Olumlu” (SYN-ACK) ne de “Olumsuz” (RST/Refused) bir cevap geldi. Paket bir kara deliğe düştü.
  • Ne Demek İstiyor?
    1. Fiziksel Kayıp: Paket yolda kayboldu. Kablo kopuk, router bozuk.
    2. DROP (En Yaygın): Aradaki bir Kurumsal Firewall veya sunucunun kendi güvenlik duvarı, paketi sessizce çöpe attı (DROP). “Seni muhatap bile almıyorum, reddettiğimi bile söylemeyeceğim” dedi.
    3. Yanlış IP/Routing: Paket yanlış bir yola girdi ve geri dönüş yolunu bulamadı.

💡 Teşhis: İşte şimdi Network ekibini arayabilirsiniz. “Paketim bir yerlerde düşüyor (Drop)” diyebilirsiniz. Veya sunucunun firewall kurallarında REJECT yerine DROP kullanılıyordur.

🌍 Yazılımcı - Kernel Sözlüğü

Uygulamanızın dili Kernelca bilmez, kendi lehçesini konuşur. İşte çeviri tablosu:

Kernel HatasıJava (Spring/Tomcat)Node.js (Axios/Express)Python (Requests/Django)
ECONNREFUSEDjava.net.ConnectException: Connection refusedError: connect ECONNREFUSED 1.2.3.4:80requests.exceptions.ConnectionError: [Errno 111] Connection refused
ETIMEDOUTjava.net.SocketTimeoutException: connect timed outError: connect ETIMEDOUTrequests.exceptions.ConnectTimeout
EADDRINUSEjava.net.BindException: Address already in useError: listen EADDRINUSE: address already in useOSError: [Errno 98] Address already in use

Loglarınızda bu satırları gördüğünüzde, sorunun aslında Kernel’ın fırlattığı TCP hataları olduğunu hatırlayın.

🕵️‍♂️ 3. Vaka Analizi: “Ping Çalışıyor Ama Uygulama Çalışmıyor”

Bu, en klasik “Bilişim Cinayet Masası” vakasıdır.

Olay Yeri: Web sunucunuz (192.168.1.50) çalışıyor. Kendi laptopunuzdan ping 192.168.1.50 yapıyorsunuz, cevaplar ms cinsinden şakır şakır dönüyor. “Oh, hat sağlam” diyorsunuz. Tarayıcıyı açıp adresi giriyorsunuz… Dönüyor… Dönüyor… Ve Connection timed out.

Yanlış Teşhis: “Ping dönüyor, demek ki sunucuya erişim var. Sorun kesin tarayıcıda, cache’de veya benim kodumda.”

Doğru Teşhis: “Ping ICMP protokolüdür (Layer 3 - Network). Web ise TCP/80 protokolüdür (Layer 4 - Transport). Bu ikisi elma ile armut gibidir. Yollarının açık olması birbirini garanti etmez.”

Gelin bu durumu strace ile, uygulamanın (örneğin curl) gözünden izleyelim:

1
2
# -v: verbose modu, detayları görmek için
strace -e trace=connect,socket,poll,select curl -v http://192.168.1.50

Çıktıda şu dramı izlersiniz:

1
2
3
4
5
socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) = 3
connect(3, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("192.168.1.50")}, 16) = -1 EINPROGRESS (Operation now in progress)
poll([{fd=3, events=POLLOUT|POLLWRBAND}], 1, 1000) = 0 (Timeout)
poll([{fd=3, events=POLLOUT|POLLWRBAND}], 1, 1000) = 0 (Timeout)
...
  1. socket(): Telefon alındı (File Descriptor 3). SOCK_STREAM parametresi bize bunun TCP olduğunu söylüyor.
  2. connect(): Numarayı çevirdi. Ama cevap (Return Code 0) hemen gelmedi. Kernel dedi ki: EINPROGRESS.
    • Teknik Detay: Modern istemciler “Non-blocking” soket kullanır. Yani connect çağrısında program donmaz, Kernel “Tamam, ben arka planda (TCP Handshake) deniyorum, sen işine bak” der.
  3. poll(): Uygulama beklemeye başladı. “Bağlantı tamamlandı mı?” diye soruyor. 1 saniye geçiyor… Cevap yok. 5 saniye geçiyor… Cevap yok.
  4. Sonunda uygulama pes eder ve “Timeout” hatasını basar.

Analiz: Ping (ICMP Echo Request) paketi, Port kavramı içermez. Sadece “O makine (IP) orada mı?” diye bakar ve kapıyı çalar. HTTP isteği (TCP Port 80) ise “O makinenin 80 numaralı kapısı açık mı?” diye bakar. Firewall kuralı (örneğin AWS Security Group veya iptables) muhtemelen şöyledir:

  • Protocol: ICMP -> ALLOW (Ping’e izin ver)
  • Protocol: TCP, Port: 80 -> BLOCK/DROP (Web trafiğini engelle)

Ders: Bağlantı testi için asla ve asla sadece ping kullanmayın. Ping yanıltıcı bir dosttur. Port bazlı test yapın:

  • telnet <ip> <port> (Eski ama altın)
  • nc -zv <ip> <port> (Netcat - Modern ve hızlı)
  • curl -v telnet://<ip>:<port> (Curl ile TCP testi)

“Portu test etmeyen test, test değildir.”

📒 4. DNS: Telefon Rehberi ve UDP

Bazen connect çağrısını bile göremezsiniz. Uygulama “donar”. Sanki numarayı çevirmeye çalışıyor ama eli havada kalmış gibidir.

1
2
3
import requests
# Kodu çalıştırıyorsunuz ve terminal burada 5-10 saniye donuyor
requests.get("http://api.internal-service.local") 

Buna strace ile baktığınızda, beklediğiniz connect (TCP 80) çağrısını göremezsiniz. Onun yerine şunu görürsünüz:

1
2
3
4
socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC, IPPROTO_IP) = 3
connect(3, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("8.8.8.8")}, 16) = 0
sendto(3, "...", ...)
poll([{fd=3, events=POLLIN}], 1, 5000) ...

Dikkat! SOCK_STREAM (TCP) değil SOCK_DGRAM (UDP) gördük. Ve Port 53.

Buradaki sorun “Arama (TCP)” değil, “Numarayı Bulma (UDP/DNS)” sorunudur. Bunu bir Telefon Rehberi analojisiyle düşünün:

  • TCP (Web - Telefon Görüşmesi): Karşı tarafı arayıp “Alo” dersiniz, o da “Efendim” der (Handshake). Bağlantı süreklidir.
  • UDP (DNS - Posta): Rehber servisine bir kağıt fırlatırsınız: “Google’ın numarası kaç?” (Fire-and-Forget). Cevap gelmesini beklersiniz.

Eğer o kağıt (UDP paketi) yolda rüzgardan uçarsa veya rehber servisi (8.8.8.8) kağıdı alıp cevap vermezse, kimse size “Hata oluştu” demez. Telefon ahizesinden “dıt dıt” sesi duymazsınız. Sadece boş boş beklersiniz (Application Freeze).

İşte bu yüzden DNS sorunları, Timeout hatalarının en sinsi nedenidir. TCP bağlantısı hiç başlamadığı için, TCP Timeout süresi de işlemez. Uygulama kendi içindeki “DNS Timeout” süresince kilitlenir.

💡 İpucu: Eğer curl, ssh veya mysql komutunu yazar yazmaz imleç donuyorsa ve hiçbir çıktı vermiyorsa, sorun %99 DNS’tir.

  • /etc/resolv.conf dosyanızı kontrol edin.
  • dig +short <hostname> komutu anında cevap veriyor mu bakın.

🔍 Teknik Detay: strace Çıktısında Neden Socket Var?

sequenceDiagram
    participant App as Uygulama (curl/browser)
    participant Glibc as glibc (getaddrinfo)
    participant Nscd as nscd / systemd-resolved
    participant DNS as DNS Sunucusu (8.8.8.8)

    App->>Glibc: "Google'ın numarası kaç?"
    Glibc->>Nscd: "Önbelleğinde var mı? (UNIX Socket)"
    
    alt Önbellekte Var
        Nscd-->>Glibc: "Evet: 142.250.72.14"
    else Önbellekte Yok
        Nscd->>DNS: "Google kaç? (UDP/53)"
        DNS-->>Nscd: "142.250.72.14"
        Nscd-->>Glibc: "Öğrendim: 142.250.72.14"
    end
    
    Glibc-->>App: "IP Adresi: 142.250.72.14"

Dikkatli bir göz şunu sorabilir: “Ben strace çıktımda UDP (SOCK_DGRAM) görmüyorum, onun yerine /var/run/nscd/socket diye bir şey görüyorum. Bu nedir?”

Modern Linux dağıtımları, DNS sorgularını hızlandırmak için nscd (Name Service Cache Daemon) veya systemd-resolved gibi yerel önbellek servisleri kullanır:

  1. Uygulamanız (örneğin curl), getaddrinfo() fonksiyonunu çağırır.
  2. glibc, sistemde nscd çalışıyor mu diye bakar.
  3. Eğer çalışıyorsa, DNS sunucusuna gitmek yerine UNIX Domain Socket üzerinden yerel servisle konuşur.

    🧐 Teknik Detay: UNIX Domain Socket Nedir?

    Klasik soketler (Network Sockets) veri transferi için ağ kartını ve IP protokolünü kullanır. UNIX Domain Sockets (AF_UNIX) ise aynı bilgisayar içindeki süreçlerin (processes) konuşması içindir. Veri ağ kartına hiç uğramaz, doğrudan Kernel belleği üzerinden ışık hızında kopyalanır. “Ağsız Ağ” gibi düşünebilirsiniz.

  4. strace çıktısında şunu görürsünüz:
    1
    2
    
    socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 3
    connect(3, {sa_family=AF_UNIX, sun_path="/var/run/nscd/socket"}, 110) = 0
    

Bu kafanızı karıştırmasın. Bu sadece “Rehbere bakma” işleminin, dışarıdaki postane yerine, binadaki sekretere sorulmasıdır. Eğer yerel servis cevabı bilmiyorsa, o sizin yerinize DNS’e (UDP/53) gidecektir.

☸️ Kubernetes Dünyasından Gerçek Bir Vaka: ndots:5 Sorunu

Kubernetes (K8s) kullanıyorsanız, DNS performansı kritik bir darboğaz olabilir. K8s varsayılan olarak /etc/resolv.conf içine ndots:5 ayarını ekler.

Bu şu demektir: Siz google.com (tek nokta) adresine gitmek istediğinizde, sistem bunu “Tam nitelikli alan adı değil” (FQDN) sanar ve sırasıyla şunları dener:

  1. google.com.default.svc.cluster.local (DNS Kaydı Yok - NXDOMAIN)
  2. google.com.svc.cluster.local (NXDOMAIN)
  3. google.com.cluster.local (NXDOMAIN)
  4. En sonunda gerçek google.com. adresini sorar.

Her basit istek için dışarıya 5 boş DNS sorgusu atarsınız. Çözüm? External domainlere bağlanırken sonuna nokta koyun: google.com. (Absolute Domain Name). Bu, kernel’a “Arama yapma, adres tam olarak bu” der.

🎧 5. Localhost Tuzağı: 0.0.0.0 vs 127.0.0.1

Bir geliştiricinin en sık kurduğu cümle: “Ama benim makinemde (localhost’ta) çalışıyor, sunucuya atınca çalışmıyor!”

Uygulamanızı başlatırken (Bind aşamasında) genelde bir “Listening Address” (Dinleme Adresi) verirsiniz.

  • 127.0.0.1 (Localhost): “Sadece bu bilgisayarın içinden gelen istekleri kabul et.” (Kişisel, içe kapanık)
  • 0.0.0.0 (Any / Wildcard): “Bu bilgisayardaki TÜM ağ kartlarından (Wifi, Ethernet, VPN vb.) gelen istekleri kabul et.” (Sosyal, dışa açık)

Eğer server.js veya app.py dosyanızda varsayılan olarak host='localhost' veya bind='127.0.0.1' yazıyorsa, dış dünyadan (hatta aynı ağdaki diğer sunucudan) kimse size ulaşamaz.

Kernel, dışarıdan gelen pakete bakar:

  1. Paket 192.168.1.5:8080 adresine geldi.
  2. Kernel dinleyenler listesine bakar.
  3. Uygulama 127.0.0.1:8080 dinliyor.
  4. Kernel: “IP’ler eşleşmiyor. Uygulama sadece içeriyi dinliyor. Gelen paket dışarıdan.”
  5. Sonuç: Connection Refused. (Sanki port kapalıymış gibi!)

Bunu ss (Socket Statistics) aracıyla şıp diye anlarsınız. netstat eskidi, artık ss kullanın.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# -l: listening ports
# -n: show numbers (don't resolve service names)
# -t: tcp only
# -p: show process name (sudo gerekir)
sudo ss -lntp

# Çıktı Örneği 1: Utangaç Servis (Yanlış)
State    Recv-Q   Send-Q     Local Address:Port      Peer Address:Port   Process
LISTEN   0        128        127.0.0.1:3000          0.0.0.0:*           users:(("node",pid=123,fd=18))
# Sadece 127.0.0.1 dinliyor. Dışarıdan erişilemez!

# Çıktı Örneği 2: Sosyal Servis (Doğru)
State    Recv-Q   Send-Q     0.0.0.0:80              0.0.0.0:*           users:(("nginx",pid=456,fd=6))
# 0.0.0.0 dinliyor. Her yerden erişilebilir.

Production ortamında dışarıya hizmet verecek bir servis, mutlaka 0.0.0.0 (veya sunucunun spesifik dış IP’sini) dinlemelidir.

🐳 Docker’ın “Sihirli” IP Adresleri

Docker konteynerlarında localhost (127.0.0.1) kavramı daha da kafa karıştırıcıdır.

  • Konteyner İçinde: 127.0.0.1 o konteynerin kendisidir. Host makine değildir.
  • Eğer konteyner içindeki uygulamanızdan Host üzerindeki MySQL’e bağlanmak için localhost yazarsanız, uygulama konteynerin kendi içinde MySQL arar ve bulamaz.
  • Çözüm: Docker’ın özel DNS ismi host.docker.internal (Mac/Windows) veya Linux’ta --network="host" kullanmak ya da Host IP’sini (172.17.0.1 gibi) vermektir.

🍽️ 6. Restoran Analojisi: Backlog Queue

Sitenize “Black Friday” yoğunluğu başladı. Load Balancer’a bakıyorsunuz, sunucu ayakta ama kullanıcılar sürekli “Timeout” alıyor. Sunucuya girip curl 127.0.0.1 yapıyorsunuz, o bile yavaş cevap veriyor.

Burada socket, bind, listen üçlüsündeki listen(fd, backlog) parametresine dönelim. Kernel’ın bu trafiği nasıl yönettiğini anlamak için “Popüler Restoran” analojisini kullanalım.

flowchart TD
    Client["Müşteri (Client)"] -- "SYN Paketi" --> SYNQ["SYN Queue<br/>(Kapı Önü Kuyruğu)"]
    

    SYNQ -- "Yer Var mı?" --> Check{"Kontrol: Dolu mu?"}
    

    Check -- "Evet (Taşmış)" --> Drop["DROP (Sessiz Red)"]
    Check -- "Hayır (Yer Var)" --> SYNACK["SYN-ACK Gönder"]
    
    SYNACK -- "ACK Geldi" --> AcceptQ["Accept Queue<br/>(İçeride Masa Bekleme)"]
    AcceptQ -- "Sıra Geldi" --> App["Uygulama (accept syscall)"]
    
    style Drop stroke:#f66,stroke-width:2px
    style AcceptQ stroke:#fc0,stroke-width:2px
    style SYNQ stroke:#6cf,stroke-width:2px

Bir TCP bağlantısı (müşteri) için iki bekleme alanı vardır:

  1. Dışarıdaki Kuyruk (SYN Queue): Müşteri restoranın kapısına geldi, “Alo” (SYN) dedi. Kapıdaki görevli ismini listeye yazdı ama henüz içeri almadı.
  2. İçerideki Bekleme Alanı (Accept Queue): Müşteri içeri alındı (ACK yollandı, bağlantı kuruldu - ESTABLISHED). Ancak henüz masaya oturtulmadı. Hostesin (yani Uygulamanızın, örn: Nginx/Tomcat) gelip “Buyrun masanız hazır” demesini (accept() çağrısını yapmasını) bekliyor.

Kernel parametresi olan backlog, işte bu İçerideki Bekleme Alanı’nın kapasitesidir.

Eğer uygulamanız (Hostes) çok yavaş hareket ediyorsa (Tek thread çalışıyor, CPU %100, Disk I/O bekliyor vb.), içerisi dolar. İçerideki bekleme alanı (Backlog) dolduğunda, Hostes kapıdaki güvenliğe (Kernel) “Kimseyi alma!” talimatı verir.

Ve Kernel acımasızlaşır: Dışarıdaki kuyrukta (SYN Queue) bekleyen veya yeni gelen müşterilerin paketlerini sessizce çöpe atar (DROP).

Müşteri (Client) ise bunu “Restoran (Sunucu) cevap vermiyor, acaba kapalı mı?” (Timeout) sanır. Halbuki restoran açık, ışıklar yanıyor, sadece bekleme alanı taştı ve Kernel yeni müşterileri “görmezden geliyor”. Bu, bir Connection Refused değildir, bu bir sessiz reddediştir.

🚨 Kernel Bağırıyor: dmesg

“Sunucuda hiçbir hata yok” demeden önce dmesg veya /var/log/kern.log dosyasına bakın. Kernel orada çığlık atıyor olabilir:

1
2
[1234.56] TCP: request_sock_TCP: Possible SYN flooding on port 80. Sending cookies.  
[1234.56] Check SNMP counters.

Bu mesajı görüyorsanız, backlog kuyruğunuz taşmış demektir.

Kontrol etmek için yine ss imdadımıza yetişir:

1
ss -lnt

Çıktıdaki Recv-Q ve Send-Q sütunlarına dikkat! Normalde Recv-Q (Bekleyenler) 0 olmalıdır.

1
2
3
State    Recv-Q   Send-Q    Local Address:Port
LISTEN   0        128       0.0.0.0:80         (Normal Durum)
LISTEN   129      128       0.0.0.0:8080       (KRİTİK DURUM!)

İkinci satırda Recv-Q (129), Send-Q (Limit olan 128)’i geçmiş. Bu demektir ki kuyruk taşmış. Kernel artık bu porta gelen paketleri çöpe atıyor.

Çözüm:

  1. Uygulamanızın backlog parametresini artırın (Nginx’te listen 80 backlog=4096;).
  2. Kernel’ın global limitini artırın (sysctl -w net.core.somaxconn=4096).
  3. Veya uygulamanızın neden bu kadar yavaş accept() ettiğini (CPU bottleneck, Blocking I/O) araştırın.

🛠️ Network Debug Karar Matrisi

Kafalar karışmasın diye, işte size duvara asmalık bir yol haritası:

Belirti (Semptom)Olası Nedenİlk Bakılacak Araç/Komut
Connection RefusedServis çalışmıyor, Port Yanlış, veya Localhost Dinliyorsudo ss -lntp (Sunucuda servis var mı?)
Connection Timed OutFirewall (DROP kuralı), Routing Hatası, IP Yanlıştcpdump, traceroute, telnet
Ping var, Telnet yokFirewall (ICMP açık, TCP kapalı)nc -zv <ip> <port>
Anında Hata (No Route to Host)Routing tablosu bozuk / IP aynı subnet’te değilip route, ip addr
Bekliyor… Bekliyor…DNS Çözümleme yavaşlığı veya UDP engelidig, nslookup, strace (DNS sorgusunu gör)
Rastgele/Aralıklı Timeout’larBacklog Queue taşıyor, SYN Flood, Paket Kaybıss -lnt (Recv-Q kontrolü), dmesg
Çok Yavaş Bağlantı (SSH vb.)Reverse DNS TimeoutSunucu config (UseDNS no)
  • Eğer SSH bağlantısı çok yavaş kuruluyorsa, sunucu sizin IP’nizi Reverse DNS (PTR) ile çözmeye çalışıyordur. SSH sunucu konfigürasyonunda UseDNS no yaparak bu çileyi bitirebilirsiniz.

🧐 Teknik Detay: Reverse DNS (PTR) Nedir?

Normal DNS “isimden IP bulma” (google.com -> 1.2.3.4) işlemidir. Reverse DNS ise “IP’den isim bulma” (1.2.3.4 -> google.com) işlemidir. Sunucular bazen güvenlik için “Bana bağlanan bu IP gerçekten kim?” diye ters sorgu yapar. Eğer IP’nizin PTR kaydı yoksa, sunucu cevabın timeout’a düşmesini bekler. O meşhur “10 saniyelik SSH beklemesi” bundandır.

This post is licensed under CC BY 4.0 by the author.