И почему меня это волнует?Вот небольшая выдержка из обобщённого вывода 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.

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

Объяснение полномочий Минюста в отношении сделки Netflix-WBD

Объяснение полномочий Минюста в отношении сделки Netflix-WBD

Публикация «Объяснение полномочий Минюста США в отношении сделки Netflix-WBD» появилась на BitcoinEthereumNews.com. БЕРБАНК, КАЛИФОРНИЯ – 5 ДЕКАБРЯ: Вид с воздуха на Warner
Поделиться
BitcoinEthereumNews2026/01/28 06:54
UK Financial Ltd привлекает ветерана криптобирж эпохи раннего Bitcoin Neulo Emmanuel (Neulo) на долгосрочную стратегическую консультационную должность

UK Financial Ltd привлекает ветерана криптобирж эпохи раннего Bitcoin Neulo Emmanuel (Neulo) на долгосрочную стратегическую консультационную должность

Лондон, Великобритания UK Financial Ltd сегодня объявила о назначении ветерана криптобиржи эпохи раннего Bitcoin Neulo Emmanuel, широко известного в секторе криптовалют как "
Поделиться
AI Journal2026/01/28 07:31
Квантовые вычисления для противоракетной обороны: в 10 раз быстрее

Квантовые вычисления для противоракетной обороны: в 10 раз быстрее

Статья «Квантовые вычисления для противоракетной обороны: в 10 раз быстрее» появилась на BitcoinEthereumNews.com. Квантовые вычисления могут помочь защититься от нескольких одновременных
Поделиться
BitcoinEthereumNews2026/01/28 06:45