Сети для самых маленьких. Часть 7: ARP

В пятой части хост решал, сосед перед ним или нужен шлюз, а в шестой роутер на каждом хопе упаковывал пакет в новый кадр с MAC-адресом следующего роутера. В обоих случаях мы молча предполагали, что MAC-адрес соседа откуда-то известен. Но в пакете есть только IP-адрес назначения, а кадру Ethernet нужен MAC. Кто-то должен перевести одно в другое. Это делает ARP — Address Resolution Protocol, RFC 826, один из самых старых и самых простых протоколов стека, и при этом один из самых важных: без него ни один пакет не покинет машину.


Задача

У хоста 192.168.1.10 есть IP-пакет для 192.168.1.1 — шлюза. Алгоритм из пятой части сказал: сосед, отправлять напрямую. Для этого нужен кадр Ethernet, а в нём — MAC-адрес получателя. У хоста его нет.

Можно было бы вшить соответствие в конфигурацию, и в 1982 году так и делали: файл со списком «адрес — MAC» на каждой машине. С десятком машин терпимо, с сотней — нет, а при замене сетевой карты приходится править все файлы в сети. ARP заменяет файл вопросом вслух: хост спрашивает у всего сегмента, у кого этот IP-адрес, и владелец отвечает.

Важная граница: ARP работает только внутри сегмента. Хост никогда не спрашивает MAC-адрес сервера в интернете: решение «сосед или шлюз» принимается раньше, и ARP-запрос уходит либо на адрес соседа, либо на адрес шлюза. Роутеры ARP-запросы не пересылают: это широковещательные кадры, а широковещание за роутер не ходит.


Запрос и ответ

ARP-пакет живёт прямо в кадре Ethernet с EtherType 0x0806, без IP-заголовка: он сам на уровне между L2 и L3. Длина — 28 байт:

 байты:   2        2       1      1      2        6         4         6         4
        ┌───────┬───────┬─────┬─────┬───────┬─────────┬────────┬─────────┬────────┐
        │ HTYPE │ PTYPE │HLEN │PLEN │ OPER  │   SHA   │  SPA   │   THA   │  TPA   │
        │  =1   │=0x0800│ =6  │ =4  │ 1 / 2 │ MAC     │ IP     │ MAC     │ IP     │
        │       │       │     │     │       │ отправи-│ отправи│ искомо- │ искомый│
        │       │       │     │     │       │ теля    │ теля   │ го      │        │
        └───────┴───────┴─────┴─────┴───────┴─────────┴────────┴─────────┴────────┘

Первые четыре поля говорят, какие адреса переводим: аппаратный тип 1 (Ethernet) длиной 6, протокольный 0x0800 (IPv4) длиной 4. ARP задуман универсальным, но на практике только для этой пары. OPER — 1 запрос, 2 ответ. Дальше две пары «MAC, IP»: отправителя (Sender Hardware/Protocol Address) и цели (Target).

Обмен:

   192.168.1.10                                       192.168.1.1
   3c:52:82:1a:2b:3c                                  d8:47:32:0a:0b:0c
        │                                                  │
        ├── запрос, кадр на ff:ff:ff:ff:ff:ff ────────────►│  (и всем остальным)
        │   SHA 3c:52:82:1a:2b:3c  SPA 192.168.1.10        │
        │   THA 00:00:00:00:00:00  TPA 192.168.1.1         │
        │                                                  │
        │◄─ ответ, кадр на 3c:52:82:1a:2b:3c ──────────────┤
        │   SHA d8:47:32:0a:0b:0c  SPA 192.168.1.1         │
        │   THA 3c:52:82:1a:2b:3c  TPA 192.168.1.10        │

Запрос уходит широковещательным кадром: его получают все в сегменте. В поле THA нули — это и есть то, что ищем. Каждый хост, получив запрос, сравнивает TPA со своими адресами. Не мой — выбросить. Мой — ответить.

Ответ уходит unicast-кадром прямо спрашивавшему: его MAC известен из SHA запроса. В ответе отправитель и цель меняются местами, и в SHA — искомый адрес.

