From 4a349481a26fe1c76549dbd86ad1b982bc19bc89 Mon Sep 17 00:00:00 2001
From: Alexander Liu <alec@atplatform.net>
Date: Wed, 22 May 2019 19:44:48 +0800
Subject: [PATCH] MEDIUM: connection: Upstream SOCKS4 proxy support

Have "socks4" and "check-via-socks4" server keyword added.
Implement handshake with SOCKS4 proxy server for tcp stream connection.
See issue #82.

I have the "SOCKS: A protocol for TCP proxy across firewalls" doc found
at "https://www.openssh.com/txt/socks4.protocol". Please reference to it.
---
 doc/SOCKS4.protocol.txt    |   1 +
 doc/configuration.txt      |  10 ++
 examples/socks4.cfg        |  55 ++++++++++
 include/proto/connection.h |   9 ++
 include/types/checks.h     |   2 +
 include/types/connection.h |  29 +++--
 include/types/server.h     |   3 +
 src/backend.c              |   6 ++
 src/checks.c               |   5 +
 src/connection.c           | 210 +++++++++++++++++++++++++++++++++++++
 src/proto_tcp.c            |   8 +-
 src/server.c               |  62 +++++++++++
 12 files changed, 389 insertions(+), 11 deletions(-)
 create mode 100644 doc/SOCKS4.protocol.txt
 create mode 100644 examples/socks4.cfg

diff --git a/doc/SOCKS4.protocol.txt b/doc/SOCKS4.protocol.txt
new file mode 100644
index 000000000..06aee8aed
--- /dev/null
+++ b/doc/SOCKS4.protocol.txt
@@ -0,0 +1 @@
+Please reference to "https://www.openssh.com/txt/socks4.protocol".
\ No newline at end of file
diff --git a/doc/configuration.txt b/doc/configuration.txt
index cc3e31ffb..40b765918 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -11900,6 +11900,11 @@ check-ssl
   See the "ssl" option for more information and "no-check-ssl" to disable
   this option.
 
+check-via-socks4
+  This option enables outgoinng health checks using upstream socks4 proxy. By 
+  default, the health checks won't go through socks tunnel even it was enabled
+  for normal traffic.
+
 ciphers <ciphers>
   This setting is only available when support for OpenSSL was built in. This
   option sets the string describing the list of cipher algorithms that is
@@ -12559,6 +12564,11 @@ stick
   default value.
   It may also be used as "default-server" setting to reset any previous
   "default-server" "non-stick" setting.
+  
+socks4 <addr>:<port>
+  This option enables upstream socks4 tunnel for outgoinng connections to the
+  server. Using this option won't force the health check to go via socks4 by 
+  default. You will have to use the keyword "check-via-socks4" to enable it.
 
 tcp-ut <delay>
   Sets the TCP User Timeout for all outgoing connections to this server. This
diff --git a/examples/socks4.cfg b/examples/socks4.cfg
new file mode 100644
index 000000000..609aeb78b
--- /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                 192.0.2.1:25                                        check inter 30000 fastinter 1000
+	server SMTPS2_Via_SocksProxy1 192.0.2.2:25 socks4 127.0.0.1:1080 check-via-socks4 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 192.0.2.1:443 ssl verify none socks4 127.0.0.1:1080 check inter 30000 fastinter 1000
+	server HTTPS2                 192.0.2.2:443 ssl verify none                       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 ede372638..915be87bf 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);
 
+/* scoks4 proxy handshake */
+int conn_send_socks4_proxy_request(struct connection *conn);
+int conn_recv_socks4_proxy_response(struct connection *conn);
+
 /* returns true is the transport layer is ready */
 static inline int conn_xprt_ready(const struct connection *conn)
 {
@@ -889,6 +893,11 @@ static inline const char *conn_err_code_str(struct connection *c)
 	case CO_ER_SSL_HANDSHAKE_HB: return "SSL handshake failure after heartbeat";
 	case CO_ER_SSL_KILLED_HB: return "Stopped a TLSv1 heartbeat attack (CVE-2014-0160)";
 	case CO_ER_SSL_NO_TARGET: return "Attempt to use SSL on an unknown target (internal error)";
+
+	case CO_ER_SOCKS4_SEND:    return "SOCKS4 Proxy write error during handshake";
+	case CO_ER_SOCKS4_RECV:    return "SOCKS4 Proxy read error during handshake";
+	case CO_ER_SOCKS4_DENY:    return "SOCKS4 Proxy deny the request";
+	case CO_ER_SOCKS4_ABORT:   return "SOCKS4 Proxy handshake aborted by server";
 	}
 	return NULL;
 }
