changeset 1025:02baa0b334e8 fastopen

async connections working
author Matt Johnston <matt@ucc.asn.au>
date Wed, 18 Feb 2015 22:46:15 +0800
parents aac0095dc3b4
children 59a1146e8b9d
files channel.h cli-main.c cli-session.c cli-tcpfwd.c common-channel.c common-session.c dbutil.c dbutil.h packet.c session.h svr-tcpfwd.c
diffstat 11 files changed, 109 insertions(+), 106 deletions(-) [+]
line wrap: on
line diff
--- a/channel.h	Wed Feb 18 00:05:27 2015 +0800
+++ b/channel.h	Wed Feb 18 22:46:15 2015 +0800
@@ -73,6 +73,7 @@
 	 * to ensure we don't run it twice (nor type->checkclose()). */
 	int close_handler_done;
 
+	struct dropbear_progress_connection *conn_pending;
 	int initconn; /* used for TCP forwarding, whether the channel has been
 					 fully initialised */
 
@@ -100,6 +101,9 @@
 	void (*closehandler)(struct Channel*);
 };
 
+/* Callback for connect_remote */
+void channel_connect_done(int result, int sock, void* user_data, const char* errstring);
+
 void chaninitialise(const struct ChanType *chantypes[]);
 void chancleanup();
 void setchannelfds(fd_set *readfd, fd_set *writefd);
--- a/cli-main.c	Wed Feb 18 00:05:27 2015 +0800
+++ b/cli-main.c	Wed Feb 18 22:46:15 2015 +0800
@@ -72,12 +72,8 @@
 	} else
 #endif
 	{
-		int sock = connect_remote(cli_opts.remotehost, cli_opts.remoteport, &error);
-		sock_in = sock_out = sock;
-	}
-
-	if (sock_in < 0) {
-		dropbear_exit("%s", error);
+		connect_remote(cli_opts.remotehost, cli_opts.remoteport, cli_connected, NULL);
+		sock_in = sock_out = -1;
 	}
 
 	cli_session(sock_in, sock_out);
--- a/cli-session.c	Wed Feb 18 00:05:27 2015 +0800
+++ b/cli-session.c	Wed Feb 18 22:46:15 2015 +0800
@@ -93,6 +93,15 @@
 	NULL /* Null termination */
 };
 
+void cli_connected(int result, int sock, void* userdata, const char *errstring)
+{
+	if (result == DROPBEAR_FAILURE)
+	{
+		dropbear_exit("Connect failed: %s", errstring);
+	}
+	ses.sock_in = ses.sock_out = sock;
+}
+
 void cli_session(int sock_in, int sock_out) {
 
 	common_session_init(sock_in, sock_out);
--- a/cli-tcpfwd.c	Wed Feb 18 00:05:27 2015 +0800
+++ b/cli-tcpfwd.c	Wed Feb 18 22:46:15 2015 +0800
@@ -254,19 +254,7 @@
 	}
 	
 	snprintf(portstring, sizeof(portstring), "%d", fwd->connectport);
-	sock = connect_remote(fwd->connectaddr, portstring, NULL);
-	if (sock < 0) {
-		TRACE(("leave newtcpdirect: sock failed"))
-		err = SSH_OPEN_CONNECT_FAILED;
-		goto out;
-	}
-
-	ses.maxfd = MAX(ses.maxfd, sock);
-
-	/* We don't set readfd, that will get set after the connection's
-	 * progress succeeds */
-	channel->writefd = sock;
-	channel->initconn = 1;
+	channel->conn_pending = connect_remote(fwd->connectaddr, portstring, channel_connect_done, channel);
 
 	channel->prio = DROPBEAR_CHANNEL_PRIO_UNKNOWABLE;
 	
--- a/common-channel.c	Wed Feb 18 00:05:27 2015 +0800
+++ b/common-channel.c	Wed Feb 18 22:46:15 2015 +0800
@@ -48,7 +48,6 @@
 static void send_msg_channel_eof(struct Channel *channel);
 static void send_msg_channel_close(struct Channel *channel);
 static void remove_channel(struct Channel *channel);
-static void check_in_progress(struct Channel *channel);
 static unsigned int write_pending(struct Channel * channel);
 static void check_close(struct Channel *channel);
 static void close_chan_fd(struct Channel *channel, int fd, int how);
@@ -163,7 +162,6 @@
 	newchan->writefd = FD_UNINIT;
 	newchan->readfd = FD_UNINIT;
 	newchan->errfd = FD_CLOSED; /* this isn't always set to start with */
-	newchan->initconn = 0;
 	newchan->await_open = 0;
 	newchan->flushing = 0;
 
