Issue with OpenSSH remote forwarding of dynamic ports

296 views
Skip to first unread message

Ron Frederick

unread,
Aug 8, 2013, 12:22:56 AM8/8/13
to openssh-...@mindrot.org
I recently ran across a problem with remote port forwarding in OpenSSH when trying to use dynamic ports. While it is possible to use OpenSSH to request a dynamic port and the OpenSSH sshd handles it just fine, the OpenSSH client gets confused when multiple ports are opened this way, due to the information passed in the "forwarded-tcpip" SSH_MSG_CHANNEL_OPEN message which is sent back to the client when connections are opened. To illustrate this problem, I tried the following with OpenSSH 6.2p1:

ssh -vvv -R 0:localhost:80 -R 0:localhost:81 localhost

In the debug output, I saw the lines:

debug1: remote forward success for: listen 0, connect localhost:80
Allocated port 60013 for remote forward to localhost:80
debug1: Updating allowed port 60013 for forwarding to host localhost port 80
debug1: remote forward success for: listen 0, connect localhost:81
Allocated port 60014 for remote forward to localhost:81
debug1: Updating allowed port 60014 for forwarding to host localhost port 81

So far, so good!

Connecting to port 60013 worked fine, causing the client to connect to localhost:80 as requested, with the following debug output:

quad:~>debug1: client_input_channel_open: ctype forwarded-tcpip rchan 5 win 2097152 max 32768
debug1: client_request_forwarded_tcpip: listen localhost port 0, originator ::1 port 60153
debug2: fd 9 setting O_NONBLOCK
debug1: connect_next: host localhost ([::1]:80) in progress, fd=9
debug2: fd 9 setting TCP_NODELAY
debug3: fd 9 is O_NONBLOCK
debug3: fd 9 is O_NONBLOCK
debug1: channel 1: new [::1]
debug1: confirm forwarded-tcpip
debug3: channel 1: waiting for connection
debug1: channel 1: connected to localhost port 80

However, connecting to port 60014 did not work as expected. In that case, I saw:

debug1: client_input_channel_open: ctype forwarded-tcpip rchan 5 win 2097152 max 32768
debug1: client_request_forwarded_tcpip: listen localhost port 0, originator ::1 port 60182
debug2: fd 9 setting O_NONBLOCK
debug1: connect_next: host localhost ([::1]:80) in progress, fd=9
debug2: fd 9 setting TCP_NODELAY
debug3: fd 9 is O_NONBLOCK
debug3: fd 9 is O_NONBLOCK
debug1: channel 1: new [::1]
debug1: confirm forwarded-tcpip
debug3: channel 1: waiting for connection
debug1: channel 1: connected to localhost port 80

Note that even though this was to the second listening port I set up, the connection was locally forwarded to port 80. The reason is that the SSH_MSG_CHANNEL_OPEN of type "forwarded-tcpip" from sshd reported the destination host & port as "localhost" port 0 for both the 60013 and 60014 connections, instead of reporting the actual listening port. The client seems to expect the 0 value here and does the right thing when you only have one dynamic listening port, but this breaks in the case where there are multiple, since there's nothing in the SSH_MSG_CHANNEL_OPEN which distinguishes between the two dynamic listeners. RFC 4254 is not completely clear on what is expected in the message. It says:

When a connection comes to a port for which remote forwarding has
been requested, a channel is opened to forward the port to the other
side.

byte SSH_MSG_CHANNEL_OPEN
string "forwarded-tcpip"
uint32 sender channel
uint32 initial window size
uint32 maximum packet size
string address that was connected
uint32 port that was connected
string originator IP address
uint32 originator port

I was expecting "port that was connected" in this message to be the dynamically allocated port so that it would always be a unique value, but this is not the case (at least with OpenSSH's sshd). Instead, it always seems to be the "port number to bind" value passed in the original SSH_MSG_GLOBAL_REQUEST "tcpip-forward" message (which is 0 since we're asking for a dynamic port).

Unfortunately, I would imagine changing this behavior on the server side might break existing clients out there which are expecting to get this 0 value back in channel open requests when they set up a dynamic listener, and I don't really see a good way to resolve this. Does anyone have any suggestions?
--
Ron Frederick
ro...@timeheart.net



_______________________________________________
openssh-unix-dev mailing list
openssh-...@mindrot.org
https://lists.mindrot.org/mailman/listinfo/openssh-unix-dev

Ángel González

unread,
Aug 15, 2013, 8:14:52 PM8/15/13
to Ron Frederick, openssh-...@mindrot.org
On 08/08/13 06:22, Ron Frederick wrote:
> When a connection comes to a port for which remote forwarding has
> been requested, a channel is opened to forward the port to the other
> side.
>
> byte SSH_MSG_CHANNEL_OPEN
> string "forwarded-tcpip"
> uint32 sender channel
> uint32 initial window size
> uint32 maximum packet size
> string address that was connected
> uint32 port that was connected
> string originator IP address
> uint32 originator port
>
> I was expecting "port that was connected" in this message to be the dynamically allocated port so that it would always be a unique value, but this is not the case (at least with OpenSSH's sshd). Instead, it always seems to be the "port number to bind" value passed in the original SSH_MSG_GLOBAL_REQUEST "tcpip-forward" message (which is 0 since we're asking for a dynamic port).
>
> Unfortunately, I would imagine changing this behavior on the server side might break existing clients out there which are expecting to get this 0 value back in channel open requests when they set up a dynamic listener, and I don't really see a good way to resolve this. Does anyone have any suggestions?
It could pass 0 for the first forward and the real port in the next ones
(which are already open), but IMHO the right thing would be to always
provide the real port.

