И почему меня это волнует?Вот небольшая выдержка из обобщённого вывода tcpdump для ssh-сеанса, в рамках которого я всего один раз нажал на клавишу:$ ./first_linИ почему меня это волнует?Вот небольшая выдержка из обобщённого вывода tcpdump для ssh-сеанса, в рамках которого я всего один раз нажал на клавишу:$ ./first_lin

[Перевод] Почему SSH отправляет 100 пакетов по одному нажатию клавиши?

И почему меня это волнует?

Вот небольшая выдержка из обобщённого вывода tcpdump для ssh-сеанса, в рамках которого я всего один раз нажал на клавишу:

$ ./first_lines_of_pcap.sh single-key.pcap 1 0.000s CLIENT->SERVER 36 bytes 2 0.007s SERVER->CLIENT 564 bytes 3 0.015s CLIENT->SERVER 0 bytes 4 0.015s CLIENT->SERVER 36 bytes 5 0.015s SERVER->CLIENT 36 bytes 6 0.026s CLIENT->SERVER 0 bytes 7 0.036s CLIENT->SERVER 36 bytes 8 0.036s SERVER->CLIENT 36 bytes 9 0.046s CLIENT->SERVER 0 bytes 10 0.059s CLIENT->SERVER 36 bytes

Действительно «небольшая», так как на самом деле строк там было очень много.

$ ./summarize_pcap.sh single-key.pcap Total packets: 270 36-byte msgs: 179 packets ( 66.3%) 6444 bytes Other data: 1 packet ( 0.4%) 564 bytes TCP ACKs: 90 packets ( 33.3%) Data sent: 6444 bytes in 36-byte messages, 564 bytes in other data Ratio: 11.4x more data in 36-byte messages than other data Data packet rate: ~90 packets/second (avg 11.1 ms between data packets)

Очень много пакетов, учитывая, что клавиша была нажата всего один раз. Что здесь происходит? И почему меня это заинтересовало?

Вот весь скрипт, если вам интересно:

# первые_строки_из_pcap.sh tshark -r "$1" \ -T fields -e frame.number -e frame.time_relative -e ip.src -e ip.dst -e tcp.len | \ awk 'NR<=10 {dir = ($3 ~ /71\.190/ ? "CLIENT->SERVER" : "SERVER->CLIENT"); printf "%3d %6.3fs %-4s %3s bytes\n", $1, $2, dir, $5}' # обобщение_pcap.sh tshark -r "$1" -Y "frame.time_relative <= 2.0" -T fields -e frame.time_relative -e tcp.len | awk ' { count++ payload = $2 if (payload == 0) { acks++ } else if (payload == 36) { mystery++ if (NR > 1 && prev_data_time > 0) { delta = $1 - prev_data_time sum_data_deltas += delta data_intervals++ } prev_data_time = $1 } else { game_data++ game_bytes = payload if (NR > 1 && prev_data_time > 0) { delta = $1 - prev_data_time sum_data_deltas += delta data_intervals++ } prev_data_time = $1 } } END { print "Total packets:", count print "" printf " 36-byte msgs: %3d packets (%5.1f%%) %5d bytes\n", mystery, 100*mystery/count, mystery*36 printf " Other data: %3d packet (%5.1f%%) %5d bytes\n", game_data, 100*game_data/count, game_bytes printf " TCP ACKs: %3d packets (%5.1f%%)\n", acks, 100*acks/count print "" printf " Data sent: %d bytes in 36-byte messages, %d bytes in other data\n", mystery*36, game_bytes printf " Ratio: %.1fx more data in 36-byte messages than other data\n", (mystery*36)/game_bytes print "" avg_ms = (sum_data_deltas / data_intervals) * 1000 printf " Data packet rate: ~%d packets/second (avg %.1f ms between data packets)\n", int(1000/avg_ms + 0.5), avg_ms }'

Проблема

