[PATCH] Implement remote dynamic TCP forwarding
Markus Friedl
mfriedl at gmail.com
Mon Oct 29 05:26:13 EST 2012
Thanks! i'll try to have a look into this....
On Mon, Oct 22, 2012 at 9:13 AM, Kai-Chieh Ku <kjackie at gmail.com> wrote:
> 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
>
> _______________________________________________
> openssh-unix-dev mailing list
> openssh-unix-dev at mindrot.org
> https://lists.mindrot.org/mailman/listinfo/openssh-unix-dev
More information about the openssh-unix-dev
mailing list