Ana içeriğe geç

Bölüm 15: HTTP/2'den HTTP/3'e

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 3: İstek Yazılıyor (Bölüm 13-17)

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

Bir sayfa yüzlerce parçadan oluşuyor ve her parça için yolu baştan yürümek eski çözümdü. İstek satırını ve durum kodlarını açarken, tek bağlantıya kaç isteğin sığdığını tek cümleyle bırakmıştım.

Borcu burada ödüyorum. Aynı yolun üstünde kaç araba var ve arkadaki neden bekliyor?

Bağlantıyı açık bırakmak neyi kurtarıyor?

Kalıcı olmayan kipte bir taşıma bağlantısı en çok tek bir nesne taşıyordu. Klasik anlatım dört adımdı: bağlan, ne istediğini söyle, cevabı al, bağlantıyı kes. Dördü de her nesnede baştan tekrarlanıyordu.

Bedeli RTT’yle ölçebilirsin. Kalıcı olmayan kipte her nesne iki tur yiyor: biri bağlantı kurulumu, biri istek ve yanıtın ilk baytları. 8. bölümde üç adımlı el sıkışmanın bedelini sayarken bu turun ilkini adım adım görmüştük.

Sayıya dökelim. 60 bağlı nesne taşıyan bir sayfa düşün ve RTT 40 ms olsun. Bağlantılar teker teker açılırsa yalnızca kurulum ve istek turları 4.800 ms tutuyor, yani 4,8 saniye. Dosya aktarım süreleri bu hesaba daha hiç girmedi; sayfa açılmadan önce sırf yolu kurmak için beş saniyeye yakın bekliyordun.

Ödediğin şey yalnızca gecikme de değil. Her yeni bağlantı işletim sistemi tarafında tampon, durum kaydı ve kontrol bloğu istiyor; bunların hepsi altmış kez birden kuruluyor.

HTTP/1.1 varsayılanı tersine çevirdi. RFC 9112’de kalıcı bağlantı varsayılandır: sunucu yanıtı gönderdikten sonra bağlantıyı açık bırakıyor ve aynı bağlantıdan birden çok istek ile yanıt geçiyor. Varsayılandan çıkmak için Connection başlığına close yazman gerekiyor. Kapatmak artık istisna.

Kazanç burada görünüyor. Kalıcı bağlantı kurulum turunu bir kereye indiriyor: aynı sayfa bir kurulum artı altmış istek, yani 61 tur ve 2.440 ms sürüyor. Süre yarıya indi ama her nesne hâlâ kendi turunu bekliyor, çünkü istemci bir yanıtı almadan sıradaki isteği göndermiyor.

Sıra zorunluluğu kimi bekletiyor?

HTTP/1.1 bu yüzden bir de boru hattını (pipelining) getirdi. İstemci yanıtı beklemeden istekleri arka arkaya gönderebiliyor. Altmış isteğin hepsi aynı turda yola çıkarsa sayfa iki gidiş-dönüşe, yani 80 ms’ye iniyor: biri kurulum, öbürü bütün nesneler için. İlk hesaba göre fark 60 kat. Sunucu güvenli metotları paralel işleyebilir ama yanıtları isteklerin geldiği sırayla göndermek zorunda.

Zorunluluğun sebebi sade. HTTP/1.1’de istekleri yanıtlarla eşleştiren bir kimlik alanı yok; tek dayanak varış sırası. Protokolün tek varsayımı da güvenilir ve sırayla teslim eden bir taşıma. HTTP/1.0’da durum daha dardı: bir bağlantıda aynı anda yalnızca tek bir istek açık kalabiliyordu.

Sonuç şu: küçük bir nesne, önündeki büyük nesnenin arkasında beklemek zorunda kalıyor. Buna baş engellemesi (head-of-line blocking) deniyor. Burada gördüğün, application layer’daki baş engellemesi. Boru hattı standartta yer aldığı hâlde tarayıcıların hiçbiri onu varsayılan olarak açmadı; bu engelleme ve yoldaki hatalı proxy’ler kazancından fazla sorun çıkarıyordu.

tek bağlantı, yanıtlar isteklerin geliş sırasında
  istekler :  A ---> B ---> C
  yanıtlar :  [============ A ============][ B ][ C ]
                                            ^ B hazırdı, A bitene kadar bekledi

2. bölümde paket anahtarlamayı ve kuyruk gecikmesini otoyol şeritleriyle anlatmıştım; tek şeritte önündeki aracı geçemezsin. Tarayıcılar bu şeritten kaçmak için aynı sunucuya paralel bağlantılar açıyor. RFC 9112 kaçışın sebebini açıkça yazıyor. Standardın eski sürümü paralel bağlantı sayısını ikiyle sınırlıyordu. O tavan kaldırıldı, çünkü pek çok uygulama için pratik değildi; yerine ölçülü olma öğüdü kaldı. Tarayıcılar bugün aynı sunucuya en fazla altı bağlantı açıyor.