Я разрабатываю высокопроизводительную игру, которая работает по ssh. Интерфейс TUI для этой игры написан при помощи bubbletea, и информацию я отправлял через ssh при помощи wish.

Игра воспроизводится в окне размером 80x60, которое обновляется 10 раз в секунду. Я намерен обслуживать не менее 2000 пользователей, играющих одновременно. Таким образом, мне требуется обновлять ~100 миллионов ячеек в секунду. Поэтому меня интересует производительность.

Итак, у меня есть скрипт, соединяющий друг с другом несколько сотен ботов по ssh, а затем приказывающий ботам сделать ход. Затем я пользуюсь отличными профилировочными инструментами, имеющимися в Go, и смотрю, что происходит.

Вчера я случайно повредил мою тестовую обвязку. Вместо регулярной отправки игровых данных мой сервер стал с равным интервалом отправлять ботам единственное сообщение: «ваш экран слишком мал». На это потратилась половина процессорной мощности, доступной для моей игры, и полоса передачи данных также сузилась наполовину.

Поначалу я был разочарован. (Ненадолго) я подумал, что мне прямо в руки даром попал способ кардинально ускорить игру, а оказалось, что это просто ошибка, возникшая при тестировании.

Но подождите.

Если я не отправлял игровые данные ботам обратно, то почему использование ЦП падало на 50%, а не на 100%?

Разбор

Устраняя эту проблему с тестовой обвязкой, я логировал при помощи tcpdump игровой трафик как с разрушающими изменениями, так и без них. В таком роде:

# Игра работает через порт 22 timeout 30s tcpdump -i eth0 'port 22' -w with-breaking-change.pcap # Обратить изменение timeout 30s tcpdump -i eth0 'port 22' -w without-breaking-change.pcap

При разрушающем изменении исчезала возможность отрисовывать игру через ssh. Так что with-breaking-change.pcap содержит те пакеты, которые являются накладными расходами при каждом соединении, а отображать игровое поле эти пакеты не позволяют.

Отладку я выполнял при помощи Claude Code, так что я попросил эту модель резюмировать, что она видит в pcap.

Может быть, посмотришь сам? Я положил with-breaking-change.pcap в этот каталог -- Вау! Вот что я нашёл: Распределение пакетов по размеру (всего 413 703 пакета): 274 907 пакетов (66%): ровно по 36 байт 138 778 пакетов (34%): по 0 байт (операции TCP ACK) 18 пакетов (<0,1%): по 72 байта

Когда я затем проанализировал другой pcap, поменьше, оказалось, что эти таинственные пакеты поступают с интервалом примерно по 20 мс.

Меня это изрядно озадачило (как и Claude Code). Мы накидали несколько идей о том, что бы это могло быть:

  • Сообщения, связанные с управлением потоком по SSH

  • Опрос о размере PTY или другие проверки состояния

  • Какие‑то причуды bubbletea или wish

Выделялось одно обстоятельство: эти проверки инициировал – мой ssh-клиент (то есть, заводской ssh, установленный на MacOS), а не мой сервер.

Как по наитию я сделал tcpdump обычного ssh-сеанса.

# у меня на mac, в одной вкладке sudo tcpdump -ien0 'port 22' # у меня на mac, в другой вкладке ssh $some_vm_of_mine

Я дождался, пока уляжется шум, характерный для начального этапа соединения, отправил на удалённую виртуальную машину сигнал ровно с одной клавиши и посмотрел вывод tcpdump.

Возник точно такой же паттерн! Что же происходит?

Отладка

Стоило мне осознать, что это свойство присуще заводскому ssh, а не моей игре, отладка значительно упростилась.

Выполнив ssh -vvv, я составил достаточно полное впечатление о том, что же происходит:

debug3: obfuscate_keystroke_timing: starting: interval ~20ms debug3: obfuscate_keystroke_timing: stopping: chaff time expired (49 chaff packets sent) debug3: obfuscate_keystroke_timing: starting: interval ~20ms debug3: obfuscate_keystroke_timing: stopping: chaff time expired (101 chaff packets sent)

