Learning Platform
Глоссарий Troubleshooting
Урок 05.02 · 18 мин
Начальный
ARPMACLink LayerNetwork

ARP — как IP-адрес превращается в MAC-адрес

Представьте: вы открываете в браузере https://github.com. Где-то в недрах системы он превратится в IP-пакет, который надо отправить на адрес условно 140.82.121.4. Но Ethernet-кадру нужен MAC-адрес получателя, а не IP. Откуда система берёт MAC, имея только IP?

Ответ: спрашивает у сети. Этот процесс называется ARP — Address Resolution Protocol. В этом уроке разберём, как именно работает ARP, что такое ARP-таблица, gratuitous ARP и почему ARP — это одна из самых старых, самых тупых и одновременно самых опасных частей сетевого стека.

Знать ARP надо потому, что 95% «странных проблем» в локальной сети — это ARP. Конфликты IP, машина «то видит, то не видит сосeда», задержки в 1-2 секунды на первый пакет к незнакомому хосту — всё ARP.

Pipes — anonymous, named FIFO и магия shell

Зачем вообще нужен ARP

Если посмотреть на стек ещё раз: у нас есть IP-пакет с dst = 140.82.121.4. Этот пакет надо положить внутрь Ethernet-кадра. У кадра свои поля — dst MAC и src MAC. Какой MAC писать в dst MAC?

Проблема -- IP знаем, MAC не знаем
Мы хотимIP-пакет с dst=192.168.1.5 надо отправить через провод. Знаем IP, не знаем MAC
Ethernet требуетКаждый кадр должен иметь конкретный MAC-адрес получателя. Без него непонятно, в какой порт свитчу класть кадр
РешениеARP: спросить broadcast'ом 'у кого IP 192.168.1.5?', получить ответ с MAC, закэшировать

Тонкость: ARP нужен только для устройств в той же локальной сети (в том же broadcast domain). Если получатель за роутером (как github.com), мы используем ARP для MAC-адреса роутера — это так называемый default gateway. Дальше уже работа роутера.

То есть для пакета на github.com последовательность такая:

  1. ОС видит: dst IP = 140.82.121.4, это не в моей подсети.
  2. Смотрит routing table: для этого IP надо отправить через шлюз 192.168.1.1.
  3. ARP-запрос: «у кого IP 192.168.1.1
  4. Роутер отвечает: «у меня, MAC 00:1c:42:00:00:18».
  5. ОС кладёт IP-пакет (с IP-адресатом github.com) в Ethernet-кадр с MAC-адресатом роутера.
  6. Свитч кладёт кадр в порт, к которому подключен роутер.
  7. Роутер видит «не мой IP», смотрит свою routing table, шлёт пакет дальше.

ARP участвует только на шаге 3-4 — между ОС и устройствами внутри её локальной сети.


Структура ARP-запроса и ответа

ARP — это отдельный link-layer протокол. Поверх Ethernet, но не поверх IP. EtherType = 0x0806.

ARP-кадр выглядит так:

ARP request -- я не знаю чужой MAC
dst MACff:ff:ff:ff:ff:ff -- broadcast. Кадр услышат ВСЕ устройства в сегменте, потому что мы не знаем, у кого нужный IP
src MACМой собственный MAC. Кто спрашивает
EtherType0x0806 -- ARP. Если бы было 0x0800 -- внутри был бы IP-пакет
Operation1 = request (ARP-запрос), 2 = reply (ARP-ответ). Один формат, разное значение поля
SenderМой IP + мой MAC. Получатель будет знать, кому отвечать
TargetИскомый IP. MAC = 00:00:00:00:00:00 (не знаем). Эта пара полей и есть суть запроса

Что происходит дальше:

  1. Все устройства в сегменте получают этот broadcast-кадр.
  2. Каждое смотрит на Target IP в запросе.
  3. Если это не их IP — молча игнорируют (но фиксируют в своих логах sender’а).
  4. Если это их IP — отправляют ARP reply.

ARP reply — такой же кадр, но:

  • dst MAC = MAC запрашивающего (узнали из Sender MAC).
  • Operation = 2.
  • Sender = (мой IP, мой MAC) — вот собственно ответ.
  • Target = (IP того, кто спрашивал, его MAC).

Запрашивающий получает reply, запоминает связку IP -> MAC в свою ARP-таблицу, и теперь может слать обычные IP-пакеты.


ARP-таблица — что и как кэшируется

Чтобы не делать ARP-запрос на каждый IP-пакет (это было бы катастрофой — broadcast на каждый byte!), ОС кэширует пары IP -> MAC в ARP-таблице (она же neighbor table).

Посмотреть таблицу:

# Linux (старый способ):
arp -n
# 192.168.1.1   ether   00:1c:42:00:00:18   C   eth0
# 192.168.1.42  ether   a4:83:e7:1a:bb:cc   C   eth0

