changeset 1024:aac0095dc3b4 fastopen

work in progress for async connect
author Matt Johnston <matt@ucc.asn.au>
date Wed, 18 Feb 2015 00:05:27 +0800
parents a00303a7d247
children 02baa0b334e8
files configure.ac dbutil.c dbutil.h packet.c session.h
diffstat 5 files changed, 246 insertions(+), 93 deletions(-) [+]
line wrap: on
line diff
--- a/configure.ac	Sun Feb 15 22:34:05 2015 +0800
+++ b/configure.ac	Wed Feb 18 00:05:27 2015 +0800
@@ -632,7 +632,7 @@
 AC_PROG_GCC_TRADITIONAL
 AC_FUNC_MEMCMP
 AC_FUNC_SELECT_ARGTYPES
-AC_CHECK_FUNCS([dup2 getspnam getusershell memset putenv select socket strdup clearenv strlcpy strlcat daemon basename _getpty getaddrinfo freeaddrinfo getnameinfo fork writev])
+AC_CHECK_FUNCS([dup2 getspnam getusershell memset putenv select socket strdup clearenv strlcpy strlcat daemon basename _getpty getaddrinfo freeaddrinfo getnameinfo fork writev sendmsg])
 
 AC_SEARCH_LIBS(basename, gen, AC_DEFINE(HAVE_BASENAME))
 
--- a/dbutil.c	Sun Feb 15 22:34:05 2015 +0800
+++ b/dbutil.c	Wed Feb 18 00:05:27 2015 +0800
@@ -439,95 +439,6 @@
 }
 #endif
 