Именно эти 20 мс выдавали проблему. Такой период в точности соответствует тому загадочному паттерну, который мы наблюдали ранее! Оставшаяся часть сообщения также весьма информативна: мы отправляем 49 «chaff» пакетов при первом нажатии клавиши и 101 «chaff» при втором.

В 2023 году в ssh была добавлена обфускация длительности нажатий на клавиши. Смысл в том, что скорость, с которой набираются различные буквы, выдаёт часть информации о том, какой именно текст вы вводите. Поэтому ssh вместе с сигналами от нажимаемых вами клавиш отправляет множество chaff-пакетов, из-за которых злоумышленнику становится сложнее определить, какие именно нажатия соответствуют смысловому вводу.

Это весьма целесообразно для обычных ssh-сеансов, при которых критически важно соблюдать приватность. Но влечёт серьёзнейшие издержки для игры, распахнутой на весь Интернет, тем более, что критически важное требование к этой игре — минимизировать задержку.

Устранение проблемы

Обфускацию нажатий клавиш можно отключить на стороне клиента. Откатив принципиальные изменения, внесённые ранее, я попытался обновить тестовую обвязку так, чтобы она проходила ObscureKeystrokeTiming=no при запуске ssh-сеансов.

Сработало отлично. Расход ресурса ЦП резко упал, а боты всё равно получали корректные данные.

Но едва ли такое решение применимо в реальной практике. Я хочу, чтобы ssh mygame Просто Работало, и мне не приходилось просить пользователей передавать мне опции, которых они, возможно, не понимают.

Claude Code исходно не верила, что нам удастся отключить эту функциональность и на стороне сервера.

Сгенерировано при помощи отличного инструмента claude-code-transcripts от Саймона Уилсона
Сгенерировано при помощи отличного инструмента claude-code-transcripts от Саймона Уилсона

К счастью, здесь описано, как именно устроена обфускация нажатий клавиш при работе с SSH. Поэтому мне не составило труда посмотреть соответствующий код в ssh-библиотеке, написанной на go (от которой я настроил транзитивную зависимость).

Сообщение лога: Ввестьи возможность пингования на транспортном уровне В таком случае добавляется пара сообщений транспортного протокола SSH SSH2_MSG_PING/PONG для реализации пингования. Эти сообщения используют числа, относящиеся к пространству номеров "локальные расширения" и объявляются при помощи "[email protected]" ext-info сообщения, в которой число "0" представлено как строка.

«Chaff»-сообщения, при помощи которых ssh маскирует нажатия клавиш – это сообщения SSH2_MSG_PING. Причём, они отправляются на серверы, которые оповещают о доступности расширения [email protected]. Почему ms просто… не сообщать об [email protected]?

Я поискал в библиотеке для ssh на go, что там сказано о [email protected] и нашёл коммит, в рамках которого была добавлена поддержка этой функции. Коммит был крошечный, и казалось, что откатить его будет очень просто.

Я склонировал репозиторий go crypto, приказал Claude откатить это изменение и обновить наши зависимости так, чтобы в коде стал использоваться наш клон (благодаря директиве replace языка go сделать форк библиотеки не составляет труда).

Затем я повторно прогнал мою тестовую обвязку. Результаты были…очень хороши:

Total CPU 29.90% -> 11.64% Syscalls 3.10s -> 0.66s Crypto 1.6s -> 0.11s Bandwidth ~6.5 Mbit/sec -> ~3 Mbit/sec

Claude также был весьма воодушевлён:

dfcb95aa02e2fb48b809f9a5b8951d46.png

Разумеется, делать форк библиотеки crypto из go страшновато, и мне требовалось тщательно продумать, как организовать безопасную поддержку этого маленького патча.

Но улучшение было огромным. Большую часть предыдущей недели я потратил на то, чтобы выжимать дополнительную производительность по капле. Я просто представить себе не мог, что смогу сократить трату ресурсов более чем на 50%.

