DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
ssh_channel.h
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file ssh_channel.h
6 * @brief SSH connection protocol - multiplexed "session" channels (RFC 4254).
7 *
8 * After authentication the client opens one or more channels; this layer confirms
9 * each, accepts a shell/exec/pty request, and exchanges SSH_MSG_CHANNEL_DATA.
10 * Inbound channel data is surfaced to the application as a raw byte stream (no PTY
11 * emulation), tagged with its channel id; outbound data is framed back to the
12 * client on a given channel. Flow control follows RFC 4254 §5.2 (per-channel
13 * window tracking + WINDOW_ADJUST).
14 *
15 * Up to DETWS_SSH_MAX_CHANNELS channels per connection are multiplexed, each with
16 * its own id, peer id, and windows. The default (1) is the original single-channel
17 * control/console link. Every inbound message is routed to its channel by the
18 * recipient channel id it carries.
19 *
20 * @author Douglas Quigg (dstroy0)
21 * @date 2026
22 */
23
24#ifndef DETERMINISTICESPASYNCWEBSERVER_SSH_CHANNEL_H
25#define DETERMINISTICESPASYNCWEBSERVER_SSH_CHANNEL_H
26
27#include "ServerConfig.h"
28#include <stddef.h>
29#include <stdint.h>
30
31/** @brief Channel type (RFC 4254). */
32enum class SshChanType : uint8_t
33{
34 SSH_CHAN_SESSION = 0, ///< "session" - shell / exec / data
35 SSH_CHAN_DIRECT_TCPIP = 1, ///< "direct-tcpip" - client-initiated TCP forward (ssh -L)
36 SSH_CHAN_FORWARDED_TCPIP = 2 ///< "forwarded-tcpip" - server-initiated TCP forward (ssh -R)
37};
38
39/** @brief Per-connection channel state. */
41{
42 bool open; ///< True once the channel is confirmed open both ways.
43 bool pending; ///< True for a server-initiated channel we opened, awaiting the client's confirmation.
44 SshChanType type; ///< session, direct-tcpip, or forwarded-tcpip.
45 uint32_t local_id; ///< Our channel id (== slot index).
46 uint32_t peer_id; ///< Client's channel id.
47 uint32_t local_window; ///< Bytes we may still receive before WINDOW_ADJUST.
48 uint32_t peer_window; ///< Bytes we may still send to the client.
49 uint32_t peer_max_pkt; ///< Client's maximum packet size.
50};
51
52/** @brief Channel pool: DETWS_SSH_MAX_CHANNELS channels per SSH connection (BSS).
53 * Owned by this layer; src/ code routes through the functions below, never the
54 * array (tests inspect it white-box). Index: [connection slot][channel slot]. */
56
57/** @brief Application callback for inbound channel data (raw bytes), tagged with
58 * the channel id it arrived on. */
59typedef void (*SshChannelDataCb)(uint8_t slot, uint32_t channel, const uint8_t *data, size_t len);
60
61/** @brief Install the inbound-data callback (session channels). */
63
64/**
65 * @brief "direct-tcpip" forward request: a client asked the server to open a TCP
66 * connection to @p host : @p port (ssh -L). The forwarding owner (which
67 * does the actual TCP I/O - this codec does not) decides whether to allow
68 * it; @p host is not NUL-terminated (@p host_len bytes).
69 * @return 0 to accept (the channel is opened and confirmed), < 0 to refuse
70 * (CHANNEL_OPEN_FAILURE, administratively prohibited / connect failed).
71 *
72 * If no callback is installed, all forward requests are refused - so forwarding is
73 * opt-in (no open relay by default).
74 */
75typedef int (*SshForwardOpenCb)(uint8_t slot, uint32_t channel, const char *host, size_t host_len, uint16_t port);
76
77/** @brief Inbound data on a direct-tcpip channel (the owner writes it to the
78 * forwarded TCP socket). Kept separate from the session data callback. */
79typedef void (*SshForwardDataCb)(uint8_t slot, uint32_t channel, const uint8_t *data, size_t len);
80
81/** @brief Install the direct-tcpip forward open-policy callback (opt-in). */
83
84/** @brief Install the direct-tcpip forward inbound-data callback. */
86
87/**
88 * @brief "tcpip-forward" remote-forward request (ssh -R): the client asks the server
89 * to listen on @p bind_addr : @p bind_port and open a channel back for each
90 * accepted connection (RFC 4254 §7.1). @p bind_addr is @p addr_len bytes (not
91 * NUL-terminated). The forwarding owner (which allocates the real listener -
92 * this codec does no I/O) decides.
93 * @return the bound port on success (echo @p bind_port, or the port the owner picked
94 * when @p bind_port == 0), or < 0 to refuse. If no callback is installed every
95 * request is refused, so remote forwarding is opt-in (no listener is opened).
96 */
97typedef int (*SshRemoteForwardOpenCb)(uint8_t slot, const char *bind_addr, size_t addr_len, uint16_t bind_port);
98
99/** @brief "cancel-tcpip-forward" request (RFC 4254 §7.1): drop a remote forward.
100 * @return 0 if a matching forward was cancelled, < 0 if none / unsupported. */
101typedef int (*SshRemoteForwardCancelCb)(uint8_t slot, const char *bind_addr, size_t addr_len, uint16_t bind_port);
102
103/** @brief Install the remote-forward (ssh -R) open-policy callback (opt-in). */
105
106/** @brief Install the remote-forward (ssh -R) cancel callback (opt-in). */
108
109/**
110 * @brief Result of the client's reply to a server-initiated forwarded-tcpip channel:
111 * @p ok = true on CHANNEL_OPEN_CONFIRMATION (the bridge may start), false on
112 * CHANNEL_OPEN_FAILURE (the owner tears the bridge down). @p channel is the
113 * local id returned by ssh_channel_open_forwarded().
114 */
115typedef void (*SshForwardConfirmCb)(uint8_t slot, uint32_t channel, bool ok);
116
117/** @brief Install the forwarded-tcpip open-confirmation callback (opt-in, ssh -R). */
119
120/**
121 * @brief Open a server-initiated "forwarded-tcpip" channel (RFC 4254 §7.2, ssh -R).
122 *
123 * Allocates a local channel on connection @p i (state = pending, awaiting the
124 * client's confirmation) and builds the SSH_MSG_CHANNEL_OPEN in @p out.
125 * @p conn_addr / @p conn_port are the forward's bound address/port (the "address
126 * that was connected"); @p orig_addr / @p orig_port are the peer that connected.
127 *
128 * @return the local channel id (>= 0) on success, or -1 (pool full, or @p out too
129 * small). On success the caller emits @p out and, on the eventual confirm,
130 * bridges bytes on the returned channel.
131 */
132int ssh_channel_open_forwarded(uint8_t i, const char *conn_addr, uint16_t conn_port, const char *orig_addr,
133 uint16_t orig_port, uint8_t *out, size_t *out_len, size_t cap);
134
135/**
136 * @brief Handle SSH_MSG_CHANNEL_OPEN_CONFIRMATION for a channel we opened (ssh -R).
137 *
138 * Matches the pending channel by our recipient id, records the peer's channel id /
139 * window / max-packet, and marks it open. Fires the confirm callback (@p ok = true).
140 * @return 0 on success, -1 if malformed or no matching pending channel.
141 */
142int ssh_channel_handle_open_confirm(uint8_t i, const uint8_t *payload, size_t len);
143
144/**
145 * @brief Handle SSH_MSG_CHANNEL_OPEN_FAILURE for a channel we opened (ssh -R).
146 *
147 * Frees the pending channel and fires the confirm callback (@p ok = false).
148 * @return 0 on success, -1 if malformed or no matching pending channel.
149 */
150int ssh_channel_handle_open_failure(uint8_t i, const uint8_t *payload, size_t len);
151
152/**
153 * @brief Handle SSH_MSG_GLOBAL_REQUEST (RFC 4254 §4).
154 *
155 * Parses the request name and want_reply flag. "tcpip-forward" /
156 * "cancel-tcpip-forward" are routed to the remote-forward seam above (accepted only
157 * when a callback is installed); a "tcpip-forward" that bound port 0 gets its
158 * allocated port echoed in the reply (RFC 4254 §7.1). Any other request name is
159 * unrecognized: per §4 it is answered with SSH_MSG_REQUEST_FAILURE when want_reply is
160 * set, and silently ignored otherwise (never SSH_MSG_UNIMPLEMENTED - GLOBAL_REQUEST is
161 * a known message type; only the request name is unknown).
162 *
163 * @return 0 on success (a reply is in @p out with *@p out_len bytes, or *@p out_len is
164 * 0 when no reply is due), -1 if the message is malformed.
165 */
166int ssh_global_request_handle(uint8_t i, const uint8_t *payload, size_t len, uint8_t *out, size_t *out_len, size_t cap);
167
168/** @brief Reset channel state for slot @p i. */
169void ssh_channel_init(uint8_t i);
170
171/**
172 * @brief Handle SSH_MSG_CHANNEL_OPEN and emit CHANNEL_OPEN_CONFIRMATION.
173 *
174 * Accepts a "session" channel; any other type yields CHANNEL_OPEN_FAILURE.
175 * @return 0 if a response was produced, -1 if malformed.
176 */
177int ssh_channel_handle_open(uint8_t i, const uint8_t *payload, size_t len, uint8_t *out, size_t *out_len, size_t cap);
178
179/**
180 * @brief Handle SSH_MSG_CHANNEL_REQUEST.
181 *
182 * "shell", "exec", and "pty-req" are accepted; anything else is refused. When
183 * want_reply is set, CHANNEL_SUCCESS / CHANNEL_FAILURE is written to @p out and
184 * *@p out_len > 0; otherwise *@p out_len is 0.
185 * @return 0 on success, -1 if malformed.
186 */
187int ssh_channel_handle_request(uint8_t i, const uint8_t *payload, size_t len, uint8_t *out, size_t *out_len,
188 size_t cap);
189
190/**
191 * @brief Handle SSH_MSG_CHANNEL_DATA: bounds-check, update the window, and
192 * invoke the data callback. If the local window is exhausted a
193 * CHANNEL_WINDOW_ADJUST is written to @p out (*@p out_len > 0).
194 * @return 0 on success, -1 if malformed or channel not open.
195 */
196int ssh_channel_handle_data(uint8_t i, const uint8_t *payload, size_t len, uint8_t *out, size_t *out_len, size_t cap);
197
198/**
199 * @brief Build an SSH_MSG_CHANNEL_DATA message carrying @p data to the client on
200 * channel @p channel (a local channel id from a prior open).
201 * @return 0 on success, -1 if the channel is closed/unknown, the peer window is
202 * too small, or @p out is too small.
203 */
204int ssh_channel_build_data(uint8_t i, uint32_t channel, const uint8_t *data, size_t len, uint8_t *out, size_t *out_len,
205 size_t cap);
206
207/**
208 * @brief Handle SSH_MSG_CHANNEL_WINDOW_ADJUST (grows the peer window).
209 * @return 0 on success, -1 if malformed.
210 */
211int ssh_channel_handle_window_adjust(uint8_t i, const uint8_t *payload, size_t len);
212
213/**
214 * @brief Build SSH_MSG_CHANNEL_EOF + SSH_MSG_CHANNEL_CLOSE for channel @p channel
215 * and mark it closed.
216 * @return 0 on success, -1 if the channel is closed/unknown or @p out is too small.
217 */
218int ssh_channel_build_close(uint8_t i, uint32_t channel, uint8_t *out, size_t *out_len, size_t cap);
219
220/**
221 * @brief Handle an inbound SSH_MSG_CHANNEL_CLOSE: route to the recipient channel,
222 * reply with EOF + CLOSE, and mark it closed.
223 * @return 0 if a response was produced, -1 if malformed or the channel is unknown.
224 */
225int ssh_channel_handle_close(uint8_t i, const uint8_t *payload, size_t len, uint8_t *out, size_t *out_len, size_t cap);
226
227#endif // DETERMINISTICESPASYNCWEBSERVER_SSH_CHANNEL_H
User-facing configuration for DeterministicESPAsyncWebServer.
#define MAX_SSH_CONNS
Maximum simultaneous SSH connections.
#define DETWS_SSH_MAX_CHANNELS
Maximum concurrent SSH channels per connection (RFC 4254 multiplexing).
void ssh_channel_set_forward_data_cb(SshForwardDataCb cb)
Install the direct-tcpip forward inbound-data callback.
int(* SshForwardOpenCb)(uint8_t slot, uint32_t channel, const char *host, size_t host_len, uint16_t port)
"direct-tcpip" forward request: a client asked the server to open a TCP connection to host : port (ss...
Definition ssh_channel.h:75
int ssh_channel_open_forwarded(uint8_t i, const char *conn_addr, uint16_t conn_port, const char *orig_addr, uint16_t orig_port, uint8_t *out, size_t *out_len, size_t cap)
Open a server-initiated "forwarded-tcpip" channel (RFC 4254 §7.2, ssh -R).
int ssh_channel_handle_data(uint8_t i, const uint8_t *payload, size_t len, uint8_t *out, size_t *out_len, size_t cap)
Handle SSH_MSG_CHANNEL_DATA: bounds-check, update the window, and invoke the data callback....
void(* SshChannelDataCb)(uint8_t slot, uint32_t channel, const uint8_t *data, size_t len)
Application callback for inbound channel data (raw bytes), tagged with the channel id it arrived on.
Definition ssh_channel.h:59
int ssh_channel_build_data(uint8_t i, uint32_t channel, const uint8_t *data, size_t len, uint8_t *out, size_t *out_len, size_t cap)
Build an SSH_MSG_CHANNEL_DATA message carrying data to the client on channel channel (a local channel...
int ssh_global_request_handle(uint8_t i, const uint8_t *payload, size_t len, uint8_t *out, size_t *out_len, size_t cap)
Handle SSH_MSG_GLOBAL_REQUEST (RFC 4254 §4).
void ssh_channel_set_forward_open_cb(SshForwardOpenCb cb)
Install the direct-tcpip forward open-policy callback (opt-in).
void ssh_channel_set_forward_confirm_cb(SshForwardConfirmCb cb)
Install the forwarded-tcpip open-confirmation callback (opt-in, ssh -R).
int ssh_channel_build_close(uint8_t i, uint32_t channel, uint8_t *out, size_t *out_len, size_t cap)
Build SSH_MSG_CHANNEL_EOF + SSH_MSG_CHANNEL_CLOSE for channel channel and mark it closed.
void(* SshForwardDataCb)(uint8_t slot, uint32_t channel, const uint8_t *data, size_t len)
Inbound data on a direct-tcpip channel (the owner writes it to the forwarded TCP socket)....
Definition ssh_channel.h:79
void ssh_channel_set_rforward_cancel_cb(SshRemoteForwardCancelCb cb)
Install the remote-forward (ssh -R) cancel callback (opt-in).
int ssh_channel_handle_window_adjust(uint8_t i, const uint8_t *payload, size_t len)
Handle SSH_MSG_CHANNEL_WINDOW_ADJUST (grows the peer window).
SshChanType
Channel type (RFC 4254).
Definition ssh_channel.h:33
@ SSH_CHAN_DIRECT_TCPIP
"direct-tcpip" - client-initiated TCP forward (ssh -L)
@ SSH_CHAN_SESSION
"session" - shell / exec / data
@ SSH_CHAN_FORWARDED_TCPIP
"forwarded-tcpip" - server-initiated TCP forward (ssh -R)
void ssh_channel_set_data_cb(SshChannelDataCb cb)
Install the inbound-data callback (session channels).
void ssh_channel_init(uint8_t i)
Reset channel state for slot i.
int ssh_channel_handle_close(uint8_t i, const uint8_t *payload, size_t len, uint8_t *out, size_t *out_len, size_t cap)
Handle an inbound SSH_MSG_CHANNEL_CLOSE: route to the recipient channel, reply with EOF + CLOSE,...
void(* SshForwardConfirmCb)(uint8_t slot, uint32_t channel, bool ok)
Result of the client's reply to a server-initiated forwarded-tcpip channel: ok = true on CHANNEL_OPEN...
int ssh_channel_handle_open_failure(uint8_t i, const uint8_t *payload, size_t len)
Handle SSH_MSG_CHANNEL_OPEN_FAILURE for a channel we opened (ssh -R).
int ssh_channel_handle_request(uint8_t i, const uint8_t *payload, size_t len, uint8_t *out, size_t *out_len, size_t cap)
Handle SSH_MSG_CHANNEL_REQUEST.
int ssh_channel_handle_open(uint8_t i, const uint8_t *payload, size_t len, uint8_t *out, size_t *out_len, size_t cap)
Handle SSH_MSG_CHANNEL_OPEN and emit CHANNEL_OPEN_CONFIRMATION.
SshChannel ssh_chan[MAX_SSH_CONNS][DETWS_SSH_MAX_CHANNELS]
Channel pool: DETWS_SSH_MAX_CHANNELS channels per SSH connection (BSS). Owned by this layer; src/ cod...
int(* SshRemoteForwardCancelCb)(uint8_t slot, const char *bind_addr, size_t addr_len, uint16_t bind_port)
"cancel-tcpip-forward" request (RFC 4254 §7.1): drop a remote forward.
int(* SshRemoteForwardOpenCb)(uint8_t slot, const char *bind_addr, size_t addr_len, uint16_t bind_port)
"tcpip-forward" remote-forward request (ssh -R): the client asks the server to listen on bind_addr : ...
Definition ssh_channel.h:97
int ssh_channel_handle_open_confirm(uint8_t i, const uint8_t *payload, size_t len)
Handle SSH_MSG_CHANNEL_OPEN_CONFIRMATION for a channel we opened (ssh -R).
void ssh_channel_set_rforward_open_cb(SshRemoteForwardOpenCb cb)
Install the remote-forward (ssh -R) open-policy callback (opt-in).
Per-connection channel state.
Definition ssh_channel.h:41
bool open
True once the channel is confirmed open both ways.
Definition ssh_channel.h:42
uint32_t peer_max_pkt
Client's maximum packet size.
Definition ssh_channel.h:49
uint32_t peer_window
Bytes we may still send to the client.
Definition ssh_channel.h:48
SshChanType type
session, direct-tcpip, or forwarded-tcpip.
Definition ssh_channel.h:44
bool pending
True for a server-initiated channel we opened, awaiting the client's confirmation.
Definition ssh_channel.h:43
uint32_t local_id
Our channel id (== slot index).
Definition ssh_channel.h:45
uint32_t local_window
Bytes we may still receive before WINDOW_ADJUST.
Definition ssh_channel.h:47
uint32_t peer_id
Client's channel id.
Definition ssh_channel.h:46