В tcpdump это две строки:

$ sudo tcpdump -i eth0 -n -e arp
3c:52:82:1a:2b:3c > ff:ff:ff:ff:ff:ff, ethertype ARP (0x0806), length 42: Request who-has 192.168.1.1 tell 192.168.1.10, length 28
d8:47:32:0a:0b:0c > 3c:52:82:1a:2b:3c, ethertype ARP (0x0806), length 60: Reply 192.168.1.1 is-at d8:47:32:0a:0b:0c, length 46

«Who-has, tell» и «is-at» — формулировки tcpdump, они точно передают смысл. Длина 42 — это 14 байт Ethernet и 28 ARP; ответ показан с паддингом до 60, потому что захвачен уже после сетевой карты.

Побочный эффект, заложенный в протокол: все, кто получил запрос, узнают MAC-адрес спрашивающего из SHA и SPA. Шлюз, отвечая, уже знает, куда слать ответ, и ему не нужен встречный запрос; хосты, которым запрос был не адресован, по умолчанию чужую запись не создают, но если она у них уже была — обновляют. Так одна пара кадров экономит вторую.


Таблица соседей

Спрашивать перед каждым пакетом — расточительно: это широковещательный кадр всему сегменту и ожидание ответа. Поэтому результат кешируется в таблице соседей (в старых системах — ARP-кэш, в Linux — neighbour table, потому что та же таблица обслуживает IPv6):

$ ip neigh
192.168.1.1 dev eth0 lladdr d8:47:32:0a:0b:0c REACHABLE
192.168.1.20 dev eth0 lladdr 00:11:22:33:44:55 STALE
192.168.1.30 dev eth0 FAILED

Записи живут не вечно: машина может сменить карту, адрес может переехать на другую машину. Запись проходит через состояния, и у Linux они такие:

Состояние Значит
INCOMPLETE запрос отправлен, ответа ещё нет; пакеты для этого адреса стоят в очереди (до трёх штук)
REACHABLE ответ получен, или сосед недавно подтвердил себя; запись используется без проверок
STALE с подтверждения прошло больше ~30 секунд (случайно от 15 до 45); использовать можно, но при первом же использовании нужно перепроверить
DELAY запись STALE использовали; стек даёт соседу 5 секунд, чтобы тот сам подтвердился (например, ответом TCP)
PROBE не подтвердился: шлём unicast-запрос прямо на известный MAC, до трёх раз
FAILED ответа нет; пакеты для этого адреса отбрасываются с ошибкой «host unreachable»
PERMANENT статическая запись, добавлена руками, не проверяется и не устаревает

Две детали стоит отметить. Во-первых, перепроверка идёт unicast-запросом, а не широковещанием, чтобы не беспокоить сегмент: широковещание остаётся на случай, когда MAC неизвестен вовсе. Во-вторых, подтверждением считается не только ARP-ответ: если от соседа пришёл любой ответ верхнего уровня, который стек может связать с отправленным пакетом (TCP ACK, например), запись возвращается в REACHABLE. В сети с живым трафиком ARP почти не слышно.

У Windows логика похожая: запись живёт в Reachable от 15 до 45 секунд, потом переходит в Stale и проверяется при обращении. Таблица — arp -a или Get-NetNeighbor.

Что происходит при первом пакете

Соберём всё вместе. Хост только загрузился, таблица пуста, пользователь запускает ping 203.0.113.10:

  1. Стек строит ICMP-пакет, таблица маршрутов говорит: через шлюз 192.168.1.1, интерфейс eth0.
  2. В таблице соседей 192.168.1.1 нет. Пакет ставится в очередь, запись переходит в INCOMPLETE, уходит широковещательный ARP-запрос.
  3. Через доли миллисекунды приходит ответ, запись REACHABLE, пакет из очереди отправляется в кадре на d8:47:32:0a:0b:0c.
  4. Следующие пакеты уходят сразу по записи.

