From c70f6692f8d505e35f71e00f35089a06363c854d Mon Sep 17 00:00:00 2001
From: Andy Green <andy@warmcat.com>
Date: Tue, 20 Jun 2017 15:56:48 +0800
Subject: [PATCH] client: getaddrinfo refactor

https://github.com/warmcat/libwebsockets/issues/926
---
 lib/client-handshake.c      | 198 +++++++++++++++++++-----------------
 lib/private-libwebsockets.h |   8 ++
 2 files changed, 112 insertions(+), 94 deletions(-)

diff --git a/lib/client-handshake.c b/lib/client-handshake.c
index 26543b9b8..6286f2d0b 100644
--- a/lib/client-handshake.c
+++ b/lib/client-handshake.c
@@ -1,20 +1,51 @@
 #include "private-libwebsockets.h"
 
+static int
+lws_getaddrinfo46(struct lws *wsi, const char *ads, struct addrinfo **result)
+{
+	struct addrinfo hints;
+
+	memset(&hints, 0, sizeof(hints));
+	*result = NULL;
+
+#ifdef LWS_USE_IPV6
+	if (wsi->ipv6) {
+
+#if !defined(__ANDROID__)
+		hints.ai_family = AF_INET6;
+		hints.ai_flags = AI_V4MAPPED;
+#endif
+	} else
+#endif
+	{
+		hints.ai_family = PF_UNSPEC;
+		hints.ai_socktype = SOCK_STREAM;
+		hints.ai_flags = AI_CANONNAME;
+	}
+
+	return getaddrinfo(ads, NULL, &hints, result);
+}
+
 struct lws *
 lws_client_connect_2(struct lws *wsi)
 {
-#ifdef LWS_USE_IPV6
-	struct sockaddr_in6 server_addr6;
-	struct addrinfo hints, *result;
-#endif
+	sockaddr46 sa46;
+	struct addrinfo *result;
 	struct lws_context *context = wsi->context;
 	struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
-	struct sockaddr_in server_addr4;
 	struct lws_pollfd pfd;
-	struct sockaddr *v;
 	const char *cce = "", *iface;
-	int n, plen = 0;
+	int n, plen = 0, port;
 	const char *ads;
+#ifdef LWS_USE_IPV6
+	char ipv6only = lws_check_opt(wsi->vhost->options,
+			LWS_SERVER_OPTION_IPV6_V6ONLY_MODIFY |
+			LWS_SERVER_OPTION_IPV6_V6ONLY_VALUE);
+
+#if defined(__ANDROID__)
+	ipv6only = 0;
+#endif
+#endif
 
 	lwsl_client("%s\n", __func__);
 
@@ -24,9 +55,15 @@ lws_client_connect_2(struct lws *wsi)
 		goto oom4;
 	}
 
-	/* proxy? */
+	/*
+	 * start off allowing ipv6 on connection if vhost allows it
+	 */
+	wsi->ipv6 = LWS_IPV6_ENABLED(wsi->vhost);
+
+	/* Decide what it is we need to connect to:
+	 *
+	 * Priority 1: connect to http proxy */
 
-	/* http proxy */
 	if (wsi->vhost->http_proxy_port) {
 		plen = sprintf((char *)pt->serv_buf,
 			"CONNECT %s:%u HTTP/1.0\x0d\x0a"
@@ -41,88 +78,63 @@ lws_client_connect_2(struct lws *wsi)
 
 		plen += sprintf((char *)pt->serv_buf + plen, "\x0d\x0a");
 		ads = wsi->vhost->http_proxy_address;
+		port = wsi->vhost->http_proxy_port;
 
-#ifdef LWS_USE_IPV6
-		if (LWS_IPV6_ENABLED(wsi->vhost)) {
-			memset(&server_addr6, 0, sizeof(struct sockaddr_in6));
-			server_addr6.sin6_port = htons(wsi->vhost->http_proxy_port);
-		} else
-#endif
-			server_addr4.sin_port = htons(wsi->vhost->http_proxy_port);
-
-	}
 #if defined(LWS_WITH_SOCKS5)
-	/* socks proxy */
-	else if (wsi->vhost->socks_proxy_port) {
+
+	/* Priority 2: Connect to SOCK5 Proxy */
+
+	} else if (wsi->vhost->socks_proxy_port) {
 		socks_generate_msg(wsi, SOCKS_MSG_GREETING, (size_t *)&plen);
 		lwsl_client("%s\n", "Sending SOCKS Greeting.");
 
 		ads = wsi->vhost->socks_proxy_address;
-
-#ifdef LWS_USE_IPV6
-		if (LWS_IPV6_ENABLED(wsi->vhost)) {
-			memset(&server_addr6, 0, sizeof(struct sockaddr_in6));
-			server_addr6.sin6_port = htons(wsi->vhost->socks_proxy_port);
-		} else
+		port = wsi->vhost->socks_proxy_port;
 #endif
-			server_addr4.sin_port = htons(wsi->vhost->socks_proxy_port);
+	} else {
+
+		/* Priority 3: Connect directly */
 
-	}
-#endif
-	else {
 		ads = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS);
-#ifdef LWS_USE_IPV6
-		if (LWS_IPV6_ENABLED(wsi->vhost)) {
-			memset(&server_addr6, 0, sizeof(struct sockaddr_in6));
-			server_addr6.sin6_port = htons(wsi->c_port);
-		} else
-#endif
-			server_addr4.sin_port = htons(wsi->c_port);
+		port = wsi->c_port;
 	}
 
 	/*
-	 * prepare the actual connection (to the proxy, if any)
+	 * prepare the actual connection
+	 * to whatever we decided to connect to
 	 */
