From ed92b6dfe75ad65a78dadfa4dc96da4568d95d69 Mon Sep 17 00:00:00 2001
From: Andy Ning <andy.ning@windriver.com>
Date: Fri, 5 May 2017 11:38:34 -0400
Subject: [PATCH] client: added socks5 proxy support

AG:

 - move creation info members to end of struct
 - add LWS_WITH_SOCKS5 CMake var, defaults to OFF
 - cast away some warnings about signed / unsigned in strncpy

Signed-off-by: Andy Ning <andy.ning@windriver.com>
---
 CMakeLists.txt              |   2 +
 lib/client-handshake.c      | 123 +++++++++++++++++++++-
 lib/client.c                | 197 +++++++++++++++++++++++++++++++++++-
 lib/context.c               |  21 ++++
 lib/libwebsockets.c         |  73 +++++++++++++
 lib/libwebsockets.h         |  29 +++++-
 lib/private-libwebsockets.h |  75 ++++++++++++++
 lws_config.h.in             |   1 +
 8 files changed, 516 insertions(+), 5 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 4f1fd544..c0606f09 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -118,6 +118,7 @@ option(LWS_FALLBACK_GETHOSTBYNAME "Also try to do dns resolution using gethostby
 option(LWS_WITH_ZIP_FOPS "Support serving pre-zipped files" ON)
 option(LWS_AVOID_SIGPIPE_IGN "Android 7+ seems to need this" OFF)
 option(LWS_WITH_STATS "Keep statistics of lws internal operations" OFF)
+option(LWS_WITH_SOCKS5 "Allow use of SOCKS5 proxy on client connections" OFF)
 
 if (LWS_WITH_LWSWS)
  message(STATUS "LWS_WITH_LWSWS --> Enabling LWS_WITH_PLUGINS and LWS_WITH_LIBUV")
@@ -1770,6 +1771,7 @@ message(" LWS_WITH_ESP32 = ${LWS_WITH_ESP32}")
 message(" LWS_WITH_ZIP_FOPS = ${LWS_WITH_ZIP_FOPS}")
 message(" LWS_AVOID_SIGPIPE_IGN = ${LWS_AVOID_SIGPIPE_IGN}")
 message(" LWS_WITH_STATS = ${LWS_WITH_STATS}")
+message(" LWS_WITH_SOCKS5 = ${LWS_WITH_SOCKS5}")
 
 message("---------------------------------------------------------------------")
 
diff --git a/lib/client-handshake.c b/lib/client-handshake.c
index c844053d..a9f86650 100644
--- a/lib/client-handshake.c
+++ b/lib/client-handshake.c
@@ -26,6 +26,7 @@ lws_client_connect_2(struct lws *wsi)
 
 	/* proxy? */
 
+	/* http proxy */
 	if (wsi->vhost->http_proxy_port) {
 		plen = sprintf((char *)pt->serv_buf,
 			"CONNECT %s:%u HTTP/1.0\x0d\x0a"
@@ -49,7 +50,26 @@ lws_client_connect_2(struct lws *wsi)
 #endif
 			server_addr4.sin_port = htons(wsi->vhost->http_proxy_port);
 
-	} else {
+	}
+#if defined(LWS_WITH_SOCKS5)
+	/* socks 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
+#endif
+			server_addr4.sin_port = htons(wsi->vhost->socks_proxy_port);
+
+	}
+#endif
+	else {
 		ads = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS);
 #ifdef LWS_USE_IPV6
 		if (LWS_IPV6_ENABLED(wsi->vhost)) {
@@ -275,6 +295,7 @@ lws_client_connect_2(struct lws *wsi)
 
 	/* we are connected to server, or proxy */
 
+	/* http proxy */
 	if (wsi->vhost->http_proxy_port) {
 
 		/*
@@ -303,6 +324,26 @@ lws_client_connect_2(struct lws *wsi)
 
 		return wsi;
 	}
+#if defined(LWS_WITH_SOCKS5)
+	/* socks proxy */
+	else if (wsi->vhost->socks_proxy_port) {
+		n = send(wsi->desc.sockfd, (char *)pt->serv_buf, plen,
+			 MSG_NOSIGNAL);
+		if (n < 0) {
+			lwsl_debug("ERROR writing greeting to socks proxy"
+				"socket.\n");
+			cce = "socks write failed";
+			goto failed;
+		}
+
+		lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_SOCKS_GREETING_REPLY,
+				AWAITING_TIMEOUT);
+
+		wsi->mode = LWSCM_WSCL_WAITING_SOCKS_GREETING_REPLY;
+
+		return wsi;
+	}
+#endif
 
 	/*
 	 * provoke service to issue the handshake directly
@@ -754,7 +795,10 @@ lws_client_connect_via_info2(struct lws *wsi)
 					  stash->method))
 			goto bail1;
 
-	lws_free_set_NULL(wsi->u.hdr.stash);
+#if defined(LWS_WITH_SOCKS5)
+	if (!wsi->vhost->socks_proxy_port)
+		lws_free_set_NULL(wsi->u.hdr.stash);
+#endif
 
 	/*
 	 * Check with each extension if it is able to route and proxy this
@@ -782,7 +826,10 @@ lws_client_connect_via_info2(struct lws *wsi)
 	return lws_client_connect_2(wsi);
 
 bail1:
-	lws_free_set_NULL(wsi->u.hdr.stash);
+#if defined(LWS_WITH_SOCKS5)
+	if (!wsi->vhost->socks_proxy_port)
+		lws_free_set_NULL(wsi->u.hdr.stash);
+#endif
 
 	return NULL;
 }
@@ -836,3 +883,73 @@ lws_client_connect(struct lws_context *context, const char *address,
 	return lws_client_connect_via_info(&i);
 }
 
+#if defined(LWS_WITH_SOCKS5)
+void socks_generate_msg(struct lws *wsi, enum socks_msg_type type,
+			size_t *msg_len)
+{
+	struct lws_context *context = wsi->context;
+	struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
+	size_t len = 0;
+
+	if (type == SOCKS_MSG_GREETING) {
+		/* socks version, version 5 only */
+		pt->serv_buf[len++] = SOCKS_VERSION_5;
+		/* number of methods */
+		pt->serv_buf[len++] = 2;
+		/* username password method */
+		pt->serv_buf[len++] = SOCKS_AUTH_USERNAME_PASSWORD;
+		/* no authentication method */
+		pt->serv_buf[len++] = SOCKS_AUTH_NO_AUTH;
+	}
+	else if (type == SOCKS_MSG_USERNAME_PASSWORD) {
+		size_t user_len = 0;
+		size_t passwd_len = 0;
+
+		user_len = strlen(wsi->vhost->socks_user);
+		passwd_len = strlen(wsi->vhost->socks_password);
+
+		/* the subnegotiation version */
+		pt->serv_buf[len++] = SOCKS_SUBNEGOTIATION_VERSION_1;
+		/* length of the user name */
+		pt->serv_buf[len++] = user_len;
+		/* user name */
+		strncpy((char *)&pt->serv_buf[len], wsi->vhost->socks_user,
+			context->pt_serv_buf_size - len);
+		len += user_len;
+		/* length of the password */
+		pt->serv_buf[len++] = passwd_len;
+		/* password */
+		strncpy((char *)&pt->serv_buf[len], wsi->vhost->socks_password,
+			context->pt_serv_buf_size - len);
+		len += passwd_len;
+	}
+	else if (type == SOCKS_MSG_CONNECT) {
+		size_t len_index = 0;
+		short net_num = 0;
+		char *net_buf = (char*)&net_num;
+
+		/* socks version */
+		pt->serv_buf[len++] = SOCKS_VERSION_5;
+		/* socks command */
+		pt->serv_buf[len++] = SOCKS_COMMAND_CONNECT;
+		/* reserved */
+		pt->serv_buf[len++] = 0;
+		/* address type */
+		pt->serv_buf[len++] = SOCKS_ATYP_DOMAINNAME;
+		len_index = len;
+		len++;
+		/* the address we tell SOCKS proxy to connect to */
+		strncpy((char *)&(pt->serv_buf[len]), wsi->u.hdr.stash->address,
+			context->pt_serv_buf_size - len);
+		len += strlen(wsi->u.hdr.stash->address);
+		net_num = htons((short)wsi->c_port);
+		/* the port we tell SOCKS proxy to connect to */
+		pt->serv_buf[len++] = net_buf[0];
+		pt->serv_buf[len++] = net_buf[1];
+		/* the length of the address, excluding port */
+		pt->serv_buf[len_index] = strlen(wsi->u.hdr.stash->address);
+	}
+
+	*msg_len = len;
+}
+#endif
diff --git a/lib/client.c b/lib/client.c
index 1919f030..4b643ac1 100755
--- a/lib/client.c
+++ b/lib/client.c
@@ -81,7 +81,10 @@ lws_client_socket_service(struct lws_context *context, struct lws *wsi,
 	const char *cce = NULL;
 	unsigned char c;
 	char *sb = p;
-	int n, len;
+	int n = 0, len = 0;
+#if defined(LWS_WITH_SOCKS5)
+	char conn_mode = 0, pending_timeout = 0;
+#endif
 
 	switch (wsi->mode) {
 
@@ -101,6 +104,195 @@ lws_client_socket_service(struct lws_context *context, struct lws *wsi,
 		/* either still pending connection, or changed mode */
 		return 0;
 
+#if defined(LWS_WITH_SOCKS5)
+	/* SOCKS Greeting Reply */
+	case LWSCM_WSCL_WAITING_SOCKS_GREETING_REPLY:
+
+		/* handle proxy hung up on us */
+
+		if (pollfd->revents & LWS_POLLHUP) {
+
+			lwsl_warn("SOCKS connection %p (fd=%d) dead\n",
+				  (void *)wsi, pollfd->fd);
+
+			lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS);
+			return 0;
+		}
+
+		n = recv(wsi->desc.sockfd, sb, context->pt_serv_buf_size, 0);
+		if (n < 0) {
+			if (LWS_ERRNO == LWS_EAGAIN) {
+				lwsl_debug("SOCKS read returned EAGAIN..."
+					"retrying\n");
+				return 0;
+			}
+
+			lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS);
+			lwsl_err("ERROR reading from SOCKS socket\n");
+			return 0;
+		}
+
+		/* processing greeting reply */
+		if (pt->serv_buf[0] == SOCKS_VERSION_5
+			&& pt->serv_buf[1] == SOCKS_AUTH_NO_AUTH)
+		{
+			lwsl_client("%s\n", "SOCKS greeting reply received "
+				"- No Authentication Method");
+			socks_generate_msg(wsi, SOCKS_MSG_CONNECT, (size_t *)&len);
+
+			conn_mode = LWSCM_WSCL_WAITING_SOCKS_CONNECT_REPLY;
+			pending_timeout = PENDING_TIMEOUT_AWAITING_SOCKS_CONNECT_REPLY;
+			lwsl_client("%s\n", "Sending SOCKS connect command");
+		}
+		else if (pt->serv_buf[0] == SOCKS_VERSION_5
+				&& pt->serv_buf[1] == SOCKS_AUTH_USERNAME_PASSWORD)
+		{
+			lwsl_client("%s\n", "SOCKS greeting reply received "
+				"- User Name Password Method");
+			socks_generate_msg(wsi, SOCKS_MSG_USERNAME_PASSWORD,
+				(size_t *)&len);
+
+			conn_mode = LWSCM_WSCL_WAITING_SOCKS_AUTH_REPLY;
+			pending_timeout = PENDING_TIMEOUT_AWAITING_SOCKS_AUTH_REPLY;
+			lwsl_client("%s\n", "Sending SOCKS user/password");
+		}
+		else
+		{
+			lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS);
+			lwsl_err("ERROR SOCKS greeting reply failed, method "
+				"code: %d\n", pt->serv_buf[1]);
+			return 0;
+		}
+
+		n = send(wsi->desc.sockfd, (char *)pt->serv_buf, len,
+			 MSG_NOSIGNAL);
+		if (n < 0) {
+			lwsl_debug("ERROR writing socks command to socks proxy "
+				"socket\n");
+			return 0;
+		}
+
+		lws_set_timeout(wsi, pending_timeout, AWAITING_TIMEOUT);
+		wsi->mode = conn_mode;
+
+		break;
+	/* SOCKS auth Reply */
+	case LWSCM_WSCL_WAITING_SOCKS_AUTH_REPLY:
+
+		/* handle proxy hung up on us */
+
+		if (pollfd->revents & LWS_POLLHUP) {
+
+			lwsl_warn("SOCKS connection %p (fd=%d) dead\n",
+				  (void *)wsi, pollfd->fd);
+
+			lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS);
+			return 0;
+		}
+
+		n = recv(wsi->desc.sockfd, sb, context->pt_serv_buf_size, 0);
+		if (n < 0) {
+			if (LWS_ERRNO == LWS_EAGAIN) {
+				lwsl_debug("SOCKS read returned EAGAIN... "
+					"retrying\n");
+				return 0;
+			}
+
+			lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS);
+			lwsl_err("ERROR reading from socks socket\n");
+			return 0;
+		}
+
+		/* processing auth reply */
+		if (pt->serv_buf[0] == SOCKS_SUBNEGOTIATION_VERSION_1
+			&& pt->serv_buf[1] == SOCKS_SUBNEGOTIATION_STATUS_SUCCESS)
+		{
+			lwsl_client("%s\n", "SOCKS password reply recieved - "
+				"successful");
+			socks_generate_msg(wsi, SOCKS_MSG_CONNECT, (size_t *)&len);
+
+			conn_mode = LWSCM_WSCL_WAITING_SOCKS_CONNECT_REPLY;
+			pending_timeout =
+				PENDING_TIMEOUT_AWAITING_SOCKS_CONNECT_REPLY;
+			lwsl_client("%s\n", "Sending SOCKS connect command");
+		}
+		else
+		{
+			lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS);
+			lwsl_err("ERROR : SOCKS user/password reply failed, "
+				"error code: %d\n", pt->serv_buf[1]);
+			return 0;
+		}
+
+		n = send(wsi->desc.sockfd, (char *)pt->serv_buf, len,
+			 MSG_NOSIGNAL);
+		if (n < 0) {
+			lwsl_debug("ERROR writing connect command to SOCKS "
+				"socket\n");
+			return 0;
+		}
+
+		lws_set_timeout(wsi, pending_timeout, AWAITING_TIMEOUT);
+		wsi->mode = conn_mode;
+
+		break;
+
+	/* SOCKS connect command Reply */
+	case LWSCM_WSCL_WAITING_SOCKS_CONNECT_REPLY:
+
+		/* handle proxy hung up on us */
+
+		if (pollfd->revents & LWS_POLLHUP) {
+
+			lwsl_warn("SOCKS connection %p (fd=%d) dead\n",
+				  (void *)wsi, pollfd->fd);
+
+			lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS);
+			return 0;
+		}
+
+		n = recv(wsi->desc.sockfd, sb, context->pt_serv_buf_size, 0);
+		if (n < 0) {
+			if (LWS_ERRNO == LWS_EAGAIN) {
+				lwsl_debug("SOCKS read returned EAGAIN... "
+					"retrying\n");
+				return 0;
+			}
+
+			lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS);
+			lwsl_err("ERROR reading from socks socket\n");
+			return 0;
+		}
+
+		/* processing connect reply */
+		if (pt->serv_buf[0] == SOCKS_VERSION_5
+			&& pt->serv_buf[1] == SOCKS_REQUEST_REPLY_SUCCESS)
+		{
+			lwsl_client("%s\n", "SOCKS connect reply recieved - "
+				"successful");
+		}
+		else
+		{
+			lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS);
+			lwsl_err("ERROR SOCKS connect reply failed, error "
+				"code: %d\n", pt->serv_buf[1]);
+			return 0;
+		}
+
+		/* free stash since we are done with it */
+		lws_free_set_NULL(wsi->u.hdr.stash);
+
+		if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS,
+			wsi->vhost->socks_proxy_address))
+			goto bail3;
+		wsi->c_port = wsi->vhost->socks_proxy_port;
+
+		/* clear his proxy connection timeout */
+
+		lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
+
+		goto start_ws_hanshake;
+#endif
 	case LWSCM_WSCL_WAITING_PROXY_REPLY:
 
 		/* handle proxy hung up on us */
