Как использовать SSH с динамическим сжатием zstd?

Вопрос или проблема

Я хочу настроить свой SSH-туннель для динамического сжатия, чтобы получить максимальную пропускную способность для доступной пропускной способности и ресурсов ЦП на каждом конце. Поскольку сжатие gzip встроено и не является подключаемым, я подумал, что могу использовать -o ProxyCommand, чтобы настроить двойной SSH-туннель, где внешний туннель отправляет сжатые данные в качестве содержимого, а внутренний туннель подключается к демону SSH на удаленном хосте. Это почти работает. Вот моя команда:

# параметры динамического сжатия для zstd опущены для краткости
ssh -o ProxyCommand='zstd | ssh -p %p %h "unzstd | nc localhost %p | zstd" | unzstd' <hostname>

Вот некоторые вещи, которые я знаю об этой команде:

  • Когда я запускаю эту команду, терминал зависает без какого-либо вывода.
  • Она работает, если я убираю [un]zstd, что не удивительно, так как использование netcat в качестве прокси-команды является стандартным методом подключения через переходные хосты.
  • cat | zstd | cat на командной строке не вернет данные немедленно, потому что программы сжатия обрабатывают данные порциями. Вам нужно отправить EOF с ctrl+d несколько раз, прежде чем они освободят сжатые данные. Вы также можете разжать данные внутри конвейера, и это будет работать так же.
  • Если я нажимаю ctrl+d при выполнении полной команды, ничего не происходит.

Что я здесь упускаю? Есть ли способ заставить это работать или другой способ, который я упустил?

С вашим подходом вы пытаетесь сжать SSH-протокол.

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

Кроме того, как вы и сказали, zstd будет сжимать порциями, поэтому он не может сжать пакеты протокола, где важно, чтобы пакеты передавались по отдельности. Вам необходимо сжимать данные пакета по отдельности и до шифрования.

Сжатие перед шифрованием может быть выполнено следующим образом:

stty=$(stty -g)
stty raw -echo
zstd -cf |
  LC_stty=$stty \
    LC_TERM=$TERM \
    LC_COLS=${COLUMNS:-$(tput cols)} \
    LC_LINES=${LINES:-$(tput lines)} ssh -o IPQoS=lowdelay host '
      export TERM=$LC_TERM
      zstd -d |
        socat - '\''SYSTEM:"stty cols \"$LC_COLS\" rows \"$LC_LINES\"; bash -il",pty,ctty,setsid,stderr'\'' 2>&1 |
        zstd -c' |
      zstd -d
      stty "$stty"

Где мы используем socat, чтобы запустить bash в псевдотерминале, а не использовать ssh -tt, так как нам нужно, чтобы сжатие происходило вне его, чтобы последующая обработка, выполняемая дисциплиной tty, не мешала. Это означает, однако, отсутствие распространения изменений размера окна. Мы пытаемся восстановить часть функциональности, передавая $TERM и настройки stty и устанавливая IPQoS на тот, который вы получили бы для интерактивной сессии.

Но снова вы столкнетесь с проблемой сжатия данных в (большие) порции.

Насколько я вижу, утилита zstd не может быть настроена для выдачи сжатых данных для каждого входа, который она получает различного размера. Вам нужно будет написать свою собственную.

Кроме того, сжимать ввод интерактивной сессии вряд ли имеет смысл. Это просто символы, отправляемые при нажатии клавиш, поэтому вы обычно отправляете по одному байту за раз, что нельзя сжать (и в моих тестах я вижу, что SSH отправляет пакеты с полезной нагрузкой 36 байт одновременно с десятками других пакетов размером 36 байт для маскировки нажатий клавиш; все случайные, непригодные для сжатия данные).

Для интерактивного zstd, который выдает сжатые данные как можно скорее, вы можете адаптировать этот пример из модуля Compress::Zstd, чтобы он использовал rawread вместо read, чтобы он считывал то, что в настоящее время доступно в канале, отключал буферизацию и очищал компрессор для каждого ввода:

#! /usr/bin/perl
use Compress::Zstd qw(ZSTD_MAX_CLEVEL);
use Compress::Zstd::Compressor qw(ZSTD_CSTREAM_IN_SIZE);
use Compress::Zstd::Decompressor qw(ZSTD_DSTREAM_IN_SIZE);

