[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