Ориентация главы

Серия SSH завершается — детали предыдущих шести глав собираются здесь. ssh_client компонует ядро/потоковую сессию/динамические каналы поверх предоставленного вызывающим xnetstream: не создаёт скрытый Engine, не блокирует Worker, не преаллоцирует каналы или буферы сообщений, не вводит вторую машину состояний (четыре «не» — SSH-редакция «нет второй реализации»). Жизненный цикл соединения: после ClientInit таблица событий и указатель данных уходят прямо в xrtNetStreamConnect (или, на уже построенном потоке, ClientAttach) — с открытием TCP ReadyTimeout (умолчание 30 секунд) единообразно ограничивает обмен версиями → KEX → доверие хоста → аутентификацию; событие Ready — единственная точка публикации SSH-пригодности. Три точки взаимодействия: доверие хоста (по умолчанию отказ — после DEFER событие HostKey отдаёт право решения), аутентификация (событие Authenticate при NEED_MORE у провайдера), неизвестные расширения протокола (callback Packet сохраняет полную способность обработки). Слой удобства Dial (DNS/Happy-Eyeballs/тайм-аут/отмена) обрезается независимо — прокси и собственные транспорты идут через Attach без ограничений.

129.1引入

Трудность «слоя финальной сборки» — закрытие ответственностей: всё состояние соединения (версия/KEX/аутентификация/каналы/переадресация) по главам деталей имеет своих хозяев; клиент нанизывает их в одно событийное целое — но поведенческие границы каждого слоя неизменны (ядро остаётся тем же ядром, окно тем же окном). Клей закрытия — таблица событий (конфигурационная структура events клиента): Ready/Error/Close/HostKey/Authenticate/Data/Drain/Global/Packet — вся поверхность взаимодействия приложения; изоморфна событийной цепочке HTTP-сервера главы 104.

Двухслойное сцепление противодавления стоит посмотреть сперва: при AGAIN TCP-очереди полный шифрованный пакет остаётся в единственном владении транспорта, а новые входы SSH приостанавливаются; callback-и LowWater/Drain сперва повторяют внутренний пакет, и лишь после фиксации внутренней транзакции и действительного осушения TCP-очереди публикуется Drain клиента — новые сообщения, отправленные приложением внутри Drain, не обгонят прежде удержанный пакет (гарантия порядка — корень корректности туннелей переадресации). OOM так же точен: провал внутреннего резервирования DATA ставит соединение на HOLD; после освобождения памяти — PacketRetry — «OOM не фиксирует полупакет».

129.2概念

Соединение и бюджет тайм-аутов

1СборкаConfigInit + ClientInit -> таблица событий уходит в NetStreamConnect (или Attach на готовом потоке)
2TCP открытпривязка Worker (scratch каналов/управляющих сообщений подключается к пулу буферов только теперь)
3ReadyTimeout (умолчание 30с, миллисекунды) покрываетобмен версиями -> KEX -> доверие хоста -> аутентификацию
4ReadySSH пригоден - стартовая точка каналов/переадресации
5Ошибка/тайм-аутструктурированная ошибка (XSSH_ERROR_TIMEOUT/XERR_TIMEOUT/домен) достигает Error/Close/всех ожидающих Future одновременно
6Сторона TCPдедлайн DNS/установления - в xnetdialconfig.Timeout (управляется независимо)

Три точки взаимодействия

  • Доверие хоста: по умолчанию отказ — после возврата HostKey ядром DEFER приложение получает момент решения через событие HostKey и отвечает xrtSshClientHostKeyAccept/Reject (двухшаговое доверие глав 122/123 в клиентской интерактивной форме — событие не синхронное суждение внутри callback: можно сверить хранилище, спросить пользователя, проверить по внешнему каналу и решить асинхронно).
  • Аутентификация: возврат провайдером NEED_MORE вызывает событие Authenticate — когда учётные данные готовы (спросили пользователя/взяли у агента), продолжает xrtSshClientContinue (асинхронный уст методов главы 124).
  • Аварийный выход Packet: callback Packet исполняется до фиксации низлежащей read-транзакции — полная способность обработки неизвестных расширений протокола и собственных типов каналов (разбор переадресации главы 126 висит ровно здесь) — клиент неизвестных сообщений не глотает.

