Маршрутизація в одному вузлі
Навіщо це
Section titled “Навіщо це”Ви ставите Linux-машину між двома мережами, вмикаєте на ній усе, що треба, і
хости по обидва боки бачать її, але не бачать одне одного. Або сервер має два
мережевих підключення, і відповіді на запити, що прийшли через друге, чомусь
вертаються через перше й губляться. Або ping відповідає з одного
інтерфейсу й мовчить з іншого. Усі ці історії розв’язуються однаково: подивитися,
яке рішення про маршрут ухвалило ядро, і зрозуміти чому.
Маршрутизація в мережевому курсі має два масштаби. Цей модуль про один вузол: що він робить із кожним пакетом, коли той надходить чи виходить. Як маршрути потрапляють у таблицю від сусідніх маршрутизаторів, розглядає модуль 8.
Передумови. Префікси й підмережі, TTL, ICMP — модуль 6. Таблиця сусідів і ARP — модуль 5. Простори імен мережі — модуль 17 курсу «Операційні системи».
Рішення про наступний стрибок
Section titled “Рішення про наступний стрибок”Вузол, що має надіслати пакет, відповідає на єдине питання: кому саме передати його далі (наступний стрибок, next hop) і через який інтерфейс. Відповідь на нього дає таблиця маршрутів (routing table): список записів «адреси такого-то префікса досяжні так-то».
Далі все залежить від того, де адресат.
- Адресат у тій самій мережі, що й один з інтерфейсів, тобто безпосередньо досяжний. Тоді вузол питає ARP про MAC-адресу самого адресата й шле кадр йому напряму.
- Адресат деінде. Тоді вузол бере MAC-адресу шлюзу (gateway), тобто сусіднього маршрутизатора, а адресу IP у пакеті лишає справжню. Кадр іде до шлюзу, а пакет усередині — до адресата. На наступному вузлі те саме рішення приймається заново.
Шлюз у записі мусить сам бути безпосередньо досяжним, бо його MAC-адресу теж треба дізнатися через ARP, і ядро перевіряє це під час додавання маршруту.
Як виглядає таблиця
Section titled “Як виглядає таблиця”Запис складається з префікса й того, що з ним робити:
10.1.0.0/24 via 10.0.1.1 dev ha proto static metric 100Тут 10.1.0.0/24 — префікс, via 10.0.1.1 — шлюз (наступний стрибок),
dev ha — інтерфейс, metric — «ціна». Якщо via немає, адресат
безпосередньо досяжний через dev. Додаткові поля (proto, scope, src)
повідомляють, хто додав запис і яку адресу відправника ядро підставить.
Деякі записи з’являються самі. Коли ви призначаєте інтерфейсу адресу
10.0.1.2/24, ядро додає в таблицю маршрут до всієї підмережі:
10.0.1.0/24 dev ha proto kernel scope link src 10.0.1.2proto kernel означає, що його створило ядро. Без такого запису вузол не
знав би, що сусіди з підмережі досяжні напряму.
Шлюз має бути досяжним. Якщо спробувати додати маршрут через адресу, якої
немає в жодній підмережі інтерфейсів вузла, ядро відмовить:
Error: Nexthop has invalid gateway. Обійти це можна ключем onlink, який
каже «вважай, що шлюз у цій ланці, без перевірки», але навмисне користуються
цим рідко.
Найдовший префікс
Section titled “Найдовший префікс”Записи в таблиці накладаються: адреса 198.51.100.7 водночас належить
198.51.100.0/25, 198.51.100.0/24 і 0.0.0.0/0 (типовому маршруту,
усьому інтернету). Усі маршрутизатори обирають за одним правилом: за
найдовшим префіксом, тобто за найвужчим записом, який містить адресу.
Вужчий запис точніше описує адресу, широкі створюють «фон». Так можна задати загальне правило й поряд виняток; весь інтернет тримається на типовому маршруті плюс уточненнях. Довжина префікса важливіша за все інше, зокрема й за метрику.
Типовим маршрутом (default route) називають запис 0.0.0.0/0, префікс
довжини нуль. Йому підходить будь-яка адреса, тож він програє всім
конкретнішим і вступає в дію лише тоді, коли більше нічого не підійшло.
Метрика
Section titled “Метрика”Якщо два записи мають однаковий префікс, за довжиною не розрізнити. Тоді вирішує метрика: менша перемагає. Другий запис — резервний:
203.0.113.0/24 via 10.0.1.1 dev ha metric 100203.0.113.0/24 via 10.0.3.1 dev hc metric 200Поки інтерфейс ha живий, пакети йдуть через 10.0.1.1. Якщо він упаде, ядро
викине з таблиці всі маршрути через нього, і працюватиме другий запис.
Зворотна сторона: коли ha повернеться, маршрути через нього самі не
з’являться. Їх мав би повернути той, хто їх додав (мережевий менеджер, демон
маршрутизації або ваш скрипт).
Маршрути-відмови
Section titled “Маршрути-відмови”Маршрут не обов’язково кудись веде. Спеціальні типи кажуть ядру, що робити з пакетами до цього префікса:
blackhole: мовчки відкинути;unreachable: відкинути, а для пересланих пакетів відповісти відправнику ICMP «host unreachable»;prohibit: відкинути, а для пересланих пакетів відповісти ICMP «administratively prohibited».
Ними користуються, щоб не пускати трафік до певної мережі, не завантажуючи
файрвол, або щоб пакети не «витікали» за типовим маршрутом. Програма, що
відправляє пакети на такі адреси, отримує різні помилки: для blackhole —
Invalid argument, для unreachable — No route to host, для prohibit —
Permission denied.
Вузол як маршрутизатор
Section titled “Вузол як маршрутизатор”Звичайний хост відкидає пакети, адресовані не йому. Щоб Linux працював маршрутизатором, треба ввімкнути пересилання:
sysctl -w net.ipv4.ip_forward=1З вимкненим пересиланням вузол приймає пакет, бачить, що адреса призначення
не його, і викидає, записуючи в лічильник IpInAddrErrors. Коли «клієнти
бачать маршрутизатор, але не бачать одне одного», найчастіше забули
ip_forward.
Далі для пакета, що пересилається, відбувається таке:
-
Пакет надходить на інтерфейс, ядро перевіряє, чи адреса призначення не належить самому вузлу (шукає в таблиці
local). Якщо належить, пакет доставляється локальним процесам і далі не їде. -
Інакше ядро шукає маршрут за адресою призначення й обирає найдовший префікс.
-
TTL зменшується на одиницю. Якщо вийшов нуль, пакет відкидається, а відправнику йде ICMP
time exceeded(саме на цьому тримаєтьсяtraceroute). -
Якщо розмір більший за MTU вихідного інтерфейсу: фрагментація або ICMP «frag needed» залежно від біта DF (модуль 6).
-
Ядро знаходить (через ARP, якщо потрібно) MAC-адресу наступного стрибка, будує новий кадр і відправляє.
Зауважте: кадр за цей час замінився цілком, а пакет у звичайному разі зазнав лише двох змін: TTL і контрольна сума заголовка.
Перевірка зворотного шляху
Section titled “Перевірка зворотного шляху”Пакет надійшов на інтерфейс із певною адресою відправника. Ядро може перевірити,
чи справді відповідь на цю адресу пішла б через той самий інтерфейс. Це
перевірка зворотного шляху (reverse path filtering, rp_filter). Вона
відкидає пакети з підмінених адрес, але ламає асиметричну маршрутизацію, коли
запит приходить одним шляхом, а відповідь піде іншим. Значення параметра:
0— вимкнено;1— строго: відповідь мусить піти тим самим інтерфейсом, яким надійшов пакет;2— вільно: достатньо, щоб до відправника існував будь-який маршрут.
Діє більше з двох значень: net.ipv4.conf.all.rp_filter та значення самого
інтерфейсу. Якщо пакети «зникають» на вузлі з кількома інтерфейсами, першим
підозрюйте rp_filter і дивіться лічильник IPReversePathFilter у nstat.
Політика маршрутизації: кілька таблиць
Section titled “Політика маршрутизації: кілька таблиць”Досі все вирішувала одна таблиця й лише адреса призначення. Але трапляються задачі, де вибір має залежати від чогось іншого. Сервер має два підключення до різних провайдерів. Запит надійшов із адреси першого провайдера, тож відповідь треба відправити через нього, а типовий маршрут веде до другого. Без додаткових засобів відповідь піде не туди, і провайдер її відкине.
Linux розв’язує це політикою маршрутизації. Таблиць маршрутів може бути
багато (кожна має номер; main — 254, local — 255,
default — 253), а правила (ip rule) вирішують, яку таблицю
переглядати. Кожне правило має пріоритет і умову (адреса відправника,
призначення, мітка пакета fwmark, вхідний інтерфейс), а також таблицю.
Ядро проходить правила за зростанням пріоритету; перша таблиця, що дала
маршрут, вирішує.
Для відповіді через потрібного провайдера кожне його підключення отримує власну таблицю з власним типовим маршрутом, а правило «якщо відправник — моя адреса в цій мережі, дивись у відповідну таблицю» вмикає її. Це маршрутизація за адресою відправника (source-based routing). Не плутайте її зі старою опцією IP source routing, у якій маршрут задавав сам пакет: ту опцію давно всюди вимикають.
ECMP: кілька рівноцінних шляхів
Section titled “ECMP: кілька рівноцінних шляхів”Інколи до префікса ведуть кілька однаково добрих шляхів. Замість того щоб тримати один у резерві, можна навантажувати їх усі: ECMP (equal-cost multipath). У таблиці такий запис має кілька наступних стрибків із вагами.
Складніше з розподілом пакетів. Якщо розкидати їх по черзі, пакети
одного з’єднання потраплять у різні шляхи з різною затримкою й прийдуть
у переплутаному порядку, а TCP сприймає це як втрату (модуль 11). Пакети
одного потоку мають завжди йти одним шляхом, а різні потоки — різними.
Для цього ядро обчислює хеш від полів пакета й обирає за ним наступний стрибок.
Які поля брати, задає net.ipv4.fib_multipath_hash_policy: 0 — лише адреси
відправника й призначення (усі з’єднання між двома хостами підуть одним
шляхом), 1 — ще й порти (різні з’єднання між тими самими хостами
розтечуться).
VRF: окремі таблиці для цілих мереж
Section titled “VRF: окремі таблиці для цілих мереж”VRF (virtual routing and forwarding) дає змогу мати на одному вузлі кілька незалежних екземплярів маршрутизації. Інтерфейси приписують до VRF, і кожна VRF має власну таблицю, так що адреси різних клієнтів можуть збігатися, не заважаючи одна одній. Так роблять провайдери, коли обслуговують клієнтів із перетинними приватними адресами. У Linux VRF зроблено як пристрій, пов’язаний із таблицею, а інтерфейси додаються до нього як підлеглі. Подібної ізоляції добивається й простір імен мережі, який ми вже використовуємо, але VRF легша: процеси лишаються в тому самому просторі, а розділені лише маршрути.
Як це насправді в Linux
Section titled “Як це насправді в Linux”Будуємо: хост h із двома підключеннями, ha до r1 (10.0.1.0/24) і hc
до r2 (10.0.3.0/24). Далі r1 і r2 ведуть до вузла x, який
має ще адресу 198.51.100.7. Топологію будує цей скрипт (в кінці модуля
не забудьте прибрати простори імен):
for n in h r1 r2 x; do sudo ip netns add $n; sudo ip netns exec $n ip link set lo up; doneL() { sudo ip link add $2 type veth peer name $4; sudo ip link set $2 netns $1; sudo ip link set $4 netns $3; }L h ha r1 rb; L h hc r2 rd; L r1 re x xf; L r2 rg x xia() { sudo ip netns exec $1 ip addr add $2 dev $3; sudo ip netns exec $1 ip link set $3 up; }a h 10.0.1.2/24 ha; a r1 10.0.1.1/24 rb; a h 10.0.3.2/24 hc; a r2 10.0.3.1/24 rda r1 10.1.0.1/24 re; a x 10.1.0.2/24 xf; a r2 10.2.0.1/24 rg; a x 10.2.0.2/24 xisudo ip netns exec x ip addr add 198.51.100.7/32 dev loБез жодних маршрутів на h є лише ті, що створило ядро:
sudo ip netns exec h ip route10.0.1.0/24 dev ha proto kernel scope link src 10.0.1.210.0.3.0/24 dev hc proto kernel scope link src 10.0.3.2Найдовший префікс наживо
Section titled “Найдовший префікс наживо”Додамо типовий маршрут і два, що накладаються, а тоді спитаємо ядро, за яким воно
піде. Для цього є ip route get: вона нічого не відправляє, а показує рішення.
sudo ip netns exec h ip route add default via 10.0.1.1sudo ip netns exec h ip route add 198.51.100.0/24 via 10.0.3.1sudo ip netns exec h ip route add 198.51.100.0/25 via 10.0.1.1for d in 10.0.1.50 198.51.100.7 198.51.100.200 8.8.8.8; do sudo ip netns exec h ip route get $ddone10.0.1.50 dev ha src 10.0.1.2 uid 0198.51.100.7 via 10.0.1.1 dev ha src 10.0.1.2 uid 0198.51.100.200 via 10.0.3.1 dev hc src 10.0.3.2 uid 08.8.8.8 via 10.0.1.1 dev ha src 10.0.1.2 uid 0Перший — безпосередньо досяжний (via немає). Другий вибрав /25, а третій,
що лежить в іншій половині, лише /24. Останній підійшов тільки під типовий.
Зверніть увагу на src: разом з інтерфейсом ядро обирає й адресу
відправника, яку підставить у пакет (для hc це 10.0.3.2). Власні адреси
вузла живуть в окремій таблиці local (ip route show table local):
ip route get 10.0.1.2 відповідає local 10.0.1.2 dev lo.
Пересилання і rp_filter
Section titled “Пересилання і rp_filter”h уже відправляє все невідоме через r1 (типовий маршрут). Щоб відповіді вузла
x могли повернутися, потрібен зворотний маршрут, і тоді перевіримо, що дає
ip_forward:
sudo ip netns exec x ip route add 10.0.1.0/24 via 10.1.0.1sudo ip netns exec h ping -c1 -W1 10.1.0.2sudo ip netns exec r1 sysctl -w net.ipv4.ip_forward=1sudo ip netns exec h ping -c1 -W1 10.1.0.2sudo ip netns exec r1 nstat -az IpInAddrErrors IpForwDatagrams1 packets transmitted, 0 received, 100% packet loss, time 0msnet.ipv4.ip_forward = 11 packets transmitted, 1 received, 0% packet loss, time 0msIpInAddrErrors 1 0.0IpForwDatagrams 2 0.0Перший пінг пропав, і IpInAddrErrors на r1 показує, куди: пакет на чужу адресу
відкинуто. Після вмикання пересилання лічильник IpForwDatagrams росте
(2 — запит і відповідь).
Тепер rp_filter. Для цього досліду r2 теж має пересилати
(sudo ip netns exec r2 sysctl -w net.ipv4.ip_forward=1), а x потрібен маршрут до
10.0.3.0/24 через r2 (sudo ip netns exec x ip route add 10.0.3.0/24 via 10.2.0.1).
Зробимо відправку несиметричною: на r1 маршрут до
10.0.3.0/24 ведемо не назад через rb, а через x (ip route replace 10.0.3.0/24 via 10.1.0.2 dev re), а h відправляє пінг із адреси 10.0.3.2 через ha:
sudo ip netns exec h ping -c1 -W1 -I 10.0.3.2 10.1.0.2 # rp_filter = 0sudo ip netns exec r1 sysctl -w net.ipv4.conf.all.rp_filter=1 net.ipv4.conf.rb.rp_filter=1sudo ip netns exec h ping -c1 -W1 -I 10.0.3.2 10.1.0.2 # строгоsudo ip netns exec r1 nstat -az TcpExtIPReversePathFiltersudo ip netns exec r1 sysctl -w net.ipv4.conf.all.rp_filter=2 net.ipv4.conf.rb.rp_filter=2sudo ip netns exec h ping -c1 -W1 -I 10.0.3.2 10.1.0.2 # вільно1 packets transmitted, 1 received, 0% packet loss, time 0ms1 packets transmitted, 0 received, 100% packet loss, time 0msTcpExtIPReversePathFilter 1 0.01 packets transmitted, 1 received, 0% packet loss, time 0msЗі строгим режимом пакет відкинуто: відповідь на 10.0.3.2 пішла б не через rb,
а з вільним (2) достатньо, що маршрут до відправника існує взагалі. Лічильник
IPReversePathFilter каже, що пакет загинув саме тут. У вашій системі
може стояти 1 або 2: саме ядро типово ставить 0, але дистрибутиви
зазвичай вмикають перевірку через sysctl.d.
Метрика, відмови
Section titled “Метрика, відмови”sudo ip netns exec h ip route add 203.0.113.0/24 via 10.0.1.1 metric 100sudo ip netns exec h ip route add 203.0.113.0/24 via 10.0.3.1 metric 200sudo ip netns exec h ip route get 203.0.113.5sudo ip netns exec h ip link set ha downsudo ip netns exec h ip route show 203.0.113.0/24sudo ip netns exec h ip link set ha upsudo ip netns exec h ip route show 203.0.113.0/24203.0.113.5 via 10.0.1.1 dev ha src 10.0.1.2 uid 0203.0.113.0/24 via 10.0.3.1 dev hc metric 200203.0.113.0/24 via 10.0.3.1 dev hc metric 200Поки ha живий, вигравав менший metric 100. Після падіння лишився лише
резервний, і після повернення ha запис із метрикою 100 не
повернувся. Так само додаються ip route add blackhole 192.0.2.0/24,
unreachable … і prohibit ….
Кілька таблиць
Section titled “Кілька таблиць”sudo ip netns exec h ip route add default via 10.0.3.1 dev hc table 100sudo ip netns exec h ip rule add from 10.0.3.2 lookup 100 priority 1000sudo ip netns exec h ip rule showsudo ip netns exec h ip route get 8.8.8.8 from 10.0.3.2sudo ip netns exec h ip route get 8.8.8.8 from 10.0.1.20: from all lookup local1000: from 10.0.3.2 lookup 10032766: from all lookup main32767: from all lookup default8.8.8.8 from 10.0.3.2 via 10.0.3.1 dev hc table 100 uid 08.8.8.8 from 10.0.1.2 via 10.0.1.1 dev ha uid 0Без правила обидві адреси пішли б однаково, за типовим маршрутом таблиці main.
Рядок table 100 у виводі показує, де знайшовся маршрут. Коли відповіді
йдуть «не туди», починайте з ip route get … from ….
sudo ip netns exec h ip route del defaultsudo ip netns exec h ip route add default nexthop via 10.0.1.1 dev ha weight 1 nexthop via 10.0.3.1 dev hc weight 1sudo ip netns exec h ip route show defaultfor p in 1000 1001 1002; do sudo ip netns exec h ip route get 8.8.8.8 ipproto tcp sport $p dport 80 | head -1donedefault nexthop via 10.0.1.1 dev ha weight 1 nexthop via 10.0.3.1 dev hc weight 18.8.8.8 via 10.0.3.1 dev hc src 10.0.3.2 uid 08.8.8.8 via 10.0.3.1 dev hc src 10.0.3.2 uid 08.8.8.8 via 10.0.3.1 dev hc src 10.0.3.2 uid 0За типовою політикою 0 хеш бере лише адреси, тож усі порти дають один
шлях. Зі значенням 1:
sudo ip netns exec h sysctl -w net.ipv4.fib_multipath_hash_policy=1різні порти розходяться між ha та hc: у цьому запуску порти
1000, 1001, 1003, 1004 пішли через hc, а 1002 і 1005 через ha.
VRF потребує модуля ядра vrf. Якщо ip link add … type vrf відповідає
Unknown device type, модуля немає (так буває в контейнерах); на ВМ
з ядром дистрибутива він є:
sudo ip link add vrf-red type vrf table 10sudo ip link set vrf-red upsudo ip link set dev eth1 master vrf-redip route show vrf vrf-redsudo ip vrf exec vrf-red ping 192.0.2.1eth1 потрапляє до VRF vrf-red, усі його маршрути тепер у таблиці 10,
ip route show vrf vrf-red їх показує, а ip vrf exec запускає команду
з прив’язкою до цієї VRF.
Прибрати за собою: sudo ip netns delete h і так само r1, r2, x.
Типові помилки розуміння
Section titled “Типові помилки розуміння”«Маршрут з меншою метрикою завжди виграє». Метрика порівнюється лише
для записів з однаковим префіксом. Запис /25 з метрикою 1000 переможе
/24 з метрикою 1: довжина префікса важливіша.
«Пакет до віддаленої мережі адресується шлюзу». Адреса призначення в пакеті лишається адресою кінцевого отримувача, шлюзу адресований лише кадр. Шлюз у записі потрібен, щоб знати, чию MAC-адресу поставити в кадр.
«Якщо ввімкнути ip_forward, вузол стане маршрутизатором». Він лише
почне пересилати чужі пакети. Щоб вони дійшли, потрібні маршрути в обох
напрямках на всіх вузлах шляху: на сусідах має бути маршрут туди й назад.
Часто буває так: маршрут прописали, а зворотного немає, і запити доходять,
а відповіді ні.
«Якщо з одного боку ping проходить, маршрут працює в обидва боки».
Запит і відповідь обираються незалежно. Вони можуть іти різними шляхами
(асиметрія), і rp_filter чи файрвол на одному з них мовчки викине
пакет.
«У Linux одна таблиця маршрутів». Їх багато, а ip route без аргументів
показує лише main. Інші (ip route show table all, ip rule) легко
пропустити, і маршрути, що діють, але не видно, збивають з пантелику.
«ECMP розкидає пакети порівну». Розкидає потоки, а не пакети. Один великий потік на одному шляху не розділиться, і шляхи можуть завантажуватися нерівномірно.
Перевір себе
Лабораторна
Section titled “Лабораторна”B2. Linux як маршрутизатор: три
підмережі, статичні маршрути й NAT. Тут ви самі прописуєте таблицю маршрутів,
вмикаєте ip_forward і перевіряєте результат за допомогою ip route get та traceroute.
B7. Налагодження зламаної мережі:
серед несправностей є маршрутна; ip route get і ip rule show — перші
інструменти на підозру «пакет іде не туди».
Джерела
Section titled “Джерела”- Kurose, Ross, Computer Networking: A Top-Down Approach, розділ про мережевий рівень і пересилання
- Peterson, Davie, Computer Networks: A Systems Approach, розділ про маршрутизацію
- RFC 1812 (вимоги до IPv4-маршрутизаторів)
man 8 ip-route,man 8 ip-rule,man 8 ip-vrf,man 8 nstat- Документація ядра Linux:
Documentation/networking/ip-sysctl.rst(ip_forward,rp_filter,fib_multipath_hash_policy) іDocumentation/networking/vrf.rst