Ethernet-кадры — как байты летают по проводу
Когда вы открываете github.com в браузере, в какой-то момент HTTP-запрос превращается в электрические импульсы (или радиоволны для Wi-Fi), которые улетают по проводу к ближайшему свитчу. Между «у нас есть TCP-сегмент» и «по проводу побежали единицы и нули» лежит link layer — канальный уровень. Его текущий де-факто стандарт в проводных и беспроводных сетях — Ethernet (включая Wi-Fi, который технически 802.11, но кадры выглядят похоже).
В этом уроке разберём, как устроен Ethernet-кадр, что такое MAC-адрес и почему MTU = 1500 преследует нас с 1980-х годов. Это нужно знать любому DE, потому что когда у вас в Kubernetes pod не может пинговать соседа, или Kafka-продюсер шлёт сообщения по 2 МБ через VPN — проблема почти всегда на этом уровне.
Зачем нужен link layer
IP-уровень (layer 3) умеет одну вещь: маршрутизировать пакеты по адресам вида 93.184.216.34. Но IP ничего не знает про то, как именно отправить пакет через конкретный кусок проводов между вашим компьютером и роутером. Это работа link layer.
Link layer — это правила, по которым устройства в одной физической сети (один свитч, один Wi-Fi роутер, один сегмент) договариваются о том, кто и когда передаёт. Для Ethernet это означает:
- Каждый интерфейс имеет уникальный аппаратный адрес — MAC.
- Данные пересылаются дискретными порциями — frames (кадрами).
- Свитч решает, в какой порт послать кадр, на основе MAC-адреса назначения.
Важно: link layer работает только внутри одного broadcast domain. Как только пакет уходит через роутер — кадр распаковывается, IP-пакет переоборачивается в новый кадр уже для следующего сегмента. MAC-адреса каждый раз меняются, IP — остаются.
Структура Ethernet-кадра
Современный Ethernet-кадр (Ethernet II, он же DIX) выглядит так:
На уровне операционной системы вы обычно видите кадр без preamble и FCS (их обрабатывает сетевая карта). То есть в tcpdump или Wireshark вы увидите: 6 байт dst MAC, 6 байт src MAC, 2 байта EtherType, потом IP-заголовок и далее.
Вот настоящий кадр из tcpdump:
# tcpdump -i en0 -e -n -X -c 1 'tcp port 443'
# Опции: -e (show MAC), -n (no DNS), -X (hex+ASCII), -c 1 (один пакет)
12:34:56.789012 a4:83:e7:1a:bb:cc > 00:1c:42:00:00:18, ethertype IPv4 (0x0800), length 78:
192.168.1.42.54321 > 140.82.121.4.443: Flags [S], seq 3478291230, win 65535, ...
0x0000: 0800 4500 0040 0000 4000 4006 ...
Здесь:
a4:83:e7:1a:bb:cc— MAC отправителя (ваш ноутбук).00:1c:42:00:00:18— MAC получателя (обычно ваш роутер, если адресат за пределами локалки).ethertype IPv4 (0x0800)— внутри IP-пакет.- Дальше уже IP-заголовок начинается с
45 00(versions/IHL/TOS).
MAC-адреса — что это и откуда берутся
MAC (Media Access Control) — 48-битный аппаратный адрес сетевого интерфейса. Записывается как 6 пар hex-цифр через двоеточие или дефис: a4:83:e7:1a:bb:cc.
Структура MAC-адреса:
Несколько важных особенностей MAC-адресов:
-
Не маршрутизируются. В отличие от IP, MAC-адреса не имеют иерархии. Свитч просто запоминает «по этому проводу видел такой-то MAC». Маршрутизация по MAC не масштабируется — поэтому для глобальной сети есть IP.
-
Можно подменить. MAC «зашит в железо», но любой современный сетевой стек позволяет его поменять программно (
ip link set dev eth0 address aa:bb:cc:dd:ee:ff). Это используется в Wi-Fi для приватности и в атаках (MAC spoofing). -
Special-адреса.
ff:ff:ff:ff:ff:ff— broadcast, услышат все в сегменте.01:00:5e:xx:xx:xx— IPv4 multicast.33:33:xx:xx:xx:xx— IPv6 multicast.00:00:00:00:00:00— обычно невалидный, иногда означает «не назначен».
Посмотрите свои MAC-адреса:
# Linux:
ip link show
# 2: enp0s31f6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 ...
# link/ether a4:83:e7:1a:bb:cc brd ff:ff:ff:ff:ff:ff
# macOS:
ifconfig en0 | grep ether
# ether a4:83:e7:1a:bb:cc
# Windows:
getmac /v /fo list
В выводе вы увидите link/ether <MAC> brd ff:ff:ff:ff:ff:ff — значит broadcast-адрес сегмента это вот эти все единицы.
MTU = 1500 — откуда это число и почему оно везде
MTU (Maximum Transmission Unit) — максимальный размер payload, который влезает в один кадр. Для классического Ethernet это 1500 байт. С заголовком (14 байт) и FCS (4 байта) получается 1518 байт всего кадра.
Почему именно 1500? Это компромисс из 1980-х:
- Слишком маленький payload — большой overhead на заголовки. Если payload = 100 байт, заголовок 14 байт даёт 12% накладных расходов.
- Слишком большой payload — если в кадре ошибка, нужно перепосылать всё. И при коллизиях на старом hub-based Ethernet задержки росли.
1500 байт — цифра, которая давала хороший баланс на медном Ethernet 10 Мбит/с в 1980-х. И этот размер закрепился в стандарте. Сегодня технические причины уже не такие, но поменять MTU = 1500 во всём интернете нельзя, потому что:
Если ваш MTU больше, чем MTU у промежуточного устройства, пакет либо фрагментируется (на IPv4), либо дропается с ICMP ‘Packet Too Big’ (на IPv6 и при DF-флаге). Это популярная причина «таинственных» проблем — TCP-handshake проходит, мелкие пакеты ходят, а большие POST зависают. Называется PMTU Black Hole.
Что это значит на практике для DE:
-
VPN/туннели уменьшают MTU. WireGuard вычитает ~60 байт, IPsec — ~50-80 байт. На VPN MTU обычно 1420-1450. Если приложение шлёт пакеты с DF (don’t fragment) и MTU между endpoints меньше — может зависать.
-
Jumbo frames. В дата-центрах часто включают MTU = 9000 (jumbo frames). Это даёт ~6x меньше пакетов на тот же трафик, меньше CPU на упаковку/распаковку. Применяется в storage-сетях, между БД и приложением, в Kubernetes-кластере.
-
TCP подстраивается. TCP MSS (Maximum Segment Size) = MTU - 40 (IP+TCP headers). При установке соединения стороны договариваются о MSS. Если на пути MTU меньше — TCP уменьшает MSS через MSS clamping.
Проверить и поменять MTU:
# Linux: посмотреть текущий MTU всех интерфейсов
ip link show | grep mtu
# Поменять (нужен root):
sudo ip link set dev eth0 mtu 9000
# Проверить, проходит ли пакет нужного размера без фрагментации:
ping -M do -s 1472 8.8.8.8
# 1472 + 28 (IP+ICMP) = 1500, влезает в стандартный MTU
# Если не проходит "Frag needed and DF set" -- значит на пути MTU меньше
# Найти реальный path MTU до хоста:
tracepath 8.8.8.8
Frame check sequence — CRC и что бывает при ошибках
В конце кадра идёт FCS — 4 байта CRC-32 над всем кадром (от dst MAC до конца payload). Это контрольная сумма, которая позволяет приёмнику обнаружить повреждение кадра.
Что делает приёмник:
- Принимает кадр целиком.
- Считает CRC над dst MAC + src MAC + EtherType + payload.
- Сравнивает с FCS в кадре.
- Если не сходится — молча выбрасывает кадр. Уведомлять отправителя — не задача link layer. Это работа более высоких уровней (TCP retransmit, например).
Счётчики ошибок видны на интерфейсе:
# Linux:
ip -s link show eth0
# 2: eth0: ...
# RX: bytes packets errors dropped overrun mcast
# 1234567 8901 12 0 0 45
# ...
# Если errors растут -- проблема на физическом уровне: кабель, разъём, помехи
# Более детально через ethtool:
ethtool -S eth0 | grep -i error
# rx_crc_errors: 12
# rx_frame_errors: 0
# rx_missed_errors: 0
CRC-ошибки на нормальном проводном Ethernet должны быть нулевыми или единичными. Если их сотни в час — меняйте кабель/SFP/порт.
Wi-Fi — тот же Ethernet?
Технически Wi-Fi — это другой link-layer протокол, 802.11. Но операционная система обычно показывает Wi-Fi-интерфейс с такими же MAC-адресами и Ethernet-like кадрами. С точки зрения приложения это та же абстракция: интерфейс с MAC, MTU обычно 1500.
Различия под капотом:
- 802.11 кадры имеют 4 MAC-адреса (а не 2): source, destination, transmitter, receiver. Нужно для роуминга между точками доступа.
- Wi-Fi не использует CRC одинаково с Ethernet — там сложнее (FCS + per-fragment).
- Передача half-duplex (либо ты говоришь, либо слушаешь), а не full-duplex как современный проводной Ethernet.
- Включена защита от коллизий (CSMA/CA вместо CD).
Для большинства задач DE это неважно. Важно одно: MAC ваш и MAC приёмника по-прежнему видны и работают.
Попробуй сам
Откройте терминал и проведите следующие эксперименты:
# 1. Найти свой MAC
ip link show # Linux
ifconfig | grep ether # macOS
# 2. Захватить ОДИН Ethernet-кадр с MAC-адресами в hex:
sudo tcpdump -i any -e -n -X -c 1 'tcp port 443'
# Откройте параллельно браузер -- сделайте любой HTTPS-запрос
# Увидите: <src MAC> > <dst MAC>, ethertype IPv4 ... + hex-дамп пакета
# 3. Понять размер кадра в проводе:
ping -s 1472 -M do 8.8.8.8
# Если идёт -- ваш MTU >= 1500
# Поменяйте на 1500:
ping -s 1473 -M do 8.8.8.8
# "ping: local error: message too long" -- ровно на 1 байт больше не влезает
# 4. Посмотреть ARP-таблицу (что подсунет следующий урок):
ip neigh # Linux
arp -a # macOS
# Увидите соответствие IP -> MAC для всех соседей в локальной сети
# 5. Найти производителя по OUI:
# Возьмите свой MAC, первые 3 байта вставьте сюда:
# https://maclookup.app/ или https://standards-oui.ieee.org/
# OUI a4:83:e7 -> Intel, 00:1c:42 -> Parallels
Особенно полезно сделать пункт 2 и посмотреть на реальный hex-дамп. Вы увидите, как первые 14 байт это Ethernet-заголовок, потом начинается IP (45 00 … для IPv4), потом TCP, и наконец данные.
MTU в Docker overlay-сетях: типичный источник PMTU проблем Kafka и MTU: размер batch vs MTU при больших сообщениях