Пути данных и управляющих сообщений

xrtSshClientSend — быстрый путь нагрузки: добавляется только SSH-кодирование пакета + один перенос владения в TCP-очередь (ноль транзитных копий). xrtSshClientBuild даёт растущее с нуля, переиспользуемое, с жёстким потолком непрерывное scratch для мутирующих управляющих сообщений (буфер построения собственных сообщений). Стандартные DATA/stderr сперва входят в staging канального I/O, после фиксации стреляет событие Data (приложение проверяет без копий или читает явно). Глобальные ответы: динамический ограниченный FIFO GlobalReplies/ReplyReserve в паре с событием Global (токен-корреляция — уже видели в главе 125).

Каналы и финал жизненного цикла

xrtSshClientChannelOpen — базовый вход для собственных типов каналов: вызывающий пишет поля типа, клиент ведает динамической нумерацией/окнами/корреляцией ответов/фиксацией/откатом — классический session и helper direct-tcpip построены на нём (вторая набор-состояний каналов не ведётся). ChannelFlush строит из очереди отправки канала куски, совместно ограниченные «удалённым окном + max-packet + TCP-противодавлением» — слияние трёх бюджетных слоёв. Финал: OwnsChannel проверяет принадлежность (не отдать чужое соединение в очередь отправки); Drain/Abort идут по семантике главы 110.

129.3示例

Первая полная программа: заявление об отсутствии скрытого рантайма

Программа ниже из examples/client — форма ресурсов минимального клиента:

extlibs/xssh/examples/client/main.c源码 ↗
#include <stdio.h>

#include <xssh.h>



/* 展示客户端由外部 Engine 和 Stream 驱动,不创建隐藏运行时。 */
int main(void)
{
	xsshclientconfig Config;
	xsshclient Client;

	if ( !xrtSshClientConfigInit(&Config) ||
		!xrtSshClientInit(&Client, &Config, NULL, NULL) ) {
		return 1;
	}
	printf("state=%d channels=%zu\n", (int)xrtSshClientState(&Client),
		Config.Channels.MaxChannels);
	return xrtSshClientClear(&Client) ? 0 : 1;
}
$ gcc -O1 -DXRT_MODULE_ALL -DXSSH_MODULE_ALL -I single -I single/extlibs -include xrt.h -include xssh.h impl.c extlibs/xssh/examples/client/main.c -lws2_32 -lbcrypt -ladvapi32 -liphlpapi(输出初始状态与通道上限的自检结果)

Что только что произошло. (1) xrtSshClientConfigInit + xrtSshClientInit(&Client, &Config, NULL, NULL) — клиент на стеке; печатаются xrtSshClientState (начальное состояние) и Channels.MaxChannels (умолчание 1024). (2) Заголовочный комментарий — проектный манифест: «управляется внешним Engine и Stream, скрытый рантайм не создаёт» — транспортное ядро главы 121 «без буферов», KEX главы 122 «без socket», здесь «без Engine» — все семь слоёв с нулём скрытого; прозрачность сборки есть накопление прозрачности деталей. (3) Финал — ClientClear: очистка неподключённого клиента без обузы. Реальная сборка (таблица событий в NetStreamConnect) — в образцах client_session/client_core.

Вторая полная программа: конфигурация слоя Dial

Вторая программа из examples/client_dial — бюджеты перед соединением:

extlibs/xssh/examples/client_dial/main.c源码 ↗
#include <stdio.h>

#include <xssh.h>



/* 展示 SSH Dial 与 TCP Dial 共享同一套可裁剪连接策略。 */
int main(void)
{
	xnetdialconfig DialConfig;
	xsshclientconfig ClientConfig;
	xsshclient Client;

	xrtNetDialConfigInit(&DialConfig);
	DialConfig.Timeout = 10000;
	DialConfig.FallbackDelay = 250;
	if ( !xrtSshClientConfigInit(&ClientConfig) ||
		!xrtSshClientInit(&Client, &ClientConfig, NULL, NULL) ) {
		return 1;
	}
	printf("dial-timeout=%llu fallback=%llu state=%d\n",
		(unsigned long long)DialConfig.Timeout,
		(unsigned long long)DialConfig.FallbackDelay,
		(int)xrtSshClientState(&Client));
	return xrtSshClientClear(&Client) ? 0 : 1;
}
$ gcc -O1 -DXRT_MODULE_ALL -DXSSH_MODULE_ALL -I single -I single/extlibs -include xrt.h -include xssh.h impl.c extlibs/xssh/examples/client_dial/main.c -lws2_32 -lbcrypt -ladvapi32 -liphlpapi(输出拨号超时、回退延迟与客户端状态的配置自检结果)