Отладка с применением БЯМ оказалась очень интересной

Я думал о том, смогут ли БЯМ частично взять на себя решение части задач, притом, что самому решать задачи мне очень нравится. Но должен сказать, отладка этой задачи с привлечением Claude Code оказалась суперинтересной.

Я достаточно хорошо знаком с tcpdump, tshark и им подобными, знаю, на что они способны. Но я недостаточно регулярно ими пользуюсь, поэтому рука в обращении с ними не набита. Мне, в самом деле, понравилось, что можно сказать агенту: «вот тут какая-то странная фиговина — расскажи, что происходит». Наблюдая, как агент выполняет команды, я мог постоянно актуализировать в голове текущее состояние решаемой задачи.

Всё равно остаются пограничные случаи. В какой-то момент, запутавшись, я переключился на ChatGPT, и она очень уверенно сообщила, что получившийся у меня вывод tcpdump — это нормальное поведение ssh.

Аналогично, мне пришлось наводить Claude Code на идею — а не сделать ли форк библиотеки ssh на go. Причём, мне пришлось применить тот самый выход из плоскости: «подождите… если тестовая обвязка сбоит, то почему использование ЦП не падает до 0%»?

В ответ на «БЯМ не полностью справляются с этой задачей» некоторые отвечают: «вы неправильно их понимаете!»

Думаю, иногда так и есть! Взаимодействие с БЯМ — это совершенно новый навык, и взаимодействие с ними воспринимается очень странно, если вы привыкли писать код так, как это делалось в 2020. Возможно, более талантливый пользователь БЯМ запросто решил бы с её помощью такую задачу.

Любой навык всегда лучше всего нарабатывается на практике. Для меня это означает – самостоятельно выяснить, как привить мой интуитивный подход к решению задач тем инструментам, с которыми я работаю.

Кроме того. Быть частью системы круто. А иначе как бы я написал этот пост?

Спасибо, что дочитали !

Источник

Отказ от ответственности: Статьи, размещенные на этом веб-сайте, взяты из общедоступных источников и предоставляются исключительно в информационных целях. Они не обязательно отражают точку зрения MEXC. Все права принадлежат первоисточникам. Если вы считаете, что какой-либо контент нарушает права третьих лиц, пожалуйста, обратитесь по адресу [email protected] для его удаления. MEXC не дает никаких гарантий в отношении точности, полноты или своевременности контента и не несет ответственности за любые действия, предпринятые на основе предоставленной информации. Контент не является финансовой, юридической или иной профессиональной консультацией и не должен рассматриваться как рекомендация или одобрение со стороны MEXC.

Вам также может быть интересно

Макиавелли мёртв, но не по тем причинам, о которых вы думаете

Макиавелли мёртв, но не по тем причинам, о которых вы думаете

Альтернативное обоснование либертарианской экономики на основе речи Милея в ДавосеВ январе 2026 года президент Аргентины Хавьер Милей выступил на Давосском фору
Поделиться
ProBlockChain2026/01/28 02:38
Забудьте о Bitcoin: XRP готов стать ведущей инвестицией с доходностью в 2026 году из-за опасений квантовых рисков

Забудьте о Bitcoin: XRP готов стать ведущей инвестицией с доходностью в 2026 году из-за опасений квантовых рисков

Bitcoin опускается ниже $90 тыс., так как квантовые риски растут, подталкивая инвесторов к стратегиям облачного майнинга BI DeFi в 2026 году. Кристофер Вуд, стратег и глобальный руководитель
Поделиться
Crypto.news2026/01/28 06:00
Tether и Circle контролируют 87% стейблкоинов на фоне ужесточения регулирования

Tether и Circle контролируют 87% стейблкоинов на фоне ужесточения регулирования

Новые данные показывают, что всего два эмитента доминируют практически во всем секторе, что вызывает новые вопросы о том, как предстоящее регулирование в США […] Публикация Tether и Circle
Поделиться
Coindoo2026/01/28 05:49