diff --git a/include/types/checks.h b/include/types/checks.h
index f89abcbab..03d230577 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_socks4;                         /* check the connection via socks4 proxy */
 };
 
 struct check_status {
diff --git a/include/types/connection.h b/include/types/connection.h
index be590f098..9a362f05d 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.
  */
@@ -155,8 +164,8 @@ 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 */
-	/* unused : 0x00020000 */
+	CO_FL_SOCKS4_SEND   = 0x00010000,  /* handshaking with upstream SOCKS4 proxy, going to send the handshake */
+	CO_FL_SOCKS4_RECV   = 0x00020000,  /* handshaking with upstream SOCKS4 proxy, going to check if handshake succeed */
 
 	/* flags used to remember what shutdown have been performed/reported */
 	CO_FL_SOCK_RD_SH    = 0x00040000,  /* SOCK layer was notified about shutr/read0 */
@@ -182,7 +191,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_SOCKS4_SEND | CO_FL_SOCKS4_RECV,
 
 	/* when any of these flags is set, polling is defined by socket-layer
 	 * operations, as opposed to data-layer. Transport is explicitly not
@@ -205,8 +214,10 @@ enum {
 	 * must be done after clearing this flag.
 	 */
 	CO_FL_XPRT_TRACKED  = 0x80000000,
-};
 
