Короткий ответ

Стек выбирает маршрут так: (1) самая длинная маска, которая покрывает цель (longest prefix match); (2) среди равных prefix — меньшая метрика / приоритет таблицы; (3) connected/on-link значит «ARP на этом NIC», не «через шлюз». Default 0.0.0.0/0 — самый короткий префикс, проигрывает любому 10.0.20.0/24. Не читайте route print сверху вниз как приоритет строк.

Проверка истины: Windows Find-NetRoute -RemoteIPAddress, Ubuntu ip route get. Якоря: LAN 10.0.10.0/24, gw 10.0.10.1, DNS 10.0.10.2.

Симптомы и как отличить

  • Копия на 10.0.10.10 внезапно идёт в VPN;
  • интернет через Wi‑Fi при воткнутом кабеле;
  • ping 10.0.20.10 unreachable при «живом интернете».

Отличия: нет подходящего префикса — нет маршрута. Prefix врёт из-за маски — маска. Таблица верная, путь разный обратно — асимметрия.

Возможные причины

  1. Два default с разными метриками.
  2. VPN опубликовал 10.0.0.0/8 и перебил LAN /24 или наоборот недостаточно специфичен.
  3. Persistent route -p из старой заявки.
  4. Policy routing (fwmark) мимо main — таблица, которую вы смотрите, не та.
  5. Weak Host / несколько NIC, ответ не с того интерфейса.

Диагностика

1. Снять таблицу целиком

Get-NetRoute -AddressFamily IPv4 |
  Sort-Object @{e={ [int]($_.DestinationPrefix -split '/')[1] }; Descending=$true}, RouteMetric |
  Format-Table DestinationPrefix, NextHop, RouteMetric, ifIndex, InterfaceAlias
Get-NetIPConfiguration
ip route
ip -4 route show table all
ip rule

ip rule важнее, чем кажется: lookup 220 у WireGuard/policy может обойти main.

2. Спросить стек про конкретный IP

Find-NetRoute -RemoteIPAddress 10.0.10.2 | Format-List
Find-NetRoute -RemoteIPAddress 10.0.20.10 | Format-List
Find-NetRoute -RemoteIPAddress 1.1.1.1 | Format-List
ip route get 10.0.10.2
ip route get 10.0.20.10
ip route get 1.1.1.1

Смотрите dev, via, src. via 10.0.10.1 — через шлюз. Нет via — on-link, будет ARP.

3. Longest prefix на пальцах

Цель 10.0.10.2:

Префикс в таблицеДлинаПокрывает 10.0.10.2?
0.0.0.0/00да
10.0.0.0/88да
10.0.10.0/2424да
10.0.10.2/3232да

Победит /32, иначе /24, не default. Поэтому host-маршрут на DNS важнее «широкого VPN /8».

Цель 10.0.20.10 при наличии только 10.0.10.0/24 и default: победит default via 10.0.10.1 (если шлюз умеет дальше). Если VPN дал 10.0.20.0/24 — он длиннее default и победит.

4. Метрика при равной длине

Два 0.0.0.0/0: Ethernet metric 25, Wi‑Fi 50 — кабель. Наоборот — «интернет уехал в Wi‑Fi». Windows: RouteMetric + InterfaceMetric. Ubuntu: metric в ip route.

Get-NetIPInterface -AddressFamily IPv4 | Format-Table InterfaceAlias, InterfaceMetric, ConnectionState

10.0.0.0/8 connected на VPN-NIC: 10.0.10.1 могут пытаться ARP в туннель, не в LAN. Ping шлюза офиса умрёт. Лечится более специфичным 10.0.10.0/24 dev eth0 (обычно connected LAN сам /24 и побеждает /8). Если LAN почему-то /32 only — сломаетесь.

Сверьте ARP: Get-NetNeighbor 10.0.10.1 на каком InterfaceAlias.

Решение

Сценарий A. Нужен LAN, VPN слишком широкий

Добавьте split: 10.0.20.0/24 в туннель, не 10.0.0.0/8. LAN 10.0.10.0/24 должен остаться connected на eth.

Сценарий B. Два default

Поднимите InterfaceMetric кабеля выше (меньше число = предпочтительнее). Или отключите лишний адаптер на тесте.