Ron Frederick

unread,
Aug 17, 2013, 12:35:10 PM8/17/13
to Ángel González, openssh-...@mindrot.org
On Aug 15, 2013, at 5:14 PM, Ángel González <kei...@gmail.com> wrote:
>
> On 08/08/13 06:22, Ron Frederick wrote:
>> When a connection comes to a port for which remote forwarding has
>> been requested, a channel is opened to forward the port to the other
>> side.
>>
>> byte SSH_MSG_CHANNEL_OPEN
>> string "forwarded-tcpip"
>> uint32 sender channel
>> uint32 initial window size
>> uint32 maximum packet size
>> string address that was connected
>> uint32 port that was connected
>> string originator IP address
>> uint32 originator port
>>
>> I was expecting "port that was connected" in this message to be the dynamically allocated port so that it would always be a unique value, but this is not the case (at least with OpenSSH's sshd). Instead, it always seems to be the "port number to bind" value passed in the original SSH_MSG_GLOBAL_REQUEST "tcpip-forward" message (which is 0 since we're asking for a dynamic port).
>>
>> Unfortunately, I would imagine changing this behavior on the server side might break existing clients out there which are expecting to get this 0 value back in channel open requests when they set up a dynamic listener, and I don't really see a good way to resolve this. Does anyone have any suggestions?
>
> It could pass 0 for the first forward and the real port in the next ones (which are already open), but IMHO the right thing would be to always provide the real port.

I did an experiment today and was pleasantly surprised to find that the OpenSSH client does work correctly when the real port number is returned by the server. The "address that was connected" can also be a real address. So, for instance, a request could be made with a bind address of "localhost" and the address reported back could be "127.0.0.1" for IPv4 or "::1" for IPv6. Here's some debug output of it working:

debug1: remote forward success for: listen 0, connect localhost:80
Allocated port 12345 for remote forward to localhost:80
debug1: Updating allowed port 12345 for forwarding to host localhost port 80
...
debug1: client_input_channel_open: ctype forwarded-tcpip rchan 1 win 2097152 max 32768
debug1: client_request_forwarded_tcpip: listen 127.0.0.1 port 12345, originator 127.0.0.1 port 56789
debug1: connect_next: host localhost ([::1]:80) in progress, fd=9
debug1: channel 1: new [1.2.3.4]
debug1: confirm forwarded-tcpip
debug1: channel 1: connected to localhost port 80

I also tried having the server respond with a port number which didn't match the random port returned in the channel open response, and the client properly rejected that. In the debug output, I saw:

WARNING: Server requests forwarding for unknown listen_port 12346
debug1: failure forwarded-tcpip

So, it looks like it wouldn't cause a problem for OpenSSH clients if the OpenSSH server always returned the correct port number in the "tcpip-forward" response when requests were made to bind to port 0. The change looks fairly straightforward to do this. Here's a proposed patch against version 6.2p2:

--- channels.c.orig 2012-12-02 14:50:55.000000000 -0800
+++ channels.c 2013-08-17 09:18:41.000000000 -0700
@@ -1384,6 +1384,8 @@
{
int direct;
char buf[1024];
+ char *local_ipaddr = get_local_ipaddr(c->sock);
+ int local_port = get_sock_port(c->sock, 1);
char *remote_ipaddr = get_peer_ipaddr(c->sock);
int remote_port = get_peer_port(c->sock);

@@ -1398,9 +1400,9 @@

snprintf(buf, sizeof buf,
"%s: listening port %d for %.100s port %d, "
- "connect from %.200s port %d",
+ "connect from %.200s port %d to %.100s port %d",
rtype, c->listening_port, c->path, c->host_port,
- remote_ipaddr, remote_port);
+ remote_ipaddr, remote_port, local_ipaddr, local_port);

xfree(c->remote_name);
c->remote_name = xstrdup(buf);
@@ -1416,9 +1418,9 @@
packet_put_cstring(c->path);
packet_put_int(c->host_port);
} else {
- /* listen address, port */
- packet_put_cstring(c->path);
- packet_put_int(c->listening_port);
+ /* connected address, port */
+ packet_put_cstring(local_ipaddr);
+ packet_put_int(local_port);
}
/* originator host and port */
packet_put_cstring(remote_ipaddr);
@@ -1435,6 +1437,7 @@
packet_send();
}
xfree(remote_ipaddr);
+ xfree(local_ipaddr);
}

static void

One minor ugliness here is that I had to use "get_sock_port()" instead of get_local_port(), as get_local_port() doesn't take a socket as an argument the way get_local_ipaddr() does and there's no equivalent function under another name. Cleaning this up would impact other files, though, and I wanted to keep the diff as clean as possible for now.
--
Ron Frederick
ro...@timeheart.net
Reply all
Reply to author
Forward
0 new messages