Trouble with port forwarding over proxy control socket
ding
ding at diinngg.com
Mon Dec 22 05:40:38 AEDT 2025
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?
Thanks!
-------------- next part --------------
diff --git a/channels.c b/channels.c
index 80014ff34..d389680fc 100644
--- a/channels.c
+++ b/channels.c
@@ -80,6 +80,7 @@
#include "authfd.h"
#include "pathnames.h"
#include "match.h"
+#include "clientloop.h"
/* XXX remove once we're satisfied there's no lurking bugs */
/* #define DEBUG_CHANNEL_POLL 1 */
@@ -3197,6 +3198,50 @@ channel_output_poll(struct ssh *ssh)
* easily.
*/
+struct channel_proxy_confirm_ctx {
+ u_int cid; /* channel id */
+};
+
+/* forward global request replies to downstream mux clients */
+static void
+channel_proxy_confirm_global_request(struct ssh *ssh, int type,
+ u_int32_t seq, void *ctxt)
+{
+ struct channel_proxy_confirm_ctx *ctx = ctxt;
+ Channel *downstream;
+ struct sshbuf *out = NULL;
+ const u_char *cp = NULL;
+ size_t len;
+ int r;
+
+ if ((downstream = channel_by_id(ssh, ctx->cid)) == NULL) {
+ /* no channel for reply */
+ /* c.f. mux_confirm_remote_forward() */
+ /* doesn't this result in a memory leak of the context? */
+ error_f("unknown channel");
+ return;
+ }
+ if ((out = 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(out, 0)) != 0 || /* padlen */
+ (r = sshbuf_put_u8(out, type)) != 0 ||
+ (r = sshbuf_put(out, cp, len)) != 0 ||
+ (r = sshbuf_put_stringb(downstream->output, out)) != 0) {
+ error_fr(r, "forward muxclient");
+ goto out;
+ }
+
+out:
+ sshbuf_free(out);
+ free(ctx);
+}
+
/*
* receive packets from downstream mux clients:
* channel callback fired on read from mux client, creates
@@ -3214,6 +3259,7 @@ channel_proxy_downstream(struct ssh *ssh, Channel *downstream)
size_t have;
int ret = -1, r;
u_int id, remote_id, listen_port;
+ struct channel_proxy_confirm_ctx *global_req_ctx = NULL;
/* sshbuf_dump(downstream->input, stderr); */
if ((r = sshbuf_get_string_direct(downstream->input, &cp, &have))
@@ -3295,7 +3341,9 @@ channel_proxy_downstream(struct ssh *ssh, Channel *downstream)
error_fr(r, "parse");
goto out;
}
- if (strcmp(ctype, "tcpip-forward") != 0) {
+ /* is there a reason why cancel wasn't supported? */
+ if (strcmp(ctype, "tcpip-forward") != 0 &&
+ strcmp(ctype, "cancel-tcpip-forward") != 0) {
error_f("unsupported request %s", ctype);
goto out;
}
@@ -3313,6 +3361,10 @@ channel_proxy_downstream(struct ssh *ssh, Channel *downstream)
/* 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);
+ global_req_ctx = xcalloc(1, sizeof(*global_req_ctx));
+ global_req_ctx->cid = downstream->self;
+ client_register_global_confirm(
+ channel_proxy_confirm_global_request, global_req_ctx);
break;
case SSH2_MSG_CHANNEL_CLOSE:
if (have < 4)
More information about the openssh-unix-dev
mailing list