Android VpnService туташат, бирок тармакка бир да пакет жетпейт
Туннель көтөрүлдү, барактар ачылбай жатат: VPN серверин күнөөлөөдөн мурун окуу режимин, socket коргоосун, TCP колдоосун жана маршруттар тизмесин текшериңиз.
Туннель көтөрүлөт, ачкыч белгиси чыгат, бирок браузердеги ар бир сурам убакыт бүтүп ишке ашпайт. VpnService колдонмосунда бул айкалыш адатта окуу цикли тирүү, ал эми чыгуу жарымы бузулганын билдирет, сервер кол алышууну четке какканын эмес. Android интерфейсти түзүп, колдонмонун трафигин ага көчүрүп, процессиңизге файл дескрипторун берген. Ошол дескриптордон кийинкинин баары сиздин код, жана бир кетип калган чакыруу бүткүл агымды жутуп коюуга жетишет.
Система эмне берет жана эмне бербейт
establish() tun интерфейси үчүн ParcelFileDescriptor кайтарат. Система чыгуучу IP пакеттерин ага жазып, процессиңиз аларды окуп, кайда кетерин чечип, кадимки socket аркылуу жөнөтөт деп күтөт. Астында өзөктүк өткөрүү жок, ошондуктан эч ким окубаса, пакеттер ташталат жана интерфейстин эсептегичтери эч качан кыймылдабайт.
Болжолдоонун ордуна эсептөөдөн баштаңыз. Ар бир ийгиликтүү окууда эсептегичти көбөйтүп, алгачкы пакеттердин багыты менен протоколун жазыңыз. Трафик жаралып жатканда эсептегич нөлдө турса, ката сиздин өткөрүү кодуңуздан жогору, бул издөөнү builder конфигурациясына жана окуу режимине чектейт. Эсептегич кыймылдап, браузер дагы ишке ашпаса, ката жөнөтүү же кайтаруу жолунда.
Блоктоочу окуу циклдын кыймылдашын чечет
setBlocking(true) дескрипторду блоктоочу режимге коёт жана API 21ден бери бар. Эски версияларда чечим fcntl аркылуу чийки дескриптордон O_NONBLOCK тазалаган кичине JNI жардамчы. Режим маанилүү, анткени күтүп жаткан пакети жок окуунун эмне кыла аларын ошол чечет.
Блоктобогон режимде бул окуу дароо терс маани менен кайтат, ал эми жыйынтыкты этибарга албаган цикл трафикти кыймылдатпай эле өзөктү айлантат. Блоктоочу режимде окуу күтөт, бул өткөрүү жибине керек, бирок андан кийин жипти токтотуу мүмкүн эмес. Дескрипторду жабуу окууну ишке ашпай калтыруунун колдоого алынган жолу, ошентип цикл чыга алат. Процессти өлтүрүү же жипти токтотуу кийинки баштоого интерфейске эми туура келбеген дескриптор калтырышы мүмкүн.
Ар бир чакырууга интерфейстин MTU өлчөмүндөгү буферге бир пакет окуңуз, ал эми нөлдү же терс кайтарууну бош пакет эмес, ката деп эсептеңиз.
protect() биринчи жөнөтүүдөн мурун, кийин эмес
Инкапсуляцияланган пакетти ташыган ар бир socket адегенде корголушу керек. Корголбогон socket жаңы эле орноткон маршруттоо эрежелерине байыйт, ошондуктан анын пакеттери туннелге кайтып, сиздин окуу циклиңиз аларды кайра алат. Андан кийин туннель өз чыгуусун чексиз өткөрүп турат, ал эми сырттан бул ачылып, эч качан жооп бербеген байланыш сыяктуу көрүнөт.
Тартип: protect, анан connect же bind, анан send. connectтен кийин protect чакырса, мурунку маршрут чечими калып, чакыруу true кайтарса да socket туура эмес жолду колдоно берет. Кайтарылган booleanды текшерип, ал false болгондо катуу ишке ашпай калыңыз, иштебей турган жөнөтүүгө кирүүнүн ордуна.
protect()ге эч качан тийбеген socketте sendtoдон келген укук катасы бул катанын адаттагы белгиси.
UDP үлгүсү HTTPS ташыбайт
Көпчүлүк окуу куралдары көчүргөн өткөрүү үлгүсү UDP менен гана иштейт. TCPге кол алышуу, иреттик номерлер, ырастоолор жана кайра жөнөтүү керек, ал эми tun дескриптору менен socketтин ортосунда жүктү көчүргөн цикл алардын бирин да бербейт. Ошондуктан UDP DNS сурамы өтүп кетиши мүмкүн, ал эми ар бир HTTPS сурамы токтоп калат. Же колдонуучу мейкиндигинде TCP стек иштетиңиз, же түзмөктө TCPни бүтүрүп, агымды өткөргөн жергиликтүү проксиге байланыштарды бериңиз.
Кайра жазардан мурун бөлүнүүнү текшериңиз: бир UDP сурам жөнөтүп, ошол хостко бир TCP байланыш ачыңыз. UDP жолу иштеп, TCP жолу биринчи SYNде токтоп калса, маселе өткөрүү кодунда, серверде эмес. Ошол бир салыштыруу пайдалуу эч нерсеси жок сервер журналдарын окууга кетчү көп убакытты үнөмдөйт.
Маршруттар тизмеси жана колдонмо боюнча чыпкалар көргөнүңүздү чектейт
addAddress, addRoute жана addDns система туннелге эмне жөнөтөрүн аныктайт. Маршруттар тизмесинен тышкаркы багыт дескрипторуңузга эч качан жетпейт, ошондуктан канча өткөрүү логикасы болбосун аны оңдобойт. Жасаган builder чакырууларыңызды жазып, колдонмо чынында жетүүгө аракет кылган даректер менен салыштырыңыз.
Колдонмо боюнча тандоонун өз чеги бар: addAllowedApplication жана addDisallowedApplication API 21 талап кылат, ал эми эски версияларда бул ыкмалар такыр жок. Пакет боюнча чыпкалаган долбоор ал жерде иштебейт.
Кайра орнотуу эски дескрипторларды калтырат
establish()ти кайра чакырганда жаңы дескриптор келип, мурунку интерфейс жабылат. Эски дескриптор эми жараксыз, ошондуктан анда дагы бөгөттөлүп турган жип ката алат, ал эми эски жол үчүн түзүлгөн socketтерди алмаштыруу керек болушу мүмкүн. onRevoke — колдонуучу же система алып койгон туннель үчүн кайтарым чакыруу. Жашоо цикли кайра орнотуудан мурун окуучуну токтоткон бир ээде болсо, туташкан-бирок-өлүк абалдардын көбүн алдын алат.
Ар бир катаны тиешелүү кадамы боюнча окуңуз. recvfromдон келген байланыштын кайра коюлушу алыскы тарап жабылганын билдирет, ал эми кодүңүз дагы окуп жаткан. Туура эмес дарек катасы аталышты талдоого же offset катасына көрсөтөт, тармакка эмес.
Иш тартиби: блоктоочу режим күйүк, ар бир окууга бир пакет, аталыш талданган, socket корголгон, жүк жөнөтүлгөн, өткөрүүдө таза жабуу жана багытты камтыган маршруттар тизмеси.