my ($decompress) = grep { $_ eq '-d' } @ARGV;
my ($level) = map { s/^-//; $_ } grep { /^-\d+$/ } @ARGV;
$level = 3 if !$level || $level < 1 || $level > ZSTD_MAX_CLEVEL;
$| = 1;

if ($decompress) {
  my $decompressor = Compress::Zstd::Decompressor->new;
  while (sysread(STDIN, my $buffer, ZSTD_DSTREAM_IN_SIZE)) {
    print $decompressor->decompress($buffer);
  }
} else {
  my $compressor = Compress::Zstd::Compressor->new($level);
  while (sysread(STDIN, my $buffer, ZSTD_CSTREAM_IN_SIZE)) {
    print $compressor->compress($buffer) . $compressor->flush;
  }
  print $compressor->end;
}

А затем вызвать SSH следующим образом:

stty=$(stty -g)
stty raw -echo
LC_stty=$stty \
  LC_TERM=$TERM \
  LC_COLS=${COLUMNS:-$(tput cols)} \
  LC_LINES=${LINES:-$(tput lines)} ssh -o IPQoS=lowdelay host '
    TERM=$LC_TERM
    socat - '\''SYSTEM:"stty cols \"$LC_COLS\" rows \"$LC_LINES\"; bash -il",pty,ctty,setsid,stderr'\'' 2>&1 |
      zstdi' |
  zstdi -d
stty "$stty"

(где zstdi – это тот скрипт на perl; пропущено сжатие ввода). Это кажется использующимся, но я сомневаюсь, что вы сэкономите много (если не меньше) ЦП по сравнению с использованием просто ssh -C и его сжатием zlib.

Сжатие вывода интерактивной сессии пользователя, вероятно, даст вам некоторые преимущества в терминах использования пропускной способности. Например, если запустить одну и ту же команду ls -l дважды в одном и том же каталоге, второй вызов должен привести к значительно меньшему трафику, но для интерактивных сеансов терминала, если пропускная способность (не путать с задержкой) так медленна, что вы чувствуете, что получите улучшение от сжатия, тогда мы говорим о нескольких KiB / с пропускной способностью (если только вы не суперчеловек, который может читать текст быстрее этого), и переход от zlib к zstd не даст много разницы в использовании ЦП, где вы всего лишь перейдете от чего-то вроде 0,001% до 0,0008% (совершенно вымышленные цифры).

zstd будет полезен, если работать с потоками, например если вы хотите отправить большое количество данных для их обработки (на лету или нет) удаленной командой и получить (также большое) выходное значение обратно, а пропускная способность сети будет узким местом:

# на лету
set -o pipefail
zstd < input |
  ssh host '
    set -o pipefail
    zstd -d | remote-process | zstd' |
  zstd -d > output
# не на лету:
set -o pipefail
zstd < input |
  ssh host '
    zstd -d > input &&
      remote-process -i input -o output >&2 &&
      zstd -c < output &&
      rm -f input output' |
  zstd -d > output

Помимо предоставления сжатия, использование zstd означает, что у вас есть дополнительный уровень целостности данных, так как zstd -d будет сообщать об ошибке, если ввод не может быть полностью распакован, например, когда передача была прервана.

Ответ или решение

Использование динамической компрессии SSH с zstd — это интересная задача, однако, как вы уже заметили, существуют важные ограничения и нюансы, которые необходимо учесть. Давайте рассмотрим подробнее, как можно использовать zstd для передачи данных по SSH, а также объясним, с какими проблемами вы можете столкнуться и как их избежать.

Проблемы с применением сжатия при использовании SSH

  1. Структура SSH: SSH использует шифрование, и сжатие данных до шифрования (например, с помощью zstd) имеет смысл, так как зашифрованные данные практически не поддаются сжатию. Это вызвано тем, что шифрование делает данные случайными, и следовательно, нет возможности извлечь статистическую информацию для эффективного сжатия.

  2. Буферизация: Программа zstd обрабатывает данные порциями, что создает сложности при попытке интерактивной передачи данных. Эта буферизация требует наличия конца файла (EOF) для начала сжатия, что нарушает потоковую передачу данных.

Чтобы организовать корректный и эффективный процесс сжатия с использованием zstd, вам потребуется реализовать передачу в виде потокового интерфейса.

Решение

Чтобы корректно настроить SSH с использованием zstd для сжатия, попробуйте следующую конструкцию:

stty=$(stty -g)
stty raw -echo
zstd -cf | 
  ssh -o IPQoS=lowdelay <hostname> '
    zstd -d | 
    socat - "SYSTEM:\"stty cols $(tput cols) rows $(tput lines); bash -il\",pty,ctty,setsid,stderr" 2>&1 | 
    zstd -c' | 
  zstd -d
stty "$stty"

Объяснение:

  1. stty: Исполнение stty передавая параметры для сохранения текущего состояния терминала. После выполнения действий необходимо вернуть состояние терминала в исходное.

  2. zstd -cf |: Сжатие данных с помощью zstd. -c означает вывод в стандартный поток, а -f указывает выполнять принудительное сжатие.

  3. ssh -o IPQoS=lowdelay: Настройка SSH для минимизации задержек в передаче данных. Проброс zstd -d в удаленную среду для распаковки поступающих данных.

  4. socat: Этот инструмент позволяет передавать данные через псевдотерминал (PTY). В этом сценарии мы используем socat, чтобы запускать bash, передавая при этом необходимые параметры терминала.

  5. zstd -c: Подача сжатых данных обратно от сервера к клиенту.

Важные замечания

  • Уровни сжатия: Выбор уровня сжатия в zstd отразится на производительности. Для интерактивных сеансов, таких как работа в терминале, возможно, не стоит использовать высокие уровни, так как это может привести к заметным задержкам.

  • Интерактивные сессии: Обратите внимание, что в основном вы будете подавать данные по одному символу за раз, что неэффективно для сжатия. Для больших объемов данных (например, при передаче файлов или работы с большими выводами) zstd будет более полезным и эффективным.

  • Проверка на ошибки: Использование zstd также дает дополнительный уровень целостности данных. Если переданная информация не может быть разжата корректно, zstd выдаст ошибку.

Заключение

Использование zstd для сжатия данных в SSH-подключениях может быть полезным, но требует дополнительной настройки и учета особенностей потоковой обработки данных. В большинстве случаев для стандартизированных и простых случаев использования вам может быть достаточно стандартной опции сжатия -C, встроенной в SSH, которая использует zlib. Но если вы хотите провести эксперименты с zstd, представленный выше скрипт будет отправной точкой для дальнейших доработок.

Оцените материал
Добавить комментарий

Капча загружается...