Источник: OpenSSH/Cookbook/Multiplexing
Из Wikibooks, открытые книги для открытого мира
Мультиплексирование (multiplexing) — это возможность передавать более одного сигнала по одной линии или соединению. В OpenSSH мультиплексирование позволяет переиспользовать существующее исходящее TCP-соединение для нескольких одновременных SSH-сеансов к удалённому SSH-серверу, избегая накладных расходов на создание нового TCP-соединения и повторную аутентификацию каждый раз.
Преимущество SSH-мультиплексирования в том, что устраняются накладные расходы на создание новых TCP-соединений и согласование защищённого соединения. Общее число соединений, которое машина может принимать, — ограниченный ресурс, и этот предел заметнее на одних машинах, чем на других, и сильно варьируется в зависимости от нагрузки и использования. Также при открытии нового соединения есть значительная задержка. Действия, при которых новые соединения открываются многократно, можно значительно ускорить с помощью мультиплексирования.
Разницу между мультиплексированием и одиночными сеансами можно увидеть, сравнив таблицы ниже. Обе — отобранный вывод netstat -nt, слегка отредактированный для ясности. Мы видим в таблице 1, «SSH-соединения, раздельные», что без мультиплексирования каждый новый вход создаёт новое TCP-соединение — по одному на сеанс входа. Далее, в таблице 2, «SSH-соединения, мультиплексированные», видно, что при использовании мультиплексирования новые входы канализуются через уже установленное TCP-соединение.
# Local Address Foreign Address State
# one connection
tcp 0 0 192.168.x.y:45050 192.168.x.z:22 ESTABLISHED
# two separate connections
tcp 0 0 192.168.x.y:45050 192.168.x.z:22 ESTABLISHED
tcp 0 0 192.168.x.y:45051 192.168.x.z:22 ESTABLISHED
# three separate connections
tcp 0 0 192.168.x.y:45050 192.168.x.z:22 ESTABLISHED
tcp 0 0 192.168.x.y:45051 192.168.x.z:22 ESTABLISHED
tcp 0 0 192.168.x.y:45052 192.168.x.z:22 ESTABLISHED
Table 1: SSH Connections, Separate
Обе таблицы показывают TCP/IP-соединения, связанные с SSH-сеансами. Таблица выше показывает новое TCP/IP-соединение для каждого нового SSH-соединения. Таблица ниже показывает единственное TCP/IP-соединение при нескольких активных SSH-сеансах.
# Local Address Foreign Address State
# one connection
tcp 0 0 192.168.x.y:58913 192.168.x.z:22 ESTABLISHED
# two multiplexed connections
tcp 0 0 192.168.x.y:58913 192.168.x.z:22 ESTABLISHED
# three multiplexed connections
tcp 0 0 192.168.x.y:58913 192.168.x.z:22 ESTABLISHED
Table 2: SSH Connections, Multiplexed
Как видим, при мультиплексировании устанавливается и используется единственное TCP-соединение, независимо от того, передаётся ли через него несколько SSH-сеансов.
Или можно сравнить время выполнения true(1) на медленном удалённом сервере с помощью time(1). Две команды выглядели бы как time ssh server.example.org true против time ssh -S ./path/to/somesocket server.example.org true, и в обеих используются ключи с агентом. Сначала без мультиплексирования — видим обычное время соединения:
real 0m0.658s
user 0m0.016s
sys 0m0.008s
Затем делаем то же самое снова, но с мультиплексированным соединением, и видим более быстрый результат:
real 0m0.029s
user 0m0.004s
sys 0m0.004s
Разница весьма велика и определённо накопится для любой активности, где соединения устанавливаются многократно и быстро друг за другом. Прирост скорости для мультиплексированных соединений приходит не с master-соединением — оно обычной скорости, — а со второго и последующих мультиплексированных соединений. Накладные расходы на новый SSH-сеанс остаются, но накладные расходы на новое TCP-соединение исключаются. Второе и последующие соединения будут переиспользовать установленное TCP-соединение снова и снова и не нуждаться в создании нового TCP-соединения для каждого нового SSH-сеанса.
SSH-клиент OpenSSH поддерживает мультиплексирование исходящих соединений, начиная с версии 3.9 (18 августа 2004)[1], с помощью конфигурационных директив ControlMaster, ControlPath и ControlPersist, которые определяются в ssh_config(5). Файл конфигурации клиента обычно по умолчанию располагается в ~/.ssh/config. Все три директивы описаны на man-странице ssh_config(5). См. также секцию «TOKENS» там же — список токенов, доступных для использования в ControlPath. Любые использованные допустимые токены разворачиваются во время выполнения.
ControlMaster определяет, будет ли ssh(1) слушать управляющие соединения и что с ними делать. ControlPath задаёт расположение управляющего сокета (control socket), используемого мультиплексированными сеансами. Они могут быть заданы глобально или локально в ssh_config(5), либо указаны во время выполнения. Управляющие сокеты автоматически удаляются при завершении master-соединения. ControlPersist может использоваться совместно с ControlMaster. Если ControlPersist установлен в 'yes', master-соединение останется открытым в фоне, принимая новые соединения, пока не будет явно убито, либо закрыто через -O, либо не истечёт предустановленный таймаут. Если ControlPersist установлен на время, master-соединение останется открытым указанное время или пока не закроется последний мультиплексированный сеанс — смотря что дольше.
Вот пример фрагмента из ssh_config(5), применимого для запуска мультиплексированного сеанса к machine1.example.org через ярлык machine1:
Host machine1
HostName machine1.example.org
ControlPath ~/.ssh/controlmasters/%r@%h:%p
ControlMaster auto
ControlPersist 10m
С этой конфигурацией первое соединение с machine1 создаст управляющий сокет в каталоге ~/.ssh/controlmasters/. Затем любые последующие соединения — по умолчанию до 10, как задано MaxSessions на SSH-сервере, — будут автоматически переиспользовать этот управляющий путь как мультиплексированные сеансы. Подтверждение каждого нового соединения можно требовать, установив ControlMaster в 'autoask' вместо 'auto'.
Обратите внимание: с настройками выше управляющий сокет будет помещён в папку ~/.ssh/controlmasters/. Если эта папка не существует заранее, SSH-клиент завершится с характерной ошибкой от unix_listener с жалобой, что файл или путь не существует:
unix_listener: cannot bind to path /home/fred/.ssh/controlmasters/fred@machine1.example.org:22.EOWsvm5xFt30O6CB: No such file or directory
С опцией -O ssh(1) можно использовать для управления соединением с той же конфигурацией ярлыка. Чтобы отменить все существующие соединения, включая master-соединение, используйте 'exit' вместо 'stop'.
$ ssh -O check machine1
Master running (pid=14379)
$ ssh -O stop machine1
Stop listening request sent
$ ssh -O check machine1
Control socket connect(/Users/Username/.ssh/sockets/machine1): No such file or directoryВ этом примере сначала проверяется статус соединения. Затем master-соединению говорится не принимать дальнейшие запросы мультиплексирования, и наконец мы проверяем снова и убеждаемся, что управляющий сокет больше недоступен.
Также можно вывести информацию о соединении. Заметьте, что ярлык machine1 был определён выше и задаёт путь к сокету ControlPath. Опция -O conninfo соответствует escape-функции ~I. Соединения устанавливаются с client.example.org по адресу 192.0.2.98 к системе по адресу 203.0.113.206 с именем machine1.example.org.
$ ssh -O conninfo machine1
Connection information for client.example.org pid 83539
tcp 192.0.2.98:18952 -> 203.0.113.206:22
kexalgorithm mlkem768x25519-sha256
hostkeyalgorithm ssh-ed25519
cipher chacha20-poly1305@openssh.com
mac <implicit>
compression none
rekey limit none interval none
traffic 112 pkts 466 blks 6.4KB in, 94 pkts 329 blks 5.8KB outАналогично, опция -O channels отображает, какие каналы открыты в этом мультиплексированном сеансе. Однако у неё нет соответствующей escape-последовательности.
$ ssh -O channels machine1
The following connections are open:
#1 mux-control (t16 [mux-control] nr0 m0 i0/0 o0/0 e[closed]/0 fd 5/5/-1 sock 5 cc -1 c2 io 0x01/0x00 I)
#2 client-session (t4 [session] r0 nm0 i0/0 o0/0 e[write]/0 fd 6/7/8 sock -1 cc 1 nc0 io 0x01/0x00 RTI)
#3 mux-control (t16 [mux-control] nr0 m0 i0/0 o0/0 e[closed]/0 fd 9/9/-1 sock 9 cc -1 c4 io 0x01/0x00 I)
#4 client-session (t4 [session] r1 nm0 i0/0 o0/0 e[write]/0 fd 10/11/12 sock -1 cc 3 nc0 io 0x01/0x00 RTI)
#5 mux-control (t16 [mux-control] nr0 m0 i0/0 o0/0 e[closed]/0 fd 13/13/-1 sock 13 cc -1 nc0 io 0x01/0x01 I)Этот пример показывает два SSH-соединения через одно сетевое соединение: одно master-соединение плюс одно дочернее.
Мультиплексированным сеансам нужен управляющий master (control master), к которому подключаться. Параметры выполнения -M и -S соответствуют ControlMaster и ControlPath. Так что сначала устанавливается начальное master-соединение с использованием -M вместе с путём к управляющему сокету через -S.
$ ssh -M -S /home/fred/.ssh/controlmasters/fred@server.example.org:22 server.example.orgЗатем последующие мультиплексированные соединения выполняются в других терминалах. Они используют ControlPath или -S, чтобы указать на управляющий сокет.
$ ssh -S /home/fred/.ssh/controlmasters/fred@server.example.org:22 server.example.orgОбратите внимание, что управляющий сокет назван «fred@server.example.org:22», или %r@%h:%p, в попытке сделать имя уникальным. Комбинация %r, %h и %p означает удалённое имя пользователя, удалённый хост и порт удалённого хоста. Управляющим сокетам следует давать уникальные имена.
Несколько опций -M переводят ssh(1) в master-режим с требованием подтверждения перед приёмом подчинённых (slave) соединений. Это то же самое, что ControlMaster=ask. Оба требуют X для запроса подтверждения.
Вот master-соединение для хоста server.example.org с запросом подтверждения новых мультиплексированных сеансов:
$ ssh -MM -S ~/.ssh/controlmasters/%r@%h:%p server.example.orgА вот последующее мультиплексированное соединение:
$ ssh -S ~/.ssh/controlmasters/%r@%h:%p server.example.orgСтатус управляющего master-соединения можно запросить с помощью -O check — он сообщит, запущено ли оно.
$ ssh -O check -S ~/.ssh/controlmasters/%r@%h:%p server.example.orgЕсли управляющий сеанс был остановлен, запрос вернёт ошибку «No such file or directory», даже если мультиплексированные сеансы ещё выполняются, потому что сокета больше нет.
Либо вместо указания -M и -S как опций времени выполнения, конфигурационные опции можно прописать полностью с помощью -o — так проще переносить их в файл конфигурации клиента, когда они подобраны.
Способ ниже задаёт конфигурационные опции как параметры выполнения, сначала запуская управляющего master.
$ ssh -o "ControlMaster=yes" -o "ControlPath=/home/fred/.ssh/controlmasters/%r@%h:%p" server.example.orgЗатем последующие сеансы подключаются к управляющему master через сокет в конце управляющего пути.
$ ssh -o "ControlPath=/home/fred/.ssh/controlmasters/%r@%h:%p" server.example.orgИ, конечно, всё это можно поместить в ssh_config(5), как показано в предыдущем разделе. Начиная с версии 6.7, комбинация %r@%h:%p и её вариации могут быть заменены на %C, который сам по себе генерирует SHA1-хеш из конкатенации %l%h%p%r.
$ ssh -S ~/.ssh/controlmasters/%C server.example.orgУ этого есть два преимущества. Во-первых, хеш может быть короче своих комбинированных элементов, при этом по-прежнему уникально идентифицируя соединение. Во-вторых, это запутывает информацию о соединении, которая иначе отображалась бы в имени сокета.
Один способ завершить мультиплексированные сеансы — выйти из всех связанных SSH-сеансов, включая управляющего master. Если управляющий master был отправлен в фон с помощью ControlPersist, его нужно будет остановить через -O с 'stop' или 'exit'. Это также требует знания полного пути и имени файла управляющего сокета, использованного при создании master-сеанса, если он не был определён в ярлыке в ssh_config(5).
$ ssh -O stop server1
$ ssh -O stop -S ~/.ssh/controlmasters/%C server1.example.orgКоманда -O stop корректно завершит мультиплексирование. После подачи этой команды управляющий сокет удаляется и новые мультиплексированные сеансы для этого master не принимаются. Существующие соединения могут продолжать работать, и master-соединение будет существовать, пока не закроется последнее мультиплексированное соединение.
Напротив, команда -O exit удаляет управляющий сокет и немедленно завершает все существующие соединения.
Опять же, директива ControlPersist может быть установлена на таймаут после заданного периода неиспользования. Интервал времени записывается в формате времени, приведённом в sshd_config(5), либо по умолчанию в секундах, если единицы не указаны. Это приведёт к автоматическому закрытию master-соединения, если у него нет клиентских соединений указанное время.
Host server1
HostName server1.example.org
ControlPath ~/.ssh/controlmasters/%C
ControlMaster yes
ControlPersist 2h
Пример выше позволяет управляющему master завершиться по таймауту после 2 часов бездействия. С постоянными управляющими сокетами нужно быть осторожным. Пользователь, который может читать и писать в управляющий сокет, может устанавливать новые соединения без дополнительной аутентификации.
Значения для настройки мультиплексирования сеансов можно задать либо в пользовательском ssh_config(5), либо в глобальном /etc/ssh/ssh_config, либо с помощью параметров при запуске из шелла или скрипта. ControlMaster можно переопределить в аргументах времени выполнения, чтобы переиспользовать существующий master, когда ControlMaster установлен в 'yes', явно установив его в 'no':
$ ssh -o "ControlMaster=no" server.example.orgControlMaster принимает пять различных значений: 'no', 'yes', 'ask', 'auto' и 'autoask'.
- 'no' — значение по умолчанию. Новые сеансы не будут пытаться подключиться к установленному master-сеансу, но дополнительные сеансы всё же могут мультиплексироваться, явно подключившись к существующему сокету.
- 'yes' создаёт новый master-сеанс каждый раз, если явно не переопределено. Новый master-сеанс будет слушать соединения.
- 'ask' создаёт новый master каждый раз, если не переопределено, который слушает соединения. При переопределении ssh-askpass(1) покажет в X сообщение с просьбой к владельцу master-сеанса одобрить или отклонить запрос. Если запрос отклонён, создаваемый сеанс откатывается к обычному автономному сеансу.
- 'auto' создаёт master-сеанс автоматически, но если master-сеанс уже доступен, последующие сеансы автоматически мультиплексируются.
- 'autoask' автоматически предполагает, что если master-сеанс существует, последующие сеансы должны мультиплексироваться, но сначала спрашивает разрешения перед добавлением сеанса.
Отказанные соединения записываются в лог master-сеанса.
Host *
ControlMaster ask
ControlPath может быть фиксированной строкой или включать любые из нескольких предопределённых переменных, описанных в секции TOKENS ssh_config(5). %L — первый компонент локального имени хоста, %l — полное локальное имя хоста. %h — целевое имя хоста, %n — исходное целевое имя хоста, %p — порт назначения на удалённом сервере. %r — удалённое имя пользователя, %u — пользователь, запускающий ssh(1). Или их можно скомбинировать как %C — SHA1-хеш, полученный из %l%h%p%r.
Host *
ControlMaster ask
ControlPath ~/.ssh/controlmasters/%C
ControlPersist принимает 'yes', 'no' или интервал времени. Если задан интервал времени, по умолчанию он в секундах. Единицы могут продлевать время до минут, часов, дней, недель или комбинации. Если 'yes' — master-соединение остаётся в фоне неограниченно.
Host *
ControlMaster ask
ControlPath ~/.ssh/controlmasters/%C
ControlPersist 10m
Можно запросить переадресацию портов без установления новых соединений. Здесь мы переадресуем порт 8080 на локальном хосте на порт 80 на удалённом хосте с помощью -L:
$ ssh -O forward -L 8080:localhost:80 -S ~/.ssh/controlmasters/fred@server.example.org:22 server.example.orgТо же самое можно сделать для удалённой переадресации с помощью -R. Escape-последовательность ~C недоступна для мультиплексированных сеансов, поэтому -O forward — единственный способ добавить переадресацию портов на лету.
Переадресацию портов можно отменить, не закрывая сеансы, с помощью -O cancel.
$ ssh -O cancel -L 8080:localhost:80 -S ~/.ssh/controlmasters/fred@server.example.org:22 server.example.orgДля отмены используется точно тот же синтаксис, что и для переадресации. Однако сейчас нет способа посмотреть, какие порты переадресуются и как.
Никогда не используйте общедоступный каталог для сокетов управляющего пути. Помещайте эти сокеты в какой-то другой каталог, к которому имеет доступ только ваш аккаунт. Например, ~/.ssh/socket/ — гораздо более безопасный выбор, а /tmp/ — плохой.
В случаях, когда полезно иметь постоянное соединение, всегда можно комбинировать мультиплексирование с другими опциями, такими как -f или -N, чтобы управляющий master ушёл в фон после подключения и не загружал шелл.
В sshd_config(5) директива MaxSessions задаёт максимальное число открытых сеансов на одно сетевое соединение. Она используется при мультиплексировании SSH-сеансов через единственное TCP-соединение. Установка MaxSessions в 1 отключает мультиплексирование, а установка в 0 полностью отключает login/shell/subsystem-сеансы. По умолчанию — 10. Директива MaxSessions также может быть установлена внутри условного блока Match, чтобы разные настройки были доступны для разных условий.
Каталог, используемый для хранения управляющих сокетов, должен находиться на файловой системе, которая реально позволяет создавать сокеты. AFS — один из примеров, где нельзя, как и некоторые реализации HFS+. При попытке создать сокет на файловой системе, которая этого не позволяет, возникнет ошибка наподобие следующей:
$ ssh -M -S /home/fred/.ssh/mux 192.0.2.57
muxserver_listen: link mux listener /home/fred/.ssh/mux.vjfeIFFzHnhgHoOV => /home/fred/.ssh/mux: Operation not permittedПохожая проблема наблюдалась при попытке создать Unix-сокеты на файловых системах OverlayFS, часто используемых с Docker, в ядрах до Linux 4.7[2].
Если файловую систему нельзя перенастроить, чтобы она разрешала сокеты, единственный другой вариант — поместить сокет управляющего пути в другом месте, на файловой системе, поддерживающей создание сокетов.
Можно сделать грубые измерения, показывающие разницу между мультиплексированными и одноразовыми соединениями, как показано в таблицах и примерах выше.
Таблицы 1 и 2 выше используют вывод netstat(8) и awk(1), чтобы показать число TCP-соединений, соответствующее числу SSH-соединений.
#!/bin/sh
netstat -nt | awk 'NR == 2'
ssh -f server.example.org sleep 60
echo # one connection
netstat -nt | awk '$5 ~ /:22$/'
ssh -f server.example.org sleep 60
echo # two connections
netstat -nt | awk '$5 ~ /:22$/'
ssh -f server.example.org sleep 60
echo # three connections
netstat -nt | awk '$5 ~ /:22$/'
echo Table 1И:
#!/bin/sh
netstat -nt | awk 'NR == 2'
ssh -f -M -S ~/.ssh/demo server.example.org sleep 60
echo # one connection
netstat -nt | awk '$5 ~ /:22$/'
ssh -f -S ~/.ssh/demo server.example.org sleep 60 &
echo # two connections
netstat -nt | awk '$5 ~ /:22$/'
ssh -f -S ~/.ssh/demo server.example.org sleep 60 &
echo # three connections
netstat -nt | awk '$5 ~ /:22$/'
echo Table 2Период sleep(1) можно увеличить, если нужна большая задержка. Пока соединения активны, можно также посмотреть на сервере с помощью ps(1), например ps uwx, чтобы увидеть процессы как средство убедиться, что действительно устанавливается несколько соединений.
Измерение времени отклика требует сначала настроить ключи с агентом, чтобы агент обрабатывал аутентификацию и устранял её как источник задержки. При необходимости смотрите раздел о ключах. Все тесты времени отклика ниже зависят от использования ключей для аутентификации.
Для одноразового соединения просто добавьте time(1), чтобы проверить, сколько занимает доступ.
$ time ssh -i ~/.ssh/rsakey server.example.org trueДля мультиплексированного соединения сначала нужно установить управляющий master-сеанс. Затем последующие соединения покажут прирост скорости.
$ ssh -f -M -S ~/.ssh/demo -i ~/.ssh/rsakey server.example.org
$ time ssh -S ~/.ssh/demo -i ~/.ssh/rsakey server.example.org true
$ time ssh -S ~/.ssh/demo -i ~/.ssh/rsakey server.example.org trueЭти времена отклика приблизительны, но разница между одноразовыми и мультиплексированными соединениями достаточно велика, чтобы её увидеть.
Вероятно, вы также захотите держать свои сеансы открытыми при бездействии. Это можно настроить с помощью опций ServerAliveInterval и ServerAliveCountMax, детали см. в ssh_config(5).
Другой вид мультиплексирования — когда по одному порту передаётся более одного протокола. sslh делает именно это с SSL и SSH. Он определяет, какое соединение приходит, и перенаправляет его в соответствующий сервис. Это позволяет серверу принимать и HTTPS, и SSH по одному порту, что в некоторых случаях даёт возможность подключаться к серверу из-за ограничивающих межсетевых экранов. Это не скрывает SSH. Даже быстрый скан на SSH на слушающем порту, скажем с scanssh(1), покажет, что он там есть. Учтите, что этот метод работает только с простыми пакетными фильтрами, такими как PF или nftables(8) и их фронтендами firewalld и UFW, которые фильтруют по порту назначения, и не обманет протокольный анализ и фильтры прикладного уровня, такие как Zorp, которые фильтруют по фактически используемому протоколу. Вот как sslh(8) можно установить за четыре шага:
- Сначала установите ваш веб-сервер и настройте его принимать HTTPS-запросы. Убедитесь, что он слушает HTTPS только на localhost. В этом может помочь нестандартный порт, скажем 2443, вместо 443.
- Затем настройте ваш SSH-сервер принимать соединения на localhost на порту 22. На самом деле это может быть любой порт, но 22 — стандарт для SSH.
- Далее создайте непривилегированного пользователя для запуска sslh(8). В примере ниже пользователь 'sslh' — непривилегированная учётная запись.
- Наконец, установите и запустите sslh(8), чтобы он слушал на порту 443 и перенаправлял HTTPS и SSH на соответствующие порты локального хоста. Подставьте внешний IP-адрес вашей машины. Фактическое имя исполняемого файла и путь могут отличаться от системы к системе:
$ /usr/local/sbin/sslh-fork -u sslh -p xx.yy.zz.aa:443 --tls 127.0.0.1:2443 --ssh 127.0.0.1:22Другой вариант — использовать конфигурационный файл с sslh(8) и не передавать параметры при запуске. В пакете при установке должен быть хотя бы один пример конфигурационного файла, basic.cfg или example.cfg. Готовый конфигурационный файл должен выглядеть примерно так:
user: "sslh";
listen: ( { host: "xx.yy.zz.aa"; port: "443" } );
on-timeout: "ssl";
protocols:
(
{ name: "ssh"; host: "localhost"; port: "22"; probe: "builtin"; },
{ name: "ssl"; host: "localhost"; port: "2443"; probe: "builtin"; }
);
Обратите внимание на кавычки, запятые и точки с запятой.
Если старая версия SSH используется вместе с ныне устаревшими TCP Wrappers, как описано в hosts_access(5), опция service: работает, чтобы предоставить имя нужного им сервиса.
{ name: "ssh"; service: "ssh"; host: "localhost"; port: "22"; probe: "builtin"; },
Если TCP Wrappers не используются — что, скорее всего, так, — service: не нужно.
Параметры времени выполнения переопределяют любые настройки конфигурационного файла, которые могут быть на месте. sslh(8) поддерживает протоколы HTTP, SSL, SSH, OpenVPN, tinc и XMPP из коробки. Но на самом деле можно использовать любой протокол, который можно идентифицировать сопоставлением с регулярным выражением. Есть два варианта sslh: форковая версия (sslh или sslh-fork) и однопоточная версия (sslh-select). Подробнее см. сайт проекта: https://www.rutschle.net/tech/sslh/README.html
- ↑ "OpenSSH 3.9 Release Notes". OpenSSH. 2004-08-18. Проверено 2018-12-16.
- ↑ Szeredi, Miklos (2016-06-16). ["GIT PULL overlayfs fixes for 4.7-rc3"]. https://lkml.org/lkml/2016/6/16/87. Проверено 2018-10-05.