Компьютерные сети IP и маршрутизация: адресация, подсети, IPv6, таблицы маршрутов, BGP
0%

IP и маршрутизация: адресация, подсети, IPv6, таблицы маршрутов, BGP

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) — там впервые описан «катенет», сеть сетей. Три инженерных решения из этой работы определили всё, что мы имеем сегодня:

  1. Единое адресное пространство поверх чужих. У каждого интерфейса появляется адрес, не зависящий от технологии канала. Ethernet, PPP, LTE — снизу может быть что угодно, сверху виден только IP.
  2. Дейтаграмма вместо соединения. Каждый пакет самодостаточен и маршрутизируется независимо. Промежуточный узел не хранит состояние потока — и потому может упасть и подняться, не разрушив передачу.
  3. 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) байтах.

Заголовки IPv4 и IPv6: раскладка полей по 32-битным строкам

Поля, которые реально нужны инженеру каждый день:

  • 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» перестают быть разными темами.

Разбор адреса 10.11.6.37/22 по битам: маска, сеть, broadcast, деление на /24

Проверим руками — модуль 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).

Планирование адресного пространства

Самая дорогая ошибка проектирования сети делается за пять минут в начале проекта и живёт годами. Правила, выстраданные практикой:

  1. Резервируйте с запасом ×4. Адреса в приватном пространстве бесплатны, а переезд подсети — нет.
  2. Выравнивайте по степеням двойки и агрегируйте по регионам. 10.<регион>.<среда>.0/24 читается глазами и сворачивается в один маршрут: например, весь eu-central = 10.16.0.0/12.
  3. Не пересекайтесь ни с кем, с кем когда-нибудь соединитесь. Классика: кластер 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) — вторая по частоте причина.
  4. Если приватного пространства не хватает (крупный кластер съедает /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.

Ключевой шаг — 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 выходного интерфейса, возможны три исхода:

  1. IPv4, DF=0 — маршрутизатор режет пакет на фрагменты, собирает их получатель. Дорого (состояние сборки, уязвимость к атакам), теряется один фрагмент — теряется весь пакет.
  2. IPv4, DF=1 — пакет дропается, в источник летит ICMP Type 3 Code 4 «Fragmentation needed» с указанием MTU. Отправитель уменьшает размер — это и есть PMTUD (RFC 1191).
  3. 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.

Методика: «пакет не доходит»

Настройки ядра, которые чаще всего оказываются виноваты:

$ 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: установка соединения, надёжность, окна, управление перегрузкой.

Нашли неточность? Выделите фрагмент текста — рядом появится жучок.

Нужен разбор именно вашей ситуации?

Статья описывает общий случай. Если у вас частный — можно разобрать его отдельно, платно. А если не хватает целого материала, предложите тему: её оплачивают вскладчину, и она выходит открытой для всех.

Доска запросов