-
-/* 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 */
-int connect_remote(const char* remotehost, const char* remoteport, char ** errstring) {
-
-	struct addrinfo *res0 = NULL, *res = NULL, hints;
-	int sock;
-	int err;
-
-	TRACE(("enter connect_remote"))
-
-	if (errstring != NULL) {
-		*errstring = NULL;
-	}
-
-	memset(&hints, 0, sizeof(hints));
-	hints.ai_socktype = SOCK_STREAM;
-	hints.ai_family = PF_UNSPEC;
-
-	err = getaddrinfo(remotehost, remoteport, &hints, &res0);
-	if (err) {
-		if (errstring != NULL && *errstring == NULL) {
-			int len;
-			len = 100 + strlen(gai_strerror(err));
-			*errstring = (char*)m_malloc(len);
-			snprintf(*errstring, len, "Error resolving '%s' port '%s'. %s", 
-					remotehost, remoteport, gai_strerror(err));
-		}
-		TRACE(("Error resolving: %s", gai_strerror(err)))
-		return -1;
-	}
-
-	sock = -1;
-	err = EADDRNOTAVAIL;
-	for (res = res0; res; res = res->ai_next) {
-
-		sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
-		if (sock < 0) {
-			err = errno;
-			continue;
-		}
-
-		setnonblocking(sock);
-
-#if defined(__linux__) && defined(TCP_DEFER_ACCEPT)
-		set_piggyback_ack(sock);
-#endif
-
-		if (connect(sock, res->ai_addr, res->ai_addrlen) < 0) {
-			if (errno == EINPROGRESS) {
-				TRACE(("Connect in progress"))
-				break;
-			} else {
-				err = errno;
-				close(sock);
-				sock = -1;
-				continue;
-			}
-		}
-
-		break; /* Success */
-	}
-
-	if (sock < 0 && !(errno == EINPROGRESS)) {
-		/* Failed */
-		if (errstring != NULL && *errstring == NULL) {
-			int len;
-			len = 20 + strlen(strerror(err));
-			*errstring = (char*)m_malloc(len);
-			snprintf(*errstring, len, "Error connecting: %s", strerror(err));
-		}
-		TRACE(("Error connecting: %s", strerror(err)))
-	} else {
-		/* Success */
-		set_sock_nodelay(sock);
-	}
-
-	freeaddrinfo(res0);
-	if (sock > 0 && errstring != NULL && *errstring != NULL) {
-		m_free(*errstring);
-	}
-
-	TRACE(("leave connect_remote: sock %d\n", sock))
-	return sock;
-}
-
 /* Sets up a pipe for a, returning three non-blocking file descriptors
  * and the pid. exec_fn is the function that will actually execute the child process,
  * it will be run after the child has fork()ed, and is passed exec_data.
@@ -1069,3 +980,213 @@
 	return time(NULL);
 }
 
+
+struct dropbear_progress_connection
+{
+	struct addrinfo *res;
+	struct addrinfo *res_iter;
+
+	char *remotehost, *remoteport; /* For error reporting */
+
+	connect_callback cb;
+	void *cb_data;
+
+	struct Queue *writequeue; /* A queue of encrypted packets to send with TCP fastopen,
+								or NULL. */
+
+	int sock;
+};
+
+/* Deallocate a progress connection. Removes from the pending list if iter!=NULL.
+Does not close sockets */
+static void remove_connect(struct dropbear_progress_connection *c, m_list_elem *iter) {
+	if (c->res) {
+		freeaddrinfo(c->res);
+	}
+	m_free(c->remotehost);
+	m_free(c->remoteport);
+	m_free(c);
+
+	if (iter) {
+		list_remove(iter);
+	}
+}
+
+static int connect_try_next(struct dropbear_progress_connection *c) {
+	int err = EADDRNOTAVAIL;
+	struct addrinfo *r;
+
+	if (!c->res_iter) {
+		return DROPBEAR_FAILURE;
+	}
+
+	for (r = c->res_iter; r; r = r->ai_next)
+	{
+		assert(c->sock == -1);
+
+		c->sock = socket(c->res_iter->ai_family, c->res_iter->ai_socktype, c->res_iter->ai_protocol);
+		if (c->sock < 0) {
+			err = errno;
+			continue;
+		}
+
+		setnonblocking(c->sock);
+
+#if defined(__linux__) && defined(TCP_DEFER_ACCEPT)
+		set_piggyback_ack(sock);
+#endif
+
+		if (connect(c->sock, r->ai_addr, r->ai_addrlen) < 0) {
+			if (errno == EINPROGRESS) {
+				TRACE(("Connect in progress"))
+				break;
+			} else {
+				err = errno;
+				close(c->sock);
+				c->sock = -1;
+				continue;
+			}
+		}
+
+		break; /* Success. Treated the same as EINPROGRESS */
+	}
+
+	if (r) {
+		c->res_iter = r->ai_next;
+	} else {
+		c->res_iter = NULL;
+	}
+
+	if (c->sock >= 0 || (errno == EINPROGRESS)) {
+		/* Success */
+		set_sock_nodelay(c->sock);
+		return DROPBEAR_SUCCESS;
+	} else {
+		/* XXX - returning error message through */
+#if 0
+		/* Failed */
+		if (errstring != NULL && *errstring == NULL) {
+			int len;
+			len = 20 + strlen(strerror(err));
+			*errstring = (char*)m_malloc(len);
+			snprintf(*errstring, len, "Error connecting: %s", strerror(err));
+		}
+		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 */
+struct dropbear_progress_connection *connect_remote(const char* remotehost, const char* remoteport,
+	connect_callback cb, void* cb_data)
+{
+	struct dropbear_progress_connection *c = NULL;
+	int err;
+	struct addrinfo hints;
+
+	c = m_malloc(sizeof(*c));
+	c->remotehost = m_strdup(remotehost);
+	c->remoteport = m_strdup(remoteport);
+	c->sock = -1;
+	c->cb = cb;
+	c->cb_data = cb_data;
+
+	memset(&hints, 0, sizeof(hints));
+	hints.ai_socktype = SOCK_STREAM;
+	hints.ai_family = PF_UNSPEC;
+
+	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", 
+				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);
+
+	return c;
+}
+
+
+void set_connect_fds(fd_set *writefd) {
+	m_list_elem *iter;
+	TRACE(("enter handle_connect_fds"))
+	for (iter = ses.conn_pending.first; iter; iter = iter->next) {
+		struct dropbear_progress_connection *c = iter->item;
+		if (c->sock >= 0) {
+			FD_SET(c->sock, writefd);
+		}
+		else
+		{
+
+		}
+	}
+}
+
+void handle_connect_fds(fd_set *writefd) {
+	m_list_elem *iter;
+	TRACE(("enter handle_connect_fds"))
+	for (iter = ses.conn_pending.first; iter; iter = iter->next) {
+		int val;
+		socklen_t vallen = sizeof(val);
+		struct dropbear_progress_connection *c = iter->item;
+
+		if (!FD_ISSET(c->sock, writefd)) {
+			continue;
+		}
+
+		TRACE(("handling %s port %s socket %d", c->remotehost, c->remoteport, c->sock));
+
+		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)))
+		} 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;
+			}
+		}
+		/* 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	Sun Feb 15 22:34:05 2015 +0800
+++ b/dbutil.h	Wed Feb 18 00:05:27 2015 +0800
@@ -76,7 +76,7 @@
 void set_sock_nodelay(int sock);
 void set_sock_priority(int sock, enum dropbear_prio prio);
 
-#ifdef __linux__
+#if defined(__linux__) && HAVE_SENDMSG
 #define DROPBEAR_TCP_FAST_OPEN
 void set_listen_fast_open(int sock);
 #endif
@@ -89,7 +89,6 @@
 #ifdef ENABLE_CONNECT_UNIX
 int connect_unix(const char* addr);
 #endif
-int connect_remote(const char* remotehost, const char* remoteport, char ** errstring);
 int buf_readfile(buffer* buf, const char* filename);
 int buf_getline(buffer * line, FILE * authfile);
 
@@ -118,4 +117,16 @@
 
 char * expand_tilde(const char *inpath);
 
+struct dropbear_progress_connection;
+
+/* result is DROPBEAR_SUCCESS or DROPBEAR_FAILURE.
+errstring is only set on DROPBEAR_FAILURE, returns failure message for the last attempted socket */
+typedef void(*connect_callback)(int result, int sock, void* data, const char* errstring);
+
+struct dropbear_progress_connection * connect_remote (const char* remotehost, const char* remoteport,
+	connect_callback cb, void *cb_data);
+
+void set_connect_fds(fd_set *writefd);
+void handle_connect_fds(fd_set *writefd);
+
 #endif /* _DBUTIL_H_ */
