Общая теория
В предыдущих главах были описаны уровни стека протоколов, обеспечивающие связность самой сети, доступность абонентов в ней и т.д. Транспортный уровень, которому посвящена данная глава, направлен непосредственно на контроль и управление передачей данных.
В рамках транспортного уровня Bottom-half целью является обеспечение цельности передаваемых данных и надёжности передачи. При работе с данными необходимо контролировать, что все отправленные данные достигли получателя. В случае множества пакетов необходимо также следить за их порядком. Для обеспечения надёжности передачи производится управление контролем передачи. Получатель и отправитель организовывают двустороннее соединение, проверяют доступность обеих сторон. При передаче данных в качестве обратной связи передаются данные о состоянии передаваемых пакетов (подтверждение доставки, ошибки, …), а также о состоянии канала для корректировки скорости и / или интенсивности передачи.
Однако, из всего многообразия требований этой цели есть важное исключение: в случае работы не со множеством связанных пакетов, а с одним пакетом (полностью описывающим всю передаваемую информацию) весь жёсткий контроль и управление передачей практически пропадают. Для одного пакета сама его доставка уже гарантирует целостность передачи. Управления контролем передачи также нет: отдельного соединения получателя и отправителя не требуется, относительно передачи сообщения и состоянии канала (а эти данные нужны для корректировки последующей передачи) вопрос даже не поднимается по причине отсутствия последующей передачи как таковой.
В связи с этим весь пласт передаваемых данных разбивается на две группы:
- Поток данных — логически связанное множество пакетов, для которых важны полная (вообще говоря, для некоторых прикладных приложений допустимы потери, но общей логики этого низлежащего уровня это не меняет) и последовательная передача;
- Датаграмма — отдельно существующий и полностью описывающий передаваемые данные единичный пакет.
Top-half целью уровня стоит различие потоков и управление ими. В рамках этой задачи необходимо идентифицировать не просто абонентов в сети, а также раздельные потоки (или датаграммы) с одного устройства, поскольку они могут быть логически связаны с разными прикладными протоколами верхнего уровня. Для этого кроме привычных идентификаторов - адреса отправителя и адреса получателя - к соответствующих передаваемых объектам (потокам или датаграммам) добавляется ещё два: порт отправителя и порт получателя. Это позволяет отслеживать параллельно транслирующиеся в сеть данные от разных приложений с одного устройства.
При работе с транспортными протоколами заметна их ассиметричная природа, в связи с чем возникают понятия “клиента” и “сервера”. В случае работы с потоком данных передача, как уже было отмечено, вообще говоря, двунаправленная. Тем не менее, для обозначения взаимодействующих абонентов принято “клиентом” называть инициирующую передачу данных сторону, а “сервером” - принимающую эти данные сторону.
В главе подробно будут рассмотрены два основных протокола передачи данных транспортного уровня: TCP для потока данных и UDP для датаграмм. Основные различия этих протоколов в описанных выше требованиях указаны в таблице:
| TCP | vs. | UDP |
|---|---|---|
| Да | Подключение | Нет |
| Да | Подтверждения | Нет |
| Да | Отслеживание цельности потока | Нет |
| Да | Отслеживание качества потока | Нет |
| Несколько | Количество пакетов | Один |
TCP
Transmission Control Protocol описывает правила работы с потоками данных. Его основными задачами являются:
- Установление и поддержка (и завершение) двунаправленного соединения;
- Подтверждение доставки каждого пакета;
- Уведомление об ошибках передачи, а также повторная передача при отсутствии уведомлений;
- Контроль перегрузок сети (за счёт технологии
Sliding Window); - Поддержка целостности потока;
Для идентификации соединения используется выше описанный набор из двух адресов и двух портов взаимодействующих устройств. Некоторые порты явно закреплены за существующими сервисами, для Linux их описание есть в файле /etc/services
~/papillon_rouge: cat /etc/services | grep -E ' ((80/)|(443/))'
http 80/tcp
http 80/udp
www 80/tcp
www 80/udp
www-http 80/tcp
www-http 80/udp
http 80/sctp
https 443/tcp
https 443/udp
https 443/sctp
Sequential number
При работе с потоком данных необходимо, во-первых, уметь в случае переупорядочивания передаваемых пакетов восстановить их последовательность, а, во-вторых, при потере или удвоении пакетов определять это (т.е. поддерживать целостность данных). Для этих задач в каждом пакете передаётся специальное число Sequential Number(SEQN), изначально случайно генерирующееся у клиента, и после увеличивающееся в каждом пакете на объём уже переданных данных. Таким образом, несоответствие объёму полученных данных и текущему SEQN (логично, за вычетом начального значения) определяет потерю данных при передаче, а нарушение последовательности SEQN (очевидно, возрастающей) говорит о необходимости переупорядочивания пакетов.
Подключение
Для управления соединением в пакетах передаются служебные данные - флаги. При установлении двунаправленного соединения используется технология трёх рукопожатий (3-way handshake) (Сокращения далее: флаг SYN — запрос на подключение + передача начального SEQN, флаг FIN — запрос на отключение, флаг ACK — подтверждение получения пакета; PAYLOAD - информационная нагрузка пакета):
- Client → ($\text{SYN}_1$) → Server
- Client ← ($\text{ACK}_1+\text{SYN}_2$) ← Server
- Client → ($\text{ACK}_2+\text{PAYLOAD}$) → Server
- …
- Кто-то первый → ($\text{FIN}_1$) → Кто-то второй
- Кто-то первый ← ($\text{ACK}_1+\text{FIN}_2$) ← Кто-то второй
- Кто-то первый → ($\text{ACK}_2$) → Кто-то второй
Сейчас данная простая структура обросла множеством технологий, оптимизирующих передачу. Однако её основа прослеживается везде.
Обеспечение надёжности соединения. Sliding window protocol
В рамках работы с TCP обязательным является подтверждение каждого передаваемого пакета. Следовательно, без оптимизации этого процесса передача данных увеличивается, минимум, вдвое (без учёта потери пакетов, переотправок и т.д.). Для ускорения процесса передачи используется технология “скользящего окна”, позволяющая передавать какое-то количество пакетов без подтверждения. Подробнее разберём данный протокол.
Окном называется последовательная группа пакетов из потока, которую можно передавать или получать без подтверждения каждого. Вся группа отправляется получателю, тот отвечает на каждый пакет своим ack-пакетом. При получении очередного ack-пакета окно «сдвигается» на следующий ещё не полученный. Если пакеты идут с разной скоростью или пропадают, в окне получателя образуется «недопринятая» область:
- Непрерывное начало потока уже получено целиком
- Окно приёма из N пакетов
- Первый пакет в окне ещё не получен (сразу после получения начало окна сдвигается)
- Далее — какие-то пакеты в окне получены (вне очереди), какие-то — нет
- Остальные пакеты потока пока только предполагаются
При отправлении TCP дожидается, пока каждому отправленному пакету придёт подтверждение (годится и подтверждение о приёме одного из следующих пакетов, поскольку согласно протоколу это гарантирует подтверждение всех предыдущих пакетов). Если некоторые подтверждения по тем или иным причинам не пришли, образуется «недопереданная» область.
- Непрерывное начало потока уже передано.
- Окно передачи из N пакетов
- Первый пакет в окне отправлен, но ещё не подтверждён (как только подтверждение пришло, начало окна сдвигается)
- Далее — какие-то пакеты в окне уже отправлены, какие-то — ещё нет, но подтверждённых среди них нет (подтверждение пакета M автоматически означает, что пакеты M-k тоже уже получены)
- Остальные пакеты потока пока только предполагаются
На логике работы окна построены алгоритмы действий для получателя и отправителя. Для более подробного изучения протокола, а также самостоятельного тестирования его работы рекомендуется прочитать статью на Хабре, попробовать исследовать тестовое приложение, изучить видеоматериал по работе алгоритмов.
Фиксированное окно
Классический алгоритм предполагает, что размеры окон получателя и отправителя фиксированы. Будем называть «очередными» пакеты, принадлежащие уже полученному потоку, а «внеочередными» — пакеты, принадлежащие недопринятой области окна, т. е. полученные раньше, чем какой-то предыдущий пакет потока.
Отправитель:
- Передаёт все непереданные пакеты в окне
- Параллельно ждёт получения подтверждения каждому из пакетов
- Подтверждение пакета N означает также подтверждение всех пакетов N-k
- При получении подтверждения пакета N начало окна перемещается на N+1 (появляются ещё непереданные пакеты)
- Для каждого ещё неподтверждённого пакета отслеживается тайм-аут. По истечении тайм-аута пакет посылается повторно.
- При получении диагностики «перешли пакет N» пакет посылается повторно
Получатель:
- Ждет получения пакетов в окне
- При получении пакета:
- Если в окне перед полученным пакетом есть непринятый — получен внеочередной пакет:
- Посылается (повторно) подтверждение о получении последнего очередного пакета
- …то есть если последний последовательно принятый пакет — N, а получен внеочередной пакет N+2, посылается подтверждение именно пакета N
- Если получен первый пакет в окне (он же — первый непринятый), это очередной пакет:
- Высчитываются новые границы окна: окно начинается с первого непринятого пакета, то есть сдвигается вперёд на количество принятых. Их может быть более одного: если, скажем, пакеты N+1 и N+2 получены вне очереди, а затем только получен пакет N, и сразу три пакета переходят в категорию принятых.
- Изменяется указатель последнего очередного пакета (в примере выше — на N+2)
- Посылается подтверждение о получении последнего очередного пакета (в примере выше — сразу N+2-го)
- Повторно полученные пакеты игнорируются.
- Если с пакетом что-то не так (не совпадает контрольная сумма и т. п.), отсылается просьба повторной пересылки
Этот алгоритм содержит несколько избыточное количество подтверждений, однако это результат очень простой логики «пакет → подтверждение/ошибка», которая позволяет терять (некоторые) подтверждения без влияния на поток.
Масштабируемое окно
В действительности, окно может быть не только фиксированного размера, но и масштабироваться в зависимости от нагрузки сети, способностей абонентов обрабатывать поток и т.д. Очевидно, что чем больше окно, тем больше потенциальная пропускная способность, но если какое-то устройство на пути передачи данных не готово или не способно передавать большой объём трафика, оно будет всегда терять пакеты из большого окна, что приведёт в посылке ещё большего количества пакетов (ошибки и повторная передача) и будет только усугублять ситуацию. Поэтому размер окна может изменяться в зависимости от «качества» канала передачи данных.
На отправителе (т. н. Congestion window)
- Договаривается с получателем о максимальном и минимальном размерах окна
- Начинает с минимального размера
- При каждом сдвиге окна:
- Если все пакеты в области сдвига были отосланы с первого раза (без повторов по тайм-ауту или ошибке), окно не только сдвигается, но и увеличивается (например, в два раза, но не больше чем максимальный размер)
- Если со времени последнего сдвига окна были повторные передачи пакетов, или получен анонс уменьшения размера окна, окно уменьшается до минимального
- Если получен анонс нулевого размера окна, передача дополнительно приостанавливается на некоторый тайм-аут или до получения другого анонса (что случится раньше)
На получателе (т. н. Receive window)
- Если получатель не справляется с обработкой, он анонсирует меньший максимальный размер окна
- Если получатель совсем не справляется с обработкой, он анонсирует нулевой размер окна
Во встречном потоке данных действуют те же правила, а абоненты меняются ролями.
UDP
User Datagram Protocol описывает правила работы с датаграммами. Поскольку передача в данном виде соединения ведётся в виде единичного пакета небольшого размера, не требуется установки соединения, а также отсутствует подтверждение о получении пакета. Если же оно необходимо для отправителя, то контроль его получения ложится на прикладной уровень протоколов. Такой способ организации соединения упрощает взаимодействие запрос-ответных сервисов, порождая не 7-9 пакетов в обе стороны, а ровно по одному от клиента и от сервера.
На UDP работает большинство прикладных протоколов, работающих на запросах, например, DNS, DHCP, NTP.
Сетевые подключения
TCP-соединение предоставляет поток данных, так что все сетевые приложения написаны примерно одинаково:
«Клиентские»
- Устанавливают подключение по определённому порту+адресу сервера
- Вторая пара, порт+адрес клиента, генерируется автоматически, причём порт выбирается случайный, но большой, > 32767
- Обменивается с сервером данными
- Либо сама закрывает соединение, либо это делает сервер
«Серверные»
- Регистрируются в системе как процессы, принимающие подключение на определённом порту (и, возможно, адресе)
- Ждут подключения
- После того, как подключение произошло, начинают обмениваться данными с клиентом
- При этом обработчик соединения может быть отдельным процессом / тредом / корутиной, а основной поток управления в это время уже ждёт следующего подключения
- Закрывает соединение, если клиент его не закрыл сам
С точки зрения ОС двунаправленный поток данных (как сложный, типа TCP-соединения, так и более простой, например, между двум процессами или вырожденный, типа датаграмм UDP) — это сокет. Универсальные инструменты по работе с сокетами умеют в оба сценария — и клиентский и серверный, они просто перенаправляют поток данных откуда-то куда-то, наподобие утилиты cat. Содержимое потока (прикладной уровень) их не волнует, зато, как видно из описания выше, даже в самых простых случаях есть множество вариантов (например, по UDP доставлять только одну датаграмму или все, и что такое «все»).
Для работы с сетевым подключением через TCP и UDP можно воспользоваться утилитами netcatи socat.
netcat
netcat - утилита для работы с текстовыми данными в сети. netcat может работать как в роли клиента, отправляя запросы, так и в роли сервера, получая некоторую информацию.
Разберём работу netcat на примере:
athena:
eth1: intnet
boney:
eth1: intnet
TCP-соединение с помощью netcat
На локальных машинах 22й порт закреплён за сервисом SSH
athena
[root@athena ~]# ss -lntp
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 128 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=637,fd=3))
LISTEN 0 128 [::]:22 [::]:* users:(("sshd",pid=637,fd=4))
[root@athena ~]#
Поднимем интерфейсы на машинах для получения LLA и обратимся с boney на athena:
athena
[root@athena ~]# ip link set eth1 up
[root@athena ~]# ip a show eth1
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 08:00:27:15:1e:1c brd ff:ff:ff:ff:ff:ff
altname enp0s8
altname enx080027151e1c
inet6 fe80::a00:27ff:fe15:1e1c/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
[root@athena ~]#
boney
[root@boney ~]# ip a show eth1
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 08:00:27:0a:32:40 brd ff:ff:ff:ff:ff:ff
altname enp0s8
altname enx0800270a3240
inet6 fe80::a00:27ff:fe0a:3240/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
[root@boney ~]#
athena
[root@athena ~]# tcpdump -vvi eth1 tcp
tcpdump: listening on eth1, link-type EN10MB (Ethernet), snapshot length 262144 bytes
boney
[root@boney ~]# netcat fe80::a00:27ff:fe15:1e1c%eth1 22
SSH-2.0-OpenSSH_9.6
exit
Invalid SSH identification string.
[root@boney ~]#
athena
[root@athena ~]# tcpdump -vvi eth1 tcp
tcpdump: listening on eth1, link-type EN10MB (Ethernet), snapshot length 262144 bytes
# Установка соединения
11:45:47.086847 IP6 (flowlabel 0x202b7, hlim 64, next-header TCP (6) payload length: 40) fe80::a00:27ff:fe0a:3240.33014 > athena.ssh:
Flags [S], cksum 0x90e4 (correct), seq 175653024, win 64800, options [mss 1440,sackOK,TS val 849087953 ecr 0,nop,wscale 7], length 0
11:45:47.086926 IP6 (flowlabel 0x9c15b, hlim 64, next-header TCP (6) payload length: 40) athena.ssh > fe80::a00:27ff:fe0a:3240.33014:
Flags [S.], cksum 0xadab (incorrect -> 0xb7db), seq 2571304909, ack 175653025, win 64260, options [mss 1440,sackOK,TS val 1994640159
ecr 849087953,nop,wscale 7], length 0
11:45:47.087198 IP6 (flowlabel 0x202b7, hlim 64, next-header TCP (6) payload length: 32) fe80::a00:27ff:fe0a:3240.33014 > athena.ssh:
Flags [.], cksum 0xdf9c (correct), seq 1, ack 1, win 507, options [nop,nop,TS val 849087954 ecr 1994640159], length 0
# Передача сообщения-запроса и получение ответа
11:45:47.101576 IP6 (flowlabel 0x9c15b, hlim 64, next-header TCP (6) payload length: 53) athena.ssh > fe80::a00:27ff:fe0a:3240.33014:
Flags [P.], cksum 0xadb8 (incorrect -> 0x17cc), seq 1:22, ack 1, win 503, options [nop,nop,TS val 1994640174 ecr 849087954], length
21: SSH: SSH-2.0-OpenSSH_9.6
11:45:47.101766 IP6 (flowlabel 0x202b7, hlim 64, next-header TCP (6) payload length: 32) fe80::a00:27ff:fe0a:3240.33014 > athena.ssh:
Flags [.], cksum 0xdf6a (correct), seq 1, ack 22, win 507, options [nop,nop,TS val 849087968 ecr 1994640174], length 0
11:45:51.550441 IP6 (flowlabel 0x202b7, hlim 64, next-header TCP (6) payload length: 37) fe80::a00:27ff:fe0a:3240.33014 > athena.ssh:
Flags [P.], cksum 0xf50f (correct), seq 1:6, ack 22, win 507, options [nop,nop,TS val 849092417 ecr 1994640174], length 5
11:45:51.550441 IP6 (flowlabel 0x9c15b, hlim 64, next-header TCP (6) payload length: 32) athena.ssh > fe80::a00:27ff:fe0a:3240.33014:
Flags [.], cksum 0xada3 (incorrect -> 0xbca7), seq 22, ack 6, win 503, options [nop,nop,TS val 1994644623 ecr 849092417], length 0
11:45:51.550441 IP6 (flowlabel 0x9c15b, hlim 64, next-header TCP (6) payload length: 66) athena.ssh > fe80::a00:27ff:fe0a:3240.33014:
Flags [P.], cksum 0xadc5 (incorrect -> 0x3a4f), seq 22:56, ack 6, win 503, options [nop,nop,TS val 1994644623 ecr 849092417], length
34
11:45:51.551930 IP6 (flowlabel 0x202b7, hlim 64, next-header TCP (6) payload length: 32) fe80::a00:27ff:fe0a:3240.33014 > athena.ssh:
Flags [.], cksum 0xbc7f (correct), seq 6, ack 56, win 507, options [nop,nop,TS val 849092419 ecr 1994644623], length 0
11:45:51.551938 IP6 (flowlabel 0x9c15b, hlim 64, next-header TCP (6) payload length: 34) athena.ssh > fe80::a00:27ff:fe0a:3240.33014:
Flags [P.], cksum 0xada5 (incorrect -> 0xaf6e), seq 56:58, ack 6, win 503, options [nop,nop,TS val 1994644624 ecr 849092419], length
2
11:45:51.552116 IP6 (flowlabel 0x202b7, hlim 64, next-header TCP (6) payload length: 32) fe80::a00:27ff:fe0a:3240.33014 > athena.ssh:
Flags [.], cksum 0xbc7c (correct), seq 6, ack 58, win 507, options [nop,nop,TS val 849092419 ecr 1994644624], length 0
# Завершение соединения
11:45:51.552265 IP6 (flowlabel 0x9c15b, hlim 64, next-header TCP (6) payload length: 32) athena.ssh > fe80::a00:27ff:fe0a:3240.33014:
Flags [F.], cksum 0xada3 (incorrect -> 0xbc7e), seq 58, ack 6, win 503, options [nop,nop,TS val 1994644625 ecr 849092419], length 0
11:45:51.552374 IP6 (flowlabel 0x202b7, hlim 64, next-header TCP (6) payload length: 32) fe80::a00:27ff:fe0a:3240.33014 > athena.ssh:
Flags [F.], cksum 0xbc79 (correct), seq 6, ack 59, win 507, options [nop,nop,TS val 849092419 ecr 1994644625], length 0
11:45:51.552381 IP6 (flowlabel 0x9c15b, hlim 64, next-header TCP (6) payload length: 32) athena.ssh > fe80::a00:27ff:fe0a:3240.33014:
Flags [.], cksum 0xada3 (incorrect -> 0xbc7d), seq 59, ack 7, win 503, options [nop,nop,TS val 1994644625 ecr 849092419], length 0
14 packets captured
14 packets received by filter
0 packets dropped by kernel
[root@athena ~]#
Теперь попробуем воспользоваться netcat, как сервером: запустим прослушивание порта 1234 (не забыв добавить флаг -6, поскольку по умолчанию будет создаваться IPv4-only сокет):
athena
[root@athena ~]# netcat -6l 1234 &
[1] 779
[root@athena ~]#
При этом в socket stat (ss) будет отображаться информация о новом слушающем соединении
athena
[root@athena ~]# ss -nltp
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 128 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=637,fd=3))
LISTEN 0 1 *:1234 *:* users:(("netcat",pid=779,fd=3))
LISTEN 0 128 [::]:22 [::]:* users:(("sshd",pid=637,fd=4))
[root@athena ~]#
Попробуем подключиться к порту и что-то в него написать:
athena
[root@athena ~]# fg
netcat -6l 1234
boney
[root@boney ~]# netcat fe80::a00:27ff:fe15:1e1c%eth1 1234
hello
athena
[root@athena ~]# fg
netcat -6l 1234
hello
Напишем ответ:
athena
[root@athena ~]# fg
netcat -6l 1234
hello
Bye ye
boney
[root@boney ~]# netcat fe80::a00:27ff:fe15:1e1c%eth1 1234
hello
Bye ye
[root@boney ~]#
Для netcat можно сделать перенаправление вывода данных:
athena
[root@athena ~]# netcat -6l 1234 > file
boney
[root@boney ~]# cal | netcat fe80::a00:27ff:fe15:1e1c%eth1 1234
[root@boney ~]#
athena
[root@athena ~]# netcat -6l 1234 > file
[root@athena ~]#
[root@athena ~]# cat file
July 2026
Su Mo Tu We Th Fr Sa
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
[root@athena ~]#
UDP-подключение
При работе с UDP-соединением не создаётся отдельное соединение. Вследствие этого задача определения обратных адресов ложится на прикладные протоколы. Как правило, в качестве идентификатора для ответного сообщения указываются адрес отправителя и тот же самый порт, с которого был сделан запрос.
Протестируем работу с UDP-протоколом на примере DNS-запроса:
TODO: Ввести сюда сервер интернета
`srv#tmux_win2`
```console
[root@srv ~]# tcpdump -nvi eth0 udp
tcpdump: listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
```
`srv#tmux_win1`
```console
[root@srv ~]# dig ww.ru
; <<>> DiG 9.18.34 <<>> ww.ru
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 16655
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;ww.ru. IN A
;; ANSWER SECTION:
ww.ru. 30 IN A 104.21.56.42
ww.ru. 30 IN A 172.67.177.66
;; Query time: 114 msec
;; SERVER: 10.0.2.3#53(10.0.2.3) (UDP)
;; WHEN: Sun Mar 30 21:00:50 UTC 2025
;; MSG SIZE rcvd: 66
[root@srv ~]#
```
`srv#tmux_win2`
```console
[root@srv ~]# tcpdump -nnvi eth0 udp
tcpdump: listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
20:53:59.325961 IP (tos 0x0, ttl 64, id 58418, offset 0, flags [none], proto UDP (17), length 74)
10.0.2.15.44825 > 10.0.2.3.53: 11283+ [1au] A? ww.ru. (46)
20:53:59.385749 IP (tos 0x0, ttl 64, id 4, offset 0, flags [none], proto UDP (17), length 94)
10.0.2.3.53 > 10.0.2.15.44825: 11283 2/0/1 ww.ru. A 172.67.177.66, ww.ru. A 104.21.56.42 (66)
```
socat
socat - аналогичная netcat утилита, позволяющая организовать сетевое соединение и передачу данных. Достоинство и одновременная сложность этой утилиты - необходимость описания параметров настраиваемого соединения. Это даёт большую гибкость подключения, однако обращение с утилитой затрудняется в сравнении с netcat.
Пример использования socat - подключение к виртуальной машине в сценарии vbconnect:
socat -,cfmakeraw,echo=0,escape=15 TCP6:localhost:порт
Первая часть параметров описывает параметры принимающей (нашей) стороны (control flow raw mode терминала, отключение дублирования вводящихся символов, отключение от соединения через Ctrl+o), вторая - стороны подключения (По TCP, на локальный адрес и соответствующий порт)
Опишем клиент-серверное подключение на socat:
athena
[root@athena ~]# socat TCP6-LISTEN:1234,reuseaddr STDOUT
Поскольку парсер команд socat использует двоеточие в качестве разделителя, IPv6-адрес описывается в квадратных скобках.
boney
[root@boney ~]# socat - TCP6:[fe80::a00:27ff:fe15:1e1c%eth1]:1234
athena
[root@athena ~]# socat TCP6-LISTEN:1234,reuseaddr STDOUT
2026/07/09 12:02:14 socat[805] W address is opened in read-write mode but only supports write-only
Утилита предупреждает что запуск был проведён в режиме write-only (параметр STDOUT), socat Всё равно открывает двусторонний сокет. Поэтому даже с указанием STDOUT мы можем отправлять сообщения в обе стороны:
boney
[root@boney ~]# socat - TCP6:[fe80::a00:27ff:fe15:1e1c%eth1]:1234
qwerty
athena
[root@athena ~]# socat TCP6-LISTEN:1234,reuseaddr STDOUT
2026/07/09 12:02:14 socat[805] W address is opened in read-write mode but only supports write-only
qwerty
athena
[root@athena ~]# socat TCP6-LISTEN:1234,reuseaddr STDOUT
2026/07/09 12:02:14 socat[805] W address is opened in read-write mode but only supports write-only
qwerty
zxcvb
[root@athena ~]#
boney
[root@boney ~]# socat - TCP6:[fe80::a00:27ff:fe15:1e1c%eth1]:1234
qwerty
zxcvb
[root@boney ~]#
Важным параметром является reuseaddr. При закрытии соединения сокет, вообще говоря, не закрывается, а в неиспользуемом состоянии висит в течение тайм-аута. Для того, чтобы пересоздавать уже существующий сокет, используется данный параметр.
Другие транспортные протоколы
TCP и UDP являются самыми старыми, известными и оттого популярными протоколами транспортного уровня. В действительности, данных протоколов хватает, чтобы покрывать все требования транспортного уровня, перенося более сложную логику прикладным приложениям. Но наряду с ними существуют протоколы, решающие задачи балансировки трафика и управления нагрузкой канала прямо на транспортном уровне, решая или хотя бы предупреждая перегрузки в сети.
Одним из первых таких протоколов стал DCCP. Данный протокол подходит для приложений, в которых данные тесно связаны со временем, а потерянные пакеты уже не важны (например, медиавещание, онлайн-игры). В протоколе добавлена интеграция с ECN, приходящие ack-пакеты помечаются предупреждениями о заполнении канала. Основная цель протокола — обеспечить стабильную передачу без старых переотправок (поскольку они неактуальны), не перегружая канал.
Протокол SCTP был одним из первых активно используемых транспортных протоколов, обрабатывающих соединение в несколько параллельных потоков через разные физические соединения (т.н. Multihoming). Транспортный идентификатор теперь не использовал IP, поскольку передача могла вестись по множеству сетевых соединений, вместо этого потоки объединялись по специальному идентификатору «assosiation» и порту. Кроме этого для избежания «SYN flood» атаки в протоколе использовался механизм четырехэтапного рукопожатия.
Непосредственно сам механизм ECN также стал самостоятельным протоколом поддержки TCP-соединений. Он не порождал дополнительного трафика (в отличие от того же SCTP, довольно сильно загружавшего сразу большое количество соединений) и достаточно хорошо прогнозировал перегрузки каналов.
На базе ECN работает ещё один протокол предотвращения перегрузки — L4S. За счёт передачи ECN-данных (дополнительных двух битов в заголовке сетевого уровня; в L4S они обозначают его специализированные сигналы) конечные пользователи могли контролировать скорость передачи данных, что позволяло на участках, поддерживающих L4S, обеспечивать большую пропускную способность. При этом L4S позволяет улучшать пропускной поток при локальном применении без развёртывания на все сегменты сети.
Для абсолютного контроля доступной пропускной способности использовались протоколы резервирования ресурсов на всём маршруте, самым известным из таких протоколов выступает RSVP.
Основной проблемой всех новых протоколов была принципиальная несовместимость их со старыми интерфейсами приложений, настроенными на TCP-соединения. Решением выступил протокол MPTCP. Данный протокол, как и SCTP, строил множество различных физических маршрутов, позволяя оптимизировать передачу данных и избегать перегрузки отдельных каналов. Но его главным преимуществом была полная совместимость с обычными TCP-интерфейсами. В случае неподдержания каким-то из узлов сети данного протокола он мог отбросить нечитаемый предзаголовок MPTCP и обработать передачу обыкновенной TCP-сессии.
Протоколы DCCP и SCTP на текущий момент являются отличными примерами транспортных протоколов, решающих локальные проблемы балансировки потоков. В современной сети требуется поддержка всех подобных решений единым протоколом. При этом создание полностью нового транспортного протокола приведёт к проблемам несовместимости, как в случае STCP. Решением и проблемы совместимости, и проблемы управления потоками сейчас является протокол QUIC. Данный протокол разворачивается поверх UDP, использует его датаграммы для непосредственной передачи данных. Как и в STCP, здесь используется IP-независимый идентификатор ассоциации для отслеживания потоков, поддерживается параллельная передача с независимым контролем и SEQN. Вся передача в QUIC по умолчанию ведётся в зашифрованном виде (подобно TLS версии не ниже 1.3) для защиты данных.
Домашнее задание
-
Суть: Почитать документацию по socat (руководство, есть статьи попроще ☺) и организовать проброс
TCP-соединения иUDP-датаграммы без использования маршрутизации. - Площадка:
Клиент-1 ← сеть1 → Клиент-2 ← Сеть-2 → Клиент-3- Поднять нужные интерфейсы на всех хостах заранее, в отчёт не входит
- Отчёт:
- (на Клиенте-1)
report 6 client1- Запустить
socatв режимеlistenна каком-нибудьTCP-порту с перенаправлением вывода на стандартный вывод
- Запустить
- (на Клиенте-2)
report 6 client2- Запустить
socatв режимеlistenна каком-нибудьTCP-порту и выводом наTCP-порт Клиента-1
- Запустить
- (на Клиенте-3)
report 6 client3- Запустить
calи перенаправить с помощьюsocatвывод наTCP-порт Клиента-2 - В результате этой команды на Клиенте-1 появится вывод
calи всеsocat-ы остановятся
- Запустить
- (снова на Клиенте-1,
reportне останавливаем)- Запустить
socatв режимеUDP6-RECVFROM: (см. документацию) на каком-нибудьUDP-порту с перенаправлением вывода на стандартный вывод
- Запустить
- (снова на Клиенте-2,
reportне останавливаем)- Запустить
socatв режимеUDP6-RECVFROM: на каком-нибудьUDP-порту с выводом наUDP-порт Клиента-1 (режимUDP-SENDTO:)
- Запустить
- (снова на Клиенте-3)
- Запустить
calи перенаправить с помощьюsocat(режимUDP-SENDTO:) вывод наUDP-порт Клиента-2 - В результате этой команды на Клиенте-1 появится вывод
calи всеsocat-ы остановятся
- Запустить
- Остановить все
report-ы
- (на Клиенте-1)
- Три отчёта (названия сохранить, должно быть:
report.06.client1,report.06.client2иreport.06.client3) переслать одним письмом в качестве приложений на uneexlectures@cs.msu.ru- В теме письма должно встречаться слово
LinuxNetwork2027
- В теме письма должно встречаться слово