ProtoCore v0.0.1
Deterministic, zero-heap network stack for embedded targets
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 PC_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 PROTOCORE_SSH_CHANNEL_H
25#define PROTOCORE_SSH_CHANNEL_H
26
27#include "protocore_config.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 SSH_CHAN_SFTP = 3, ///< a "session" running the "sftp" subsystem (PC_ENABLE_SSH_SFTP)
38 SSH_CHAN_SCP = 4 ///< a "session" running an `exec "scp …"` (PC_ENABLE_SSH_SCP)
39};
40
41/** @brief Per-connection channel state. */
43{
44 bool open; ///< True once the channel is confirmed open both ways.
45 bool pending; ///< True for a server-initiated channel we opened, awaiting the client's confirmation.
46 SshChanType type; ///< session, direct-tcpip, or forwarded-tcpip.
47 uint32_t local_id; ///< Our channel id (== slot index).
48 uint32_t peer_id; ///< Client's channel id.
49 uint32_t local_window; ///< Bytes we may still receive before WINDOW_ADJUST.
50 uint32_t peer_window; ///< Bytes we may still send to the client.
51 uint32_t peer_max_pkt; ///< Client's maximum packet size.
52};
53
54/** @brief Channel pool: PC_SSH_MAX_CHANNELS channels per SSH connection (BSS).
55 * Owned by this layer; src/ code routes through the functions below, never the
56 * array (tests inspect it white-box). Index: [connection slot][channel slot]. */
58
59/** @brief Application callback for inbound channel data (raw bytes), tagged with
60 * the channel id it arrived on. */
61using SshChannelDataCb = void (*)(uint8_t slot, uint32_t channel, const uint8_t *data, size_t len);
62
63/** @brief Install the inbound-data callback (session channels). */
65
66/**
67 * @brief "direct-tcpip" forward request: a client asked the server to open a TCP
68 * connection to @p host : @p port (ssh -L). The forwarding owner (which
69 * does the actual TCP I/O - this codec does not) decides whether to allow
70 * it; @p host is not NUL-terminated (@p host_len bytes).
71 * @return 0 to accept (the channel is opened and confirmed), < 0 to refuse
72 * (CHANNEL_OPEN_FAILURE, administratively prohibited / connect failed).
73 *
74 * If no callback is installed, all forward requests are refused - so forwarding is
75 * opt-in (no open relay by default).
76 */
77using SshForwardOpenCb = int (*)(uint8_t slot, uint32_t channel, const char *host, size_t host_len, uint16_t port);
78
79/** @brief Inbound data on a direct-tcpip channel (the owner writes it to the
80 * forwarded TCP socket). Kept separate from the session data callback. */
81using SshForwardDataCb = void (*)(uint8_t slot, uint32_t channel, const uint8_t *data, size_t len);
82
83/** @brief Install the direct-tcpip forward open-policy callback (opt-in). */
85
86/** @brief Install the direct-tcpip forward inbound-data callback. */
88
89/**
90 * @brief "tcpip-forward" remote-forward request (ssh -R): the client asks the server
91 * to listen on @p bind_addr : @p bind_port and open a channel back for each
92 * accepted connection (RFC 4254 §7.1). @p bind_addr is @p addr_len bytes (not
93 * NUL-terminated). The forwarding owner (which allocates the real listener -
94 * this codec does no I/O) decides.
95 * @return the bound port on success (echo @p bind_port, or the port the owner picked
96 * when @p bind_port == 0), or < 0 to refuse. If no callback is installed every
97 * request is refused, so remote forwarding is opt-in (no listener is opened).
98 */
99using SshRemoteForwardOpenCb = int (*)(uint8_t slot, const char *bind_addr, size_t addr_len, uint16_t bind_port);
100
101/** @brief "cancel-tcpip-forward" request (RFC 4254 §7.1): drop a remote forward.
102 * @return 0 if a matching forward was cancelled, < 0 if none / unsupported. */
103using SshRemoteForwardCancelCb = int (*)(uint8_t slot, const char *bind_addr, size_t addr_len, uint16_t bind_port);
104
105/** @brief Install the remote-forward (ssh -R) open-policy callback (opt-in). */
107
108/** @brief Install the remote-forward (ssh -R) cancel callback (opt-in). */
110
111/**
112 * @brief Result of the client's reply to a server-initiated forwarded-tcpip channel:
113 * @p ok = true on CHANNEL_OPEN_CONFIRMATION (the bridge may start), false on
114 * CHANNEL_OPEN_FAILURE (the owner tears the bridge down). @p channel is the
115 * local id returned by pc_ssh_channel_open_forwarded().
116 */
117using SshForwardConfirmCb = void (*)(uint8_t slot, uint32_t channel, bool ok);
118
119/** @brief Install the forwarded-tcpip open-confirmation callback (opt-in, ssh -R). */
121
122#if PC_ENABLE_SSH_SFTP
123/** @brief A `subsystem "sftp"` request was accepted on @p channel; the binding starts an SFTP session. */
124using SshSftpOpenCb = void (*)(uint8_t slot, uint32_t channel);
125/** @brief Inbound bytes on an SFTP channel (the raw SSH_FXP_* stream) - kept out of the session data cb. */
126using SshSftpDataCb = void (*)(uint8_t slot, uint32_t channel, const uint8_t *data, size_t len);
127void pc_ssh_channel_set_sftp_open_cb(SshSftpOpenCb cb);
128void pc_ssh_channel_set_sftp_data_cb(SshSftpDataCb cb);
129#endif
130
131#if PC_ENABLE_SSH_SCP
132/** @brief An `exec "scp …"` request was accepted on @p channel (@p cmd is @p cmd_len bytes, not NUL-terminated). */
133using SshScpOpenCb = void (*)(uint8_t slot, uint32_t channel, const char *cmd, size_t cmd_len);
134/** @brief Inbound bytes on an SCP channel (the RCP protocol stream). */
135using SshScpDataCb = void (*)(uint8_t slot, uint32_t channel, const uint8_t *data, size_t len);
136void pc_ssh_channel_set_scp_open_cb(SshScpOpenCb cb);
137void pc_ssh_channel_set_scp_data_cb(SshScpDataCb cb);
138#endif
139
140/**
141 * @brief Open a server-initiated "forwarded-tcpip" channel (RFC 4254 §7.2, ssh -R).
142 *
143 * Allocates a local channel on connection @p i (state = pending, awaiting the
144 * client's confirmation) and builds the SSH_MSG_CHANNEL_OPEN in @p out.
145 * @p conn_addr / @p conn_port are the forward's bound address/port (the "address
146 * that was connected"); @p orig_addr / @p orig_port are the peer that connected.
147 *
148 * @return the local channel id (>= 0) on success, or -1 (pool full, or @p out too
149 * small). On success the caller emits @p out and, on the eventual confirm,
150 * bridges bytes on the returned channel.
151 */
152int pc_ssh_channel_open_forwarded(uint8_t i, const char *conn_addr, uint16_t conn_port, const char *orig_addr,
153 uint16_t orig_port, uint8_t *out, size_t *out_len, size_t cap);
154
155/**
156 * @brief Handle SSH_MSG_CHANNEL_OPEN_CONFIRMATION for a channel we opened (ssh -R).
157 *
158 * Matches the pending channel by our recipient id, records the peer's channel id /
159 * window / max-packet, and marks it open. Fires the confirm callback (@p ok = true).
160 * @return 0 on success, -1 if malformed or no matching pending channel.
161 */
162int pc_ssh_channel_handle_open_confirm(uint8_t i, const uint8_t *payload, size_t len);
163
164/**
165 * @brief Handle SSH_MSG_CHANNEL_OPEN_FAILURE for a channel we opened (ssh -R).
166 *
167 * Frees the pending channel and fires the confirm callback (@p ok = false).
168 * @return 0 on success, -1 if malformed or no matching pending channel.
169 */
170int pc_ssh_channel_handle_open_failure(uint8_t i, const uint8_t *payload, size_t len);
171
172/**
173 * @brief Handle SSH_MSG_GLOBAL_REQUEST (RFC 4254 §4).
174 *
175 * Parses the request name and want_reply flag. "tcpip-forward" /
176 * "cancel-tcpip-forward" are routed to the remote-forward seam above (accepted only
177 * when a callback is installed); a "tcpip-forward" that bound port 0 gets its
178 * allocated port echoed in the reply (RFC 4254 §7.1). Any other request name is
179 * unrecognized: per §4 it is answered with SSH_MSG_REQUEST_FAILURE when want_reply is
180 * set, and silently ignored otherwise (never SSH_MSG_UNIMPLEMENTED - GLOBAL_REQUEST is
181 * a known message type; only the request name is unknown).
182 *
183 * @return 0 on success (a reply is in @p out with *@p out_len bytes, or *@p out_len is
184 * 0 when no reply is due), -1 if the message is malformed.
185 */
186int ssh_global_request_handle(uint8_t i, const uint8_t *payload, size_t len, uint8_t *out, size_t *out_len, size_t cap);
187
188/** @brief Reset channel state for slot @p i. */
189void pc_ssh_channel_init(uint8_t i);
190
191/**
192 * @brief Handle SSH_MSG_CHANNEL_OPEN and emit CHANNEL_OPEN_CONFIRMATION.
193 *
194 * Accepts a "session" channel; any other type yields CHANNEL_OPEN_FAILURE.
195 * @return 0 if a response was produced, -1 if malformed.
196 */
197int pc_ssh_channel_handle_open(uint8_t i, const uint8_t *payload, size_t len, uint8_t *out, size_t *out_len,
198 size_t cap);
199
200/**
201 * @brief Handle SSH_MSG_CHANNEL_REQUEST.
202 *
203 * "shell", "exec", "pty-req", and "env" are accepted; anything else is refused - except that when
204 * PC_ENABLE_SSH_SFTP is set a `subsystem "sftp"` is accepted (the channel is tagged SSH_CHAN_SFTP and the
205 * sftp-open callback fires), and when PC_ENABLE_SSH_SCP is set an `exec "scp …"` is additionally tagged
206 * SSH_CHAN_SCP (the scp-open callback fires with the command). When want_reply is set, CHANNEL_SUCCESS /
207 * CHANNEL_FAILURE is written to @p out and *@p out_len > 0; otherwise *@p out_len is 0.
208 * @return 0 on success, -1 if malformed.
209 */
210int pc_ssh_channel_handle_request(uint8_t i, const uint8_t *payload, size_t len, uint8_t *out, size_t *out_len,
211 size_t cap);
212
213/**
214 * @brief Handle SSH_MSG_CHANNEL_DATA: bounds-check, update the window, and
215 * invoke the data callback. If the local window is exhausted a
216 * CHANNEL_WINDOW_ADJUST is written to @p out (*@p out_len > 0).
217 * @return 0 on success, -1 if malformed or channel not open.
218 */
219int pc_ssh_channel_handle_data(uint8_t i, const uint8_t *payload, size_t len, uint8_t *out, size_t *out_len,
220 size_t cap);
221
222/**
223 * @brief Build an SSH_MSG_CHANNEL_DATA message carrying @p data to the client on
224 * channel @p channel (a local channel id from a prior open).
225 * @return 0 on success, -1 if the channel is closed/unknown, the peer window is
226 * too small, or @p out is too small.
227 */
228int pc_ssh_channel_build_data(uint8_t i, uint32_t channel, const uint8_t *data, size_t len, uint8_t *out,
229 size_t *out_len, size_t cap);
230
231/**
232 * @brief Handle SSH_MSG_CHANNEL_WINDOW_ADJUST (grows the peer window).
233 * @return 0 on success, -1 if malformed.
234 */
235int pc_ssh_channel_handle_window_adjust(uint8_t i, const uint8_t *payload, size_t len);
236
237/**
238 * @brief Build SSH_MSG_CHANNEL_EOF + SSH_MSG_CHANNEL_CLOSE for channel @p channel
239 * and mark it closed.
240 * @return 0 on success, -1 if the channel is closed/unknown or @p out is too small.
241 */
242int pc_ssh_channel_build_close(uint8_t i, uint32_t channel, uint8_t *out, size_t *out_len, size_t cap);
243
244/**
245 * @brief Handle an inbound SSH_MSG_CHANNEL_CLOSE: route to the recipient channel,
246 * reply with EOF + CLOSE, and mark it closed.
247 * @return 0 if a response was produced, -1 if malformed or the channel is unknown.
248 */
249int pc_ssh_channel_handle_close(uint8_t i, const uint8_t *payload, size_t len, uint8_t *out, size_t *out_len,
250 size_t cap);
251
252#endif // PROTOCORE_SSH_CHANNEL_H
#define MAX_SSH_CONNS
Definition c2_defaults.h:85
#define PC_SSH_MAX_CHANNELS
Definition c2_defaults.h:88
User-facing configuration for ProtoCore.
int pc_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.
int pc_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).
int(*)(uint8_t slot, uint32_t channel, const char *host, size_t host_len, uint16_t port) SshForwardOpenCb
"direct-tcpip" forward request: a client asked the server to open a TCP connection to host : port (ss...
Definition ssh_channel.h:77
int pc_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 pc_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,...
int pc_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.
int pc_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 pc_ssh_channel_set_forward_data_cb(SshForwardDataCb cb)
Install the direct-tcpip forward inbound-data callback.
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 pc_ssh_channel_init(uint8_t i)
Reset channel state for slot i.
void pc_ssh_channel_set_data_cb(SshChannelDataCb cb)
Install the inbound-data callback (session channels).
void pc_ssh_channel_set_forward_confirm_cb(SshForwardConfirmCb cb)
Install the forwarded-tcpip open-confirmation callback (opt-in, ssh -R).
void pc_ssh_channel_set_rforward_open_cb(SshRemoteForwardOpenCb cb)
Install the remote-forward (ssh -R) open-policy callback (opt-in).
int pc_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(*)(uint8_t slot, const char *bind_addr, size_t addr_len, uint16_t bind_port) SshRemoteForwardOpenCb
"tcpip-forward" remote-forward request (ssh -R): the client asks the server to listen on bind_addr : ...
Definition ssh_channel.h:99
void pc_ssh_channel_set_rforward_cancel_cb(SshRemoteForwardCancelCb cb)
Install the remote-forward (ssh -R) cancel callback (opt-in).
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)
@ SSH_CHAN_SCP
a "session" running an exec "scp …" (PC_ENABLE_SSH_SCP)
@ SSH_CHAN_SFTP
a "session" running the "sftp" subsystem (PC_ENABLE_SSH_SFTP)
int(*)(uint8_t slot, const char *bind_addr, size_t addr_len, uint16_t bind_port) SshRemoteForwardCancelCb
"cancel-tcpip-forward" request (RFC 4254 §7.1): drop a remote forward.
int pc_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).
SshChannel ssh_chan[MAX_SSH_CONNS][PC_SSH_MAX_CHANNELS]
Channel pool: PC_SSH_MAX_CHANNELS channels per SSH connection (BSS). Owned by this layer; src/ code r...
void(*)(uint8_t slot, uint32_t channel, const uint8_t *data, size_t len) SshForwardDataCb
Inbound data on a direct-tcpip channel (the owner writes it to the forwarded TCP socket)....
Definition ssh_channel.h:81
int pc_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 pc_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.
void pc_ssh_channel_set_forward_open_cb(SshForwardOpenCb cb)
Install the direct-tcpip forward open-policy callback (opt-in).
void(*)(uint8_t slot, uint32_t channel, bool ok) SshForwardConfirmCb
Result of the client's reply to a server-initiated forwarded-tcpip channel: ok = true on CHANNEL_OPEN...
void(*)(uint8_t slot, uint32_t channel, const uint8_t *data, size_t len) SshChannelDataCb
Application callback for inbound channel data (raw bytes), tagged with the channel id it arrived on.
Definition ssh_channel.h:61
Per-connection channel state.
Definition ssh_channel.h:43
bool open
True once the channel is confirmed open both ways.
Definition ssh_channel.h:44
uint32_t peer_max_pkt
Client's maximum packet size.
Definition ssh_channel.h:51
uint32_t peer_window
Bytes we may still send to the client.
Definition ssh_channel.h:50
SshChanType type
session, direct-tcpip, or forwarded-tcpip.
Definition ssh_channel.h:46
bool pending
True for a server-initiated channel we opened, awaiting the client's confirmation.
Definition ssh_channel.h:45
uint32_t local_id
Our channel id (== slot index).
Definition ssh_channel.h:47
uint32_t local_window
Bytes we may still receive before WINDOW_ADJUST.
Definition ssh_channel.h:49
uint32_t peer_id
Client's channel id.
Definition ssh_channel.h:48