Ölçülü olmayan taraf kazanıyor. 9 bağlantının paylaştığı bir hatta tek bağlantı isteyen onda bir pay alıyor, 11 bağlantı açan ise hattın yarısından fazlasını alıyor. Tıkanıklık denetimini polisleyen kimse yok. Dahası, tıkanık olmayan bir ağda bile eşzamanlı başlayan gönderimler, daha az bağlantıyla hiç oluşmayacak bir tıkanıklık yaratabiliyor.

Frame’e bölmek yetiyor mu?

HTTP/2 ilk olarak 2015’te RFC 7540 ile yayımlandı; yürürlükteki belgesi RFC 9113 oldu. Metotlar, durum kodları ve başlık alanlarının çoğu HTTP/1.1’den olduğu gibi kaldı. Değişen anlam değil, taşıma biçimi.

İkili framing geldi. Nesneler frame’lere bölünüyor ve frame’ler iç içe geçirilerek gönderiliyor; küçük nesne artık büyüğün bitmesini beklemiyor. Gönderim sırası da geliş sırasına göre değil, istemcinin bildirdiği önceliğe göre kurulabiliyor. Başlık alanları ayrıca HPACK adlı bir yöntemle sıkıştırılarak kodlanıyor.

HTTP/2 bir yenilik daha getirmişti: sunucu itmesi (server push). Sunucu, tarayıcının birazdan isteyeceğini tahmin ettiği nesneleri istenmeden gönderebiliyordu. Pratikte tarayıcının önbelleğinde zaten duran nesneleri de yeniden yolladığı için kazançtan çok israf üretti ve Chrome 2022’de desteği kaldırdı.

Paralellik sayıyla korunuyor. SETTINGS_MAX_CONCURRENT_STREAMS için önerilen alt sınır 100 eşzamanlı stream ve gerekçesi açık: paralelliği gereksiz yere kısmamak. Her stream 65.535 baytlık bir başlangıç penceresiyle açılıyor. Pencere, alıcının “şu kadar baytı onaylamadan gönderme” demesinin yoludur ve adı flow control (akış denetimi); hızlı bir göndereni yavaş bir alıcının tamponunu taşırmaktan alıkoyar.

İş burada bitmiyor. HTTP/2 application layer’daki baş engellemesini çözüyor, transport layer’dakini çözmüyor. RFC 9113 bunu kendi giriş bölümünde açıkça yazıyor. Sebebi tek cümle: tek taşıma bağlantısı verinin sırayla teslim edilmesini garanti ediyor. Kaybolan ya da sırası bozulan tek bir paket, o paketle hiç ilgisi olmayan bütün işlemleri de durduruyor.

Multiplexing’i application layer’a koydular ama alttaki taşımanın kayıp kurtarma mekanizması onu göremiyor. Tarayıcıların hâlâ paralel bağlantı açma teşviki buradan geliyor.

Taşımanın kendisi neden değişti?

Çıplak bir taşıma bağlantısı kendi başına hiçbir güvenlik getirmiyor. Şifreleme ayrı bir katmandan geliyor ve ayrı bir el sıkışma istiyor. Eski kurulumda iki el sıkışma arka arkaya çalışıyordu: önce taşımanın güvenilirlik ve tıkanıklık durumu, sonra güvenlik katmanının kimlik ve anahtar durumu. Biri bitmeden öbürü başlamıyordu.

HTTP/3 anlamları hiç değiştirmiyor. RFC 9114 aynı HTTP anlamlarını yeni bir taşıma protokolünün üstüne taşıyor: QUIC. Yığın şöyle diziliyor: HTTP/3, altında QUIC, onun altında UDP, onun altında IP.

QUIC yeni bir fikir de icat etmiyor. Bağlantı kurulumu, hata denetimi ve tıkanıklık denetimi için bilinen taşıma çözümlerini uygulama tarafına taşıyor. Spesifikasyon algoritmaların koşut olduğunu kendisi söylüyor.

Asıl fark akışlarda. QUIC’te stream multiplexing’i ve stream başına flow control taşıma protokolünün kendi işi. Güvenilir aktarım stream başına ayrı tutuluyor, tıkanıklık denetimi ise ortak: bütün stream’ler tek bir bütçeyi paylaşıyor. Paket kaybında yalnızca o pakette verisi bulunan stream’ler yeniden gönderimi bekliyor. Ötekiler yoluna devam ediyor.

Üç kuşağı aynı kayıp üstünde karşılaştırmak istersen:

