++dynamic port forwarding patch + blog entry

Signed-off-by: Nico Schottelius <nico@bento.schottelius.org>
This commit is contained in:
Nico Schottelius 2013-05-15 23:29:34 +02:00
parent a4b8201801
commit 0264bb72fb
2 changed files with 180 additions and 0 deletions

View file

@ -0,0 +1,98 @@
[[!meta title="OpenSSH 6.2: Add callback functionality (using dynamic remote port forwarding)"]]
## Introduction
This article describes a patch to OpenSSH 6.2 that I wrote to enable
**ssh callback** using dynamic ports. This is rather useful to have
for various types of software, including [[cdist|software/cdist]]
and [[ccollect|software/ccollect]].
## Background
Assume you have two hosts:
* A **target host**
* A **control host**
(backup server in case of [[ccollect|software/ccollect]],
configuration server in case of [[cdist|software/cdist]])
Assume further that the target host can directly reach the control
host, but the control host cannot connect to the target host directly.
This is for instance the case,
when the target host is hidden by NAT
or protected by a firewall.
## Approaches
### Create a tunnel from the target host to the control host
A very simple solution is to create a static tunnel
from the target host to the control host, which allows
the control host to connect back:
targethost% ssh -R 42523:localhost:22 controlhost
controlhost% ssh -p 42523 localhost
The drawback is that the remote port needs to be defined
beforehand and both sides needs to know about it.
This is especially nasty, if you have a lot of
target hosts that need to be backed up / configured.
### Use dynamic port allocation
The [OpenSSH](http://openssh.org/) developers seem to have
spotted this problem and include an option to use a random
free port: If port 0 is chosen as the remote
forwarding port, the port is dynamically chosen by the
ssh server, which in our case runs on the controlhost.
Even better, the port information is also displayed on stdout:
targethost% ssh -R 0:localhost:22 controlhost
Allocated port 59818 for remote forward to localhost:22
The problem here is: The shell on the remote side does not
know which port was chosen, as it is only printed on stdout
by the **ssh client**.
### Expose remote forwarding ports
[[This patch|openssh-6.2p1-expose-remote-port-forwarding.diff]]
against OpenSSH 6.2p1 creates a new environment variable
***SSH_REMOTE_FORWARDING_PORTS***, which contains all ports
that are used for remote forwarding:
targethost % ssh -R 1234:localhost:22 controlhost
controlhost % echo $SSH_REMOTE_FORWARDING_PORTS
1234
As this works for all remotely forwarded ports, this can
also be used for dynamic port assignments:
targethost % ssh -R 0:localhost:22 controlhost
controlhost % echo $SSH_REMOTE_FORWARDING_PORTS
54294
If more than one port forwarding definition is given, they are listed
space separated:
targethost % ssh -R 0:localhost:22 -R 1234:localhost:22 controlhost
controlhost % echo $SSH_REMOTE_FORWARDING_PORTS
59056 1234
## Limitations
The given patch has some known limitations:
* The destination is not shown, as it is not known for ssh2 connections
* The number of listed ports is limited by the buffer size of 256 characters
## Future
The patch will be submitted to the
[openssh-unix-dev mailinglist](https://lists.mindrot.org/mailman/listinfo/openssh-unix-dev) for discussion.
[[!tag net unix]]

View file

@ -0,0 +1,82 @@
diff -ru openssh-6.2p1/channels.c openssh-6.2p1.patched/channels.c
--- openssh-6.2p1/channels.c 2012-12-02 23:50:55.000000000 +0100
+++ openssh-6.2p1.patched/channels.c 2013-05-15 23:26:17.119989982 +0200
@@ -2865,6 +2865,52 @@
return success;
}
+/*
+ * Write list of remote forwarding ports into an existing buffer
+ */
+void
+channel_list_rport_listener(char *buf, size_t size)
+{
+ u_int i, j, num_ports = 0;
+ int offset = 0;
+ int *ports;
+ int skip;
+
+ ports = xcalloc(channels_alloc, sizeof(int));
+
+ for (i = 0; i < channels_alloc; i++) {
+ skip = 0;
+ Channel *c = channels[i];
+ if (c == NULL || c->type != SSH_CHANNEL_RPORT_LISTENER)
+ continue;
+
+ /* Skip already added ports - IPv4 + IPv6 == same port twice */
+ for(j = 0; j < num_ports; j++) {
+ if (ports[j] == c->listening_port) {
+ skip = 1;
+ break;
+ }
+ }
+
+ if(skip) continue;
+
+ ports[num_ports] = c->listening_port;
+ num_ports++;
+
+ if(!offset) {
+ offset += snprintf(&buf[offset], size - offset, "%d", c->listening_port);
+ } else
+ offset += snprintf(&buf[offset], size - offset, " %d", c->listening_port);
+
+ if(offset >= size) {
+ error("Exceeded buffer space for remote forwarding ports listing");
+ break;
+ }
+ }
+
+ xfree(ports);
+}
+
int
channel_cancel_rport_listener(const char *host, u_short port)
{
Only in openssh-6.2p1.patched/: .channels.c.swp
diff -ru openssh-6.2p1/channels.h openssh-6.2p1.patched/channels.h
--- openssh-6.2p1/channels.h 2012-04-22 03:21:10.000000000 +0200
+++ openssh-6.2p1.patched/channels.h 2013-05-09 23:21:37.385423623 +0200
@@ -222,6 +222,7 @@
void channel_cancel_cleanup(int);
int channel_close_fd(int *);
void channel_send_window_changes(void);
+void channel_list_rport_listener(char *buf, size_t size);
/* protocol handler */
diff -ru openssh-6.2p1/session.c openssh-6.2p1.patched/session.c
--- openssh-6.2p1/session.c 2013-03-15 01:22:37.000000000 +0100
+++ openssh-6.2p1.patched/session.c 2013-05-15 23:27:12.459989713 +0200
@@ -1235,6 +1235,9 @@
xfree(laddr);
child_set_env(&env, &envsize, "SSH_CONNECTION", buf);
+ channel_list_rport_listener(buf, sizeof buf);
+ child_set_env(&env, &envsize, "SSH_REMOTE_FORWARDING_PORTS", buf);
+
if (s->ttyfd != -1)
child_set_env(&env, &envsize, "SSH_TTY", s->tty);
if (s->term)
Only in openssh-6.2p1.patched/: .session.c.swp