[PATCH] Implement remote dynamic TCP forwarding

Kai-Chieh Ku kjackie at gmail.com
Mon Oct 22 18:13:21 EST 2012


Hi all,

This is a client side only implementation of reversed dynamic (SOCKS) TCP
forwarding, which means it is compatible with any existing servers
have 'remote forward' capability.

To establish such forward, use "ssh -R [BIND_ADDRESS:]PORT ...".
The server will listen on that port and address and accept SOCKS
traffics.

Hope this will be useful for you.

There was an implementation which need to patch the server, too:
https://lists.mindrot.org/pipermail/openssh-unix-dev/2010-January/028122.html

Please CC me while replying because I do not subscribe the list.

This patch is based on openssh-6.1p1.

Regards,

Kai-Chieh Ku
---
 channels.c | 222 +++++++++++++++++++++++++++++++++++++++++++++++++------------
 channels.h |   3 +-
 ssh.c      |   3 +-
 3 files changed, 183 insertions(+), 45 deletions(-)

diff --git a/channels.c b/channels.c
index 7791feb..6e46229 100644
--- a/channels.c
+++ b/channels.c
@@ -172,6 +172,7 @@ static void port_open_helper(Channel *c, char *rtype);
 /* non-blocking connect helpers */
 static int connect_next(struct channel_connect *);
 static void channel_connect_ctx_free(struct channel_connect *);
+static int connect_to_helper(const char *host, u_short port, struct channel_connect *cctx);
 
 /* -- channel core */
 
@@ -209,6 +210,7 @@ channel_lookup(int id)
 	case SSH_CHANNEL_LARVAL:
 	case SSH_CHANNEL_CONNECTING:
 	case SSH_CHANNEL_DYNAMIC:
+	case SSH_CHANNEL_RDYNAMIC:
 	case SSH_CHANNEL_OPENING:
 	case SSH_CHANNEL_OPEN:
 	case SSH_CHANNEL_INPUT_DRAINING:
@@ -534,6 +536,7 @@ channel_still_open(void)
 		case SSH_CHANNEL_CLOSED:
 		case SSH_CHANNEL_AUTH_SOCKET:
 		case SSH_CHANNEL_DYNAMIC:
+		case SSH_CHANNEL_RDYNAMIC:
 		case SSH_CHANNEL_CONNECTING:
 		case SSH_CHANNEL_ZOMBIE:
 			continue;
@@ -573,6 +576,7 @@ channel_find_open(void)
 		switch (c->type) {
 		case SSH_CHANNEL_CLOSED:
 		case SSH_CHANNEL_DYNAMIC:
+		case SSH_CHANNEL_RDYNAMIC:
 		case SSH_CHANNEL_X11_LISTENER:
 		case SSH_CHANNEL_PORT_LISTENER:
 		case SSH_CHANNEL_RPORT_LISTENER:
@@ -635,6 +639,7 @@ channel_open_message(void)
 		case SSH_CHANNEL_OPENING:
 		case SSH_CHANNEL_CONNECTING:
 		case SSH_CHANNEL_DYNAMIC:
+		case SSH_CHANNEL_RDYNAMIC:
 		case SSH_CHANNEL_OPEN:
 		case SSH_CHANNEL_X11_OPEN:
 		case SSH_CHANNEL_INPUT_DRAINING:
@@ -1033,14 +1038,23 @@ channel_decode_socks4(Channel *c, fd_set *readset, fd_set *writeset)
 		u_int16_t dest_port;
 		struct in_addr dest_addr;
 	} s4_req, s4_rsp;
+	Buffer *input, *output;
+
+	if (c->type == SSH_CHANNEL_RDYNAMIC) {
+		input = &c->output;
+		output = &c->input;
+	} else {
+		input = &c->input;
+		output = &c->output;
+	}
 
 	debug2("channel %d: decode socks4", c->self);
 
-	have = buffer_len(&c->input);
+	have = buffer_len(input);
 	len = sizeof(s4_req);
 	if (have < len)
 		return 0;
-	p = buffer_ptr(&c->input);
+	p = buffer_ptr(input);
 
 	need = 1;
 	/* SOCKS4A uses an invalid IP address 0.0.0.x */
@@ -1065,12 +1079,12 @@ channel_decode_socks4(Channel *c, fd_set *readset, fd_set *writeset)
 	}
 	if (found < need)
 		return 0;
