From 6767c73a40a9d242cce061cc7feaf480ad416314 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 openssh's website under OpenSSH Sprcifications along with other RFCs,
at "https://www.openssh.com/specs.html",
and "https://www.openssh.com/txt/socks4.protocol".

OpenSSH is using GPL, so I believe it should be fine to have the doc here.
---
 doc/SOCKS4.protocol.txt    | 150 +++++++++++++++++++++++++++++
 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              |   4 +
 src/checks.c               |   9 ++
 src/connection.c           | 188 +++++++++++++++++++++++++++++++++++++
 src/proto_sockpair.c       |  15 ++-
 src/proto_tcp.c            |  16 +++-
 src/proto_uxst.c           |   4 +-
 src/server.c               |  62 ++++++++++++
 14 files changed, 541 insertions(+), 15 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..16483deeb
--- /dev/null
+++ b/doc/SOCKS4.protocol.txt
@@ -0,0 +1,150 @@
+	SOCKS: A protocol for TCP proxy across firewalls
+
+			Ying-Da Lee
+		yingda@best.com  or  yingda@esd.sgi.com
+
+SOCKS was originally developed by David Koblas and subsequently modified
+and extended by me to its current running version -- version 4. It is a
+protocol that relays TCP sessions at a firewall host to allow application
+users transparent access across the firewall. Because the protocol is
+independent of application protocols, it can be (and has been) used for
+many different services, such as telnet, ftp, finger, whois, gopher, WWW,
+etc. Access control can be applied at the beginning of each TCP session;
+thereafter the server simply relays the data between the client and the
+application server, incurring minimum processing overhead. Since SOCKS
+never has to know anything about the application protocol, it should also
+be easy for it to accommodate applications which use encryption to protect
+their traffic from nosey snoopers.
+
+Two operations are defined: CONNECT and BIND.
+
+1) CONNECT
+
+The client connects to the SOCKS server and sends a CONNECT request when
+it wants to establish a connection to an application server. The client
+includes in the request packet the IP address and the port number of the
+destination host, and userid, in the following format.
+
+		+----+----+----+----+----+----+----+----+----+----+....+----+
+		| VN | CD | DSTPORT |      DSTIP        | USERID       |NULL|
+		+----+----+----+----+----+----+----+----+----+----+....+----+
+ # of bytes:	   1    1      2              4           variable       1
+
+VN is the SOCKS protocol version number and should be 4. CD is the
+SOCKS command code and should be 1 for CONNECT request. NULL is a byte
+of all zero bits.
+
+The SOCKS server checks to see whether such a request should be granted
+based on any combination of source IP address, destination IP address,
+destination port number, the userid, and information it may obtain by
+consulting IDENT, cf. RFC 1413.  If the request is granted, the SOCKS
+server makes a connection to the specified port of the destination host.
+A reply packet is sent to the client when this connection is established,
+or when the request is rejected or the operation fails. 
+
+		+----+----+----+----+----+----+----+----+
+		| 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.
+
+The SOCKS server closes its connection immediately after notifying
+the client of a failed or rejected request. For a successful request,
+the SOCKS server gets ready to relay traffic on both directions. This
+enables the client to do I/O on its connection as if it were directly
+connected to the application server.
+
+
+2) BIND
+
+The client connects to the SOCKS server and sends a BIND request when
+it wants to prepare for an inbound connection from an application server.
+This should only happen after a primary connection to the application
+server has been established with a CONNECT.  Typically, this is part of
+the sequence of actions:
+
+-bind(): obtain a socket
+-getsockname(): get the IP address and port number of the socket
+-listen(): ready to accept call from the application server
+-use the primary connection to inform the application server of
+ the IP address and the port number that it should connect to.
+-accept(): accept a connection from the application server
+
+The purpose of SOCKS BIND operation is to support such a sequence
+but using a socket on the SOCKS server rather than on the client.
+
+The client includes in the request packet the IP address of the
+application server, the destination port used in the primary connection,
+and the userid.
+
+		+----+----+----+----+----+----+----+----+----+----+....+----+
+		| VN | CD | DSTPORT |      DSTIP        | USERID       |NULL|
+		+----+----+----+----+----+----+----+----+----+----+....+----+
+ # of bytes:	   1    1      2              4           variable       1
+
+VN is again 4 for the SOCKS protocol version number. CD must be 2 to
+indicate BIND request.
+
+The SOCKS server uses the client information to decide whether the
+request is to be granted. The reply it sends back to the client has
+the same format as the reply for CONNECT request, i.e.,
+
+		+----+----+----+----+----+----+----+----+
+		| 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.
+
+However, for a granted request (CD is 90), the DSTPORT and DSTIP fields
+are meaningful.  In that case, the SOCKS server obtains a socket to wait
+for an incoming connection and sends the port number and the IP address
+of that socket to the client in DSTPORT and DSTIP, respectively. If the
+DSTIP in the reply is 0 (the value of constant INADDR_ANY), then the
+client should replace it with the IP address of the SOCKS server to which
+the cleint is connected. (This happens if the SOCKS server is not a
+multi-homed host.)  In the typical scenario, these two numbers are
+made available to the application client prgram via the result of the
+subsequent getsockname() call.  The application protocol must provide a
+way for these two pieces of information to be sent from the client to
+the application server so that it can initiate the connection, which
+connects it to the SOCKS server rather than directly to the application
+client as it normally would.
+
+The SOCKS server sends a second reply packet to the client when the
+anticipated connection from the application server is established.
+The SOCKS server checks the IP address of the originating host against
+the value of DSTIP specified in the client's BIND request.  If a mismatch
+is found, the CD field in the second reply is set to 91 and the SOCKS
+server closes both connections.  If the two match, CD in the second
+reply is set to 90 and the SOCKS server gets ready to relay the traffic
+on its two connections. From then on the client does I/O on its connection
+to the SOCKS server as if it were directly connected to the application
+server.
+
+
+
+For both CONNECT and BIND operations, the server sets a time limit
+(2 minutes in current CSTC implementation) for the establishment of its
+connection with the application server. If the connection is still not
+establiched when the time limit expires, the server closes its connection
+to the client and gives up.
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..09623e884 100644
--- a/src/backend.c
+++ b/src/backend.c
@@ -1532,6 +1532,10 @@ int connect_server(struct stream *s)
 		}
 
 		assign_tproxy_address(s);