@@ -242,12 +240,6 @@
 
 		/* write to program/pipe stdin */
 		if (channel->writefd >= 0 && FD_ISSET(channel->writefd, writefds)) {
-			if (channel->initconn) {
-				/* XXX should this go somewhere cleaner? */
-				check_in_progress(channel);
-				continue; /* Important not to use the channel after
-							 check_in_progress(), as it may be NULL */
-			}
 			writechannel(channel, channel->writefd, channel->writebuf);
 			do_check_close = 1;
 		}
@@ -374,27 +366,27 @@
  * if so, set up the channel properly. Otherwise, the channel is cleaned up, so
  * it is important that the channel reference isn't used after a call to this
  * function */
-static void check_in_progress(struct Channel *channel) {
+void channel_connect_done(int result, int sock, void* user_data, const char* UNUSED(errstring)) {
 
-	int val;
-	socklen_t vallen = sizeof(val);
-
-	TRACE(("enter check_in_progress"))
+	struct Channel *channel = user_data;
 
-	if (getsockopt(channel->writefd, SOL_SOCKET, SO_ERROR, &val, &vallen)
-			|| val != 0) {
-		send_msg_channel_open_failure(channel->remotechan,
-				SSH_OPEN_CONNECT_FAILED, "", "");
-		close(channel->writefd);
-		remove_channel(channel);
-		TRACE(("leave check_in_progress: fail"))
-	} else {
+	TRACE(("enter channel_connect_done"))
+
+	if (result == DROPBEAR_SUCCESS)
+	{
+		channel->readfd = channel->writefd = sock;
+		channel->conn_pending = NULL;
 		chan_initwritebuf(channel);
 		send_msg_channel_open_confirmation(channel, channel->recvwindow,
 				channel->recvmaxpacket);
-		channel->readfd = channel->writefd;
-		channel->initconn = 0;
-		TRACE(("leave check_in_progress: success"))
+		TRACE(("leave channel_connect_done: success"))
+	}
+	else
+	{
+		send_msg_channel_open_failure(channel->remotechan,
+				SSH_OPEN_CONNECT_FAILED, "", "");
+		remove_channel(channel);
+		TRACE(("leave check_in_progress: fail"))
 	}
 }
 
@@ -514,8 +506,7 @@
 		}
 
 		/* Stuff from the wire */
-		if (channel->initconn
-			||(channel->writefd >= 0 && cbuf_getused(channel->writebuf) > 0)) {
+		if (channel->writefd >= 0 && cbuf_getused(channel->writebuf) > 0) {
 				FD_SET(channel->writefd, writefds);
 		}
 
@@ -599,6 +590,10 @@
 		channel->close_handler_done = 1;
 	}
 
+	if (channel->conn_pending) {
+		cancel_connect(channel->conn_pending);
+	}
+
 	ses.channels[channel->index] = NULL;
 	m_free(channel);
 	ses.chancount--;
@@ -1149,7 +1144,7 @@
 		struct Channel *chan = ses.channels[i];
 		if (chan
 				&& !(chan->sent_eof || chan->recv_eof)
-				&& !(chan->await_open || chan->initconn)) {
+				&& !(chan->await_open)) {
 			return chan;
 		}
 	}
--- a/common-session.c	Wed Feb 18 00:05:27 2015 +0800
+++ b/common-session.c	Wed Feb 18 22:46:15 2015 +0800
@@ -167,6 +167,9 @@
 		/* set up for channels which can be read/written */
 		setchannelfds(&readfd, &writefd);
 
+		/* Pending connections to test */
+		set_connect_fds(&writefd);
+
 		val = select(ses.maxfd+1, &readfd, &writefd, NULL, &timeout);
 
 		if (exitflag) {
@@ -214,11 +217,13 @@
 				process_packet();
 			}
 		}
-		
+
 		/* if required, flush out any queued reply packets that
 		were being held up during a KEX */
 		maybe_flush_reply_queue();
 
+		handle_connect_fds(&writefd);
+
 		/* process pipes etc for the channels, ses.dataallowed == 0
 		 * during rekeying ) */
 		channelio(&readfd, &writefd);
--- a/dbutil.c	Wed Feb 18 00:05:27 2015 +0800
+++ b/dbutil.c	Wed Feb 18 22:46:15 2015 +0800
@@ -995,6 +995,8 @@
 								or NULL. */
 
 	int sock;
