DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
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 DETWS_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; // det_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[DETWS_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 (DETWS_SSH_FWD_CHUNK bytes each).
47static const int kFwdBurst = 4;
48
49static int fwd_find_free()
50{
51 for (int i = 0; i < DETWS_SSH_FWD_MAX; i++)
52 if (!s_fwd.fwd[i].active)
53 return i;
54 return -1;
55}
56
57static SshFwd *fwd_lookup(uint8_t ssh_slot, uint32_t channel)
58{
59 for (int i = 0; i < DETWS_SSH_FWD_MAX; i++)
60 if (s_fwd.fwd[i].active && s_fwd.fwd[i].ssh_slot == ssh_slot && s_fwd.fwd[i].channel == channel)
61 return &s_fwd.fwd[i];
62 return nullptr;
63}
64
65// ===========================================================================
66// Remote forwarding (ssh -R): the client asks the server to LISTEN on a port and
67// tunnel each accepted connection back over a server-initiated forwarded-tcpip
68// channel. One bind == one real listener_pool[] slot; one bridge == one accepted
69// conn_pool slot glued to one SSH channel. All storage static (no heap).
70// ===========================================================================
71
72// A remote-forward binding: a listener this SSH connection asked us to open.
73struct SshRFwdBind
74{
75 bool active;
76 uint8_t ssh_slot;
77 uint8_t listener_idx; // slot in listener_pool[]
78 uint16_t bind_port; // port bound on the device
79 char bind_addr[DETWS_SSH_FWD_HOST_MAX]; // address the client requested (echoed in CHANNEL_OPEN)
80};
81
82// One bridged connection: an accepted TCP socket glued to a forwarded-tcpip channel.
83struct SshRFwdBridge
84{
85 bool active;
86 bool confirmed; // client CONFIRMED the channel: bytes may flow
87 uint8_t ssh_slot; // the owning SSH connection
88 uint8_t conn_slot; // the accepted TCP conn_pool slot
89 uint32_t channel; // our local forwarded-tcpip channel id
90};
91
92// All SSH remote-forward (ssh -R) state, owned by one instance (internal linkage): the listener
93// bindings and the accepted-connection bridges. One named owner, unreachable cross-TU.
94struct SshRFwdCtx
95{
96 SshRFwdBind rbind[DETWS_SSH_RFWD_MAX];
97 SshRFwdBridge rbridge[DETWS_SSH_RFWD_BRIDGE_MAX];
98};
99static SshRFwdCtx s_rfwd;
100
101static int rbind_find_free()
102{
103 for (int i = 0; i < DETWS_SSH_RFWD_MAX; i++)
104 if (!s_rfwd.rbind[i].active)
105 return i;
106 return -1;
107}
108static SshRFwdBind *rbind_by_listener(uint8_t listener_idx)
109{
110 for (int i = 0; i < DETWS_SSH_RFWD_MAX; i++)
111 if (s_rfwd.rbind[i].active && s_rfwd.rbind[i].listener_idx == listener_idx)
112 return &s_rfwd.rbind[i];
113 return nullptr;
114}
115static SshRFwdBind *rbind_find(uint8_t ssh_slot, uint16_t port)
116{
117 for (int i = 0; i < DETWS_SSH_RFWD_MAX; i++)
118 if (s_rfwd.rbind[i].active && s_rfwd.rbind[i].ssh_slot == ssh_slot && s_rfwd.rbind[i].bind_port == port)
119 return &s_rfwd.rbind[i];
120 return nullptr;
121}
122static int rbridge_find_free()
123{
124 for (int i = 0; i < DETWS_SSH_RFWD_BRIDGE_MAX; i++)
125 if (!s_rfwd.rbridge[i].active)
126 return i;
127 return -1;
128}
129static SshRFwdBridge *rbridge_by_conn(uint8_t conn_slot)
130{
131 for (int i = 0; i < DETWS_SSH_RFWD_BRIDGE_MAX; i++)
132 if (s_rfwd.rbridge[i].active && s_rfwd.rbridge[i].conn_slot == conn_slot)
133 return &s_rfwd.rbridge[i];
134 return nullptr;
135}
136static SshRFwdBridge *rbridge_by_channel(uint8_t ssh_slot, uint32_t channel)
137{
138 for (int i = 0; i < DETWS_SSH_RFWD_BRIDGE_MAX; i++)
139 if (s_rfwd.rbridge[i].active && s_rfwd.rbridge[i].ssh_slot == ssh_slot && s_rfwd.rbridge[i].channel == channel)
140 return &s_rfwd.rbridge[i];
141 return nullptr;
142}
143
144// Move accepted-socket bytes to the client over the SSH channel. Read only what the
145// channel peer window (and max packet) allow so ssh_conn_send never has to reject
146// bytes already pulled from the ring; bounded per poll so one tunnel cannot starve
147// the others. Leftover bytes stay in the rx ring (backpressure) for the next poll.
148static void rbridge_pump_to_client(SshRFwdBridge *br)
149{
150 if (br->channel >= DETWS_SSH_MAX_CHANNELS)
151 return;
152 SshChannel *c = &ssh_chan[br->ssh_slot][br->channel];
153 uint8_t buf[DETWS_SSH_FWD_CHUNK];
154 for (int burst = 0; burst < 4; burst++)
155 {
156 size_t avail = det_conn_available(br->conn_slot);
157 uint32_t win = c->peer_window;
158 if (avail == 0 || win == 0 || !c->open)
159 break;
160 size_t budget = avail;
161 if (budget > win)
162 budget = win;
163 if (c->peer_max_pkt && budget > c->peer_max_pkt)
164 budget = c->peer_max_pkt;
165 if (budget > sizeof(buf))
166 budget = sizeof(buf);
167 size_t n = det_conn_read(br->conn_slot, buf, budget);
168 if (n == 0)
169 break;
170 if (ssh_conn_send(br->ssh_slot, br->channel, buf, n) < 0)
171 break; // channel gone: retry next poll
172 }
173}
174
175// direct-tcpip open: check policy, connect to host:port (blocking), bind a slot.
176// Returns 0 to confirm the channel, < 0 to refuse (denied / connect failed / full).
177static int on_forward_open(uint8_t ssh_slot, uint32_t channel, const char *host, size_t host_len, uint16_t port)
178{
179 int idx = fwd_find_free();
180 if (idx < 0)
181 return -1; // no forward capacity
182 char hbuf[DETWS_SSH_FWD_HOST_MAX];
183 if (host_len == 0 || host_len >= sizeof(hbuf))
184 return -1;
185 memcpy(hbuf, host, host_len);
186 hbuf[host_len] = 0;
187 if (s_fwd.policy && !s_fwd.policy(hbuf, port))
188 return -1; // target administratively denied
189 int cid = det_client_open(hbuf, port, DETWS_SSH_FWD_CONNECT_MS); // blocks on DNS + connect
190 if (cid < 0)
191 return -1; // -> CHANNEL_OPEN_FAILURE (connect failed)
192 s_fwd.fwd[idx].active = true;
193 s_fwd.fwd[idx].ssh_slot = ssh_slot;
194 s_fwd.fwd[idx].channel = channel;
195 s_fwd.fwd[idx].cid = cid;
196 return 0;
197}
198
199// Inbound channel bytes. direct-tcpip (ssh -L): client -> outbound target socket.
200// forwarded-tcpip (ssh -R): client -> the accepted socket we bridged back to it.
201static void on_forward_data(uint8_t ssh_slot, uint32_t channel, const uint8_t *data, size_t len)
202{
203 SshFwd *f = fwd_lookup(ssh_slot, channel);
204 if (f)
205 {
206 det_client_send(f->cid, data, len);
207 return;
208 }
209 SshRFwdBridge *br = rbridge_by_channel(ssh_slot, channel);
210 if (br && br->confirmed)
211 {
212 det_conn_send(br->conn_slot, data, (u16_t)len);
213 det_conn_flush(br->conn_slot);
214 }
215}
216
217// ---------------------------------------------------------------------------
218// Remote-forward seam (GLOBAL_REQUEST tcpip-forward / cancel-tcpip-forward, ssh -R)
219// ---------------------------------------------------------------------------
220
221// Open a listener bound to bind_port and remember it for this SSH connection.
222// Returns the bound port (>= 0) on success, -1 to refuse.
223static int on_rforward_open(uint8_t ssh_slot, const char *addr, size_t addr_len, uint16_t bind_port)
224{
225 if (bind_port == 0)
226 return -1; // ephemeral-port allocation is not supported: require an explicit port
227 if (rbind_find(ssh_slot, bind_port))
228 return -1; // already forwarding this port on this connection
229 int bi = rbind_find_free();
230 if (bi < 0)
231 return -1; // remote-forward table full
232 // Find a free listener_pool slot (the app's own listeners are .active).
233 int li = -1;
234 for (int k = 0; k < MAX_LISTENERS; k++)
235 if (!listener_pool[k].active)
236 {
237 li = k;
238 break;
239 }
240 if (li < 0)
241 return -1; // no listener capacity
242 // Dynamic (tcpip_thread-marshaled) create: this runs in the SSH worker task.
243 if (listener_add_dynamic((uint8_t)li, bind_port, ConnProto::PROTO_SSH_RFWD) != 1)
244 return -1; // bind failed (port already in use, etc.)
245
246 s_rfwd.rbind[bi].active = true;
247 s_rfwd.rbind[bi].ssh_slot = ssh_slot;
248 s_rfwd.rbind[bi].listener_idx = (uint8_t)li;
249 s_rfwd.rbind[bi].bind_port = bind_port;
250 size_t al = addr_len < sizeof(s_rfwd.rbind[bi].bind_addr) - 1 ? addr_len : sizeof(s_rfwd.rbind[bi].bind_addr) - 1;
251 memcpy(s_rfwd.rbind[bi].bind_addr, addr, al);
252 s_rfwd.rbind[bi].bind_addr[al] = 0;
253 return bind_port;
254}
255
256// Cancel a remote forward: stop accepting new connections (existing bridges finish).
257static int on_rforward_cancel(uint8_t ssh_slot, const char *addr, size_t addr_len, uint16_t bind_port)
258{
259 (void)addr;
260 (void)addr_len;
261 SshRFwdBind *b = rbind_find(ssh_slot, bind_port);
262 if (!b)
263 return -1;
264 listener_stop_dynamic(b->listener_idx);
265 b->active = false;
266 return 0;
267}
268
269// The client's reply to a server-initiated forwarded-tcpip open.
270static void on_forward_confirm(uint8_t ssh_slot, uint32_t channel, bool ok)
271{
272 SshRFwdBridge *br = rbridge_by_channel(ssh_slot, channel);
273 if (!br)
274 return;
275 if (ok)
276 br->confirmed = true; // bytes may now flow (pumped on the next poll)
277 else
278 {
279 det_conn_close(br->conn_slot); // client refused the tunnel: drop the accepted socket
280 br->active = false;
281 }
282}
283
284// ---------------------------------------------------------------------------
285// ConnProto::PROTO_SSH_RFWD handler: an inbound connection on a forwarded port.
286// ---------------------------------------------------------------------------
287
288static void rfwd_on_accept(uint8_t conn_slot)
289{
290 SshRFwdBind *b = rbind_by_listener(conn_pool[conn_slot].listener_id);
291 if (!b)
292 {
293 det_conn_close(conn_slot); // no binding owns this listener (stale): drop
294 return;
295 }
296 int idx = rbridge_find_free();
297 if (idx < 0)
298 {
299 det_conn_close(conn_slot); // bridge table full
300 return;
301 }
302 // Originator address (advisory, RFC 4254 §7.2); the peer port is not exposed by the
303 // transport, so it is reported as 0.
304 char orig[DET_IP_STR_MAX];
305 orig[0] = 0;
306 DetIp rip;
307 if (det_conn_remote_addr(conn_slot, &rip))
308 det_ip_format(&rip, orig, sizeof(orig));
309 // Open the forwarded-tcpip channel back to the client, echoing the requested bind
310 // address as the "address that was connected".
311 int ch = ssh_conn_open_forwarded(b->ssh_slot, b->bind_addr[0] ? b->bind_addr : "0.0.0.0", b->bind_port, orig, 0);
312 if (ch < 0)
313 {
314 det_conn_close(conn_slot); // SSH connection gone or channel pool full
315 return;
316 }
317 s_rfwd.rbridge[idx].active = true;
318 s_rfwd.rbridge[idx].confirmed = false;
319 s_rfwd.rbridge[idx].ssh_slot = b->ssh_slot;
320 s_rfwd.rbridge[idx].conn_slot = conn_slot;
321 s_rfwd.rbridge[idx].channel = (uint32_t)ch;
322}
323
324static void rfwd_on_data(uint8_t conn_slot)
325{
326 SshRFwdBridge *br = rbridge_by_conn(conn_slot);
327 if (br && br->confirmed)
328 rbridge_pump_to_client(br); // otherwise the bytes wait in the ring until confirmed
329}
330
331static void rfwd_on_close(uint8_t conn_slot)
332{
333 SshRFwdBridge *br = rbridge_by_conn(conn_slot);
334 if (!br)
335 return;
336 ssh_conn_close_channel(br->ssh_slot, br->channel); // tell the client EOF + CLOSE
337 br->active = false;
338}
339
340static void rfwd_on_poll(uint8_t conn_slot)
341{
342 // The dispatch loop now polls every slot uniformly; it used to poll only ACTIVE slots, so preserve
343 // that gate here (a closing/free forward slot has nothing to pump).
344 if (!det_conn_active(conn_slot))
345 return;
346 SshRFwdBridge *br = rbridge_by_conn(conn_slot);
347 if (!br || !br->confirmed)
348 return;
349 // The client closed its side of the channel -> close the accepted socket.
350 if (br->channel < DETWS_SSH_MAX_CHANNELS && !ssh_chan[br->ssh_slot][br->channel].open)
351 {
352 det_conn_close(conn_slot);
353 br->active = false;
354 return;
355 }
356 rbridge_pump_to_client(br); // drain anything the window blocked earlier
357}
358
359static const ProtoHandler s_rfwd_handler = {rfwd_on_accept, rfwd_on_data, rfwd_on_close, rfwd_on_poll};
360
361void ssh_forward_set_policy_cb(SshForwardPolicyCb cb)
362{
363 s_fwd.policy = cb;
364}
365
366void ssh_forward_begin()
367{
368 for (int i = 0; i < DETWS_SSH_FWD_MAX; i++)
369 s_fwd.fwd[i].active = false;
370 for (int i = 0; i < DETWS_SSH_RFWD_MAX; i++)
371 s_rfwd.rbind[i].active = false;
372 for (int i = 0; i < DETWS_SSH_RFWD_BRIDGE_MAX; i++)
373 s_rfwd.rbridge[i].active = false;
374 ssh_channel_set_forward_open_cb(on_forward_open);
375 ssh_channel_set_forward_data_cb(on_forward_data);
376 // Remote forwarding (ssh -R): the request/cancel seam, the open-confirmation
377 // callback, and the accept handler for connections on a forwarded port.
378 ssh_channel_set_rforward_open_cb(on_rforward_open);
379 ssh_channel_set_rforward_cancel_cb(on_rforward_cancel);
380 ssh_channel_set_forward_confirm_cb(on_forward_confirm);
382}
383
384void ssh_forward_pump(uint8_t ssh_slot)
385{
386 uint8_t buf[DETWS_SSH_FWD_CHUNK];
387 for (int i = 0; i < DETWS_SSH_FWD_MAX; i++)
388 {
389 SshFwd *f = &s_fwd.fwd[i];
390 if (!f->active || f->ssh_slot != ssh_slot)
391 continue;
392 if (f->channel >= DETWS_SSH_MAX_CHANNELS) // defensive: stale binding
393 {
394 det_client_close(f->cid);
395 f->active = false;
396 continue;
397 }
398 SshChannel *c = &ssh_chan[ssh_slot][f->channel];
399
400 // Client closed its side of the channel: drop the target socket.
401 if (!c->open)
402 {
403 det_client_close(f->cid);
404 f->active = false;
405 continue;
406 }
407
408 // Target -> client: forward what the peer window allows, bounded per poll.
409 for (int burst = 0; burst < kFwdBurst; burst++)
410 {
411 size_t avail = det_client_available(f->cid);
412 uint32_t win = c->peer_window;
413 if (avail == 0 || win == 0)
414 break;
415 size_t budget = avail;
416 if (budget > win)
417 budget = win;
418 if (c->peer_max_pkt && budget > c->peer_max_pkt)
419 budget = c->peer_max_pkt;
420 if (budget > sizeof(buf))
421 budget = sizeof(buf);
422 size_t n = det_client_read(f->cid, buf, budget);
423 if (n == 0)
424 break;
425 if (ssh_conn_send(ssh_slot, f->channel, buf, n) < 0)
426 break; // sized to the window, so this should send; retry next poll
427 }
428
429 // Target closed (FIN) and fully drained: EOF + CLOSE to the client, free.
430 if (det_client_is_closed(f->cid) && det_client_available(f->cid) == 0)
431 {
432 ssh_conn_close_channel(ssh_slot, f->channel);
433 det_client_close(f->cid);
434 f->active = false;
435 }
436 }
437}
438
439void ssh_forward_reset(uint8_t ssh_slot)
440{
441 // direct-tcpip (ssh -L): close outbound target sockets this connection owned.
442 for (int i = 0; i < DETWS_SSH_FWD_MAX; i++)
443 if (s_fwd.fwd[i].active && s_fwd.fwd[i].ssh_slot == ssh_slot)
444 {
445 det_client_close(s_fwd.fwd[i].cid);
446 s_fwd.fwd[i].active = false;
447 }
448 // remote (ssh -R): stop this connection's forwarded listeners and drop every
449 // accepted socket it had bridged (the SSH channels go away with the connection).
450 for (int i = 0; i < DETWS_SSH_RFWD_MAX; i++)
451 if (s_rfwd.rbind[i].active && s_rfwd.rbind[i].ssh_slot == ssh_slot)
452 {
453 listener_stop_dynamic(s_rfwd.rbind[i].listener_idx);
454 s_rfwd.rbind[i].active = false;
455 }
456 for (int i = 0; i < DETWS_SSH_RFWD_BRIDGE_MAX; i++)
457 if (s_rfwd.rbridge[i].active && s_rfwd.rbridge[i].ssh_slot == ssh_slot)
458 {
459 det_conn_close(s_rfwd.rbridge[i].conn_slot);
460 s_rfwd.rbridge[i].active = false;
461 }
462}
463
464#endif // DETWS_SSH_PORT_FORWARD
#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 DETWS_SSH_RFWD_BRIDGE_MAX
Maximum concurrent bridged connections across all remote forwards.
#define DETWS_SSH_FWD_CHUNK
Max bytes moved per forward channel per poll, target -> client (<= SSH_PKT_BUF_SIZE).
#define DETWS_SSH_FWD_MAX
Maximum concurrent forwarded TCP connections (must be <= DETWS_CLIENT_CONNS).
#define DETWS_SSH_FWD_CONNECT_MS
Blocking connect timeout (ms) when opening a forward target.
#define DETWS_SSH_FWD_HOST_MAX
Maximum forward target hostname length including null terminator.
#define DETWS_SSH_RFWD_MAX
Maximum concurrent remote-forward listeners (ssh -R / tcpip-forward).
#define DETWS_SSH_MAX_CHANNELS
Maximum concurrent SSH channels per connection (RFC 4254 multiplexing).
size_t det_client_available(int)
Wire bytes currently buffered and ready to read.
Definition client.cpp:326
bool det_client_is_closed(int)
True once the peer closed (FIN) or the connection errored.
Definition client.cpp:318
int det_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:310
size_t det_client_read(int, uint8_t *, size_t)
Drain up to cap buffered wire bytes into buf; returns the count.
Definition client.cpp:330
void det_client_close(int)
Tear down the connection (marshaled) and return the slot to the pool.
Definition client.cpp:334
bool det_client_send(int, const void *, size_t)
Queue len wire bytes for transmission (marshaled tcp_write + output).
Definition client.cpp:322
Layer 4 outbound TCP client transport - the client-side peer of the (server) transport in tcp....
size_t det_ip_format(const DetIp *ip, char *out, size_t cap)
Format ip into out as its RFC 5952 canonical text.
Definition ip.cpp:388
Layer 3 (Network) - a family-tagged IP address (IPv4 or IPv6) with RFC-faithful text parsing,...
#define DET_IP_STR_MAX
Longest text an det_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:619
Listener listener_pool[MAX_LISTENERS]
Static pool of listener contexts. Defined in listener.cpp.
Definition listener.cpp:34
void listener_stop_dynamic(uint8_t idx)
Definition listener.cpp:655
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
void ssh_channel_set_forward_data_cb(SshForwardDataCb cb)
Install the direct-tcpip forward inbound-data callback.
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).
void ssh_channel_set_rforward_cancel_cb(SshRemoteForwardCancelCb cb)
Install the remote-forward (ssh -R) cancel callback (opt-in).
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...
void ssh_channel_set_rforward_open_cb(SshRemoteForwardOpenCb cb)
Install the remote-forward (ssh -R) open-policy callback (opt-in).
SSH connection protocol - multiplexed "session" channels (RFC 4254).
int 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:126
int 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:158
int 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:97
Glue between the TCP transport (conn_pool) and the SSH protocol stack.
SSH direct-tcpip port-forwarding owner (the ssh -L target side).
A v4 or v6 address in network (big-endian) byte order.
Definition ip.h:52
Per-protocol connection event/poll callbacks (Layer 5 dispatch vtable).
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
bool det_conn_send(uint8_t slot, const void *data, u16_t len)
Send len bytes on connection slot (copies data; TLS-aware).
Definition tcp.cpp:362
void det_conn_flush(uint8_t slot)
Flush queued bytes / finish the send on slot (TLS-aware).
Definition tcp.cpp:413
void det_conn_close(uint8_t slot)
Close connection slot gracefully (tcp_close), aborting if the FIN cannot be queued....
Definition tcp.cpp:467
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:347
bool det_conn_remote_addr(uint8_t slot, DetIp *out)
The connected peer's address as a family-tagged DetIp (IPv4 or IPv6).
Definition tcp.cpp:691
Layer 4 (Transport) - TCP connection pool, ring buffers, and lwIP integration.