Тунелот се крева, иконата со клуч се појавува, а секое барање во прелистувачот сè уште истекува. Во VpnService апликација таа комбинација обично значи дека циклусот за читање е жив, а излезната половина е скршена, а не дека серверот го одбил ракувањето. Android веќе ја создаде интерфејсот, го премести сообраќајот на апликацијата во него и му даде на вашиот процес датотечен дескриптор. Сè по тој дескриптор е ваш код, и еден пропуштен повик е доволен да го проголта целиот тек.

Што ви дава системот, а што не

establish() враќа ParcelFileDescriptor за tun интерфејс. Системот запишува излезни IP пакети во него и очекува вашиот процес да ги прочита, да одлучи каде одат и да ги испрати преку обичен socket. Под него нема проследување во јадрото, па кога никој не чита, пакетите се отфрлаат и бројачите на интерфејсот никогаш не се движат.

Започнете со броење наместо со погодување. Зголемете бројач при секое успешно читање и забележете ја дестинацијата и протоколот на првите пакети. Ако бројачот остане на нула додека се генерира сообраќај, грешката е над вашиот код за проследување, што го стеснува пребарувањето на конфигурацијата на builder и режимот на читање. Ако бројачот се движи, а прелистувачот сè уште не работи, грешката е во патеката на испраќање или на враќање.

Блокирачкото читање одлучува дали циклусот се движи

setBlocking(true) го става дескрипторот во блокирачки режим и постои од API 21. На постари изданија решението е мал JNI помошник што го брише O_NONBLOCK со fcntl врз суровиот дескриптор. Режимот е важен зашто одлучува што смее да прави читањето без пакет што чека.

Во неблокирачки режим тоа читање веднаш се враќа со негативна вредност, а циклусот што го игнорира резултатот врти јадро без да пренесе ни бајт сообраќај. Во блокирачки режим читањето чека, што е она што го сака нишката за проследување, но тогаш нишката повеќе не може да се прекине. Затворањето на дескрипторот е поддржаниот начин да се направи читањето да не успее за циклусот да излезе. Убивањето на процесот или прекинувањето на нишката може да го остави следното стартување со дескриптор што повеќе не одговара на интерфејсот.

Читајте еден пакет по повик во бафер со големина на MTU на интерфејсот, и третирајте нула или негативен поврат како грешка, а не како празен пакет.

protect() пред првото испраќање, не по него

Секој socket што носи инкапсулиран пакет мора прво да биде заштитен. Незаштитен socket ги следи истите правила за рутирање што штотуку ги поставивте, па неговите пакети се враќаат во тунелот и вашиот циклус за читање повторно ги зема. Тогаш тунелот бесконечно го проследува својот излез, и однадвор изгледа како врска што се отвора и никогаш не одговара.

Редоследот е protect, потоа connect или bind, потоа send. Повикот protect по connect може да ја остави претходната одлука за рута, па socket продолжува да го користи погрешниот пат иако повикот вратил true. Проверете го boolean што го враќа и гласно не успејте кога е false, наместо да влезете во испраќање што не може да работи.

Грешка со дозвола од sendto на socket што никогаш не допрел protect() е вообичаениот потпис на оваа грешка.

UDP примерот не носи HTTPS

Примерот за проследување што го копираат повеќето упатства обработува UDP и ништо друго. TCP бара ракување, редни броеви, потврди и повторно испраќање, а циклус што копира товар помеѓу tun дескриптор и socket не дава ништо од тоа. Затоа UDP DNS барање може да помине додека секое HTTPS барање застанува. Или стартувајте TCP стек во кориснички простор или предајте ги врските на локален proxy што го завршува TCP на уредот и го проследува текот.

Тестирајте ја поделбата пред да препишете било што: испратете едно UDP барање и отворете една TCP врска до истиот хост. Ако UDP патеката работи, а TCP патеката виси на првиот SYN, проблемот е во кодот за проследување, а не во серверот. Таа една споредба штеди многу време поминато во читање серверски дневници што не содржат ништо корисно.

Списокот рути и филтрите по апликација ограничуваат што гледате

addAddress, addRoute и addDns одредуваат што системот испраќа во тунелот. Дестинација надвор од списокот рути никогаш не стигнува до вашиот дескриптор, па ниедна количина логика за проследување нема да го поправи тоа. Забележете ги повиците на builder што ги правите и споредете ги со адресите до кои апликацијата навистина се обидува да стигне.

Изборот по апликација има свој праг: addAllowedApplication и addDisallowedApplication бараат API 21, а на постари изданија методите едноставно ги нема. Дизајн што филтрира по пакет таму воопшто не може да работи.

Повторното воспоставување остава застарени дескриптори

Повторен повик на establish() враќа нов дескриптор и го затвора претходниот интерфејс. Стариот дескриптор сега е невалиден, па нишка што сè уште блокира на него добива грешка, а socket-ите создадени за стариот пат можеби треба да се заменат. onRevoke е повратниот повик за тунел што корисникот или системот го одзеле. Држењето на животниот циклус кај еден сопственик, што го запре читачот пред повторно воспоставување, спречува повеќето состојби поврзано-но-мртво.

Читајте ја секоја грешка според чекорот на кој припаѓа. Ресетирање на врската од recvfrom значи дека далечниот крај се затворил додека вашиот код сè уште читал. Грешка за лоша адреса укажува на парсирање на заглавието или грешка во поместувањето, не на мрежата.

Редослед на работа: блокирачки режим вклучен, еден пакет по читање, заглавие парсирано, socket заштитен, товар испратен, чисто затворање при предавање и список рути што ја покрива дестинацијата.

Поврзани статии