Раздзеленае тунэляванне ў Android VpnService: што насамрэч можна маршрутызаваць
VpnService перадае толькі тыя пакеты, якія builder накіраваў у свой дэскрыптар. Праверце прэфіксы, аўтэнтыфікацыю проксі, DNS і спісы праграм, перш чым вінаваціць раздзеленае тунэляванне.
Раздзеленае тунэляванне ў праграме на VpnService ламаецца ў некалькіх канкрэтных месцах, і кожнаму патрэбна сваё выпраўленне. Кліент паказвае толькі адзін пункт прызначэння, які не адпрацоўвае, таму спачатку трэба вызначыць, якую мяжу вы ўпрэцеся: маршруты, якія атрымаў builder, проксі, да якога не ўдаецца аўтэнтыфікавацца, рэзалвер, які вы толькі абвясцілі, або спіс праграм, які платформа прымяняе для кожнага карыстальніка. Менавіта таму я трымаю чатырохкрокавую праверку. Яна ідзе ў парадку ніжэй і спыняецца на першай памылцы.
Пацвердзіце, што builder перадаў платформе
VpnService.Builder — гэта спіс дэкларацый. establish() вяртае ParcelFileDescriptor для віртуальнага інтэрфейсу, і платформа накіроўвае ў гэты дэскрыптар толькі трафік, які адпавядае прэфіксам з addAddress і addRoute; addDnsServer паведамляе кліентам, які рэзалвер запытваць. Пункт прызначэння па-за ўсімі дададзенымі прэфіксамі ніколі не дасягне вашай праграмы, што б ні абяцаў інтэрфейс. Гэты адзіны факт тлумачыць большасць скаргаў на зламанае раздзеленае тунэляванне яшчэ да змяненняў у кодзе.
Залогіруйце спісы адрасоў, маршрутаў і DNS, якія сабраў ваш builder, і параўнайце іх з пунктамі прызначэння, што не адпрацоўваюць. Адсутны /32 для кіраўніцкага хоста сустракаецца значна часцей, чым зламаны тунэль. Калі спіс слушны, ідзіце далей; калі не, рэшта чэк-ліста — марная праца.
excludeRoute існуе толькі з API 33
Выключэнне па пункце прызначэння — гэта запыт, які часцей за ўсё ператвараецца ў тэму падтрымкі. builder.excludeRoute(IpPrefix) з'явіўся ў API 33; да гэтага адзінае падтрыманае набліжэнне — дадаць маршруты для ўсяго, акрамя выключанага прэфікса, а значыць, давядзецца самастойна вылічыць дапаўняльныя прэфіксы. Гэтыя два выклікі не люстэркавыя: адзін адымае з набору маршрутаў інтэрфейсу, другі будуе набор з нуля, а старэйшая прылада кіне выключэнне або моўчкі праігнаруе невядомы выклік. Разгаліноўвайце па Build.VERSION.SDK_INT і правярайце абодва шляхі на рэальным абсталяванні.
Калі вылічваеце дапаўненні, сачыце за выраўноўваннем прэфіксаў. Выключэнне /24 унутры маршруту /16 патрабуе пакрывальных прэфіксаў, якія сапраўды апісваюць астатнюю частку, а не адзін няправільны /8. Залогіруйце канчатковы спіс і праверце, што выключаны хост па-ранейшаму рэзалвіцца праз шлях па-за тунэлем.
Уліковыя даныя проксі немагчыма выразиць
setHttpProxy(ProxyInfo) накіроўвае інтэрфейс на HTTP- або SOCKS-проксі, а PAC-URL можа выбіраць паміж імі для кожнага пункта прызначэння. Ні ProxyInfo, ні фармат PAC не перадаюць імя карыстальніка і пароль, таму проксі з аўтэнтыфікацыяй застаецца па-за API платформы. Праграмы, якім ён патрэбны, звычайна ўбудоўваюць карыстальніцкі стек, tun2socks або падобны рухавік, і самі адказваюць на выклік 407.
Гэты выбар мае наступствы. Карыстальніцкі стек бачыць толькі той трафік, які вы накіравалі ў яго, таму ён мусіць жыць у тых жа межах дэскрыптара і наследуе тую ж патрабу protect() для ўласнага сокета ўзыходзячага канала. Прапусціце protect() — і тунэль зловіць уласны транспарт, што выглядае як рукапацісканне, якое застойваецца пасля першага пакета. Правярайце на проксі, які патрабуе ўліковыя даныя, бо адкрыты проксі праходзіць па шляху платформы і нічога не даказвае.
Спісы праграм і DNS — таксама дэкларацыі
У Builder ёсць і addAllowedApplication, і addDisallowedApplication, а яны ўзаемавыключальныя: выклік абодвух кідае выключэнне. У любым выпадку спіс прымяняецца для кожнага карыстальніка пры наладзе інтэрфейсу, таму праграма працоўнага профілю кіруецца тунэлем працоўнага профілю, а не вашым. Чаго не дае ніводнае API — гэта кантролю над рашэннем іншай праграмы: вы не можаце знешне загнаць пакет у свой тунэль і не можаце закрыць або пераканфігураваць ужо актыўны VPN.
DNS ідзе па той жа схеме. addDnsServer абвяшчае адрас рэзалвера; ён не дазваляе вашай праграме адказваць на запыты. Калі хочаце рэзалвіць імёны самі, запусціце рэзалвер і накіруйце яго адрас у тунэль. Праграмы, што выкарыстоўваюць DoT або DoH да фіксаванага імя хоста, могуць абысці абвешчаны вамі адрас, таму лічыце распазнаванне імёнаў лепшым намаганнем, пакуль вы не перахопліваеце і шлях запыту.
Маніфест, always-on і чатырохкрокавы тэст
Права на працу абвяшчаюцца ў маніфесце: BIND_VPN_SERVICE, intent-фільтр android.net.VpnService і foreground-сэрвіс, калі тунэль мусіць выжыць у фоне. Always-on VPN дадаткова патрабуе чыстага onRevoke, бо сістэма выклікае яго, калі карыстальнік змяняе налады або іншая праграма перахапляе кіраванне.
Выконвайце праверкі па парадку і спыняйцеся на першай памылцы. Першае: выведзіце спісы адрасоў, маршрутаў і DNS, перададзеныя builder. Другое: пацвердзіце, што establish() вярнуў дэскрыптар і што транспартны сокет абаронены. Трэцяе: запытайце адзін пункт прызначэння ўнутры ўключанага прэфікса і адзін — унутры выключанага. Чацвёртае: высветліце, які рэзалвер фактычна адказаў, запытаўшы імя хоста, якое можа рэзалвіць толькі ваш рэзалвер.
Завершанае рукапацісканне не даказвае ніводнага з наступных крокаў. Кожная праверка ізалюе адзін пласт, таму поспех на другім кроку нічога не кажа пра чацвёрты.
Даведка па VpnService.Builder пералічвае ўсю паверхню builder. Усё, што патрэбна звыш яе, — гэта іншае API, іншая праграма або ваш уласны карыстальніцкі стек, і гэта рашэнне праектавання, а не памылка раздзеленага тунэлявання.