Раздел 1 · Сети — глава 1.2
Сети: IP, ARP и NAT
Три темы, которые кажутся школьными, пока не начинаешь объяснять их вслух. Ключ ко всем трём — одна история: что именно происходит с пакетом от момента, когда приложение вызвало send(), до момента, когда электрический сигнал ушёл в провод. Расскажем её один раз подробно, и дальше ARP и NAT встанут на свои места сами.
§1IP-адрес и маска: что они на самом деле разделяют
IP-адрес — это 32 бита, которые для удобства пишут четырьмя байтами. Маска делит эти биты на две части: какая сеть и какой хост в этой сети.
Число после слэша — это просто количество единичных бит в маске. /24 — 24 бита сети и 8 бит хоста, то есть 256 адресов, из которых два служебных. /16 — 65536 адресов. Полезный ориентир: каждый шаг маски вниз удваивает размер сети, /25 — 128 адресов, /26 — 64, /30 — четыре (два используемых, классика для линка между маршрутизаторами), /32 — ровно один адрес.
Смысл всего этого ровно один: по маске хост определяет, находится ли получатель с ним в одном сегменте. Это и есть единственное решение, которое принимает отправитель, и из него вытекает вся дальнейшая механика.
Заодно стоит помнить приватные диапазоны, потому что они всплывают в каждом разговоре про NAT: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16. Эти адреса не маршрутизируются в интернете — любой пакет с таким адресом назначения провайдер отбросит. Отдельно есть 100.64.0.0/10 — диапазон для CGNAT, который провайдеры используют между своим оборудованием и абонентами.
§2Решение о маршруте: два случая, и всё
Когда ядру нужно отправить пакет, оно смотрит в таблицу маршрутизации и выбирает самый специфичный подходящий маршрут — то есть с самой длинной маской (longest prefix match). Дальше возможны ровно два исхода.
$ ip route
default via 192.168.10.1 dev eth0 # 0.0.0.0/0 — всё остальное
10.244.0.0/16 via 192.168.10.20 dev eth0 # сеть подов через другой узел
192.168.10.0/24 dev eth0 proto kernel scope link src 192.168.10.37
↑ моя сеть, напрямую
Случай первый: получатель в моей сети (маршрут со scope link, без via). Пакет надо отдать напрямую. Для этого нужен MAC-адрес получателя — и вот здесь вступает ARP.
Случай второй: получателя нет в моей сети (маршрут с via). Пакет надо отдать маршрутизатору, чтобы он передал его дальше. IP-адрес назначения в пакете при этом остаётся адресом конечного получателя — но MAC в кадре будет MAC-адресом маршрутизатора. Значит, нужен MAC маршрутизатора — и снова ARP.
Вот это и есть та самая мысль, которую хотят услышать на собесе: IP-адреса в пакете отвечают за то, куда он идёт в конечном счёте; MAC-адреса в кадре — только за то, кому его передать на следующем шаге. IP от начала до конца остаётся тем же (кроме NAT), а MAC переписывается на каждом маршрутизаторе.
§3ARP: как узнать MAC по IP
ARP решает единственную задачу — узнать канальный адрес того, кому надо передать кадр прямо сейчас. Работает предельно просто: широковещательный вопрос всем, персональный ответ от одного.
Пока идёт ARP-запрос, исходный пакет ждёт в очереди. Если ответа нет, ядро повторяет запрос несколько раз и в итоге возвращает приложению ошибку EHOSTUNREACH — «нет маршрута до хоста». Именно поэтому ping до соседа в той же сети при отключённом кабеле выдаёт «Destination Host Unreachable», а до адреса за маршрутизатором — просто молчит и таймаутит: в первом случае ошибка локальная и известна сразу, во втором пакет ушёл и просто не вернулся.
$ ip neigh # современная замена arp -n
192.168.10.1 dev eth0 lladdr 00:0c:29:aa:bb:cc REACHABLE
192.168.10.50 dev eth0 lladdr 00:1a:2b:3c:4d:5e STALE
192.168.10.99 dev eth0 FAILED ← никто не ответил
Состояния записей стоит понимать: REACHABLE — недавно подтверждена, STALE — устарела, но будет использована и проверена при следующей отправке, FAILED — ответа не было. Записи живут порядка минут и обновляются лениво.
Из природы ARP следуют два свойства, важных на практике. Первое: ARP работает только в пределах широковещательного домена — за маршрутизатором он не ходит, потому что маршрутизаторы не пересылают broadcast. Второе: ARP не имеет никакой аутентификации. Любой в сегменте может ответить на чужой запрос и увести трафик на себя (ARP spoofing). В доверенной сети это не проблема, в общей — основа классической атаки посередине; защищаются на коммутаторе (Dynamic ARP Inspection) или шифрованием.
§4Путь пакета целиком, от сокета до провода
Соберём всё вместе. Клиент 192.168.1.10 открывает соединение с сервером 10.20.30.40, между ними два маршрутизатора.
Если по дороге стоит NAT — а он стоит почти всегда — то на нём поменяется и IP источника. Это единственное исключение из правила «IP не меняются», и оно как раз следующий раздел.
Спросят
«Что меняется в пакете при прохождении маршрутизатора, а что — при прохождении коммутатора?» — Маршрутизатор переписывает оба MAC-адреса (источник — свой, назначение — следующего хопа), уменьшает TTL и пересчитывает контрольную сумму IP-заголовка; IP-адреса не трогает. Коммутатор не меняет ничего — он работает на втором уровне, смотрит только MAC назначения и отдаёт кадр в порт по своей таблице. Если коммутатор не знает нужный MAC, он рассылает кадр во все порты, кроме входящего.
§5Где ARP всплывает в реальной работе: failover виртуального IP
Отдельно вынесено, потому что это ровно тот случай, где абстрактное знание про ARP превращается в понимание боевого инцидента — и потому что VIP с HAProxy встречается почти у всех.
Схема стандартная: два узла, между ними плавающий виртуальный адрес, которым управляет keepalived (VRRP). Активный узел держит VIP на своём интерфейсе и обслуживает трафик, резервный ждёт.
Теперь представь, что активный узел умер и VIP переехал на резервный. IP-адрес тот же, а вот MAC-адрес за ним — другой, потому что это физически другая сетевая карта. И все, кто общался с этим VIP, имеют в своих ARP-кешах старую пару «VIP → MAC мёртвого узла». Они продолжат честно отправлять кадры на MAC, которого больше нет, — и трафик будет уходить в никуда, пока записи не протухнут. Это минуты полного простоя при формально успешном файловере.
Решается gratuitous ARP — «беспричинным» ARP-объявлением. Новый владелец адреса сразу после захвата VIP рассылает широковещательно ARP-пакет вида «192.168.1.100 — это я, вот мой MAC», хотя никто не спрашивал. Все, кто его слышит, обновляют записи в кеше немедленно. Keepalived делает это автоматически (и обычно шлёт несколько пакетов подряд, на случай потери), но полезно знать, что происходит.
# поймать момент переезда VIP
$ tcpdump -i eth0 -n arp
ARP, Reply 192.168.1.100 is-at 00:1a:2b:cc:dd:ee, length 28
ARP, Reply 192.168.1.100 is-at 00:1a:2b:cc:dd:ee, length 28
ARP, Reply 192.168.1.100 is-at 00:1a:2b:cc:dd:ee, length 28
↑ gratuitous ARP: новый узел объявляет о себе
# отправить вручную, если что-то залипло
$ arping -U -I eth0 -c 3 192.168.1.100
Грабли
Если VIP переехал, а клиенты продолжают ходить на мёртвый узел, причин обычно две. Либо gratuitous ARP не отправляется или блокируется — некоторые коммутаторы и облачные сети фильтруют такие пакеты, и тогда VRRP в облаке просто не работает, там нужны API-вызовы к провайдеру для переноса адреса. Либо между клиентами и VIP стоит маршрутизатор, и обновлять кеш нужно ему, а не клиентам, — а он может держать записи дольше и игнорировать беспричинные объявления по соображениям безопасности.
§6NAT изнутри: подмена адресов и таблица трансляций
NAT появился как затычка от нехватки IPv4-адресов и остался навсегда, попутно став ещё и способом изоляции сетей. Идея простая: маршрутизатор на границе переписывает адреса в проходящих пакетах и запоминает, что и на что поменял, чтобы обратный трафик вернуть правильному хозяину.
SNAT / masquerade — подмена источника, для исходящих
Хост в приватной сети хочет в интернет. Его адрес 192.168.1.10 в интернете не маршрутизируется — ответ просто не найдёт дорогу назад. Поэтому шлюз подменяет адрес источника на свой публичный, а чтобы различать десятки хостов за одним адресом, заодно подменяет и порт источника.
Строго говоря, подмену вместе с портом называют PAT или NAPT, а в мире Linux — masquerade, если внешний адрес берётся автоматически с интерфейса (нужно для динамических адресов), и SNAT, если адрес прописан явно (быстрее, потому что не надо каждый раз читать адрес интерфейса).
DNAT — подмена назначения, для входящих
Обратная задача: снаружи стучатся на публичный адрес, а обслужить должен внутренний сервер. Шлюз переписывает адрес назначения. Это и есть «проброс порта».
# весь исходящий трафик из 192.168.1.0/24 наружу — под адресом шлюза
iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth0 -j MASQUERADE
# входящие на 443 отдать внутреннему веб-серверу
iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 443 \
-j DNAT --to-destination 192.168.1.20:443
Почему цепочки именно такие: DNAT делается в PREROUTING, то есть до принятия решения о маршрутизации, — иначе ядро отправило бы пакет не туда, ведь маршрут выбирается по адресу назначения. SNAT делается в POSTROUTING, то есть после выбора маршрута и исходящего интерфейса, — иначе было бы неизвестно, чей адрес подставлять. Это любимый уточняющий вопрос, и логика в нём абсолютно прозрачная.
§7Conntrack: где живёт вся правда
Ключевая деталь, которую упускают: правила NAT применяются только к первому пакету соединения. Дальше работает conntrack — подсистема ядра, отслеживающая состояние соединений. Она запоминает трансляцию, и все последующие пакеты обрабатываются по записи в таблице, минуя правила. Это и делает NAT быстрым.
Та же подсистема обеспечивает работу stateful-файрвола — правила вида «разрешить установленные соединения» опираются именно на неё:
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
У таблицы conntrack есть предел, и его переполнение — один из самых противных инцидентов, потому что симптомы выглядят как «сеть барахлит», а не как «кончился ресурс».
$ sysctl net.netfilter.nf_conntrack_max
net.netfilter.nf_conntrack_max = 262144
$ cat /proc/sys/net/netfilter/nf_conntrack_count
261998 ← почти упёрлись
$ dmesg | tail
nf_conntrack: table full, dropping packet ← вот он, диагноз
nf_conntrack: table full, dropping packet
Когда таблица заполнена, ядро молча отбрасывает пакеты новых соединений. Со стороны это выглядит как случайные таймауты у части клиентов при работающем сервере — и ищут это обычно долго, потому что в логах приложения ничего нет. Первое, что стоит проверять при жалобах «иногда не коннектится», — счётчик conntrack и dmesg.
Лечится увеличением nf_conntrack_max (учитывая, что каждая запись занимает несколько сотен байт памяти) и сокращением таймаутов, особенно неприлично долгого дефолта для установленных TCP-соединений — пять суток:
net.netfilter.nf_conntrack_max = 1048576
net.netfilter.nf_conntrack_tcp_timeout_established = 86400 # сутки вместо 5
net.netfilter.nf_conntrack_tcp_timeout_time_wait = 30
§8Что NAT ломает
Полезно проговорить осознанно, потому что половина «странных» проблем в инфраструктуре — отсюда.
Входящие соединения не работают в принципе. Пока хост за NAT сам не инициировал соединение, записи в таблице нет, и пришедший снаружи пакет некуда транслировать. Это и защита, и главное ограничение. Отсюда растут STUN/TURN для видеозвонков, техника hole punching и весь UPnP.
Теряется настоящий IP клиента. Сервер видит адрес NAT-шлюза, а не пользователя. Для веба это решается заголовком X-Forwarded-For (седьмой уровень), для TCP вообще — proxy protocol, который умеют HAProxy и nginx.
Порты кончаются. Комбинаций «внешний адрес + порт» конечное число — около 64 тысяч на один внешний IP. Если много хостов ломится к одному и тому же сервису (скажем, тысяча подов в одну базу), диапазон исчерпывается, и новые соединения не устанавливаются. Симптом почти неотличим от предыдущего пункта, а лечится добавлением адресов в пул SNAT или, опять же, пулами соединений вместо постоянного переоткрытия.
Hairpin. Хост внутри сети обращается к своему же сервису по внешнему адресу. Пакет уходит на шлюз, тот делает DNAT и отправляет обратно внутрь — и сервер отвечает напрямую клиенту, минуя шлюз, потому что они в одной сети. Клиент получает ответ с адреса, с которым он не устанавливал соединение, и отбрасывает его. Лечится дополнительным SNAT на шлюзе (hairpin NAT) или, что проще и правильнее, split-horizon DNS — чтобы внутри имя резолвилось во внутренний адрес.
Спросят
«Почему из офиса сайт открывается, а изнутри дата-центра по тому же адресу — нет?» — Скорее всего, hairpin NAT. Расскажи механику: пакет уходит на шлюз, там DNAT, ответ идёт напрямую в обход шлюза, адрес источника в ответе не совпадает с тем, к которому подключались, клиент его отбрасывает. И назови оба решения — hairpin NAT на шлюзе или внутренняя DNS-зона.
§9Как это выглядит в Kubernetes
Всё вышеописанное в кластере используется буквально, и понимание этого сильно облегчает отладку.
Под ходит наружу — на узле срабатывает MASQUERADE, внешний сервис видит IP узла, а не пода. Отсюда и исчерпание портов при большом числе исходящих соединений, и невозможность отфильтровать доступ к внешней базе по адресу конкретного пода.
Трафик на Service — это DNAT с ClusterIP на IP пода, который ставит kube-proxy, плюс запись в conntrack, фиксирующая соединение за выбранным подом. Про то, как именно выбирается под, — в главе 3.1.
NodePort и externalTrafficPolicy — самый практически важный сюжет. По умолчанию (Cluster) пакет, прилетевший на NodePort любого узла, может быть переброшен на под, живущий на другом узле. Чтобы ответ вернулся тем же путём, узел делает ещё и SNAT — и приложение видит адрес узла вместо адреса клиента. Настоящий IP теряется.
Если поставить externalTrafficPolicy: Local, узел отдаёт трафик только подам на себе, SNAT не нужен, исходный IP клиента сохраняется. Цена: если на узле нет подов этого сервиса, трафик туда просто отбрасывается, — поэтому внешний балансировщик обязан опрашивать health check и отправлять только на узлы с подами. Плюс нагрузка распределяется по узлам, а не по подам, и при неравномерном размещении получается перекос.
Cluster (по умолчанию) | Local | |
|---|---|---|
| IP клиента | теряется (SNAT) | сохраняется |
| Лишний хоп | возможен | нет |
| Узлы без подов | принимают и пересылают | отбрасывают трафик |
| Равномерность | по подам | по узлам |
§10Инструменты: чем и что смотреть
Знать теорию и уметь разобрать поломку — разные навыки, и на собеседовании второй проверяют почти всегда, задавая вопрос вида «а как бы ты это диагностировал». Здесь — набор, которым закрывается почти всё, с объяснением, зачем каждый инструмент нужен и что именно в его выводе смотреть.
ss — сокеты и состояния
Первое, что смотрят при любой проблеме с подключением. Заменил собой netstat, работает заметно быстрее на машинах с тысячами соединений.
# кто вообще слушает на этой машине
$ ss -tulpn
Netid State Local Address:Port Process
tcp LISTEN 127.0.0.1:5432 users:(("postgres",pid=1203))
tcp LISTEN 0.0.0.0:8080 users:(("java",pid=4410))
↑ вот эта разница критична: 127.0.0.1 — только локально,
0.0.0.0 — со всех интерфейсов
Разница между 127.0.0.1 и 0.0.0.0 в этом выводе — одна из самых частых причин «порт открыт, но не подключиться». Приложение слушает только локальный интерфейс, снаружи к нему не достучаться никак, и никакие правила файрвола тут не помогут. В контейнерном мире это отдельно болезненно: приложение, слушающее 127.0.0.1 внутри пода, недоступно даже для kubelet с его пробами.
# соединения к конкретному хосту с состояниями
$ ss -tan dst 10.0.5.7
State Recv-Q Send-Q Local Address:Port Peer Address:Port
ESTAB 0 0 10.244.3.17:41022 10.0.5.7:5432
TIME-WAIT 0 0 10.244.3.17:40988 10.0.5.7:5432
# посчитать состояния — быстрый способ увидеть аномалию
$ ss -tan | awk '{print $1}' | sort | uniq -c | sort -rn
28431 TIME-WAIT ← кончаются исходящие порты, нет keep-alive
1204 ESTAB
417 CLOSE-WAIT ← приложение не закрывает сокеты, это баг в коде
# подробности по TCP: окно, RTT, повторы — для разбора медленной передачи
$ ss -tin dst 10.0.5.7
cubic wscale:7,7 rto:204 rtt:3.5/1.2 cwnd:10 retrans:0/47 ...
↑ задержка ↑ было 47 повторных передач
Про разницу между большим числом TIME-WAIT и большим числом CLOSE-WAIT подробно в главе 1.1 — это очень разные диагнозы, и умение их различать сразу выделяет.
ip — адреса, маршруты, соседи
$ ip -br a # коротко: интерфейсы, статус, адреса
$ ip route get 10.0.5.7 # КАК ИМЕННО пойдёт пакет к этому адресу
10.0.5.7 via 192.168.1.1 dev eth0 src 192.168.1.20
↑ через шлюз ↑ выйдет отсюда ↑ с этим адресом источника
$ ip neigh # ARP-кеш (см. §3)
$ ip -s link show eth0 # счётчики ошибок и дропов на интерфейсе
ip route get недооценивают, а это самый прямой способ ответить на вопрос «куда вообще пойдёт этот пакет»: ядро само посчитает и покажет выбранный маршрут, интерфейс и адрес источника. При разборе проблем с несколькими интерфейсами, VPN или policy-based routing — первое, что стоит спросить.
traceroute и mtr — где по пути
traceroute работает на механизме TTL из главы 1.1: отправляет пакеты с TTL=1, 2, 3 и так далее, и каждый маршрутизатор, обнуливший TTL, присылает ICMP-сообщение со своим адресом. Так и получается список переходов.
Ограничение, которое надо понимать: звёздочки в выводе не означают потерю. Многие маршрутизаторы просто не отвечают на такие пакеты или ограничивают частоту ответов — при этом транзитный трафик через них идёт прекрасно. Тревожиться стоит, только если звёздочки идут до самого конца.
$ mtr -rwc 100 10.0.5.7 # 100 пакетов, отчётом
HOST Loss% Snt Avg Best Wrst
1. 192.168.1.1 0.0% 100 0.5 0.3 1.2
2. 10.10.0.1 0.0% 100 2.1 1.8 9.4
3. 172.16.4.9 12.0% 100 4.5 3.9 88.1 ← вот здесь теряется
4. 10.0.5.7 0.0% 100 5.0 4.6 12.3
mtr лучше traceroute тем, что шлёт пакеты непрерывно и показывает статистику потерь по каждому переходу. Читать её надо аккуратно: потери на промежуточном хопе при нулевых потерях на конечном означают, что этот маршрутизатор просто не любит отвечать, а не что трафик страдает. Реальная проблема — когда потери начинаются на каком-то хопе и сохраняются до конца.
curl — проверка того, что выше четвёртого уровня
$ curl -v https://shop.example.com/health
* Connected to shop.example.com (203.0.113.10) port 443 # TCP есть
* TLSv1.3 (OUT), TLS handshake # TLS пошёл
* Server certificate: CN=shop.example.com # сертификат тот
> GET /health HTTP/2 # запрос ушёл
< HTTP/2 200 # ответ получен
# проверить сервер в обход DNS — отделить «сломан DNS» от «сломан сервер»
$ curl -v --resolve shop.example.com:443:203.0.113.10 https://shop.example.com/
# где именно тратится время
$ curl -w 'dns %{time_namelookup}s tcp %{time_connect}s tls %{time_appconnect}s всего %{time_total}s\n' \
-o /dev/null -s https://shop.example.com
dns 0.004s tcp 0.021s tls 0.089s всего 0.310s
Последняя команда — очень недооценённый приём. Она за один вызов показывает, на что уходит время: на резолв, на установку соединения, на TLS или на сам ответ приложения. Это сразу направляет расследование в нужную сторону вместо гадания.
Что ещё
| Инструмент | Когда |
|---|---|
tcpdump | Когда надо увидеть настоящие пакеты. Подробно — глава 1.1, §10 |
dig | Всё про DNS. Подробно — глава 1.3, §10 |
nc -zv host port | Быстрая проверка «открыт ли порт» без установки лишнего |
openssl s_client | Разбор проблем с сертификатами и TLS |
conntrack -L | Посмотреть таблицу трансляций живьём: кто с кем и во что транслируется |
iptables -L -v -n | Счётчики по правилам — видно, срабатывает ли правило вообще |
netstat -s | Сводные счётчики стека: переполнения очередей, отброшенные пакеты, повторы |
§11Разбор типовых случаев
Здесь собрано то, что действительно случается, — с последовательностью действий. На собеседовании обычно и просят проговорить именно последовательность, а не назвать причину сразу.
«Сервис недоступен»
Самая общая формулировка, и метод один — идти снизу вверх по слоям, отсекая половину пространства поиска на каждом шаге.
Мгновенный отказ против таймаута
Различие, которое стоит проговаривать в первую очередь, потому что оно делит все сетевые проблемы пополам.
Мгновенный connection refused означает, что пакет дошёл, ядро на той стороне увидело, что порт никто не слушает, и ответило сбросом. Сеть, маршруты и файрволы в порядке — проблема в приложении. Проверять: запущено ли оно, на каком адресе слушает, тот ли порт.
Долгий таймаут означает, что ответа не пришло вовсе. Тут виноват путь: дропает файрвол или сетевая политика, нет маршрута, ответ уходит не туда. Проверять парным дампом.
Работает не у всех / плавающие отказы
Слово «иногда» почти всегда означает исчерпание какого-то ресурса или неоднородность путей. Проверять по порядку:
dmesg | grep -i conntrack— не переполнилась ли таблица трансляций. При переполнении пакеты новых соединений молча отбрасываются, и в логах приложения нет ничего.ss -tan state time-wait | wc -l— не кончаются ли исходящие порты.netstat -s | grep -i -E 'listen|overflow'— не переполняется ли очередь принятых соединений на сервере.- Не за балансировщиком ли часть бэкендов мертва: тогда «иногда» — это просто попадание на сломанный экземпляр.
Соединение есть, а большие ответы не приходят
Диагноз почти наверняка MTU, метод — ping -M do -s с подбором размера, подробности в главе 1.1. Отдельно проверь, не блокируется ли ICMP: без него механизм определения MTU по пути не работает, и вместо аккуратного уменьшения сегментов получаешь чёрную дыру.
После переключения трафик всё ещё идёт на старый сервер
Два кандидата, и различаются они уровнем:
- Если переключали виртуальный IP — устаревший ARP-кеш у клиентов или у маршрутизатора. Проверять
tcpdump -i eth0 arpна предмет gratuitous ARP от нового владельца, лечитьarping -U. Механика — в §5. - Если меняли DNS-запись — не вытек TTL, либо приложение кеширует резолв само. Проверять
digс разных резолверов и смотреть оставшийся TTL.
В Kubernetes: под не достучится до сервиса
1. $ kubectl get endpointslice -l kubernetes.io/service-name=myapp
пусто → селектор не совпал ИЛИ поды не Ready. Это причина №1.
2. $ kubectl exec -it другой-под -- nc -zv 10.244.3.17 8080
напрямую по IP пода: живо ли приложение вообще
3. $ kubectl exec -it под -- nslookup myapp.prod.svc.cluster.local
резолвится ли имя — жив ли CoreDNS
4. $ kubectl get networkpolicy -n prod
не запрещает ли политика. Помни: одна политика на namespace
превращает модель в «запрещено всё, кроме описанного»
5. и только потом — дамп внутри netns пода
В бою
Держи под рукой отладочный под: kubectl run netshoot --rm -it --image=nicolaka/netshoot -- bash. Внутри есть dig, tcpdump, ss, curl, mtr, ipvsadm — всё, чего нет в нормальных минимальных образах приложений. Проверять связность из него честнее, чем с узла: под живёт в той же сети подов и под теми же сетевыми политиками, что и настоящее приложение.
§12Как это звучит в ответе
«Когда хост отправляет пакет, он по маске сравнивает адрес получателя со своим и решает одно: получатель в моём сегменте или за маршрутизатором. В первом случае кадр отправляется прямо получателю, во втором — шлюзу. И там, и там нужен MAC-адрес следующего шага — за этим и нужен ARP: широковещательный вопрос "у кого такой IP", unicast-ответ, запись в кеш на несколько минут.
Дальше по пути IP-адреса в пакете не меняются — они говорят, куда доставить в итоге. А MAC-адреса переписываются на каждом маршрутизаторе, потому что они отвечают только за передачу на следующий хоп. Заодно на каждом хопе уменьшается TTL, на чём и работает traceroute.
ARP в работе всплывает при файловере виртуального IP: адрес переехал, а MAC за ним другой, и у всех в кешах старая запись — поэтому новый владелец рассылает gratuitous ARP, чтобы кеши обновились немедленно. Без этого файловер формально проходит, а трафик минуты уходит в никуда.
NAT — это подмена адресов на границе с запоминанием трансляции. SNAT/masquerade меняет источник для исходящих и делается в POSTROUTING, после выбора маршрута. DNAT меняет назначение для входящих и делается в PREROUTING, до выбора маршрута, иначе пакет уйдёт не туда. Правила применяются только к первому пакету, дальше всё держит conntrack — и вот у него есть лимит: при переполнении ядро молча дропает новые соединения и пишет в dmesg "table full", а выглядит это как случайные таймауты. В Kubernetes всё то же самое буквально: выход подов наружу — это masquerade на узле, Service — это DNAT, а externalTrafficPolicy: Local нужен ровно затем, чтобы убрать лишний SNAT и сохранить IP клиента.»