Trouble with port forwarding over proxy control socket
ding
ding at diinngg.com
Sun Dec 28 00:24:50 AEDT 2025
On 22/12/2025 04:21, Damien Miller wrote:
> On Sun, 21 Dec 2025, ding wrote:
>
>> Hi,
>>
>> This is attempt 3 at sending this email, apologies to the admins.
>>
>> I'm working on support for connecting to a control socket in the Go SSH
>> package
>> (a continuation of https://github.com/golang/crypto/pull/205), but I'm having
>> trouble getting port forwarding to work correctly.
>>
>> As far as I can tell the comment at
>> https://github.com/openssh/openssh-portable/blob/b652322cdc5e94f059b37a8fb87e44ccb1cdff33/channels.c#L3169
>> suggests that it *should* work, but looking through the code I can't see how.
>> The problem I'm hitting is that the response to the global "tcpip-forward"
>> request never gets forwarded downstream back over the control socket.
>>
>> I can only assume that anyone currently using this is just not waiting for the
>> response? If the port is known ahead of time and you don't care about handling
>> a failure response then I guess you can just ignore it? It seems like it
>> definitely breaks the ability to use port 0 for remote forwarding though as
>> there's no way to find out which port was chosen on the server.
>>
>> This seems to break with the OpenSSH client with `-O proxy` as well. Running
>> `ssh -R 0:127.0.0.1:<localport> -N <host>` allows connecting from a random
>> port
>> on the server to the local port (and the log shows "Allocated port <...> for
>> remote forward to 127.0.0.1:<localport>"). Doing the same through a control
>> socket with `ssh -O proxy -R 0:127.0.0.1:<localport> -N <host>` doesn't output
>> the port to log, finding the port on the server with `lsof` and writing to it
>> results in the control master process logging "WARNING: Server requests
>> forwarding for unknown listen_port <...>".
>>
>> Building "ssh" with the attached patch fixes the issue for me (at least for
>> the
>> Go implementation), but including "clientloop.h" in "channels.c" seems wrong,
>> and it breaks a bunch of build targets. There's a comment above
>> `struct global_confirm` suggesting that it should be moved elsewhere, so I
>> assume that problem can be solved by moving it and the related functions to a
>> more accessible place. There could also be bigger technical problems with this
>> approach that I'm not aware of.
>>
>> Before putting more effort into this I wanted to check if anyone had any
>> thoughts or better ideas on what the problem might be, or how to fix it?
> AFAIK your analysis is 100% correct, and thanks for looking so deeply
> into this.
>
> I don't have a great idea on how to fix this yet either, though perhaps
> the queue of pending global confirms could move from clientloop.c to
> channels.c, with the actual TAILQ anchor in struct ssh_channels.
>
> This wouldn't be a layering violation because 1) global confirms *are*
> channels-related, 2) both ssh and sshd could conceivably issue and
> therefore want to track them (though sshd doesn't ATM) and 3) I think
> you've demonstrated that the mux proxy code needs this facility to
> function properly.
>
> Anyway that's one possible approach...
>
> -d
> _______________________________________________
> openssh-unix-dev mailing list
> openssh-unix-dev at mindrot.org
> https://lists.mindrot.org/mailman/listinfo/openssh-unix-dev
I've had some more time to work on this, and I think I've gotten it to a
working state.
The first attached patch moves the global requests confirm list into
`struct ssh_channels` as suggested, the second patch then adds support
for both TCP and unix socket remote forwarding. I've added some tests in
`regress/forwarding-proxy.sh`, essentially just a copy of
`regress/forwarding.sh` (the file header is a bit of a guess).
I'll hopefully look at submitting a bug report for this later on today.
One outstanding question: it looks like there's a memory leak of the
passed-in context in `mux_confirm_remote_forward()` in the first `if`
statement, if so then I guess that should have its own report?
Thanks!
-------------- next part --------------
From 543f62efa3f9d289d8913dbcaa7b8f1b15814f36 Mon Sep 17 00:00:00 2001
From: ding <ding at diinngg.com>
Date: Wed, 24 Dec 2025 22:41:12 +0000
Subject: [PATCH] Move global requests callback list to struct ssh_channels
---
channels.c | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++
channels.h | 5 +++++
clientloop.c | 59 ++++--------------------------------------------
clientloop.h | 4 ----
mux.c | 2 +-
ssh.c | 3 ++-
6 files changed, 75 insertions(+), 61 deletions(-)
diff --git a/channels.c b/channels.c
index 80014ff34..7a3a22979 100644
--- a/channels.c
+++ b/channels.c
@@ -96,6 +96,15 @@
/* Per-channel callback for pre/post IO actions */
typedef void chan_fn(struct ssh *, Channel *c);
+/* Global request success/failure callbacks */
+struct global_confirm {
+ TAILQ_ENTRY(global_confirm) entry;
+ global_confirm_cb *cb;
+ void *ctx;
+ int ref_count;
+};
+TAILQ_HEAD(global_confirms, global_confirm);
+
/*
* Data structure for storing which hosts are permitted for forward requests.
* The local sides of any remote forwards are stored in this array to prevent
@@ -170,6 +179,9 @@ struct ssh_channels {
chan_fn **channel_pre;
chan_fn **channel_post;
+ /* Global confirm callbacks */
+ struct global_confirms global_confirms;
+
/* -- tcp forwarding */
struct permission_set local_perms;
struct permission_set remote_perms;
@@ -236,6 +248,8 @@ channel_init_channels(struct ssh *ssh)
fatal_f("allocation failed");
sc->channels_alloc = 10;
sc->channels = xcalloc(sc->channels_alloc, sizeof(*sc->channels));
+ sc->global_confirms =
+ (struct global_confirms)TAILQ_HEAD_INITIALIZER(sc->global_confirms);
sc->IPv4or6 = AF_UNSPEC;
sc->bulk_classifier_tty = xstrdup(CHANNEL_BULK_TTY);
sc->bulk_classifier_notty = xstrdup(CHANNEL_BULK_NOTTY);
@@ -859,6 +873,8 @@ void
channel_free_channels(struct ssh *ssh)
{
struct ssh_channels *sc;
+ struct global_confirms *gcs;
+ struct global_confirm *gc;
if (ssh == NULL || ssh->chanctxt == NULL)
return;
@@ -868,6 +884,11 @@ channel_free_channels(struct ssh *ssh)
channel_clear_permission(ssh, FORWARD_ADM, FORWARD_LOCAL);
channel_clear_permission(ssh, FORWARD_ADM, FORWARD_REMOTE);
sc = ssh->chanctxt;
+ gcs = &sc->global_confirms;
+ while ((gc = TAILQ_FIRST(gcs)) != NULL) {
+ TAILQ_REMOVE(gcs, gc, entry);
+ freezero(gc, sizeof(*gc));
+ }
free(sc->bulk_classifier_tty);
free(sc->bulk_classifier_notty);
free(sc->channel_pre);
@@ -1312,6 +1333,48 @@ channel_set_fds(struct ssh *ssh, int id, int rfd, int wfd, int efd,
fatal_fr(r, "channel %i", c->self);
}
+void
+channel_register_global_confirm(struct ssh *ssh, global_confirm_cb *cb,
+ void *ctx)
+{
+ struct global_confirms *gcs = &ssh->chanctxt->global_confirms;
+ struct global_confirm *gc, *last_gc;
+
+ /* Coalesce identical callbacks */
+ last_gc = TAILQ_LAST(gcs, global_confirms);
+ if (last_gc && last_gc->cb == cb && last_gc->ctx == ctx) {
+ if (++last_gc->ref_count >= INT_MAX)
+ fatal_f("last_gc->ref_count = %d",
+ last_gc->ref_count);
+ return;
+ }
+
+ gc = xcalloc(1, sizeof(*gc));
+ gc->cb = cb;
+ gc->ctx = ctx;
+ gc->ref_count = 1;
+ TAILQ_INSERT_TAIL(gcs, gc, entry);
+}
+
+int
+channel_global_request_reply(int type, u_int32_t seq, struct ssh *ssh)
+{
+ struct global_confirms *gcs = &ssh->chanctxt->global_confirms;
+ struct global_confirm *gc;
+
+ if ((gc = TAILQ_FIRST(gcs)) == NULL)
+ return 0;
+ if (gc->cb != NULL)
+ gc->cb(ssh, type, seq, gc->ctx);
+ if (--gc->ref_count <= 0) {
+ TAILQ_REMOVE(gcs, gc, entry);
+ freezero(gc, sizeof(*gc));
+ }
+
+ ssh_packet_set_alive_timeouts(ssh, 0);
+ return 0;
+}
+
static void
channel_pre_listener(struct ssh *ssh, Channel *c)
{
diff --git a/channels.h b/channels.h
index 7456541f8..edfa50dc1 100644
--- a/channels.h
+++ b/channels.h
@@ -318,6 +318,11 @@ int channel_close_fd(struct ssh *, Channel *, int *);
void channel_send_window_changes(struct ssh *);
int channel_has_bulk(struct ssh *);
+/* global request confirmation callbacks */
+typedef void global_confirm_cb(struct ssh *, int, u_int32_t, void *);
+void channel_register_global_confirm(struct ssh *, global_confirm_cb *, void *);
+int channel_global_request_reply(int, u_int32_t, struct ssh *);
+
/* channel inactivity timeouts */
void channel_add_timeout(struct ssh *, const char *, int);
void channel_clear_timeouts(struct ssh *);
diff --git a/clientloop.c b/clientloop.c
index a78dfa6e0..fe3b98d63 100644
--- a/clientloop.c
+++ b/clientloop.c
@@ -174,18 +174,6 @@ struct channel_reply_ctx {
enum confirm_action action;
};
-/* Global request success/failure callbacks */
-/* XXX move to struct ssh? */
-struct global_confirm {
- TAILQ_ENTRY(global_confirm) entry;
- global_confirm_cb *cb;
- void *ctx;
- int ref_count;
-};
-TAILQ_HEAD(global_confirms, global_confirm);
-static struct global_confirms global_confirms =
- TAILQ_HEAD_INITIALIZER(global_confirms);
-
static void quit_message(const char *fmt, ...)
__attribute__((__format__ (printf, 1, 2)));
@@ -468,24 +456,6 @@ client_check_window_change(struct ssh *ssh)
channel_send_window_changes(ssh);
}
-static int
-client_global_request_reply(int type, u_int32_t seq, struct ssh *ssh)
-{
- struct global_confirm *gc;
-
- if ((gc = TAILQ_FIRST(&global_confirms)) == NULL)
- return 0;
- if (gc->cb != NULL)
- gc->cb(ssh, type, seq, gc->ctx);
- if (--gc->ref_count <= 0) {
- TAILQ_REMOVE(&global_confirms, gc, entry);
- freezero(gc, sizeof(*gc));
- }
-
- ssh_packet_set_alive_timeouts(ssh, 0);
- return 0;
-}
-
static void
schedule_server_alive_check(void)
{
@@ -508,7 +478,7 @@ server_alive_check(struct ssh *ssh)
(r = sshpkt_send(ssh)) != 0)
fatal_fr(r, "send packet");
/* Insert an empty placeholder to maintain ordering */
- client_register_global_confirm(NULL, NULL);
+ channel_register_global_confirm(ssh, NULL, NULL);
schedule_server_alive_check();
}
@@ -896,27 +866,6 @@ client_expect_confirm(struct ssh *ssh, int id, const char *request,
client_abandon_status_confirm, cr);
}
-void
-client_register_global_confirm(global_confirm_cb *cb, void *ctx)
-{
- struct global_confirm *gc, *last_gc;
-
- /* Coalesce identical callbacks */
- last_gc = TAILQ_LAST(&global_confirms, global_confirms);
- if (last_gc && last_gc->cb == cb && last_gc->ctx == ctx) {
- if (++last_gc->ref_count >= INT_MAX)
- fatal_f("last_gc->ref_count = %d",
- last_gc->ref_count);
- return;
- }
-
- gc = xcalloc(1, sizeof(*gc));
- gc->cb = cb;
- gc->ctx = ctx;
- gc->ref_count = 1;
- TAILQ_INSERT_TAIL(&global_confirms, gc, entry);
-}
-
/*
* Returns non-zero if the client is able to handle a hostkeys-00 at openssh.com
* hostkey update request.
@@ -2647,7 +2596,7 @@ client_input_hostkeys(struct ssh *ssh)
}
if ((r = sshpkt_send(ssh)) != 0)
fatal_fr(r, "send hostkeys-prove");
- client_register_global_confirm(
+ channel_register_global_confirm(ssh,
client_global_hostkeys_prove_confirm, ctx);
ctx = NULL; /* will be freed in callback */
prove_sent = 1;
@@ -2841,8 +2790,8 @@ client_init_dispatch(struct ssh *ssh)
ssh_dispatch_set(ssh, SSH2_MSG_KEXINIT, &kex_input_kexinit);
/* global request reply messages */
- ssh_dispatch_set(ssh, SSH2_MSG_REQUEST_FAILURE, &client_global_request_reply);
- ssh_dispatch_set(ssh, SSH2_MSG_REQUEST_SUCCESS, &client_global_request_reply);
+ ssh_dispatch_set(ssh, SSH2_MSG_REQUEST_FAILURE, &channel_global_request_reply);
+ ssh_dispatch_set(ssh, SSH2_MSG_REQUEST_SUCCESS, &channel_global_request_reply);
}
void
diff --git a/clientloop.h b/clientloop.h
index 1f550b35c..7997cee32 100644
--- a/clientloop.h
+++ b/clientloop.h
@@ -54,10 +54,6 @@ void *client_new_escape_filter_ctx(int);
void client_filter_cleanup(struct ssh *, int, void *);
int client_simple_escape_filter(struct ssh *, Channel *, char *, int);
-/* Global request confirmation callbacks */
-typedef void global_confirm_cb(struct ssh *, int, u_int32_t, void *);
-void client_register_global_confirm(global_confirm_cb *, void *);
-
/* Channel request confirmation callbacks */
enum confirm_action { CONFIRM_WARN = 0, CONFIRM_CLOSE, CONFIRM_TTY };
void client_expect_confirm(struct ssh *, int, const char *,
diff --git a/mux.c b/mux.c
index 53cbab0fc..9654b3d96 100644
--- a/mux.c
+++ b/mux.c
@@ -865,7 +865,7 @@ mux_master_process_open_fwd(struct ssh *ssh, u_int rid,
fctx->cid = c->self;
fctx->rid = rid;
fctx->fid = options.num_remote_forwards - 1;
- client_register_global_confirm(mux_confirm_remote_forward,
+ channel_register_global_confirm(ssh, mux_confirm_remote_forward,
fctx);
freefwd = 0;
c->mux_pause = 1; /* wait for mux_confirm_remote_forward */
diff --git a/ssh.c b/ssh.c
index 461b60975..38a812a40 100644
--- a/ssh.c
+++ b/ssh.c
@@ -2153,7 +2153,8 @@ ssh_init_forwarding(struct ssh *ssh, char **ifname)
if ((options.remote_forwards[i].handle =
channel_request_remote_forwarding(ssh,
&options.remote_forwards[i])) >= 0) {
- client_register_global_confirm(
+ channel_register_global_confirm(
+ ssh,
ssh_confirm_remote_forward,
&options.remote_forwards[i]);
forward_confirms_pending++;
--
2.51.0
-------------- next part --------------
From b6d51aff533d9050c76e41938536d963181cee63 Mon Sep 17 00:00:00 2001
From: ding <ding at diinngg.com>
Date: Sat, 27 Dec 2025 12:41:01 +0000
Subject: [PATCH] Add support for remote forwarding in proxy mode
---
channels.c | 242 +++++++++++++++++++++++++++++++++---
clientloop.c | 36 +++++-
packet.c | 4 +-
regress/Makefile | 1 +
regress/forwarding-proxy.sh | 124 ++++++++++++++++++
5 files changed, 383 insertions(+), 24 deletions(-)
create mode 100644 regress/forwarding-proxy.sh
diff --git a/channels.c b/channels.c
index 7a3a22979..5e642d51f 100644
--- a/channels.c
+++ b/channels.c
@@ -237,6 +237,12 @@ static int rdynamic_connect_finish(struct ssh *, Channel *);
/* Setup helper */
static void channel_handler_init(struct ssh_channels *sc);
+/* Remote forwarding permission check helpers */
+static int open_listen_match_tcpip(struct permission *allowed_open,
+ const char *requestedhost, u_short requestedport, int translate);
+static int open_listen_match_streamlocal(struct permission *allowed_open,
+ const char *requestedpath);
+
/* -- channel core */
void
@@ -3260,6 +3266,131 @@ channel_output_poll(struct ssh *ssh)
* easily.
*/
+static void
+channel_proxy_remote_forward(struct ssh *ssh, Channel *downstream,
+ const char *listen_host, const char *listen_path, int listen_port)
+{
+ permission_set_add(ssh, FORWARD_USER, FORWARD_LOCAL,
+ "<mux>", -1, listen_host, listen_path, listen_port, downstream);
+}
+
+static int
+channel_proxy_cancel_remote_forward_tcpip(struct ssh *ssh,
+ const char *listen_host, int listen_port)
+{
+ struct permission *perm = NULL, **permp;
+ u_int i, *npermp;
+
+ permission_set_get_array(ssh, FORWARD_USER, FORWARD_LOCAL, &permp, &npermp);
+ for (i = 0; i < *npermp; i++) {
+ perm = (*permp + i);
+ if (open_listen_match_tcpip(perm, listen_host, listen_port, 0)) {
+ fwd_perm_clear(perm);
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int
+channel_proxy_cancel_remote_forward_streamlocal(struct ssh *ssh,
+ const char *listen_path)
+{
+ struct permission *perm = NULL, **permp;
+ u_int i, *npermp;
+
+ permission_set_get_array(ssh, FORWARD_USER, FORWARD_LOCAL, &permp, &npermp);
+ for (i = 0; i < *npermp; i++) {
+ perm = (*permp + i);
+ if (open_listen_match_streamlocal(perm, listen_path)) {
+ fwd_perm_clear(perm);
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static void
+channel_proxy_cancel_remote_forward(struct ssh *ssh,
+ const char *listen_host, const char *listen_path, int listen_port)
+{
+ if (listen_path != NULL) {
+ if (channel_proxy_cancel_remote_forward_streamlocal(ssh, listen_path))
+ return;
+ } else {
+ if (channel_proxy_cancel_remote_forward_tcpip(ssh,
+ listen_host, listen_port))
+ return;
+ }
+ debug_f("requested forward not found");
+}
+
+struct channel_proxy_remote_forward_ctx {
+ int cid; /* Channel id. */
+ int cancel; /* If this is a cancel request. */
+ char *host; /* Remote forward host. */
+ char *path; /* Remote forward socket path. */
+ int port; /* Remote forward port. */
+};
+
+static void
+channel_proxy_confirm_remote_forward(struct ssh *ssh, int type,
+ u_int32_t seq, void *ctxt)
+{
+ struct channel_proxy_remote_forward_ctx *ctx = ctxt;
+ Channel *downstream;
+ struct sshbuf *b = NULL;
+ const u_char *cp = NULL;
+ size_t len;
+ u_int port;
+ int r;
+
+ if ((downstream = channel_by_id(ssh, ctx->cid)) == NULL) {
+ /* No channel for reply. */
+ error_f("unknown channel");
+ goto free_ctx;
+ }
+ if ((b = sshbuf_new()) == NULL)
+ fatal_f("sshbuf_new");
+ /* Forward the packet to muxclient. */
+ cp = sshpkt_ptr(ssh, &len);
+ if (cp == NULL) {
+ error_f("no packet");
+ goto out;
+ }
+ if ((r = sshbuf_put_u8(b, 0)) != 0 || /* Padlen. */
+ (r = sshbuf_put_u8(b, type)) != 0 ||
+ (r = sshbuf_put(b, cp, len)) != 0 ||
+ (r = sshbuf_put_stringb(downstream->output, b)) != 0) {
+ error_fr(r, "forward muxclient");
+ goto out;
+ }
+
+ /* Update permissions. */
+ if (!ctx->cancel) {
+ if (type == SSH2_MSG_REQUEST_SUCCESS) {
+ if (ctx->port == 0) {
+ /* Update the remote port. */
+ if ((r = sshpkt_get_u32(ssh, &port)) != 0)
+ fatal_fr(r, "parse packet");
+ ctx->port = (int)port;
+ }
+ channel_proxy_remote_forward(ssh, downstream,
+ ctx->host, ctx->path, ctx->port);
+ }
+ } else {
+ channel_proxy_cancel_remote_forward(ssh,
+ ctx->host, ctx->path, ctx->port);
+ }
+
+out:
+ sshbuf_free(b);
+free_ctx:
+ free(ctx->host);
+ free(ctx->path);
+ free(ctx);
+}
+
/*
* receive packets from downstream mux clients:
* channel callback fired on read from mux client, creates
@@ -3272,11 +3403,13 @@ channel_proxy_downstream(struct ssh *ssh, Channel *downstream)
Channel *c = NULL;
struct sshbuf *original = NULL, *modified = NULL;
const u_char *cp;
- char *ctype = NULL, *listen_host = NULL;
+ char *ctype = NULL, *listen_host = NULL, *listen_path = NULL;
+ u_char want_reply;
u_char type;
size_t have;
int ret = -1, r;
u_int id, remote_id, listen_port;
+ struct channel_proxy_remote_forward_ctx *rfwd_ctx = NULL;
/* sshbuf_dump(downstream->input, stderr); */
if ((r = sshbuf_get_string_direct(downstream->input, &cp, &have))
@@ -3354,28 +3487,100 @@ channel_proxy_downstream(struct ssh *ssh, Channel *downstream)
error_f("alloc");
goto out;
}
- if ((r = sshbuf_get_cstring(original, &ctype, NULL)) != 0) {
+ if ((r = sshbuf_get_cstring(original, &ctype, NULL)) != 0 ||
+ (r = sshbuf_get_u8(original, &want_reply)) != 0) {
error_fr(r, "parse");
goto out;
}
- if (strcmp(ctype, "tcpip-forward") != 0) {
+ if (strcmp(ctype, "tcpip-forward") == 0) {
+ if ((r = sshbuf_get_cstring(original, &listen_host, NULL)) != 0 ||
+ (r = sshbuf_get_u32(original, &listen_port)) != 0) {
+ error_fr(r, "parse");
+ goto out;
+ }
+ if (listen_port > 65535) {
+ error_f("tcpip-forward for %s: bad port %u",
+ listen_host, listen_port);
+ goto out;
+ }
+ if (want_reply) {
+ rfwd_ctx = xcalloc(1, sizeof(*rfwd_ctx));
+ rfwd_ctx->cid = downstream->self;
+ rfwd_ctx->cancel = 0;
+ rfwd_ctx->host = xstrdup(listen_host);
+ rfwd_ctx->path = NULL;
+ rfwd_ctx->port = (int)listen_port;
+ channel_register_global_confirm(ssh,
+ channel_proxy_confirm_remote_forward, rfwd_ctx);
+ } else {
+ channel_proxy_remote_forward(ssh, downstream,
+ listen_host, NULL, (int)listen_port);
+ }
+ } else if (strcmp(ctype, "cancel-tcpip-forward") == 0) {
+ if ((r = sshbuf_get_cstring(original, &listen_host, NULL)) != 0 ||
+ (r = sshbuf_get_u32(original, &listen_port)) != 0) {
+ error_fr(r, "parse");
+ goto out;
+ }
+ if (listen_port > 65535) {
+ error_f("cancel-tcpip-forward for %s: bad port %u",
+ listen_host, listen_port);
+ goto out;
+ }
+ if (want_reply) {
+ rfwd_ctx = xcalloc(1, sizeof(*rfwd_ctx));
+ rfwd_ctx->cid = downstream->self;
+ rfwd_ctx->cancel = 1;
+ rfwd_ctx->host = xstrdup(listen_host);
+ rfwd_ctx->path = NULL;
+ rfwd_ctx->port = (int)listen_port;
+ channel_register_global_confirm(ssh,
+ channel_proxy_confirm_remote_forward, rfwd_ctx);
+ } else {
+ channel_proxy_cancel_remote_forward(ssh,
+ listen_host, NULL, (int)listen_port);
+ }
+ } else if (strcmp(ctype, "streamlocal-forward at openssh.com") == 0) {
+ if ((r = sshbuf_get_cstring(original, &listen_path, NULL)) != 0) {
+ error_fr(r, "parse");
+ goto out;
+ }
+ if (want_reply) {
+ rfwd_ctx = xcalloc(1, sizeof(*rfwd_ctx));
+ rfwd_ctx->cid = downstream->self;
+ rfwd_ctx->cancel = 0;
+ rfwd_ctx->host = NULL;
+ rfwd_ctx->path = xstrdup(listen_path);
+ rfwd_ctx->port = PORT_STREAMLOCAL;
+ channel_register_global_confirm(ssh,
+ channel_proxy_confirm_remote_forward, rfwd_ctx);
+ } else {
+ channel_proxy_remote_forward(ssh, downstream,
+ NULL, listen_path, 0);
+ }
+ } else if (strcmp(ctype,
+ "cancel-streamlocal-forward at openssh.com") == 0) {
+ if ((r = sshbuf_get_cstring(original, &listen_path, NULL)) != 0) {
+ error_fr(r, "parse");
+ goto out;
+ }
+ if (want_reply) {
+ rfwd_ctx = xcalloc(1, sizeof(*rfwd_ctx));
+ rfwd_ctx->cid = downstream->self;
+ rfwd_ctx->cancel = 1;
+ rfwd_ctx->host = NULL;
+ rfwd_ctx->path = xstrdup(listen_path);
+ rfwd_ctx->port = PORT_STREAMLOCAL;
+ channel_register_global_confirm(ssh,
+ channel_proxy_confirm_remote_forward, rfwd_ctx);
+ } else {
+ channel_proxy_cancel_remote_forward(ssh,
+ NULL, listen_path, 0);
+ }
+ } else {
error_f("unsupported request %s", ctype);
goto out;
}
- if ((r = sshbuf_get_u8(original, NULL)) != 0 ||
- (r = sshbuf_get_cstring(original, &listen_host, NULL)) != 0 ||
- (r = sshbuf_get_u32(original, &listen_port)) != 0) {
- error_fr(r, "parse");
- goto out;
- }
- if (listen_port > 65535) {
- error_f("tcpip-forward for %s: bad port %u",
- listen_host, listen_port);
- goto out;
- }
- /* Record that connection to this host/port is permitted. */
- permission_set_add(ssh, FORWARD_USER, FORWARD_LOCAL, "<mux>",
- -1, listen_host, NULL, (int)listen_port, downstream);
break;
case SSH2_MSG_CHANNEL_CLOSE:
if (have < 4)
@@ -3408,6 +3613,7 @@ channel_proxy_downstream(struct ssh *ssh, Channel *downstream)
out:
free(ctype);
free(listen_host);
+ free(listen_path);
sshbuf_free(original);
sshbuf_free(modified);
return ret;
@@ -4920,6 +5126,8 @@ channel_connect_by_listen_path(struct ssh *ssh, const char *path,
for (i = 0; i < pset->num_permitted_user; i++) {
perm = &pset->permitted_user[i];
+ if (perm->downstream)
+ return perm->downstream;
if (open_listen_match_streamlocal(perm, path)) {
return connect_to(ssh,
perm->host_to_connect, perm->port_to_connect,
diff --git a/clientloop.c b/clientloop.c
index fe3b98d63..1c39e6550 100644
--- a/clientloop.c
+++ b/clientloop.c
@@ -1726,16 +1726,17 @@ client_request_forwarded_tcpip(struct ssh *ssh, const char *request_type,
}
static Channel *
-client_request_forwarded_streamlocal(struct ssh *ssh,
- const char *request_type, int rchan)
+client_request_forwarded_streamlocal(struct ssh *ssh, const char *request_type,
+ int rchan, u_int rwindow, u_int rmaxpack)
{
Channel *c = NULL;
- char *listen_path;
+ struct sshbuf *b = NULL;
+ char *listen_path, *reserved;
int r;
/* Get the remote path. */
if ((r = sshpkt_get_cstring(ssh, &listen_path, NULL)) != 0 ||
- (r = sshpkt_get_string(ssh, NULL, NULL)) != 0 || /* reserved */
+ (r = sshpkt_get_cstring(ssh, &reserved, NULL)) != 0 ||
(r = sshpkt_get_end(ssh)) != 0)
fatal_fr(r, "parse packet");
@@ -1743,7 +1744,31 @@ client_request_forwarded_streamlocal(struct ssh *ssh,
c = channel_connect_by_listen_path(ssh, listen_path,
"forwarded-streamlocal at openssh.com", "forwarded-streamlocal");
+
+ if (c != NULL && c->type == SSH_CHANNEL_MUX_CLIENT) {
+ if ((b = sshbuf_new()) == NULL) {
+ error_f("alloc reply");
+ goto out;
+ }
+ /* reconstruct and send to muxclient */
+ if ((r = sshbuf_put_u8(b, 0)) != 0 || /* padlen */
+ (r = sshbuf_put_u8(b, SSH2_MSG_CHANNEL_OPEN)) != 0 ||
+ (r = sshbuf_put_cstring(b, request_type)) != 0 ||
+ (r = sshbuf_put_u32(b, rchan)) != 0 ||
+ (r = sshbuf_put_u32(b, rwindow)) != 0 ||
+ (r = sshbuf_put_u32(b, rmaxpack)) != 0 ||
+ (r = sshbuf_put_cstring(b, listen_path)) != 0 ||
+ (r = sshbuf_put_cstring(b, reserved)) != 0 ||
+ (r = sshbuf_put_stringb(c->output, b)) != 0) {
+ error_fr(r, "compose for muxclient");
+ goto out;
+ }
+ }
+
+out:
+ sshbuf_free(b);
free(listen_path);
+ free(reserved);
return c;
}
@@ -1891,7 +1916,8 @@ client_input_channel_open(int type, u_int32_t seq, struct ssh *ssh)
c = client_request_forwarded_tcpip(ssh, ctype, rchan, rwindow,
rmaxpack);
} else if (strcmp(ctype, "forwarded-streamlocal at openssh.com") == 0) {
- c = client_request_forwarded_streamlocal(ssh, ctype, rchan);
+ c = client_request_forwarded_streamlocal(ssh, ctype, rchan, rwindow,
+ rmaxpack);
} else if (strcmp(ctype, "x11") == 0) {
c = client_request_x11(ssh, ctype, rchan);
} else if (strcmp(ctype, "auth-agent at openssh.com") == 0) {
diff --git a/packet.c b/packet.c
index 2a5a56a88..7d0c289bc 100644
--- a/packet.c
+++ b/packet.c
@@ -1550,10 +1550,10 @@ ssh_packet_read_poll2_mux(struct ssh *ssh, u_char *typep, u_int32_t *seqnr_p)
*typep = SSH_MSG_NONE;
cp = sshbuf_ptr(state->input);
if (state->packlen == 0) {
- if (sshbuf_len(state->input) < 4 + 1)
+ if (sshbuf_len(state->input) < 4 + 1 + 1) /* packlen + padlen + type */
return 0; /* packet is incomplete */
state->packlen = PEEK_U32(cp);
- if (state->packlen < 4 + 1 ||
+ if (state->packlen < 1 + 1 || /* padlen + type */
state->packlen > PACKET_MAX_SIZE)
return SSH_ERR_MESSAGE_INCOMPLETE;
}
diff --git a/regress/Makefile b/regress/Makefile
index bd44b0489..c5cf6dd9b 100644
--- a/regress/Makefile
+++ b/regress/Makefile
@@ -71,6 +71,7 @@ LTESTS= connect \
reconfigure \
dynamic-forward \
forwarding \
+ forwarding-proxy \
multiplex \
reexec \
brokenkeys \
diff --git a/regress/forwarding-proxy.sh b/regress/forwarding-proxy.sh
new file mode 100644
index 000000000..b90e5916d
--- /dev/null
+++ b/regress/forwarding-proxy.sh
@@ -0,0 +1,124 @@
+# $OpenBSD: forwarding-proxy.sh,v 1.00 2025/12/27 11:51:45 ding Exp $
+# Placed in the Public Domain.
+
+tid="local and remote forwarding (proxy mode)"
+
+DATA=/bin/ls${EXEEXT}
+
+start_sshd
+
+base=33
+last=$PORT
+fwd=""
+make_tmpdir
+CTL=${SSH_REGRESS_TMP}/ctl-sock
+CTL=/Users/ding/Documents/dev/cpp/openssh-portable/sshcs
+SSH_PROXY="${SSH} -S $CTL -O proxy"
+
+start_proxy() {
+ rm -f $CTL
+ ${SSH} -F $OBJ/ssh_config -S $CTL -M -N -f somehost
+}
+
+stop_proxy() {
+ ${SSH} -F $OBJ/ssh_config -S $CTL -O exit somehost 2>/dev/null
+ rm -f $CTL
+}
+
+for j in 0 1 2; do
+ for i in 0 1 2; do
+ a=$base$j$i
+ b=`expr $a + 50`
+ c=$last
+ # fwd chain: $a -> $b -> $c
+ fwd="$fwd -L$a:127.0.0.1:$b -R$b:127.0.0.1:$c"
+ last=$a
+ done
+done
+
+start_proxy
+trace "start forwarding, fork to background"
+${SSH_PROXY} -N -F $OBJ/ssh_config -f $fwd somehost
+
+trace "transfer over forwarded channels and check result"
+${SSH} -F $OBJ/ssh_config -p$last -o 'ConnectionAttempts=10' \
+ somehost cat ${DATA} > ${COPY}
+test -s ${COPY} || fail "failed copy of ${DATA}"
+cmp ${DATA} ${COPY} || fail "corrupted copy of ${DATA}"
+stop_proxy
+
+start_proxy
+for d in L R; do
+ trace "exit on -$d forward failure"
+
+ # this one should succeed
+ ${SSH_PROXY} -F $OBJ/ssh_config \
+ -$d ${base}01:127.0.0.1:$PORT \
+ -$d ${base}02:127.0.0.1:$PORT \
+ -$d ${base}03:127.0.0.1:$PORT \
+ -$d ${base}04:127.0.0.1:$PORT \
+ -oExitOnForwardFailure=yes somehost true
+ if [ $? != 0 ]; then
+ fatal "connection failed, should not"
+ else
+ # this one should fail
+ ${SSH_PROXY} -q -F $OBJ/ssh_config \
+ -$d ${base}01:127.0.0.1:$PORT \
+ -$d ${base}02:127.0.0.1:$PORT \
+ -$d ${base}03:127.0.0.1:$PORT \
+ -$d ${base}01:localhost:$PORT \
+ -$d ${base}04:127.0.0.1:$PORT \
+ -oExitOnForwardFailure=yes somehost true
+ r=$?
+ if [ $r != 255 ]; then
+ fail "connection not terminated, but should ($r)"
+ fi
+ fi
+done
+stop_proxy
+
+start_proxy
+trace "simple clear forwarding"
+${SSH_PROXY} -F $OBJ/ssh_config -oClearAllForwardings=yes somehost true
+stop_proxy
+
+start_proxy
+trace "clear local forward"
+${SSH_PROXY} -N -f -F $OBJ/ssh_config -L ${base}01:127.0.0.1:$PORT \
+ -oClearAllForwardings=yes somehost
+if [ $? != 0 ]; then
+ fail "connection failed with cleared local forwarding"
+else
+ # this one should fail
+ ${SSH} -F $OBJ/ssh_config -p ${base}01 somehost true \
+ >>$TEST_REGRESS_LOGFILE 2>&1 && \
+ fail "local forwarding not cleared"
+fi
+stop_proxy
+
+start_proxy
+trace "clear remote forward"
+${SSH_PROXY} -N -f -F $OBJ/ssh_config -R ${base}01:127.0.0.1:$PORT \
+ -oClearAllForwardings=yes somehost
+if [ $? != 0 ]; then
+ fail "connection failed with cleared remote forwarding"
+else
+ # this one should fail
+ ${SSH} -F $OBJ/ssh_config -p ${base}01 somehost true \
+ >>$TEST_REGRESS_LOGFILE 2>&1 && \
+ fail "remote forwarding not cleared"
+fi
+stop_proxy
+
+start_proxy
+trace "transfer over chained unix domain socket forwards and check result"
+rm -f $OBJ/unix-[123].fwd
+${SSH_PROXY} -N -f -F $OBJ/ssh_config -L[$OBJ/unix-3.fwd]:127.0.0.1:$PORT somehost
+${SSH_PROXY} -N -f -F $OBJ/ssh_config -R[$OBJ/unix-2.fwd]:[$OBJ/unix-3.fwd] somehost
+${SSH_PROXY} -N -f -F $OBJ/ssh_config -L[$OBJ/unix-1.fwd]:[$OBJ/unix-2.fwd] somehost
+${SSH_PROXY} -N -f -F $OBJ/ssh_config -R${base}01:[$OBJ/unix-1.fwd] somehost
+${SSH} -F $OBJ/ssh_config -p${base}01 -o 'ConnectionAttempts=10' \
+ somehost cat ${DATA} > ${COPY}
+test -s ${COPY} || fail "failed copy ${DATA}"
+cmp ${DATA} ${COPY} || fail "corrupted copy of ${DATA}"
+stop_proxy
--
2.51.0
More information about the openssh-unix-dev
mailing list