-	buffer_get(&c->input, (char *)&s4_req.version, 1);
-	buffer_get(&c->input, (char *)&s4_req.command, 1);
-	buffer_get(&c->input, (char *)&s4_req.dest_port, 2);
-	buffer_get(&c->input, (char *)&s4_req.dest_addr, 4);
-	have = buffer_len(&c->input);
-	p = buffer_ptr(&c->input);
+	buffer_get(input, (char *)&s4_req.version, 1);
+	buffer_get(input, (char *)&s4_req.command, 1);
+	buffer_get(input, (char *)&s4_req.dest_port, 2);
+	buffer_get(input, (char *)&s4_req.dest_addr, 4);
+	have = buffer_len(input);
+	p = buffer_ptr(input);
 	len = strlen(p);
 	debug2("channel %d: decode socks4: user %s/%d", c->self, p, len);
 	len++;					/* trailing '\0' */
@@ -1078,7 +1092,7 @@ channel_decode_socks4(Channel *c, fd_set *readset, fd_set *writeset)
 		fatal("channel %d: decode socks4: len %d > have %d",
 		    c->self, len, have);
 	strlcpy(username, p, sizeof(username));
-	buffer_consume(&c->input, len);
+	buffer_consume(input, len);
 
 	if (c->path != NULL) {
 		xfree(c->path);
@@ -1088,8 +1102,8 @@ channel_decode_socks4(Channel *c, fd_set *readset, fd_set *writeset)
 		host = inet_ntoa(s4_req.dest_addr);
 		c->path = xstrdup(host);
 	} else {				/* SOCKS4A: two strings */
-		have = buffer_len(&c->input);
-		p = buffer_ptr(&c->input);
+		have = buffer_len(input);
+		p = buffer_ptr(input);
 		len = strlen(p);
 		debug2("channel %d: decode socks4a: host %s/%d",
 		    c->self, p, len);
@@ -1103,7 +1117,7 @@ channel_decode_socks4(Channel *c, fd_set *readset, fd_set *writeset)
 			return -1;
 		}
 		c->path = xstrdup(p);
-		buffer_consume(&c->input, len);
+		buffer_consume(input, len);
 	}
 	c->host_port = ntohs(s4_req.dest_port);
 
@@ -1119,7 +1133,7 @@ channel_decode_socks4(Channel *c, fd_set *readset, fd_set *writeset)
 	s4_rsp.command = 90;			/* cd: req granted */
 	s4_rsp.dest_port = 0;			/* ignored */
 	s4_rsp.dest_addr.s_addr = INADDR_ANY;	/* ignored */
-	buffer_append(&c->output, &s4_rsp, sizeof(s4_rsp));
+	buffer_append(output, &s4_rsp, sizeof(s4_rsp));
 	return 1;
 }
 
@@ -1145,12 +1159,21 @@ channel_decode_socks5(Channel *c, fd_set *readset, fd_set *writeset)
 	u_int16_t dest_port;
 	u_char *p, dest_addr[255+1], ntop[INET6_ADDRSTRLEN];
 	u_int have, need, i, found, nmethods, addrlen, af;
+	Buffer *input, *output;
+
+	if (c->type == SSH_CHANNEL_RDYNAMIC) {
+		input = &c->output;
+		output = &c->input;
+	} else {
+		input = &c->input;
+		output = &c->output;
+	}
 
 	debug2("channel %d: decode socks5", c->self);
-	p = buffer_ptr(&c->input);
+	p = buffer_ptr(input);
 	if (p[0] != 0x05)
 		return -1;
-	have = buffer_len(&c->input);
+	have = buffer_len(input);
 	if (!(c->flags & SSH_SOCKS5_AUTHDONE)) {
 		/* format: ver | nmethods | methods */
 		if (have < 2)
@@ -1170,10 +1193,11 @@ channel_decode_socks5(Channel *c, fd_set *readset, fd_set *writeset)
 			    c->self);
 			return -1;
 		}
-		buffer_consume(&c->input, nmethods + 2);
-		buffer_put_char(&c->output, 0x05);		/* version */
-		buffer_put_char(&c->output, SSH_SOCKS5_NOAUTH);	/* method */
-		FD_SET(c->sock, writeset);
+		buffer_consume(input, nmethods + 2);
+		buffer_put_char(output, 0x05);		/* version */
+		buffer_put_char(output, SSH_SOCKS5_NOAUTH);	/* method */
+		if (c->sock >= 0)
+			FD_SET(c->sock, writeset);
 		c->flags |= SSH_SOCKS5_AUTHDONE;
 		debug2("channel %d: socks5 auth done", c->self);
 		return 0;				/* need more */
@@ -1210,11 +1234,11 @@ channel_decode_socks5(Channel *c, fd_set *readset, fd_set *writeset)
 		need++;
 	if (have < need)
 		return 0;
