11#if PC_SSH_PORT_FORWARD
41 SshForwardPolicyCb policy =
nullptr;
43static SshFwdCtx s_fwd;
47static const int kFwdBurst = 4;
49static int fwd_find_free()
53 if (!s_fwd.fwd[i].active)
61static SshFwd *fwd_lookup(uint8_t ssh_slot, uint32_t channel)
65 if (s_fwd.fwd[i].active && s_fwd.fwd[i].ssh_slot == ssh_slot && s_fwd.fwd[i].channel == channel)
107static SshRFwdCtx s_rfwd;
109static int rbind_find_free()
113 if (!s_rfwd.rbind[i].active)
120static SshRFwdBind *rbind_by_listener(uint8_t listener_idx)
124 if (s_rfwd.rbind[i].active && s_rfwd.rbind[i].listener_idx == listener_idx)
126 return &s_rfwd.rbind[i];
131static SshRFwdBind *rbind_find(uint8_t ssh_slot, uint16_t port)
135 if (s_rfwd.rbind[i].active && s_rfwd.rbind[i].ssh_slot == ssh_slot && s_rfwd.rbind[i].bind_port == port)
137 return &s_rfwd.rbind[i];
142static int rbridge_find_free()
146 if (!s_rfwd.rbridge[i].active)
153static SshRFwdBridge *rbridge_by_conn(uint8_t conn_slot)
157 if (s_rfwd.rbridge[i].active && s_rfwd.rbridge[i].conn_slot == conn_slot)
159 return &s_rfwd.rbridge[i];
164static SshRFwdBridge *rbridge_by_channel(uint8_t ssh_slot, uint32_t channel)
168 if (s_rfwd.rbridge[i].active && s_rfwd.rbridge[i].ssh_slot == ssh_slot && s_rfwd.rbridge[i].channel == channel)
170 return &s_rfwd.rbridge[i];
180static void rbridge_pump_to_client(SshRFwdBridge *br)
188 for (
int burst = 0; burst < 4; burst++)
190 size_t avail = pc_conn_available(br->conn_slot);
192 if (avail == 0 || win == 0 || !c->
open)
196 size_t budget = avail;
205 if (budget >
sizeof(buf))
207 budget =
sizeof(buf);
209 size_t n = pc_conn_read(br->conn_slot, buf, budget);
223static int on_forward_open(uint8_t ssh_slot, uint32_t channel,
const char *host,
size_t host_len, uint16_t port)
225 int idx = fwd_find_free();
231 if (host_len == 0 || host_len >=
sizeof(hbuf))
235 memcpy(hbuf, host, host_len);
237 if (s_fwd.policy && !s_fwd.policy(hbuf, port))
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;
255static void on_forward_data(uint8_t ssh_slot, uint32_t channel,
const uint8_t *data,
size_t len)
257 SshFwd *f = fwd_lookup(ssh_slot, channel);
263 SshRFwdBridge *br = rbridge_by_channel(ssh_slot, channel);
264 if (br && br->confirmed)
277static int on_rforward_open(uint8_t ssh_slot,
const char *addr,
size_t addr_len, uint16_t bind_port)
283 if (rbind_find(ssh_slot, bind_port))
287 int bi = rbind_find_free();
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;
323static int on_rforward_cancel(uint8_t ssh_slot,
const char *addr,
size_t addr_len, uint16_t bind_port)
327 SshRFwdBind *b = rbind_find(ssh_slot, bind_port);
338static void on_forward_confirm(uint8_t ssh_slot, uint32_t channel,
bool ok)
340 SshRFwdBridge *br = rbridge_by_channel(ssh_slot, channel);
347 br->confirmed =
true;
360static void rfwd_on_accept(uint8_t conn_slot)
362 SshRFwdBind *b = rbind_by_listener(
conn_pool[conn_slot].listener_id);
368 int idx = rbridge_find_free();
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;
398static void rfwd_on_data(uint8_t conn_slot)
400 SshRFwdBridge *br = rbridge_by_conn(conn_slot);
401 if (br && br->confirmed)
403 rbridge_pump_to_client(br);
407static void rfwd_on_close(uint8_t conn_slot)
409 SshRFwdBridge *br = rbridge_by_conn(conn_slot);
418static void rfwd_on_poll(uint8_t conn_slot)
422 if (!pc_conn_active(conn_slot))
426 SshRFwdBridge *br = rbridge_by_conn(conn_slot);
427 if (!br || !br->confirmed)
438 rbridge_pump_to_client(br);
441static const ProtoHandler s_rfwd_handler = {rfwd_on_accept, rfwd_on_data, rfwd_on_close, rfwd_on_poll};
443void pc_ssh_forward_set_policy_cb(SshForwardPolicyCb cb)
448void pc_ssh_forward_begin()
452 s_fwd.fwd[i].active =
false;
456 s_rfwd.rbind[i].active =
false;
460 s_rfwd.rbridge[i].active =
false;
472void pc_ssh_forward_pump(uint8_t ssh_slot)
477 SshFwd *f = &s_fwd.fwd[i];
478 if (!f->active || f->ssh_slot != ssh_slot)
499 for (
int burst = 0; burst < kFwdBurst; burst++)
503 if (avail == 0 || win == 0)
507 size_t budget = avail;
516 if (budget >
sizeof(buf))
518 budget =
sizeof(buf);
541void pc_ssh_forward_reset(uint8_t ssh_slot)
546 if (s_fwd.fwd[i].active && s_fwd.fwd[i].ssh_slot == ssh_slot)
549 s_fwd.fwd[i].active =
false;
556 if (s_rfwd.rbind[i].active && s_rfwd.rbind[i].ssh_slot == ssh_slot)
559 s_rfwd.rbind[i].active =
false;
564 if (s_rfwd.rbridge[i].active && s_rfwd.rbridge[i].ssh_slot == ssh_slot)
567 s_rfwd.rbridge[i].active =
false;
#define PC_SSH_MAX_CHANNELS
bool pc_client_send(int, const void *, size_t)
Queue len wire bytes for transmission (marshaled tcp_write + output).
size_t pc_client_read(int, uint8_t *, size_t)
Drain up to cap buffered wire bytes into buf; returns the count.
bool pc_client_is_closed(int)
True once the peer closed (FIN) or the connection errored.
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.
size_t pc_client_available(int)
Wire bytes currently buffered and ready to read.
void pc_client_close(int)
Tear down the connection (marshaled) and return the slot to the pool.
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.
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).
int32_t listener_add_dynamic(uint8_t idx, uint16_t port, ConnProto proto)
Add / stop a listener from a running task (thread-safe variant).
Listener listener_pool[MAX_LISTENERS]
Static pool of listener contexts. Defined in listener.cpp.
void listener_stop_dynamic(uint8_t idx)
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).
#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.
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...
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 ...
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.
bool open
True once the channel is confirmed open both ways.
SshFlow flow
RFC 4254 sec 5.2 window pair (owner: ssh_flow_control.*).
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.
TcpConn conn_pool[CONN_POOL_SLOTS]
Static pool of connection contexts. Defined in tcp.cpp. Sized CONN_POOL_SLOTS: MAX_CONNS TCP slots pl...
void pc_conn_flush(uint8_t slot)
Flush queued bytes / finish the send on slot (TLS-aware).
void pc_conn_close(uint8_t slot)
Close connection slot gracefully (tcp_close), aborting if the FIN cannot be queued....
bool pc_conn_send(uint8_t slot, const void *data, u16_t len)
Send len bytes on connection slot (copies data; TLS-aware).
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).
Layer 4 (Transport) - TCP connection pool, ring buffers, and lwIP integration.