Если шлюз не отвечает (выключен, адрес неверный, кабель не в том VLAN), стек повторяет запрос трижды с интервалом в секунду, после чего запись становится FAILED, и ping печатает Destination Host Unreachable. Обратите внимание: это сообщение сгенерировано собственным стеком, а не пришло из сети, и означает ровно «сосед не ответил на ARP». Это самый точный диагноз, который даёт ping: если видите его, проблема не в интернете, а в метре от вас.


Gratuitous ARP и проверка дубликатов

Протокол допускает кадр, который никто не спрашивал. Gratuitous ARP — запрос (реже ответ), в котором SPA и TPA — один и тот же адрес, свой собственный, отправленный широковещательно:

3c:52:82:1a:2b:3c > ff:ff:ff:ff:ff:ff, ethertype ARP (0x0806), length 42: Request who-has 192.168.1.10 tell 192.168.1.10, length 28

Хост как бы спрашивает «у кого 192.168.1.10?», будучи сам владельцем. Назначений два:

  • Объявить о себе. Все, у кого есть запись для 192.168.1.10, обновят в ней MAC. Это нужно, когда адрес переехал: отказоустойчивая пара серверов с общим виртуальным адресом при переключении на резервный шлёт gratuitous ARP, чтобы соседи и коммутаторы перестали слать кадры на умерший MAC. Без этого соседи продолжали бы отправлять кадры в никуда ещё десятки секунд, пока записи не устареют. То же при миграции виртуальной машины на другой хост.
  • Обнаружить дубликат. Если кто-то ответил на такой запрос, значит, адрес уже занят. Стек сообщает о конфликте.

Для второй цели есть и аккуратный вариант, ARP probe (RFC 5227): запрос с SPA 0.0.0.0. Поскольку адреса отправителя в нём нет, никто не обновит свои таблицы по ещё не подтверждённому адресу. Так проверяют адрес перед тем, как начать им пользоваться: хост, получивший адрес от DHCP-сервера (часть 11), шлёт probe и, услышав ответ, отказывается от аренды. Увидеть можно, вручную: arping -D -I eth0 192.168.1.10.

Конфликт адресов

Две машины с одним IP-адресом в сегменте — классика. На ARP-запрос отвечают обе, и в таблицу спрашивающего попадает та, чей ответ пришёл последним. Через полминуты перепроверка, и может выиграть другая. Симптомы: связь с адресом «моргает», в ip neigh MAC у записи меняется, коммутатор видит один адрес с разных портов (но ему всё равно: для него это разные MAC), Windows показывает «обнаружен конфликт IP-адресов». Поймать виновника проще всего захватом ARP-ответов: tcpdump -n -e arp reply покажет оба MAC, а по OUI — оба устройства.


Proxy ARP

Роутер может отвечать на ARP-запросы за чужие адреса: за те, до которых знает маршрут, хотя они не в этом сегменте. Хост с неправильной маской из пятой части, считающий соседом адрес из другой сети, шлёт ARP-запрос; настоящий сосед молчит, а роутер отвечает своим MAC, и пакет уходит ему, а он доставит. Хост так и не узнаёт о своей ошибке. Это proxy ARP, и он был в ходу, когда шлюзы по умолчанию ещё не умели настраивать все хосты.

Сегодня он встречается в двух местах: VPN-серверы делают клиентов «видимыми» в локальной сети, отвечая за их адреса, и некоторые провайдеры собирают абонентов в одну сеть с маской /32 на каждом порту, а трафик между ними ходит через роутер благодаря proxy ARP. В остальных случаях proxy ARP скорее маскирует ошибки конфигурации, чем помогает, и на Linux он выключен (net.ipv4.conf.all.proxy_arp = 0).


Почему ARP так легко обмануть

В протоколе нет ни аутентификации, ни проверки: кто ответил — тому и верят. Атакующий в том же сегменте шлёт жертве ARP-ответ (или gratuitous-запрос) «192.168.1.1 is-at <MAC атакующего>», и жертва обновляет запись шлюза. Теперь все её пакеты в интернет идут атакующему, тот пересылает их настоящему шлюзу, и жертва ничего не замечает. То же самое в обратную сторону — шлюзу про жертву, и получается полный перехват в обе стороны: ARP spoofing, самый простой способ встать посередине в локальной сети. На этом же основана половина «умных» гостевых порталов: кафе так перехватывает трафик до авторизации.