Aynı kayıp, üç kuşakta üç sonuç
HTTP/1.1HTTP/2HTTP/3ABCAABBCCAABBCCB ve C, A bitene kadar bekliyor — engelleme uygulamadakayıpkayıp segment üçünü birden durduruyor — engelleme taşımadakayıpyalnızca C bekliyor; A ve B akmaya devam ediyor
HTTP/1.1: sıra zorunluluğu. Tek bağlantıda yanıtlar isteklerin geldiği sırayla dönmek zorunda, çünkü hangi yanıtın hangi isteğe ait olduğunu söyleyen bir kimlik alanı yok. B hazır olsa bile A bitene kadar bekliyor.
  1. HTTP/1.1: sıra zorunluluğu. Tek bağlantıda yanıtlar isteklerin geldiği sırayla dönmek zorunda, çünkü hangi yanıtın hangi isteğe ait olduğunu söyleyen bir kimlik alanı yok. B hazır olsa bile A bitene kadar bekliyor.
  2. HTTP/2: aynı bağlantıda üç akış birden. Her nesne kendi akış kimliğini taşıyan frame'lere bölünüyor ve frame'ler karışık akabiliyor. Application layer'ındaki engelleme kalkıyor: B artık Ayı beklemiyor.
  3. Ama engelleme yok olmadı, bir kat aşağı indi. Üç akış tek bir TCP bağlantısında. Bir segment kaybolduğunda TCP sırayı korumak için bütün akışları bekletiyor; kaybolan frame Cye ait olsa bile A ve B de duruyor. Transport layer akışların varlığından habersiz.
  4. HTTP/3: sıra her akışta ayrı tutuluyor. QUIC akış kavramını transport layer'ına indiriyor ve her akış kendi sıra numarasını taşıyor. Kaybolan paket yalnızca kendi akışını bekletiyor; A ile B akmayı sürdürüyor.

Ayrım paket sınırında duruyor. Multiplexing, farklı stream’lerin STREAM frame’lerini aynı QUIC paketlerine iç içe koyuyor. Tek pakete birden çok stream’in verisi konabiliyor. O paketin kaybı hepsini birden durduruyor. Gerçekleştirimlere pakete gereğinden fazla stream koymamaları öğütleniyor.

DerinleşmeQUIC neden kernel'a değil, UDP üstüne kuruldu?

Cevap teknik olmaktan çok dağıtımla ilgili.

Klasik bir taşıma protokolü kernel’ın içinde yaşar. Yeni bir taşıma yazmak, dünyadaki bütün işletim sistemlerinin onu göndermesini beklemek demek. Üstelik yoldaki kutular, yani adres çeviren NAT’lar (46. bölüm) ve firewall’lar, tanımadıkları bir protokol numarasını geçirmiyor; 2000’de standartlaşan SCTP’nin geniş internete hiç yayılamamasının sebebi buydu. İki klasik taşıma protokolü 40 yıl boyunca alanın tamamını tuttu; sebebi rakipsiz olmaları değil, rakibin oraya girememesiydi.

QUIC kilidi kırmak için taşımayı kernel’ın dışına çıkardı. Altta kalan tek şey UDP ve UDP her yerde zaten var: yeni bir kernel sürümü de yeni bir soket ailesi de gerekmiyor.

Bunun pratik karşılığı yayılma hızı. QUIC önce Google sunucularında, Chrome’da ve mobil YouTube uygulamasında çalıştı; standartlaşması sonradan geldi. Bir tarayıcı güncellemesi, bir sunucu güncellemesi ve yeni taşıma sahada. Aynı işi kernel üstünden yapmak yıllar alırdı.

Bedeli de var. Uygulama tarafında çalışan bir taşıma, kernel’ın yıllarca ayarlanmış kodundan ve donanım hızlandırmalarından mahrum kalıyor. Şifreleme her pakette çalışıyor ve bu işlemci zamanı kullanıcının makinesinden çıkıyor.

El sıkışma da tek tura indi. QUIC, TLS 1.3’ü transport layer’ının içine aldı; güvenilirlik, tıkanıklık denetimi, kimlik doğrulama ve şifreleme durumu tek el sıkışmada birlikte kuruluyor. İstemci daha önce sakladığı oturum biletini kullanırsa el sıkışma gecikmesi sıfıra iniyor ve uygulama verisi ilk pakette gidiyor. O biletin bedelini biliyorsun: tekrar oynatma saldırısına karşı koruma yok. RFC 9114 bu yüzden karşı önlemleri zorunlu tutuyor.

