İki yol bir Android uygulamasının önüne bir IP arayüzü koyabilir ve ikisi de sahipleri çok farklı olan aynı tür dosya tanımlayıcısında biter. Biri VpnService ve builder'ı üzerinden, diğeri bir root kabuğu ve /dev/net/tun üzerinden geçer. O tanımlayıcıdaki her okuma giden bir pakettir, her yazma gelen bir pakettir. Yolları ayıran şey politikayı kimin tuttuğu, bir OS yükseltmesini kimin atlattığı ve yönlendirme tablosunun ne kadarını değiştirebildiğindir.

Reddettiği belirli şeyi adlandıramıyorsan platform yolunu seç. Bu ret genellikle TAP, kendi uygulama kümenin dışında yakalama ya da framework'ün asla açığa çıkarmadığı bir yönlendirme kuralıdır.

Platform yolunda komutlar yerine niyet bildirirsin. En az bir adres, istediğin rotalar, DNS sunucuları ve isteğe bağlı bir uygulama listesi verirsin, sonra Android'in sistem hizmeti ayrıcalıklı kurulumu kendi kimliğiyle yapar. Arayüz adını asla önceden öğrenmezsin ve onu oluşturan ioctl'ü asla yayınlamazsın, ve asıl mesele budur.

IP arayüzü elde etmenin iki yolu

Root yolu daha eski tariftir. /dev/net/tun'u açarsın, IFF_TUN ile TUNSETIFF çağırırsın, arayüzü ip link ile kaldırırsın ve adresleri ve rotaları elle eklersin. Her adım doğru yapmak senin işindir, framework'ün sessizce halledeceği adımlar dahil. Eksik tek bir rota kullanıcıya ölü sunucuyla aynı raporu verir.

Hata ayıklama da kurulum kadar farklıdır. Root yolunda arayüz ya vardır ya da ioctl'in doğrudan okuyabileceğin bir hata döndürmüştür. Platform yolunda arayüz ancak establish() başarılı olduktan sonra vardır, bu yüzden hata bir cihaz düğümü yerine istisna ya da null tanımlayıcı olarak gelir, kurcalayabileceğin bir şey değildir.

establish() gerçekte ne döndürür

Builder.establish() üç sonuç üretir ve her biri farklı bir yanıt ister. Tanımlayıcı arayüzün yapılandırıldığını ve kullanımının senin olduğunu, null isteğin geçmediğini, fırlatılan istisna ise sistem hizmetinin onu reddettiğini gösterir. null ile başarıyı tek bir boolean'da birleştirmek, bir uygulamanın hiç paket taşımayan çalışan bir tünel bildirmesinin en yaygın yoludur. Builder başvurusu adres gereksinimini belirtir ve sunucuyu suçlamadan önce okumaya değer.

Yönleri tanımlayıcı belirler, protokolün değil. Okumalar sana VPN için zaten seçilmiş giden paketleri verir, yazmalar gelen paketleri arayüze geri enjekte eder. Bu paketleri şifrelemek, doğrulamak ve çerçevelemek senin kendi taşıma katmanının işidir, çünkü platform paket sınırında durur.

Binder bir adımdır, JNI başka bir eksendir

Bir builder isteği Binder üzerinden sisteme girer ve bunun altında bir yerde platform cihazı yapılandırmak için yerel kod çağırır. Bunlar tek bir çağrının içine yığılmış iki farklı mekanizmadır. Binder uygulamanla bir sistem hizmeti arasındaki süreçler arası iletişimdir, JNI ise bir süreç içinde Java'dan yerel koda köprüdür. Bir Android 9 AOSP ağacını okumak yığını gerçek görünümlü yapar ve iç sınıf adları ile dosya yollarının sürümler arasında yer değiştirdiğini hatırlamakta yarar var.

Yer değiştirmeyen şey güvenebileceğin sözleşmedir: builder, döndürdüğü tanımlayıcı ve etrafındaki hizmet yaşam döngüsü. Hiçbir uygulama arayüz almak için iç bir hizmet işlemi çağırmamalı ya da cihaz düğümünü yeniden oluşturmamalıdır. Kodun bir AOSP sınıf adına bağlıysa test etmediğin bir sürümde kırılır.

TUN IP taşır, TAP Ethernet taşır

TUN bir IP arayüzüdür. TAP bir Ethernet arayüzüdür ve bu fark köprülemenin mümkün olup olmadığını belirler. Bir TUN cihazının MAC adresi yoktur ve iletecek Ethernet başlığı yoktur, bu yüzden bir TUN istemcisi rotalar nasıl yazılırsa yazılsın katman 2 köprüsüne katılamaz.

VpnService sana IP varyantını verir, uygulama düzeyinde bir VPN'in ihtiyaç duyduğu ve ham Ethernet yığını olmadan destekleyebildiği şey budur. Tasarımın köprüleme gerektiriyorsa platform yolu seni oraya götürmez, root yolu ise ancak çekirdek ve cihaz izinleri işbirliği yaparsa götürür.

Root yolunun hâlâ kazandığı yer

Üç iş insanları platform yolundan iter. Katman 2 köprülemesi TAP gerektirir. Kendi VPN kapsamın dışındaki uygulamaların trafiğini yakalamak framework'ün vermediği görünürlüğü gerektirir. Builder'ın asla açığa çıkarmadığı politika yönlendirmesi, örneğin tek bir hedef için ayrı tablo, kendi kurduğun kuralları gerektirir.

Maliyetler sonra gelir: kilidi açılmış bir bootloader ya da rootlanmış bir imaj, altında kayan SELinux politikası ve OTA'dan sonra doğru davranmayı bırakan bir cihaz. Rootlanmış tarif normal uygulama dağıtımıyla da sunulamaz, bu yüzden çoğu ekip onu ürün yerine iç teşhis yolu olarak tutar.

İkisi arasında seçim

Elindeki koddan değil, ihtiyaç duyduğun arayüzden başla. Cevap uygulamayla sınırlı rotaları olan bir TUN cihazıysa platform yolu daha kısadır ve yükseltmeleri atlatır. Cevap Ethernet çerçeveleri, diğer uygulamaların yakalanması ya da builder'ın reddettiği yönlendirme kuralları içeriyorsa root bölgesindesin ve bunu işletme maliyeti olarak bütçelemelisin.

Tartışmaların çoğunu çözen bir kontrol: önce platform yolunu kur, establish() sonucunu uzak oturum durumundan ayrı kaydet ve her yönde bir paketi doğrula. Ancak ondan sonra eksik yeteneğin desteklenen yolu bırakmayı haklı çıkarıp çıkarmadığına karar ver.

İlgili makaleler