Set-NetIPInterface -InterfaceAlias 'Ethernet' -InterfaceMetric 10
Set-NetIPInterface -InterfaceAlias 'Wi-Fi' -InterfaceMetric 50

Сценарий C. Мусор persistent

Get-NetRoute -PolicyStore PersistentStore
Remove-NetRoute -DestinationPrefix '0.0.0.0/0' -NextHop 192.168.0.1 -Confirm:$false

Только свой ошибочный next-hop.

Сценарий D. Добавить филиал на шлюзе, не на ПК

Клиенту достаточно default 10.0.10.1. Маршрут 10.0.20.0/24 живёт на 10.0.10.1.

6. Разбор на живых адресах лаборатории

Возьмите три цели: 10.0.10.2 (DNS в LAN), 10.0.20.10 (филиал), 1.1.1.1 (интернет). На здоровом ПК 10.0.10.10/24 gw 10.0.10.1 ожидайте: DNS on-link на Ethernet; филиал via 10.0.10.1 либо via VPN, если split так задуман; интернет via default. Если 10.0.10.2 via VPN — слишком широкий prefix туннеля, чините AllowedIPs. Если филиал on-link — маска /8 или /16. Два default: смотрите InterfaceMetric, не порядок строк route print. PersistentStore: старый 192.168.0.1 победит в командировке и в офисе — удалите. ip rule на Ubuntu: таблица 220 с более специфичным префиксом важнее main default; смотрите ip route get, не только ip route. Не учите коллег «верхняя строка главная». После VPN disconnect таблица должна вернуть LAN default без ручного route add. Проверка: три Find-NetRoute/ip route get, ping шлюза, traceroute филиала. Соседняя статья про отсутствие маршрута начинается там, где get уже unreachable.

Добавьте в шпаргалку: /32 > /24 > /16 > /8 > /0; метрика только при равной длине; NextHop 0.0.0.0 на Windows = on-link; не путать AD Cisco с метрикой хоста.

Как проверить, что проблема устранена

Find-NetRoute -RemoteIPAddress 10.0.10.2
Find-NetRoute -RemoteIPAddress 10.0.20.10
Find-NetRoute -RemoteIPAddress 1.1.1.1
ping -n 2 10.0.10.1
ping -n 2 10.0.10.2
tracert -d 10.0.20.10
ip route get 10.0.10.2
ip route get 10.0.20.10
ip route get 1.1.1.1

10.0.10.2 — через LAN NIC, не VPN. Филиал — ожидаемый via. Интернет — default via 10.0.10.1 (или осознанный VPN). Повторите методику трёх инструментов.

Если не помогло

  • ip route get верный, пакеты всё равно не там — nft PBR/mark, смотрите ip rule.
  • IPv6 default живой, IPv4 таблица «для галочки».
  • Windows Hyper-V vEthernet default metric сюрприз.
  • Multipath ECMP: nexthop несколько, hash делит потоки.

Профилактика

  • Не публиковать 10.0.0.0/8 в VPN без нужды.
  • Инвентаризация PersistentStore.
  • Документ: какие prefix через какие if.
  • Метрики NIC: кабель < Wi‑Fi, VPN по политике split.

FAQ

Почему в route print сверху 0.0.0.0, и все думают, что он главный?

Это default, он последний по длине. Windows группирует по сетевым маскам: сначала /32, потом /24… Читайте по маске, не по «первой строке».

/24 и /24 на двух NIC до одной сети?

Одинаковая длина — метрика и connected. Два интерфейса в один L2 без нужды — петля/непредсказуемость.

NextHop 0.0.0.0 в Windows?

On-link. Не «шлюз ноль».

ip route get vs ping как проверка?

get показывает выбор. Ping проверяет, что next-hop отвечает. Нужны оба.

Метрика 0 лучше 1?

Меньше — предпочтительнее. 0 на Linux часто connected. Не ставьте 0 на default вручную без понимания.

Нужно ли знать ADSC/administrative distance как в Cisco?

На хосте Windows/Ubuntu вы видите метрику ОС, не Cisco AD. На самом маршрутизаторе 10.0.10.1 — уже его таблица (OSPF/static distance). Не путайте route print ПК с show ip route ядра.