Что будет, если не использовать TCP или UDP?
- воскресенье, 6 апреля 2025 г. в 00:00:11
Коммутаторы, маршрутизаторы, брандмауэры — все это устройства, на которых держится интернет. Они перекидывают, фильтруют, дублируют и вырезают трафик такими способами, о которых большинство даже не догадывается. Без них вы бы не смогли прочитать этот текст.
Но сеть — это всего лишь один из уровней. Операционная система тоже играет по своим правилам: классификация, очереди, правила фаервола, NAT — все это влияет на то, что проходит, а что отбрасывается без следа. Каждый слой работает по-своему, и вместе они формируют ответ на вопрос: «А этот пакет вообще можно пропустить?»
Однажды мне стало интересно: а что будет, если отправить пакет с несуществующим транспортным протоколом? Не TCP, не UDP, не ICMP — вообще что-то выдуманное. Пропустит ли его ОС? Дойдет ли он хотя бы до сетевого интерфейса? Не зарежет ли его какой-нибудь промежуточный маршрутизатор? А вдруг он еще и быстрее обычного дойдет, потому что никто не знает, что с ним делать?
Ответа у меня не было. Так что я решил проверить.
Сначала — самый простой эксперимент: отправить такой пакет самому себе. Посмотреть, как мой собственный компьютер справится с этим ядом, который я приготовил. Потом — попробовать переслать его через океан на удаленную Linux-машину, чтобы проверить, смогут ли они действительно добраться туда.
Эту часть можно пропустить, если вы знаете, как устроен интернет. Остальным — добро пожаловать.
Что вообще такое «транспортный протокол»? Почему все говорят про TCP и UDP, но никто не говорит о протоколе номер 42?
Интернет — не магия, хоть иногда и выглядит как волшебство. На самом деле это аккуратная стопка протоколов. Каждый — со своей задачей, своей областью ответственности. Они передают данные друг другу, слой за слоем, пока байты не окажутся там, где надо.
Сверху — приложения. Браузеры, мессенджеры, игры. Они говорят: «дай страницу», «отправь сообщение», «подключи меня к серверу». Все это превращается в запрос. И этот запрос начинает обрастать метаданными: адресами, портами, заголовками. Слой за слоем. Пока не останется просто поток битов, уходящий в никуда.
IP говорит: «Этот пакет — туда». Он отвечает за доставку. Канальный уровень — это физическая передача: Wi-Fi, Ethernet, оптика. Тут мы останавливаться не будем, потому что интересное начинается дальше.
И вот он, транспортный уровень. Первый по-настоящему сложный уровень протокола. Тут уже не просто «отправь куда-то». Тут — «передай надежно», «раздели между приложениями», «проверь, все ли дошло». Здесь живут TCP и UDP.
В заголовке каждого IP-пакета есть поле Protocol. Если там стоит 6 — это TCP. Если 17 — это UDP. А еще есть десятки других номеров. Некоторые — задокументированы. Некоторые — зарезервированы на будущее. А некоторые — просто ничьи.
Что будет, если взять и отправить пакет с одним из этих «ничейных» номеров?
В этом эксперименте слишком много переменных: моя ОС, мой роутер, гипотетическая ОС получателя и целая пачка промежуточных звеньев в интернете. Пытаться разгрести такую кашу — не самая благодарная задача. Поэтому я решил начать с самого простого: отправить пакеты самому себе.
Такой подход исключает все лишнее. Результаты будут зависеть только от поведения моей операционной системы и сетевого стека.
Я написал свой транспортный протокол. Назвал его HDP. Что он делает — неважно. Главное — он не похож ни на один из известных. Это что-то совсем чуждое, ни одна ОС такого не ждет.
Потом я написал сервер — он же слушатель. Он просто ждет пакеты с определенным номером протокола. А еще — клиент, который эти пакеты отправляет. Все просто.
План действий:
Запустить HDP-сервер
→ Он попросит ОС перенаправлять все IP-пакеты с номером протокола 255 на свой сокет.
Запустить HDP-клиент
→ Он отправит пакет на 127.0.0.1 — локальный адрес, он же loopback.
→ ОС направит пакет на интерфейс обратной петли.
→ Интерфейс решит: «Ага, это для нас» — и вернет пакет обратно в систему.
ОС доставит этот пакет на серверный сокет — без изменений… по крайней мере, я на это надеюсь.
Давайте попробуем. Открываю два терминала. В первом — сервер:
$ sudo cargo run --bin server
Во втором — клиент:
$ fortune | cowsay | sudo cargo run --bin client 127.0.0.1
3... 2... 1... Сервер получил сообщение!
$ sudo cargo run --bin server
~~~ IP Header ~~~
Version: 4
IHL: 5
DSCP: 0
ECN: 0
Total Length: 58625
Identification: 36455
Flags: 0
Fragment Offset: 0
TTL: 64
Protocol: 255
Header Checksum: 0
Source IP: [127, 0, 0, 1]
Destination IP: [127, 0, 0, 1]
~~~ HDP Header & Data ~~~
Source Port: 420
Destination Port: 420
Timestamp: 1739640243546134000
Data: _________________________________________
/ Marriage is not merely sharing the \
| fettucine, but sharing the burden of |
| finding the fettucine restaurant in the |
| first place. |
| |
\ -- Calvin Trillin /
-----------------------------------------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||
Победа! Пакет с номером 255 не только не был отброшен, но и вернулся обратно. ОС спокойно его приняла и передала серверу. Я ожидал подвоха. А его не было. Но эксперимент на этом не закончился. Мне стало интересно: а если отправить пакет не с 255, а с чем-то более... необычным?
Например:
6 — это TCP;
2 — это ICMP (все, что связано с ping);
256 — вообще за пределами допустимого диапазона.
Что сделает ОС? Примет? Отбросит? Или просто зависнет? Давайте узнаем:
fortune | cowsay | sudo cargo run --bin client 127.0.0.1 # На этот раз перебираем номера протоколов
Protocol Number | Source IP (Server) | Byte Sum (Server) | Received (Server) | Succeeded (Client) | Byte sum (Client) | Failure reason (Client) | Time difference (μs) |
0 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 70 |
1 | nan | nan | 🤯 | 🫡 | 373 | - | nan |
2 | nan | nan | 🤯 | 🫡 | 373 | - | nan |
3 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 61 |
4 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 52 |
5 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 54 |
6 | nan | nan | 🤯 | 🫡 | 373 | - | nan |
7 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 77 |
8 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 63 |
9 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 66 |
10 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 52 |
11 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 52 |
12 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 63 |
13 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 63 |
14 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 50 |
15 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 80 |
16 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 64 |
17 | nan | nan | 🤯 | 🫡 | 373 | - | nan |
18 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 42 |
19 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 82 |
20 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 71 |
21 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 59 |
22 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 50 |
23 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 51 |
24 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 54 |
25 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 46 |
26 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 48 |
27 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 43 |
28 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 46 |
29 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 66 |
30 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 56 |
31 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 65 |
32 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 56 |
33 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 49 |
34 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 47 |
35 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 48 |
36 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 59 |
37 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 47 |
38 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 45 |
39 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 52 |
40 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 57 |
41 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 56 |
42 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 51 |
43 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 45 |
44 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 58 |
45 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 52 |
46 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 50 |
47 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 46 |
48 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 51 |
49 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 84 |
50 | nan | nan | 🤯 | 🤯 | - | Operation not supported on socket (os error 102) | nan |
51 | nan | nan | 🤯 | 🤯 | - | Operation not supported on socket (os error 102) | nan |
52 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 92 |
53 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 115 |
54 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 81 |
55 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 83 |
56 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 96 |
57 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 71 |
58 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 69 |
59 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 80 |
60 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 84 |
61 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 105 |
62 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 109 |
63 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 97 |
64 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 100 |
65 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 94 |
66 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 124 |
67 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 101 |
68 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 100 |
69 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 87 |
70 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 95 |
71 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 101 |
72 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 97 |
73 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 111 |
74 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 104 |
75 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 115 |
76 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 96 |
77 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 77 |
78 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 65 |
79 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 54 |
80 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 150 |
81 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 95 |
82 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 97 |
83 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 74 |
84 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 93 |
85 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 71 |
86 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 77 |
87 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 70 |
88 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 49 |
89 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 59 |
90 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 74 |
91 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 78 |
92 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 61 |
93 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 59 |
94 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 55 |
95 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 46 |
96 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 59 |
97 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 94 |
98 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 66 |
99 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 54 |
100 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 53 |
101 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 91 |
102 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 148 |
103 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 111 |
104 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 119 |
105 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 75 |
106 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 52 |
107 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 53 |
108 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 52 |
109 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 44 |
110 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 59 |
111 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 51 |
112 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 45 |
113 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 75 |
114 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 91 |
115 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 85 |
116 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 84 |
117 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 64 |
118 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 24 |
119 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 46 |
120 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 62 |
121 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 48 |
122 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 50 |
123 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 50 |
124 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 49 |
125 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 74 |
126 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 54 |
127 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 46 |
128 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 103 |
129 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 73 |
130 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 57 |
131 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 49 |
132 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 62 |
133 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 43 |
134 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 47 |
135 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 90 |
136 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 112 |
137 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 94 |
138 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 53 |
139 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 57 |
140 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 74 |
141 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 64 |
142 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 77 |
143 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 77 |
144 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 75 |
145 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 77 |
146 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 88 |
147 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 96 |
148 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 106 |
149 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 72 |
150 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 80 |
151 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 77 |
152 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 78 |
153 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 91 |
154 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 75 |
155 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 80 |
156 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 96 |
157 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 110 |
158 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 105 |
159 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 83 |
160 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 89 |
161 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 95 |
162 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 111 |
163 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 103 |
164 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 97 |
165 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 91 |
166 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 95 |
167 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 84 |
168 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 57 |
169 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 50 |
170 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 65 |
171 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 75 |
172 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 80 |
173 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 78 |
174 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 67 |
175 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 55 |
176 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 60 |
177 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 85 |
178 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 78 |
179 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 73 |
180 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 79 |
181 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 91 |
182 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 96 |
183 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 88 |
184 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 95 |
185 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 91 |
186 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 74 |
187 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 92 |
188 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 79 |
189 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 75 |
190 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 81 |
191 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 96 |
192 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 95 |
193 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 91 |
194 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 88 |
195 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 92 |
196 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 99 |
197 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 90 |
198 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 90 |
199 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 100 |
200 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 96 |
201 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 89 |
202 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 100 |
203 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 92 |
204 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 109 |
205 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 104 |
206 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 108 |
207 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 95 |
208 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 96 |
209 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 71 |
210 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 76 |
211 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 71 |
212 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 78 |
213 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 91 |
214 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 97 |
215 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 97 |
216 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 93 |
217 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 105 |
218 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 97 |
219 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 91 |
220 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 98 |
221 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 90 |
222 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 108 |
223 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 92 |
224 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 104 |
225 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 109 |
226 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 94 |
227 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 99 |
228 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 94 |
229 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 79 |
230 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 84 |
231 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 79 |
232 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 102 |
233 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 101 |
234 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 113 |
235 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 95 |
236 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 100 |
237 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 91 |
238 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 106 |
239 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 92 |
240 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 97 |
241 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 89 |
242 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 99 |
243 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 90 |
244 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 98 |
245 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 93 |
246 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 94 |
247 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 91 |
248 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 94 |
249 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 94 |
250 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 90 |
251 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 88 |
252 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 94 |
253 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 96 |
254 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 76 |
255 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | nan |
255 | 127.0.0.1 | 373 | 🫡 | 🫡 | 373 | - | 71 |
256 | nan | nan | 🤯 | 🤯 | - | Invalid argument (os error 22) | nan |
Большинство номеров протоколов сработали нормально — ОС принимала пакеты, возвращала их на loopback-интерфейс, и сервер их успешно получал. Но не все номера оказались такими покладистыми. Некоторые пакеты терялись в пути, причем по разным причинам:
Протоколы 1, 2 и 6 (например, ICMP и TCP) не доходили до сервера. Клиент их отправлял, но ОС на серверной стороне перехватывала их до того, как они попадали в сокет.
Протоколы 50 и 51 даже не уходили с клиента — ОС отказывалась их отправлять.
Протокол 256 вообще не проходил вызов socket() — значение вне диапазона, невалидно.
Почему так? Что именно заставляет ОС вести себя по-разному?
Одна из самых полезных техник отладки, которую я освоил в ходе моего эксперимента — при работе с низкоуровневым кодом отслеживать системные вызовы, которые выполняет процесс.
Системный вызов для непосвященных — это просто функция, которая позволяет приложениям запрашивать привилегированные ресурсы у ОС, будь то открытие файла, выделение памяти или, в нашем случае, отправка пакета по сети.
В моем коде на Rust я использовал библиотеку socket2, которая оборачивает системные вызовы в удобный интерфейс. Вот пример кода, создающего сокет:
int sockfd = socket(
AF_INET, // Домен: Интернет-протоколы ARPA. Это сообщает ОС, что нас интересуют IP-протоколы
SOCK_RAW, // Тип: Сырой сокет. Обычно ОС обрабатывает транспортный уровень, но это дает нам полный контроль.
255 // Протокол: Мы перебирали это поле.
);
Этот вызов говорит ОС: «Я хочу прямой доступ к IP-пакетам, вот протокол, с которым собираюсь работать». А дальше уже от ОС зависит — пустит она нас или нет.
1, 2 и 6: Сервер их не видит
Эти пакеты клиент успешно отправлял, но сервер их не видел вовсе. Значит, ОС что-то сделала с ними по пути — возможно, перехватила, отбросила или обработала по-другому.
Я предполагал, что мой сервер сможет принимать вообще все IP-пакеты. Сокет я создавал вот так:
int sockfd = socket(
AF_INET, // Интернет-домен
SOCK_RAW, // Сырой сокет: должен дать нам полный контроль
0 // Пусть ОС решает, какой протокол использовать
);
Я думал, что 0 — это универсальный вариант. Мол, «передай все, что можешь». Но оказалось, это не так.
Для контекста: эксперименты я запускал на Mac, который работает на Darwin. Darwin похож на BSD, но с тонной макияжа. Поэтому он унаследовал не только системные вызовы, но и все причуды BSD-сокетов.
Немного покопавшись в документации, я не нашел ничего полезного про protocol = 0. Но потом наткнулся в BSD-документации на раздражающе расплывчатую фразу:
Значение 0 для protocol позволит системе выбрать подходящий протокол для запрошенного типа сокета.
То есть, вместо того чтобы честно передавать все подряд, система молча и без объяснений фильтрует пакеты. Например, ICMP(1), IGMP(2) или TCP(6) просто не доходят до моего сокета. Видимо, Darwin решил, что он лучше знает, что именно я должен получать.
Вот и ответ: не все сокеты одинаково полезны. А с Darwin — еще и не всегда предсказуемы.
50 и 51: Клиент даже не может их отправить
На этом этапе стало понятно: есть номера протоколов, к которым ОС относится как к особо охраняемым. Протоколы 50 и 51 — это не просто какие-то случайные значения. Это IPSec: ESP и AH, применяемые для шифрования VPN-трафика.
Darwin категорически отказался их отправлять. Почему? Точной причины нет. Но я предполагаю, что это встроенная мера безопасности: мол, если ты не VPN, не лезь.
256: Вызов socket() немедленно завершается неудачей
Этот случай прост:
поле protocol в IP-заголовке — 8-битное;
максимальное значение — 255;
256 просто не помещается.
ОС моментально отвергла этот аргумент, даже не попытавшись его обработать.
Честно говоря, ничего удивительного. Но вот что действительно удивило — это поведение Linux.
После всех этих несостыковок я решил: а давай-ка посмотрим, что скажет Linux. Поднял виртуалку, повторил все шаги — и сразу увидел другое поведение.
Linux, в отличие от Darwin, не позволяет привязать сырой сокет к протоколу 0. Но при этом он разрешил использовать некоторые нестандартные значения, включая те, которые на macOS даже не создавали сокет. В том числе — 256.
Результаты я сохранил в results_no_server_linux_client_loopback. И остался доволен тем, что хотя бы часть моих ожиданий оправдалась.
Написать свой транспортный протокол — технически возможно. Но ОС от этого будет не в восторге. Сетевой стек забит предположениями, которые не всегда очевидны, а «сырой» сокет оказывается не таким уж и сырым.
Все это, как мне кажется, и объясняет, почему подавляющее большинство новых протоколов живут на уровне приложений. Вместо того чтобы бодаться с ОС и файрволлами, инженеры просто строят поверх чего-то, что уже работает. Например, QUIC — это фактически новый транспортный протокол, но он катается на UDP и избегает всей этой возни.
Если вдруг решите поиграть с сырыми сокетами — умоляю, проверяйте код на разных ОС. То, что позволяет сделать Darwin, может вызывать ступор у Linux. А то, что позволяет сделать Linux, в Windows работать вообще не будет. Поведение не стандартизовано, даже если все клянутся в POSIX-соответствии.
До сих пор эти пакеты никогда не покидали мой компьютер. Теперь я хочу отправить HDP через публичный интернет:
Будут ли маршрутизаторы пересылать его или отбрасывать?
Пропустят ли его брандмауэры или отметят как атаку?
Будет ли у него другая задержка по сравнению с TCP?
Не уроню ли я случайно DigitalOcean?
Пора выяснить.
Изначально казалось, что этот эксперимент будет простым (спойлер: НЕТ). Я хотел взять самый дешевый VPS на DigitalOcean, запустить там сервер и начать швыряться в него чем попало: TCP, UDP, мой кастомный HDP и все остальное. Считать потери, смотреть задержки, делать выводы. В теории — просто.
На практике… все пошло не так. Не потому, что что-то не настроилось. А потому что результаты были странные. Они не вписывались в мои ожидания, и я не был морально готов их распутывать.
Я арендовал самый дешевый VPS на Digital Ocean, который смог найти, а затем настроил свой сервер и все необходимые инструменты. Отлично! Осталось понять, где он физически находится.
root@debian-s-1vcpu-512mb-10gb-fra1-01:~# curl myip.wtf
161.35.222.56
root@debian-s-1vcpu-512mb-10gb-fra1-01:~# curl ipinfo.io/161.35.222.56
{
"ip": "161.35.222.56",
"city": "Frankfurt am Main",
"region": "Hesse",
"country": "DE",
"loc": "50.1155,8.6842",
"org": "AS14061 DigitalOcean, LLC",
"postal": "60306",
"timezone": "Europe/Berlin",
"readme": "https://ipinfo.io/missingauth"
}
Франкфурт. Отлично. А клиент у меня работает из Саудовской Аравии. Эксперимент — межконтинентальный. Прежде чем кидаться пакетами, я решил проверить, как пингуется сервер:
❯ ping 161.35.222.56
PING 161.35.222.56 (161.35.222.56): 56 data bytes
64 bytes from 161.35.222.56: icmp_seq=0 ttl=47 time=125.364 ms
64 bytes from 161.35.222.56: icmp_seq=1 ttl=47 time=128.061 ms
64 bytes from 161.35.222.56: icmp_seq=2 ttl=47 time=177.931 ms
64 bytes from 161.35.222.56: icmp_seq=3 ttl=47 time=225.798 ms
64 bytes from 161.35.222.56: icmp_seq=4 ttl=47 time=130.101 ms
64 bytes from 161.35.222.56: icmp_seq=5 ttl=47 time=194.563 ms
64 bytes from 161.35.222.56: icmp_seq=6 ttl=47 time=159.518 ms
64 bytes from 161.35.222.56: icmp_seq=7 ttl=47 time=134.343 ms
64 bytes from 161.35.222.56: icmp_seq=8 ttl=47 time=501.139 ms
64 bytes from 161.35.222.56: icmp_seq=9 ttl=47 time=153.672 ms
64 bytes from 161.35.222.56: icmp_seq=10 ttl=47 time=137.927 ms
64 bytes from 161.35.222.56: icmp_seq=11 ttl=47 time=355.672 ms
64 bytes from 161.35.222.56: icmp_seq=12 ttl=47 time=138.777 ms
64 bytes from 161.35.222.56: icmp_seq=13 ttl=47 time=166.116 ms
64 bytes from 161.35.222.56: icmp_seq=14 ttl=47 time=288.758 ms
64 bytes from 161.35.222.56: icmp_seq=15 ttl=47 time=151.458 ms
64 bytes from 161.35.222.56: icmp_seq=16 ttl=47 time=164.025 ms
64 bytes from 161.35.222.56: icmp_seq=17 ttl=47 time=170.132 ms
64 bytes from 161.35.222.56: icmp_seq=18 ttl=47 time=279.034 ms
^C
--- 161.35.222.56 ping statistics ---
19 packets transmitted, 19 packets received, 0.0% packet loss
Похоже, он довольно далеко, но мне кажется все в порядке. Давайте отправим несколько пакетов, используя наш новый протокол!
Сначала запускаю сервер на машине DigitalOcean:
root@debian-s-1vcpu-512mb-10gb-fra1-01:~/hdp/hdp# sudo cargo run --bin server
Listening on protocol 255
И со своего Mac отправляю пакет:
❯ fortune | cowsay | sudo cargo run --bin client 161.35.222.56
| Protocol Number | Succeeded (Client) | Time (μs) (Client) | Byte sum (Client) | Failure reason (Client) |
| 255 | 🫡 | timestamp | 563 | - |
Пакет отправлен. Давайте снова проверим сервер:
root@debian-s-1vcpu-512mb-10gb-fra1-01:~/hdp/hdp# sudo cargo run --bin server
Listening on protocol 255
| Protocol Number | Time (μs) (Server) | Source IP (Server) | Byte Sum (Server) |
| --- | --- | --- |
| 255 | timestamp | my_ip | 563 |
Отлично. Похоже, все прошло хорошо, по крайней мере, так я думал. На самом деле, с этого момента все пошло под откос. Я сделал короткий перерыв, а затем вернулся и попробовал отправить пакет снова:
| Protocol Number | Time (μs) (Server) | Source IP (Server) | Byte Sum (Server) |
| --- | --- | --- |
| 255 | timestamp | my_ip | 563 |
Зависло? Я не вижу второй пакет. Пусто. Второй пакет не приходит. Жму Ctrl+C, пробую еще. Ноль. Лезу в tcpdump:
❯ sudo tcpdump -i any 'ip[9] == 255'
tcpdump: data link type PKTAP
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on any, link-type PKTAP (Apple DLT_PKTAP), snapshot length 524288 bytes
IP mac > 161.35.222.56: reserved 427
IP mac > 161.35.222.56: reserved 427
IP mac > 161.35.222.56: reserved 427
IP mac > 161.35.222.56: reserved 427
IP mac > 161.35.222.56: reserved 427
IP mac > 161.35.222.56: reserved 427
IP mac > 161.35.222.56: reserved 427
IP mac > 161.35.222.56: reserved 427
Хорошо. Значит, мой клиент их точно отправляет. Что там на сервере?
root@debian-s-1vcpu-512mb-10gb-fra1-01:~/hdp# tcpdump -i any 'ip[9] > 17'
tcpdump: data link type LINUX_SLL2
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes
Тут у меня начали сдавать нервы. Я пересмотрел старые логи — нет, я не сошел с ума. Пакет действительно доходил. Метка времени, сумма байт — все совпадает. Но остальные — исчезают в пустоте. Может, Торвальдс лично меня газлайтит?
Стоп. Как NAT-устройство моего интернет-провайдера переслало пакет? NAT полагается на порты — но мой протокол для них просто черная магия. Так что вполне возможно, он просто не знает, что с этим делать — и блокирует. После небольшого ресерча я нашел подтверждение: DigitalOcean не поддерживает нестандартные IP-протоколы.
Что, впрочем, только еще больше запутало ситуацию. Если они не поддерживают, то как первый пакет прошел? Ответа нет. Все, что у меня осталось — это лог, доказывающий, что он действительно дошел.
Если где-то и существует облачный провайдер, поддерживающий нестандартные IP-протоколы, то это AWS. Я поднял две виртуалки, настроил сервер и клиент. Оно работает!
admin@ip-172-31-13-218:~/hdp$ sudo cargo run --bin server 255
Server is listening on SockAddr { ss_family: 2, len: 16 }, protocol: 255
| Protocol Number | Time (μs) (Server) | Source IP (Server) | Byte Sum (Server) |
| --- | --- | --- |
| 255 | timestamp | 54.153.13.186 | 33 |
| 255 | timestamp | 54.153.13.186 | 34 |
| 255 | timestamp | 54.153.13.186 | 35 |
| 255 | timestamp | 54.153.13.186 | 36 |
Правда, сервер находился всего в двух хопах от клиента, так что страшных приключений через весь интернет не было.
Я замерил разницу между HDP и UDP. Разница есть — но настолько крошечная, что ей можно пренебречь: примерно 20 мкс в среднем. Никакой реальной выгоды.
А как насчет интернета?
Я попробовал отправлять пакеты со своего Mac на сервер в AWS — и получил тот же эффект: первый пакет доходит, и все. Оставил результаты в файле tcpdump_tokyo_server_mac_client.md. Послал по одному пакету для каждого протокола. Работают только TCP, UDP и ICMP. Все остальное — тишина после первого запроса.
Как и ожидалось, между машиной на DigitalOcean и AWS ничего не передается. Гарантированно ничего сказать нельзя, но выводы напрашиваются.
Теоретически — да, можно использовать свой IP-протокол. Но если вы не мазохист — не надо.
Код будет привязан к ОС. Поддерживать это на кросс-платформе — отдельный ад.
NAT, фаерволы, маршрутизаторы — все будет стараться убить ваш пакет. И в большинстве случаев — убьет.
Локально, может, и заработает. В интернете — забудьте.
И главное: никакого выигрыша по задержке за счет использования нестандартного протокола я не увидел.
TL;DR: Используйте UDP или TCP.
Несколько читателей предложили попробовать мой протокол по IPv6 — там нет NAT'а, как в IPv4. Любопытно? Еще бы.
Я добавил поддержку IPv6, подключился к тому же серверу на AWS:
admin@ip-172-31-2-72:~/hdp$ RUST_BACKTRACE=full sudo cargo run --bin server 6 255 # The 6 is for IPv6
| Protocol Number | Time (μs) (Server) | Source IP (Server) | Byte Sum (Server) |
| --- | --- | --- |
Теперь запускаю клиента с Mac, через океаны и материки. И — вуаля:
❯ fortune | cowsay | sudo cargo run --bin client 200 '2600:1f1c:1cf:b1ce:f653:afc7:4650:8aa0' 255 hdp
Running `target/debug/client 2000 '2600:1f1c:1cf:b1ce:f653:afc7:4650:8aa0' 17 udp`
| Protocol Number | Succeeded (Client) | Time (μs) (Client) | Byte sum (Client) | Failure reason (Client) |
| 255 | 🫡 | 1740779795209398 | 49 | - |
| 255 | 🫡 | 1740779795413344 | 50 | - |
| 255 | 🫡 | 1740779795620781 | 51 | - |
Он появляется на сервере | 255 | 1740779715088323 | my_ip | 49 |
Сработало! Это были настоящее американские горки, и поездка удалась.
Полезные ссылки
Спецификация UDP-протокола — настолько минимальна, что это даже смешно.
Протоколы, официально поддерживаемые IP.
Статья про отличия raw sockets в Linux и FreeBSD.
Интересный ответ на тему: как реализовать NAT для чего-то кроме TCP/UDP.