Сети для самых маленьких. Часть 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:
- Стек строит ICMP-пакет, таблица маршрутов говорит: через шлюз
192.168.1.1, интерфейсeth0. - В таблице соседей
192.168.1.1нет. Пакет ставится в очередь, запись переходит вINCOMPLETE, уходит широковещательный ARP-запрос. - Через доли миллисекунды приходит ответ, запись
REACHABLE, пакет из очереди отправляется в кадре наd8:47:32:0a:0b:0c. - Следующие пакеты уходят сразу по записи.
Если шлюз не отвечает (выключен, адрес неверный, кабель не в том 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» ломает сеть.
Оставить комментарий могут только зарегистрированные пользователи.
Войдите на сайт или зарегистрируйтесь, чтобы оставить комментарий.