Защита живёт не в ARP, а вокруг него:

  • Статические записи ip neigh add 192.168.1.1 lladdr d8:47:32:0a:0b:0c dev eth0 nud permanent — на критичные адреса. Не масштабируется.
  • Dynamic ARP Inspection на управляемых коммутаторах: коммутатор подсматривает, какой адрес какому порту выдал DHCP-сервер, и отбрасывает ARP-ответы, где SPA/SHA не совпадают с этой таблицей.
  • Изоляция клиентов в гостевых сетях: коммутатор или точка доступа не пересылает кадры между портами клиентов, и спрашивать соседей становится некого.
  • Шифрование выше: TLS и VPN не мешают перехватить пакеты, но делают перехваченное бесполезным.

В IPv6 ту же задачу решает другой протокол — Neighbor Discovery (часть 14), с групповыми запросами вместо широковещательных и возможностью криптографической защиты, которой, впрочем, почти никто не пользуется.


Смотрим руками

Таблица соседей и её очистка (после очистки первый пакет к каждому соседу снова пойдёт через ARP-запрос — удобно, чтобы увидеть обмен):

$ ip neigh
$ sudo ip neigh flush all

Спросить вручную, не трогая стек:

$ sudo arping -I eth0 -c 3 192.168.1.1
ARPING 192.168.1.1 from 192.168.1.10 eth0
Unicast reply from 192.168.1.1 [d8:47:32:0a:0b:0c]  0.612ms
Unicast reply from 192.168.1.1 [d8:47:32:0a:0b:0c]  0.588ms
Unicast reply from 192.168.1.1 [d8:47:32:0a:0b:0c]  0.590ms

arping работает там, где ICMP может быть закрыт файрволом хоста: на ARP отвечает сам стек, его не фильтруют. Если на ping адрес молчит, а на arping отвечает — машина жива, закрыт именно ICMP. Два ответа с разными MAC на один адрес — конфликт.

Посмотреть обмен целиком, вместе с первым пакетом, который его вызвал:

$ sudo ip neigh flush all && sudo tcpdump -i eth0 -n -e -c 4 'arp or icmp' &
$ ping -c 1 192.168.1.1

В выводе будут запрос, ответ и только затем echo request и echo reply. Разница во времени между первой и третьей строкой — цена ARP, обычно меньше миллисекунды.

На Windows: arp -a показывает таблицу, arp -d * чистит, Get-NetNeighbor -State Reachable в PowerShell фильтрует по состоянию.


Что запомнить

  • ARP переводит IP-адрес соседа в MAC-адрес: широковещательный запрос «у кого этот адрес», unicast-ответ «у меня, вот MAC». 28 байт прямо в кадре Ethernet, без IP.
  • Решение «сосед или шлюз» принимается до ARP; за пределы сегмента запросы не ходят, для чужих сетей спрашивают MAC шлюза.
  • Ответы кешируются в таблице соседей; запись проходит REACHABLE → STALE → проверка unicast-запросом → FAILED. Подтверждает запись и обычный трафик от соседа.
  • Destination Host Unreachable в ping — сосед не ответил на ARP, проблема рядом.
  • Gratuitous ARP объявляет адрес и обновляет чужие таблицы при переезде адреса; ARP probe проверяет, свободен ли адрес.
  • Проверок в ARP нет: любой в сегменте может назваться шлюзом. Защита — на коммутаторе и уровнем выше.

Дальше — Часть 8: ICMP: протокол, по которому сеть сообщает об ошибках и на котором построены ping и traceroute. Разберём типы сообщений, как traceroute находит каждый роутер на пути с помощью TTL, как работает Path MTU Discovery и почему «файрвол, который режет весь ICMP» ломает сеть.

Оставить комментарий могут только зарегистрированные пользователи.

Войдите на сайт или зарегистрируйтесь, чтобы оставить комментарий.