From 4230e92171a658508a83e40b00adfcab26a2865b Mon Sep 17 00:00:00 2001
From: Alexander Liu <alec@atplatform.net>
Date: Fri, 26 Apr 2019 22:33:17 +0800
Subject: [PATCH 1/2] MEDIUM: Adding upstream socks4 proxy support

Have "socks4" and "check-via-socks" server keyword added.
Implement handshake with Socks4 proxy server for tcp stream connection.
---
 examples/socks4.cfg        |  55 +++++++++++++
 include/proto/connection.h |   4 +
 include/types/checks.h     |   2 +
 include/types/connection.h |  32 +++++++-
 include/types/server.h     |   3 +
 src/backend.c              |  12 +++
 src/checks.c               |  19 +++++
 src/connection.c           | 155 +++++++++++++++++++++++++++++++++++++
 src/proto_tcp.c            |  12 ++-
 src/server.c               |  61 +++++++++++++++
 10 files changed, 351 insertions(+), 4 deletions(-)
 create mode 100644 examples/socks4.cfg

diff --git a/examples/socks4.cfg b/examples/socks4.cfg
new file mode 100644
index 00000000..84d3d77e
--- /dev/null
+++ b/examples/socks4.cfg
@@ -0,0 +1,55 @@
+global
+	log /dev/log local0
+	log /dev/log local1 notice
+	stats timeout 30s
+	
+defaults
+	log global
+	mode http
+	option httplog
+	option dontlognull
+	timeout connect 5000
+	timeout client  50000
+	timeout server  50000
+
+listen SMTP-20025
+	bind 0.0.0.0:20025
+	mode tcp
+	option tcplog
+	maxconn 2000
+	timeout connect 5000
+	timeout client  50000
+	timeout server  50000
+	option tcp-check
+	server SMTPS1_Via_SocksProxy1 169.38.103.42:25   socks4 172.31.1.74:1080 check-via-socks check inter 30000 fastinter 1000
+	server SMTPS2_Via_SocksProxy2 161.202.148.179:25 socks4 127.0.0.1:1080 check-via-socks check inter 30000 fastinter 1000 backup
+
+listen SSL-20080
+	bind 0.0.0.0:20080
+	mode tcp
+	option tcplog
+	maxconn 2000
+	timeout connect 5000
+	timeout client  50000
+	timeout server  50000
+	option tcp-check
+	server HTTPS1_Via_SocksProxy1 161.202.148.166:443 ssl verify none socks4 172.31.1.74:1080 check-via-socks check inter 30000 fastinter 1000
+	server HTTPS2_Via_SocksProxy2 169.38.103.48:443   ssl verify none socks4 127.0.0.1:1080   check-via-socks check inter 30000 fastinter 1000 backup
+
+# HAProxy web ui
+listen stats
+	bind 0.0.0.0:20936
+	mode http
+	log global
+
+	maxconn 10
+	timeout client 100s
+	timeout server 100s
+	timeout connect 100s
+	timeout queue 100s
+
+	stats enable
+	stats uri /haproxy?stats
+	stats realm HAProxy\ Statistics
+	stats admin if TRUE
+	stats show-node
diff --git a/include/proto/connection.h b/include/proto/connection.h
index f1919ce7..c4dd6209 100644
--- a/include/proto/connection.h
+++ b/include/proto/connection.h
@@ -60,6 +60,10 @@ int conn_sock_send(struct connection *conn, const void *buf, int len, int flags)
 /* drains any pending bytes from the socket */
 int conn_sock_drain(struct connection *conn);
 