Bir kazanç daha var. QUIC bağlantısı adresle değil, bağlantı kimliğiyle tanınıyor ve bu kimlik QUIC sürüm 1’de en çok 20 bayt olabiliyor. Uç noktanın adresi ve portu değişse bile bağlantı yaşamaya devam ediyor; buna bağlantı göçü deniyor. Değişim her zaman kasıtlı da değil; aradaki NAT kutusu yeni bir giden port atadığında da oluyor. Her iki durumda da yeni yolun doğrulanması gerekiyor; doğrulama başarısızsa yalnızca o yol kullanılmıyor. Göçü yalnızca istemci başlatabiliyor, üstelik el sıkışma doğrulanmadan başlatamıyor.

HTTP/3 tarafında eşleme sade. Her istek, istemcinin açtığı çift yönlü bir QUIC stream’inde gidiyor ve bir stream’de yalnızca tek bir istek gönderilebiliyor. İkinci istek yeni stream demek. Stream kimliği 62 bitlik bir tam sayı; en düşük bit stream’i kimin açtığını, ikinci bit tek yönlü mü çift yönlü mü olduğunu söylüyor. İki bit dört stream türü üretiyor.

HTTP/3 mesajının parçaları        frame      tür kodu
  başlık bölümü                   HEADERS      0x01
  gövde                           DATA         0x00
  kuyruk alanları (trailers)      HEADERS      0x01
  bağlantı ayarları               SETTINGS     0x04
  kapanış duyurusu                GOAWAY       0x07

Gövdesi olmayan bir yanıtta DATA frame’i hiç gönderilmiyor; gövde 0 bayt ise ortada frame de yok. Her uç ayrıca en az 3 tek yönlü stream’e izin vermek zorunda: bir HTTP denetim stream’i ve başlıkları sıkıştıran QPACK’in iki stream’i. Bu stream’lere en az 1.024 baytlık flow control kredisi verilmesi öğütleniyor.

Bunu kendi makinende görebilirsin. Sürüm seçimi TLS el sıkışmasında ALPN uzantısındaki h3 belirteciyle bildiriliyor, sunucu da desteğini Alt-Svc yanıt başlığıyla duyuruyor. HTTP/3 için zorunlu tek bir UDP portu yok; duyuruda port bu yüzden açıkça yazılıyor.

Alt-Svc’nin kusuru ilk bağlantıda görünüyor: duyuru bir yanıt başlığında geldiği için tarayıcı onu ancak eski protokolle bir tur attıktan sonra öğreniyor. 4. bölümde DNS kayıtlarını sayarken andığım HTTPS kaydı o turu siliyor. Kayıt, DNS cevabının içinde “bu sunucu h3 konuşuyor” diyor ve tarayıcı daha ilk bağlantısını QUIC ile açıyor.

curl -sI https://cloudflare-quic.com/ | grep -i '^alt-svc'
# alt-svc: h3=":443"; ma=86400

Sonra trafiği dinle. İlk QUIC datagramı en az 1.200 bayt olmak zorunda ve gerekirse PADDING frame’leriyle şişiriliyor; sebebi yükseltme saldırısını sınırlamak. Yol o boyu taşıyamıyorsa QUIC hiç kullanılmıyor. QUIC en küçük IP paketini 1.280 bayt varsayıyor. IPv6’da bundan 40 baytlık IP başlığı ile 8 baytlık UDP başlığı düşülünce her yolda taşınacağı garanti edilen UDP yükü 1.232 bayt çıkıyor. IPv4 başlığı daha kısa olduğu için orada bu sayı 1.252 bayt.

sudo tcpdump -n -c 5 'udp port 443'

QUIC de 443’ü kullanıyor, ama UDP üstünde. UDP’nin engellendiği bir ağda hiçbir şey göremezsin, çünkü orada QUIC bağlantısı hiç kurulamıyor. Standart bu durumda istemcinin eski sürümlere düşmesini bekliyor.

Özet

  • Kalıcı bağlantı varsayılan oldu: sunucu yanıttan sonra kapatmıyor, kapatmak için Connection başlığına close yazmak gerekiyor.
  • Baş engellemesi sıra zorunluluğundan doğuyor: istekleri yanıtlarla eşleştiren bir kimlik alanı olmayınca tek dayanak varış sırası kalıyor.
  • HTTP/2 veriyi frame’lere böldü ama taşımayı değiştirmedi: iç içe geçen frame’ler application layer’ı kurtarıyor, kaybolan tek paket yine hepsini durduruyor.
  • QUIC güvenilirliği stream başına ayırdı: kayıp yalnızca o pakette verisi olan stream’leri bekletiyor, el sıkışma ise tek tura iniyor.

Aynı yolun üstünde artık birden çok araba var ve hiçbiri ötekini beklemiyor. İsteğin yola çıktı; peki gerçekten adres çubuğuna yazdığın makineye mi gidiyor?

16. bölüme devam et: İçerik Dağıtım Ağları