Post

Ağ Var Ama Bağlantı Yok: 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.

Ağ Var Ama Bağlantı Yok: Linux'ta Network Sorunlarını Syscall Perspektifiyle Okumak

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, bilişim dünyasının en eski açmazlarından biridir: Ağ var ama bağlantı yok.

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. OSI katmanlarını ezberlemek gibi teorik sıkıcılıklara girmeden, Linux çekirdeğinin (Kernel) ağ trafiğini nasıl yönettiğini anlayacağız. connect, bind, accept gibi sistem çağrılarının (syscall) aslında ne anlama geldiğini, bir paketin neden reddedildiğini veya neden kaybolduğunu Kernel’ın kendi ağzından dinleyeceğiz.

Kemerlerinizi bağlayın. OSI Layer 4’e, Transport katmanının sisli vadisine iniyoruz.

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

Linux’ta ağ iletişimi (Network I/O), dosya I/O’suna ş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:

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.

  2. bind() (Hat Bağlatma): Diğer insanların sizi arayabilmesi için bir numaraya ihtiyacınız var. “Ben 192.168.1.5 IP adresindeki 80 numaralı dahili hatta (Port) bakacağım” dersiniz. Eğer o port doluysa, Kernel “Bu numara kullanımda (EADDRINUSE)” hatasını verir.

  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.

🕵️‍♂️ 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 (Kernel) 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: Görünmez Duvar ve UDP

Bazen connect çağrısını bile göremezsiniz. Uygulama “donar”. Ne hata verir, ne log basar. Öylece bekler.

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. Bu bir DNS Sorgusu. Program henüz hedef sunucuya (api.internal-service.local) bağlanmaya çalışmıyor bile. Önce “Bu ismin IP adresi ne?” diye soruyor.

DNS sunucusu (8.8.8.8 veya local DNS) cevap vermiyorsa, veya UDP trafiği firewall’da engellendiyse (UDP genelde unutulur), uygulamanız “DNS Resolution” aşamasında kilitlenir kalır. TCP Timeout süresi başlamaz bile çünkü ortada bir TCP bağlantısı yoktur!

💡 İ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.
  • 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.

🎧 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.

⚡ 6. “Busy Signal”: Backlog Queue Dolduğunda

“Black Friday” indirimi başladı. Sitenize binlerce kullanıcı akın ediyor. Load Balancer’a bakıyorsunuz: Sunucu ayakta. CPU kullanımı %50’lerde (yani henüz yanmıyor). RAM var. Ama kullanıcılar (veya LB) sürekli “Bağlantı Hatası” veya “Timeout” alıyor. Sunucuya girip curl localhsot yapıyorsunuz, o bile yavaş veya hata veriyor.

Burada socket, bind, listen üçlüsündeki listen(fd, backlog) parametresine dönelim. Kernel’da backlog parametresi, “Memur (Uygulama) işlem yaparken sırada bekleyebilecek maksimum kişi sayısı”dır.

Linux network yığını, bağlantı henüz accept() edilmeden önce (Half-Open Connection) ve kabul edildikten hemen sonra (Full Connection) bu paketleri bir kuyrukta tutar.

Eğer uygulamanız (örneğin Node.js veya Python Single Thread çalıştığı için) accept() çağrısını yeterince hızlı yapamazsa, Kernel gelen yeni bağlantıları bu kuyrukta (“SYN Queue” ve “Accept Queue”) bekletir.

Kuyruk dolduğunda ise Kernel acımasızdır: Gelen yeni SYN paketlerini sessizce düşürür (DROP). İstemci tarafında bu bir Timeout olarak algılanır. Siz uygulama loglarında (Nginx error log, App log) HİÇBİR ŞEY göremezsiniz. Çünkü paket uygulamaya hiç ulaşmamıştır! Kernel kapıdaki güvenlik görevlisi gibi “İçerisi çok kalabalık, giremezsin” demiştir ama bunu nezaketle (Refused) değil, görmezden gelerek (Drop/Timeout) yapmıştır.

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)

👋 Son Söz

Ağ sorunları, görünmez ve elle tutulamaz oldukları için korkutucudur. Kablonun içinden geçen elektriği göremezsiniz. Ama unutmayın, network trafiği sihir değildir. Sadece kuralları çok sıkı olan protokoller ve bayt yığınlarıdır. Kernel ise bu trafiğin en sadık, en dürüst muhasebecisidir. Ne gelirse kaydeder, ne giderse bilir.

Network ekibini aramadan önce terminali açın. strace ile uygulamanızın kulağına eğilip connect çağrısını dinleyin. ss ile portlarınızın durumunu kontrol edin. Ve nc ile port bazlı test yapın.

O zaman göreceksiniz ki, “Ağ var ama bağlantı yok” sorunu aslında çözülemez bir gizem değil, sadece Kernel’ın size anlatmaya çalıştığı bir hikayedir.

Linux sistem programlama serüvenimiz devam edecek. Bir sonraki durakta, Linux’un en vahşi, en tehlikeli ormanına gireceğiz: Memory Management (Bellek Yönetimi). RAM’in aslında nasıl çalıştığını, “Memory Leak”in neye benzediğini ve Kernel’ın seri katili OOM Killer‘ın kurbanlarını nasıl seçtiğini konuşacağız.

O zamana kadar, paketleriniz kaybolmasın, latency’niz düşük, uptime’ınız bol olsun.

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