diff options
author | Damien Miller <djm@cvs.openbsd.org> | 2008-05-09 04:55:57 +0000 |
---|---|---|
committer | Damien Miller <djm@cvs.openbsd.org> | 2008-05-09 04:55:57 +0000 |
commit | d79fe0902bd789511d7ab7d040d668007459034f (patch) | |
tree | 1fbf6e7cfe89fe95cb7910cb381bbe58cabf50df /usr.bin/ssh/channels.c | |
parent | fc872de97eda819b6ba097cc31132f80eedb9d35 (diff) |
Try additional addresses when connecting to a port forward destination
whose DNS name resolves to more than one address. The previous behaviour
was to try the first address and give up.
Reported by stig AT venaas.com in bz#343
great feedback and ok markus@
Diffstat (limited to 'usr.bin/ssh/channels.c')
-rw-r--r-- | usr.bin/ssh/channels.c | 167 |
1 files changed, 110 insertions, 57 deletions
diff --git a/usr.bin/ssh/channels.c b/usr.bin/ssh/channels.c index 244e5e1f30c..d8a4404b849 100644 --- a/usr.bin/ssh/channels.c +++ b/usr.bin/ssh/channels.c @@ -1,4 +1,4 @@ -/* $OpenBSD: channels.c,v 1.275 2008/05/08 12:02:23 djm Exp $ */ +/* $OpenBSD: channels.c,v 1.276 2008/05/09 04:55:56 djm Exp $ */ /* * Author: Tatu Ylonen <ylo@cs.hut.fi> * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland @@ -161,6 +161,10 @@ static int IPv4or6 = AF_UNSPEC; /* helper */ 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 *); + /* -- channel core */ Channel * @@ -1420,7 +1424,7 @@ channel_post_auth_listener(Channel *c, fd_set *readset, fd_set *writeset) static void channel_post_connecting(Channel *c, fd_set *readset, fd_set *writeset) { - int err = 0; + int err = 0, sock; socklen_t sz = sizeof(err); if (FD_ISSET(c->sock, writeset)) { @@ -1429,7 +1433,9 @@ channel_post_connecting(Channel *c, fd_set *readset, fd_set *writeset) error("getsockopt SO_ERROR failed"); } if (err == 0) { - debug("channel %d: connected", c->self); + debug("channel %d: connected to %s port %d", + c->self, c->connect_ctx.host, c->connect_ctx.port); + channel_connect_ctx_free(&c->connect_ctx); c->type = SSH_CHANNEL_OPEN; if (compat20) { packet_start(SSH2_MSG_CHANNEL_OPEN_CONFIRMATION); @@ -1443,8 +1449,19 @@ channel_post_connecting(Channel *c, fd_set *readset, fd_set *writeset) packet_put_int(c->self); } } else { - debug("channel %d: not connected: %s", + debug("channel %d: connection failed: %s", c->self, strerror(err)); + /* Try next address, if any */ + 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; + } + /* Exhausted all addresses */ + error("connect_to %.100s port %d: failed.", + c->connect_ctx.host, c->connect_ctx.port); + channel_connect_ctx_free(&c->connect_ctx); if (compat20) { packet_start(SSH2_MSG_CHANNEL_OPEN_FAILURE); packet_put_int(c->remote_id); @@ -2310,7 +2327,7 @@ channel_input_port_open(int type, u_int32_t seq, void *ctxt) Channel *c = NULL; u_short host_port; char *host, *originator_string; - int remote_id, sock = -1; + int remote_id; remote_id = packet_get_int(); host = packet_get_string(NULL); @@ -2322,20 +2339,16 @@ channel_input_port_open(int type, u_int32_t seq, void *ctxt) originator_string = xstrdup("unknown (remote did not supply name)"); } packet_check_eom(); - sock = channel_connect_to(host, host_port); - if (sock != -1) { - c = channel_new("connected socket", - SSH_CHANNEL_CONNECTING, sock, sock, -1, 0, 0, 0, - originator_string, 1); - c->remote_id = remote_id; - } + c = channel_connect_to(host, host_port, + "connected socket", originator_string); xfree(originator_string); + xfree(host); if (c == NULL) { packet_start(SSH_MSG_CHANNEL_OPEN_FAILURE); packet_put_int(remote_id); packet_send(); - } - xfree(host); + } else + c->remote_id = remote_id; } /* ARGSUSED */ @@ -2747,35 +2760,26 @@ channel_clear_adm_permitted_opens(void) num_adm_permitted_opens = 0; } -/* return socket to remote host, port */ +/* Try to start non-blocking connect to next host in cctx list */ static int -connect_to(const char *host, u_short port) +connect_next(struct channel_connect *cctx) { - struct addrinfo hints, *ai, *aitop; + int sock, saved_errno; char ntop[NI_MAXHOST], strport[NI_MAXSERV]; - int gaierr; - int sock = -1; - 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, &aitop)) != 0) { - error("connect_to %.100s: unknown host (%s)", host, - ssh_gai_strerror(gaierr)); - return -1; - } - for (ai = aitop; ai; ai = ai->ai_next) { - if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6) + for (; cctx->ai; cctx->ai = cctx->ai->ai_next) { + if (cctx->ai->ai_family != AF_INET && + cctx->ai->ai_family != AF_INET6) continue; - if (getnameinfo(ai->ai_addr, ai->ai_addrlen, ntop, sizeof(ntop), - strport, sizeof(strport), NI_NUMERICHOST|NI_NUMERICSERV) != 0) { - error("connect_to: getnameinfo failed"); + if (getnameinfo(cctx->ai->ai_addr, cctx->ai->ai_addrlen, + ntop, sizeof(ntop), strport, sizeof(strport), + NI_NUMERICHOST|NI_NUMERICSERV) != 0) { + error("connect_next: getnameinfo failed"); continue; } - sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); - if (sock < 0) { - if (ai->ai_next == NULL) + if ((sock = socket(cctx->ai->ai_family, cctx->ai->ai_socktype, + cctx->ai->ai_protocol)) == -1) { + if (cctx->ai->ai_next == NULL) error("socket: %.100s", strerror(errno)); else verbose("socket: %.100s", strerror(errno)); @@ -2783,45 +2787,94 @@ connect_to(const char *host, u_short port) } if (set_nonblock(sock) == -1) fatal("%s: set_nonblock(%d)", __func__, sock); - if (connect(sock, ai->ai_addr, ai->ai_addrlen) < 0 && - errno != EINPROGRESS) { - error("connect_to %.100s port %s: %.100s", ntop, strport, + if (connect(sock, cctx->ai->ai_addr, + cctx->ai->ai_addrlen) == -1 && errno != EINPROGRESS) { + debug("connect_next: host %.100s ([%.100s]:%s): " + "%.100s", cctx->host, ntop, strport, strerror(errno)); + saved_errno = errno; close(sock); + errno = saved_errno; continue; /* fail -- try next */ } - break; /* success */ + debug("connect_next: host %.100s ([%.100s]:%s) " + "in progress, fd=%d", cctx->host, ntop, strport, sock); + cctx->ai = cctx->ai->ai_next; + set_nodelay(sock); + return sock; + } + return -1; +} + +static void +channel_connect_ctx_free(struct channel_connect *cctx) +{ + xfree(cctx->host); + if (cctx->aitop) + freeaddrinfo(cctx->aitop); + bzero(cctx, sizeof(*cctx)); + cctx->host = NULL; + 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) +{ + struct addrinfo hints; + int gaierr; + int sock = -1; + char strport[NI_MAXSERV]; + struct channel_connect cctx; + Channel *c; + + 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) { + error("connect_to %.100s: unknown host (%s)", host, + ssh_gai_strerror(gaierr)); + return NULL; } - freeaddrinfo(aitop); - if (!ai) { - error("connect_to %.100s port %d: failed.", host, port); - return -1; + + cctx.host = xstrdup(host); + cctx.port = port; + cctx.ai = cctx.aitop; + + 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; } - /* success */ - set_nodelay(sock); - return sock; + 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; + return c; } -int -channel_connect_by_listen_address(u_short listen_port) +Channel * +channel_connect_by_listen_address(u_short listen_port, char *ctype, char *rname) { int i; - for (i = 0; i < num_permitted_opens; i++) + for (i = 0; i < num_permitted_opens; i++) { if (permitted_opens[i].host_to_connect != NULL && - permitted_opens[i].listen_port == listen_port) + permitted_opens[i].listen_port == listen_port) { return connect_to( permitted_opens[i].host_to_connect, - permitted_opens[i].port_to_connect); + permitted_opens[i].port_to_connect, ctype, rname); + } + } error("WARNING: Server requests forwarding for unknown listen_port %d", listen_port); - return -1; + return NULL; } /* Check if connecting to that port is permitted and connect. */ -int -channel_connect_to(const char *host, u_short port) +Channel * +channel_connect_to(const char *host, u_short port, char *ctype, char *rname) { int i, permit, permit_adm = 1; @@ -2847,9 +2900,9 @@ channel_connect_to(const char *host, u_short port) if (!permit || !permit_adm) { logit("Received request to connect to host %.100s port %d, " "but the request was denied.", host, port); - return -1; + return NULL; } - return connect_to(host, port); + return connect_to(host, port, ctype, rname); } void |