- djm@cvs.openbsd.org 2008/05/09 04:55:56
     [channels.c channels.h clientloop.c serverloop.c]
     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@
diff --git a/ChangeLog b/ChangeLog
index bd6b640..9e68314 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -104,6 +104,13 @@
      client stderr (subject to LogLevel in the mux master) - better than
      silently failing.
      most bits ok markus@ (as part of a larger diff)
+   - djm@cvs.openbsd.org 2008/05/09 04:55:56
+     [channels.c channels.h clientloop.c serverloop.c]
+     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@
 
 20080403
  - (djm) [openbsd-compat/bsd-poll.c] Include stdlib.h to avoid compile-
@@ -3964,4 +3971,4 @@
    OpenServer 6 and add osr5bigcrypt support so when someone migrates
    passwords between UnixWare and OpenServer they will still work. OK dtucker@
 
-$Id: ChangeLog,v 1.4924 2008/05/19 05:36:08 djm Exp $
+$Id: ChangeLog,v 1.4925 2008/05/19 05:37:09 djm Exp $
diff --git a/channels.c b/channels.c
index b5e28da..1e57951 100644
--- a/channels.c
+++ b/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
@@ -165,6 +165,10 @@
 /* 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 *
@@ -1425,7 +1429,7 @@
 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)) {
@@ -1434,7 +1438,9 @@
 			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);
@@ -1448,8 +1454,19 @@
 				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);
@@ -2327,7 +2344,7 @@
 	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);
@@ -2339,20 +2356,16 @@
 		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 */
@@ -2770,35 +2783,26 @@
 	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));
@@ -2806,45 +2810,94 @@
 		}
 		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;
 	}
-	freeaddrinfo(aitop);
-	if (!ai) {
-		error("connect_to %.100s port %d: failed.", host, port);
-		return -1;
-	}
-	/* success */
-	set_nodelay(sock);
-	return sock;
-}
-
-int
-channel_connect_by_listen_address(u_short listen_port)
-{
-	int i;
-
-	for (i = 0; i < num_permitted_opens; i++)
-		if (permitted_opens[i].host_to_connect != NULL &&
-		    permitted_opens[i].listen_port == listen_port)
-			return connect_to(
-			    permitted_opens[i].host_to_connect,
-			    permitted_opens[i].port_to_connect);
-	error("WARNING: Server requests forwarding for unknown listen_port %d",
-	    listen_port);
 	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;
+	}
+
+	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;
+	}
+	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;
+}
+
+Channel *
+channel_connect_by_listen_address(u_short listen_port, char *ctype, char *rname)
+{
+	int i;
+
+	for (i = 0; i < num_permitted_opens; i++) {
+		if (permitted_opens[i].host_to_connect != NULL &&
+		    permitted_opens[i].listen_port == listen_port) {
+			return connect_to(
+			    permitted_opens[i].host_to_connect,
+			    permitted_opens[i].port_to_connect, ctype, rname);
+		}
+	}
+	error("WARNING: Server requests forwarding for unknown listen_port %d",
+	    listen_port);
+	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;
 
@@ -2870,9 +2923,9 @@
 	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
diff --git a/channels.h b/channels.h
index 46cde03..d4ac24a 100644
--- a/channels.h
+++ b/channels.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: channels.h,v 1.90 2008/05/08 12:02:23 djm Exp $ */
+/* $OpenBSD: channels.h,v 1.91 2008/05/09 04:55:56 djm Exp $ */
 
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -75,6 +75,13 @@
 };
 TAILQ_HEAD(channel_confirms, channel_confirm);
 
+/* Context for non-blocking connects */
+struct channel_connect {
+	char *host;
+	int port;
+	struct addrinfo *ai, *aitop;
+};
+
 struct Channel {
 	int     type;		/* channel type/state */
 	int     self;		/* my own channel identifier */
@@ -125,7 +132,11 @@
 	channel_infilter_fn	*input_filter;
 	channel_outfilter_fn	*output_filter;
 
-	int     datagram;	/* keep boundaries */
+	/* keep boundaries */
+	int     		datagram;
+
+	/* non-blocking connect */
+	struct channel_connect	connect_ctx;
 };
 
 #define CHAN_EXTENDED_IGNORE		0