# Linux (современный, IPv4+IPv6):
ip neigh show
# 192.168.1.1 dev eth0 lladdr 00:1c:42:00:00:18 STALE
# 192.168.1.42 dev eth0 lladdr a4:83:e7:1a:bb:cc REACHABLE
# fe80::a683:e7ff:fe1a:bbcc dev eth0 lladdr a4:83:e7:1a:bb:cc REACHABLE

# macOS:
arp -a
# ? (192.168.1.1) at 0:1c:42:0:0:18 on en0 ifscope [ethernet]
# ? (192.168.1.42) at a4:83:e7:1a:bb:cc on en0 ifscope [ethernet]

Записи имеют состояния (state machine на Linux):

States ARP-записи в Linux neighbor table
INCOMPLETEARP-запрос отправлен, ответа ещё не было. Пакеты к этому IP ожидают
reply
REACHABLEЗапись свежая, недавно использовалась или подтверждалась. Использовать без вопросов. ~30 секунд (gc_stale_time)
timeout
STALEПрошло достаточно времени, есть подозрение что запись устарела. Использовать, но при первом сомнении перепроверить
DELAYЗапись STALE, по ней пошёл пакет. Ждём подтверждения от вышестоящих слоёв (ACK от TCP, например)
PROBEОС шлёт unicast ARP-запрос на проверку. Если не отвечает -- запись удаляется
no reply
FAILEDARP-запрос не получил ответа. Запись удаляется. Следующий пакет вызовет новый запрос

Дефолтные таймауты в Linux (/proc/sys/net/ipv4/neigh/default/):

  • gc_stale_time — 60 секунд. Через сколько REACHABLE становится STALE.
  • base_reachable_time — 30 секунд (по умолчанию). Случайно варьируется (0.5x .. 1.5x) чтобы не было синхронных перепроверок.

Это значит: если хост в локалке не виден ~60 секунд, ОС перепроверит. Если виден — запись живёт.


Gratuitous ARP — кричу в пустоту

Gratuitous ARP — это ARP-запрос от своего IP про свой IP. Зачем?

  1. Сообщить сети, что я тут. Когда машина поднимается, она шлёт gratuitous ARP с Sender IP = Target IP = свой IP. Все, у кого в кэше был старый MAC для этого IP — обновят запись.

  2. Проверить конфликт IP. Если кто-то другой ответит на ARP про мой IP — значит в сети дублирующийся IP.

  3. Failover. Когда в active-passive HA один сервер падает, второй забирает Virtual IP и шлёт gratuitous ARP, чтобы свитчи и соседи обновили MAC для этого IP. Без этого пакеты продолжали бы идти на мёртвый MAC ~60 секунд, пока ARP-таблица не протухнет.

Gratuitous ARP при HA-failover
VIP доVirtual IP 10.0.0.100 закреплён за primary, MAC = aa:aa:aa:aa:aa:aa. ARP-кэш у всех соседей знает эту пару
primary down
Secondary берёт VIPStandby поднимает интерфейс с 10.0.0.100, теперь его собственный MAC = bb:bb:bb:bb:bb:bb
Gratuitous ARPStandby рассылает 'у меня IP 10.0.0.100, мой MAC bb:bb'. Все соседи обновляют свой ARP-кэш
СоседиОбновили кэш. Следующие пакеты пойдут на bb:bb:bb. Без gratuitous ARP downtime был бы до протухания записи (минуты)

В keepalived, Pacemaker, AWS Elastic IP — везде под капотом gratuitous ARP при failover. Если вы поднимали VIP — видели в логах строчку «Sending gratuitous ARP».


Реальный ARP в действии

Захватим ARP-обмен tcpdump’ом и разберём:

# В одном терминале -- слушаем ARP:
sudo tcpdump -i en0 -e -n 'arp'

# В другом терминале -- провоцируем ARP, удалив запись и запинговав:
ip neigh del 192.168.1.1 dev eth0 2>/dev/null || sudo arp -d 192.168.1.1
ping -c 1 192.168.1.1

# В первом терминале увидим:
# 12:34:56.789 a4:83:e7:1a:bb:cc > ff:ff:ff:ff:ff:ff, ethertype ARP (0x0806),
#   length 42: Request who-has 192.168.1.1 tell 192.168.1.42, length 28
# 12:34:56.790 00:1c:42:00:00:18 > a4:83:e7:1a:bb:cc, ethertype ARP (0x0806),
#   length 42: Reply 192.168.1.1 is-at 00:1c:42:00:00:18, length 28

Видим:

  1. Кадр Request идёт на broadcast ff:ff:ff:....
  2. Текст «Request who-has X tell Y» означает «кто имеет IP X? скажите Y».
  3. Ответ — unicast напрямую запрашивающему.
  4. «Reply X is-at Y» — «X находится по MAC Y».

После этого ip neigh show покажет свежую REACHABLE-запись для 192.168.1.1.


Atаки и проблемы с ARP

