Ana içeriğe geç

Bölüm 66: Encapsulation Çözme

9 dakikalık okumaGüncelleme:

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
  1. Giriş
  2. Adres Çubuğundan İsteğe
  3. Paket Anahtarlamalı Yol
  4. İsimden Adrese
  5. DNS Kayıtları
  6. Önbellek, Tazelik ve Güven
  7. Port Numarası ve Soket
  8. Bağlantısız Taşıma: UDP
  9. Üç Adımlı El Sıkışma
  10. Simetrik Şifreleme
  11. Açık Anahtarlı Kriptografi
  12. TLS El Sıkışması
  13. Sertifika Zinciri
  14. HTTP Mesajının Anatomisi
  15. Durum, Çerez ve Oturum
  16. HTTP/2'den HTTP/3'e
  17. İçerik Dağıtım Ağları
  18. Diğer Uygulama Protokolleri
  19. Katmanlı Mimari
  20. Hizmet ve Protokol
  21. Encapsulation ve Başlıklar
  22. Paket Yakalama ve Çözümleme
  23. Ağ Interface Kartı
  24. Fiziksel Adresleme
  25. Hata Sezme ve CRC
  26. Adres Çözümleme: ARP
  27. Ortam Erişimi: CSMA/CD
  28. Switch'ler
  29. Ağ Topolojileri
  30. Sanal Yerel Ağlar
  31. Kablosuz Erişim: 802.11
  32. Radyo Kanalı ve Kapasite
  33. Kablosuz Ağ Güvenliği
  34. Hücresel Ağlar
  35. Spektrum ve Kuşaklar
  36. Hareketlilik ve Aktarma
  37. Adres Kiralama: DHCP
  38. Adresin Anatomisi
  39. Adres Tükenmesi ve IPv6
  40. Router Mimarisi
  41. Kuyruk Yönetimi
  42. Cihaz Yapılandırma
  43. Routing Protokolleri
  44. Kontrol Düzlemi ve SDN
  45. Ağ İzleme ve Yönetimi
  46. Alanlar Arası Routing
  47. Adres Çevirisi
  48. Ara Kutular
  49. Tünelleme ve IPsec
  50. Firewall'lar
  51. Ağ Saldırıları
  52. Operatör ve Metro Ağları
  53. Bitten Sinyale
  54. Sembol Hızı ve Kanal Kapasitesi
  55. Multiplexing Teknikleri
  56. İletim Ortamları
  57. Bakır Kablo Sonlandırma
  58. Fiber Sonlandırma
  59. Fiber Optik İletim
  60. Bakır Erişim Ağı: DSL
  61. DSL'de Hız ve Mesafe
  62. Pasif Optik Ağlar
  63. Kablo Erişim Ağları
  64. Kablo Modem ve CMTS
  65. Denizaltı Kabloları
  66. Veri Merkezi Ağları
  67. Encapsulation Çözme
  68. Güvenilir Aktarım
  69. Tıkanıklık Denetimi
  70. Trafik Çözümleme
  71. Gecikme Bütçesi
  72. Soket Programlama
  73. Eşten Eşe ve Protokol Tasarımı
  74. Uyarlanır Video Akışı
  75. Gerçek Zamanlı Ses ve Görüntü
  76. Kablosuz Sensör Ağlarının Mimarisi
  77. Sensör Ağlarında Ortam Erişimi
  78. Sensör Ağlarında Yönlendirme ve IP
  79. Zaman Eşitleme ve Konum Bulma
  80. Sensör Ağı Uygulamaları
  81. Mobil Kablosuz Sensör Ağları
  82. 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   ESTABLISHED

Sunucu 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