Компьютерные сети
Разбираться в сетях физику приходится потому, что почти любые данные, необходимые для работы, сегодня получаются через сеть. Установка отдаёт телеметрию по TCP, каталог наблюдений находится за HTTP-интерфейсом, расчёт запускается на кластере по SSH, а результаты отправляются на сервер группы. Когда что-либо из этого перестаёт работать, место разрыва быстро находит тот, кто представляет путь от программы до провода.
В настоящей главе рассматривается устройство сети ниже прикладного уровня: слои и адреса, маршрутизация, транспортные протоколы UDP и TCP, контроль перегрузок и средства проверки каждого уровня. Прикладные протоколы — система доменных имён, HTTP и TLS — и работа с сетью из Python рассмотрены в следующей главе, «Веб-технологии».
Вызов requests.get('https://api.example.org/runs', timeout=5) выглядит как одна операция, однако за ним стоят пять шагов на разных уровнях сети, и каждый из них может завершиться ошибкой.
| Шаг | Протокол | Чем проверить |
|---|---|---|
имя api.example.org превращается в IP-адрес | DNS | dig, запрос к DNS поверх HTTPS |
| ядро выбирает маршрут к этому адресу | IP | ip route get, tracepath |
| открывается соединение с портом 443 сервера | TCP | ss, tcpdump |
| поверх соединения устанавливается шифрованный канал | TLS | openssl, curl -v |
| по каналу уходит запрос и приходит ответ | HTTP | curl -v, requests |
Представление о пути запроса целиком позволяет отличить отсутствие маршрута от неразрешённого имени, закрытый порт от ошибки сервера и медленный канал от медленной программы. Маршрут и соединение TCP рассматриваются в настоящей главе, разрешение имени, шифрование и обмен по HTTP — в главе «Веб-технологии».
Листинги главы выполнены на одной машине — виртуальной машине под Ubuntu 24.04 с ядром Linux 6.8, iproute2 6.1, tcpdump 4.99 и Python 3.12, на которой записаны демонстрации лекции «Введение в веб»; выводы опыта с CUBIC и BBR взяты из записи этой демонстрации. Учебные сети построены внутри машины из сетевых пространств имён и получают адреса из диапазонов, отведённых для документации, а серверы слушают петлевой адрес 127.0.0.1, поэтому на другой машине выводы те же, кроме времени, случайных номеров портов, адресов MAC и номеров последовательности.
Слайды к главе. Материал этой главы и главы «Веб-технологии» изложен также в лекции «Введение в веб» (слои и адреса, маршрутизация, транспорт, DNS, HTTP и TLS, клиент на Python, собственный сервис) с демонстрациями в терминале; слайды лекции доступны на сайте книги и в PDF.
Сетевые модели: OSI и TCP/IP
Слайды к главе. Слои сети, инкапсуляция заголовков, адреса IPv4 и IPv6, MAC-адреса и ARP изложены также в первой части лекции «Введение в веб» с демонстрацией учебной сети из двух узлов; слайды лекции доступны на сайте книги и в PDF.
Сеть устроена слоями. Каждый слой решает свою узкую задачу и пользуется услугами слоя, лежащего ниже, ничего не зная о его внутреннем устройстве. Приложение формулирует задачу «отправить эти байты на такой-то адрес» и не учитывает ни маршрутов, ни того, медный там кабель или оптика. Благодаря такому разделению Wi-Fi был изобретён без переписывания браузеров и протокола IP.
Классическая модель OSI, созданная как эталонная, насчитывает семь уровней. В таблице указан и уровень более компактной модели TCP/IP, к которому относится каждый из них.
| № | Уровень OSI | Чем занимается | Уровень TCP/IP | Единица данных |
|---|---|---|---|---|
| 7 | Прикладной | протоколы приложений: HTTP, DNS, SSH | прикладной | сообщение |
| 6 | Представления | кодировки, сжатие, шифрование | прикладной | сообщение |
| 5 | Сеансовый | управление сеансами связи | прикладной | сообщение |
| 4 | Транспортный | доставка между процессами: TCP, UDP, QUIC | транспортный | сегмент TCP, датаграмма UDP |
| 3 | Сетевой | адресация и маршрутизация между сетями: IP, ICMP | межсетевой | пакет |
| 2 | Канальный | передача между соседними узлами: Ethernet, Wi-Fi | сетевого доступа | кадр |
| 1 | Физический | биты через провод или эфир | сетевого доступа | бит |
Названия единиц данных представляют собой устоявшееся соглашение, а не часть стандарта: в модели OSI единица любого уровня называется блоком данных протокола этого уровня, а в спецификации IP (RFC 791) пакет называется датаграммой.
Интернет построен по модели TCP/IP из четырёх уровней: сетевого доступа (в RFC 1122 он называется канальным, link layer), межсетевого, транспортного и прикладного. Физический и канальный уровни OSI в ней объединены в первый, а три верхних слиты в один прикладной. Протоколы стека TCP/IP разнесены по уровням OSI условно: стек создавался параллельно с моделью и в неё в точности не укладывается; так, TLS шифрует данные приложения, но работает поверх TCP и целиком не помещается ни на один уровень. Далее мы рассмотрим уровни этой модели снизу вверх.
Инкапсуляция
Каждый уровень добавляет к данным свой заголовок. HTTP-запрос приложения становится полезной нагрузкой сегмента TCP, в заголовке которого записаны порты, номера последовательности и флаги. Сегмент вкладывается в пакет IP с адресами отправителя и получателя и счётчиком TTL, а пакет — в кадр Ethernet с MAC-адресами соседних узлов и контрольной суммой FCS в конце.
┌──────────┬──────┬───────┬───────────────────┬─────┐
│ Ethernet │ IP │ TCP │ HTTP: GET /runs … │ FCS │
└──────────┴──────┴───────┴───────────────────┴─────┘
└ данные приложения ┘
└─────── сегмент TCP ───────┘
└──────────── пакет IP ────────────┘
└────────────────── кадр Ethernet ──────────────────┘
Получатель снимает заголовки в обратном порядке. Маршрутизатор на пути разбирает только кадр и пакет: он отбрасывает старый кадр, строит новый для следующего участка и уменьшает TTL на единицу, а заголовок TCP и данные не трогает. Поэтому MAC-адреса меняются на каждом участке пути, а IP-адреса отправителя и получателя сохраняются до тех пор, пока их вместе с портами TCP не подменит трансляция адресов NAT, рассмотренная ниже.
Уровень сетевого доступа
Самый нижний уровень отвечает на вопрос «как физически доставить биты соседнему узлу».
Физический уровень
Средой передачи здесь являются витая пара и коаксиальный кабель, оптоволокно (одномодовое для дальних линий, многомодовое для коротких), радио в виде Wi-Fi по стандарту 802.11, Bluetooth и сотовой связи. Уровень преобразует единицы и нули в сигнал и обратно.
Среду передачи характеризуют скорость, полоса пропускания, затухание сигнала и помехоустойчивость. Проблемы на этом уровне: перебитый кабель, наводка от силового шкафа, некачественно обжатый коннектор, растянутый сверх нормы сегмент. В лаборатории они дают заметную долю жалоб на медленную сеть, поэтому диагностику начинают снизу, с проверки физического подключения: при отсоединённом кабеле ip link показывает у интерфейса флаг NO-CARRIER — отсутствие несущей.
Канальный уровень
Уровнем выше принятые биты собираются в кадры (frames) и передаются между устройствами, находящимися внутри одной локальной сети. Здесь работают Ethernet (стандарт IEEE 802.3) и Wi-Fi (IEEE 802.11), а для соединений «точка-точка» — PPP.
У каждого сетевого интерфейса есть 48-битный MAC-адрес. У физической сетевой карты его задаёт производитель (первые три байта образуют код производителя), у виртуальных интерфейсов — пар veth, мостов, контейнеров и виртуальных машин — он выбирается случайно, а современные операционные системы при подключении к Wi-Fi часто подставляют случайный адрес вместо заводского. Программа знает IP-адрес получателя, а кадр отправляется на MAC-адрес следующего узла: самого получателя, если он находится в той же сети, иначе шлюза. Соответствие между адресами выясняет протокол ARP: узел рассылает широковещательный вопрос «у кого адрес 192.0.2.20?» и запоминает ответ в таблице соседей. В IPv6 ту же задачу решает протокол обнаружения соседей NDP. В институтских сетях встречается VLAN, представляющая собой логическое разделение одной физической сети на независимые сегменты, не видящие друг друга, чтобы, например, сеть управления установкой не пересекалась с офисной.
Состояние этого уровня позволяют просмотреть следующие команды.
ip -br link # интерфейсы, их состояние и MAC-адреса
ip -s link show eth0 # счётчики кадров, в том числе ошибки и отброшенные
ethtool eth0 # скорость, дуплекс, наличие несущей
ethtool -S eth0 # подробная статистика драйвера
ip neigh # таблица соседей: IP-адреса и MAC-адреса
Утилиты ifconfig, arp и netstat из пакета net-tools устарели, и современные дистрибутивы, в том числе Ubuntu, по умолчанию их не устанавливают: их заменяют ip и ss из пакета iproute2. Имя eth0 в примерах условно; в современных дистрибутивах интерфейсы называются по расположению устройства, например enp0s1 или ens3.
Учебная сеть из двух узлов
Рассмотрим работу канального уровня на учебной сети, для которой достаточно одной машины. Сетевое пространство имён представляет собой механизм ядра Linux, дающий процессам собственный набор интерфейсов, адресов и маршрутов (см. главу «Внутреннее устройство Linux»); оно играет роль отдельной машины, а пара виртуальных интерфейсов veth соединяет два пространства подобно кабелю. Адреса взяты из диапазона 192.0.2.0/24, отведённого для документации, поэтому учебная сеть не пересекается с настоящей. Здесь и далее команды, создающие пространства имён и перехватывающие пакеты, выполняются от имени root, например в оболочке, открытой командой sudo -i; ключ -n a команды ip и префикс ip netns exec a выполняют команду в пространстве a.
$ cat lan.sh
# Два узла одной сети: пространства имён a и b, соединённые парой veth
ip netns add a
ip netns add b
ip link add va netns a type veth peer name vb netns b
ip -n a addr add 192.0.2.10/24 dev va
ip -n b addr add 192.0.2.20/24 dev vb
ip -n a link set va up
ip -n b link set vb up
$ sh lan.sh
$ ip -n a -br link
lo DOWN 00:00:00:00:00:00 <LOOPBACK>
va@if2 UP c2:4a:45:2f:6e:af <BROADCAST,MULTICAST,UP,LOWER_UP>
$ ip -n a -br addr
lo DOWN
va@if2 UP 192.0.2.10/24 fe80::c04a:45ff:fe2f:6eaf/64
$ ip netns exec a ping -c 2 192.0.2.20
PING 192.0.2.20 (192.0.2.20) 56(84) bytes of data.
64 bytes from 192.0.2.20: icmp_seq=1 ttl=64 time=0.081 ms
64 bytes from 192.0.2.20: icmp_seq=2 ttl=64 time=0.080 ms
--- 192.0.2.20 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1008ms
rtt min/avg/max/mdev = 0.080/0.080/0.081/0.000 ms
$ ip -n a neigh
192.0.2.20 dev va lladdr b2:b5:3f:8e:8d:30 REACHABLE
В пространстве a есть только петлевой интерфейс lo и конец пары va со случайным MAC-адресом; кроме адреса 192.0.2.10/24, у него есть локальный адрес IPv6, который интерфейс назначил себе сам. Эхо-запрос ping до соседа проходит за десятую долю миллисекунды, а после него в таблице соседей появляется MAC-адрес узла b, полученный по ARP. Пространство имён удаляется командой ip netns del a вместе со своими интерфейсами; процессы, запущенные в нём, перед этим необходимо завершить.
Сетевой уровень
Канальный уровень доставляет кадры только внутри одной локальной сети. Чтобы достичь машины, находящейся в другом городе, необходим уровень выше, сетевой, с главным протоколом IP.
IP задаёт адреса интерфейсов и формат пакета. Каждый узел на пути пересылает пакет дальше по своей таблице маршрутов, а заполняют таблицы администратор или протоколы маршрутизации, например OSPF внутри организации и BGP между организациями. Сообщения о недоставке и эхо-запросы ping передаёт сопутствующий протокол ICMP.
Адресация IPv4
IPv4 задаёт 32-битный адрес, записываемый четырьмя десятичными октетами, например 192.0.2.10. Всего таких адресов около четырёх миллиардов, и они давно распределены. Отсюда трансляция адресов (NAT) в большинстве домашних и многих институтских сетей: машины внутри получают частные адреса, а наружу выходят под общим публичным адресом маршрутизатора, который подменяет в пакетах адрес и порт отправителя. Провайдеры добавляют к этому ещё и общий NAT на адресах 100.64.0.0/10. Долгосрочным решением служит IPv6.
Запись вида 192.0.2.0/24 называется префиксом. Число после косой черты — длина префикса, то есть количество старших битов адреса, отведённых под номер сети; оставшиеся биты нумеруют узлы. Сеть 192.0.2.0/24 содержит 256 адресов, различающихся последним октетом, но узлам из них можно назначить 254: первый адрес обозначает саму сеть, а последний служит широковещательным. Модуль ipaddress стандартной библиотеки позволяет выполнять арифметику адресов и префиксов.
import ipaddress
net = ipaddress.ip_network('192.0.2.0/24')
print(net.num_addresses, net.netmask, net.broadcast_address)
print(len(list(net.hosts())), ipaddress.ip_address('192.0.2.200') in net)
print(ipaddress.ip_address('192.0.2.10').is_private, ipaddress.ip_address('192.0.2.10').is_global)
addr = ipaddress.ip_address('2001:0db8:85a3:0000:0000:8a2e:0370:7334')
print(addr)
print(addr.exploded)
256 255.255.255.0 192.0.2.255
254 True
True False
2001:db8:85a3::8a2e:370:7334
2001:0db8:85a3:0000:0000:8a2e:0370:7334
Часть диапазонов зарезервирована для особых целей.
| Диапазон | Назначение |
|---|---|
10.0.0.0/8 (16,7 млн адресов), 172.16.0.0/12 (1 млн), 192.168.0.0/16 (65 536) | частные сети (RFC 1918): в интернете не маршрутизируются и повторяются в разных организациях |
100.64.0.0/10 | общий NAT провайдера (RFC 6598) |
127.0.0.0/8 | петлевой интерфейс, сама машина |
169.254.0.0/16 | локальные адреса канала, которые узел назначает себе сам, не получив адреса (RFC 3927) |
192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24 | примеры в документации (RFC 5737) |
RFC 1918 не закрепляет диапазоны за организациями определённого размера; домашние маршрутизаторы обычно берут подсеть из последнего. Адреса документации не встречаются в настоящих сетях, поэтому именно они выбраны для учебных сетей этой главы. Атрибут is_private модуля ipaddress истинен не только для диапазонов RFC 1918, но и для петлевых адресов, адресов документации и других диапазонов, которые реестр IANA относит к неглобальным: адрес 192.0.2.10 в выводе выше он тоже назвал частным.
Адресация IPv6
IPv6 решает проблему нехватки адресов. Адрес занимает 128 бит и записывается восемью группами по четыре шестнадцатеричные цифры, например 2001:0db8:85a3:0000:0000:8a2e:0370:7334. Ведущие нули в каждой группе опускаются, а одну непрерывную цепочку нулевых групп заменяет двойное двоеточие, и записанный выше адрес превращается в 2001:db8:85a3::8a2e:370:7334, как в выводе ipaddress. Двойное двоеточие допускается в адресе один раз, иначе запись стала бы неоднозначной; канонический вид — строчные буквы и самая длинная цепочка нулевых групп, заменённая на ::, — установлен RFC 5952. Для документации отведён префикс 2001:db8::/32 (RFC 3849), петлевой адрес записывается как ::1, локальные адреса канала начинаются с fe80::, а аналогом частных сетей служат уникальные локальные адреса fc00::/7 (RFC 4193).
Маршрутизация
Слайды к главе. Таблица маршрутов, выбор самого длинного префикса, правила маршрутизации и диагностика пути изложены также во второй части лекции «Введение в веб» с демонстрацией учебной сети с маршрутизатором; слайды лекции доступны на сайте книги и в PDF.
Маршрутизация отвечает на вопрос «куда отправить принятый пакет дальше». Каждый узел, включая рабочий компьютер, хранит таблицу маршрутов и для каждого пакета выбирает подходящую строку.
Таблица маршрутов
Строка таблицы сопоставляет префикс сети со способом доставки: напрямую через интерфейс, если сеть к нему подключена, или через шлюз, которым является соседний маршрутизатор. Так выглядит таблица узла a учебной сети, построенной в конце раздела:
$ ip -n a route
default via 192.0.2.1 dev va
192.0.2.0/24 dev va proto kernel scope link src 192.0.2.10
198.51.100.128/25 via 192.0.2.254 dev va
Из всех строк, под которые подходит адрес, выбирается строка с самым длинным префиксом, а при равной длине — с меньшей метрикой. Маршрут default, он же 0.0.0.0/0, подходит под любой адрес и срабатывает последним. Пакет к соседу 192.0.2.20 уходит прямо в интерфейс va, пакет к 198.51.100.200 подходит под /25 и уходит через шлюз 192.0.2.254, хотя маршрут по умолчанию тоже подходит, а всё остальное уходит через 192.0.2.1. Какой маршрут ядро выберет для конкретного адреса, показывает ip route get; пакетов команда не отправляет.
$ ip -n a route get 198.51.100.200
198.51.100.200 via 192.0.2.254 dev va src 192.0.2.10 uid 0
cache
$ ip -n a route get 198.51.100.10
198.51.100.10 via 192.0.2.1 dev va src 192.0.2.10 uid 0
cache
Когда сервер соседнего корпуса не отвечает, а машины своей комнаты доступны, проверку начинают с ip route get и адреса сервера: команда показывает, через какой шлюз и интерфейс уйдёт пакет. При маршруте по умолчанию путь формально есть до любой сети, поэтому причиной чаще оказываются неверный шлюз, отсутствие маршрута обратно у удалённой стороны или фильтрация пакетов на пути.
Задача о выборе маршрута
Рассмотрим правило самого длинного префикса на таблице из пяти строк, где префиксы разной длины перекрываются.
default via 192.0.2.1
64.0.0.0/4 via 192.0.2.2
128.0.0.0/1 via 192.0.2.19
192.0.2.0/25 via 192.0.2.9
192.0.2.48 via 192.0.2.17
Префикс /n задаёт число старших битов, которые должны совпасть с адресом, поэтому для каждого адреса найдём все подходящие строки и выберем из них самую длинную.
| Адрес | Подходящие строки | Выбранная строка |
|---|---|---|
| 192.0.2.48 | default, /1, /25, /32 | 192.0.2.48 via 192.0.2.17: точный адрес |
| 192.0.2.100 | default, /1, /25 | 192.0.2.0/25 via 192.0.2.9 |
| 200.1.2.3 | default, /1 | 128.0.0.0/1 via 192.0.2.19 |
| 10.1.1.1 | default | default via 192.0.2.1 |
Строка 192.0.2.48 без длины префикса означает маршрут к одному узлу, /32, конкретнее любого другого. Адрес 192.0.2.100 подходит и под 128.0.0.0/1, поскольку первый октет 192 в двоичной записи равен 11000000 и начинается с единицы, и под 192.0.2.0/25, охватывающую адреса от 0 до 127 в последнем октете; выбирается более длинный префикс /25. Адрес 200.1.2.3 тоже начинается с единичного бита и уходит по строке /1, а 10.1.1.1 не подходит ни под одну строку, кроме default. Префикс 64.0.0.0/4 охватывает адреса от 64.0.0.0 до 79.255.255.255: четыре старших бита адреса должны совпасть с 0100, — и ни один из четырёх адресов под него не подходит. Проверить ответы позволяет само ядро: запишем ту же таблицу в пространство имён q с интерфейсом-заглушкой dummy и спросим ip route get.
$ cat quiz.sh
# Таблица маршрутов задачи о выборе маршрута в пространстве имён q
ip netns add q
ip -n q link add d0 type dummy
ip -n q addr add 192.0.2.10/24 dev d0
ip -n q link set d0 up
ip -n q route add default via 192.0.2.1
ip -n q route add 64.0.0.0/4 via 192.0.2.2
ip -n q route add 128.0.0.0/1 via 192.0.2.19
ip -n q route add 192.0.2.0/25 via 192.0.2.9
ip -n q route add 192.0.2.48 via 192.0.2.17
$ sh quiz.sh
$ ip -n q route
default via 192.0.2.1 dev d0
64.0.0.0/4 via 192.0.2.2 dev d0
128.0.0.0/1 via 192.0.2.19 dev d0
192.0.2.0/25 via 192.0.2.9 dev d0
192.0.2.0/24 dev d0 proto kernel scope link src 192.0.2.10
192.0.2.48 via 192.0.2.17 dev d0
$ for a in 192.0.2.48 192.0.2.100 200.1.2.3 10.1.1.1; do ip -n q route get $a | head -1; done
192.0.2.48 via 192.0.2.17 dev d0 src 192.0.2.10 uid 0
192.0.2.100 via 192.0.2.9 dev d0 src 192.0.2.10 uid 0
200.1.2.3 via 192.0.2.19 dev d0 src 192.0.2.10 uid 0
10.1.1.1 via 192.0.2.1 dev d0 src 192.0.2.10 uid 0
К пяти строкам задачи ядро добавило шестую, 192.0.2.0/24: сеть, подключённую к интерфейсу d0. На ответы она не влияет, поскольку у адресов 192.0.2.48 и 192.0.2.100 есть более длинные подходящие префиксы.
Правила и несколько таблиц
Таблица маршрутов в Linux не одна. Какую использовать для конкретного пакета, определяют правила маршрутизации; так выглядят правила по умолчанию в новом пространстве имён:
$ ip -n q rule
0: from all lookup local
32766: from all lookup main
32767: from all lookup default
Правила просматриваются по возрастанию номера приоритета — меньший номер означает более высокий приоритет. Сначала пакет ищется в таблице local с адресами самой машины, затем в main — обычной таблице, которую показывает ip route, — и в пустой таблице default. Поэтому правило самого длинного префикса действует внутри одной таблицы: пакет на собственный адрес машины уходит в петлевой интерфейс по таблице local, хотя в main есть маршрут по умолчанию.
Для машины в двух разных подсетях достаточно одной таблицы main. Отдельные таблицы и правила нужны, когда путь зависит не только от адреса назначения, но и от адреса отправителя, входящего интерфейса или метки пакета. Типичный случай — компьютер на установке, подключённый к институтской сети и к сети управления: ответы с адреса сети установки должны уходить через её шлюз. По маршруту по умолчанию они ушли бы в институтскую сеть, и её фильтры отбросили бы их как пакеты с чужим адресом отправителя. Скрипт rule.sh строит такую машину в пространстве имён p с двумя интерфейсами-заглушками:
$ cat rule.sh
# Машина в двух сетях: институтской 192.0.2.0/24 и сети установки 203.0.113.0/24
ip netns add p
ip -n p link add inst0 type dummy
ip -n p link add ctl0 type dummy
ip -n p addr add 192.0.2.10/24 dev inst0
ip -n p addr add 203.0.113.10/24 dev ctl0
ip -n p link set inst0 up
ip -n p link set ctl0 up
ip -n p link set lo up
ip -n p route add default via 192.0.2.1
# пакеты с адреса сети установки — через её шлюз, по отдельной таблице 100
ip -n p route add default via 203.0.113.1 table 100
ip -n p rule add from 203.0.113.0/24 lookup 100
$ sh rule.sh
$ ip -n p rule
0: from all lookup local
32765: from 203.0.113.0/24 lookup 100
32766: from all lookup main
32767: from all lookup default
$ ip -n p route get 198.51.100.7
198.51.100.7 via 192.0.2.1 dev inst0 src 192.0.2.10 uid 0
cache
$ ip -n p route get 198.51.100.7 from 203.0.113.10
198.51.100.7 from 203.0.113.10 via 203.0.113.1 dev ctl0 table 100 uid 0
cache
$ ip -n p route get 192.0.2.10
local 192.0.2.10 dev lo table local src 192.0.2.10 uid 0
cache <local>
Пакет к адресу 198.51.100.7 по умолчанию уходит через институтский шлюз, а тот же пакет с адресом отправителя из сети установки — через шлюз 203.0.113.1 по таблице 100; пакет на собственный адрес 192.0.2.10 попадает в таблицу local и уходит в lo. Таким образом, путь пакета определяет не только адрес назначения, но и правило, выбравшее таблицу.
Диагностика пути
Когда маршрут есть, а пакеты не доходят, необходимо выяснить, где они теряются. Команда ping проверяет доступность адресата и измеряет время туда и обратно. Каждый маршрутизатор уменьшает поле TTL пакета на единицу и отбрасывает пакет, когда оно доходит до нуля, сообщая об этом отправителю сообщением ICMP. На этом основаны traceroute и tracepath: они отправляют пакеты с растущим TTL и перечисляют узлы пути. Утилита traceroute в современных дистрибутивах часто не установлена; tracepath из пакета iputils работает без прав root и заодно определяет MTU — наибольший размер пакета, проходящего участок без фрагментации. Утилита mtr делает то же непрерывно и подсчитывает потери на каждом узле.
ip route get 198.51.100.10 # какой маршрут выберет ядро; пакеты не отправляются
ping -c 4 198.51.100.10 # доступность и время туда и обратно
tracepath -n 198.51.100.10 # узлы пути и MTU пути
mtr -n -r -c 100 198.51.100.10 # отчёт по 100 пробам на каждом узле
Значение TTL в ответе ping тоже информативно: Linux отправляет пакеты с TTL 64, и ответ с ttl=63 означает один маршрутизатор на пути. Отчёт mtr позволяет показать сетевым администраторам, на каком переходе начинается разрыв; значимы при этом потери, сохраняющиеся до последнего узла. Потери только на промежуточном маршрутизаторе обычно означают, что он ограничивает частоту ответов ICMP, а не теряет транзитные пакеты.
Учебная сеть с маршрутизатором
Скрипт net.sh строит три пространства имён: узел a в сети 192.0.2.0/24, узел b в сети 198.51.100.0/24 и маршрутизатор r с интерфейсами в обеих сетях. Маршрутизатором r делает параметр ядра net.ipv4.ip_forward, разрешающий пересылку пакетов между интерфейсами.
$ cat net.sh
# Две сети и маршрутизатор r между ними: 192.0.2.0/24 и 198.51.100.0/24
for ns in a r b; do ip netns add $ns; done
ip link add va netns a type veth peer name ra netns r
ip link add vb netns b type veth peer name rb netns r
ip -n a addr add 192.0.2.10/24 dev va
ip -n r addr add 192.0.2.1/24 dev ra
ip -n r addr add 198.51.100.1/24 dev rb
ip -n b addr add 198.51.100.10/24 dev vb
for d in "a va" "r ra" "r rb" "b vb"; do ip -n ${d% *} link set ${d#* } up; done
ip netns exec r sysctl -qw net.ipv4.ip_forward=1
ip -n b route add default via 198.51.100.1
$ sh net.sh
$ ip -n a route
192.0.2.0/24 dev va proto kernel scope link src 192.0.2.10
$ ip netns exec a ping -c 1 198.51.100.10
ping: connect: Network is unreachable
$ ip -n a route add default via 192.0.2.1
$ ip netns exec a ping -c 2 198.51.100.10
PING 198.51.100.10 (198.51.100.10) 56(84) bytes of data.
64 bytes from 198.51.100.10: icmp_seq=1 ttl=63 time=0.099 ms
64 bytes from 198.51.100.10: icmp_seq=2 ttl=63 time=0.108 ms
--- 198.51.100.10 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1051ms
rtt min/avg/max/mdev = 0.099/0.103/0.108/0.004 ms
$ ip netns exec a tracepath -n 198.51.100.10
1?: [LOCALHOST] pmtu 1500
1: 192.0.2.1 0.066ms
1: 192.0.2.1 0.006ms
2: 198.51.100.10 0.010ms reached
Resume: pmtu 1500 hops 2 back 2
$ ip -n a route add 198.51.100.128/25 via 192.0.2.254
В таблице узла a сначала есть только подключённая сеть, поэтому ping до 198.51.100.10 сразу завершается сообщением Network is unreachable: ядро не знает, куда отправить пакет. После добавления маршрута по умолчанию через r ответ приходит с ttl=63 — на один маршрутизатор меньше начальных 64, а tracepath показывает путь из двух узлов и MTU 1500. Более конкретный маршрут 198.51.100.128/25 через 192.0.2.254 действует для адресов от .128 до .255, а 198.51.100.10 по-прежнему уходит через маршрут по умолчанию; шлюза 192.0.2.254 в сети нет, и ip route get этого не замечает, поскольку пакетов не отправляет. Таблица узла a после последней команды и выбор маршрута для адресов 198.51.100.200 и 198.51.100.10 приведены в начале раздела.
Транспортный уровень
Слайды к главе. Порты и сокеты, UDP и TCP, рукопожатие в перехвате tcpdump и контроль перегрузок с опытом CUBIC и BBR на линии с потерями изложены также в третьей части лекции «Введение в веб» с демонстрациями в терминале; слайды лекции доступны на сайте книги и в PDF.
IP доставляет пакет до машины, однако на ней работают десятки программ. Какой из них предназначены данные, указывает номер порта — первая задача транспортного уровня. Вторая — надёжность доставки; здесь выбирают из двух протоколов в зависимости от того, что дороже: потеря данных или задержка.
Порты и сокеты
Порт представляет собой 16-битное число от 1 до 65 535. Сервер слушает известный порт: 22 — SSH, 53 — DNS, 80 и 443 — HTTP и HTTPS, 5432 — PostgreSQL. Порты ниже 1024 считаются привилегированными: в Linux занять их по умолчанию может только процесс root или процесс с правом CAP_NET_BIND_SERVICE, а границу задаёт параметр ядра net.ipv4.ip_unprivileged_port_start. Клиенту ядро выдаёт временный порт из диапазона net.ipv4.ip_local_port_range, а параметр net.ipv4.tcp_syn_retries задаёт число повторов запроса на соединение.
$ sysctl net.ipv4.ip_local_port_range net.ipv4.ip_unprivileged_port_start net.ipv4.tcp_syn_retries
net.ipv4.ip_local_port_range = 32768 60999
net.ipv4.ip_unprivileged_port_start = 1024
net.ipv4.tcp_syn_retries = 6
Соединение TCP однозначно задаётся четвёркой: адрес и порт клиента, адрес и порт сервера. Поэтому к одному порту сервера одновременно подключаются тысячи клиентов: их соединения различаются адресами и временными портами. Программа работает с соединением через сокет, представляющий собой объект ядра, которому в процессе соответствует файловый дескриптор, а в Python — объект socket.socket. Сокет бывает и без IP: локальный сокет AF_UNIX представлен файлом, и через него, например, клиент PostgreSQL по умолчанию подключается к серверу на той же машине.
Чтобы проверить, слушает ли программа порт, достаточно команды ss -tln; с ключом -p она показывает и процессы, которым принадлежат сокеты (процессы других пользователей видны только с sudo). Адрес 127.0.0.1 в колонке Local Address означает, что сервер доступен только с самой машины, 0.0.0.0 — со всех её интерфейсов IPv4. Для встроенного в Python веб-сервера, который раздаёт каталог www с файлом hello.txt и слушает петлевой адрес, вывод выглядит так; знак & запускает сервер в фоне, а останавливается он командой kill с номером задания, например kill %1:
$ python3 -m http.server 8000 --bind 127.0.0.1 --directory www >/dev/null 2>&1 &
$ ss -tln 'sport = :8000'
State Recv-Q Send-Q Local Address:Port Peer Address:PortProcess
LISTEN 0 5 127.0.0.1:8000 0.0.0.0:*
Попытка соединения может завершиться двумя разными ошибками, и различие между ними позволяет определить, где искать причину. Если порт не слушает ни один процесс, ядро адресата сразу отвечает сегментом RST, и клиент получает отказ Connection refused. Если же пакеты по пути или на входе адресата отбрасывает межсетевой экран, ответа нет вовсе, и попытка завершается по тайм-ауту; без явного ограничения ядро Linux повторяет запрос на соединение шесть раз с удваивающейся паузой, начиная с 1 с, то есть около двух минут. Воспроизведём оба случая на учебной сети из двух узлов, построенной скриптом lan.sh: на узле b запустим сервер на порту 8000, а межсетевому экрану nftables поручим отбрасывать пакеты к порту 8001. Клиент conn.py по очереди соединяется с портами 8000, 8001 и 8002 узла b, ограничив ожидание двумя секундами.
import socket
for port in (8000, 8001, 8002):
try:
with socket.create_connection(('192.0.2.20', port), timeout=2) as s:
print(port, 'соединение', s.getsockname(), '->', s.getpeername())
except OSError as err:
print(port, type(err).__name__, err)
$ cat fw.sh
# Узел b из lan.sh: сервер на порту 8000, порт 8001 закрыт межсетевым экраном
ip netns exec b python3 -m http.server 8000 --bind 192.0.2.20 >/dev/null 2>&1 &
ip netns exec b nft add table inet fw
ip netns exec b nft add chain inet fw input '{ type filter hook input priority 0; }'
ip netns exec b nft add rule inet fw input tcp dport 8001 drop
$ sh fw.sh
$ ip netns exec a python3 conn.py
8000 соединение ('192.0.2.10', 57004) -> ('192.0.2.20', 8000)
8001 TimeoutError timed out
8002 ConnectionRefusedError [Errno 111] Connection refused
Вывод показывает и четвёрку соединения: клиент получил от ядра временный порт, а сервер принял соединение на порту 8000. Порт 8001 не ответил за отведённые 2 с, порт 8002 сразу ответил отказом. Таким образом, отказ указывает на адресата, у которого порт никто не слушает, а тайм-аут — на фильтр по пути или на входе адресата.
UDP: быстро и без гарантий
UDP не устанавливает соединения, не подтверждает доставку, не следит за порядком приходящих датаграмм и не борется с перегрузками. Датаграмма может потеряться, прийти дважды или обогнать предыдущую, и протокол об этом не узнает.
Зато у UDP минимальные накладные расходы и задержка. Поэтому на нём работают запросы DNS (повторить запрос проще, чем поддерживать соединение; большие ответы и передача зон идут по TCP), голосовая и видеосвязь, онлайн-игры. Потерянный фрагмент там уже не нужен: пока его запрашивают повторно, разговор уходит вперёд. Видео по запросу и большинство трансляций, напротив, передаются по HTTP поверх TCP или QUIC. На UDP построен и сам QUIC, транспорт HTTP/3, заново реализующий надёжную доставку и шифрование. Для физика существенно, что о потере пакетов телеметрии UDP не сообщает: пропуски выявляет только номер пакета, записанный в самих данных.
TCP: надёжно, но дороже
TCP доставляет поток байтов в исходном порядке, без пропусков и дубликатов либо сообщает приложению об ошибке: соединение разрывается по тайм-ауту или сбрасывается сегментом RST. Ценой являются установление соединения, подтверждения, повторные передачи и контроль перегрузок. 16-битная контрольная сумма TCP пропускает часть искажений, поэтому целостность важных данных проверяют на уровне приложения: средствами TLS или хешем файла.
Соединение открывается тройным рукопожатием:
- клиент отправляет серверу сегмент
SYNсо своим случайным начальным номером последовательности x; - сервер отвечает сегментом
SYN-ACK— своим начальным номером y и подтверждением x + 1; - клиент подтверждает его сегментом
ACK, и соединение установлено.
В сегментах SYN и SYN-ACK стороны сообщают и параметры соединения: наибольший размер сегмента MSS, множитель окна, поддержку выборочных подтверждений SACK и метки времени. Данные клиент отправляет сразу после SYN-ACK, вместе с подтверждением или следом за ним, поэтому рукопожатие обходится в одно время туда и обратно (RTT); этой задержки нет у UDP. TCP Fast Open (RFC 7413) при повторных соединениях передаёт данные уже в SYN, а QUIC совмещает установление соединения с рукопожатием TLS.
Гарантии TCP обеспечивают поля его заголовка; в скобках указана длина поля в битах.
| Порт отправителя (16) | Порт получателя (16) |
| Номер последовательности (32) |
| Номер подтверждения (32) |
| Смещение (4) | Резерв (4) | Флаги (8) | Окно (16) |
| Контрольная сумма (16) | Указатель срочности (16) |
| Параметры: MSS, множитель окна, SACK, метки времени (0–320) |
Номер последовательности является номером первого байта сегмента в потоке. Номер подтверждения — номер следующего ожидаемого байта: всё, что идёт до него, получено. Окно сообщает, сколько ещё байтов готов принять получатель с учётом множителя из параметров (RFC 7323), и тем самым управляет потоком. Отправитель держит в пути не больше меньшего из двух окон: окна получателя и собственного окна перегрузки, рассмотренного ниже. Смещение данных — длина заголовка в 32-битных словах, от 20 до 60 байт. Флаги отмечают назначение сегмента: SYN — установление соединения, ACK — подтверждение, PSH — данные, которые получатель передаёт приложению без задержки, FIN — закрытие, RST — сброс.
Закрывается соединение сегментами FIN с каждой стороны. Сторона, закрывшая соединение первой, ещё 60 с держит его в состоянии TIME_WAIT, чтобы запоздавшие сегменты не попали в новое соединение с той же четвёркой адресов и портов.
Различия двух транспортных протоколов сведены в таблицу.
| Свойство | UDP | TCP |
|---|---|---|
| соединение | нет | рукопожатие перед данными |
| доставка | без гарантий | подтверждения и повторы |
| порядок | не сохраняется | сохраняется |
| единица передачи | датаграмма | поток байтов |
| применение | DNS, голосовая и видеосвязь, QUIC | HTTP/1.1 и HTTP/2, SSH, почта |
Рукопожатие в перехвате tcpdump
Рассмотрим весь обмен в перехвате пакетов. Сервер из раздела о портах продолжает работать. Запрос к нему запишем в файл req.txt: стартовая строка, заголовок Host и пустая строка, каждая строка заканчивается парой символов \r\n (устройство запроса HTTP рассмотрено в главе «Веб-технологии»). Утилита tcpdump записывает в файл сегменты с портом 8000 на петлевом интерфейсе lo, а утилита nc открывает соединение, отправляет файл запроса и выводит ответ, пока сервер не закроет соединение.
$ printf 'GET /hello.txt HTTP/1.1\r\nHost: localhost\r\n\r\n' > req.txt
$ tcpdump -i lo -U -w hs.pcap 'tcp port 8000' 2>/dev/null &
$ nc 127.0.0.1 8000 < req.txt
HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/3.12.3
Date: Sat, 03 Oct 2026 03:51:25 GMT
Content-type: text/plain
Content-Length: 13
Last-Modified: Sat, 03 Oct 2026 03:51:23 GMT
pulse 4.2 mA
$ ss -tan 'sport = :8000'
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 5 127.0.0.1:8000 0.0.0.0:*
TIME-WAIT 0 0 127.0.0.1:8000 127.0.0.1:47054
После остановки перехвата командой kill разберём записанный файл; параметры TCP из строк удалены фильтром sed, чтобы строки умещались на странице.
$ tcpdump -r hs.pcap -nn -t 2>/dev/null | sed -E 's/, options \[[^]]*\]//'
IP 127.0.0.1.47054 > 127.0.0.1.8000: Flags [S], seq 4126516469, win 65495, length 0
IP 127.0.0.1.8000 > 127.0.0.1.47054: Flags [S.], seq 2648559322, ack 4126516470, win 65483, length 0
IP 127.0.0.1.47054 > 127.0.0.1.8000: Flags [.], ack 1, win 512, length 0
IP 127.0.0.1.47054 > 127.0.0.1.8000: Flags [P.], seq 1:45, ack 1, win 512, length 44
IP 127.0.0.1.8000 > 127.0.0.1.47054: Flags [.], ack 45, win 512, length 0
IP 127.0.0.1.8000 > 127.0.0.1.47054: Flags [P.], seq 1:187, ack 45, win 512, length 186
IP 127.0.0.1.47054 > 127.0.0.1.8000: Flags [.], ack 187, win 511, length 0
IP 127.0.0.1.8000 > 127.0.0.1.47054: Flags [P.], seq 187:200, ack 45, win 512, length 13
IP 127.0.0.1.47054 > 127.0.0.1.8000: Flags [.], ack 200, win 511, length 0
IP 127.0.0.1.8000 > 127.0.0.1.47054: Flags [F.], seq 200, ack 45, win 512, length 0
IP 127.0.0.1.47054 > 127.0.0.1.8000: Flags [F.], seq 45, ack 201, win 512, length 0
IP 127.0.0.1.8000 > 127.0.0.1.47054: Flags [.], ack 46, win 512, length 0
Буквы в квадратных скобках — флаги сегмента: S — SYN, . — ACK, P — PSH, F — FIN. Первые три строки составляют рукопожатие: SYN клиента с временного порта, SYN-ACK сервера и ACK клиента. Затем идут запрос с флагом PSH — 44 байта файла req.txt, — его подтверждение сервером, два сегмента ответа — заголовки длиной 186 байт и тело длиной 13 байт — и их подтверждения. Последними идут FIN сервера, который закрывает соединение первым, поскольку встроенный сервер отвечает по HTTP/1.0 и соединение не поддерживает, FIN клиента, подтверждающий FIN сервера, и последнее подтверждение сервера; утилита nc без ключа -N сама соединение не закрывает и ждёт, пока его закроет сервер. Номера seq и ack после рукопожатия tcpdump показывает относительными, отсчитанными от начальных номеров сторон: seq 1:45 означает байты запроса с первого по 44-й, а ack 45 — что сервер получил их все и ожидает 45-й. Команда ss сразу после запроса показывает соединение сервера в состоянии TIME-WAIT: закрыл его первым сервер.
Контроль перегрузок
TCP не знает пропускной способности пути заранее и определяет её опытным путём, регулируя окно перегрузки — объём данных, отправленных без подтверждения. В начале соединения окно растёт вдвое за каждое время туда и обратно; это медленный старт, который продолжается до первой потери или до порога. Дальше алгоритмы различаются:
- Tahoe (1988) после любой потери возвращается к медленному старту с одного сегмента;
- Reno (1990) при трёх повторных подтверждениях вдвое уменьшает окно и продолжает передачу (быстрое восстановление), а к медленному старту возвращается только по тайм-ауту (RFC 5681);
- CUBIC (RFC 9438) после потери уменьшает окно лишь до 0,7 прежнего и наращивает его по кубической функции времени: быстро вдали от прежнего максимума и осторожно вблизи него, независимо от RTT. Это алгоритм по умолчанию в Linux, Windows и системах Apple;
- BBR (Google, 2016) строит модель пути — полосу узкого места и минимальную задержку — и не принимает случайную потерю за признак перегрузки, поэтому выигрывает на длинных путях с потерями. В основной ветке ядра Linux находится первая версия алгоритма; в общем узком месте с малым буфером она вытесняет соседние потоки CUBIC.
Для алгоритмов, уменьшающих окно вдвое при каждой потере, установившуюся скорость позволяет оценить формула Матиса:
$$ v \approx \frac{\mathrm{MSS}}{\mathrm{RTT}} \cdot \frac{C}{\sqrt{p}}, \qquad C = \sqrt{3/2} \approx 1{,}22, $$
где \(p\) — доля потерянных пакетов. Размер сегмента выводится из MTU: 1500 − 20 байт заголовка IP − 20 байт заголовка TCP = 1460 байт, а с метками времени в каждом сегменте — 1448. Оценим скорость для линии с RTT 50 мс и потерями 1 %: при MSS 1448 байт получим 1448 / 0,05 · 1,22 / 0,1 ≈ 353 КБ/с, независимо от полосы канала. Формула является оценкой для Reno и подобных ему алгоритмов, а не пределом скорости любого TCP: опыт ниже показывает BBR при тех же потерях в 25 раз быстрее. Поэтому файл, копируемый между корпусами института медленнее, чем позволяет канал, нередко упирается не в «плохую сеть», а в реакцию алгоритма на редкие потери при большой задержке. Другая частая причина — окно получателя и буферы сокетов меньше произведения полосы на задержку: при 100 Мбит/с и RTT 30 мс в пути должно находиться около 375 КБ данных. Окно перегрузки, RTT и алгоритм каждого соединения показывает команда ss -ti.
Опыт: CUBIC и BBR на линии с потерями
Влияние алгоритма на скорость покажем на учебной линии с задержкой и потерями. Скрипт netem.sh строит цепочку из трёх пространств имён: клиент a в сети 192.0.2.0/24, сервер b в сети 198.51.100.0/24 и маршрутизатор r между ними. Дисциплина очереди netem на интерфейсе r, обращённом к клиенту, задерживает на 50 мс каждый пакет, уходящий к клиенту, и случайно теряет 1 % таких пакетов; в обратную сторону пакеты идут без задержки. Укрупнение пакетов TSO, GSO и GRO на виртуальных интерфейсах выключено, чтобы потеря касалась одного пакета, а не пачки. В пространстве b встроенный сервер раздаёт файл случайных данных размером 8 МиБ.
# Клиент a — маршрутизатор r — сервер b; на r задержка 50 мс и потери 1 %
for ns in a r b; do ip netns add $ns; done
ip link add va netns a type veth peer name ra netns r
ip link add vb netns b type veth peer name rb netns r
ip -n a addr add 192.0.2.10/24 dev va
ip -n r addr add 192.0.2.1/24 dev ra
ip -n r addr add 198.51.100.1/24 dev rb
ip -n b addr add 198.51.100.10/24 dev vb
for d in "a va" "r ra" "r rb" "b vb"; do ip -n ${d% *} link set ${d#* } up
ip netns exec ${d% *} ethtool -K ${d#* } tso off gso off gro off; done
ip netns exec r sysctl -qw net.ipv4.ip_forward=1
ip -n a route add default via 192.0.2.1
ip -n b route add default via 198.51.100.1
tc -n r qdisc add dev ra root netem delay 50ms loss 1%
mkdir -p www && head -c 8M /dev/urandom > www/data.bin
ip netns exec b python3 -m http.server 8000 -d www >/dev/null 2>&1 &
Скрипт get.sh скачивает файл из пространства a и печатает время:
curl -s -o /dev/null -w '%{time_total} с\n' http://198.51.100.10:8000/data.bin
$ sh netem.sh
$ ip netns exec a ping -c 4 -q 198.51.100.10
PING 198.51.100.10 (198.51.100.10) 56(84) bytes of data.
--- 198.51.100.10 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3009ms
rtt min/avg/max/mdev = 50.148/63.712/101.371/21.750 ms
$ ip netns exec b sysctl net.ipv4.tcp_congestion_control
net.ipv4.tcp_congestion_control = cubic
$ for i in 1 2 3; do ip netns exec a sh get.sh; done
23.651585 с
22.971182 с
25.421624 с
$ ip netns exec b sysctl -w net.ipv4.tcp_congestion_control=bbr
net.ipv4.tcp_congestion_control = bbr
$ for i in 1 2 3; do ip netns exec a sh get.sh; done
0.938207 с
0.904590 с
0.986031 с
Время туда и обратно составило 50 мс; первый эхо-запрос ждал ещё и ответа ARP от r, который netem тоже задержал, поэтому максимум равен 101 мс, а среднее — 64 мс. Алгоритм задаётся у отправителя данных, то есть у сервера в b, параметром net.ipv4.tcp_congestion_control его пространства имён, а новое соединение получает алгоритм, действующий в момент установления. При CUBIC, выбранном в Linux по умолчанию, файл скачивается за 23,0–25,4 с со скоростью 0,33–0,37 МБ/с, в среднем 0,35 МБ/с, — почти точно столько, сколько даёт оценка Матиса, 353 КБ/с: окно уменьшается при каждой потере. BBR при тех же потерях скачивает файл за 0,90–0,99 с со скоростью 8,5–9,3 МБ/с, в среднем в 25 раз быстрее, поскольку окно из-за каждой потери не уменьшает. Таким образом, на линии со случайными потерями скорость определяет не полоса канала, а алгоритм контроля перегрузок. Дисциплину netem и алгоритм BBR реализуют модули ядра sch_netem и tcp_bbr, которые загружаются при первом использовании.
Наблюдение за соединениями
Состояние соединений и сами пакеты показывают следующие команды.
ss -tn # установленные и закрывающиеся соединения TCP
sudo ss -tlnp # слушающие порты и процессы
ss -ti # окно перегрузки, RTT и алгоритм соединений
sudo tcpdump -ni any 'tcp port 443' # все интерфейсы, без разрешения имён
sudo tcpdump -ni eth0 host 192.0.2.20 # обмен с одним узлом
sudo tcpdump -i lo -w dump.pcap 'port 8000' # запись в файл для разбора позже
tcpdump -r dump.pcap -nn -A # чтение файла с выводом содержимого
Ключ -n утилиты tcpdump отключает разрешение адресов в имена, чтобы перехват сам не порождал запросов DNS, а -nn — ещё и номеров портов. Псевдоинтерфейс any слушает все интерфейсы сразу, но без неразборчивого режима. Фильтр на языке BPF отбирает нужное: host, port, tcp, udp и их сочетания через and, or и not. Файл .pcap открывает Wireshark, который позволяет построить графики окна TCP, найти повторные передачи и измерить задержки. Для HTTPS перехват показывает только записи TLS, а содержимое запросов видно лишь в открытом HTTP. Для перехвата необходимы права root, а выполняется он только в своей сети или с разрешения её владельца: пакеты содержат чужие данные.
Резюме
Таким образом, запоминать необходимо не столько детали протоколов, сколько идею слоёв: каждый уровень пользуется услугами нижнего и ничего не знает о его устройстве. Поэтому один и тот же код на requests одинаково работает и по Wi-Fi, и по оптике. Маршрут пакета определяет самый длинный подходящий префикс в таблице, выбранной правилами, а выбор ядра показывает ip route get. TCP обеспечивает надёжную доставку ценой рукопожатия и контроля перегрузок, и на линии со случайными потерями скорость определяет алгоритм контроля перегрузок: BBR сохраняет её там, где CUBIC её теряет.
Когда что-либо не работает, уровни проверяются в порядке цены проверки, а не строго снизу вверх: сначала самое дешёвое и самое частое. Разрешение имени помещено в таблице на второе место по этой причине: оно проверяется одной командой, а нарушается чаще прочего, хотя уровень у него прикладной; DNS рассматривается в главе «Веб-технологии».
| Что проверяем | Уровень | Чем смотреть |
|---|---|---|
| Физическое подключение интерфейса | физический, канальный | ip link, ethtool |
| Разрешение имени | прикладной (DNS) | dig, nslookup |
| Маршрут и прохождение пакетов | сетевой | ip route get, ping, tracepath, mtr |
| Открытый порт и слушающий его процесс | транспортный | ss -tlnp; отказ или тайм-аут соединения |
| Содержимое ответа сервера | прикладной | curl -v; tcpdump — для открытого HTTP и для проверки прохождения пакетов |
Бессмысленно отлаживать код запроса, если имя сервера не разрешается, и проверять DNS, если не поднят сетевой интерфейс.
Упражнения
- Дополнить таблицу задачи о выборе маршрута строкой
192.0.2.64/26 via 192.0.2.33, предсказать маршруты адресов 192.0.2.48, 192.0.2.70 и 192.0.2.100 и проверить предсказание командойip route getв пространстве имён. - Повторить опыт с CUBIC и BBR при задержке 10 мс и потерях 0,1 %, оценить скорость CUBIC по формуле Матиса и сравнить оценку с измеренной.
- Перехватить
tcpdumpрукопожатие запроса HTTPS и найти в нём сообщение ClientHello с именем сервера; устройство TLS рассматривается в главе «Веб-технологии».
Полезные ссылки
- J. Kurose, K. Ross. Computer Networking: A Top-Down Approach, 9-е изд. — Pearson, 2026, учебник по сетям, построенный сверху вниз, от приложений к каналу;
- RFC 9293 (TCP), RFC 5681 (контроль перегрузок TCP) и RFC 9438 (CUBIC), первоисточники по транспортному уровню;
- M. Mathis, J. Semke, J. Mahdavi, T. Ott. The Macroscopic Behavior of the TCP Congestion Avoidance Algorithm // ACM SIGCOMM Computer Communication Review, 1997, т. 27, № 3 — статья с оценкой скорости TCP при потерях;
- N. Cardwell, Y. Cheng, C. S. Gunn, S. H. Yeganeh, V. Jacobson. BBR: Congestion-Based Congestion Control // ACM Queue, 2016, т. 14, № 5 — описание алгоритма BBR;
- страницы руководства ip-route(8), ip-netns(8), tc-netem(8) и tcpdump(1), справочник по командам главы.
Поверх адресов, маршрутов и соединений TCP работают прикладные протоколы — DNS, HTTP и TLS; им и работе с сетью из Python посвящена следующая глава, «Веб-технологии».