@@ -149,6 +341,9 @@ lws_client_socket_service(struct lws_context *context, struct lws *wsi,
 		 * take care of our lws_callback_on_writable
 		 * happening at a time when there's no real connection yet
 		 */
+#if defined(LWS_WITH_SOCKS5)
+start_ws_hanshake:
+#endif
 		if (lws_change_pollfd(wsi, LWS_POLLOUT, 0))
 			return -1;
 
diff --git a/lib/context.c b/lib/context.c
index f791ca6a..e9945c70 100644
--- a/lib/context.c
+++ b/lib/context.c
@@ -497,9 +497,14 @@ lws_create_vhost(struct lws_context *context,
 #if !defined(LWS_WITH_ESP8266)
 	vh->http_proxy_port = 0;
 	vh->http_proxy_address[0] = '\0';
+#if defined(LWS_WITH_SOCKS5)
+	vh->socks_proxy_port = 0;
+	vh->socks_proxy_address[0] = '\0';
+#endif
 
 	/* either use proxy from info, or try get it from env var */
 
+	/* http proxy */
 	if (info->http_proxy_address) {
 		/* override for backwards compatibility */
 		if (info->http_proxy_port)
@@ -512,7 +517,23 @@ lws_create_vhost(struct lws_context *context,
 			lws_set_proxy(vh, p);
 #endif
 	}
+#if defined(LWS_WITH_SOCKS5)
+	/* socks proxy */
+	if (info->socks_proxy_address) {
+		/* override for backwards compatibility */
+		if (info->socks_proxy_port)
+			vh->socks_proxy_port = info->socks_proxy_port;
+		lws_set_socks(vh, info->socks_proxy_address);
+	} else {
+#ifdef LWS_HAVE_GETENV
+		p = getenv("socks_proxy");
+		if (p)
+			lws_set_socks(vh, p);
 #endif
+	}
+#endif
+#endif
+
 	vh->ka_time = info->ka_time;
 	vh->ka_interval = info->ka_interval;
 	vh->ka_probes = info->ka_probes;
diff --git a/lib/libwebsockets.c b/lib/libwebsockets.c
index 9fd89cd9..b8e89745 100755
--- a/lib/libwebsockets.c
+++ b/lib/libwebsockets.c
@@ -1325,6 +1325,79 @@ auth_too_long:
 	return -1;
 }
 
+#if defined(LWS_WITH_SOCKS5)
+LWS_VISIBLE int
+lws_set_socks(struct lws_vhost *vhost, const char *socks)
+{
+#if !defined(LWS_WITH_ESP8266)
+	char *p_at, *p_colon;
+	char user[96];
+	char password[96];
+
+	if (!socks)
+		return -1;
+
+	vhost->socks_user[0] = '\0';
+	vhost->socks_password[0] = '\0';
+
+	p_at = strchr(socks, '@');
+	if (p_at) { /* auth is around */
+		if ((unsigned int)(p_at - socks) > (sizeof(user)
+			+ sizeof(password) - 2)) {
+			lwsl_err("Socks auth too long\n");
+			goto bail;
+		}
+
+		p_colon = strchr(socks, ':');
+		if (p_colon) {
+			if ((unsigned int)(p_colon - socks) > (sizeof(user)
+				- 1) ) {
+				lwsl_err("Socks user too long\n");
+				goto bail;
+			}
+			if ((unsigned int)(p_at - p_colon) > (sizeof(password)
+				- 1) ) {
+				lwsl_err("Socks password too long\n");
+				goto bail;
+			}
+		}
+		strncpy(vhost->socks_user, socks, p_colon - socks);
+		strncpy(vhost->socks_password, p_colon + 1,
+			p_at - (p_colon + 1));
+
+		lwsl_info(" Socks auth, user: %s, password: %s\n",
+			vhost->socks_user, vhost->socks_password );
+
+		socks = p_at + 1;
+	}
+
+	strncpy(vhost->socks_proxy_address, socks,
+				sizeof(vhost->socks_proxy_address) - 1);
+	vhost->socks_proxy_address[sizeof(vhost->socks_proxy_address) - 1]
+		= '\0';
+
+	p_colon = strchr(vhost->socks_proxy_address, ':');
+	if (!p_colon && !vhost->socks_proxy_port) {
+		lwsl_err("socks_proxy needs to be address:port\n");
+		return -1;
+	} else {
+		if (p_colon) {
+			*p_colon = '\0';
+			vhost->socks_proxy_port = atoi(p_colon + 1);
+		}
+	}
+
+	lwsl_info(" Socks %s:%u\n", vhost->socks_proxy_address,
+			vhost->socks_proxy_port);
+
+	return 0;
+
+bail:
+#endif
+	return -1;
+}
+#endif
+
 LWS_VISIBLE const struct lws_protocols *
 lws_get_protocol(struct lws *wsi)
 {
diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h
index 4eb0cd05..185a5690 100644
--- a/lib/libwebsockets.h
+++ b/lib/libwebsockets.h
@@ -1812,7 +1812,7 @@ struct lws_context_creation_info {
 	/**< VHOST + CONTEXT: 0, or LWS_SERVER_OPTION_... bitfields */
 	void *user;
 	/**< CONTEXT: optional user pointer that can be recovered via the context
- *		pointer using lws_context_user */
+	 *		pointer using lws_context_user */
 	int ka_time;
 	/**< CONTEXT: 0 for no TCP keepalive, otherwise apply this keepalive
 	 * timeout to all libwebsocket sockets, client or server */
@@ -1955,6 +1955,11 @@ struct lws_context_creation_info {
 	 */
 	int simultaneous_ssl_restriction;
 	/**< CONTEXT: 0 (no limit) or limit of simultaneous SSL sessions possible.*/
+	const char *socks_proxy_address;
+	/**< VHOST: If non-NULL, attempts to proxy via the given address.
+	 * If proxy auth is required, use format "username:password\@server:port" */
+	unsigned int socks_proxy_port;
+	/**< VHOST: If socks_proxy_address was non-NULL, uses this port */
 
 	/* Add new things just above here ---^
 	 * This is part of the ABI, don't needlessly break compatibility
@@ -2070,6 +2075,25 @@ lws_context_is_deprecated(struct lws_context *context);
 LWS_VISIBLE LWS_EXTERN int
 lws_set_proxy(struct lws_vhost *vhost, const char *proxy);
 
+/**
+ * lws_set_socks() - Setup socks to lws_context.
+ * \param vhost:	pointer to struct lws_vhost you want set socks for
+ * \param socks: pointer to c string containing socks in format address:port
+ *
+ * Returns 0 if socks string was parsed and socks was setup.
+ * Returns -1 if socks is NULL or has incorrect format.
+ *
+ * This is only required if your OS does not provide the socks_proxy
+ * environment variable (eg, OSX)
+ *
+ *   IMPORTANT! You should call this function right after creation of the
+ *   lws_context and before call to connect. If you call this
+ *   function after connect behavior is undefined.
+ *   This function will override proxy settings made on lws_context
+ *   creation with genenv() call.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_set_socks(struct lws_vhost *vhost, const char *socks);
 
 struct lws_vhost;
 
@@ -3471,6 +3495,9 @@ enum pending_timeout {
 	PENDING_TIMEOUT_WS_PONG_CHECK_SEND_PING			= 16,
 	PENDING_TIMEOUT_WS_PONG_CHECK_GET_PONG			= 17,
 	PENDING_TIMEOUT_CLIENT_ISSUE_PAYLOAD			= 18,
+	PENDING_TIMEOUT_AWAITING_SOCKS_GREETING_REPLY	        = 19,
+	PENDING_TIMEOUT_AWAITING_SOCKS_CONNECT_REPLY		= 20,
+	PENDING_TIMEOUT_AWAITING_SOCKS_AUTH_REPLY		= 21,
 
 	/****** add new things just above ---^ ******/
 };
diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h
index 8de93dc0..ad61015f 100644
--- a/lib/private-libwebsockets.h
+++ b/lib/private-libwebsockets.h
@@ -574,12 +574,75 @@ enum connection_mode {
 	LWSCM_WSCL_WAITING_SERVER_REPLY,
 	LWSCM_WSCL_WAITING_EXTENSION_CONNECT,
 	LWSCM_WSCL_PENDING_CANDIDATE_CHILD,
+	LWSCM_WSCL_WAITING_SOCKS_GREETING_REPLY,
+	LWSCM_WSCL_WAITING_SOCKS_CONNECT_REPLY,
+	LWSCM_WSCL_WAITING_SOCKS_AUTH_REPLY,
 
 	/****** add new things just above ---^ ******/
 
 
 };
 
+/* enums of socks version */
+enum socks_version {
+	SOCKS_VERSION_4 = 4,
+	SOCKS_VERSION_5 = 5
+};
+
+/* enums of subnegotiation version */
+enum socks_subnegotiation_version {
+	SOCKS_SUBNEGOTIATION_VERSION_1 = 1,
+};
+
+/* enums of socks commands */
+enum socks_command {
+	SOCKS_COMMAND_CONNECT = 1,
+	SOCKS_COMMAND_BIND = 2,
+	SOCKS_COMMAND_UDP_ASSOCIATE = 3
+};
+
+/* enums of socks address type */
+enum socks_atyp {
+	SOCKS_ATYP_IPV4 = 1,
+	SOCKS_ATYP_DOMAINNAME = 3,
+	SOCKS_ATYP_IPV6 = 4
+};
+
+/* enums of socks authentication methods */
+enum socks_auth_method {
+	SOCKS_AUTH_NO_AUTH = 0,
+	SOCKS_AUTH_GSSAPI = 1,
+	SOCKS_AUTH_USERNAME_PASSWORD = 2
+};
+
+/* enums of subnegotiation status */
+enum socks_subnegotiation_status {
+	SOCKS_SUBNEGOTIATION_STATUS_SUCCESS = 0,
+};
+
+/* enums of socks request reply */
+enum socks_request_reply {
+	SOCKS_REQUEST_REPLY_SUCCESS = 0,
+	SOCKS_REQUEST_REPLY_FAILURE_GENERAL = 1,
+	SOCKS_REQUEST_REPLY_CONNECTION_NOT_ALLOWED = 2,
+	SOCKS_REQUEST_REPLY_NETWORK_UNREACHABLE = 3,
+	SOCKS_REQUEST_REPLY_HOST_UNREACHABLE = 4,
+	SOCKS_REQUEST_REPLY_CONNECTION_REFUSED = 5,
+	SOCKS_REQUEST_REPLY_TTL_EXPIRED = 6,
+	SOCKS_REQUEST_REPLY_COMMAND_NOT_SUPPORTED = 7,
+	SOCKS_REQUEST_REPLY_ATYP_NOT_SUPPORTED = 8
+};
+
+/* enums used to generate socks messages */
+enum socks_msg_type {
+	/* greeting */
+	SOCKS_MSG_GREETING,
+	/* credential, user name and password */
+	SOCKS_MSG_USERNAME_PASSWORD,
+	/* connect command */
+	SOCKS_MSG_CONNECT
+};
+
 enum {
 	LWS_RXFLOW_ALLOW = (1 << 0),
 	LWS_RXFLOW_PENDING_CHANGE = (1 << 1),
@@ -774,6 +837,11 @@ struct lws_vhost {
 #if !defined(LWS_WITH_ESP8266)
 	char http_proxy_address[128];
 	char proxy_basic_auth_token[128];
+#if defined(LWS_WITH_SOCKS5)
+	char socks_proxy_address[128];
+	char socks_user[96];
+	char socks_password[96];
+#endif
 #endif
 #if defined(LWS_WITH_ESP8266)
 	/* listen sockets need a place to hang their hat */
@@ -801,6 +869,9 @@ struct lws_vhost {
 
 	int listen_port;
 	unsigned int http_proxy_port;
+#if defined(LWS_WITH_SOCKS5)
+	unsigned int socks_proxy_port;
+#endif
 	unsigned int options;
 	int count_protocols;
 	int ka_time;
@@ -2103,6 +2174,10 @@ static inline uint64_t lws_stats_atomic_max(struct lws_context * context,
 	(void)context; (void)pt; (void)index; (void)val; return 0; }
 #endif
 
+/* socks */
+void socks_generate_msg(struct lws *wsi, enum socks_msg_type type,
+			size_t *msg_len);
+
 #ifdef __cplusplus
 };
 #endif
diff --git a/lws_config.h.in b/lws_config.h.in
index 96c17053..c9a0a1ff 100644
--- a/lws_config.h.in
+++ b/lws_config.h.in
@@ -134,6 +134,7 @@
 #cmakedefine LWS_FALLBACK_GETHOSTBYNAME
 
 #cmakedefine LWS_WITH_STATS
+#cmakedefine LWS_WITH_SOCKS5
 
 /* OpenSSL various APIs */
 
-- 
GitLab