Bloga dön
·12 dk okuma·productdevbook

macOS Ağ Katmanları: Geliştirici Rehberi

macOS ağ yığınında geliştiriciye yönelik bir tur: socket(2)'den Network.framework'e kadar her katmanda gözlemlenebilirlik notlarıyla birlikte.

  • Developer tools
  • macOS
  • Networking
  • Deep dive

Yavaş bir API çağrısının hatasını ayıklıyorsunuz. Sunucu günlükleri yanıtın 80 ms'de ayrıldığını söylüyor. Kullanıcı sayfanın dört saniye sürdüğünü söylüyor. Hat ile UI arasındaki yığında bir yerde, zaman kayboldu — ve "ağ" yararlı olamayacak kadar geniş bir cevaptır. Nereye bakacağınızı bilmek için, en altta BSD soket çağrısından en üstte uygulamanızın gerçekte kullandığı çerçeveye kadar macos network layers'ın zihinsel bir haritasına ihtiyacınız var.

Bu, bir paketin macOS'ta nasıl bir URLResponse haline geldiğine, her katmanın nerede günlüklediğine ve enstrümanlandığına ve her seviyede hangi araca uzandığınıza dair bir geliştirici turudur. Uzundur çünkü yığın iyi nedenlerle katmanlıdır ve hepsini anlamaya değer.

macos network layers, üstten alta

Aşağıdan yukarıya, macOS ağ yığını kabaca şöyle görünür:

  1. Donanım ve çekirdek sürücüleri — Wi-Fi için IO80211Family, ethernet için IOEthernetFamily gibi IOKit aileleri.
  2. BSD ağ yığını — IP, TCP, UDP, soket tamponu, paket filtreleme için pf.
  3. Network Extensions — içerik filtreleri, paket tüneli sağlayıcıları, DNS proxy'leri, yeni nw_path makinesi.
  4. socket(2) syscall — en düşük kullanıcı alanı API'si; artık nadiren doğrudan çağrılır.
  5. CFNetwork / Network.framework — bağlantılar, yollar ve dinleyiciler için modern C/Swift API.
  6. NSURLSession (URLSession) — Foundation'ın HTTP/HTTPS istemcisi. Neredeyse her uygulama için varsayılan.
  7. Uygulama seviyesi kütüphaneler — Alamofire, AFNetworking, gRPC-Swift, WebSocket kütüphaneleri, Apollo, URLSession'ı saran her şey.
  8. Görünüm katmanınız — sonunda yanıtı oluşturan SwiftUI, UIKit, AppKit görünümleri.

Çoğu uygulama geliştiricisi zamanını 6-8 katmanlarında geçirir ve aşağıdaki her şeyi "ağ" olarak değerlendirir. Bir şey bozulana kadar bu iyidir. Gerçek bir hatayı izlerken — TLS el sıkışması zaman aşımına uğruyor, istekler proxy auth'unda takılı, bir bağımlılıktan şüpheli trafik — macos network layers'ın her birinin ne sunduğunu ve nerede günlüklendiğini bilmeniz gerekir.

Alt yığın: BSD, Network Extensions, soketler

Alt üç programlanabilir katman sıkı bir şekilde birbirine uyar, bu yüzden onlara tek bir levha olarak bakmaya değer.

Katman 2: BSD yığını

Bir paket en0'a ulaştığında, çekirdek onu BSD ağ yığınından geçirir. Paketin sizin için olup olmadığına karar verir (hedef IP ve portu bir soketle eşleştirerek), pf'den (BSD paket filtresi, macOS güvenlik duvarı) geçirir ve alıcı soketin tamponuna kuyruğa alır.

Bu katman, tcpdump ve Wireshark'ın yaşadığı yerdir. Çekirdek henüz onlarla çok şey yapmadan ham paketleri gören BPF (Berkeley Packet Filter) cihazına dokunurlar.

sudo tcpdump -i en0 -n 'tcp port 443'

Yararlı BSD-yığın seviyesi araçları:

  • netstat -an — dinleyen soketler, kurulmuş bağlantılar, dinleyen portlar
  • netstat -rn — yönlendirme tablosu
  • pfctl -s rules — mevcut pf kural seti (güvenlik duvarını etkinleştirmediğiniz sürece genellikle boş)
  • ifconfig / networksetup -listallnetworkservices — arayüz durumu

IP/TCP katmanında bir sorun olduğundan şüphelendiğinizde burada enstrümanlarsınız: bir TCP yeniden iletim fırtınası, bir yol çırpınması, trafiği engelleyen bir pf kuralı.

