Bölüm 66: Encapsulation Çözme
Ağlar'a “Sen”i Katmak kitabının bir bölümü: adres çubuğuna yazdığın tek bir isteğin peşinden bakırla fiberin fiziğine inip karşı taraftan geri dönen uzun bir teknik yolculuk.
Kısım 9: Karşı Tarafta ve Dönüşte (Bölüm 65-70)
Tüm bölümler
- Giriş
- Adres Çubuğundan İsteğe
- Paket Anahtarlamalı Yol
- İsimden Adrese
- DNS Kayıtları
- Önbellek, Tazelik ve Güven
- Port Numarası ve Soket
- Bağlantısız Taşıma: UDP
- Üç Adımlı El Sıkışma
- Simetrik Şifreleme
- Açık Anahtarlı Kriptografi
- TLS El Sıkışması
- Sertifika Zinciri
- HTTP Mesajının Anatomisi
- Durum, Çerez ve Oturum
- HTTP/2'den HTTP/3'e
- İçerik Dağıtım Ağları
- Diğer Uygulama Protokolleri
- Katmanlı Mimari
- Hizmet ve Protokol
- Encapsulation ve Başlıklar
- Paket Yakalama ve Çözümleme
- Ağ Interface Kartı
- Fiziksel Adresleme
- Hata Sezme ve CRC
- Adres Çözümleme: ARP
- Ortam Erişimi: CSMA/CD
- Switch'ler
- Ağ Topolojileri
- Sanal Yerel Ağlar
- Kablosuz Erişim: 802.11
- Radyo Kanalı ve Kapasite
- Kablosuz Ağ Güvenliği
- Hücresel Ağlar
- Spektrum ve Kuşaklar
- Hareketlilik ve Aktarma
- Adres Kiralama: DHCP
- Adresin Anatomisi
- Adres Tükenmesi ve IPv6
- Router Mimarisi
- Kuyruk Yönetimi
- Cihaz Yapılandırma
- Routing Protokolleri
- Kontrol Düzlemi ve SDN
- Ağ İzleme ve Yönetimi
- Alanlar Arası Routing
- Adres Çevirisi
- Ara Kutular
- Tünelleme ve IPsec
- Firewall'lar
- Ağ Saldırıları
- Operatör ve Metro Ağları
- Bitten Sinyale
- Sembol Hızı ve Kanal Kapasitesi
- Multiplexing Teknikleri
- İletim Ortamları
- Bakır Kablo Sonlandırma
- Fiber Sonlandırma
- Fiber Optik İletim
- Bakır Erişim Ağı: DSL
- DSL'de Hız ve Mesafe
- Pasif Optik Ağlar
- Kablo Erişim Ağları
- Kablo Modem ve CMTS
- Denizaltı Kabloları
- Veri Merkezi Ağları
- Encapsulation Çözme
- Güvenilir Aktarım
- Tıkanıklık Denetimi
- Trafik Çözümleme
- Gecikme Bütçesi
- Soket Programlama
- Eşten Eşe ve Protokol Tasarımı
- Uyarlanır Video Akışı
- Gerçek Zamanlı Ses ve Görüntü
- Kablosuz Sensör Ağlarının Mimarisi
- Sensör Ağlarında Ortam Erişimi
- Sensör Ağlarında Yönlendirme ve IP
- Zaman Eşitleme ve Konum Bulma
- Sensör Ağı Uygulamaları
- Mobil Kablosuz Sensör Ağları
- Son Söz
Paket rafın tepesindeki switch’ten geçip içerideki sunucunun kartına düştü. Makine onu henüz okumadı; okumak için önce soyması gerekiyor.
Gidişte takılan her zarf, gelişte ters sırayla açılıyor. Kartın elinde şimdilik bir bit dizisi var. Senin yazdığın satır oradan nasıl çıkacak?
Kart frame’i neye bakarak kabul ediyor?
Kablodan gelen bitler karta sırayla düşüyor. Frame’i baştan okumaya kalksan ilk göreceğin şey veri olmaz: önde 7 baytlık bir önsöz (preamble) ve 1 baytlık başlangıç işareti (start frame delimiter, SFD) duruyor. Bu 8 bayt frame uzunluğu hesabına girmiyor; işleri tek, alıcının saatini gelen bitlerin ritmine oturtmak.
23. bölümde kart kablodaki her frame’i en azından hedef adres alanına kadar okuyor, adres kendisine ait değilse gerisini okumadan bırakıyordu.
Frame’in sonunda ise 4 baytlık bir FCS alanı buluyorsun; gönderen onu yola çıkmadan önce yazmış oluyor. 24. bölümde döngüsel artıklık denetimi sezme ile düzeltme arasına bir sınır çekiyordu; alıcı burada aynı hesabı tekrarlayıp sonucu alandaki değerle karşılaştırıyor.
Sıraya dikkat et, çünkü bağlayıcı. Frame alındığında önce FCS sınanıyor; sonuç tutmuyorsa hedef MAC adresine hiç bakılmadan frame atılıyor. Adres elemesini bitler akarken yapılan bir hızlandırma olarak düşün; kabul kararını asıl veren şey sağlama.
Gönderene haber vermenin de bir yolu yok ve sebebi düşündüğünden basit: hasar görmüş olabilecek alanlardan biri Kaynak Adres alanının kendisi. Haberin gideceği adres de şüphelilerin arasında. Frame bu yüzden sessizce düşüyor.
İki sınamadan da geçen frame karta ait tampona yazılıyor; ancak ondan sonra üst katmanların gündemine girebiliyor. 22. bölümde ağ kartı kablonun ucunda duran ayrı bir işlemciydi; halka tampona düşenler işte bunlar. Elenen frame ise hiçbir yere yazılmıyor; makinenin geri kalanı onun geldiğini bile duymuyor. Sayaç tutmasan varlığından haberin olmaz: 53. bölümdeki ethtool -S çıktısının CRC hata satırları tam bu sessiz düşüşleri sayıyor. Bu kabul kararı, ters encapsulation zincirinin ilk halkası.
Her zarf açılırken aynı soruyu cevaplıyor
Kart zarfı atmadan önce bir alanı daha okuyor. Ethernet adreslerinin hemen ardından gelen 16 bitlik tür alanına bakıyor ve içeride hangi üst katman protokolünün durduğunu oradan öğreniyor: 0x0800 IPv4, 0x86DD IPv6, 0x0806 ARP. Alıcı paketi hangi yığına vereceğini buradan biliyor.
6. bölümde port numarasıyla tanıdığın demultiplexing’in burada her katmanda bir kez tekrarlandığını göreceksin. Sorulan şey her seferinde aynı kalıyor: içindeki nedir? Zarfların bu sırayla açılmasına ters encapsulation deniyor.
18. bölümdeki kargo benzetmesinde her görevli yalnızca kendi etiketine bakıyordu; soyulmada da her katman kendi etiketini okuyup içindekini bir üstüne uzatıyor ve üsttekinin etiketini yorumlamıyor.
20. bölümde encapsulation merdivenini inerken saydığın 18 baytlık sabit zarf burada ters yönde açılıyor; kartın kabul aralığı da o tablodan çıkıyor, 64 ile 1.518 bayt. Soyma bittiğinde kenara atılan başlık toplamı 58 bayt eder: 18 baytlık frame zarfı ve 20’şer baytlık IP ile TCP başlıkları. Tam boy bir frame’in %3,8’i. En küçük frame’de aynı hesap acımasız çalışıyor: 46 baytlık veri alanının 40 baytını iki başlık yiyor, uygulamaya 6 bayt kalıyor. Kablo tarafında buna 8 baytlık önsözle 12 baytlık frame arası boşluk da eklenince, 84 baytlık bir iş için uygulamaya yine 6 bayt kalıyor. Küçük paket göndermenin neden pahalı olduğunu bir kez bu sayının üstünde gör; bir daha unutmazsın.
IP başlığında aynı soruyu 8 bitlik Protokol alanı cevaplıyor. 49. bölümde süzme kuralları yazarken kullandığın numaralar, TCP için 6 ve UDP için 17, burada ikinci zarfı kimin açacağını söylüyor. Alan bir sayı taşıyor, protokolün adını değil.
Aynı soruyu üçüncü kez taşıma başlığında soruyorsun ve cevabı hedef port veriyor. Bu üçüncü cevap artık bir protokolü değil, o makinede çalışan tek bir uygulamayı gösteriyor.
Hedef uç sistem de tıpkı bir router gibi datagramı frame’in içinden çıkarıyor. Fark tek bir adımda: router onu yeniden sarıyor, hedef makine sarmıyor.
IPv4 başlığının kendi sağlaması 16 bit ve yalnızca başlığa uygulanıyor; veri alanı bu hesaba hiç girmiyor. TTL ile parçalanma alanları yolda değiştiği için sağlamayı her router yeniden doğrulayıp yeniden hesaplıyor (39. bölümdeki artımlı hesap). Yani hedefe varan sağlama değerini gönderen yazmamış oluyor; onu yolun son router’ı yazıyor. 38. bölümde IPv6’nın bu alanı neden hiç taşımadığını görmüştün.
Şimdi iki sağlamayı yan yana koyalım, çünkü güçleri hiç aynı değil. FCS 32 bitlik olduğu için 4.294.967.296 farklı sonuç üretiyor; bozulmuş bir frame’in doğru değeri rastgele tutturma olasılığı kabaca %0,000000023 kalıyor. Başlıktaki 16 bitlik alanda aynı olasılık %0,0015’e çıkıyor. Ucuz olan alan, kabloyu değil yolda değişen başlığı korumak için seçilmiş.
Datagram parçalanmışsa birleştirmeyi yalnızca burada göreceksin; ara router’lar parçaları taşıyor ama toplamıyor. Aynı kimlik değerini taşıyan parçalar geldikçe veri alanları tampondaki doğru konuma yerleşiyor. Birleşme, ofset sıfırdan başlayan kesintisiz bir dizi oluştuğunda ve MF (More Fragments) bayrağı sıfır olan parça geldiğinde tamamlanıyor. Bir parça hiç gelmezse zamanlayıcı doluyor ve eldeki bütün parçalar atılıyor; sekiz parçadan yedisi elinde olsa bile hiçbir işine yaramıyor.
Bir datagramı yolun sonuna getirmeyen üç sebep var ve üçü de kitap boyunca karşına çıktı: ömrünün dolması, tıkanıklık ve sağlamanın tutmaması.
Segment doğru sürece nasıl teslim ediliyor?
Transport layer makineleri değil süreçleri birbirine bağlıyor. Süreç, bir makinede çalışmakta olan bir program örneği; şu anda kendi makinende yüzlercesi birden koşuyor.
Yığındaki her katmanın kendi kimlik kavramı var ve üçünü de tanıyorsun: kart MAC adresiyle, düğüm IP adresiyle, uygulama da port numarasıyla anılıyor. Katmanlı mimarinin interface sözleşmesi gereği her kimlik kendi katmanının başlığında taşınıyor.
Soket bu kimliklerin üçünü tek adda topluyor: transport layer protokolü, IP adresi ve port numarası. MAC adresini o üçlünün içinde arama; frame çoktan soyuldu.
49. bölümde firewall’ın durum tablosunda gördüğün five-tuple burada da karşına çıkıyor: taşıma protokolü de kimliğin parçası ve TCP’nin protokol numarası 6. Somut vakada sunucu 203.0.113.10 üzerinde 443 numaralı portu dinliyor, istemci ise 198.51.100.7 üzerinden 51844 numaralı portla bağlanıyor.
UDP tarafında alıcının üç işi var: IP’den segmenti almak, sağlama alanını denetlemek ve mesajı soket üzerinden uygulamaya çıkarmak. Sağlamanın kapsamı taşıma başlığıyla sınırlı kalmıyor; 7. bölümdeki sözde başlık sayesinde UDP başlık alanlarının yanında IP adreslerini de içine katıyor.
Teslim noktasında bir tampon buluyorsun. Linux’ta TCP alıcı tamponu en az 4 KiB; 4 KiB’a 1.460 baytlık tam boy segmentten yalnızca 2,8 tanesi sığardı. Varsayılan ise 128 KiB ile, yani yaklaşık 90 segmentle başlıyor ve bağlantı hızlandıkça kernel onu belleğe göre megabaytlara kadar kendiliğinden büyütüyor (autotuning). Tamponun neden bu kadar büyümesi gerektiğini 68. bölümde hesaplayacağız. Bir de tuzak var: tamponu SO_RCVBUF soket seçeneğiyle elle ayarlarsan kernel o soket için otomatik büyütmeyi kapatıyor.
Tamponu iki uç birden işletiyor: transport layer içine yazıyor, uygulama da okuyarak boşaltıyor. Uygulaman yavaş okursa boş yer eriyor ve alıcı kalanı TCP başlığındaki pencere alanıyla (rwnd) karşıya bildiriyor.
Erimeyi çıplak gözle de görebilirsin. netstat -tan çıktısındaki Recv-Q sütunu, uygulamanın henüz almadığı veriyi bayt olarak gösteriyor:
$ netstat -tan
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 32120 0 203.0.113.10:443 198.51.100.7:51844 ESTABLISHEDSunucu yavaşladıkça o sütun büyüyor. Senin isteğin de bu satırın kuyruğunda bekliyor ve sunucu süreci onu oradan okuyor. Bugünün Linux’unda aynı bilgiyi ss -tan veriyor.
Aynı anda yüz bin kişi varsa?
Buraya kadar tek bir bağlantıyı izledik. Gerçek bir sunucuda o satırın yanında on binlerce satır daha var ve bu, kitabın şimdiye kadar sormadığı bir soruyu doğuruyor: tek bir makine bu kadar soketi nasıl taşıyor?
Girişte iki kuyruk var, ikisi de kernel’da. Gelen SYN paketleri yarı açık kuyruğa düşüyor; bu kuyruğun taşırılması 8. bölümde bir saldırı yöntemiydi, SYN çerezi de 50. bölümde onun ilacıydı. El sıkışma tamamlanınca bağlantı ikinci kuyruğa, kabul kuyruğuna geçiyor ve orada uygulamanın onu almasını bekliyor. Bu kuyruğun derinliğini uygulama listen() çağrısındaki değerle bildiriyor; Linux o değeri net.core.somaxconn ile sınırlıyor ve 5.4 sürümünden beri bu sınırın varsayılanı 4.096. Uygulama almakta geri kalırsa kuyruk dolar ve yeni bağlantılar sessizce düşer; istemci tarafında bu, “sunucu var ama cevap vermiyor” diye görünür.
Asıl soru kabul edildikten sonra başlıyor. En sade tasarım her bağlantıya bir iş parçacığı vermek: kod okunaklı olur, çünkü her parçacık kendi bağlantısını baştan sona takip eder. On bin bağlantıda çöküyor. Her parçacığın kendi yığını var ve kernel’ın on bin parçacık arasında sıra dağıtması işin kendisinden pahalıya geliyor. Bu duvara Dan Kegel’in 1999’da koyduğu ad hâlâ kullanılıyor: C10K problemi.
Çıkış yolu tasarımı tersine çevirmekten geçti: her bağlantıya bir parçacık verme, tek bir parçacığa bütün bağlantıları sor. Uygulama kernel’a “şu soketlerden hangisinde okunacak veri var?” diye soruyor ve yalnızca hazır olanlarla ilgileniyor. Buna olay döngüsü deniyor.
Sorunun nasıl sorulduğu ise otuz yılı aşkın bir iyileştirme hikâyesi ve tek bir kusurun etrafında dönüyor.
İlk yöntemler, 1983’ten kalan select ve ardından gelen poll, her turda bütün listeyi kernel’a veriyor ve kernel onu baştan sona tarıyordu. Yüz bin soketin durumunu sormak, yüz bin soketi tek tek yoklamak demekti; oysa o an belki üçünde veri vardı. Maliyet soket sayısıyla büyüyordu.
Bugünkü yöntem, Linux’ta epoll (2002), BSD’de kqueue (2000), listeyi bir kez kaydediyor ve her turda yalnızca hazır olanları döndürüyor. İş artık soket sayısıyla değil olay sayısıyla orantılı: doksan dokuz bin dokuz yüz doksan yedi sessiz soket hiçbir maliyet üretmiyor. nginx de Node.js de bu yöntemin üstünde çalışıyor. 2019’da gelen io_uring bir adım daha atıp sistem çağrılarının kendisini de kernel ile paylaşılan kuyruklara taşıyor.
Tek bir olay döngüsü de tek bir işlemci çekirdeğini doldurabiliyor. İkinci hamle aynı porta 6. bölümdeki SO_REUSEPORT ile birden çok bekleyen koymak: kernel gelen bağlantıları bunlar arasında kendisi dağıtıyor ve her işlemci çekirdeğine ayrı bir kabul kuyruğu düşüyor.
Kalan darboğaz veriyi kopyalamakta. Bir dosyayı ağa göndermek klasik yolla dört kopya istiyor: diskten kernel’a, kernel’dan uygulamaya, uygulamadan kernel’a, kernel’dan karta. Ortadaki iki kopya tamamen gereksiz: uygulama veriye bakmıyor bile. Modern işletim sistemleri onları atlayıp veriyi kernel’ın içinden doğrudan karta geçirebiliyor; yöntemin adı sıfır kopya, Linux’taki çağrının adı sendfile(). Statik dosya sunucularının bu kadar az işlemci harcamasının sebebi bu.
Son darboğaz ağ kartının kendisinde. Kart bütün gelen trafiği tek bir kesme hattından bildirirse, o kesmeyi işleyen tek bir çekirdek dolar ve makinenin geri kalanı boş kalır. Modern kartlar bu yüzden çok kuyruklu: gelen her paketin soket four-tuple’ından bir özet çıkarıp paketi o özete karşılık gelen kuyruğa koyuyorlar ve her kuyruk ayrı bir çekirdeğe bağlanıyor. Yöntemin adı alış tarafı ölçekleme (receive side scaling, RSS). 65. bölümdeki ECMP’nin özet hilesi burada da çalışıyor: aynı flow’un bütün paketleri hep aynı çekirdeğe düşüyor, böylece hem önbellek ısınıyor hem de sıra bozulmuyor.
Dördünün ortak noktası aynı: hiçbiri işi hızlandırmıyor, gereksiz işi kaldırıyor. Taranmayan liste, kopyalanmayan bayt, beklemeyen parçacık.
Özet
- Adres elemesi: elemeden geçen frame karta ait tampona yazılıyor, elenen frame ise hiçbir yere yazılmıyor.
- Sağlama önceliği: FCS tutmayan frame hedef MAC adresine hiç bakılmadan atılıyor; kabul kararının sahibi adres değil, sağlama.
- Sessiz düşüş: bozuk frame için gönderene haber gitmiyor, çünkü hasarlı olabilecek alanlardan biri Kaynak Adres alanının kendisi.
- Demultiplexing: tür alanı, Protokol alanı ve hedef portu aynı soruyu üç kez cevaplıyor: bu zarfın içindekini kim alacak?
- Teslim kimliği: segmenti doğru sürece ulaştıran şey adreslerle portların birleşimi; protokolle birlikte bu kimlik beş değere, five-tuple’a çıkıyor.
- Ölçek gereksiz işi kaldırmaktan geliyor: bağlantı başına bir iş parçacığı on binde çöküyor; olay bildirimi soket sayısı yerine olay sayısıyla çalışıyor, sıfır kopya ara kopyaları atlıyor, çok kuyruklu kart kesmeyi çekirdeklere dağıtıyor.
Sunucu süreci ilk satırı okudu ve yanıtı hazırladı. Bu sefer geri dönecek olan küçük bir istek değil, iki yüz parçaya bölünecek koca bir dosya. Parçalardan biri yolda kaybolursa gönderen bunu kimden öğrenecek?
67. bölüme devam et: Güvenilir Aktarım