ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
ssh_forward.cpp
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_forward.cpp
6 * @brief SSH direct-tcpip port-forwarding owner - outbound TCP + byte bridge.
7 */
8
10
11#if PC_SSH_PORT_FORWARD
12
16
17// Remote forwarding (ssh -R) uses the inbound transport + listener layer directly:
18// it allocates a real listener and bridges each accepted socket to a server-initiated
19// forwarded-tcpip channel.
24
25#include <string.h>
26
27// One forwarded TCP connection: an SSH channel bridged to a client-transport slot.
28struct SshFwd
29{
30 bool active;
31 uint8_t ssh_slot;
32 uint32_t channel; // local channel id (== ssh_chan row index)
33 int cid; // pc_client connection id
34};
35
36// All SSH local-forward (ssh -L) state, owned by one instance (internal linkage): the active
37// forward table and the policy callback. One named owner, unreachable cross-TU.
38struct SshFwdCtx
39{
40 SshFwd fwd[PC_SSH_FWD_MAX];
41 SshForwardPolicyCb policy = nullptr;
42};
43static SshFwdCtx s_fwd;
44
45// Target -> client iterations per channel per poll: bounds the work each loop so
46// one busy tunnel cannot starve the others (PC_SSH_FWD_CHUNK bytes each).
47static const int kFwdBurst = 4;
48
49static int fwd_find_free()
50{
51 for (int i = 0; i < PC_SSH_FWD_MAX; i++)
52 {
53 if (!s_fwd.fwd[i].active)
54 {
55 return i;
56 }
57 }
58 return -1;
59}
60
61static SshFwd *fwd_lookup(uint8_t ssh_slot, uint32_t channel)
62{
63 for (int i = 0; i < PC_SSH_FWD_MAX; i++)
64 {
65 if (s_fwd.fwd[i].active && s_fwd.fwd[i].ssh_slot == ssh_slot && s_fwd.fwd[i].channel == channel)
66 {
67 return &s_fwd.fwd[i];
68 }
69 }
70 return nullptr;
71}
72
73// ===========================================================================
74// Remote forwarding (ssh -R): the client asks the server to LISTEN on a port and
75// tunnel each accepted connection back over a server-initiated forwarded-tcpip
76// channel. One bind == one real listener_pool[] slot; one bridge == one accepted
77// conn_pool slot glued to one SSH channel. All storage static (no heap).
78// ===========================================================================
79
80// A remote-forward binding: a listener this SSH connection asked us to open.
81struct SshRFwdBind
82{
83 bool active;
84 uint8_t ssh_slot;
85 uint8_t listener_idx; // slot in listener_pool[]
86 uint16_t bind_port; // port bound on the device
87 char bind_addr[PC_SSH_FWD_HOST_MAX]; // address the client requested (echoed in CHANNEL_OPEN)
88};
89
90// One bridged connection: an accepted TCP socket glued to a forwarded-tcpip channel.
91struct SshRFwdBridge
92{
93 bool active;
94 bool confirmed; // client CONFIRMED the channel: bytes may flow
95 uint8_t ssh_slot; // the owning SSH connection
96 uint8_t conn_slot; // the accepted TCP conn_pool slot
97 uint32_t channel; // our local forwarded-tcpip channel id
98};
99
100// All SSH remote-forward (ssh -R) state, owned by one instance (internal linkage): the listener
101// bindings and the accepted-connection bridges. One named owner, unreachable cross-TU.
102struct SshRFwdCtx
103{
104 SshRFwdBind rbind[PC_SSH_RFWD_MAX];
105 SshRFwdBridge rbridge[PC_SSH_RFWD_BRIDGE_MAX];
106};
107static SshRFwdCtx s_rfwd;
108
109static int rbind_find_free()
110{
111 for (int i = 0; i < PC_SSH_RFWD_MAX; i++)
112 {
113 if (!s_rfwd.rbind[i].active)
114 {
115 return i;
116 }
117 }
118 return -1;
119}
120static SshRFwdBind *rbind_by_listener(uint8_t listener_idx)
121{
122 for (int i = 0; i < PC_SSH_RFWD_MAX; i++)
123 {
124 if (s_rfwd.rbind[i].active && s_rfwd.rbind[i].listener_idx == listener_idx)
125 {
126 return &s_rfwd.rbind[i];
127 }
128 }
129 return nullptr;
130}
131static SshRFwdBind *rbind_find(uint8_t ssh_slot, uint16_t port)
132{
133 for (int i = 0; i < PC_SSH_RFWD_MAX; i++)
134 {
135 if (s_rfwd.rbind[i].active && s_rfwd.rbind[i].ssh_slot == ssh_slot && s_rfwd.rbind[i].bind_port == port)
136 {
137 return &s_rfwd.rbind[i];
138 }
139 }
140 return nullptr;
141}
142static int rbridge_find_free()
143{
144 for (int i = 0; i < PC_SSH_RFWD_BRIDGE_MAX; i++)
145 {
146 if (!s_rfwd.rbridge[i].active)
147 {
148 return i;
149 }
150 }
151 return -1;
152}
153static SshRFwdBridge *rbridge_by_conn(uint8_t conn_slot)
154{
155 for (int i = 0; i < PC_SSH_RFWD_BRIDGE_MAX; i++)
156 {
157 if (s_rfwd.rbridge[i].active && s_rfwd.rbridge[i].conn_slot == conn_slot)
158 {
159 return &s_rfwd.rbridge[i];
160 }
161 }
162 return nullptr;
163}
164static SshRFwdBridge *rbridge_by_channel(uint8_t ssh_slot, uint32_t channel)
165{
166 for (int i = 0; i < PC_SSH_RFWD_BRIDGE_MAX; i++)
167 {
168 if (s_rfwd.rbridge[i].active && s_rfwd.rbridge[i].ssh_slot == ssh_slot && s_rfwd.rbridge[i].channel == channel)
169 {
170 return &s_rfwd.rbridge[i];
171 }
172 }
173 return nullptr;
174}
175
176// Move accepted-socket bytes to the client over the SSH channel. Read only what the
177// channel peer window (and max packet) allow so pc_ssh_conn_send never has to reject
178// bytes already pulled from the ring; bounded per poll so one tunnel cannot starve
179// the others. Leftover bytes stay in the rx ring (backpressure) for the next poll.
180static void rbridge_pump_to_client(SshRFwdBridge *br)
181{
182 if (br->channel >= PC_SSH_MAX_CHANNELS)
183 {
184 return;
185 }
186 SshChannel *c = &ssh_chan[br->ssh_slot][br->channel];
187 uint8_t buf[PC_SSH_FWD_CHUNK];
188 for (int burst = 0; burst < 4; burst++)
189 {
190 size_t avail = pc_conn_available(br->conn_slot);
191 uint32_t win = pc_ssh_flow_peer_window(&c->flow);
192 if (avail == 0 || win == 0 || !c->open)
193 {
194 break;
195 }
196 size_t budget = avail;
197 if (budget > win)
198 {
199 budget = win;
200 }
201 if (c->flow.peer_max_pkt && budget > c->flow.peer_max_pkt)
202 {
203 budget = c->flow.peer_max_pkt;
204 }
205 if (budget > sizeof(buf))
206 {
207 budget = sizeof(buf);
208 }
209 size_t n = pc_conn_read(br->conn_slot, buf, budget);
210 if (n == 0)
211 {
212 break;
213 }
214 if (pc_ssh_conn_send(br->ssh_slot, br->channel, buf, n) < 0)
215 {
216 break; // channel gone: retry next poll
217 }
218 }
219}
220
221// direct-tcpip open: check policy, connect to host:port (blocking), bind a slot.
222// Returns 0 to confirm the channel, < 0 to refuse (denied / connect failed / full).
223static int on_forward_open(uint8_t ssh_slot, uint32_t channel, const char *host, size_t host_len, uint16_t port)
224{
225 int idx = fwd_find_free();
226 if (idx < 0)
227 {
228 return -1; // no forward capacity
229 }
230 char hbuf[PC_SSH_FWD_HOST_MAX];
231 if (host_len == 0 || host_len >= sizeof(hbuf))
232 {
233 return -1;
234 }
235 memcpy(hbuf, host, host_len);
236 hbuf[host_len] = 0;
237 if (s_fwd.policy && !s_fwd.policy(hbuf, port))
238 {
239 return -1; // target administratively denied
240 }
241 int cid = pc_client_open(hbuf, port, PC_SSH_FWD_CONNECT_MS); // blocks on DNS + connect
242 if (cid < 0)
243 {
244 return -1; // -> CHANNEL_OPEN_FAILURE (connect failed)
245 }
246 s_fwd.fwd[idx].active = true;
247 s_fwd.fwd[idx].ssh_slot = ssh_slot;
248 s_fwd.fwd[idx].channel = channel;
249 s_fwd.fwd[idx].cid = cid;
250 return 0;
251}
252
253// Inbound channel bytes. direct-tcpip (ssh -L): client -> outbound target socket.
254// forwarded-tcpip (ssh -R): client -> the accepted socket we bridged back to it.
255static void on_forward_data(uint8_t ssh_slot, uint32_t channel, const uint8_t *data, size_t len)
256{
257 SshFwd *f = fwd_lookup(ssh_slot, channel);
258 if (f)
259 {
260 pc_client_send(f->cid, data, len);
261 return;
262 }
263 SshRFwdBridge *br = rbridge_by_channel(ssh_slot, channel);
264 if (br && br->confirmed)
265 {
266 pc_conn_send(br->conn_slot, data, (u16_t)len);
267 pc_conn_flush(br->conn_slot);
268 }
269}
270
271// ---------------------------------------------------------------------------
272// Remote-forward seam (GLOBAL_REQUEST tcpip-forward / cancel-tcpip-forward, ssh -R)
273// ---------------------------------------------------------------------------
274
275// Open a listener bound to bind_port and remember it for this SSH connection.
276// Returns the bound port (>= 0) on success, -1 to refuse.
277static int on_rforward_open(uint8_t ssh_slot, const char *addr, size_t addr_len, uint16_t bind_port)
278{
279 if (bind_port == 0)
280 {
281 return -1; // ephemeral-port allocation is not supported: require an explicit port
282 }
283 if (rbind_find(ssh_slot, bind_port))
284 {
285 return -1; // already forwarding this port on this connection
286 }
287 int bi = rbind_find_free();
288 if (bi < 0)
289 {
290 return -1; // remote-forward table full
291 }
292 // Find a free listener_pool slot (the app's own listeners are .active).
293 int li = -1;
294 for (int k = 0; k < MAX_LISTENERS; k++)
295 {
296 if (!listener_pool[k].active)
297 {
298 li = k;
299 break;
300 }
301 }
302 if (li < 0)
303 {
304 return -1; // no listener capacity
305 }
306 // Dynamic (tcpip_thread-marshaled) create: this runs in the SSH worker task.
307 if (listener_add_dynamic((uint8_t)li, bind_port, ConnProto::PROTO_SSH_RFWD) != 1)
308 {
309 return -1; // bind failed (port already in use, etc.)
310 }
311
312 s_rfwd.rbind[bi].active = true;
313 s_rfwd.rbind[bi].ssh_slot = ssh_slot;
314 s_rfwd.rbind[bi].listener_idx = (uint8_t)li;
315 s_rfwd.rbind[bi].bind_port = bind_port;
316 size_t al = addr_len < sizeof(s_rfwd.rbind[bi].bind_addr) - 1 ? addr_len : sizeof(s_rfwd.rbind[bi].bind_addr) - 1;
317 memcpy(s_rfwd.rbind[bi].bind_addr, addr, al);
318 s_rfwd.rbind[bi].bind_addr[al] = 0;
319 return bind_port;
320}
321
322// Cancel a remote forward: stop accepting new connections (existing bridges finish).
323static int on_rforward_cancel(uint8_t ssh_slot, const char *addr, size_t addr_len, uint16_t bind_port)
324{
325 (void)addr;
326 (void)addr_len;
327 SshRFwdBind *b = rbind_find(ssh_slot, bind_port);
328 if (!b)
329 {
330 return -1;
331 }
332 listener_stop_dynamic(b->listener_idx);
333 b->active = false;
334 return 0;
335}
336
337// The client's reply to a server-initiated forwarded-tcpip open.
338static void on_forward_confirm(uint8_t ssh_slot, uint32_t channel, bool ok)
339{
340 SshRFwdBridge *br = rbridge_by_channel(ssh_slot, channel);
341 if (!br)
342 {
343 return;
344 }
345 if (ok)
346 {
347 br->confirmed = true; // bytes may now flow (pumped on the next poll)
348 }
349 else
350 {
351 pc_conn_close(br->conn_slot); // client refused the tunnel: drop the accepted socket
352 br->active = false;
353 }
354}
355
356// ---------------------------------------------------------------------------
357// ConnProto::PROTO_SSH_RFWD handler: an inbound connection on a forwarded port.
358// ---------------------------------------------------------------------------
359
360static void rfwd_on_accept(uint8_t conn_slot)
361{
362 SshRFwdBind *b = rbind_by_listener(conn_pool[conn_slot].listener_id);
363 if (!b)
364 {
365 pc_conn_close(conn_slot); // no binding owns this listener (stale): drop
366 return;
367 }
368 int idx = rbridge_find_free();
369 if (idx < 0)
370 {
371 pc_conn_close(conn_slot); // bridge table full
372 return;
373 }
374 // Originator address (advisory, RFC 4254 ยง7.2); the peer port is not exposed by the
375 // transport, so it is reported as 0.
376 char orig[PC_IP_STR_MAX];
377 orig[0] = 0;
378 pc_ip rip;
379 if (pc_conn_remote_addr(conn_slot, &rip))
380 {
381 pc_ip_format(&rip, orig, sizeof(orig));
382 }
383 // Open the forwarded-tcpip channel back to the client, echoing the requested bind
384 // address as the "address that was connected".
385 int ch = pc_ssh_conn_open_forwarded(b->ssh_slot, b->bind_addr[0] ? b->bind_addr : "0.0.0.0", b->bind_port, orig, 0);
386 if (ch < 0)
387 {
388 pc_conn_close(conn_slot); // SSH connection gone or channel pool full
389 return;
390 }
391 s_rfwd.rbridge[idx].active = true;
392 s_rfwd.rbridge[idx].confirmed = false;
393 s_rfwd.rbridge[idx].ssh_slot = b->ssh_slot;
394 s_rfwd.rbridge[idx].conn_slot = conn_slot;
395 s_rfwd.rbridge[idx].channel = (uint32_t)ch;
396}
397
398static void rfwd_on_data(uint8_t conn_slot)
399{
400 SshRFwdBridge *br = rbridge_by_conn(conn_slot);
401 if (br && br->confirmed)
402 {
403 rbridge_pump_to_client(br); // otherwise the bytes wait in the ring until confirmed
404 }
405}
406
407static void rfwd_on_close(uint8_t conn_slot)
408{
409 SshRFwdBridge *br = rbridge_by_conn(conn_slot);
410 if (!br)
411 {
412 return;
413 }
414 pc_ssh_conn_close_channel(br->ssh_slot, br->channel); // tell the client EOF + CLOSE
415 br->active = false;
416}
417
418static void rfwd_on_poll(uint8_t conn_slot)
419{
420 // The dispatch loop now polls every slot uniformly; it used to poll only ACTIVE slots, so preserve
421 // that gate here (a closing/free forward slot has nothing to pump).
422 if (!pc_conn_active(conn_slot))
423 {
424 return;
425 }
426 SshRFwdBridge *br = rbridge_by_conn(conn_slot);
427 if (!br || !br->confirmed)
428 {
429 return;
430 }
431 // The client closed its side of the channel -> close the accepted socket.
432 if (br->channel < PC_SSH_MAX_CHANNELS && !ssh_chan[br->ssh_slot][br->channel].open)
433 {
434 pc_conn_close(conn_slot);
435 br->active = false;
436 return;
437 }
438 rbridge_pump_to_client(br); // drain anything the window blocked earlier
439}
440
441static const ProtoHandler s_rfwd_handler = {rfwd_on_accept, rfwd_on_data, rfwd_on_close, rfwd_on_poll};
442
443void pc_ssh_forward_set_policy_cb(SshForwardPolicyCb cb)
444{
445 s_fwd.policy = cb;
446}
447
448void pc_ssh_forward_begin()
449{
450 for (int i = 0; i < PC_SSH_FWD_MAX; i++)
451 {
452 s_fwd.fwd[i].active = false;
453 }
454 for (int i = 0; i < PC_SSH_RFWD_MAX; i++)
455 {
456 s_rfwd.rbind[i].active = false;
457 }
458 for (int i = 0; i < PC_SSH_RFWD_BRIDGE_MAX; i++)
459 {
460 s_rfwd.rbridge[i].active = false;
461 }
462 pc_ssh_channel_set_forward_open_cb(on_forward_open);
463 pc_ssh_channel_set_forward_data_cb(on_forward_data);
464 // Remote forwarding (ssh -R): the request/cancel seam, the open-confirmation
465 // callback, and the accept handler for connections on a forwarded port.
466 pc_ssh_channel_set_rforward_open_cb(on_rforward_open);
467 pc_ssh_channel_set_rforward_cancel_cb(on_rforward_cancel);
468 pc_ssh_channel_set_forward_confirm_cb(on_forward_confirm);
470}
471
472void pc_ssh_forward_pump(uint8_t ssh_slot)
473{
474 uint8_t buf[PC_SSH_FWD_CHUNK];
475 for (int i = 0; i < PC_SSH_FWD_MAX; i++)
476 {
477 SshFwd *f = &s_fwd.fwd[i];
478 if (!f->active || f->ssh_slot != ssh_slot)
479 {
480 continue;
481 }
482 if (f->channel >= PC_SSH_MAX_CHANNELS) // defensive: stale binding
483 {
484 pc_client_close(f->cid);
485 f->active = false;
486 continue;
487 }
488 SshChannel *c = &ssh_chan[ssh_slot][f->channel];
489
490 // Client closed its side of the channel: drop the target socket.
491 if (!c->open)
492 {
493 pc_client_close(f->cid);
494 f->active = false;
495 continue;
496 }
497
498 // Target -> client: forward what the peer window allows, bounded per poll.
499 for (int burst = 0; burst < kFwdBurst; burst++)
500 {
501 size_t avail = pc_client_available(f->cid);
502 uint32_t win = pc_ssh_flow_peer_window(&c->flow);
503 if (avail == 0 || win == 0)
504 {
505 break;
506 }
507 size_t budget = avail;
508 if (budget > win)
509 {
510 budget = win;
511 }
512 if (c->flow.peer_max_pkt && budget > c->flow.peer_max_pkt)
513 {
514 budget = c->flow.peer_max_pkt;
515 }
516 if (budget > sizeof(buf))
517 {
518 budget = sizeof(buf);
519 }
520 size_t n = pc_client_read(f->cid, buf, budget);
521 if (n == 0)
522 {
523 break;
524 }
525 if (pc_ssh_conn_send(ssh_slot, f->channel, buf, n) < 0)
526 {
527 break; // sized to the window, so this should send; retry next poll
528 }
529 }
530
531 // Target closed (FIN) and fully drained: EOF + CLOSE to the client, free.
532 if (pc_client_is_closed(f->cid) && pc_client_available(f->cid) == 0)
533 {
534 pc_ssh_conn_close_channel(ssh_slot, f->channel);
535 pc_client_close(f->cid);
536 f->active = false;
537 }
538 }
539}
540
541void pc_ssh_forward_reset(uint8_t ssh_slot)
542{
543 // direct-tcpip (ssh -L): close outbound target sockets this connection owned.
544 for (int i = 0; i < PC_SSH_FWD_MAX; i++)
545 {
546 if (s_fwd.fwd[i].active && s_fwd.fwd[i].ssh_slot == ssh_slot)
547 {
548 pc_client_close(s_fwd.fwd[i].cid);
549 s_fwd.fwd[i].active = false;
550 }
551 }
552 // remote (ssh -R): stop this connection's forwarded listeners and drop every
553 // accepted socket it had bridged (the SSH channels go away with the connection).
554 for (int i = 0; i < PC_SSH_RFWD_MAX; i++)
555 {
556 if (s_rfwd.rbind[i].active && s_rfwd.rbind[i].ssh_slot == ssh_slot)
557 {
558 listener_stop_dynamic(s_rfwd.rbind[i].listener_idx);
559 s_rfwd.rbind[i].active = false;
560 }
561 }
562 for (int i = 0; i < PC_SSH_RFWD_BRIDGE_MAX; i++)
563 {
564 if (s_rfwd.rbridge[i].active && s_rfwd.rbridge[i].ssh_slot == ssh_slot)
565 {
566 pc_conn_close(s_rfwd.rbridge[i].conn_slot);
567 s_rfwd.rbridge[i].active = false;
568 }
569 }
570}
571
572#endif // PC_SSH_PORT_FORWARD
#define PC_SSH_MAX_CHANNELS
Definition c2_defaults.h:88
bool pc_client_send(int, const void *, size_t)
Queue len wire bytes for transmission (marshaled tcp_write + output).
Definition client.cpp:366
size_t pc_client_read(int, uint8_t *, size_t)
Drain up to cap buffered wire bytes into buf; returns the count.
Definition client.cpp:374
bool pc_client_is_closed(int)
True once the peer closed (FIN) or the connection errored.
Definition client.cpp:362
int pc_client_open(const char *, uint16_t, uint32_t)
Resolve host (dotted-quad fast path, else DNS) and connect to host : port, blocking up to timeout_ms.
Definition client.cpp:354
size_t pc_client_available(int)
Wire bytes currently buffered and ready to read.
Definition client.cpp:370
void pc_client_close(int)
Tear down the connection (marshaled) and return the slot to the pool.
Definition client.cpp:378
Layer 4 outbound TCP client transport - the client-side peer of the (server) transport in tcp....
size_t pc_ip_format(const pc_ip *ip, char *out, size_t cap)
Format ip into out as its RFC 5952 canonical text.
Definition ip.cpp:488
Layer 3 (Network) - a family-tagged IP address (IPv4 or IPv6) with RFC-faithful text parsing,...
#define PC_IP_STR_MAX
Longest text an pc_ip_format can produce, including the NUL (RFC 5952 v4-mapped).
Definition ip.h:58
int32_t listener_add_dynamic(uint8_t idx, uint16_t port, ConnProto proto)
Add / stop a listener from a running task (thread-safe variant).
Definition listener.cpp:741
Listener listener_pool[MAX_LISTENERS]
Static pool of listener contexts. Defined in listener.cpp.
Definition listener.cpp:35
void listener_stop_dynamic(uint8_t idx)
Definition listener.cpp:784
Layer 4 (Listener) - per-port TCP listener abstraction.
Layer 5 (Session) - per-protocol connection handler dispatch table.
void proto_register(ConnProto proto, const ProtoHandler *h)
Register h for protocol proto (replaces any previous handler).
Definition session.cpp:38
#define MAX_LISTENERS
Maximum number of simultaneously active listener ports.
@ PROTO_SSH_RFWD
SSH remote-forward listener (ssh -R): accepts bridge to a forwarded-tcpip channel.
#define PC_SSH_FWD_CHUNK
Max bytes moved per forward channel per poll, target -> client (<= SSH_PKT_BUF_SIZE).
#define PC_SSH_FWD_CONNECT_MS
Blocking connect timeout (ms) when opening a forward target.
#define PC_SSH_RFWD_MAX
Maximum concurrent remote-forward listeners (ssh -R / tcpip-forward).
#define PC_SSH_FWD_MAX
Maximum concurrent forwarded TCP connections (must be <= PC_CLIENT_CONNS).
#define PC_SSH_RFWD_BRIDGE_MAX
Maximum concurrent bridged connections across all remote forwards.
#define PC_SSH_FWD_HOST_MAX
Maximum forward target hostname length including null terminator.
void pc_ssh_channel_set_forward_data_cb(SshForwardDataCb cb)
Install the direct-tcpip forward inbound-data callback.
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).
void pc_ssh_channel_set_rforward_cancel_cb(SshRemoteForwardCancelCb cb)
Install the remote-forward (ssh -R) cancel callback (opt-in).
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 pc_ssh_channel_set_forward_open_cb(SshForwardOpenCb cb)
Install the direct-tcpip forward open-policy callback (opt-in).
SSH connection protocol - multiplexed "session" channels (RFC 4254).
int pc_ssh_conn_send(uint8_t ssh_slot, uint32_t channel, const uint8_t *data, size_t len)
Send application data to the client over an SSH channel.
Definition ssh_conn.cpp:117
int pc_ssh_conn_open_forwarded(uint8_t ssh_slot, const char *conn_addr, uint16_t conn_port, const char *orig_addr, uint16_t orig_port)
Open a server-initiated "forwarded-tcpip" channel to the client (ssh -R): build the CHANNEL_OPEN (RFC...
Definition ssh_conn.cpp:201
int pc_ssh_conn_close_channel(uint8_t ssh_slot, uint32_t channel)
Close an SSH channel from the server side: frame CHANNEL_EOF and CHANNEL_CLOSE as two binary packets ...
Definition ssh_conn.cpp:156
Glue between the TCP transport (conn_pool) and the SSH protocol stack.
uint32_t pc_ssh_flow_peer_window(const SshFlow *f)
Bytes we may still send the peer - the bound a producer sizes its next read to.
SSH direct-tcpip port-forwarding owner (the ssh -L target side).
Per-protocol connection event/poll callbacks (Layer 5 dispatch vtable).
Per-connection channel state.
Definition ssh_channel.h:44
bool open
True once the channel is confirmed open both ways.
Definition ssh_channel.h:45
SshFlow flow
RFC 4254 sec 5.2 window pair (owner: ssh_flow_control.*).
Definition ssh_channel.h:50
uint32_t peer_max_pkt
Peer's maximum packet size; caps a single send independently of the window.
A v4 or v6 address in network (big-endian) byte order.
Definition ip.h:52
TcpConn conn_pool[CONN_POOL_SLOTS]
Static pool of connection contexts. Defined in tcp.cpp. Sized CONN_POOL_SLOTS: MAX_CONNS TCP slots pl...
Definition tcp.cpp:425
void pc_conn_flush(uint8_t slot)
Flush queued bytes / finish the send on slot (TLS-aware).
Definition tcp.cpp:561
void pc_conn_close(uint8_t slot)
Close connection slot gracefully (tcp_close), aborting if the FIN cannot be queued....
Definition tcp.cpp:645
bool pc_conn_send(uint8_t slot, const void *data, u16_t len)
Send len bytes on connection slot (copies data; TLS-aware).
Definition tcp.cpp:500
bool pc_conn_remote_addr(uint8_t slot, pc_ip *out)
The connected peer's address as a family-tagged pc_ip (IPv4 or IPv6).
Definition tcp.cpp:912
Layer 4 (Transport) - TCP connection pool, ring buffers, and lwIP integration.