Android VpnService piesakās, bet paketes nesasniedz tīklu
Tunelis pacelts, bet lapas joprojām neiet: pārbaudiet lasīšanas režīmu, socket aizsardzību, TCP atbalstu un maršrutu sarakstu, pirms vainot VPN serveri.
Tunelis paceļas, atslēgas ikona parādās, un katrs pārlūka pieprasījums joprojām beidzas ar taimautu. VpnService lietotnē šī kombinācija parasti nozīmē, ka lasīšanas cilpa ir dzīva un izvades puse ir salauzta, nevis ka serveris noraidīja rokasspiedienu. Android jau izveidoja saskarni, pārvietoja lietotnes trafiku tajā un nodeva jūsu procesam faila deskriptoru. Viss pēc šī deskriptora ir jūsu kods, un viens iztrūkstošs izsaukums ir pietiekams, lai aprītu visu plūsmu.
Ko sistēma dod un ko ne
establish() atgriež ParcelFileDescriptor tun saskarnei. Sistēma ieraksta tajā izejošās IP paketes un sagaida, ka jūsu process tās lasīs, izlems, kurp tās iet, un nosūtīs pa parastu socket. Zem tā nav kodola pārsūtīšanas, tāpēc, kad neviens nelasa, paketes tiek izmestas un saskarnes skaitītāji nekad nekustas.
Sāciet ar skaitīšanu, nevis minēšanu. Palieliniet skaitītāju pie katras veiksmīgas lasīšanas un reģistrējiet pirmo pakešu galamērķi un protokolu. Ja skaitītājs paliek nullē, kamēr tiek ģenerēts trafiks, kļūda ir augstāk par jūsu pārsūtīšanas kodu, kas sašaurina meklēšanu līdz builder konfigurācijai un lasīšanas režīmam. Ja skaitītājs kustas un pārlūks joprojām neiet, kļūda ir sūtīšanas vai atgriešanās ceļā.
Bloķējošā lasīšana izšķir, vai cilpa kustas
setBlocking(true) ievieto deskriptoru bloķējošā režīmā, un tas pastāv kopš API 21. Vecākās versijās risinājums ir mazs JNI palīgs, kas ar fcntl notīra O_NONBLOCK uz neapstrādātā deskriptora. Režīms ir svarīgs, jo tas nosaka, ko drīkst darīt lasīšana bez gaidošas paketes.
Nebloķējošā režīmā šī lasīšana nekavējoties atgriežas ar negatīvu vērtību, un cilpa, kas ignorē rezultātu, griež kodolu, nepārvietojot ne mazāko trafiku. Bloķējošā režīmā lasīšana gaida, ko vēlas pārsūtīšanas pavediens, taču tad pavedienu vairs nevar pārtraukt. Deskriptora aizvēršana ir atbalstītais veids, kā likt lasīšanai neizdoties, lai cilpa varētu iziet. Procesa nogalināšana vai pavediena pārtraukšana var atstāt nākamo startu ar deskriptoru, kas vairs neatbilst saskarnei.
Lasiet vienu paketi uz izsaukumu buferī, kura izmērs atbilst saskarnes MTU, un uzskatiet nulli vai negatīvu atdevi par kļūdu, nevis tukšu paketi.
protect() pirms pirmās sūtīšanas, ne pēc tās
Katrs socket, kas pārvadā iekapsulētu paketi, vispirms ir jāaizsargā. Neaizsargāts socket pakļaujas tiem pašiem maršrutēšanas noteikumiem, kurus tikko uzstādījāt, tāpēc tā paketes atgriežas tunelī, un jūsu pašu lasīšanas cilpa tās atkal paņem. Tad tunelis bezgalīgi pārsūta savu izvadi, un no ārpuses tas izskatās kā savienojums, kas atveras un nekad neatbild.
Secība ir protect, tad connect vai bind, tad send. protect izsaukšana pēc connect var atstāt iepriekšējo maršruta lēmumu, tāpēc socket turpina izmantot nepareizo ceļu, lai gan izsaukums atgrieza true. Pārbaudiet atgriezto boolean un skaļi neizdodieties, kad tas ir false, tā vietā, lai iekļūtu sūtīšanā, kas nevar darboties.
Atļaujas kļūda no sendto uz socket, kas nekad nav aizskāris protect(), ir ierastā šīs kļūdas pazīme.
UDP paraugs nepārvadā HTTPS
Pārsūtīšanas paraugs, ko kopē lielākā daļa apmācību, apstrādā tikai UDP. TCP nepieciešama rokasspiediens, secības numuri, apstiprinājumi un atkārtota sūtīšana, un cilpa, kas kopē kravu starp tun deskriptoru un socket, nenodrošina neko no tā. Tāpēc UDP DNS vaicājums var iziet, kamēr katrs HTTPS pieprasījums iestrēgst. Vai nu darbiniet TCP steku lietotāja telpā, vai nododiet savienojumus vietējam proxy, kas ierīcē pabeidz TCP un pārsūta plūsmu.
Pārbaudiet sadalījumu pirms kaut ko pārrakstīt: nosūtiet vienu UDP vaicājumu un atveriet vienu TCP savienojumu uz to pašu resursdatoru. Ja UDP ceļš darbojas un TCP ceļš iestrēgst pie pirmā SYN, problēma ir pārsūtīšanas kodā, nevis serverī. Šis viens salīdzinājums ietaupa daudz laika, kas pavadīts, lasot servera žurnālus, kuros nav nekā noderīga.
Maršrutu saraksts un filtru pēc lietotnes ierobežo redzamo
addAddress, addRoute un addDns nosaka, ko sistēma sūta tunelī. Galamērķis ārpus maršrutu saraksta nekad nesasniedz jūsu deskriptoru, tāpēc nekāds pārsūtīšanas loģikas apjoms to nelabos. Reģistrējiet veiktos builder izsaukumus un salīdziniet tos ar adresēm, uz kurām lietotne patiešām cenšas nokļūt.
Izvēlei pēc lietotnes ir sava apakšējā robeža: addAllowedApplication un addDisallowedApplication prasa API 21, un vecākās versijās šo metožu vienkārši nav. Dizains, kas filtrē pēc paketes, tur nemaz nevar darboties.
Atjaunošana atstāj novecojušus deskriptorus
Atkārtots establish() izsaukums atgriež jaunu deskriptoru un aizver iepriekšējo saskarni. Vecais deskriptors tagad ir nederīgs, tāpēc pavediens, kas tajā vēl bloķējas, saņem kļūdu, un vecajam ceļam izveidotie socket var būt jāaizstāj. onRevoke ir atzvanīšana tunelim, ko lietotājs vai sistēma ir atņēmuši. Dzīves cikla turēšana pie viena īpašnieka, kas pārtrauc lasītāju pirms atjaunošanas, novērš lielāko daļu savienots-bet-miris stāvokļu.
Lasiet katru kļūdu pēc soļa, kuram tā pieder. Savienojuma atiestatīšana no recvfrom nozīmē, ka tālais gals aizvērās, kamēr jūsu kods vēl lasīja. Sliktas adreses kļūda norāda uz galvenes analīzi vai nobīdes kļūdu, nevis uz tīklu.
Darbu secība: bloķējošais režīms ieslēgts, viena pakete uz lasīšanu, galvene analizēta, socket aizsargāts, krava nosūtīta, tīra aizvēršana nodošanas brīdī un maršrutu saraksts, kas aptver galamērķi.