Что только что произошло. (1) Timeout=10s/FallbackDelay=250ms у xrtNetDialConfigInit — бюджет стороны TCP (DNS/установление/откат Happy-Eyeballs), двухуровневая независимость от ReadyTimeout (сторона SSH): TCP 10 секунд + рукопожатие и аутентификация 30 секунд; суммарный бюджет ясен и настраиваем (два рода дедлайнов главы 99 в SSH-редакции). (2) Callback завершения Dial означает лишь финал TCP — пригодность SSH по-прежнему публикует только Ready (семантика callback не подменяется). (3) Прокси/собственные транспорты Dial не ограничены — путь ClientAttach всегда открыт (стык комбинации «прокси-туннель главы 70 + SSH»). Семья: client_core (сборка ядра + аутентификации паролем), client_session (поток сессии), client_future (форма Future), client_auth_ed25519 (аутентификация открытым ключом), client_pty (согласование терминала).

129.4契约

  • Четыре «не»: без скрытого Engine / без блокировки Worker / без преаллокации каналов и буферов сообщений / без второй машины состояний.
  • Сборка соединения: таблица событий уходит в NetStreamConnect; готовый поток делает ClientAttach на Worker-владельце; scratch привязывается к пулу буферов Worker после открытия TCP.
  • Двухуровневый тайм-аут: ReadyTimeout (умолчание 30с, ноль отключает) ведает версией→KEX→доверием→аутентификацией; Timeout у Dial ведает DNS/установлением — независимо.
  • Ready единственно: состояние пригодности SSH публикуется только событием Ready; callback завершения Dial ≠ SSH готов.
  • Хост по умолчанию на отказ: DEFER → событие HostKey → Accept/Reject решаются асинхронно.
  • Асинхронный уст аутентификации: NEED_MORE → событие Authenticate → Continue.
  • Аварийный выход Packet: исполняется до фиксации read-транзакции; полная способность для неизвестных расширений и собственных каналов; клиент неизвестных сообщений не глотает.
  • Пути отправки: Send = быстрый путь без транзита; Build = переиспользуемый scratch; слияние трёх бюджетов в ChannelFlush.
  • Порядок Drain: внутренние пакеты повторяются первыми; публикация лишь после фиксации транзакции и осушения очереди — новые сообщения не обгоняют.
  • Точность OOM: полупакет не фиксируется; провал резервирования — HOLD; после освобождения PacketRetry.
  • Глобальные ответы: динамический ограниченный FIFO + токен-коррелированное событие Global.

Типичные ошибки

Ловушка 1: завершение Dial принято за готовность SSH:Ловушка 1: завершение Dial принято за готовность SSH

Симптом: в callback Dial сразу открывается канал — ошибка или падение; SSH ещё рукопожатается.

Причина: callback Dial означает лишь, что TCP поднялся; версия/KEX/доверие/аутентификация дальше — Ready и есть сигнал готовности.

错误写法
on_dial_done(...) {
	xrtSshClientChannelOpen(Client, "session", ...);  /* посреди рукопожатия: ошибка */
}
正确写法
on_dial_done(...) { /* это только TCP - ждать Ready */ }
on_ready(...) {
	/* открывать каналы/слать запросы переадресации только здесь */
	xrtSshClientChannelOpen(Client, "session", ...);
}
Ловушка 2: в событии HostKey забыли ответить (висит до тайм-аута):Ловушка 2: в событии HostKey забыли ответить (висит до тайм-аута)

Симптом: соединение застревает и в конце ReadyTimeout — событие никто не Accept/Reject-нул.