+
        lwsl_notice("%s: %p: address %s\n", __func__, wsi, ads);
 
-#ifdef LWS_USE_IPV6
-	if (LWS_IPV6_ENABLED(wsi->vhost)) {
-		memset(&hints, 0, sizeof(struct addrinfo));
-#if !defined(__ANDROID__)
-		hints.ai_family = AF_INET6;
-		hints.ai_flags = AI_V4MAPPED;
-#endif
-		n = getaddrinfo(ads, NULL, &hints, &result);
-		if (n) {
-#ifdef _WIN32
-			lwsl_err("getaddrinfo: %ls\n", gai_strerrorW(n));
-#else
-			lwsl_err("getaddrinfo: %s\n", gai_strerror(n));
-#endif
-			cce = "getaddrinfo (ipv6) failed";
-			goto oom4;
-		}
+       n = lws_getaddrinfo46(wsi, ads, &result);
 
-		server_addr6.sin6_family = AF_INET6;
+#ifdef LWS_USE_IPV6
+	if (wsi->ipv6) {
+
+		memset(&sa46, 0, sizeof(sa46));
+
+		sa46.sa6.sin6_family = AF_INET6;
 		switch (result->ai_family) {
-#if defined(__ANDROID__)
 		case AF_INET:
+			if (ipv6only)
+				break;
 			/* map IPv4 to IPv6 */
-			bzero((char *)&server_addr6.sin6_addr,
-						sizeof(struct in6_addr));
-			server_addr6.sin6_addr.s6_addr[10] = 0xff;
-			server_addr6.sin6_addr.s6_addr[11] = 0xff;
-			memcpy(&server_addr6.sin6_addr.s6_addr[12],
+			bzero((char *)&sa46.sa6.sin6_addr,
+						sizeof(sa46.sa6.sin6_addr));
+			sa46.sa6.sin6_addr.s6_addr[10] = 0xff;
+			sa46.sa6.sin6_addr.s6_addr[11] = 0xff;
+			memcpy(&sa46.sa6.sin6_addr.s6_addr[12],
 				&((struct sockaddr_in *)result->ai_addr)->sin_addr,
 							sizeof(struct in_addr));
+			lwsl_notice("uplevelling AF_INET to AF_INET6\n");
 			break;
-#endif
+
 		case AF_INET6:
-			memcpy(&server_addr6.sin6_addr,
+			memcpy(&sa46.sa6.sin6_addr,
 			  &((struct sockaddr_in6 *)result->ai_addr)->sin6_addr,
 						sizeof(struct in6_addr));
-			server_addr6.sin6_scope_id = ((struct sockaddr_in6 *)result->ai_addr)->sin6_scope_id;
-			server_addr6.sin6_flowinfo = ((struct sockaddr_in6 *)result->ai_addr)->sin6_flowinfo;
+			sa46.sa6.sin6_scope_id = ((struct sockaddr_in6 *)result->ai_addr)->sin6_scope_id;
+			sa46.sa6.sin6_flowinfo = ((struct sockaddr_in6 *)result->ai_addr)->sin6_flowinfo;
 			break;
 		default:
 			lwsl_err("Unknown address family\n");
@@ -130,23 +142,18 @@ lws_client_connect_2(struct lws *wsi)
 			cce = "unknown address family";
 			goto oom4;
 		}
-
-		freeaddrinfo(result);
 	} else
-#endif
+#endif /* use ipv6 */
+
+	/* use ipv4 */
 	{
-		struct addrinfo ai, *res, *result = NULL;
 		void *p = NULL;
-		int addr_rv;
 
-		memset (&ai, 0, sizeof ai);
-		ai.ai_family = PF_UNSPEC;
-		ai.ai_socktype = SOCK_STREAM;
-		ai.ai_flags = AI_CANONNAME;
+		if (!n) {
+			struct addrinfo *res = result;
+
+			/* pick the first AF_INET (IPv4) result */
 
-		addr_rv = getaddrinfo(ads, NULL, &ai, &result);
-		if (!addr_rv) {
-			res = result;
 			while (!p && res) {
 				switch (res->ai_family) {
 				case AF_INET:
@@ -157,7 +164,7 @@ lws_client_connect_2(struct lws *wsi)
 				res = res->ai_next;
 			}
 #if defined(LWS_FALLBACK_GETHOSTBYNAME)
-		} else if (addr_rv == EAI_SYSTEM) {
+		} else if (n == EAI_SYSTEM) {
 			struct hostent *host;
 
 			lwsl_info("getaddrinfo (ipv4) failed, trying gethostbyname\n");
@@ -172,7 +179,7 @@ lws_client_connect_2(struct lws *wsi)
 #endif
 		} else {
 			lwsl_err("getaddrinfo failed\n");
-			cce = "getaddrinfo (ipv4) failed";
+			cce = "getaddrinfo failed";
 			goto oom4;
 		}
 
@@ -184,21 +191,28 @@ lws_client_connect_2(struct lws *wsi)
 			goto oom4;
 		}
 
-		server_addr4.sin_family = AF_INET;
-		server_addr4.sin_addr = *((struct in_addr *)p);
-		bzero(&server_addr4.sin_zero, 8);
-		if (result)
-			freeaddrinfo(result);
+		sa46.sa4.sin_family = AF_INET;
+		sa46.sa4.sin_addr = *((struct in_addr *)p);
+		bzero(&sa46.sa4.sin_zero, 8);
 	}
 
+	if (result)
+		freeaddrinfo(result);
+
+	/* now we decided on ipv4 or ipv6, set the port */
+
 	if (!lws_socket_is_valid(wsi->desc.sockfd)) {
 
 #ifdef LWS_USE_IPV6
-		if (LWS_IPV6_ENABLED(wsi->vhost))
+		if (wsi->ipv6) {
+			sa46.sa6.sin6_port = htons(port);
 			wsi->desc.sockfd = socket(AF_INET6, SOCK_STREAM, 0);
-		else
+		} else
 #endif
+		{
+			sa46.sa4.sin_port = htons(port);
 			wsi->desc.sockfd = socket(AF_INET, SOCK_STREAM, 0);
+		}
 
 		if (!lws_socket_is_valid(wsi->desc.sockfd)) {
 			lwsl_warn("Unable to open socket\n");
@@ -252,17 +266,13 @@ lws_client_connect_2(struct lws *wsi)
 	}
 
 #ifdef LWS_USE_IPV6
-	if (LWS_IPV6_ENABLED(wsi->vhost)) {
-		v = (struct sockaddr *)&server_addr6;
+	if (wsi->ipv6)
 		n = sizeof(struct sockaddr_in6);
-	} else
+	else
 #endif
-	{
-		v = (struct sockaddr *)&server_addr4;
 		n = sizeof(struct sockaddr);
-	}
 
-	if (connect(wsi->desc.sockfd, v, n) == -1 ||
+	if (connect(wsi->desc.sockfd, (const struct sockaddr *)&sa46, n) == -1 ||
 	    LWS_ERRNO == LWS_EISCONN) {
 		if (LWS_ERRNO == LWS_EALREADY ||
 		    LWS_ERRNO == LWS_EINPROGRESS ||
diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h
index 420076ebc..dc6146ce5 100644
--- a/lib/private-libwebsockets.h
+++ b/lib/private-libwebsockets.h
@@ -1160,6 +1160,13 @@ LWS_EXTERN void lws_feature_status_libevent(struct lws_context_creation_info *in
 #define LWS_UNIX_SOCK_ENABLED(vhost) (0)
 #endif
 
+typedef union {
+#ifdef LWS_USE_IPV6
+	struct sockaddr_in6 sa6;
+#endif
+	struct sockaddr_in sa4;
+} sockaddr46;
+
 enum uri_path_states {
 	URIPS_IDLE,
 	URIPS_SEEN_SLASH,
@@ -1623,6 +1630,7 @@ struct lws {
 	unsigned int sending_chunked:1;
 	unsigned int already_did_cce:1;
 	unsigned int told_user_closed:1;
+	unsigned int ipv6:1;
 
 #if defined(LWS_WITH_ESP8266)
 	unsigned int pending_send_completion:3;