-	buffer_consume(&c->input, sizeof(s5_req));
+	buffer_consume(input, sizeof(s5_req));
 	if (s5_req.atyp == SSH_SOCKS5_DOMAIN)
-		buffer_consume(&c->input, 1);    /* host string length */
-	buffer_get(&c->input, (char *)&dest_addr, addrlen);
-	buffer_get(&c->input, (char *)&dest_port, 2);
+		buffer_consume(input, 1);    /* host string length */
+	buffer_get(input, (char *)&dest_addr, addrlen);
+	buffer_get(input, (char *)&dest_port, 2);
 	dest_addr[addrlen] = '\0';
 	if (c->path != NULL) {
 		xfree(c->path);
@@ -1244,9 +1268,9 @@ channel_decode_socks5(Channel *c, fd_set *readset, fd_set *writeset)
 	((struct in_addr *)&dest_addr)->s_addr = INADDR_ANY;
 	dest_port = 0;				/* ignored */
 
-	buffer_append(&c->output, &s5_rsp, sizeof(s5_rsp));
-	buffer_append(&c->output, &dest_addr, sizeof(struct in_addr));
-	buffer_append(&c->output, &dest_port, sizeof(dest_port));
+	buffer_append(output, &s5_rsp, sizeof(s5_rsp));
+	buffer_append(output, &dest_addr, sizeof(struct in_addr));
+	buffer_append(output, &dest_port, sizeof(dest_port));
 	return 1;
 }
 
@@ -1317,6 +1341,92 @@ channel_pre_dynamic(Channel *c, fd_set *readset, fd_set *writeset)
 	}
 }
 
+static void
+channel_pre_rdynamic(Channel *c, fd_set *readset, fd_set *writeset)
+{
+	u_char *p;
+	u_int have;
+	int ret;
+
+	if (c->sock >= 0) {
+		/* SOCKS session was established. */
+		FD_SET(c->sock, writeset);
+		return;
+	}
+
+	have = buffer_len(&c->output);
+	debug2("channel %d: pre_rdynamic: have %d", c->self, have);
+	/* buffer_dump(&c->input); */
+	/* check if the fixed size part of the packet is in buffer. */
+	if (have < 3) {
+		/* need more */
+		return;
+	}
+	/* try to guess the protocol */
+	p = buffer_ptr(&c->output);
+	switch (p[0]) {
+	case 0x04:
+		ret = channel_decode_socks4(c, readset, writeset);
+		break;
+	case 0x05:
+		ret = channel_decode_socks5(c, readset, writeset);
+		break;
+	default:
+		ret = -1;
+		break;
+	}
+	if (ret < 0) {
+		chan_mark_dead(c);
+	} else if (ret == 0) {
+		debug2("channel %d: pre_rdynamic: need more", c->self);
+		/* need more */
+	} else {
+		/* switch to the next state */
+		struct channel_connect cctx;
+		int sock;
+
+		sock = connect_to_helper(c->path, c->host_port, &cctx);
+		if (sock < 0) {
+			chan_mark_dead(c);
+			return;
+		}
+
+		channel_register_fds(c, sock, sock, -1, 0, 1, 0);
+		c->connect_ctx = cctx;
+
+		FD_SET(c->sock, writeset);
+	}
+}
+
+static void
+channel_post_rdynamic(Channel *c, fd_set *readset, fd_set *writeset)
+{
+	if (c->sock < 0)
+		return;
+	if (FD_ISSET(c->sock, writeset)) {
+		int err = 0;
+		socklen_t sz = sizeof(err);
+
+		if (getsockopt(c->sock, SOL_SOCKET, SO_ERROR, &err, &sz) < 0) {
+			err = errno;
+			error("getsockopt SO_ERROR failed");
+		}
+		if (err == 0)
+			c->type = SSH_CHANNEL_OPEN;
+		else {
+			/* Try next address, if any */
+			int sock;
+			if ((sock = connect_next(&c->connect_ctx)) > 0) {
+				close(c->sock);
+				c->sock = c->rfd = c->wfd = sock;
+				channel_max_fd = channel_find_maxfd();
+				return;
+			}
+			chan_mark_dead(c);
+		}
+	}
+}
+
 /* This is our fake X11 server socket. */
 /* ARGSUSED */
 static void
@@ -1984,6 +2094,7 @@ channel_handler_init_20(void)
 	channel_pre[SSH_CHANNEL_DYNAMIC] =		&channel_pre_dynamic;
 	channel_pre[SSH_CHANNEL_MUX_LISTENER] =		&channel_pre_listener;
 	channel_pre[SSH_CHANNEL_MUX_CLIENT] =		&channel_pre_mux_client;