+
+	char* errstring;
 };
 
 /* Deallocate a progress connection. Removes from the pending list if iter!=NULL.
@@ -1005,6 +1007,7 @@
 	}
 	m_free(c->remotehost);
 	m_free(c->remoteport);
+	m_free(c->errstring);
 	m_free(c);
 
 	if (iter) {
@@ -1012,12 +1015,24 @@
 	}
 }
 
-static int connect_try_next(struct dropbear_progress_connection *c) {
+static void cancel_callback(int result, int sock, void* UNUSED(data), const char* UNUSED(errstring)) {
+	if (result == DROPBEAR_SUCCESS)
+	{
+		m_close(sock);
+	}
+}
+
+void cancel_connect(struct dropbear_progress_connection *c) {
+	c->cb = cancel_callback;
+	c->cb_data = NULL;
+}
+
+static void connect_try_next(struct dropbear_progress_connection *c) {
 	int err = EADDRNOTAVAIL;
 	struct addrinfo *r;
 
 	if (!c->res_iter) {
-		return DROPBEAR_FAILURE;
+		return;
 	}
 
 	for (r = c->res_iter; r; r = r->ai_next)
@@ -1030,6 +1045,7 @@
 			continue;
 		}
 
+		ses.maxfd = MAX(ses.maxfd, c->sock);
 		setnonblocking(c->sock);
 
 #if defined(__linux__) && defined(TCP_DEFER_ACCEPT)
@@ -1060,8 +1076,12 @@
 	if (c->sock >= 0 || (errno == EINPROGRESS)) {
 		/* Success */
 		set_sock_nodelay(c->sock);
-		return DROPBEAR_SUCCESS;
+		return;
 	} else {
+		if (!c->res_iter)
+		{
+
+		}
 		/* XXX - returning error message through */
 #if 0
 		/* Failed */
@@ -1073,15 +1093,10 @@
 		}
 		TRACE(("Error connecting: %s", strerror(err)))
 #endif
-		return DROPBEAR_FAILURE;
 	}
 }
 
-/* Connect via TCP to a host. Connection will try ipv4 or ipv6, will
- * return immediately if nonblocking is set. On failure, if errstring
- * wasn't null, it will be a newly malloced error message */
-
-/* TODO: maxfd */
+/* Connect via TCP to a host. */
 struct dropbear_progress_connection *connect_remote(const char* remotehost, const char* remoteport,
 	connect_callback cb, void* cb_data)
 {
@@ -1096,6 +1111,8 @@
 	c->cb = cb;
 	c->cb_data = cb_data;
 
+	list_append(&ses.conn_pending, c);
+
 	memset(&hints, 0, sizeof(hints));
 	hints.ai_socktype = SOCK_STREAM;
 	hints.ai_family = PF_UNSPEC;
@@ -1103,29 +1120,18 @@
 	err = getaddrinfo(remotehost, remoteport, &hints, &c->res);
 	if (err) {
 		int len;
-		char *errstring;
 		len = 100 + strlen(gai_strerror(err));
-		errstring = (char*)m_malloc(len);
-		snprintf(errstring, len, "Error resolving '%s' port '%s'. %s", 
+		c->errstring = (char*)m_malloc(len);
+		snprintf(c->errstring, len, "Error resolving '%s' port '%s'. %s", 
 				remotehost, remoteport, gai_strerror(err));
-		c->cb(DROPBEAR_FAILURE, -1, c->cb_data, errstring);
-		m_free(errstring);
 		TRACE(("Error resolving: %s", gai_strerror(err)))
-		remove_connect(c, NULL);
 		return NULL;
 	}
 
 	c->res_iter = c->res;
 
-	if (connect_try_next(c) == DROPBEAR_FAILURE) {
-		/* Should not happen - getaddrinfo() should return failure if there are no addresses */
-		c->cb(DROPBEAR_FAILURE, -1, c->cb_data, "No address to try");
-		TRACE(("leave handle_connect_fds - failed"))
-		remove_connect(c, NULL);
-		return NULL;
-	}
-
-	list_append(&ses.conn_pending, c);
+	/* Set one going */
+	connect_try_next(c);
 
 	return c;
 }
@@ -1136,12 +1142,24 @@
 	TRACE(("enter handle_connect_fds"))
 	for (iter = ses.conn_pending.first; iter; iter = iter->next) {
 		struct dropbear_progress_connection *c = iter->item;
+		/* Set one going */
+		while (c->res_iter && c->sock < 0)
+		{
+			connect_try_next(c);
+		}
 		if (c->sock >= 0) {
 			FD_SET(c->sock, writefd);
-		}
-		else
-		{
-
+		} else {
+			m_list_elem *remove_iter;
+			/* Final failure */
+			if (!c->errstring) {
+				c->errstring = m_strdup("unexpected failure");
+			}
+			c->cb(DROPBEAR_FAILURE, -1, c->cb_data, c->errstring);
+			/* Safely remove without invalidating iter */
+			remove_iter = iter;
+			iter = iter->prev;
+			remove_connect(c, remove_iter);
 		}
 	}
 }