+
+		if (srv && (srv->flags & SRV_F_SOCKS4_PROXY)) {
+			srv_conn->send_proxy_ofs = 1;
+		}
 	}
 	else if (!conn_xprt_ready(srv_conn)) {
 		if (srv_conn->mux->reset)
diff --git a/src/checks.c b/src/checks.c
index d264aecf8..5dbfd0e9a 100644
--- a/src/checks.c
+++ b/src/checks.c
@@ -1612,6 +1612,15 @@ static int connect_conn_chk(struct task *t)
 		conn->addr.to = s->addr;
 	}
 
+	if (s->check.via_socks4) {
+		if (s->flags & SRV_F_SOCKS4_PROXY) {
+			conn->send_proxy_ofs = 1;
+			conn->flags |= CO_FL_SOCKS4_SEND;
+		}
+	}else{
+		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..e0663534b 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
+			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,182 @@ 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));
+	strcpy(req_line.user_id,"HAProxy");
+
+	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);
+
+		/* Turn to receiving response */
+		conn->flags |= CO_FL_SOCKS4_RECV;
+
+		/* 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;
+	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 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, 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);
+			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 have not able to read enough data.
+		 */
+		fd_cant_recv(conn->handle.fd);
+		return 0;
+	}
+
+	if (line[1] != 0x5A) {
+		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]);
+		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) {
+			conn->err_code = CO_ER_SOCKS4_RECV;
+			goto fail;
+		}
+	} while (0);
+
+	conn->flags &= (~CO_FL_SOCKS4_RECV);
+	return 1;
+
+ recv_abort:
+	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_sockpair.c b/src/proto_sockpair.c
index 97a93480a..af77f17ef 100644
--- a/src/proto_sockpair.c
+++ b/src/proto_sockpair.c
@@ -240,12 +240,19 @@ int send_fd_uxst(int fd, int send_fd)
 static int sockpair_connect_server(struct connection *conn, int flags)
 {
 	int sv[2], fd, dst_fd = -1;
+	struct server *srv;
 
 	/* the FD is stored in the sockaddr struct */
 	dst_fd = ((struct sockaddr_in *)&conn->addr.to)->sin_addr.s_addr;
 
-	if (obj_type(conn->target) != OBJ_TYPE_PROXY &&
-	    obj_type(conn->target) != OBJ_TYPE_SERVER) {
+	switch (obj_type(conn->target)) {
+	case OBJ_TYPE_PROXY:
+		srv = NULL;
+		break;
+	case OBJ_TYPE_SERVER:
+		srv = objt_server(conn->target);
+		break;
+	default:
 		conn->flags |= CO_FL_ERROR;
 		return SF_ERR_INTERNAL;
 	}
@@ -289,7 +296,7 @@ static int sockpair_connect_server(struct connection *conn, int flags)
 	}
 
 	/* if a send_proxy is there, there are data */
-	if (conn->send_proxy_ofs)
+	if (srv && srv->pp_opts)
 		flags |= CONNECT_HAS_DATA;
 
 	if (global.tune.server_sndbuf)
@@ -315,7 +322,7 @@ static int sockpair_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)
+	if (srv && srv->pp_opts)
 		conn->flags |= CO_FL_SEND_PROXY;
 
 	conn_ctrl_init(conn);       /* registers the FD */