+	channel_pre[SSH_CHANNEL_RDYNAMIC] =		&channel_pre_rdynamic;
 
 	channel_post[SSH_CHANNEL_OPEN] =		&channel_post_open;
 	channel_post[SSH_CHANNEL_PORT_LISTENER] =	&channel_post_port_listener;
@@ -1994,6 +2105,7 @@ channel_handler_init_20(void)
 	channel_post[SSH_CHANNEL_DYNAMIC] =		&channel_post_open;
 	channel_post[SSH_CHANNEL_MUX_LISTENER] =	&channel_post_mux_listener;
 	channel_post[SSH_CHANNEL_MUX_CLIENT] =		&channel_post_mux_client;
+	channel_post[SSH_CHANNEL_RDYNAMIC] =		&channel_post_rdynamic;
 }
 
 static void
@@ -2008,6 +2120,7 @@ channel_handler_init_13(void)
 	channel_pre[SSH_CHANNEL_OUTPUT_DRAINING] =	&channel_pre_output_draining;
 	channel_pre[SSH_CHANNEL_CONNECTING] =		&channel_pre_connecting;
 	channel_pre[SSH_CHANNEL_DYNAMIC] =		&channel_pre_dynamic;
+	channel_pre[SSH_CHANNEL_RDYNAMIC] =		&channel_pre_rdynamic;
 
 	channel_post[SSH_CHANNEL_OPEN] =		&channel_post_open;
 	channel_post[SSH_CHANNEL_X11_LISTENER] =	&channel_post_x11_listener;
@@ -2016,6 +2129,7 @@ channel_handler_init_13(void)
 	channel_post[SSH_CHANNEL_OUTPUT_DRAINING] =	&channel_post_output_drain_13;
 	channel_post[SSH_CHANNEL_CONNECTING] =		&channel_post_connecting;
 	channel_post[SSH_CHANNEL_DYNAMIC] =		&channel_post_open;
+	channel_post[SSH_CHANNEL_RDYNAMIC] =		&channel_post_rdynamic;
 }
 
 static void
@@ -2028,6 +2142,7 @@ channel_handler_init_15(void)
 	channel_pre[SSH_CHANNEL_AUTH_SOCKET] =		&channel_pre_listener;
 	channel_pre[SSH_CHANNEL_CONNECTING] =		&channel_pre_connecting;
 	channel_pre[SSH_CHANNEL_DYNAMIC] =		&channel_pre_dynamic;
+	channel_pre[SSH_CHANNEL_RDYNAMIC] =		&channel_pre_rdynamic;
 
 	channel_post[SSH_CHANNEL_X11_LISTENER] =	&channel_post_x11_listener;
 	channel_post[SSH_CHANNEL_PORT_LISTENER] =	&channel_post_port_listener;
@@ -2035,6 +2150,7 @@ channel_handler_init_15(void)
 	channel_post[SSH_CHANNEL_OPEN] =		&channel_post_open;
 	channel_post[SSH_CHANNEL_CONNECTING] =		&channel_post_connecting;
 	channel_post[SSH_CHANNEL_DYNAMIC] =		&channel_post_open;
+	channel_post[SSH_CHANNEL_RDYNAMIC] =		&channel_post_rdynamic;
 }
 
 static void
@@ -2190,10 +2306,12 @@ channel_output_poll(void)
 		 */
 		if (compat13) {
 			if (c->type != SSH_CHANNEL_OPEN &&
-			    c->type != SSH_CHANNEL_INPUT_DRAINING)
+			    c->type != SSH_CHANNEL_INPUT_DRAINING &&
+			    c->type != SSH_CHANNEL_RDYNAMIC)
 				continue;
 		} else {
-			if (c->type != SSH_CHANNEL_OPEN)
+			if (c->type != SSH_CHANNEL_OPEN &&
+			    c->type != SSH_CHANNEL_RDYNAMIC)
 				continue;
 		}
 		if (compat20 &&
@@ -2318,7 +2436,8 @@ channel_input_data(int type, u_int32_t seq, void *ctxt)
 
 	/* Ignore any data for non-open channels (might happen on close) */
 	if (c->type != SSH_CHANNEL_OPEN &&
-	    c->type != SSH_CHANNEL_X11_OPEN)
+	    c->type != SSH_CHANNEL_X11_OPEN &&
+		c->type != SSH_CHANNEL_RDYNAMIC)
 		return;
 
 	/* Get the data. */
