

1/1/1970
Foony'yi Proxy'lerin Arkasında Nasıl Çalışır Hale Getirdim
Selam! Web proxy'lerinin sitelerle uyumluluk sorunlarına yol açtığını uzun zamandır biliyorum. Ama Foony'nin proxy'lerde desteği nam salmış derecede kötüydü ve Foony'yi proxy'lerle uyumlu hale getirmek epey çetrefilli çıktı.
Bu, “Foony çok egzotik API'ler kullanıyor da ondan” türü bir sorun da değil (gerçi kullanıyoruz). Mesele şu şeylerin birleşimiydi:
- Proxy'lerin kesinlikle dokunmaması gereken yerlerde bile agresif şekilde string değiştirmesi.
- Proxy'lerin ana site domain'ine, “diğer” domain'lerden (CDN'ler, asset host'ları vs.) farklı davranması.
- Ve bazı proxy'lerin modern web uygulamalarını (HTTPS'in doğruluğu, WebSocket'ler vs.) hiç destekleyememesi gerçeği.
Her proxy ile çalışmıyoruz ama en azından artık croxyproxy ve proxyorb ile çalışıyoruz, hedef de buydu.
Aşağıda nelerin bozulduğunu, neden bozulduğunu ve gerçekten işe yarayan çözümleri anlatıyorum.
1. Tur: Geçerli ama Bozulmuş Three.js Shader'ları
Belirti
croxyproxy'yi denediğimde, 8 Ball Pool ya da Foony'nin diğer three.js oyunlarını oynayamıyordum. Sürekli Three.js içinde shader derleme hatası alıyordum; şöyle mesajlar geliyordu:
- “Shader Error 1282 - VALIDATE_STATUS false”
Bu mesaj neredeyse tamamen işe yaramaz. Genelde “shader'in geçersiz, geçmiş olsun” anlamına gelir. Harika. Foony'de her hata için neden mutlaka benzersiz hata mesajı kullandığımı merak edersen, sebebi bu. Sorunları tam yerinde yakalamaya yardım ediyor, sadece “kod bozuk, düzelt işte” demekten fazlasını yapıyor.
Ama gayet geçerli three.js shader'ları neden patlıyordu? Neler dönüyordu?
Asıl sebep: proxy'ler layout(location = N) satırını bozuyordu
Three.js, şöyle layout niteleyicileri içeren GLSL üretiyor:
layout(location = 0) in vec3 position;
Bazı proxy'ler, JavaScript location API'sine benzeyen her şeyi, düşüncesizce global bir “bul-değiştir” ile yeniden yazmaya çalışıyor. Bu, sadece JS içinde bile berbat bir fikirken, aynı şeyi shader kaynak stringlerinin içinde de yapıyorlar. Sanırım AST parse etmek onlar için fazla pahalı.
Sonuçta shader kaynağı şöyle bir şeye dönüşüyordu:
layout(__cpLocation = 0) in vec3 position;
Oradaki tanımlayıcının mutlaka location olması gerekiyor. Başka her şey geçersiz GLSL sayılıyor ve derleyici reddediyor. (GLSL'de Layout Niteleyicileri)
Bu, Three.js'in suçu sayılır sadece şu anlamda: Three.js shader'ları dinamik üretiyor ve biz de onları çalışma anında WebGL'e veriyoruz. Asıl bug proxy'nin stringleri değiştirme stratejisinde.
Neden “proxy'yi düzeltmedim”
Saf bir yaklaşım, croxyproxy'nin location yerine koyduğu __cpLocation stringini arayıp tekrar location ile değiştirmek olurdu.
Ama farklı proxy'ler farklı isimler kullanıyor. Bazıları __cpLocation diyor, bazıları bambaşka garip tanımlayıcılar kullanıyor. Yani “__cpLocation gördün mü tekrar location yap” gibi gömülü bir çözüm aşırı kırılgan.
Benim ihtiyacım olan şuydu:
- Genel bir çözüm (hiçbir proxy ismini gömmeden).
- Proxy benim JavaScript'imdeki
locationkelimesini bile değiştiriyor olsa da çalışan bir çözüm.
Base64 numarası: location kelimesini proxy'den saklamak
Proxy gördüğü her location kelimesini değiştiriyorsa, en basit hareket hiç location kullanmamak. Yeterince kolay. Bu tür numaraları daha önce Lua'da, RestedXP'in rehber koruma sistemini tersine mühendislik yaparken görmüştüm (yanlış hatırlamıyorsam, BNGetInfo kullanımını böyle karartıyorlardı, mesela _G("\x42\x4E\x47\x65\x74\x49\x6E\x66\x6F")).
Bu numara JavaScript'te de gayet işe yarıyor tabii. client/index.html içinde, çalışma anında şunu çözüyorum:
// Bu proxy'ler gördükleri her `location`'ı değiştirmeye çalıştığı için, base64 ile kodlanmış bir string kullanıyoruz.
const suffix = 'pb24=';
const locStr = atob('bG9jYXR' + suffix); // "location"
const loc = window[locStr]; // window.location
Bu atob() çağrısı, proxy HTML/JS üzerinde değişikliklerini yaptıktan sonra çalışıyor, dolayısıyla stringi önceden bozamıyor. Stringi ikiye böldüm ki tespit etmeleri iyice zor olsun ve 'atob' kullandım çünkü canım öyle istedi, ama String.fromCharCode ya da window['\x6c\x6f\x63\x61\x74\x69\x6f\x6e'] gibi hex kaçışları da işe yarayabilirdi.
Bozulmuş shader kalıbı yapısal olarak hep aynı:
layout(<something> = <number>)
O yüzden bu kalıbı genel olarak yakalayıp <something> kısmını doğru tanımlayıcıyla değiştiriyorum:
source.replace(/layout\s*\(\s*[^=)]+\s*=\s*(\d+)\s*\)/g, 'layout(' + locStr + ' = $1)');
WebGL kancası: shaderSource'u yamalamak (WebGL1 + WebGL2)
Three.js gl.shaderSource(shader, source) çağırdığı için, ben de doğrudan shaderSource'u yamaladım:
const originalShaderSource = WebGLRenderingContext.prototype.shaderSource;
Object.defineProperty(WebGLRenderingContext.prototype, 'shaderSource', {
value: function (shader, source) {
return originalShaderSource.call(this, shader, fixCorruptedShaderSource(source));
},
writable: true,
configurable: true,
});
Ve aynı yamayı, varsa WebGL2RenderingContext'e de uyguluyorum.
Bu devreye girdikten sonra shader derleme hataları yok oldu. Bu noktada croxyproxy çalışıyordu ama proxyorb hâlâ çöküyordu. Neden?! Aynı şekilde çalışması gerekmiyor muydu?
2. Tur: İkinci sorun (domain'ler) ve foony.io'yu kaldırmanın her şeyi neden kolaylaştırdığı
Foony tarihsel olarak iki domain kullanıyordu, en azından son bir aydır:
- Ana site için
foony.com - Statik dosyalar için
foony.io
İlk sebep tamamen pratiktir: varlıkları çerezsiz bir domain'den servis etmek, her statik dosya isteğinde gereksiz cookie header'larının gönderilmesini engelliyor. Bu harika bir şey, ama HTTP/2, header'lar için gönderilen byte sayısını azaltmak adına HPACK kullandığı için, sandığın kadar da zorunlu değil.
Normal tarayıcı kullanımı için gayet geçerli bir optimizasyon.
Proxy'lerin arkasında ise ciddi bir bozulma kaynağına dönüştü. Ve Foony kitlesi proxy'lere bayılıyor. iç çeker
Proxy'ler “ana siteyi” ve “diğer siteleri” farklı ele alıyor
Birçok proxy, “şu tek sayfayı/domain'i proxy'le” senaryosu için optimize edilmiş. Ana HTML'i düzgün yüklüyorlar, script enjekte ediyorlar, kendi ServiceWorker'larını kaydediyorlar vs.
Ama uygulama farklı bir origin'den (mesela foony.io) asset çekmeye başladığında, her türden eğlenceli bozulma yaşanmaya başlıyor:
- ServiceWorker yakalama hataları, mesela:
- “ServiceWorker intercepted the request and encountered an unexpected error”
- “Loading failed for the module with source”
- Proxy altyapısının query parametrelerine ihtiyaç duyması (ve bunların yanlışlıkla silinmesinin çok kolay olması).
- Asset isteklerinin proxy'nin dahili yönlendirme metadatasını kaybetmesi.
- Tüm isteği
generic-php-slug.php?someQueryParam=hugeEncodedStringile değiştiren garip proxy'ler (evet, onu desteklemekle uğraşmadım).
Bu proxy'lerin iç mekanizmaları query parametrelerine ve URL kodlamaya bağlı, ve epey kırılganlar.
Tipik örneklerden biri şu tarz bir asset URL'siydi:
https://<proxy-ip>/assets/firebase-<hash>.js?__pot=aHR0cHM6Ly9mb29ueS5jb20
Oradaki ?__pot=... kısmı, isteğin hangi domain'e ait olduğunu proxy'ye söyleyen kendi yönlendirme/durum bilgisiydi. Bunu silersen, proxy'ler isteği doğru çözemiyor ve ServiceWorker hata yoluna düşüyorsun.
“Resource swapping” imdada yetişiyor (ve neden çok hızlı karmaşaya dönüştü)
Bir noktada geçici bir çözüm denedim: “proxy'nin arkasındayız” durumunu tespit edip, tüm foony.io resource URL'lerini mevcut origin'e çevirmek, böylece proxy'nin her şeyi aynı origin'den geliyormuş gibi görmesini sağlamak.
Kulağa mantıklı geliyor ve croxyproxy için de çalıştı, ama bir sürü karmaşıklık ekledi:
- HTML'de halihazırda duran
linkvescriptetiketlerini de değiştirmek gerekiyor. - Dinamik olarak eklenen etiketleri (modulepreload, stylesheet vs.) yakalamak için bir
MutationObserverkullanman gerekiyor. - Proxy'nin query parametrelerini korumak zorundasın, yoksa onların yönlendirmesini bozuyorsun. Ve tabii ki her proxy bunu başka şekilde yapıyor. Çünkü elbette.
- Ve tüm bunları yaparken mantığı genel tutman gerekiyor (proxy'ye özel global değişkenler olmadan) ki kod şişkin, çöplük gibi bir şeye dönüşmesin.
“Base64 numarası” da burada tekrar devreye girdi: Kendi JavaScript'imde bile location kelimesine dikkat etmek zorundaydım çünkü proxy onu da değiştirebiliyordu.
CroxyProxy'nin enjekte ettiği script'i tersine mühendislik yapmak
Bu noktada meraklandım: proxy aslında sayfama ne yapıyor? Kendi reklamlarını mı enjekte ediyor? Daha mı kötü bir şey?
CroxyProxy'nin istemci tarafı script'i fena halde karartılmış.
(new Function(new TextDecoder('utf-8').decode(new Uint8Array((atob('NjY3NTZlN...')).match(/.{1,2}/g).map(b => parseInt(b, 16))))))();
Çalıştırdığında şöyle bir şeye dönüşüyor:
function a0_0x5ebf(_0x213dc9,_0x1c49b6){var _0x4aa7c1=a0_0x4274();return a0_0x5ebf=function(_0x159600,_0x51d898){_0x159600=...
Buna bakınca croxyproxy'nin bu karartma için Obfuscator.io kullandığı anlaşılıyor. Neyse ki bu karartmayı webcrack ile çözmek gayet kolay.
Sonuçta çok daha okunabilir bir JavaScript elde ediyorsun:
((_0x15ca2c, _0x489eaa) => {
if (typeof module == "object" && module.exports) {
module.exports = _0x489eaa(require("./punycode"), require("./IPv6"), require("./SecondLevelDomains"));
} else if (typeof define == "function" && define.amd) {
define(["./punycode", "./IPv6", "./SecondLevelDomains"], _0x489eaa);
} else {
_0x15ca2c.URI = _0x489eaa(_0x15ca2c.punycode, _0x15ca2c.IPv6, _0x15ca2c.SecondLevelDomains, _0x15ca2c);
}
})(this, function (_0x724467, _0x275183, _0x219d84, _0x2dcc2c) {
var _0x114c1e = _0x2dcc2c && _0x2dcc2c.URI;
function _0x5a9187(_0x2fd4ea, _0x3bd460) {
var _0x23a83f = arguments.length >= 1;
if (!(this instanceof _0x5a9187)) {
Güzel. Artık ne yaptığını görebiliyoruz. Ve... çoğunlukla masum görünüyor. Bence bu karartma daha çok proxy'nin tespit edilmesini zorlaştırmak için yapılmış. Çoğunlukla.
Biraz reklam / arayüz enjeksiyonu var:
Bi(_0x308e2f) {
console.log("Ads: " + _0x331b11.showAds);
console.log("Ad codes: " + !!_0x331b11.adsJson);
console.log("Adblock: " + _0x308e2f);
_0x34fa18.document.body.insertAdjacentHTML("afterbegin", _0x331b11.modal);
if (_0x331b11.header) {
if (_0x331b11.header) {
this.Ri(_0x308e2f);
}
[...document.querySelectorAll("#__cpsHeader a")].forEach(_0xcce595 => {
_0xcce595.addEventListener("click", function (_0x3ef5f1) {
if (this.target === "_blank") {
_0x34fa18.open(this.href, "_blank").focus();
} else {
_0x34fa18.location.href = this.href;
}
_0x3ef5f1.stopImmediatePropagation();
_0x3ef5f1.preventDefault();
}, true);
});
}
return this;
}
Başka yerler de var ama temelde yaptığı şey reklam göstermek, pop-under tarzı reklamlar da dahil. Bir de FuckAdBlock kullanıyor.
Ama stringleri asıl değiştirme işi sunucu tarafında oluyor. Ve orada neler döndüğünü kim biliyor.
Nasıl olursa olsun, hesap güvenliğin senin için önemliyse web proxy'lerini kesinlikle kullanmamalısın. Mecbursan bile, hiçbir PII (kişisel bilgi) / hesap / satın alma bilgini girmemeye çalış.
"Resource swapping" çöpe gitti
Resource swapping'in getirdiği karmaşıklığın, bir de üstüne foony.io desteği için kodun diğer kısımlarına eklenen karmaşıklığın, o güzel, çerezsiz isteklerden gelen minicik ağ tasarrufuna değmeyeceğine karar verdim.
foony.io'ya geçtiğimizden beri oyun oynama dönüşümlerinde açıklayamadığımız bir düşüş de görüyorduk, o yüzden farkında olmadığımız başka foony.io sorunları da olduğundan şüpheleniyorum.
Bu yüzden foony.io'yu kaldırdım. En azından şimdilik.
foony.io CDN mantığını sildikten ve her şeyi foony.com üstüne topladıktan sonra, proxy desteği dramatik biçimde basitleşti:
- Aynı origin'den asset yüklemeleri.
- Proxy ServiceWorker'ına açıklanması gereken daha az “özel durum”.
- Daha az yeniden yazma.
- Daha az kırılgan kod.
Kısacası, foony.io'yu kaldırmak, garip proxy davranışlarının yüzey alanını azaltan mimari bir sadeleştirme oldu.
3. Tur: Ne çalışıyor, ne çalışmıyor ve neden
Çalıştığı doğrulanan proxy'ler
Bu noktada Foony şu proxy'lerin arkasında çalışıyor:
- croxyproxy
- proxyorb
Muhtemelen başka proxy'lerde de çalışıyordur. Bahse girerim çoğunda hâlâ çalışmıyordur. Ama en azından insanların oyun oynamak için sıklıkla kullandıkları önemli olanlar çalışıyor gibi duruyor.
Neden “tüm proxy'ler” değil?
Bazı proxy'ler modern, çok oyunculu bir web uygulamasını basitçe kaldıramıyor. Örneğin:
- HTTPS'i doğru düzgün desteklemeyen proxy'ler.
- WebSocket'leri bozan ya da engelleyen proxy'ler (Foony gerçek zamanlı networking kullanıyor). Teorik olarak bunun etrafından dolaşmak mümkün ama ekstra karmaşıklık ekler.
- Cross-origin istekler, header'lar ya da ServiceWorker'lar konusunda aşırı kısıtlama getiren proxy'ler.
Öne çıkan noktalar
Web proxy'leri çok güvensiz
Bunlar öyle ara katmanlar ki:
- HTML'i yeniden yazıyorlar
- JavaScript'i yeniden yazıyorlar
- bazen bir ServiceWorker enjekte ediyorlar
- ve istekleri yönlendirmek için çoğu zaman query parametrelerine / URL kodlamaya bağlılar
- sayfalarınla aklına gelebilecek her şekilde oynayabiliyorlar
Bazı proxy'lerin ne kadar derine indiğine şaşırdım: shader kaynak stringlerini, yorum satırlarını ve kim bilir daha neleri yeniden yazıyorlar.
Bazen en iyi çözüm mimari olandır
WebGL yaması oyunların tekrar çizilmesini sağladı, ama çok domain'li CDN stratejisini çöpe atmak proxy desteğinin kalıcı olarak stabil olmasını sağladı.
Bu güzel bir hatırlatma: Zekice optimizasyonlar, düşmanca davranan bir ara katmanla çarpışana kadar tamamen mantıklı görünebilir. Ya da kullanıcının tarayıcı eklentileriyle. Ya da Safari ile. Ya da dil ayarlarıyla. Ya da erişilebilirlik özellikleriyle. Ya da güneş patlamalarıyla. Ya da aslında herhangi bir şeyle.
Sonuç
Foony artık önemli proxy'lerin (croxyproxy ve proxyorb) arkasında çalışıyor, hem de kod tabanını proxy'ye özel bir enkaza çevirmeden:
- Genel bir Three.js shader düzeltmesi (proxy'ye özel tanımlayıcı yok).
- Daha basit bir domain stratejisi (her yerde foony.com).