+	/* below we have all SOCKS handshake flags grouped into one */
+	CO_FL_SOCKS4        = CO_FL_SOCKS4_SEND | CO_FL_SOCKS4_RECV,
+};
 
 /* possible connection error codes */
 enum {
@@ -254,6 +265,11 @@ enum {
 	CO_ER_SSL_KILLED_HB,    /* Stopped a TLSv1 heartbeat attack (CVE-2014-0160) */
 	CO_ER_SSL_NO_TARGET,    /* unknown target (not client nor server) */
 	CO_ER_SSL_EARLY_FAILED, /* Server refused early data */
+
+	CO_ER_SOCKS4_SEND,       /* SOCKS4 Proxy write error during handshake */
+	CO_ER_SOCKS4_RECV,       /* SOCKS4 Proxy read error during handshake */
+	CO_ER_SOCKS4_DENY,       /* SOCKS4 Proxy deny the request */
+	CO_ER_SOCKS4_ABORT,      /* SOCKS4 Proxy handshake aborted by server */
 };
 
 /* source address settings for outgoing connections */
@@ -426,7 +442,7 @@ struct connection {
 	/* first cache line */
 	enum obj_type obj_type;       /* differentiates connection from applet context */
 	unsigned char err_code;       /* CO_ER_* */
-	signed short send_proxy_ofs;  /* <0 = offset to (re)send from the end, >0 = send all */
+	signed short send_proxy_ofs;  /* <0 = offset to (re)send from the end, >0 = send all (reused for SOCKS4) */
 	unsigned int flags;           /* CO_FL_* */
 	const struct protocol *ctrl;  /* operations at the socket layer */
 	const struct xprt_ops *xprt;  /* operations at the transport layer */
@@ -569,7 +585,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.
@@ -577,6 +592,8 @@ struct tlv_ssl {
 /* Max number of file descriptors we send in one sendmsg() */
 #define MAX_SEND_FD 253
 
+#define SOCKS4_HS_RSP_LEN 8
+
 #endif /* _TYPES_CONNECTION_H */
 
 /*
diff --git a/include/types/server.h b/include/types/server.h
index 0d53d2600..219f5ab1d 100644
--- a/include/types/server.h
+++ b/include/types/server.h
@@ -141,6 +141,7 @@ enum srv_initaddr {
 #define SRV_F_AGENTADDR    0x0080        /* this server has a agent addr configured */
 #define SRV_F_COOKIESET    0x0100        /* this server has a cookie configured, so don't generate dynamic cookies */
 #define SRV_F_FASTOPEN     0x0200        /* Use TCP Fast Open to connect to server */
+#define SRV_F_SOCKS4_PROXY 0x0400        /* this server uses SOCKS4 proxy */
 
 /* configured server options for send-proxy (server->pp_opts) */
 #define SRV_PP_V1               0x0001   /* proxy protocol version 1 */
@@ -336,6 +337,8 @@ struct server {
 		char reason[128];
 	} op_st_chg;				/* operational status change's reason */
 	char adm_st_chg_cause[48];		/* administrative status change's cause */
+
+	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 467ef57a9..0bd0743e0 100644
--- a/src/backend.c
+++ b/src/backend.c
@@ -1526,12 +1526,18 @@ int connect_server(struct stream *s)
 
 		if (srv && srv->pp_opts) {
 			srv_conn->flags |= CO_FL_PRIVATE;
+			srv_conn->flags |= CO_FL_SEND_PROXY;
 			srv_conn->send_proxy_ofs = 1; /* must compute size */
 			if (cli_conn)
 				conn_get_to_addr(cli_conn);
 		}
 
 		assign_tproxy_address(s);
+
+		if (srv && (srv->flags & SRV_F_SOCKS4_PROXY)) {
+			srv_conn->send_proxy_ofs = 1;
+			srv_conn->flags |= CO_FL_SOCKS4;
+		}
 	}
 	else if (!conn_xprt_ready(srv_conn)) {
 		if (srv_conn->mux->reset)
diff --git a/src/checks.c b/src/checks.c
index d264aecf8..90f461477 100644
--- a/src/checks.c
+++ b/src/checks.c
@@ -1612,6 +1612,11 @@ static int connect_conn_chk(struct task *t)
 		conn->addr.to = s->addr;
 	}
 
+	if (s->check.via_socks4 &&  (s->flags & SRV_F_SOCKS4_PROXY)) {
+		conn->send_proxy_ofs = 1;
+		conn->flags |= CO_FL_SOCKS4;
+	}
+
 	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 908d098e4..6983137ff 100644
--- a/src/connection.c
+++ b/src/connection.c
@@ -27,6 +27,8 @@
 #include <proto/sample.h>
 #include <proto/ssl_sock.h>
 
+#include <common/debug.h>
+
 DECLARE_POOL(pool_head_connection, "connection",  sizeof(struct connection));
 DECLARE_POOL(pool_head_connstream, "conn_stream", sizeof(struct conn_stream));
 
@@ -69,6 +71,16 @@ void conn_fd_handler(int fd)
 		if (unlikely(conn->flags & CO_FL_ERROR))
 			goto leave;
 
+		if (conn->flags & CO_FL_SOCKS4_SEND) {
+			//Going to send the request to the socks4 proxy
+			if (!conn_send_socks4_proxy_request(conn))
+				goto leave;
+		}
+
+		if (conn->flags & CO_FL_SOCKS4_RECV)
+			if (!conn_recv_socks4_proxy_response(conn))
+				goto leave;
+
 		if (conn->flags & CO_FL_ACCEPT_CIP)
 			if (!conn_recv_netscaler_cip(conn, CO_FL_ACCEPT_CIP))
 				goto leave;
@@ -959,6 +971,204 @@ int conn_recv_netscaler_cip(struct connection *conn, int flag)
 	return 0;
 }
 
+
+int conn_send_socks4_proxy_request(struct connection *conn)
+{
+	struct socks4_request req_line;
+
+	/* 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;
+
+	req_line.version = 0x04;
+	req_line.command = 0x01;
+	req_line.port    = get_net_port(&(conn->addr.to));
+	req_line.ip      = is_inet_addr(&(conn->addr.to));
+	memcpy(req_line.user_id, "HAProxy\0", 8);
+
+	if (conn->send_proxy_ofs > 0) {
+		/*
+		 * This is the first call to send the request
+		 */
+		conn->send_proxy_ofs = -(int)sizeof(req_line);
+	}
+
+	while (conn->send_proxy_ofs < 0) {
+		int ret = 0;
+
+		/* 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 *)(&req_line)) + (sizeof(req_line)+conn->send_proxy_ofs),
+				-conn->send_proxy_ofs,
+				(conn->flags & CO_FL_XPRT_WR_ENA) ? MSG_MORE : 0);
+
+		DPRINTF(stderr, "SOCKS PROXY HS FD[%04X]: Before send remain is [%d], sent [%d]\n",
+				conn->handle.fd, -conn->send_proxy_ofs, ret);
+
+		if (ret < 0) {
+			goto out_error;
+		}
+
+		conn->send_proxy_ofs += ret; /* becomes zero once complete */
+		if (conn->send_proxy_ofs != 0) {
+			goto out_wait;
+		}
+
+		/* OK we've the whole request sent */
+		conn->flags &= ~CO_FL_SOCKS4_SEND;
+
+		/* mark it for receive the response */
+		__conn_sock_want_recv(conn);
+		break;
+	}
+
+	if (conn->flags & CO_FL_SEND_PROXY) {
+		/*
+		 * Get the send_proxy_ofs ready for the send_proxy due to we are
+		 * reusing the "send_proxy_ofs", and SOCKS4 handshake should be done
+		 * before sending PROXY Protocol.
+		 */
+		conn->send_proxy_ofs = 1;
+	}
+	return 1;
+
+ out_error:
+	/* Write error on the file descriptor */
+	conn->flags |= CO_FL_ERROR;
+	if (conn->err_code == CO_ER_NONE) {
+		conn->err_code = CO_ER_SOCKS4_SEND;
+	}
+	return 0;
+
+ out_wait:
+	__conn_sock_stop_recv(conn);
+	return 0;
+}
+
+int conn_recv_socks4_proxy_response(struct connection *conn)
+{
+	char line[SOCKS4_HS_RSP_LEN];
+	int ret;
+
+	/* 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;
+
+	do {
+		/* SOCKS4 Proxy will response with 8 bytes, 0x00 | 0x5A | 0x00 0x00 | 0x00 0x00 0x00 0x00
+		 * Try to peek into it, before all 8 bytes ready.
+		 */
+		ret = recv(conn->handle.fd, line, SOCKS4_HS_RSP_LEN, MSG_PEEK);
+
+		if (ret == 0) {
+			/* the socket has been closed or shutdown for send */
+			DPRINTF(stderr, "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);
+			if (conn->err_code == CO_ER_NONE) {
+				conn->err_code = CO_ER_SOCKS4_RECV;
+			}
+			goto fail;
+		}
+
+		if (ret > 0) {
+			if (ret == SOCKS4_HS_RSP_LEN) {
+				DPRINTF(stderr, "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{
+				DPRINTF(stderr, "SOCKS PROXY HS FD[%04X]: Received ret[%d], first byte is [%02X], last bye is [%02X]\n", conn->handle.fd, ret, line[0], line[ret-1]);
+			}
+		} else {
+			DPRINTF(stderr, "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 recv_abort;
+		}
+	} while (0);
+
+	if (ret < SOCKS4_HS_RSP_LEN) {
+		/* Missing data. Since we're using MSG_PEEK, we can only poll again if
+		 * we are not able to read enough data.
+		 */
+		fd_cant_recv(conn->handle.fd);
+		return 0;
+	}
+
+	/*
+	 * Base on the SOCSK4 protocol:
+	 *
+	 *			+----+----+----+----+----+----+----+----+
+	 *			| VN | CD | DSTPORT |      DSTIP        |
+	 *			+----+----+----+----+----+----+----+----+
+ 	 *	# of bytes:	   1    1      2              4
+ 	 *	VN is the version of the reply code and should be 0. CD is the result
+ 	 *	code with one of the following values:
+ 	 *	90: request granted
+ 	 *	91: request rejected or failed
+ 	 *	92: request rejected becasue SOCKS server cannot connect to identd on the client
+ 	 *	93: request rejected because the client program and identd report different user-ids
+ 	 *	The remaining fields are ignored.
+	 */
+	if (line[1] != 90) {
+		conn->flags &= (~CO_FL_SOCKS4_RECV);
+
+		DPRINTF(stderr, "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]);
+		if (conn->err_code == CO_ER_NONE) {
+			conn->err_code = CO_ER_SOCKS4_DENY;
+		}
+		goto fail;
+	}
+
+	/* remove the 8 bytes response from the stream */
+	do {
+		ret = recv(conn->handle.fd, line, SOCKS4_HS_RSP_LEN, 0);
+		if (ret < 0 && errno == EINTR) {
+			continue;
+		}
+		if (ret != SOCKS4_HS_RSP_LEN) {
+			if (conn->err_code == CO_ER_NONE) {
+				conn->err_code = CO_ER_SOCKS4_RECV;
+			}
+			goto fail;
+		}
+	} while (0);
+
+	conn->flags &= (~CO_FL_SOCKS4_RECV);
+	return 1;
+
+ recv_abort:
+	if (conn->err_code == CO_ER_NONE) {
+		conn->err_code = CO_ER_SOCKS4_ABORT;
+	}
+	conn->flags |= (CO_FL_SOCK_RD_SH | CO_FL_SOCK_WR_SH);
+	goto fail;
+
+ 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 95068ee6c..c8187b943 100644
--- a/src/proto_tcp.c
+++ b/src/proto_tcp.c
@@ -294,6 +294,7 @@ int tcp_connect_server(struct connection *conn, int flags)
 	struct proxy *be;
 	struct conn_src *src;
 	int use_fastopen = 0;
+	struct sockaddr_storage *addr;
 
 	conn->flags |= CO_FL_WAIT_L4_CONN; /* connection in progress */
 
@@ -514,7 +515,8 @@ int tcp_connect_server(struct connection *conn, int flags)
 	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) {
+	addr = (conn->flags & CO_FL_SOCKS4) ? &(srv->socks4_addr) : &(conn->addr.to);
+	if (connect(fd, (const struct sockaddr *)addr, get_addr_len(addr)) == -1) {
 		if (errno == EINPROGRESS || errno == EALREADY) {
 			/* common case, let's wait for connect status */
 			conn->flags |= CO_FL_WAIT_L4_CONN;
@@ -567,10 +569,6 @@ int tcp_connect_server(struct connection *conn, int flags)
 
 	conn->flags |= CO_FL_ADDR_TO_SET;
 
-	/* Prepare to send a few handshakes related to the on-wire protocol. */
-	if (conn->send_proxy_ofs)
-		conn->flags |= CO_FL_SEND_PROXY;
-
 	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 b88ab30df..d6dbb062f 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-socks4" server keyword */
+static int srv_parse_check_via_socks4(char **args, int *cur_arg,
+                                      struct proxy *curproxy, struct server *newsrv, char **err)
+{
+	newsrv->check.via_socks4 = 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,55 @@ 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->flags |= SRV_F_SOCKS4_PROXY;
+	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;
+}
+
 
 /* parse the "tfo" server keyword */
 static int srv_parse_tfo(char **args, int *cur_arg, struct proxy *px, struct server *newsrv, char **err)
@@ -1286,6 +1343,8 @@ static struct srv_kw_list srv_kws = { "ALL", { }, {
 	{ "stick",               srv_parse_stick,               0,  1 }, /* Enable stick-table persistence */
 	{ "tfo",                 srv_parse_tfo,                 0,  0 }, /* enable TCP Fast Open of server */
 	{ "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-socks4",    srv_parse_check_via_socks4,    0,  1 }, /* enable socks4 proxy for health checks */
 	{ NULL, NULL, 0 },
 }};
 
@@ -1721,6 +1780,9 @@ static void srv_settings_cpy(struct server *srv, struct server *src, int srv_tmp
 
 	if (srv_tmpl)
 		srv->srvrq = src->srvrq;
+
+	srv->check.via_socks4         = src->check.via_socks4;
+	srv->socks4_addr              = src->socks4_addr;
 }
 
 struct server *new_server(struct proxy *proxy)
-- 
2.20.1 (Apple Git-117)