Katman 3: Network Extensions

Network Extensions, üçüncü taraf uygulamaların ve Apple'ın kendisinin kext yazmadan (ki kullanımdan kaldırıldı) veri yolunu nasıl kancaladığıdır. Büyük kategoriler:

  • Content Filter Providers — akış meta verilerini görür ve izin/red kararı verir. Little Snitch ve LuLu bunu kullanır.
  • Packet Tunnel Providers — tam VPN tarzı tünelleme. Tailscale, WireGuard uygulamaları, NordVPN vb.
  • App Proxy Providers — uzak bir uç nokta üzerinden uygulama başına yönlendirme.
  • DNS Proxy Providers — DNS sorgularını çözünürlükten önce yakalar.

Trafiğiniz beklemediğiniz bir yere gidiyorsa, bir Network Extension daha olası suçlulardan biridir. Etkin uzantılar için systemextensionsctl list'i kontrol edin.

Katman 4: socket(2) ve arkadaşları

Klasik POSIX API'si: socket(), bind(), connect(), send(), recv(), close(). macOS'ta artık neredeyse hiç bu kodu doğrudan yazmazsınız — Apple yeni uygulamalar için bunu önermez çünkü bağlantı değişiklikleri için nw_path ile entegre olmaz, Happy Eyeballs (IPv4/IPv6 çift yığını) işlemez ve TLS'yi ücretsiz almaz.

Ancak hâlâ temeldir. Her yüksek seviye API sonunda socket()'i çağırır. Çekirdekten süreç atfı okuduğunuzda — proc_pidinfo aracılığıyla PROC_PIDFDSOCKETINFO ile veya /dev/bpf aracılığıyla — soket çağrılarıyla ayarlanan durumu okuyorsunuz.

Burada şunlarla enstrümanlarsınız:

  • lsof -i -P -n — sistemdeki her açık soket, PID ve süreç adıyla
  • proc_pidinfo API — Apple'ın kutsal yolu "bu PID hangi soketleri açık tutuyor?"
  • dtrace ve dtruss — sistem çağrı seviyesi izleme (imzalanmamış komut dosyaları için SIP'in kapalı olmasını gerektirir)
  • Instruments Network şablonu — soket çağrıları için dtrace problarını sarar

ova gibi uygulama başına bir bant genişliği izleyicisi bu katmanı okur. Çekirdeğin PID başına soket ve arayüz bayt sayaçlarını kabaca 1 Hz'de örnekler, her PID'in baytlarını üst uygulama paketine atar ve elde edilen zaman serisini yerel olarak saklar. Paket inceleme yok — baytlar çekirdeğin nettop'a sunduğu aynı sayaçlardan gelir.

Orta yığın: Network.framework ve URLSession

Çoğu uygulama seviyesi ağ kullanımı bu iki API'de oturur. Soyları aynıdır ancak farklı sorunları çözerler.

Katman 5: CFNetwork ve Network.framework

CFNetwork daha eski C API'sidir. Network.framework (2018'de tanıtıldı, Swift dostu) onun modern yerine geçendir ve Apple'ın yeni düşük seviye ağ kodu için önerdiği şeydir.

Network.framework size şunları verir:

  • Giden bir bağlantı için NWConnection (TCP, UDP, QUIC, TLS, özel protokoller)
  • Gelen bağlantıları kabul etmek için NWListener
  • Yol kullanılabilirliğini gözlemlemek için NWPathMonitor (Wi-Fi vs hücresel vs ethernet)
  • Bonjour keşfi için NWBrowser
  • NWProtocolFramer olarak birleştirilebilir TLS, proxy ve protokol yığınları

Eşler arası protokoller, özel ikili protokoller, multipath TCP yazıyorsanız veya macOS için sunucu yazılımı uyguluyorsanız doğrudan kullanırsınız. Tüm HTTP-şekilli her şey için, bir katman yukarı çıkarsınız.

Bu katmandaki enstrümanlama: NWConnection.stateUpdateHandler, yol değişikliklerini izlemek için NWPathMonitor ve com.apple.network birleştirilmiş günlük alt sistemi (birleştirilmiş günlükle ilgili daha fazlası aşağıda).

Katman 6: URLSession