@@ -225,8 +236,8 @@
 void	 channel_clear_permitted_opens(void);
 void	 channel_clear_adm_permitted_opens(void);
 int      channel_input_port_forward_request(int, int);
-int	 channel_connect_to(const char *, u_short);
-int	 channel_connect_by_listen_address(u_short);
+Channel	*channel_connect_to(const char *, u_short, char *, char *);
+Channel	*channel_connect_by_listen_address(u_short, char *, char *);
 int	 channel_request_remote_forwarding(const char *, u_short,
 	     const char *, u_short);
 int	 channel_setup_local_fwd_listener(const char *, u_short,
@@ -241,7 +252,7 @@
 int	 x11_create_display_inet(int, int, int, u_int *, int **);
 void     x11_input_open(int, u_int32_t, void *);
 void	 x11_request_forwarding_with_spoofing(int, const char *, const char *,
-	    const char *);
+	     const char *);
 void	 deny_input_open(int, u_int32_t, void *);
 
 /* agent forwarding */
diff --git a/clientloop.c b/clientloop.c
index c40f2c3..7bd1af6 100644
--- a/clientloop.c
+++ b/clientloop.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: clientloop.c,v 1.190 2008/05/08 13:06:10 djm Exp $ */
+/* $OpenBSD: clientloop.c,v 1.191 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
@@ -1762,7 +1762,6 @@
 	Channel *c = NULL;
 	char *listen_address, *originator_address;
 	int listen_port, originator_port;
-	int sock;
 
 	/* Get rest of the packet */
 	listen_address = packet_get_string(NULL);
@@ -1771,19 +1770,13 @@
 	originator_port = packet_get_int();
 	packet_check_eom();
 
-	debug("client_request_forwarded_tcpip: listen %s port %d, originator %s port %d",
-	    listen_address, listen_port, originator_address, originator_port);
+	debug("client_request_forwarded_tcpip: listen %s port %d, "
+	    "originator %s port %d", listen_address, listen_port,
+	    originator_address, originator_port);
 
-	sock = channel_connect_by_listen_address(listen_port);
-	if (sock < 0) {
-		xfree(originator_address);
-		xfree(listen_address);
-		return NULL;
-	}
-	c = channel_new("forwarded-tcpip",
-	    SSH_CHANNEL_CONNECTING, sock, sock, -1,
-	    CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, 0,
-	    originator_address, 1);
+	c = channel_connect_by_listen_address(listen_port,
+	    "forwarded-tcpip", originator_address);
+
 	xfree(originator_address);
 	xfree(listen_address);
 	return c;
diff --git a/serverloop.c b/serverloop.c
index 20991c3..2142f38 100644
--- a/serverloop.c
+++ b/serverloop.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: serverloop.c,v 1.149 2008/05/08 12:02:23 djm Exp $ */
+/* $OpenBSD: serverloop.c,v 1.150 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
@@ -938,7 +938,6 @@
 server_request_direct_tcpip(void)
 {
 	Channel *c;
-	int sock;
 	char *target, *originator;
 	int target_port, originator_port;
 
@@ -948,18 +947,16 @@
 	originator_port = packet_get_int();
 	packet_check_eom();
 
-	debug("server_request_direct_tcpip: originator %s port %d, target %s port %d",
-	    originator, originator_port, target, target_port);
+	debug("server_request_direct_tcpip: originator %s port %d, target %s "
+	    "port %d", originator, originator_port, target, target_port);
 
 	/* XXX check permission */
-	sock = channel_connect_to(target, target_port);
-	xfree(target);
+	c = channel_connect_to(target, target_port,
+	    "direct-tcpip", "direct-tcpip");
+
 	xfree(originator);
-	if (sock < 0)
-		return NULL;
-	c = channel_new("direct-tcpip", SSH_CHANNEL_CONNECTING,
-	    sock, sock, -1, CHAN_TCP_WINDOW_DEFAULT,
-	    CHAN_TCP_PACKET_DEFAULT, 0, "direct-tcpip", 1);
+	xfree(target);
+
 	return c;
 }