@@ -1162,31 +1180,25 @@
 
 		if (getsockopt(c->sock, SOL_SOCKET, SO_ERROR, &val, &vallen) != 0) {
 			TRACE(("handle_connect_fds getsockopt(%d) SO_ERROR failed: %s", c->sock, strerror(errno)))
+			/* This isn't expected to happen - Unix has surprises though, continue gracefully. */
+			m_close(c->sock);
+			c->sock = -1;
 		} else if (val != 0) {
 			/* Connect failed */
 			TRACE(("connect to %s port %s failed.", c->remotehost, c->remoteport))
 			m_close(c->sock);
 			c->sock = -1;
 
-			if (connect_try_next(c) == DROPBEAR_FAILURE) {
-				c->cb(DROPBEAR_FAILURE, -1, c->cb_data, strerror(val));
-				TRACE(("leave handle_connect_fds - failed"))
-				remove_connect(c, iter);
-				/* Must return here - remove_connect() invalidates iter */
-				return; 
-			} else {
-				/* new connection try was successfuly started, will be finished by a
-				later call to handle_connect_fds() */
-				TRACE(("leave handle_connect_fds - new try"))
-				continue;
-			}
+			m_free(c->errstring);
+			c->errstring = strerror(val);
+		} else {
+			/* New connection has been established */
+			c->cb(DROPBEAR_SUCCESS, c->sock, c->cb_data, NULL);
+			remove_connect(c, iter);
+			TRACE(("leave handle_connect_fds - success"))
+			/* Must return here - remove_connect() invalidates iter */
+			return; 
 		}
-		/* New connection has been established */
-		c->cb(DROPBEAR_SUCCESS, c->sock, c->cb_data, "");
-		remove_connect(c, iter);
-		TRACE(("leave handle_connect_fds - success"))
-		/* Must return here - remove_connect() invalidates iter */
-		return; 
 	}
 	TRACE(("leave handle_connect_fds - end iter"))
 }
--- a/dbutil.h	Wed Feb 18 00:05:27 2015 +0800
+++ b/dbutil.h	Wed Feb 18 22:46:15 2015 +0800
@@ -129,4 +129,7 @@
 void set_connect_fds(fd_set *writefd);
 void handle_connect_fds(fd_set *writefd);
 
+/* Doesn't actually stop the connect, but adds a dummy callback instead */
+void cancel_connect(struct dropbear_progress_connection *c);
+
 #endif /* _DBUTIL_H_ */
--- a/packet.c	Wed Feb 18 00:05:27 2015 +0800
+++ b/packet.c	Wed Feb 18 22:46:15 2015 +0800
@@ -52,6 +52,7 @@
 static void buf_compress(buffer * dest, buffer * src, unsigned int len);
 #endif
 
+#if 0
 struct iovec * dropbear_queue_to_iovec(struct Queue *queue) {
 
 	struct iovec *iov = NULL;
@@ -69,6 +70,7 @@
 void dropbear_queue_consume(struct Queue *queue, ssize_t written) {
 
 }
+#endif
 
 /* non-blocking function writing out a current encrypted packet */
 void write_packet() {
--- a/session.h	Wed Feb 18 00:05:27 2015 +0800
+++ b/session.h	Wed Feb 18 22:46:15 2015 +0800
@@ -61,6 +61,7 @@
 
 /* Client */
 void cli_session(int sock_in, int sock_out);
+void cli_connected(int result, int sock, void* userdata, const char *errstring);
 void cleantext(unsigned char* dirtytext);
 
 /* crypto parameters that are stored individually for transmit and receive */
--- a/svr-tcpfwd.c	Wed Feb 18 00:05:27 2015 +0800
+++ b/svr-tcpfwd.c	Wed Feb 18 22:46:15 2015 +0800
@@ -270,19 +270,7 @@
 	}
 
 	snprintf(portstring, sizeof(portstring), "%d", destport);
-	sock = connect_remote(desthost, portstring, NULL);
-	if (sock < 0) {
-		err = SSH_OPEN_CONNECT_FAILED;
-		TRACE(("leave newtcpdirect: sock failed"))
-		goto out;
-	}
-
-	ses.maxfd = MAX(ses.maxfd, sock);
-
-	 /* We don't set readfd, that will get set after the connection's
-	 * progress succeeds */
-	channel->writefd = sock;
-	channel->initconn = 1;
+	channel->conn_pending = connect_remote(desthost, portstring, channel_connect_done, channel);
 
 	channel->prio = DROPBEAR_CHANNEL_PRIO_UNKNOWABLE;