Uygulamanız HTTP konuşuyorsa, neredeyse kesinlikle URLSession kullanıyorsunuzdur. HTTP/1.1, HTTP/2, HTTP/3 (QUIC) desteklenen yerlerde, TLS, yönlendirmeler, önbellekleme, çerezler ve kimlik doğrulama meydan okumalarını işler. Ayrıca trafiğini test için yakalayabilmeniz için NSURLProtocol ile entegre olur.

Buradaki en yararlı enstrümanlama kancaları şunlardır:

  • URLSessionDelegate ve URLSessionTaskDelegate — her yönlendirmeyi, meydan okumayı ve tamamlanmayı görün
  • URLSessionTaskMetrics — görev başına zamanlama dökümü: DNS, bağlantı, güvenli bağlantı, istek, yanıt
  • URLSessionConfiguration.protocolClasses — testte her isteği günlüklemek için bir NSURLProtocol alt sınıfı kaydedin
  • com.apple.CFNetwork alt sistemiyle os_log — CFNetwork'ün yaydığı birleştirilmiş günlük kanalı

URLSessionTaskMetrics az kullanılıyor. Size DNS çözünürlüğü vs TCP bağlantısı vs TLS el sıkışması vs istek iletimi vs sunucu işleme vs yanıt iletimi için görev başına ne kadar zaman harcandığını tam olarak söyler. Yavaş API çağrı probleminiz aslında yavaş bir el sıkışma ise, bunu kanıtlayan veri budur.

func urlSession(_ session: URLSession,
                task: URLSessionTask,
                didFinishCollecting metrics: URLSessionTaskMetrics) {
    for tx in metrics.transactionMetrics {
        print("DNS:", tx.domainLookupEndDate?.timeIntervalSince(tx.domainLookupStartDate ?? .distantPast) ?? 0)
        print("Connect:", tx.connectEndDate?.timeIntervalSince(tx.connectStartDate ?? .distantPast) ?? 0)
        print("TLS:", tx.secureConnectionEndDate?.timeIntervalSince(tx.secureConnectionStartDate ?? .distantPast) ?? 0)
    }
}

ova'yı eylemde görün

Bir bakışta görülebilir bir menü çubuğu bant genişliği izleyicisi — yerel, imzalanmış, ~3 MB.

Download for macOS

Yığının üstü: kütüphaneler ve görünüm katmanı

İki en yüksek katman, çoğu uygulama kodunun zamanını geçirdiği yerdir. Buradaki hatalar ağ sorunlarıyla karıştırması kolay olur.

Katman 7: uygulama seviyesi kütüphaneler

Çoğu uygulama, URLSession'ı Alamofire veya özel bir ağ modülü gibi bir şeyle sarar. Bu kütüphaneler genellikle günlüklemek için kancalayabileceğiniz bir istek tamamlandı sinyali gösterir. Ayrıca genellikle alt katmanlarda sorunları maskeleyebilen kendi yeniden deneme, kısma ve kuyruk mantığları vardır.

Hata ayıklarken:

  • Kütüphanenin otomatik yeniden denemeler yapıp yapmadığını kontrol edin — üç yeniden deneme, başarısız bir isteği günlüğe dört satır ve hatta dört kat bayt yapabilir.
  • Zaman aşımı yapılandırmasını kontrol edin. Varsayılanlar genellikle şaşırtıcıdır (URLSession kaynak zaman aşımı için 60 saniye).
  • Gizli paralelliği kontrol edin. maxConcurrentOperationCount = 1 olan OperationQueue, istekleri beklemediğiniz şekillerde sıralayacaktır.

Katman 8: görünüm katmanı

Bazen hata ağda hiç değildir. Yanıt 80 ms'de geldi ancak görünüm 3,9 saniye sürdü çünkü biri ana iş parçacığında senkron bir JSON kod çözmesi koydu veya düzen geçişi 800 hücreye basamaklandı. Instruments'ın Time Profiler'ı bunun için doğru araçtır — size ana iş parçacığı zamanının JSONDecoder.decode'ta mı yoksa UIView.layoutSubviews'da mı harcandığını gösterecektir.

Ders: ölçmediğiniz sürece ağı suçlamayın. URLSessionTaskMetrics artı bir Time Profiler izi çoğu "yavaş sayfa" tartışmasını 10 dakikada çözer.

Birleştirilmiş günlük

Her katmanı kesen birleştirilmiş günlüktür (os_log / Logger). Ağ ile ilgili alt sistemler şunları içerir:

  • com.apple.CFNetwork — URLSession seviyesi olaylar
  • com.apple.network — Network.framework olayları
  • com.apple.networkd — ağ arka plan programı olayları
  • com.apple.cfnetwork.NSURLConnection — eski NSURLConnection günlükleri