Причина: событие HostKey — точка решения, требующая ответа (отказ по умолчанию — защита от «не настроена политика доверия», а не «событие можно игнорировать»). После сверки хранилища/вопроса пользователю обязательно ответьте.

错误写法
on_host_key(...) {
	audit_log(Key);   /* записано, но не отвечено: соединение висит до тайм-аута */
}
正确写法
on_host_key(...) {
	audit_log(Key);
	if ( known_hosts_check(Key) == TRUST ) {
		xrtSshClientHostKeyAccept(Client);
	} else {
		xrtSshClientHostKeyReject(Client);
	}
}
Ловушка 3: бездумное употребление дефолтов бюджета каналов вокруг READY:Ловушка 3: бездумное употребление дефолтов бюджета каналов вокруг READY

Симптом: при целях в десятки тысяч конкурентности потолок 1024 каналов незаметно перехватывает — половина соединений не может открыть новые каналы; или наоборот — на встраиваемых целях нетронутые бюджеты давят память.

Причина: дефолты вроде Channels.MaxChannels — универсальные значения (контракт набора главы 125: «настраивать под нагрузку, на безграничный рост не полагаться») — параметры развёртывания не частное дело библиотеки.

错误写法
xrtSshClientConfigInit(&Config);   /* всё по умолчанию в продакшн */
正确写法
xrtSshClientConfigInit(&Config);
Config.Channels.MaxChannels = my_model.channels;
Config.Channels.ReceiveWindow = my_model.window;
Config.GlobalReplyLimit = my_model.global_tokens;
/* объявить явно по модели нагрузки - бюджет есть контракт развёртывания */

Практические задания

Базовое: наблюдение машины состояний

Против реального SSH-сервера (локальный sshd или контейнер) напечатать последовательность событий целого соединения: Dial→(TCP)→версия→KEX→HostKey→аутентификация→Ready→канал→Close. Критерии приёмки: последовательность сходится с диаграммой этой главы; двухуровневые тайм-ауты срабатывают каждый независимо (эксперименты с короткими значениями).

Продвинутое: асинхронное решение о доверии

В событии HostKey запустить асинхронную проверку (имитация внешней сверки с задержкой 200 мс) и затем Accept — проверка способности события решать асинхронно. Контраст с путём немедленного синхронного отказа. Критерии приёмки: тайминг Ready/Close обоих путей верен; соединение живёт на протяжении решения (без тайм-аута).

Вызов: полноценный туннельный инструмент

Упражнение финальной сборки: Dial + аутентификация паролем + HostKey (интеграция known_hosts) + локальная переадресация (форма -L) + 3 конкурентных канала + грациозный Drain — пригодный мини ssh -L. Критерии приёмки: HTTP-запросы успешно ходят через туннель туда-обратно; три канала конкурентно без помех; Ctrl-C запускает Drain, вся цепочка выходит чисто (сверка бюджетов и ссылок).

Итоги

ТемаКратко
Четыре «не»без скрытого Engine / без блокировки Worker / ноль преаллокаций / без второй машины состояний
Сборкатаблица событий в NetStreamConnect или Attach на Worker-владельце
Двухуровневый тайм-аутReadyTimeout (весь путь SSH, умолчание 30с) независимо от Timeout у Dial
Ready единственнопригодность SSH публикует только Ready — завершение Dial ≠ готовность
Доверие хостаотказ по умолчанию; DEFER → событие HostKey → Accept/Reject с обязательным ответом
Аутентификация асинхронноNEED_MORE → Authenticate → Continue
Аварийный выход Packetисполняется до фиксации; неизвестные сообщения не глотаются — способность собственных каналов
ОтправкаSend без транзита / Build scratch / Flush сливает три бюджета
Порядок Drainвнутренние пакеты первыми; публикация при осушении — новые сообщения не обгоняют
Глобальные ответыограниченный FIFO + токен для события Global
Карта сборки117 транспорт → 118 KEX → 119 доверие → 120 аутентификация → 121 каналы → 122 переадресация → 123 клиент

📖 Справочники API в этой главе:xssh-ssh_client (31 записей)xssh-ssh_client_core (9 записей)xssh-ssh_session_stream (14 записей)

Полные сигнатуры, ограничения параметров, возвращаемые значения и ошибки