IP и маршрутизация: адресация, подсети, IPv6, таблицы маршрутов, BGP
На канальном уровне мы научились доставлять кадр соседу по проводу: MAC-адрес, ARP, коммутатор, широковещательный домен. Дальше начинается проблема, которую L2 не решает и решить не может: широковещательный домен не масштабируется. Плоское адресное пространство MAC не агрегируется — чтобы знать путь к любому из десятков миллиардов интерфейсов планеты, коммутатору пришлось бы хранить запись про каждый. Уже на десятках тысяч устройств такая сеть захлёбывается в broadcast-штормах.
IP — это ответ на вопрос «как соединить сети, которые не хотят быть одинаковыми». Не «как передать биты», а как заставить кооперироваться Ethernet в офисе, Wi-Fi в кафе, оптику магистрального оператора и радиоканал спутника, причём так, чтобы никто из них не знал устройства остальных. В статье разбираем адресацию до уровня битов, таблицы маршрутов до уровня системного вызова, а маршрутизацию в интернете — до уровня «почему упал Facebook в октябре 2021».
Общая карта уровней — в обзоре трека.
Задача, которую решает IP
В 1974 году Винт Серф и Боб Кан публикуют «A Protocol for Packet Network Intercommunication» (IEEE Transactions on Communications, doi:10.1109/TCOM.1974.1092259) — там впервые описан «катенет», сеть сетей. Три инженерных решения из этой работы определили всё, что мы имеем сегодня:
- Единое адресное пространство поверх чужих. У каждого интерфейса появляется адрес, не зависящий от технологии канала. Ethernet, PPP, LTE — снизу может быть что угодно, сверху виден только IP.
- Дейтаграмма вместо соединения. Каждый пакет самодостаточен и маршрутизируется независимо. Промежуточный узел не хранит состояние потока — и потому может упасть и подняться, не разрушив передачу.
- Best effort и fate sharing. Сеть не обещает доставку, порядок и отсутствие дублей. Состояние соединения живёт только на концах — «судьба» соединения связана с судьбой концевых узлов, а не с судьбой роутера посередине. Дэвид Кларк объясняет мотивацию в классической «The Design Philosophy of the DARPA Internet Protocols» (SIGCOMM 1988, PDF).
| Зачем появился | Какую проблему решил | Чем платим |
|---|---|---|
| Объединить разнородные сети без единого владельца | Агрегируемая иерархическая адресация вместо плоских MAC; маршрутизация вместо широковещания | Ненадёжность: потери, дубли, переупорядочивание — разгребает TCP или приложение |
| Убрать состояние из середины сети | Отказоустойчивость: пакеты переедут на другой путь сами | Нет гарантий задержки и полосы; QoS работает только внутри одного administrative domain |
| Сделать заголовок дешёвым для обработки | Пересылка на скорости провода, в железе | Мало места под метаданные; расширять протокол почти невозможно (IPv6 идёт 25+ лет) |
Дейтаграмма изнутри
Всё, что маршрутизатор знает о пакете, лежит в первых 20 (IPv4) или 40 (IPv6) байтах.
Поля, которые реально нужны инженеру каждый день:
- TTL / Hop Limit — счётчик оставшихся хопов. Каждый маршрутизатор уменьшает на единицу; на нуле пакет уничтожается и в источник летит
ICMP Time Exceeded. Это единственная защита от петель маршрутизации и одновременно фундамент traceroute. - Protocol / Next Header — что лежит внутри: 6 = TCP, 17 = UDP, 1 = ICMP, 47 = GRE, 50 = ESP (IPsec). Реестр IANA.
- DSCP + ECN — приоритизация и сигнал перегрузки. ECN сохраняет пакет вместо дропа: маршрутизатор ставит бит
CE, а получатель через TCP сообщает отправителю. Подробнее — в статье про TCP. - Total Length / Payload Length — длина. У IPv4 она включает заголовок, у IPv6 — нет; типичная ошибка при ручном разборе.
- Identification / Flags / Fragment Offset — фрагментация, к ней вернёмся отдельно.
Посмотрим на реальный пакет. Классический способ увидеть TTL в работе — запустить traceroute в одном окне и tcpdump в другом:
$ sudo tcpdump -ni eth0 -vv -c 2 'icmp or udp port 33434'
tcpdump: listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
12:04:11.883421 IP (tos 0x0, ttl 1, id 0, offset 0, flags [DF], proto UDP (17), length 60)
10.0.7.23.51201 > 203.0.113.9.33434: UDP, length 32
12:04:11.884902 IP (tos 0xc0, ttl 64, id 5316, offset 0, flags [none], proto ICMP (1), length 88)
10.0.0.1 > 10.0.7.23: ICMP time exceeded in-transit, length 68
IP (tos 0x0, ttl 1, id 0, offset 0, flags [DF], proto UDP (17), length 60)
10.0.7.23.51201 > 203.0.113.9.33434: UDP, length 32
Обратите внимание на вложенность: ICMP-ошибка несёт внутри себя первые 28+ байт исходного пакета. Именно поэтому отправитель понимает, к какому соединению относится ошибка. В Wireshark это дерево Internet Control Message Protocol → Internet Protocol Version 4 → User Datagram Protocol внутри одного пакета.
Полезные display-фильтры Wireshark для L3:
ip.addr == 203.0.113.9 # любое направление
ip.ttl < 5 # почти истёкшие — признак петли
icmp.type == 11 # Time Exceeded
icmp.type == 3 && icmp.code == 4 # Fragmentation needed — сигнал проблем с MTU
ip.flags.mf == 1 || ip.frag_offset > 0 # фрагменты
ipv6.nxt == 44 # IPv6-фрагмент
Адрес — это 32 бита, а не четыре числа
Запись 10.11.6.37 — просто человекочитаемая форма числа 0x0A0B0625. Вся арифметика подсетей — это операции над битами, и как только вы начинаете видеть биты, темы «маска», «broadcast», «агрегация» и «longest prefix match» перестают быть разными темами.
Проверим руками — модуль ipaddress из стандартной библиотеки Python:
import ipaddress
net = ipaddress.ip_network("10.11.6.37/22", strict=False) # strict=False — «обрезать» до сети
print(net) # 10.11.4.0/22
print(net.netmask) # 255.255.252.0
print(net.broadcast_address) # 10.11.7.255
print(net.num_addresses) # 1024
print(sum(1 for _ in net.hosts())) # 1022
a = ipaddress.ip_address("10.11.6.37")
b = ipaddress.ip_address("10.11.5.9")
print(a in net, b in net) # True True — первые 22 бита совпали
# деление на четыре /24 — ровно то, что на схеме
print([str(s) for s in net.subnets(new_prefix=24)])
# ['10.11.4.0/24', '10.11.5.0/24', '10.11.6.0/24', '10.11.7.0/24']
# обратная операция — агрегация
print(list(ipaddress.collapse_addresses(net.subnets(new_prefix=24)))) # [IPv4Network('10.11.4.0/22')]
Таблица, которую стоит держать в голове (её же выдаёт утилита ipcalc):
| Префикс | Маска | Адресов | Хостов | Типичное применение |
|---|---|---|---|---|
| /8 | 255.0.0.0 | 16 777 216 | 16 777 214 | целый блок RIR, 10.0.0.0/8 |
| /16 | 255.255.0.0 | 65 536 | 65 534 | VPC среднего размера |
| /22 | 255.255.252.0 | 1024 | 1022 | сегмент офиса, пул подсетей k8s |
| /24 | 255.255.255.0 | 256 | 254 | минимальный анонсируемый в BGP префикс |
| /29 | 255.255.255.248 | 8 | 6 | линк до провайдера с запасом |
| /30 | 255.255.255.252 | 4 | 2 | point-to-point по старой школе |
| /31 | 255.255.255.254 | 2 | 2 | point-to-point по RFC 3021 — сеть и broadcast не резервируются |
| /32 | 255.255.255.255 | 1 | 1 | loopback роутера, host route, anycast VIP |
Два адреса «съедаются» в каждой подсети, потому что все нули — это идентификатор самой сети, а все единицы — направленный broadcast. На линках точка-точка это 50 % потерь, и /31 (RFC 3021) как раз чинит нелепость. В IPv6 такой проблемы нет: broadcast там отменили.
Прикладной смысл маски. Хост сравнивает адрес назначения со своей подсетью: совпало — ARP и отправка напрямую соседу; не совпало — пакет уходит на шлюз. Отсюда классическая авария «поменяли /24 на /25, половина сети перестала видеть шлюз».
Специальные диапазоны
Полный реестр — RFC 6890 и IANA Special-Purpose Address Registry. Практический минимум:
| Диапазон | Что это | Где встречается |
|---|---|---|
10/8, 172.16/12, 192.168/16 |
приватные, RFC 1918 | LAN, VPC, docker |
100.64/10 |
CGNAT, RFC 6598 | мобильные операторы; не для вашей LAN, но спасает при исчерпании 1918 |
127/8 |
loopback | 127.0.0.1 — лишь один адрес из 16 миллионов |
169.254/16 |
link-local, RFC 3927 | признак «DHCP не ответил»; 169.254.169.254 — metadata-сервис облаков |
224/4 |
multicast | OSPF 224.0.0.5, mDNS 224.0.0.251 |
192.0.2/24, 198.51.100/24, 203.0.113/24 |
документационные, RFC 5737 | примеры в статьях (в том числе в этой) |
240/4 |
зарезервирован «на будущее» | 268 млн адресов, которые до сих пор не удалось разморозить |
fc00::/7, fe80::/10, 2000::/3 |
ULA, link-local, глобальный юникаст IPv6 | см. раздел про IPv6 |
От классов к CIDR: история, объясняющая всё остальное
Изначально старшие биты адреса жёстко задавали размер сети: класс A — /8 (16 млн адресов), B — /16 (65 тысяч), C — /24 (254). Организации на 500 машин класс C был мал, а класс B — избыточен в 130 раз; выдавали B. К началу 90-х стало ясно, что пространство кончится не потому, что адресов мало, а потому, что они раздаются кусками неподходящего размера.
CIDR (RFC 1519, актуальная версия — RFC 4632) убрал классы: длина префикса стала независимым числом, передаваемым вместе с адресом. Это дало две вещи сразу:
- VLSM — нарезать блок подсетями разного размера под фактическую потребность;
- агрегацию — провайдер с блоком
/16анонсирует наружу один префикс вместо 256 отдельных/24. Без агрегации глобальная таблица маршрутов давно бы не поместилась ни в одно железо.
Чем платим: маску теперь обязан передавать протокол маршрутизации (RIPv1 не умел — отсюда «classful» аварии в старых сетях), а операторы обязаны фильтровать чужие анонсы (не всегда делают — см. раздел про инциденты BGP).
Планирование адресного пространства
Самая дорогая ошибка проектирования сети делается за пять минут в начале проекта и живёт годами. Правила, выстраданные практикой:
- Резервируйте с запасом ×4. Адреса в приватном пространстве бесплатны, а переезд подсети — нет.
- Выравнивайте по степеням двойки и агрегируйте по регионам.
10.<регион>.<среда>.0/24читается глазами и сворачивается в один маршрут: например, весьeu-central=10.16.0.0/12. - Не пересекайтесь ни с кем, с кем когда-нибудь соединитесь. Классика: кластер Kubernetes с pod CIDR
10.244.0.0/16и корпоративная сеть10.244.0.0/16— после поднятия VPN половина трафика уезжает в никуда. Дефолтные диапазоны Docker (172.17.0.0/16) и multipass/libvirt (192.168.122.0/24) — вторая по частоте причина. - Если приватного пространства не хватает (крупный кластер съедает
/8под поды), берите100.64.0.0/10— это легальный «второй RFC 1918».
Пример разумного плана на регион:
| Блок | Назначение |
|---|---|
10.16.0.0/16 |
сеть VPC: подсети AZ по /20 |
10.17.0.0/16 |
pod CIDR кластера (по /24 на узел, до 256 узлов) |
10.18.0.0/20 |
service CIDR (ClusterIP — виртуальные адреса, наружу не маршрутизируются) |
10.19.0.0/24 |
линки VPN и point-to-point по /31 |
10.20.0.0/24 |
loopback-адреса роутеров и anycast VIP, по /32 |
Forwarding и routing — это два разных механизма
Термины путают постоянно, а разница принципиальная:
- Routing (control plane) — построение таблицы: протоколы, политики, статические маршруты. Работает в секундах, на CPU, в userspace-демоне (BIRD, FRR).
- Forwarding (data plane) — принятие решения по каждому пакету на основе уже готовой таблицы. Работает в наносекундах, в ядре или в ASIC.
или broadcast/multicast?"} B -- да --> C["Наверх: локальная доставка
в сокет по protocol + порту"] B -- нет --> D{"net.ipv4.ip_forward = 1?"} D -- нет --> E["Дроп: хост не роутер"] D -- да --> F["Поиск в FIB:
longest prefix match"] F --> G{"Маршрут найден?"} G -- нет --> H["ICMP Destination Unreachable
net unreachable"] G -- да --> I{"TTL − 1 > 0?"} I -- нет --> J["ICMP Time Exceeded
пакет уничтожен"] I -- да --> K{"Размер > MTU интерфейса?"} K -- "да, DF=1" --> L["ICMP Frag Needed
с указанием MTU"] K -- "да, DF=0" --> M["Фрагментация
только IPv4"] K -- нет --> N["ARP/NDP для next-hop
отправка кадра"] M --> N
Ключевой шаг — longest prefix match: из всех подходящих маршрутов выигрывает самый специфичный, то есть с наибольшей длиной префикса. 0.0.0.0/0 (дефолт) подходит всегда и потому проигрывает всем. Это правило и делает возможной иерархию: провайдер анонсирует /16, а клиент, переехавший к другому провайдеру, — свой /24 из середины этого блока, и трафик пойдёт к нему.
Таблица маршрутов Linux
$ ip route show
default via 10.0.0.1 dev eth0 proto dhcp src 10.0.7.23 metric 100
10.0.0.0/22 dev eth0 proto kernel scope link src 10.0.7.23 metric 100
10.17.0.0/16 via 10.0.7.1 dev eth0 proto bird metric 32
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 linkdown
192.168.90.0/24 dev wg0 proto kernel scope link src 192.168.90.4
blackhole 198.51.100.0/24
Как это читать:
proto kernel— маршрут появился сам при назначении адреса на интерфейс (connected).proto dhcp,proto static,proto bird— кто его поставил.scope link— назначение достижимо напрямую, без шлюза (ARP по адресу назначения).via— есть next-hop.src— какой адрес источника подставится, если приложение не выбрало явно.metric— при равных префиксах выигрывает меньшая метрика.blackhole— молча дропать;unreachableиprohibit— дропать с ICMP. Дешёвый способ отрезать сеть без фаервола.
Не гадайте, что выберет ядро, — спросите:
$ ip route get 10.17.3.9
10.17.3.9 via 10.0.7.1 dev eth0 src 10.0.7.23 uid 1000
cache
$ ip route get 8.8.8.8 from 192.168.90.4
8.8.8.8 from 192.168.90.4 via 10.0.0.1 dev eth0 uid 1000
cache
ip route get прогоняет настоящий алгоритм поиска ядра, включая правила policy routing. Это первая команда при разборе «почему трафик идёт не туда».
Проверить, какой источник в итоге выбрался для живых соединений:
$ ss -tn state established '( dport = :443 )'
Recv-Q Send-Q Local Address:Port Peer Address:Port
0 0 10.0.7.23:51442 203.0.113.9:443
0 0 [2a01:4f8:1:2::5]:33210 [2606:4700::6810:85e5]:443
Policy routing: маршрутизация не только по адресату
Linux ищет не в одной таблице, а в списке правил (RPDB). Каждому правилу соответствует своя таблица:
$ ip rule
0: from all lookup local
100: from 192.168.90.0/24 lookup vpn
32766: from all lookup main
32767: from all lookup default
$ ip route show table vpn
default dev wg0 scope link
Правила просматриваются по возрастанию приоритета; первое, чья таблица дала маршрут, побеждает. Так делают split tunneling, маркировку трафика через fwmark (ip rule add fwmark 0x1 table vpn) и multi-homing с двумя провайдерами, когда ответ обязан уйти через тот же аплинк, откуда пришёл запрос.
Как это устроено внутри и сколько стоит
Наивный поиск «перебрать все маршруты, выбрать самый длинный подходящий» — O(n) по числу маршрутов, что при миллионе префиксов немыслимо. Используют бинарный (двоичный) trie по битам адреса:
поиск(адрес):
узел ← корень; лучший ← значение корня # дефолтный маршрут
для i от 0 до W-1: # W = 32 для IPv4, 128 для IPv6
бит ← i-й слева бит адреса
узел ← узел.потомок[бит]
если узел = ∅: прервать
если у узла есть маршрут: лучший ← маршрут узла
вернуть лучший
import ipaddress
class Node:
__slots__ = ("child", "route")
def __init__(self):
self.child = [None, None] # потомки по биту 0 и 1
self.route = None
class Fib:
"""Учебная реализация longest prefix match на двоичном trie."""
def __init__(self):
self.root = Node()
def add(self, prefix: str, nexthop: str) -> None:
net = ipaddress.ip_network(prefix)
bits, width = int(net.network_address), net.max_prefixlen
node = self.root
for i in range(net.prefixlen): # спускаемся ровно на длину префикса
b = (bits >> (width - 1 - i)) & 1
if node.child[b] is None:
node.child[b] = Node()
node = node.child[b]
node.route = (prefix, nexthop) # маршрут висит на узле-глубине префикса
def lookup(self, addr: str):
ip = ipaddress.ip_address(addr)
bits, width = int(ip), ip.max_prefixlen
node, best = self.root, self.root.route
for i in range(width):
b = (bits >> (width - 1 - i)) & 1
node = node.child[b]
if node is None: # дальше веток нет — глубже не найдём
break
if node.route is not None:
best = node.route # запоминаем самый длинный из встреченных
return best
fib = Fib()
fib.add("0.0.0.0/0", "10.0.0.1")
fib.add("10.0.0.0/22", "link:eth0")
fib.add("10.17.0.0/16", "10.0.7.1")
fib.add("10.17.3.0/24", "10.0.7.9") # более специфичный — перебивает /16
print(fib.lookup("10.17.3.9")) # ('10.17.3.0/24', '10.0.7.9')
print(fib.lookup("10.17.9.1")) # ('10.17.0.0/16', '10.0.7.1')
print(fib.lookup("8.8.8.8")) # ('0.0.0.0/0', '10.0.0.1')
Сложность: поиск и вставка — O(W), где W — разрядность адреса (32 или 128), не зависит от числа маршрутов. Память — O(n · W) узлов в худшем случае. Реальные реализации сжимают путь: Linux использует LC-trie (level-compressed trie, работа Нильссона и Карлссона, 1999) — статистику можно увидеть прямо в ядре:
$ cat /proc/net/fib_triestat
Basic info: size of leaf: 40 bytes, size of tnode: 40 bytes.
Id 254:
Aver depth: 2.33
Max depth: 4
Leaves: 9
Prefixes: 11
Internal nodes: 5
Аппаратные маршрутизаторы идут другим путём: TCAM ищет по всем записям параллельно за один такт, но стоит дорого по цене и энергопотреблению — отсюда жёсткие лимиты на размер таблицы (см. «512k day» ниже). Программные ускорители вроде DIR-24-8 разменивают память на скорость: массив на 16 млн записей даёт ответ за одно обращение для 99 % префиксов.
traceroute: увидеть путь своими глазами
$ traceroute -n 203.0.113.9
traceroute to 203.0.113.9 (203.0.113.9), 30 hops max, 60 byte packets
1 10.0.0.1 0.641 ms 0.598 ms 0.577 ms
2 100.72.4.1 8.112 ms 8.090 ms 8.070 ms
3 * * *
4 198.51.100.17 9.442 ms 9.410 ms 198.51.100.21 9.881 ms
5 203.0.113.9 10.204 ms 10.180 ms 10.155 ms
Что здесь важно понять, иначе выводы будут неверными:
- Звёздочки на хопе 3 — почти никогда не потеря трафика. Маршрутизатор генерирует ICMP на медленном control plane и жёстко лимитирует его (
icmp_ratelimit), а многие операторы вообще отключают ответы. Если хоп 4 и 5 отвечают — путь исправен. - Разные адреса на одном хопе (
198.51.100.17и.21) — это ECMP: несколько равнозначных путей, и хеш от пятёрки полей раскидал зонды по разным.traceroute -T(TCP SYN на фиксированный порт) или Paris-traceroute держат пятёрку постоянной и рисуют один настоящий путь. - Traceroute показывает только путь «туда». Обратный маршрут может быть совсем другим (asymmetric routing), и рост RTT на хопе означает лишь то, что ответ этого конкретного роутера шёл дольше.
- Linux по умолчанию шлёт UDP на высокие порты,
-Iпереключает на ICMP Echo,-T— на TCP SYN. Через фаерволы часто проходит только-T --port 443.
mtr — тот же механизм в цикле, и он лучше отличает «ICMP лимитируют» от «здесь реально теряется»:
$ mtr -rwzbc 50 203.0.113.9
HOST: laptop Loss% Snt Last Avg Best Wrst StDev
1. AS??? 10.0.0.1 0.0% 50 0.6 0.7 0.5 1.9 0.2
2. AS64512 100.72.4.1 0.0% 50 8.1 8.4 7.9 14.2 1.1
3. AS64512 ??? 100.0% 50 0.0 0.0 0.0 0.0 0.0
4. AS64500 198.51.100.17 0.0% 50 9.4 9.6 9.2 12.7 0.6
5. AS64500 203.0.113.9 0.0% 50 10.2 10.4 10.1 13.0 0.5
Правило чтения: потери имеют значение только если они сохраняются до конца трассы. 100 % на промежуточном хопе при 0 % на последнем — артефакт, а не инцидент. Подробный разбор методики — в статье про диагностику сети.
MTU, фрагментация и чёрные дыры PMTUD
MTU — максимальный размер полезной нагрузки кадра: 1500 байт для классического Ethernet, 9000 для jumbo, 1420–1440 внутри туннелей. Если IP-пакет больше MTU выходного интерфейса, возможны три исхода:
- IPv4, DF=0 — маршрутизатор режет пакет на фрагменты, собирает их получатель. Дорого (состояние сборки, уязвимость к атакам), теряется один фрагмент — теряется весь пакет.
- IPv4, DF=1 — пакет дропается, в источник летит
ICMP Type 3 Code 4 «Fragmentation needed»с указанием MTU. Отправитель уменьшает размер — это и есть PMTUD (RFC 1191). - IPv6 — маршрутизаторы не фрагментируют вообще, только
ICMPv6 Packet Too Big. Фрагментировать может лишь отправитель, через заголовок расширения.
Современный Linux ставит DF почти всегда, поэтому вся схема держится на доставке ICMP. И вот тут возникает PMTUD black hole: администратор «закрывает ICMP целиком» на фаерволе, ICMP Frag Needed не доходит, отправитель продолжает слать 1500-байтные пакеты, они молча пропадают. Симптом узнаваемый: пинг идёт, TCP-соединение устанавливается, curl отдаёт заголовки — а тело ответа виснет намертво. Ровно в момент, когда пошли полноразмерные сегменты.
Проверка занимает 10 секунд:
# 1472 + 8 (ICMP) + 20 (IP) = ровно 1500
$ ping -M do -s 1472 -c 2 203.0.113.9
PING 203.0.113.9 (203.0.113.9) 1472(1500) bytes of data.
1480 bytes from 203.0.113.9: icmp_seq=1 ttl=57 time=10.3 ms
$ ping -M do -s 1473 -c 1 203.0.113.9
ping: local error: message too long, mtu=1500
# через туннель с MTU 1400 сигнал приходит от промежуточного узла:
$ ping -M do -s 1472 -c 1 10.17.3.9
From 10.0.7.1 icmp_seq=1 Frag needed and DF set (mtu = 1400)
$ tracepath -n 203.0.113.9
1?: [LOCALHOST] pmtu 1500
1: 10.0.0.1 0.712ms
2: 100.72.4.1 4.331ms
3: 100.72.4.1 4.298ms pmtu 1400
3: 203.0.113.9 21.884ms reached
Resume: pmtu 1400 hops 3 back 3
Лечение на практике: не блокировать icmp type 3 code 4 и icmpv6 packet-too-big, а на границе туннелей — подрезать MSS, чтобы TCP сразу договорился о безопасном размере:
# классический MSS clamping на роутере с туннелем
$ sudo iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN \
-j TCPMSS --clamp-mss-to-pmtu
В Wireshark чёрная дыра выглядит так: несколько маленьких пакетов проходят нормально, потом один сегмент в 1514 байт ретранслируется снова и снова ([TCP Retransmission]) без единого ACK — и ни одного ICMP в захвате. Фрагментированный трафик, наоборот, видно по пакетам с пометкой Fragmented IP protocol и финальным Reassembled IPv4 in frame #N.
IPv6 — не «IPv4 с длинными адресами»
Адресов в IPv4 — 4,3 млрд, и они кончились: IANA раздала последние блоки RIR 3 февраля 2011 года, RIPE NCC исчерпал пул 25 ноября 2019-го. IPv6 даёт 128 бит, но главное изменение не в длине.
Конкретные значения: ::1 — loopback (аналог 127.0.0.1), :: — неопределённый адрес до настройки, ff02::1 — все узлы на линке, ff02::2 — все маршрутизаторы, ::ffff:0:0/96 — IPv4-mapped адреса, которые видно внутри dual-stack сокетов.
Что реально изменилось по сравнению с IPv4:
/64— не про экономию, а про архитектуру. Младшие 64 бита отданы под идентификатор интерфейса, потому что на них построен SLAAC. Клиенту выдают/64на сегмент и/56–/48на площадку. Делать/120«чтобы сэкономить» — сломать автоконфигурацию.- ARP заменён на NDP (RFC 4861) поверх ICMPv6: Neighbor Solicitation/Advertisement, Router Solicitation/Advertisement, Duplicate Address Detection. NDP использует multicast solicited-node вместо broadcast — сетевые карты не будят весь сегмент.
- Автоконфигурация без DHCP (RFC 4862): маршрутизатор рассылает RA с префиксом, хост сам достраивает адрес. Практическое следствие: фильтровать ICMPv6 нельзя — сеть просто не поднимется.
- Нет broadcast, нет фрагментации в пути, нет контрольной суммы заголовка. Целостность проверяют L2 (FCS) и L4 — причём в IPv6 контрольная сумма UDP стала обязательной.
- Приватность встроена: постоянный адрес из MAC (EUI-64) позволял отслеживать устройство между сетями, поэтому хосты генерируют стабильно-случайные (RFC 7217) и временные (RFC 8981) адреса. Отсюда «зачем у моего ноутбука шесть адресов v6».
$ ip -6 addr show dev eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP
inet6 2a01:4f8:1:2::5/64 scope global
valid_lft forever preferred_lft forever
inet6 2a01:4f8:1:2:f1a3:9c2e:8b41:77d0/64 scope global temporary dynamic
valid_lft 604663sec preferred_lft 85663sec
inet6 fe80::546f:f3ff:fe01:1804/64 scope link
valid_lft forever preferred_lft forever
$ ip -6 route
2a01:4f8:1:2::/64 dev eth0 proto kernel metric 256 pref medium
fe80::/64 dev eth0 proto kernel metric 256 pref medium
default via fe80::1 dev eth0 proto ra metric 1024 expires 1798sec pref medium
Обратите внимание на две вещи: дефолтный маршрут указывает на link-local адрес шлюза (fe80::1) и поставлен proto ra — то есть из Router Advertisement, без DHCP. Соседи вместо ARP-таблицы:
$ ip -6 neigh
fe80::1 dev eth0 lladdr 00:1c:73:00:99:99 router REACHABLE
2a01:4f8:1:2::a dev eth0 lladdr 52:54:00:12:34:56 STALE
Литерал IPv6 в URL и утилитах требует квадратных скобок — забывают почти все:
$ curl -sv -o /dev/null 'http://[2a01:4f8:1:2::5]:8080/health' 2>&1 | head -2
* Trying [2a01:4f8:1:2::5]:8080...
* Connected to 2a01:4f8:1:2::5 (2a01:4f8:1:2::5) port 8080
$ openssl s_client -connect '[2606:4700::6810:85e5]:443' -servername example.com </dev/null 2>&1 | head -2
CONNECTED(00000003)
depth=2 C = US, O = Internet Security Research Group, CN = ISRG Root X1
Dual stack и Happy Eyeballs. Пока живут оба протокола, клиент запрашивает и A, и AAAA, и по RFC 8305 пробует их почти одновременно, отдавая IPv6 фору в ~50 мс. Поэтому «сломанный IPv6» у клиента почти не заметен на глаз — но добавляет задержку. Проверить, что выбралось:
$ dig +short AAAA example.com
2606:2800:21f:cb07:6820:80da:af6b:8b2c
$ curl -s -o /dev/null -w '%{remote_ip}\n' https://example.com # что выбрал Happy Eyeballs
2606:2800:21f:cb07:6820:80da:af6b:8b2c
$ curl -4 -s -o /dev/null -w '%{remote_ip}\n' https://example.com # принудительно IPv4
93.184.215.14
| Зачем появился | Что решил | Чем платим |
|---|---|---|
| Исчерпание 32-битного пространства | 128 бит, автоконфигурация, отказ от NAT как обязательного элемента | Двадцать пять лет сосуществования двух стеков: два набора правил фаервола, две таблицы маршрутов, два класса багов |
| Упростить обработку в железе | Фиксированный заголовок, нет пересчёта checksum, нет фрагментации на транзите | +20 байт на пакет; цепочки extension headers, которые middlebox часто дропают |
| Вернуть end-to-end связность | Каждый хост снова адресуем | Прямая адресуемость означает прямую достижимость — фаервол теперь обязателен, а не «получается сам собой из NAT» |
Про NAT, NAT64/DNS64 и то, почему «у меня работает, а у клиента нет», — отдельная статья про NAT, фаерволы и VPN.
Автономные системы: два класса протоколов маршрутизации
Интернет — не одна сеть, а около 80 тысяч автономных систем (AS): сетей под единым административным управлением со своей политикой. У каждой есть номер ASN, выданный RIR (AS13335 — Cloudflare, AS15169 — Google). Внутри AS маршруты считает IGP, между AS — EGP.
| IGP (внутри AS) | EGP (между AS) | |
|---|---|---|
| Примеры | OSPF, IS-IS, RIP, EIGRP | BGP-4 — единственный |
| Цель | найти кратчайший путь по метрике | реализовать политику: с кем и на каких условиях обмениваться трафиком |
| Метрика | стоимость линка, пропускная способность, задержка | AS_PATH, LOCAL_PREF, MED, communities — деньги и договорённости |
| Масштаб | сотни–тысячи маршрутов | ~1 млн префиксов IPv4 |
| Скорость сходимости | секунды | десятки секунд–минуты |
OSPF (link-state) устроен так: каждый маршрутизатор рассылает описание своих линков, все собирают одинаковую базу топологии и независимо считают кратчайшие пути алгоритмом Дейкстры — O((V + E) log V) по времени с двоичной кучей и O(V + E) по памяти. Именно поэтому большие OSPF-домены режут на areas: пересчёт всей топологии при каждом флапе линка дорог.
RIP (distance vector) знает только «до сети X — 4 хопа через соседа Y», рассылает вектор расстояний и страдает от count-to-infinity, откуда лимит в 15 хопов. Исторически важен, в новых сетях не применяется.
BGP — path vector: анонс несёт полный список AS, через которые прошёл. Это решает проблему петель радикально: увидел свой номер в AS_PATH — отбросил анонс.
BGP: маршрутизация как политика, а не как кратчайший путь
BGP-4 (RFC 4271) работает поверх TCP на порту 179 — надёжность доставки обновлений отдана транспорту. Сессия устанавливается вручную, между заранее согласованными соседями: интернет держится на десятках тысяч двусторонних договорённостей, а не на автообнаружении.
Мигание между Connect и Active — самый частый симптом: TCP не поднимается. Причины по убыванию частоты: фильтр на 179, неверный адрес соседа, несовпадение MD5/TCP-AO, несовпадение номера AS в OPEN.
Атрибуты, из которых складывается политика:
| Атрибут | Смысл | Кто ставит |
|---|---|---|
AS_PATH |
список пройденных AS | добавляется на каждом eBGP-хопе |
NEXT_HOP |
куда физически слать | сосед; внутри AS часто требует next-hop-self |
LOCAL_PREF |
«наш приоритет» — какой аплинк предпочесть | локальная политика, разлетается по iBGP |
MED |
подсказка соседу, каким из наших линков к нам заходить | мы, для соседа |
COMMUNITY |
метки-теги для автоматизации политик | все; де-факто язык договорённостей между операторами |
Порядок выбора лучшего пути (упрощённо, порядок важен — он объясняет, почему трафик идёт «не туда»): наибольший LOCAL_PREF → короче AS_PATH → лучше ORIGIN → меньше MED → eBGP предпочтительнее iBGP → меньше метрика IGP до next-hop → старше маршрут → меньше router-ID.
Ключевой вывод: AS_PATH стоит только на четвёртом месте, и он считает AS, а не километры и не миллисекунды. Путь через одну огромную AS «короче» пути через три маленькие, даже если физически он идёт через другой континент. BGP выбирает не быстрый маршрут, а тот, за который договорились платить.
$ sudo vtysh -c "show bgp ipv4 unicast summary"
BGP router identifier 198.51.100.2, local AS number 64500 vrf-id 0
BGP table version 1043921
RIB entries 1978432, using 363 MiB of memory
Neighbor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd
198.51.100.1 4 64501 918442 1204 0 0 0 3d02h12m 989114
198.51.100.5 4 64502 901233 1198 0 0 0 3d02h11m 988902
$ sudo vtysh -c "show bgp ipv4 unicast 203.0.113.9"
BGP routing table entry for 203.0.113.0/24
Paths: (2 available, best #1, table default)
64501 64510 64520
198.51.100.1 from 198.51.100.1 (198.51.100.1)
Origin IGP, localpref 200, valid, external, best (Local Pref)
Community: 64501:100
64502 64530 64510 64520
198.51.100.5 from 198.51.100.5 (198.51.100.5)
Origin IGP, localpref 100, valid, external
Здесь второй путь короче по числу пересадок? Нет — но даже будь он короче, localpref 200 решил бы всё раньше, чем дошло до сравнения AS_PATH. У BIRD та же картина другими словами:
$ birdc show protocols
Name Proto Table State Since Info
upstream1 BGP --- up 2026-07-13 Established
$ birdc show route for 203.0.113.9 all
203.0.113.0/24 unicast [upstream1 09:41:02.114] * (100) [AS64520i]
via 198.51.100.1 on eth0
BGP.as_path: 64501 64510 64520
Посмотреть на чужую AS можно и без доступа к железу — через whois-сервис Team Cymru или публичные коллекторы вроде bgp.tools, RIPE RIS / RIPEstat, RouteViews:
$ whois -h whois.cymru.com " -v 1.1.1.1"
AS | IP | BGP Prefix | CC | Registry | Allocated | AS Name
13335 | 1.1.1.1 | 1.1.1.0/24 | US | ARIN | 2010-07-14 | CLOUDFLARENET, US
Чем BGP платит: доверие по умолчанию
BGP спроектирован в эпоху, когда все операторы знали друг друга лично. Проверки «а имеет ли сосед право анонсировать этот префикс» в протоколе нет — есть только фильтры, которые каждый настраивает сам. Отсюда две беды:
- Hijack — чужой префикс анонсируется как свой. Более специфичный
/24перебивает легитимный/16по longest prefix match, и трафик уезжает к атакующему. - Route leak — маршруты, полученные от одного провайдера, случайно ретранслируются другому, и мелкий оператор становится «транзитом» для гигантского потока.
Хроника, которую стоит знать:
| Дата | Что случилось |
|---|---|
| 24.02.2008 | Pakistan Telecom анонсирует 208.65.153.0/24, чтобы заблокировать YouTube внутри страны; анонс утекает наружу — YouTube недоступен по всему миру ~2 часа |
| 24.04.2018 | Hijack префиксов Amazon Route 53; трафик MyEtherWallet уведён на подставной DNS, украдено криптовалюты на сотни тысяч USD |
| 24.06.2019 | BGP-«оптимизатор» у мелкого оператора генерирует более специфичные префиксы, Verizon их принимает без фильтров — разбор Cloudflare |
| 01.04.2020 | Утечка через Ростелеком: маршруты 200+ сетей, включая крупные облака, ушли не туда |
| 04.10.2021 | Facebook отзывает собственные префиксы после ошибочной команды на бэкбоне; вместе с маршрутами исчезают авторитативные DNS-серверы — официальный разбор |
Что с этим делают сейчас:
- RPKI + ROV (RFC 6811): владелец префикса публикует криптографически подписанный ROA «этот префикс имеет право анонсировать AS N», оператор проверяет анонсы валидатором (
rpki-client, Routinator) и отбрасываетinvalid. Покрытие уже более половины анонсов — статус своей сети видно на rpki.cloudflare.com и isbgpsafeyet.com. - IRR-фильтры и prefix-limit: принимать от клиента только зарегистрированные им объекты и рвать сессию, если пришло аномально много префиксов.
- MANRS — набор минимальных обязательств оператора: фильтрация, антиспуфинг (BCP 38), координаты для связи, актуальные данные в IRR.
512k day. 12 августа 2014 года глобальная таблица перевалила за 512 тысяч префиксов — предел TCAM по умолчанию у популярных Cisco 7600/ASR. Маршруты перестали помещаться, часть интернета «моргала» несколько часов. В 2019-м то же повторилось на отметке 768k. Сегодня в DFZ около миллиона префиксов IPv4 и ~200 тысяч IPv6; текущие числа — на bgp.potaroo.net и в CIDR Report. Мораль для инженера: агрегируйте анонсы — каждый лишний /24 оплачивает памятью весь интернет.
Anycast: один префикс во множестве мест
Anycast — не отдельный протокол, а трюк поверх обычной маршрутизации: один и тот же префикс анонсируется из десятков точек мира, и BGP сам приводит клиента к «ближайшей» по своим метрикам. Так работают 1.1.1.1, 8.8.8.8, корневые DNS-серверы и точки присутствия CDN.
Проверить, в какой POP вы попали, можно штатными средствами DNS:
$ dig +short CH TXT id.server @1.1.1.1
"FRA"
$ dig +short TXT o-o.myaddr.l.google.com @8.8.8.8
"203.0.113.44"
Что это даёт: близость (меньше RTT), устойчивость (упал сайт — анонс снимается, трафик перетекает), размазывание DDoS по всем POP. Чем платим: состояние нельзя держать на конкретном узле — смена маршрута посреди TCP-сессии рвёт соединение. Поэтому anycast идеален для UDP/DNS и требует аккуратности для TCP. Продолжение темы — в статьях про DNS, балансировку и CDN и edge.
Методика: «пакет не доходит»
маршрут есть?"} A -- нет --> A1["Нет маршрута или blackhole
чинить таблицу"] A -- да --> B{"tcpdump на исходящем
интерфейсе: пакет виден?"} B -- нет --> B1["Проблема локально:
фаервол OUTPUT, привязка к адресу,
policy routing выбрал другую таблицу"] B -- да --> C{"ARP/NDP разрешился?
ip neigh"} C -- "FAILED" --> C1["Сосед недоступен на L2:
VLAN, порт, MAC — см. канальный уровень"] C -- да --> D{"tcpdump на приёмнике:
пакет пришёл?"} D -- нет --> D1["Теряется в пути:
traceroute, mtr, MTU, фильтр у провайдера"] D -- да --> E{"Ответ ушёл обратно
тем же путём?"} E -- нет --> E1["Асимметрия:
rp_filter, conntrack, policy routing"] E -- да --> F["L3 исправен —
разбираться выше: TCP, TLS, приложение"]
Настройки ядра, которые чаще всего оказываются виноваты:
$ sysctl net.ipv4.ip_forward net.ipv4.conf.all.rp_filter net.ipv6.conf.all.forwarding
net.ipv4.ip_forward = 1
net.ipv4.conf.all.rp_filter = 1 # 1 = strict: ответ обязан уходить тем же интерфейсом
net.ipv6.conf.all.forwarding = 1
rp_filter=1 (RFC 3704, strict mode) молча дропает пакеты при асимметричной маршрутизации — на multi-homed хостах нужен режим 2 (loose) или policy routing. Это один из самых коварных багов: трафик исчезает без единой записи в логах, а tcpdump показывает вход и не показывает выход. Счётчики видно в nstat -az | grep -i martian и /proc/net/netstat.
Типичные ошибки
- Пересекающиеся приватные диапазоны. Проектируйте адресное пространство до первого
terraform apply, а не после первого VPN. - «Закрыли ICMP для безопасности». Ломает PMTUD в IPv4 и вообще всю сеть в IPv6. Разрешайте как минимум
type 3 code 4,time-exceededи весь ICMPv6 на линке. - Вера в traceroute как в источник истины. Он показывает путь «туда», ICMP лимитируется, ECMP размывает картину. Потери на промежуточном хопе без потерь на конечном — не проблема.
- Дефолтный маршрут в двух таблицах. С policy routing легко получить конфигурацию, где
ip route getдаёт один ответ, а трафик из контейнера идёт иначе. Проверяйте сfromиiif. /24как рефлекс. Для линка точка-точка это 254 потерянных адреса; берите/31или IPv6.- Отсутствие RPKI ROA на своих префиксах. Стоит полчаса работы в LIR-портале и защищает от целого класса hijack.
- Забытая агрегация. Анонс четырёх смежных
/24вместо одного/22умножает нагрузку на каждый маршрутизатор планеты. - MTU туннеля по умолчанию. WireGuard, VXLAN, GRE, IPsec отъедают 40–80 байт; без MSS clamping это классический «SSH работает, а
git cloneвиснет».
Мини-итог
- IP решает задачу объединения разнородных сетей ценой отказа от гарантий: дейтаграмма, best effort, состояние только на концах.
- Адрес — это биты. Маска — граница внутри них. Всё остальное (подсети, агрегация, longest prefix match, anycast) — следствия этой одной идеи.
- Forwarding и routing разделены: таблицу строит медленный control plane, решение по пакету принимает быстрый data plane за
O(W)независимо от размера таблицы. ip route get,tcpdump,traceroute/mtr,ping -M do,tracepathзакрывают 90 % диагностики L3. Начинайте с них, а не с гипотез.- IPv6 — не «длинный IPv4»: другой механизм автоконфигурации, обязательный ICMPv6, отсутствие фрагментации на транзите.
- BGP выбирает не кратчайший, а политически предпочтительный путь, и доверяет соседям по умолчанию. RPKI, фильтры и агрегация — то, чем инженер платит за чужое спокойствие и своё.
Источники
- Cerf V., Kahn R. «A Protocol for Packet Network Intercommunication», 1974 — ieeexplore.ieee.org/document/1092259
- Clark D. «The Design Philosophy of the DARPA Internet Protocols», SIGCOMM 1988 — dl.acm.org/doi/10.1145/52324.52336
- RFC 791 (IPv4), RFC 8200 (IPv6), RFC 4632 (CIDR), RFC 4271 (BGP-4)
- RFC 1191 и RFC 8201 — Path MTU Discovery для IPv4 и IPv6
- RFC 7454 «BGP Operations and Security» — практический чек-лист фильтров
- Kurose J., Ross K. «Computer Networking: A Top-Down Approach», глава 4 — лучший учебник по forwarding и маршрутизации
- Beau Bullock, Iljitsch van Beijnum «BGP» (O’Reilly) и документация FRRouting / BIRD
man 8 ip-route,man 8 ip-rule,man 7 rtnetlink— исчерпывающее описание модели маршрутизации Linux
Что дальше
Мы научились доставлять отдельный пакет до нужного хоста — но пакет может потеряться, продублироваться и прийти не в том порядке, а приложению нужен поток байтов. Как из ненадёжных дейтаграмм собирают надёжное соединение, зачем нужны окна и что происходит, когда сеть перегружена, — в следующей статье: TCP: установка соединения, надёжность, окна, управление перегрузкой.