Ağ günlüğünü canlı okuma:

log stream --predicate 'subsystem == "com.apple.network"' --level debug

Son bir saatlik CFNetwork olaylarını okuma:

log show --last 1h --predicate 'subsystem == "com.apple.CFNetwork"'

Bu, sorun "isteğim cihazdan hiç ayrılmadı" olduğunda son derece yardımcı olur. Günlük size yolun karşılanmadığını, proxy auto-config komut dosyasının DIRECT döndürdüğünü veya TLS sunucu sertifikasının belirli bir neden koduyla reddedildiğini söyleyecektir.

Doğru aracı seçme ve birkaç alışkanlık

"Bu katmanda bir sorunum var, hangi araç?" için bir hile sayfası:

BelirtiOlası katmanİlk araç
Uygulama hiçbir ana bilgisayara ulaşamıyor2-3 (BSD / NE'ler)ping, traceroute, systemextensionsctl list
Bir ana bilgisayar erişilemiyor2 (BSD / DNS)dig, nslookup, dscacheutil -q host
Yavaş el sıkışma5-6 (TLS / URLSession)URLSessionTaskMetrics, birleştirilmiş günlük
İstek cihazdan hiç ayrılmıyor5-6 (Network.framework)birleştirilmiş günlük com.apple.network
Uygulama başına trafik bütçesi bilinmiyor4 (çekirdek sayaçları)nettop, ova
Şüpheli hedef2-3 (BSD / NE filtresi)lsof -i, içerik filtresi
Rastgele gövde bozulması6+ (HTTP)URLProtocol yakalayıcı, paket yakalama
Hızlı yanıttan sonra yavaş oluşturma8 (görünüm)Instruments Time Profiler
Uygulama başına bant genişliği görünümü
ova soket katmanında çekirdek sayaçlarını okur, yardımcı PID'leri üst uygulama altında katlar ve size canlı bir menü çubuğu oranı artı kaydırılabilir bir geçmiş verir — "bu hatta ağ mı?" yanıtlanması gereken ilk soru olduğunda faydalıdır.

Bunların hepsinde geri dönen birkaç alışkanlık:

  1. Her zaman taskMetrics'i günlükleyin. Hatta sürümde. Ucuzdur ve daha sonra saatleri kurtarır.
  2. Her giden isteği etiketleyin. X-Request-Id gibi özel bir başlık ayarlayın ve istemci günlük satırlarınıza ve sunucu günlük satırlarınıza koyun. Şimdi onları birleştirebilirsiniz.
  3. Etkinlik göstergesine güvenmeyin. Yalnızca bir isteğin uçuşta olduğunu gösterir, hangisinin veya ne kadar süredir beklediğini değil.
  4. Soğuk ve sıcağı ayrı profilleyin. Başlatmadan sonra ilk istek DNS, TCP, TLS ve muhtemelen HTTP/3 müzakeresine çarpar. Onuncu istek bağlantıyı yeniden kullanır ve 10-50 kat daha hızlıdır. Her iki sayı da önemlidir.
  5. Bağlantı birleştirmesine dikkat edin. HTTP/2 ve HTTP/3, sertifikayı paylaşan ana bilgisayarlar arasında tek bir bağlantıyı yeniden kullanacaktır. Bu, ana bilgisayar başına akıl yürütmeyi zorlaştırır.

Toparlarken

macos network layers yığını yoğundur ancak bilinebilir. En altta socket(2)'den en üstte URLSession ve görünüm katmanınıza kadar, her katman belirli bir mercek sunar — paketler, akışlar, istekler, görevler, çerçeveler. Doğru merceği seçin ve hatalar "ağ yavaş"tan "api.example.com'a TLS el sıkışması, sertifika zinciri her seferinde yeniden çekildiği için 1,2 saniye sürüyor"a daralır.

Bir terminalde nettop çalışmadan şu anda hangi uygulamanın bant genişliğinizi kullandığına dair gündelik, her zaman açık bir görünüm istiyorsanız, ova'yı yükleyin. Bir menü çubuğu uygulamasıdır, yaklaşık 3 MB, macOS 14 ve sonrasında çalışır, yaklaşık 1 Hz'de örnekler ve her şeyi yerel olarak saklar. Telemetri yok, uzak pano yok, tek seferlik ödeme.