VpnService на Android няма маршрут за изключване и никакво четене на API няма да го създаде. Builder.addRoute() винаги добавя само местоназначения към тунела, така че запазването на LAN сегмент локален означава, че този сегмент никога не влиза в списъка с маршрути. Това, което добавяте вместо това, е допълнението на сегмента, изразено като обикновени префикси. Работата е аритметика и се проваля на няколко предвидими места.

Изпълнете проверките по-долу по ред. Всяка предполага, че предишната е минала, затова спрете на първия провал вместо да кърпите симптоми по-надолу.

Започнете от това, което тунелът твърди

Подаването на 0.0.0.0/0 към addRoute() казва на Android, че всяко IPv4 местоназначение принадлежи на тунела. Ето защо толкова много клиенти изглеждат неспособни на разделено тунелиране: те претендират за цялото адресно пространство и след това се опитват да извадят от него. Нищо в конструктора не изважда. По-конкретен маршрут за мрежата, която искате да остане локална, прави обратното на очакваното, защото най-дългият съвпадащ префикс печели и тази мрежа попада вътре в тунела.

Списъкът с маршрути е цялата политика. Зад него няма вето за отделно местоназначение и справката за Builder не изброява метод, който би го осигурил.

Генерирайте допълнението от аритметика на префикси

Допълнението на изключен префикс е множеството от неговите братя по пътя към корена на адресното дърво. За всяка дължина от 1 до изключената дължина вземете блока с тази дължина, съдържащ вашия префикс, обърнете бита непосредствено под префикса и изведете резултата. Звучи абстрактно, докато не го изпълните.

Изключването на 10.0.0.0/8 дава осем маршрута: 128.0.0.0/1, 64.0.0.0/2, 32.0.0.0/3, 16.0.0.0/4, 0.0.0.0/5, 12.0.0.0/6, 8.0.0.0/7, 11.0.0.0/8. Никой от тях не съдържа 10.0.0.0/8 и заедно покриват всичко останало.

Осем реда генерират този списък за всеки префикс:

import ipaddress
def complement(addr, length):
    base = int(ipaddress.IPv4Address(addr))
    out = []
    for k in range(1, length + 1):
        block = base & (0xFFFFFFFF << (32 - k))
        sibling = block ^ (1 << (32 - k))
        out.append(f"{ipaddress.IPv4Address(sibling)}/{k}")
    return out
print(complement("10.0.0.0", 8))

Броят на маршрутите е равен на дължината на префикса и това е числото, което трябва да следите. Едно изключване на /21 струва 21 маршрута и остава четимо. Десет изключвания могат да струват няколкостотин, което обикновено означава, че дизайнът на мрежата трябва да се промени, а не генераторът.

Проверете адресното семейство и обхвата на тунела

IPv4 маршрутите не правят нищо за IPv6. Ако тунелът претендира за IPv6 адрес, IPv6 LAN префиксът се нуждае от собствено допълнение, генерирано със същата аритметика върху 128 бита. Честа причина изключването да работи на хартия, докато локалното устройство остава недостъпно, е следната: хостът е достигнат по IPv6, а само IPv4 е бил изрязан. Проверете двете семейства, преди да заключите, че математиката е грешна.

Обхватът е другата половина на проверката. Android проследява маршрутизацията по мрежа и по приложение, така че един и същ префикс може да минава през тунела за едно приложение и директно за друго в зависимост от това как е деклариран VPN.

Поставете DNS и правилата за приложения там, където им е мястото

Изключванията на маршрути не променят резолвера. Локално име на хост все още може да бъде отговаряно от DNS сървъра на тунела, докато адресът му се маршрутизира локално, а несъответствието се проявява като име, което се разрешава до нещо, което LAN не може да достигне. Задайте политиката на резолвера умишлено, с домейн за търсене или правило за разделен DNS. Не предполагайте, че маршрутите са го решили вместо вас.

addDisallowedApplication() държи едно приложение извън тунела; той не държи местоназначение извън за приложенията, които остават вътре. protect() позволява на сокет, който отворите, да заобиколи тунела и така тунелът избягва да улавя собствения си транспорт. Нито едно от двете не замества допълнението.

Проверявайте с търсене, не със собствения си списък

След като тунелът е вдигнат, попитайте устройството накъде всъщност отива адрес, вместо да препрочитате списъка, който сте изградили. При сборка, която доставя iproute2, adb shell ip route get 10.1.2.3 назовава интерфейса, избран от ядрото. Изпълнете същата команда за публичен адрес и потвърдете, че назовава тунела.

Още две проверки затварят цикъла. Първо разрешете локално име, след това изпълнете adb shell ip route get върху разрешения адрес и изисквайте интерфейс, който не е тунелът, защото успешен adb shell ping -c 1 host.lan доказва достижимост, а не пътя. Повторете теста за адресно семейство по IPv6, ако тунелът претендира за такова. Тествайте на нивото на API, което доставяте, защото начинът, по който VPN мрежите се показват на таблицата за маршрутизация, се е променял между версиите. Списък с маршрути, който изглежда правилен на хартия, и търсене, което избира грешен интерфейс, са различни дефекти и само вторият достига до потребителите.

Ако списъкът с изключвания продължава да расте, спрете да генерирате маршрути и преномерирайте отдалечената мрежа или стеснете тунела вместо това. Генераторът е заобикаляне на липсващия API, а не инструмент за дизайн.

Подобни статии