diff --git a/src/proto_tcp.c b/src/proto_tcp.c
index 95068ee6c..d5471c8ef 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;
+	int ret;
 
 	conn->flags |= CO_FL_WAIT_L4_CONN; /* connection in progress */
 
@@ -492,7 +493,7 @@ int tcp_connect_server(struct connection *conn, int flags)
 	 */
 	if (flags & (CONNECT_DELACK_ALWAYS) ||
 	    ((flags & CONNECT_DELACK_SMART_CONNECT ||
-	      (flags & CONNECT_HAS_DATA) || conn->send_proxy_ofs) &&
+	      (flags & CONNECT_HAS_DATA) || (srv && srv->pp_opts)) &&
 	     (be->options2 & PR_O2_SMARTCON)))
                 setsockopt(fd, IPPROTO_TCP, TCP_QUICKACK, &zero, sizeof(zero));
 #endif
@@ -514,7 +515,12 @@ 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) {
+	if (srv->flags & SRV_F_SOCKS4_PROXY) {
+		ret = connect(fd, (struct sockaddr *)&(srv->socks4_addr), get_addr_len(&(srv->socks4_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;
@@ -568,9 +574,13 @@ 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)
+	if (srv && srv->pp_opts)
 		conn->flags |= CO_FL_SEND_PROXY;
 
+	/* Prepare to send socks proxy related handshakes */
+	if (srv && srv->flags & SRV_F_SOCKS4_PROXY)
+		conn->flags |= CO_FL_SOCKS4_SEND;
+
 	conn_ctrl_init(conn);       /* registers the FD */
 	fdtab[fd].linger_risk = 1;  /* close hard if needed */
 
diff --git a/src/proto_uxst.c b/src/proto_uxst.c
index 42e0dc878..3f8213347 100644
--- a/src/proto_uxst.c
+++ b/src/proto_uxst.c
@@ -510,7 +510,7 @@ static int uxst_connect_server(struct connection *conn, int flags)
 	}
 
 	/* if a send_proxy is there, there are data */
-	if (conn->send_proxy_ofs)
+	if (srv && srv->pp_opts)
 		flags |= CONNECT_HAS_DATA;
 
 	if (global.tune.server_sndbuf)
@@ -566,7 +566,7 @@ static int uxst_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)
+	if (srv && srv->pp_opts)
 		conn->flags |= CO_FL_SEND_PROXY;
 
 	conn_ctrl_init(conn);       /* registers the FD */
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)