@@ -3301,38 +3420,51 @@ channel_connect_ctx_free(struct channel_connect *cctx)
 	cctx->ai = cctx->aitop = NULL;
 }
 
-/* Return CONNECTING channel to remote host, port */
-static Channel *
-connect_to(const char *host, u_short port, char *ctype, char *rname)
+static int
+connect_to_helper(const char *host, u_short port, struct channel_connect *cctx)
 {
 	struct addrinfo hints;
 	int gaierr;
 	int sock = -1;
 	char strport[NI_MAXSERV];
-	struct channel_connect cctx;
-	Channel *c;
 
-	memset(&cctx, 0, sizeof(cctx));
+	memset(cctx, 0, sizeof(*cctx));
 	memset(&hints, 0, sizeof(hints));
 	hints.ai_family = IPv4or6;
 	hints.ai_socktype = SOCK_STREAM;
 	snprintf(strport, sizeof strport, "%d", port);
-	if ((gaierr = getaddrinfo(host, strport, &hints, &cctx.aitop)) != 0) {
+	if ((gaierr = getaddrinfo(host, strport, &hints, &cctx->aitop)) != 0) {
 		error("connect_to %.100s: unknown host (%s)", host,
 		    ssh_gai_strerror(gaierr));
-		return NULL;
+		return -1;
 	}
 
-	cctx.host = xstrdup(host);
-	cctx.port = port;
-	cctx.ai = cctx.aitop;
+	cctx->host = xstrdup(host);
+	cctx->port = port;
+	cctx->ai = cctx->aitop;
 
-	if ((sock = connect_next(&cctx)) == -1) {
+	if ((sock = connect_next(cctx)) == -1) {
 		error("connect to %.100s port %d failed: %s",
 		    host, port, strerror(errno));
-		channel_connect_ctx_free(&cctx);
-		return NULL;
+		channel_connect_ctx_free(cctx);
+		return -1;
 	}
+
+	return sock;
+}
+
+/* Return CONNECTING channel to remote host, port */
+static Channel *
+connect_to(const char *host, u_short port, char *ctype, char *rname)
+{
+	int sock;
+	struct channel_connect cctx;
+	Channel *c;
+
+	sock = connect_to_helper(host, port, &cctx);
+	if (sock == -1)
+		return NULL;
+
 	c = channel_new(ctype, SSH_CHANNEL_CONNECTING, sock, sock, -1,
 	    CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, 0, rname, 1);
 	c->connect_ctx = cctx;
@@ -3347,6 +3479,10 @@ channel_connect_by_listen_address(u_short listen_port, char *ctype, char *rname)
 	for (i = 0; i < num_permitted_opens; i++) {
 		if (permitted_opens[i].host_to_connect != NULL &&
 		    port_match(permitted_opens[i].listen_port, listen_port)) {
+			if (permitted_opens[i].port_to_connect == FWD_PERMIT_ANY_PORT)
+				return channel_new(ctype, SSH_CHANNEL_RDYNAMIC, -1, -1, -1,
+				    CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, 0, rname, 1);
+
 			return connect_to(
 			    permitted_opens[i].host_to_connect,
 			    permitted_opens[i].port_to_connect, ctype, rname);
diff --git a/channels.h b/channels.h
index d75b800..cf6553e 100644
--- a/channels.h
+++ b/channels.h
@@ -55,7 +55,8 @@
 #define SSH_CHANNEL_ZOMBIE		14	/* Almost dead. */
 #define SSH_CHANNEL_MUX_LISTENER	15	/* Listener for mux conn. */
 #define SSH_CHANNEL_MUX_CLIENT		16	/* Conn. to mux slave */
-#define SSH_CHANNEL_MAX_TYPE		17
+#define SSH_CHANNEL_RDYNAMIC		17  /* reversed SSH_CHANNEL_DYNAMIC */
+#define SSH_CHANNEL_MAX_TYPE		18
 
 #define CHANNEL_CANCEL_PORT_STATIC	-1
 
diff --git a/ssh.c b/ssh.c
index 3f61eb0..a407aaa 100644
--- a/ssh.c
+++ b/ssh.c
@@ -549,7 +549,8 @@ main(int ac, char **av)
 			break;
 
 		case 'R':
-			if (parse_forward(&fwd, optarg, 0, 1)) {
+			if (parse_forward(&fwd, optarg, 1, 1) ||
+				parse_forward(&fwd, optarg, 0, 1)) {
 				add_remote_forward(&options, &fwd);
 			} else {
 				fprintf(stderr,
-- 
1.7.12.3



More information about the openssh-unix-dev mailing list