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:
- Donanım ve çekirdek sürücüleri — Wi-Fi için
IO80211Family, ethernet içinIOEthernetFamilygibi IOKit aileleri. - BSD ağ yığını — IP, TCP, UDP, soket tamponu, paket filtreleme için
pf. - Network Extensions — içerik filtreleri, paket tüneli sağlayıcıları, DNS proxy'leri, yeni
nw_pathmakinesi. socket(2)syscall — en düşük kullanıcı alanı API'si; artık nadiren doğrudan çağrılır.- CFNetwork / Network.framework — bağlantılar, yollar ve dinleyiciler için modern C/Swift API.
- NSURLSession (
URLSession) — Foundation'ın HTTP/HTTPS istemcisi. Neredeyse her uygulama için varsayılan. - Uygulama seviyesi kütüphaneler — Alamofire, AFNetworking, gRPC-Swift, WebSocket kütüphaneleri, Apollo,
URLSession'ı saran her şey. - 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 portlarnetstat -rn— yönlendirme tablosupfctl -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ıylaproc_pidinfoAPI — Apple'ın kutsal yolu "bu PID hangi soketleri açık tutuyor?"dtracevedtruss— sistem çağrı seviyesi izleme (imzalanmamış komut dosyaları için SIP'in kapalı olmasını gerektirir)InstrumentsNetwork şablonu — soket çağrıları içindtraceprobları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 NWProtocolFramerolarak 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:
URLSessionDelegateveURLSessionTaskDelegate— her yönlendirmeyi, meydan okumayı ve tamamlanmayı görünURLSessionTaskMetrics— görev başına zamanlama dökümü: DNS, bağlantı, güvenli bağlantı, istek, yanıtURLSessionConfiguration.protocolClasses— testte her isteği günlüklemek için birNSURLProtocolalt sınıfı kaydedincom.apple.CFNetworkalt sistemiyleos_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.
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 (
URLSessionkaynak zaman aşımı için 60 saniye). - Gizli paralelliği kontrol edin.
maxConcurrentOperationCount = 1olanOperationQueue, 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 olaylarcom.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 debugSon 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ı:
| Belirti | Olası katman | İlk araç |
|---|---|---|
| Uygulama hiçbir ana bilgisayara ulaşamıyor | 2-3 (BSD / NE'ler) | ping, traceroute, systemextensionsctl list |
| Bir ana bilgisayar erişilemiyor | 2 (BSD / DNS) | dig, nslookup, dscacheutil -q host |
| Yavaş el sıkışma | 5-6 (TLS / URLSession) | URLSessionTaskMetrics, birleştirilmiş günlük |
| İstek cihazdan hiç ayrılmıyor | 5-6 (Network.framework) | birleştirilmiş günlük com.apple.network |
| Uygulama başına trafik bütçesi bilinmiyor | 4 (çekirdek sayaçları) | nettop, ova |
| Şüpheli hedef | 2-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şturma | 8 (görünüm) | Instruments Time Profiler |
Bunların hepsinde geri dönen birkaç alışkanlık:
- Her zaman
taskMetrics'i günlükleyin. Hatta sürümde. Ucuzdur ve daha sonra saatleri kurtarır. - Her giden isteği etiketleyin.
X-Request-Idgibi ö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. - 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.
- 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.
- 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.