+/* scoks proxy handshake */
+int conn_send_socks_proxy_request(struct connection *conn);
+int conn_recv_socks_proxy_response(struct connection *conn, unsigned int flag);
+
 /* returns true is the transport layer is ready */
 static inline int conn_xprt_ready(const struct connection *conn)
 {
diff --git a/include/types/checks.h b/include/types/checks.h
index f89abcba..b09b5f89 100644
--- a/include/types/checks.h
+++ b/include/types/checks.h
@@ -189,6 +189,8 @@ struct check {
 	char *sni;				/* Server name */
 	char *alpn_str;                         /* ALPN to use for checks */
 	int alpn_len;                           /* ALPN string length */
+
+	int via_socks;				/* check the connection via socks proxy */
 };
 
 struct check_status {
diff --git a/include/types/connection.h b/include/types/connection.h
index 308be7d7..84e3ae0d 100644
--- a/include/types/connection.h
+++ b/include/types/connection.h
@@ -47,6 +47,15 @@ struct server;
 struct session;
 struct pipe;
 
+/* socks4 upstream proxy definitions */
+struct socks4_request {
+	uint8_t version;	/* SOCKS version number, 1 byte, must be 0x04 for this version */
+	uint8_t command;	/* 0x01 = establish a TCP/IP stream connection */
+	uint16_t port;		/* port number, 2 bytes (in network byte order) */
+	uint32_t ip;		/* IP address, 4 bytes (in network byte order) */
+	char user_id[8];	/* the user ID string, variable length, terminated with a null (0x00); Using "HAProxy\0" */
+};
+
 /* Note: subscribing to these events is only valid after the caller has really
  * attempted to perform the operation, and failed to proceed or complete.
  */
@@ -156,7 +165,9 @@ enum {
 
 	CO_FL_EARLY_SSL_HS  = 0x00004000,  /* We have early data pending, don't start SSL handshake yet */
 	CO_FL_EARLY_DATA    = 0x00008000,  /* At least some of the data are early data */
-	/* unused : 0x00010000 */
+
+	CO_FL_SOCKS_HS      = 0x00010000,  /* doing SOCKS upstream proxy handshake */
+
 	/* unused : 0x00020000 */
 
 	/* flags used to remember what shutdown have been performed/reported */
@@ -183,7 +194,7 @@ enum {
 	CO_FL_ACCEPT_CIP    = 0x08000000,  /* receive a valid NetScaler Client IP header */
 
 	/* below we have all handshake flags grouped into one */
-	CO_FL_HANDSHAKE     = CO_FL_SEND_PROXY | CO_FL_SSL_WAIT_HS | CO_FL_ACCEPT_PROXY | CO_FL_ACCEPT_CIP,
+	CO_FL_HANDSHAKE     = CO_FL_SEND_PROXY | CO_FL_SSL_WAIT_HS | CO_FL_ACCEPT_PROXY | CO_FL_ACCEPT_CIP | CO_FL_SOCKS_HS,
 
 	/* when any of these flags is set, polling is defined by socket-layer
 	 * operations, as opposed to data-layer. Transport is explicitly not
@@ -208,6 +219,15 @@ enum {
 	CO_FL_XPRT_TRACKED  = 0x80000000,
 };
 
+/* flags for use in connection->socks4_proxy_status */
+enum {
+	CO_SOCKS_NONE,
+	CO_SOCKS_SENDING,
+	CO_SOCKS_SENT,
+	CO_SOCKS_RECEIVING,
+	CO_SOCKS_READY,
+	CO_SOCKS_DENY
+};
 
 /* possible connection error codes */
 enum {
@@ -451,6 +471,13 @@ struct connection {
 	struct {
 		struct sockaddr_storage from;	/* client address, or address to spoof when connecting to the server */
 		struct sockaddr_storage to;	/* address reached by the client, or address to connect to */
+		struct {
+			unsigned char use;			/* socks4 proxy enabled  */
+			struct sockaddr_storage addr;		/* the address of the socks4 proxy*/
+			struct socks4_request req_line;		/* the info send to socks4 proxy */
+			unsigned int status;			/* CO_SOCKS_* */
+			signed short req_remain_len;		/* the length remain to sent */
+		} socks_proxy;
 	} addr; /* addresses of the remote side, client for producer and server for consumer */
 	unsigned int idle_time;                 /* Time the connection was added to the idle list, or 0 if not in the idle list */
 };
@@ -570,7 +597,6 @@ struct tlv_ssl {
 #define PP2_CLIENT_CERT_CONN     0x02
 #define PP2_CLIENT_CERT_SESS     0x04
 
-
 /*
  * Linux seems to be able to send 253 fds per sendmsg(), not sure
  * about the other OSes.
diff --git a/include/types/server.h b/include/types/server.h
index 7835f11c..7a383d44 100644
--- a/include/types/server.h
+++ b/include/types/server.h
@@ -338,6 +338,9 @@ struct server {
 		char reason[128];
 	} op_st_chg;				/* operational status change's reason */
 	char adm_st_chg_cause[48];		/* administrative status change's cause */
+
+	unsigned char use_socks4;		/* socks4 proxy enabled  */
+	struct sockaddr_storage socks4_addr;	/* the address of the socks4 proxy, including the port */
 };
 
 /* Descriptor for a "server" keyword. The ->parse() function returns 0 in case of
diff --git a/src/backend.c b/src/backend.c
index d7695bf2..52b74e7c 100644
--- a/src/backend.c
+++ b/src/backend.c
@@ -888,6 +888,18 @@ int assign_server_address(struct stream *s, struct connection *srv_conn)
 		srv_conn->addr.to = __objt_server(s->target)->addr;
 		set_host_port(&srv_conn->addr.to, __objt_server(s->target)->svc_port);
 
+		if (__objt_server(s->target)->use_socks4) {
+			srv_conn->addr.socks_proxy.use = 1;
+			srv_conn->addr.socks_proxy.addr = __objt_server(s->target)->socks4_addr;
+			srv_conn->addr.socks_proxy.status = CO_SOCKS_NONE;
+			srv_conn->addr.socks_proxy.req_line.version = 0x04;
+			srv_conn->addr.socks_proxy.req_line.command = 0x01;
+			srv_conn->addr.socks_proxy.req_line.port = get_net_port(&(srv_conn->addr.to));
+			srv_conn->addr.socks_proxy.req_line.ip = is_inet_addr(&(srv_conn->addr.to));
+			strcpy(srv_conn->addr.socks_proxy.req_line.user_id, "HAProxy");
+			srv_conn->addr.socks_proxy.req_remain_len = sizeof(srv_conn->addr.socks_proxy.req_line);
+		}
+
 		if (!is_addr(&srv_conn->addr.to) && cli_conn) {
 			/* if the server has no address, we use the same address
 			 * the client asked, which is handy for remapping ports
diff --git a/src/checks.c b/src/checks.c
index 76bd8e3d..0ecd9cdb 100644
--- a/src/checks.c
+++ b/src/checks.c
@@ -1611,6 +1611,25 @@ static int connect_conn_chk(struct task *t)
 		conn->addr.to = s->addr;
 	}
 
+
+	if (s->check.via_socks) {
+		if (s->use_socks4) {
+			conn->addr.socks_proxy.use = 1;
+			conn->addr.socks_proxy.addr = s->socks4_addr;
+			conn->addr.socks_proxy.status = CO_SOCKS_NONE;
+			conn->addr.socks_proxy.req_line.version = 0x04;
+			conn->addr.socks_proxy.req_line.command = 0x01;
+			conn->addr.socks_proxy.req_line.port = get_net_port(&(s->addr));
+			conn->addr.socks_proxy.req_line.ip = is_inet_addr(&(s->addr));
+			strcpy(conn->addr.socks_proxy.req_line.user_id, "HAProxy");
+			conn->addr.socks_proxy.req_remain_len = sizeof(conn->addr.socks_proxy.req_line);
+
+			conn->addr.to = s->socks4_addr;
+
+			conn->flags |= CO_FL_SOCKS_HS;
+		}
+	}
+
 	proto = protocol_by_family(conn->addr.to.ss_family);
 	conn->target = &s->obj_type;
 
diff --git a/src/connection.c b/src/connection.c
index 2a66996b..b7ec820c 100644
--- a/src/connection.c
+++ b/src/connection.c
@@ -25,6 +25,7 @@
 #include <proto/proto_tcp.h>
 #include <proto/stream_interface.h>
 #include <proto/sample.h>
+#include <proto/log.h>
 
 #ifdef USE_OPENSSL
 #include <proto/ssl_sock.h>
@@ -72,6 +73,21 @@ void conn_fd_handler(int fd)
 		if (unlikely(conn->flags & CO_FL_ERROR))
 			goto leave;
 
+		if (conn->flags & CO_FL_SOCKS_HS) {
+			if (conn->addr.socks_proxy.status == CO_SOCKS_NONE || conn->addr.socks_proxy.status == CO_SOCKS_SENDING) {
+				//Going to send the request to the socks4 proxy
+				conn_send_socks_proxy_request(conn);
+				goto leave;
+			}
+
+			if (conn->addr.socks_proxy.status == CO_SOCKS_SENT || conn->addr.socks_proxy.status == CO_SOCKS_RECEIVING) {
+				//Check the response from the socks4 proxy
+				if (!conn_recv_socks_proxy_response(conn, CO_FL_SOCKS_HS)) {
+					goto leave;
+				}
+			}
+		}
+
 		if (conn->flags & CO_FL_ACCEPT_CIP)
 			if (!conn_recv_netscaler_cip(conn, CO_FL_ACCEPT_CIP))
 				goto leave;
@@ -966,6 +982,145 @@ int conn_recv_netscaler_cip(struct connection *conn, int flag)
 	return 0;
 }
 
+
+int conn_send_socks_proxy_request(struct connection *conn)
+{
+	/* we might have been called just after an asynchronous shutw */
+	if (conn->flags & CO_FL_SOCK_WR_SH)
+		goto out_error;
+
+	if (!conn_ctrl_ready(conn))
+		goto out_error;
+
+	while (conn->addr.socks_proxy.req_remain_len) {
+		int ret = 0;
+
+		conn->addr.socks_proxy.status = CO_SOCKS_SENDING;
+
+		/* we are sending the socks4_req_line here. If the data layer
+		 * has a pending write, we'll also set MSG_MORE.
+		 */
+		ret = conn_sock_send(conn,
+				     ((char *)(&(conn->addr.socks_proxy.req_line))) +
+				     (sizeof(conn->addr.socks_proxy.req_line)-(conn->addr.socks_proxy.req_remain_len)),
+				     conn->addr.socks_proxy.req_remain_len,
+		                     (conn->flags & CO_FL_XPRT_WR_ENA) ? MSG_MORE : 0);
+
+		ha_alert("SOCKS PROXY HS FD[%04X]: Before send remain is [%d], sent [%d]\n", conn->handle.fd, conn->addr.socks_proxy.req_remain_len, ret);
+
+		if (ret < 0) {
+			goto out_error;
+		}
+
+		conn->addr.socks_proxy.req_remain_len -= ret; /* becomes zero once complete */
+		if (conn->addr.socks_proxy.req_remain_len != 0) {
+			goto out_wait;
+		}
+
+		/* OK we've the whole request sent */
+		conn->addr.socks_proxy.status = CO_SOCKS_SENT;
+
+		/* mark it for receive the response */
+		__conn_sock_want_recv(conn);
+		break;
+	}
+
+	return 1;
+
+ out_error:
+	/* Write error on the file descriptor */
+	conn->flags |= CO_FL_ERROR;
+	return 0;
+
+ out_wait:
+	__conn_sock_stop_recv(conn);
+	return 0;
+}
+
+int conn_recv_socks_proxy_response(struct connection *conn, unsigned int flag)
+{
+	char line[8];
+	int ret, read_count;
+
+	/* we might have been called just after an asynchronous shutr */
+	if (conn->flags & CO_FL_SOCK_RD_SH)
+		goto fail;
+
+	if (!conn_ctrl_ready(conn))
+		goto fail;
+
+	if (!fd_recv_ready(conn->handle.fd))
+		return 0;
+
+	read_count = 0;
+	do {
+		/* SOCKS4 Proxy request granted server response, 0x00 | 0x5A | 0x00 0x00 | 0x00 0x00 0x00 0x00
+		 * Try to peek into it, before all 8 bytes of response ready.
+		 */
+		ret = recv(conn->handle.fd, line+read_count, 8-read_count, MSG_PEEK);
+
+		if (ret == 0) {
+			/* the socket has been closed or shutdown for send */
+			ha_alert("SOCKS PROXY HS FD[%04X]: Received ret[%d], errno[%d], looks like the socket has been closed or shutdown for send\n", conn->handle.fd, ret, errno);
+			goto fail;
+		}
+
+		if (ret > 0) {
+			conn->addr.socks_proxy.status = CO_SOCKS_RECEIVING;
+			if (ret == 8){
+				ha_alert("SOCKS PROXY HS FD[%04X]: Received 8 bytes, the response is [%02X|%02X|%02X %02X|%02X %02X %02X %02X]\n", conn->handle.fd,
+					line[0], line[1], line[2], line[3], line[4], line[5], line[6], line[7]);
+			}else{
+				ha_alert("SOCKS PROXY HS FD[%04X]: Received ret[%d], first byte is [%0X]\n", conn->handle.fd, ret, line[read_count]);
+			}
+		} else {
+			ha_alert("SOCKS PROXY HS FD[%04X]: Received ret[%d], errno[%d]\n", conn->handle.fd, ret, errno);
+		}
+
+		if (ret < 0) {
+			if (errno == EINTR) {
+				continue;
+			}
+			if (errno == EAGAIN) {
+				fd_cant_recv(conn->handle.fd);
+				return 0;
+			}
+			goto fail;
+		} else {
+			read_count += ret;
+		}
+	} while (read_count < 8);
+
+	if (line[1] != 0x5A) {
+		conn->addr.socks_proxy.status = CO_SOCKS_DENY;
+		conn->flags &= ~flag;
+
+		ha_alert("SOCKS PROXY HS FD[%04X]: FAIL, the response is [%02X|%02X|%02X %02X|%02X %02X %02X %02X]\n", conn->handle.fd,
+			line[0], line[1], line[2], line[3], line[4], line[5], line[6], line[7]);
+		goto fail;
+	}
+
+	/* remove the 8 bytes response from the stream */
+	do {
+		ret = recv(conn->handle.fd, line, 8, 0);
+		if (ret < 0 && errno == EINTR) {
+			continue;
+		}
+		if (ret != 8) {
+			goto fail;
+		}
+	} while (0);
+
+	conn->addr.socks_proxy.status = CO_SOCKS_READY;
+	conn->flags &= ~flag;
+	return 1;
+
+ fail:
+	__conn_sock_stop_both(conn);
+	conn->flags |= CO_FL_ERROR;
+	return 0;
+}
+
 /* Note: <remote> is explicitly allowed to be NULL */
 int make_proxy_line(char *buf, int buf_len, struct server *srv, struct connection *remote)
 {
diff --git a/src/proto_tcp.c b/src/proto_tcp.c
index d4cad223..54949533 100644
--- a/src/proto_tcp.c
+++ b/src/proto_tcp.c
@@ -293,6 +293,7 @@ int tcp_connect_server(struct connection *conn, int data, int delack)
 	struct server *srv;
 	struct proxy *be;
 	struct conn_src *src;
+	int ret;
 
 	conn->flags |= CO_FL_WAIT_L4_CONN; /* connection in progress */
 
@@ -496,7 +497,12 @@ int tcp_connect_server(struct connection *conn, int data, int delack)
 	if (global.tune.server_rcvbuf)
                 setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &global.tune.server_rcvbuf, sizeof(global.tune.server_rcvbuf));
 
-	if (connect(fd, (struct sockaddr *)&conn->addr.to, get_addr_len(&conn->addr.to)) == -1) {
+	if (conn->addr.socks_proxy.use) {
+		ret = connect(fd, (struct sockaddr *)&conn->addr.socks_proxy.addr, get_addr_len(&conn->addr.socks_proxy.addr));
+	} else {
+		ret = connect(fd, (struct sockaddr *)&conn->addr.to, get_addr_len(&conn->addr.to));
+	}
+	if (ret == -1) {
 		if (errno == EINPROGRESS || errno == EALREADY) {
 			/* common case, let's wait for connect status */
 			conn->flags |= CO_FL_WAIT_L4_CONN;
@@ -553,6 +559,10 @@ int tcp_connect_server(struct connection *conn, int data, int delack)
 	if (conn->send_proxy_ofs)
 		conn->flags |= CO_FL_SEND_PROXY;
 
+	/* Prepare to send socks proxy related handshakes */
+	if (conn->addr.socks_proxy.use)
+		conn->flags |= CO_FL_SOCKS_HS;
+
 	conn_ctrl_init(conn);       /* registers the FD */
 	fdtab[fd].linger_risk = 1;  /* close hard if needed */
 
diff --git a/src/server.c b/src/server.c
index 12a14adc..b504658b 100644
--- a/src/server.c
+++ b/src/server.c
@@ -322,6 +322,14 @@ static int srv_parse_check_send_proxy(char **args, int *cur_arg,
 	return 0;
 }
 
+/* Parse the "check-via-socks" server keyword */
+static int srv_parse_check_via_socks(char **args, int *cur_arg,
+                                           struct proxy *curproxy, struct server *newsrv, char **err)
+{
+	newsrv->check.via_socks = 1;
+	return 0;
+}
+
 /* Parse the "cookie" server keyword */
 static int srv_parse_cookie(char **args, int *cur_arg,
                             struct proxy *curproxy, struct server *newsrv, char **err)
@@ -888,6 +896,57 @@ static int srv_parse_track(char **args, int *cur_arg,
 	return 0;
 }
 
+/* Parse the "socks4" server keyword */
+static int srv_parse_socks4(char **args, int *cur_arg,
+                            struct proxy *curproxy, struct server *newsrv, char **err)
+{
+	char *errmsg;
+	int port_low, port_high;
+	struct sockaddr_storage *sk;
+	struct protocol *proto;
+
+	errmsg = NULL;
+
+	if (!*args[*cur_arg + 1]) {
+		memprintf(err, "'%s' expects <addr>:<port> as argument.\n", args[*cur_arg]);
+		goto err;
+	}
+
+	/* 'sk' is statically allocated (no need to be freed). */
+	sk = str2sa_range(args[*cur_arg + 1], NULL, &port_low, &port_high, &errmsg, NULL, NULL, 1);
+	if (!sk) {
+		memprintf(err, "'%s %s' : %s\n", args[*cur_arg], args[*cur_arg + 1], errmsg);
+		goto err;
+	}
+
+	proto = protocol_by_family(sk->ss_family);
+	if (!proto || !proto->connect) {
+		ha_alert("'%s %s' : connect() not supported for this address family.\n",
+			 args[*cur_arg], args[*cur_arg + 1]);
+		goto err;
+	}
+
+	newsrv->use_socks4 = 1;
+	newsrv->socks4_addr = *sk;
+
+	if (port_low != port_high) {
+		ha_alert("'%s' does not support port offsets (found '%s').\n",
+			args[*cur_arg], args[*cur_arg + 1]);
+		goto err;
+	}
+
+	if (!port_low) {
+		ha_alert("'%s': invalid port range %d-%d.\n", args[*cur_arg], port_low, port_high);
+		goto err;
+	}
+
+	return 0;
+
+ err:
+	free(errmsg);
+	return ERR_ALERT | ERR_FATAL;
+}
+
 
 /* Shutdown all connections of a server. The caller must pass a termination
  * code in <why>, which must be one of SF_ERR_* indicating the reason for the
@@ -1278,6 +1337,8 @@ static struct srv_kw_list srv_kws = { "ALL", { }, {
 	{ "source",              srv_parse_source,             -1,  1 }, /* Set the source address to be used to connect to the server */
 	{ "stick",               srv_parse_stick,               0,  1 }, /* Enable stick-table persistence */
 	{ "track",               srv_parse_track,               1,  1 }, /* Set the current state of the server, tracking another one */
+	{ "socks4",              srv_parse_socks4,              1,  1 }, /* Set the socks4 proxy of the server*/
+	{ "check-via-socks",     srv_parse_check_via_socks,     0,  1 }, /* enable socks proxy for health checks */
 	{ NULL, NULL, 0 },
 }};
 
-- 
2.20.1 (Apple Git-117)

