VpnService w Androidzie nie ma trasy wykluczenia i żadna ilość czytania API jej nie stworzy. Builder.addRoute() zawsze dodaje tylko miejsca docelowe do tunelu, więc utrzymanie segmentu LAN lokalnie oznacza, że ten segment nigdy nie trafia do listy tras. Zamiast tego dodajesz dopełnienie segmentu, wyrażone zwykłymi prefiksami. Praca jest arytmetyką i zawodzi w kilku przewidywalnych miejscach.

Przejdź przez poniższe kontrole po kolei. Każda zakłada, że poprzednia się utrzymała, więc zatrzymaj się na pierwszym błędzie zamiast łatać objawy niżej.

Zacznij od tego, co tunel deklaruje

Przekazanie 0.0.0.0/0 do addRoute() mówi Androidowi, że każde miejsce docelowe IPv4 należy do tunelu. Dlatego tak wiele klientów wydaje się niezdolnych do dzielonego tunelowania: żądają całej przestrzeni adresowej, a potem próbują z niej odejmować. Nic w kreatorze nie odejmuje. Bardziej szczegółowa trasa dla sieci, którą chciałeś zachować lokalnie, działa odwrotnie niż oczekiwano, bo wygrywa najdłuższy pasujący prefiks i ta sieć ląduje w tunelu.

Lista tras to cała polityka. Nie ma za nią weta dla pojedynczego celu, a dokumentacja Builder nie wymienia żadnej metody, która by je dawała.

Wygeneruj dopełnienie z arytmetyki prefiksów

Dopełnienie wykluczonego prefiksu to zbiór jego braci i sióstr na drodze do korzenia drzewa adresów. Dla każdej długości od 1 do wykluczonej długości weź blok tej długości zawierający twój prefiks, odwróć bit bezpośrednio pod prefiksem i wypisz wynik. Brzmi abstrakcyjnie, dopóki tego nie uruchomisz.

Wykluczenie 10.0.0.0/8 daje osiem tras: 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. Żadna z nich nie zawiera 10.0.0.0/8, a razem pokrywają wszystko inne.

Osiem linii generuje tę listę dla dowolnego prefiksu:

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))

Liczba tras jest równa długości prefiksu i to jest liczba, na którą trzeba uważać. Jedno wykluczenie /21 kosztuje 21 tras i pozostaje czytelne. Dziesięć wykluczeń może kosztować kilkaset, co zwykle oznacza, że zmienić trzeba projekt sieci, a nie generator.

Sprawdź rodzinę adresów i zakres tunelu

Trasy IPv4 nic nie robią dla IPv6. Jeśli tunel deklaruje adres IPv6, prefiks LAN IPv6 potrzebuje własnego dopełnienia, wygenerowanego tą samą arytmetyką na 128 bitach. Częstą przyczyną tego, że wykluczenie działa na papierze, a lokalne urządzenie pozostaje nieosiągalne, jest to: do hosta dotarto przez IPv6, a wycięto tylko IPv4. Sprawdź obie rodziny, zanim uznasz, że matematyka jest błędna.

Zakres to druga połowa kontroli. Android śledzi trasowanie według sieci i według aplikacji, więc ten sam prefiks może iść przez tunel dla jednej aplikacji, a bezpośrednio dla innej, zależnie od tego, jak zadeklarowano VPN.

Umieść reguły DNS i dla aplikacji tam, gdzie należą

Wykluczenia tras nie zmieniają resolvera. Lokalna nazwa hosta nadal może być rozwiązywana przez serwer DNS tunelu, podczas gdy jej adres jest trasowany lokalnie, a niezgodność objawia się jako nazwa rozwiązująca się na coś, do czego sieć LAN nie dotrze. Ustaw politykę resolvera świadomie, z domeną wyszukiwania lub regułą dzielonego DNS. Nie zakładaj, że trasy zdecydowały o tym za ciebie.

addDisallowedApplication() trzyma jedną aplikację poza tunelem; nie trzyma miejsca docelowego poza dla aplikacji, które zostają w środku. protect() pozwala otwartemu gniazdu ominąć tunel, i tak tunel unika przechwytywania własnego transportu. Żadne z nich nie zastępuje dopełnienia.

Weryfikuj wyszukiwaniem, nie własną listą

Gdy tunel jest podniesiony, zapytaj urządzenie, dokąd adres naprawdę trafia, zamiast ponownie czytać zbudowaną listę. Na kompilacji dostarczanej z iproute2 adb shell ip route get 10.1.2.3 nazywa interfejs wybrany przez jądro. Uruchom to samo polecenie dla adresu publicznego i potwierdź, że nazywa tunel.

Dwie kolejne kontrole zamykają pętlę. Najpierw rozwiąż lokalną nazwę, potem uruchom adb shell ip route get na rozwiązanym adresie i wymagaj interfejsu, który nie jest tunelem, bo udane adb shell ping -c 1 host.lan dowodzi osiągalności, a nie ścieżki. Powtórz test rodziny adresów na IPv6, jeśli tunel jej wymaga. Testuj na poziomie API, który dostarczasz, bo sposób eksponowania sieci VPN tabeli trasowania zmieniał się między wydaniami. Lista tras, która wygląda dobrze na papierze, i wyszukiwanie wybierające zły interfejs to różne błędy, a tylko drugi dociera do użytkowników.

Jeśli lista wykluczeń nadal rośnie, przestań generować trasy i przenumeruj sieć zdalną albo zwęź tunel. Generator jest obejściem brakującego API, a nie narzędziem projektowym.

Powiązane artykuły