ARP был спроектирован в эпоху, когда «свои» были только за столом в одной комнате. Никакой аутентификации в протоколе нет. Любой может ответить на любой ARP-запрос. Это даёт атаки:

1. ARP spoofing / ARP poisoning. Атакующий в той же локалке постоянно шлёт фейковые ARP reply: «IP 192.168.1.1 находится по MAC cc:cc:cc:...» (своему). Все жертвы обновляют ARP-кэш, теперь трафик к роутеру идёт через машину атакующего. Это даёт ему MITM.

2. ARP flood. Машина шлёт миллион ARP-запросов в секунду. Свитчи перегружаются, ARP-таблицы переполняются, маленькие свитчи начинают работать как hub’ы (failopen) и атакующий видит весь трафик.

3. Дублирующиеся IP. Если два устройства настроены с одним IP — они оба отвечают на ARP. Сетевой кэш «дрожит», пакеты идут то одному, то другому, всё лагает. В логах: kernel: ARP packet from 192.168.1.5 had src ... but src mac doesn't match.

DANGER

Если в сети не работает ARP-spoofing-защита (Dynamic ARP Inspection на enterprise-свитчах) — ваша Wi-Fi сеть в кофейне беззащитна. Любой подключившийся может MITM ваш HTTP-трафик. HTTPS защищает от чтения содержимого, но метаданные (с какими доменами вы общаетесь) — всё равно видны. Защита: VPN, HSTS, использование Wi-Fi только с уважающим вас провайдером.

Защита на стороне свитчей:

  • Dynamic ARP Inspection (DAI) — свитч проверяет, что src IP в ARP действительно соответствует src MAC из DHCP snooping table. Не своё — дропается.
  • Port Security — каждый порт свитча привязан к ограниченному списку MAC. Чужой MAC — порт отключается.

Эти штуки умеют только managed switches (Cisco, Mikrotik с RouterOS, Aruba). Домашние роутеры — беззащитны.


Попробуй сам

# 1. Посмотреть свой ARP-кэш
ip neigh show       # Linux
arp -a              # macOS

# 2. Удалить запись и посмотреть, как она восстановится
sudo ip neigh del 192.168.1.1 dev eth0   # Linux
sudo arp -d 192.168.1.1                  # macOS
ping -c 1 192.168.1.1
ip neigh show 192.168.1.1                # увидим INCOMPLETE -> REACHABLE

# 3. Захватить ARP-обмен tcpdump'ом
sudo tcpdump -i any -e -n 'arp' -c 4
# В другом терминале пингуйте новый IP:
ping -c 1 192.168.1.99   # если такого нет -- увидите только requests

# 4. Найти, какие IP активны в локалке (через ARP scan):
# Linux:
sudo apt install arp-scan
sudo arp-scan -l    # быстро находит всех соседей в локалке через ARP

# macOS (через nmap):
nmap -sn 192.168.1.0/24

# 5. Посмотреть статистику ARP:
ip -s neigh   # Linux: счётчики REACHABLE, STALE, etc
sysctl net.ipv4.neigh.default   # таймеры и лимиты

# 6. Проследить, что происходит с конкретным IP:
# Включить логи neigh-table (требует root):
sysctl -w net.ipv4.neigh.default.app_solicit=3
# После этого сообщения о неответивших ARP попадут в kernel log:
dmesg -w | grep -i arp

Особенно полезно сделать пункт 3 и увидеть собственными глазами «Request who-has» / «Reply is-at». Это сразу делает ARP конкретной штукой, а не абстракцией.


Проверка знанийKnowledge check
Вы поднимаете HA-кластер из двух Postgres-инстансов с floating IP 10.0.0.100. После failover (primary падает, standby становится primary) клиенты ждут ~60 секунд, прежде чем подключиться к новому primary. Все TCP-таймауты выкручены коротко. Где проблема и как починить?
ОтветAnswer
Проблема в том, что standby взял floating IP 10.0.0.100, но не отправил gratuitous ARP. Клиенты в локальной сети помнят старый MAC primary -- продолжают слать пакеты на него (а primary мёртв). Через ~60 секунд ARP-запись в их кэше становится STALE, потом PROBE отправляет запрос, кэш обновляется на новый MAC -- и только тогда трафик идёт на новый primary. Решение: при failover скрипт (или keepalived/Pacemaker) должен явно отправить gratuitous ARP с нового primary. На Linux это делается командой 'arping -A -I eth0 -c 3 10.0.0.100' или 'ip neigh notify dev eth0 10.0.0.100'. Многие HA-решения делают это автоматически, но при кастомных скриптах -- легко забыть. Дополнительно: можно посмотреть, что роутер на пути тоже знал свежий MAC -- если клиенты в другой подсети, ARP'ы должен обновлять роутер. На AWS этот механизм скрыт под Elastic IP / ENI failover.

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 5. Зачем нужен ARP, если у нас уже есть IP-адреса?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 4