--- a/packet.c	Sun Feb 15 22:34:05 2015 +0800
+++ b/packet.c	Wed Feb 18 00:05:27 2015 +0800
@@ -52,10 +52,29 @@
 static void buf_compress(buffer * dest, buffer * src, unsigned int len);
 #endif
 
+struct iovec * dropbear_queue_to_iovec(struct Queue *queue) {
+
+	struct iovec *iov = NULL;
+	struct Link *l;
+	int iov_max_count;
+
+	#ifndef IOV_MAX
+	#define IOV_MAX UIO_MAXIOV
+	#endif
+
+#error incomplete
+
+}
+
+void dropbear_queue_consume(struct Queue *queue, ssize_t written) {
+
+}
+
 /* non-blocking function writing out a current encrypted packet */
 void write_packet() {
 
-	int len, written;
+	ssize_t written;
+	int len;
 	buffer * writebuf = NULL;
 	unsigned packet_type;
 #ifdef HAVE_WRITEV
--- a/session.h	Sun Feb 15 22:34:05 2015 +0800
+++ b/session.h	Wed Feb 18 00:05:27 2015 +0800
@@ -144,6 +144,8 @@
 	
 	int signal_pipe[2]; /* stores endpoints of a self-pipe used for
 						   race-free signal handling */
+
+	m_list conn_pending;
 						
 	/* time of the last packet send/receive, for keepalive. Not real-world clock */
 	time_t last_packet_time_keepalive_sent;