Hi.

I was able to create a working setup with the attached patches, I'm pretty sure that the patch will need some adoptions until it' ready to commit to the dev branch.

It would be nice to get some feedback.

There are some open questions which I'm not able to do by my self because it requires some design discussion.

* It is possible to set some headers like Host and Proxy-Authorization within in CONNECT Method [0]. As the "CONNECT" is a TCP session it would be nice to have in "tcp-request content|session " a "upstream-proxy-header" option but in the "struct proxy {...}" https://github.com/haproxy/haproxy/blob/master/include/haproxy/proxy-t.h#L271 isn't any useful member in "struct {...} tcp_req;" https://github.com/haproxy/haproxy/blob/master/include/haproxy/proxy-t.h#L299-L304

My suggestion is to add list wich holdes the upstream proxy headers, opinions on this?

* What's the most efficient way to add this headers into the "conn_send_upstream_proxy_tunnel_request"?

As you can see in the patch I have tried to add the header into he "proxy_connect" string with "snprintf()", but without success just because I'm a little bit out of training with C, any help to fix this is very welcome.

* My test setup is shown in examples/upstream-proxy.cfg.

Best regards
Alex

[0]https://www.rfc-editor.org/rfc/rfc9110#name-connect

On 2024-05-31 (Fr.) 12:08, Aleksandar Lazic wrote:
Hi.

Anyone who have some Ideas how to fix the return way?

Regards

Alex

On 2024-05-27 (Mo.) 09:12, Aleksandar Lazic wrote:
Hi.

I have done some progress with the feature :-)

The test setup runs in 4 shells.

# shell1: curl -vk --connect-to www.test1.com:4433:127.0.0.1:8080 -H "Host: www.test1.com" https://www.test1.com:4433
# shell2: ./haproxy -d -f examples/upstream-proxy.cfg
# shell3: sudo podman run --rm -it --name squid -e TZ=UTC -p 3128:3128 --network host ubuntu/squid # shell4: openssl s_server -trace -www -bugs -debug -cert reg-tests/ssl/common.pem

The Request reaches the s_server but I 'm stuck with the return way "connection.c:conn_recv_upstream_proxy_tunnel_response()"

Have anyone an Idea what's wrong?

Maybe it's too late for 3.0 but it would be nice to have this feature in 3.1 :-)

Regards

Alex

On 2024-05-24 (Fr.) 00:08, Aleksandar Lazic wrote:
Hi.

I have seen https://github.com/haproxy/haproxy/issues/1542 which requests that feature.

Now I have tried to "port" the https://github.com/brentcetinich/haproxy/commit/bc258bff030677d855a6a84fec881398e8f1e082 to the current dev branch and attached the patch.

I'm pretty sure that there are some issues in the patch and I'm happy to make some rounds to fix the issues :-)

One question for me is, as I'm not that fit anymore in C and datatype, does this `0x10000` still fits into 32bit?

```from the Patch

+++ b/include/haproxy/server-t.h
@@ -154,6 +154,7 @@ enum srv_initaddr {
 #define SRV_F_NON_PURGEABLE 0x2000       /* this server cannot be removed at runtime */
 #define SRV_F_DEFSRV_USE_SSL 0x4000      /* default-server uses SSL */
 #define SRV_F_DELETED 0x8000             /* srv is deleted but not yet purged */

+#define SRV_F_UPSTREAM_PROXY_TUNNEL 0x10000  /* this server uses a upstream proxy tunnel with CONNECT method */

```

Another Question raised to me is: Why are not "TRACE(...)" entries in src/connection.c only DPRINTF?

On that way a big thanks to brentcetinich for his great work for the initil work to that patch.

Regards

Alex

On 2024-05-23 (Do.) 22:32, Aleksandar Lazic wrote:
Hi.

I follow the development more or less closely and I must say I not always understand all changes :-).

Just for my clarification is the following setup now possible with HAProxy with all the new shiny features  :-)

client => frontend
              |
              \-> backend server dest1 IP:port
                    |
                    \-> call "CONNECT IP:PORT" on upstream proxy
                          |
                          \-> TCP FLOW to destination IP


I know there is the http://docs.haproxy.org/2.9/configuration.html#5.2-socks4 option but sadly not too much enterprise Proxies admins offers socks4 nowadays.

I think the Scenario is still not possible but I would like to have a second eye opinion on that.

Maybe somebody on the list have a working solution for the scenario and can share it, maybe only via direct mail. ¯\_(ツ)_/¯

Regards
Alex

From 73cf1e51b3624e6cbad3a8b45e2c2f4557cfa81b Mon Sep 17 00:00:00 2001
From: Aleksandar Lazic <al-hapr...@none.at>
Date: Fri, 7 Jun 2024 00:35:36 +0200
Subject: [PATCH 4/4] This commit makes it possible for HAProxy to reach target
 server via a upstream http proxy.

This patch is based on the work of @brentcetinich
and refer to gh #1542
---
 examples/upstream-proxy.cfg | 30 +++++++++++++-
 include/haproxy/server-t.h  | 10 +++++
 src/connection.c            | 81 +++++++++++++++++++++++++++++++++----
 src/server.c                | 74 +++++++++++++++++++++++++++++++++
 src/xprt_handshake.c        |  2 +-
 5 files changed, 187 insertions(+), 10 deletions(-)

diff --git a/examples/upstream-proxy.cfg b/examples/upstream-proxy.cfg
index 2648e3abdc..837e437370 100644
--- a/examples/upstream-proxy.cfg
+++ b/examples/upstream-proxy.cfg
@@ -1,13 +1,34 @@
 # Test setup.
 # shell1: curl -vk --connect-to www.test1.com:4433:127.0.0.1:8080 -H "Host: www.test1.com" https://www.test1.com:4433
 # shell2: ./haproxy -d -f examples/upstream-proxy.cfg
-# shell3: sudo podman run --rm -it --name squid -e TZ=UTC -p 3128:3128 --network host ubuntu/squid
+# shell3: sudo podman run --rm -it --name squid -e TZ=UTC -p 3128:3128 --network host -v /datadisk/git-repos/haproxy/examples/upstream-proxy-squid.conf:/etc/squid/squid.conf ubuntu/squid > squid_debug_all_allow_all.log 2>&1
 # shell4: openssl s_server -trace -www -bugs -debug -cert reg-tests/ssl/common.pem
 
 global
 	log stdout format raw daemon debug
 	stats timeout 30s
 
+    #tune.pt.zero-copy-forwarding off
+
+	# turn on stats unix socket
+  	stats socket /datadisk/git-repos/haproxy/stats mode 660 level admin
+
+	#expose-experimental-directives
+	#trace h1 sink stdout
+	#trace h1 level developer
+	#trace h1 verbosity complete
+	#trace h1 start now
+
+	#trace stream sink stdout
+	#trace stream level developer
+	#trace stream verbosity complete
+	#trace stream start now
+
+	#trace pt sink stdout
+	#trace pt level developer
+	#trace pt verbosity complete
+	#trace pt start now
+
 listen proxy-forward-8080
 	bind 0.0.0.0:8080
 	mode tcp
@@ -18,6 +39,11 @@ listen proxy-forward-8080
 	timeout client  50s
 	timeout server  50s
 
+	#tcp-request connection or session upstream-proxy-header Host www.test1.com
+	#tcp-request connection or session upstream-proxy-header Proxy-Authorization "basic base64-pass"
+
     # dig +short php.net
     # 185.85.0.29
-	server https_Via_Proxy1 www.test1.com:4433 upstream-proxy-tunnel 127.0.0.1:3128 sni str(www.test1.com) init-addr 127.0.0.1
+	server https_Via_Proxy1 www.test1.com:4433 upstream-proxy-tunnel 127.0.0.1:3128 upstream-proxy-header-host "www.test1.com:4433" sni str(www.test1.com) init-addr 127.0.0.1
+#	server https_Via_Proxy1 www.test1.com:4433 upstream-proxy-tunnel 127.0.0.1:3128 upstream-proxy-header-host "www.test1.com" upstream-proxy-header-auth "base64-pass" upstream-proxy-header-custom "My-header: my-value" sni str(www.test1.com) init-addr 127.0.0.1
+
diff --git a/include/haproxy/server-t.h b/include/haproxy/server-t.h
index 8b21e32229..baf71ca47f 100644
--- a/include/haproxy/server-t.h
+++ b/include/haproxy/server-t.h
@@ -467,6 +467,16 @@ struct server {
 
 	struct guid_node guid;			/* GUID global tree node */
 
+	/* TODO: 
+	 * due to lack of missing tcp rule header struct are this headers
+	 * strings here.
+	 * As soon as there is a "tcp-request content|session set-header" option
+	 * should this be moved to that solution
+	 */
+    char *upstream_proxy_header_auth;
+	char *upstream_proxy_header_host;
+	char *upstream_proxy_header_custom;
+
 	/* warning, these structs are huge, keep them at the bottom */
 	struct conn_src conn_src;                           /* connection source settings */
 	struct sockaddr_storage addr;                       /* the address to connect to, doesn't include the port */
diff --git a/src/connection.c b/src/connection.c
index d0039e5824..4d399efa55 100644
--- a/src/connection.c
+++ b/src/connection.c
@@ -1785,10 +1785,12 @@ int conn_recv_socks4_proxy_response(struct connection *conn)
 	return 0;
 }
 
+#include <haproxy/list.h>
+
 /* TODO: use *TRACE* features from HAProxy instead of DPRINTF */
 int conn_send_upstream_proxy_tunnel_request(struct connection *conn) {
 
-	char proxy_connect[512]; /* fqdn + port (connect string) + headers */
+	char proxy_connect[1024]; /* fqdn + port (connect string) + headers */
 	int connect_length;
 	struct server *srv = NULL;
 
@@ -1804,13 +1806,54 @@ int conn_send_upstream_proxy_tunnel_request(struct connection *conn) {
 	srv = objt_server(conn->target);
 	BUG_ON(!srv);
 
+	memset(proxy_connect,0,sizeof(proxy_connect));
+
 	DPRINTF(stderr,"hostname: %s\n", srv->hostname);
+	DPRINTF(stderr,"upstream_proxy_header_host :%s:\n", srv->upstream_proxy_header_host);
+	// DPRINTF(stderr,"upstream_proxy_header_auth :%s:\n", srv->upstream_proxy_header_auth);
+	// DPRINTF(stderr,"upstream_proxy_header_custom :%s:\n", srv->upstream_proxy_header_custom);
 
+    /* TODO: Add feature to be able to add proxy headers */
+	/* 
 	connect_length = snprintf(proxy_connect, sizeof(proxy_connect), 
 							"CONNECT %s:%u HTTP/1.1\r\n\r\n",
 							srv->hostname,
 							ntohs(get_net_port(conn->dst)));
+	*/
 
+	connect_length = snprintf(proxy_connect, sizeof(proxy_connect), 
+							"CONNECT %s:%u HTTP/1.1\r\n\r\n",
+							srv->hostname,
+							ntohs(get_net_port(conn->dst)));
+/* 
+	if (srv->upstream_proxy_header_host)
+	{
+		connect_length += snprintf(proxy_connect+connect_length, sizeof(proxy_connect)-connect_length, 
+							"%sHost: %s\r\n",
+							proxy_connect,
+							srv->upstream_proxy_header_host);
+	}
+
+	if (srv->upstream_proxy_header_auth)
+	{
+		connect_length += snprintf(proxy_connect+connect_length, sizeof(proxy_connect)-connect_length, 
+							"%sProxy-Authorization: basic %s\r\n",
+							proxy_connect,
+							srv->upstream_proxy_header_auth);
+	}
+
+	if (srv->upstream_proxy_header_custom)
+	{
+		connect_length += snprintf(proxy_connect+connect_length, sizeof(proxy_connect)-connect_length, 
+							"%s%s\r\n",
+							proxy_connect,
+							srv->upstream_proxy_header_custom);
+	}
+ * /
+	connect_length += snprintf(proxy_connect+strlen(proxy_connect), sizeof(proxy_connect)-strlen(proxy_connect), 
+							"%s\r\n",
+							proxy_connect);
+*/
 	DPRINTF(stderr,"connect_length :%d: proxy_connect :%s:\n", connect_length, proxy_connect);
 	DPRINTF(stderr,"send_proxy_ofs: %d\n", conn->send_proxy_ofs);
 
@@ -1868,7 +1911,7 @@ int conn_send_upstream_proxy_tunnel_request(struct connection *conn) {
 		conn->send_proxy_ofs = 1;
 	}
 
-	DPRINTF(stderr, "HTTP TUNNEL SEND end\n");
+	DPRINTF(stderr, "HTTP TUNNEL SEND end: okay\n");
 	return 1;
 
  out_error:
@@ -1887,6 +1930,9 @@ int conn_send_upstream_proxy_tunnel_request(struct connection *conn) {
 
 
 int conn_recv_upstream_proxy_tunnel_response(struct connection *conn) {
+
+	struct ist upstream_proxy_response = IST_NULL;
+	struct ist upstream_proxy_successful = ist("HTTP/1.1 200 Connection established");
 	int ret;
 
 	DPRINTF(stderr, "HTTP TUNNEL RECV start\n");
@@ -1902,25 +1948,46 @@ int conn_recv_upstream_proxy_tunnel_response(struct connection *conn) {
 	}
 
 	while (1) {
-		ret = recv(conn->handle.fd, trash.area, trash.size, MSG_PEEK);
-		DPRINTF(stderr, "HTTP TUNNEL RECV ret :%d: errno :%s:\n", ret, strerror(errno));
-		/*
+		ret = recv(conn->handle.fd, trash.area, trash.size, 128);
+		
+		/* 
+		DPRINTF(stderr,"HTTP TUNNEL RECV ret :%d: errno :%d: strerror :%s:\n", ret, errno ,strerror(errno));
+		DPRINTF(stderr,"trash.size :%ld:\n",trash.size);
+		DPRINTF(stderr,"trash.data :%ld:\n",trash.data);
+		DPRINTF(stderr,"trash.area :%s:\n",trash.area);
+		 */
+
 		if (ret < 0) {
-			if (errno == EINTR)
+			if (errno == EAGAIN)
 				continue;
+			/*
 			if (errno == EAGAIN || errno == EWOULDBLOCK) {
 				fd_cant_recv(conn->handle.fd);
 				DPRINTF(stderr, "HTTP TUNNEL RECV errno\n");
 				goto not_ready;
 			}
 			goto recv_abort;
+			*/
 		}
-		*/
+		
 		trash.data = ret;
+		upstream_proxy_response = ist2(trash.area, trash.data);
+
+		/* get the first line from proxy response*/
+    	upstream_proxy_response = iststop(upstream_proxy_response,'\r');
+		DPRINTF(stderr,"upstream_proxy_response len :%ld:\n",istlen(upstream_proxy_response));
+		DPRINTF(stderr,"Test isteq :%d:\n",isteq(upstream_proxy_response,upstream_proxy_successful));
+
+		/* check for HTTP/1.1 200 Connection established */
+		if(!isteq(upstream_proxy_response,upstream_proxy_successful)){
+			DPRINTF(stderr,"HTTP TUNNEL no 200 ret :%d: errno :%d: strerror :%s:\n", ret, errno ,strerror(errno));
+			goto recv_abort;
+		}
 		break;
 	}
 
 	DPRINTF(stderr, "HTTP TUNNEL RECV end\n");
+	conn->flags &= ~CO_FL_WAIT_L4_CONN;
 	conn->flags &= ~CO_FL_UPSTREAM_PROXY_TUNNEL_RECV;
 	return 1;
 
diff --git a/src/server.c b/src/server.c
index df8a2f8f9d..79c4e6559c 100644
--- a/src/server.c
+++ b/src/server.c
@@ -1948,6 +1948,73 @@ static int srv_parse_upstream_proxy_tunnel(char **args, int *cur_arg,
 	return ERR_ALERT | ERR_FATAL;
 }
 
+/* Parse the "upstream-proxy-header-host" server keyword */
+static int srv_parse_upstream_header_host(char **args, int *cur_arg,
+								  struct proxy *curproxy, struct server *newsrv, char **err) {
+
+	char *arg;
+
+	arg = args[*cur_arg + 1];
+
+	if (!*arg) {
+		memprintf(err, "'%s' expects an string as argument.\n", arg);
+		goto err;
+	}
+
+	//newsrv->flags |= SRV_F_UPSTREAM_PROXY_TUNNEL;
+	newsrv->upstream_proxy_header_host = strdup(arg);
+	DPRINTF(stderr,"upstream_proxy_header_custom :%s:\n", newsrv->upstream_proxy_header_host);
+
+	return 0;
+
+ err:
+	return ERR_ALERT | ERR_FATAL;
+}
+
+/* Parse the "upstream-proxy-header-auth" server keyword */
+static int srv_parse_upstream_header_auth(char **args, int *cur_arg,
+								  struct proxy *curproxy, struct server *newsrv, char **err) {
+
+	char *arg;
+
+	arg = args[*cur_arg + 1];
+
+	if (!*arg) {
+		memprintf(err, "'%s' expects an string as argument.\n", arg);
+		goto err;
+	}
+
+	newsrv->flags |= SRV_F_UPSTREAM_PROXY_TUNNEL;
+	newsrv->upstream_proxy_header_auth = strdup(arg);
+
+	return 0;
+
+ err:
+	return ERR_ALERT | ERR_FATAL;
+}
+
+/* Parse the "upstream-proxy-header-custom" server keyword */
+static int srv_parse_upstream_header_custom(char **args, int *cur_arg,
+								  struct proxy *curproxy, struct server *newsrv, char **err) {
+
+	char *arg;
+
+	arg = args[*cur_arg + 1];
+
+	if (!*arg) {
+		memprintf(err, "'%s' expects an string as argument.\n", arg);
+		goto err;
+	}
+
+	newsrv->flags |= SRV_F_UPSTREAM_PROXY_TUNNEL;
+	newsrv->upstream_proxy_header_custom = strdup(arg);
+
+	return 0;
+
+ err:
+	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)
 {
@@ -2341,6 +2408,9 @@ static struct srv_kw_list srv_kws = { "ALL", { }, {
 	{ "tfo",                   srv_parse_tfo,                   0,  1,  1 }, /* enable TCP Fast Open of server */
 	{ "track",                 srv_parse_track,                 1,  1,  1 }, /* Set the current state of the server, tracking another one */
 	{ "upstream-proxy-tunnel", srv_parse_upstream_proxy_tunnel, 1,  1,  0 }, /* Set the upstream proxy tunnel backend of the server*/
+	{ "upstream-proxy-header-host", srv_parse_upstream_header_host, 1,  1,  0 }, /* Set the Host header for the  upstream proxy tunnel */
+	{ "upstream-proxy-header-auth", srv_parse_upstream_header_auth, 1,  1,  0 }, /* Set the Proxy-Auth header for the upstream proxy tunnel */
+	{ "upstream-proxy-header-custom", srv_parse_upstream_header_custom, 1,  1,  0 }, /* Set an Custom header for the upstream proxy tunnel */
 	{ "socks4",                srv_parse_socks4,                1,  1,  0 }, /* Set the socks4 proxy of the server*/
 	{ "usesrc",                srv_parse_usesrc,                0,  1,  1 }, /* safe-guard against usesrc without preceding <source> keyword */
 	{ "weight",                srv_parse_weight,                1,  1,  1 }, /* Set the load-balancing weight */
@@ -2960,6 +3030,10 @@ void srv_free_params(struct server *srv)
 		ha_free(&srv_tlv->fmt_string);
 		ha_free(&srv_tlv);
 	}
+
+	free(srv->upstream_proxy_header_host);
+	free(srv->upstream_proxy_header_auth);
+	free(srv->upstream_proxy_header_custom);
 }
 
 /* Deallocate a server <srv> and its member. <srv> must be allocated. For
diff --git a/src/xprt_handshake.c b/src/xprt_handshake.c
index 1bc2e0b358..27dfbe3f3a 100644
--- a/src/xprt_handshake.c
+++ b/src/xprt_handshake.c
@@ -69,7 +69,7 @@ struct task *xprt_handshake_io_cb(struct task *t, void *bctx, unsigned int state
 	/* TODO: check flags to send optional headers like keep alive host etc */
 	if (conn->flags & CO_FL_UPSTREAM_PROXY_TUNNEL_RECV) {
 		if (!conn_recv_upstream_proxy_tunnel_response(conn)) {
-			ctx->xprt->subscribe(conn, ctx->xprt_ctx, SUB_RETRY_SEND,
+			ctx->xprt->subscribe(conn, ctx->xprt_ctx, SUB_RETRY_RECV,
 								 &ctx->wait_event);
 
 			goto out;
-- 
2.34.1

From 07bd57bf6fc9aa276cc6ba573f9199272b4e2add Mon Sep 17 00:00:00 2001
From: Aleksandar Lazic <al-hapr...@none.at>
Date: Wed, 29 May 2024 14:43:26 +0200
Subject: [PATCH 3/4] Add some more debugging infos

---
 include/haproxy/connection.h |  2 +-
 src/connection.c             | 13 +++++++++----
 2 files changed, 10 insertions(+), 5 deletions(-)

diff --git a/include/haproxy/connection.h b/include/haproxy/connection.h
index 31218a52c7..cdb37216b4 100644
--- a/include/haproxy/connection.h
+++ b/include/haproxy/connection.h
@@ -76,7 +76,7 @@ int conn_recv_socks4_proxy_response(struct connection *conn);
 
 /* upstream http proxy handshake */
 int conn_send_upstream_proxy_tunnel_request(struct connection *conn);
-int conn_send_upstream_proxy_tunnel_request(struct connection *conn);
+int conn_recv_upstream_proxy_tunnel_response(struct connection *conn);
 
 /* If we delayed the mux creation because we were waiting for the handshake, do it now */
 int conn_create_mux(struct connection *conn);
diff --git a/src/connection.c b/src/connection.c
index cd17d28db4..d0039e5824 100644
--- a/src/connection.c
+++ b/src/connection.c
@@ -1886,8 +1886,7 @@ int conn_send_upstream_proxy_tunnel_request(struct connection *conn) {
 }
 
 
-int conn_recv_upstream_proxy_tunnel_response(struct connection *conn)
-{
+int conn_recv_upstream_proxy_tunnel_response(struct connection *conn) {
 	int ret;
 
 	DPRINTF(stderr, "HTTP TUNNEL RECV start\n");
@@ -1897,20 +1896,26 @@ int conn_recv_upstream_proxy_tunnel_response(struct connection *conn)
 
 	BUG_ON(conn->flags & CO_FL_FDLESS);
 
-	if (!fd_recv_ready(conn->handle.fd))
+	if (!fd_recv_ready(conn->handle.fd)){
+		DPRINTF(stderr, "HTTP TUNNEL RECV fd_recv_ready\n");
 		goto not_ready;
+	}
 
 	while (1) {
-		ret = recv(conn->handle.fd, trash.area, trash.size, 0);
+		ret = recv(conn->handle.fd, trash.area, trash.size, MSG_PEEK);
+		DPRINTF(stderr, "HTTP TUNNEL RECV ret :%d: errno :%s:\n", ret, strerror(errno));
+		/*
 		if (ret < 0) {
 			if (errno == EINTR)
 				continue;
 			if (errno == EAGAIN || errno == EWOULDBLOCK) {
 				fd_cant_recv(conn->handle.fd);
+				DPRINTF(stderr, "HTTP TUNNEL RECV errno\n");
 				goto not_ready;
 			}
 			goto recv_abort;
 		}
+		*/
 		trash.data = ret;
 		break;
 	}
-- 
2.34.1

From 5ac8750390ef91974691c07251f6c32782573c72 Mon Sep 17 00:00:00 2001
From: Aleksandar Lazic <al-hapr...@none.at>
Date: Mon, 27 May 2024 09:05:39 +0200
Subject: [PATCH 2/4] FEATURE/MAJOR: Add upstream-proxy-tunnel feature

This enables HAProxy to reach an target server via a upstream
http proxy.

This commit should close gh #1542
---
 doc/configuration.txt              |  9 +++
 examples/upstream-proxy-squid.conf | 60 +++++++++++++++++++
 examples/upstream-proxy.cfg        | 23 +++++++
 include/haproxy/connection-t.h     |  1 +
 include/haproxy/connection.h       |  1 +
 src/connection.c                   | 96 +++++++++++++++++++++++++-----
 src/xprt_handshake.c               | 15 ++++-
 7 files changed, 186 insertions(+), 19 deletions(-)
 create mode 100644 examples/upstream-proxy-squid.conf
 create mode 100644 examples/upstream-proxy.cfg

diff --git a/doc/configuration.txt b/doc/configuration.txt
index c0667af8f8..59a7460558 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -18015,6 +18015,13 @@ tls-tickets
   It may also be used as "default-server" setting to reset any previous
   "default-server" "no-tls-tickets" setting.
 
+upstream-proxy-tunnel <addr>:<port>
+  May be used in the following contexts: tcp, http
+
+  This option enables upstream http proxy tunnel for outgoing connections to
+  the server. Using this option won't force the health check to go via upstream
+  http proxy by default.
+
 verify [none|required]
   May be used in the following contexts: tcp, http, log, peers, ring
 
@@ -21926,6 +21933,8 @@ fc_err_str : string
   | 41 | "SOCKS4 Proxy deny the request"                                           |
   | 42 | "SOCKS4 Proxy handshake aborted by server"                                |
   | 43 | "SSL fatal error"                                                         |
+  | 44 | "Error during reverse connect"                                            |
+  | 45 | "Upstream http proxy write error during handshake"                        |
   +----+---------------------------------------------------------------------------+
 
 fc_fackets : integer
diff --git a/examples/upstream-proxy-squid.conf b/examples/upstream-proxy-squid.conf
new file mode 100644
index 0000000000..3dc391e7a1
--- /dev/null
+++ b/examples/upstream-proxy-squid.conf
@@ -0,0 +1,60 @@
+#
+# ATTETNTION: This squid config is only for test purpose!
+# ATTETNTION: http_access allow all
+# ATTETNTION: http_access allow CONNECT all
+#
+
+#debug_options all,9
+
+acl localnet src 0.0.0.1-0.255.255.255	# RFC 1122 "this" network (LAN)
+acl localnet src 10.0.0.0/8		# RFC 1918 local private network (LAN)
+acl localnet src 100.64.0.0/10		# RFC 6598 shared address space (CGN)
+acl localnet src 169.254.0.0/16 	# RFC 3927 link-local (directly plugged) machines
+acl localnet src 172.16.0.0/12		# RFC 1918 local private network (LAN)
+acl localnet src 192.168.0.0/16		# RFC 1918 local private network (LAN)
+acl localnet src fc00::/7       	# RFC 4193 local private network range
+acl localnet src fe80::/10      	# RFC 4291 link-local (directly plugged) machines
+
+acl SSL_ports port 443
+acl Safe_ports port 80		# http
+acl Safe_ports port 21		# ftp
+acl Safe_ports port 443		# https
+acl Safe_ports port 70		# gopher
+acl Safe_ports port 210		# wais
+acl Safe_ports port 1025-65535	# unregistered ports
+acl Safe_ports port 280		# http-mgmt
+acl Safe_ports port 488		# gss-http
+acl Safe_ports port 591		# filemaker
+acl Safe_ports port 777		# multiling http
+
+http_access allow all
+
+http_access allow CONNECT all
+
+# http_access allow localhost manager
+# http_access deny manager
+
+# http_access allow localhost
+
+
+# http_access deny to_localhost
+
+# http_access deny to_linklocal
+
+include /etc/squid/conf.d/*.conf
+
+
+#http_access deny all
+
+http_port 3128
+
+coredump_dir /var/spool/squid
+
+refresh_pattern ^ftp:		1440	20%	10080
+refresh_pattern -i (/cgi-bin/|\?) 0	0%	0
+refresh_pattern \/(Packages|Sources)(|\.bz2|\.gz|\.xz)$ 0 0% 0 refresh-ims
+refresh_pattern \/Release(|\.gpg)$ 0 0% 0 refresh-ims
+refresh_pattern \/InRelease$ 0 0% 0 refresh-ims
+refresh_pattern \/(Translation-.*)(|\.bz2|\.gz|\.xz)$ 0 0% 0 refresh-ims
+refresh_pattern .		0	20%	4320
+
diff --git a/examples/upstream-proxy.cfg b/examples/upstream-proxy.cfg
new file mode 100644
index 0000000000..2648e3abdc
--- /dev/null
+++ b/examples/upstream-proxy.cfg
@@ -0,0 +1,23 @@
+# Test setup.
+# shell1: curl -vk --connect-to www.test1.com:4433:127.0.0.1:8080 -H "Host: www.test1.com" https://www.test1.com:4433
+# shell2: ./haproxy -d -f examples/upstream-proxy.cfg
+# shell3: sudo podman run --rm -it --name squid -e TZ=UTC -p 3128:3128 --network host ubuntu/squid
+# shell4: openssl s_server -trace -www -bugs -debug -cert reg-tests/ssl/common.pem
+
+global
+	log stdout format raw daemon debug
+	stats timeout 30s
+
+listen proxy-forward-8080
+	bind 0.0.0.0:8080
+	mode tcp
+    log global
+	option tcplog
+	maxconn 2000
+	timeout connect  5s
+	timeout client  50s
+	timeout server  50s
+
+    # dig +short php.net
+    # 185.85.0.29
+	server https_Via_Proxy1 www.test1.com:4433 upstream-proxy-tunnel 127.0.0.1:3128 sni str(www.test1.com) init-addr 127.0.0.1
diff --git a/include/haproxy/connection-t.h b/include/haproxy/connection-t.h
index 660c7bc7ba..1a24ae1ac1 100644
--- a/include/haproxy/connection-t.h
+++ b/include/haproxy/connection-t.h
@@ -251,6 +251,7 @@ enum {
 	CO_ER_REVERSE,           /* Error during reverse connect */
 
 	CO_ER_PROXY_CONNECT_SEND, /* Upstream http proxy write error during handshake */
+	CO_ER_PROXY_CONNECT_RECV, /* Upstream http proxy read error during handshake */
 };
 
 /* error return codes for accept_conn() */
diff --git a/include/haproxy/connection.h b/include/haproxy/connection.h
index 3541000f38..31218a52c7 100644
--- a/include/haproxy/connection.h
+++ b/include/haproxy/connection.h
@@ -76,6 +76,7 @@ int conn_recv_socks4_proxy_response(struct connection *conn);
 
 /* upstream http proxy handshake */
 int conn_send_upstream_proxy_tunnel_request(struct connection *conn);
+int conn_send_upstream_proxy_tunnel_request(struct connection *conn);
 
 /* If we delayed the mux creation because we were waiting for the handshake, do it now */
 int conn_create_mux(struct connection *conn);
diff --git a/src/connection.c b/src/connection.c
index 41c7594106..cd17d28db4 100644
--- a/src/connection.c
+++ b/src/connection.c
@@ -758,6 +758,8 @@ const char *conn_err_code_str(struct connection *c)
 	case CO_ERR_SSL_FATAL:     return "SSL fatal error";
 
 	case CO_ER_REVERSE:        return "Reverse connect failure";
+
+	case CO_ER_PROXY_CONNECT_SEND: return "Upstream http proxy write error during handshake";
 	}
 	return NULL;
 }
@@ -1784,12 +1786,13 @@ int conn_recv_socks4_proxy_response(struct connection *conn)
 }
 
 /* TODO: use *TRACE* features from HAProxy instead of DPRINTF */
-int conn_send_proxy_tunnel_request(struct connection *conn) {
+int conn_send_upstream_proxy_tunnel_request(struct connection *conn) {
 
-	char addr[256];
-	int getaddr;
 	char proxy_connect[512]; /* fqdn + port (connect string) + headers */
 	int connect_length;
+	struct server *srv = NULL;
+
+	DPRINTF(stderr, "HTTP TUNNEL SEND start\n");
 
 	if (!conn_ctrl_ready(conn))
 		goto out_error;
@@ -1797,17 +1800,19 @@ int conn_send_proxy_tunnel_request(struct connection *conn) {
 	if (!conn_get_dst(conn))
 		goto out_error;
 
-	getaddr = addr_to_str(conn->dst, addr, sizeof(addr));
+	/* srv must be set */
+	srv = objt_server(conn->target);
+	BUG_ON(!srv);
 
-	/* 0  => the address family is not supported
-	 * -1 => is returned upon error
-	*/
-	if(getaddr == 0 || getaddr == -1 )
-		goto out_error;	
+	DPRINTF(stderr,"hostname: %s\n", srv->hostname);
 
-	connect_length = snprintf(proxy_connect, sizeof(proxy_connect), "CONNECT %s:%u HTTP/1.1\r\n\r\n",
-							  addr,
-							  ntohs(get_net_port(conn->dst)));
+	connect_length = snprintf(proxy_connect, sizeof(proxy_connect), 
+							"CONNECT %s:%u HTTP/1.1\r\n\r\n",
+							srv->hostname,
+							ntohs(get_net_port(conn->dst)));
+
+	DPRINTF(stderr,"connect_length :%d: proxy_connect :%s:\n", connect_length, proxy_connect);
+	DPRINTF(stderr,"send_proxy_ofs: %d\n", conn->send_proxy_ofs);
 
 	if (conn->send_proxy_ofs > 0) {
 		/*
@@ -1828,10 +1833,13 @@ int conn_send_proxy_tunnel_request(struct connection *conn) {
 				-conn->send_proxy_ofs,
 				(conn->subs && conn->subs->events & SUB_RETRY_SEND) ? CO_SFL_MSG_MORE : 0);
 
-		DPRINTF(stderr, "HTTP TUNNEL HS FD[%04X]: Before send remain is [%d], sent [%d]\n",
-				conn->handle.fd, -conn->send_proxy_ofs, ret);
+		DPRINTF(stderr, "HTTP TUNNEL send_proxy_ofs FD[%04X]: Before send remain is [%d], sent [%d]\n",
+				conn->handle.fd, conn->send_proxy_ofs, ret);
 
 		if (ret < 0) {
+			DPRINTF(stderr, "HTTP TUNNEL FD[%04X]: Before send remain is [%d], sent [%d]\n",
+				conn->handle.fd, conn->send_proxy_ofs, ret);
+
 			goto out_error;
 		}
 
@@ -1859,17 +1867,73 @@ int conn_send_proxy_tunnel_request(struct connection *conn) {
 		 */
 		conn->send_proxy_ofs = 1;
 	}
+
+	DPRINTF(stderr, "HTTP TUNNEL SEND end\n");
 	return 1;
 
-	out_error:
+ 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_PROXY_CONNECT_SEND;
 	}
+	DPRINTF(stderr, "HTTP TUNNEL SEND end: out_error\n");
+	return 0;
+
+ out_wait:
+	DPRINTF(stderr, "HTTP TUNNEL SEND end: out_wait\n");
+	return 0;
+}
+
+
+int conn_recv_upstream_proxy_tunnel_response(struct connection *conn)
+{
+	int ret;
+
+	DPRINTF(stderr, "HTTP TUNNEL RECV start\n");
+
+	if (!conn_ctrl_ready(conn))
+		goto fail;
+
+	BUG_ON(conn->flags & CO_FL_FDLESS);
+
+	if (!fd_recv_ready(conn->handle.fd))
+		goto not_ready;
+
+	while (1) {
+		ret = recv(conn->handle.fd, trash.area, trash.size, 0);
+		if (ret < 0) {
+			if (errno == EINTR)
+				continue;
+			if (errno == EAGAIN || errno == EWOULDBLOCK) {
+				fd_cant_recv(conn->handle.fd);
+				goto not_ready;
+			}
+			goto recv_abort;
+		}
+		trash.data = ret;
+		break;
+	}
+
+	DPRINTF(stderr, "HTTP TUNNEL RECV end\n");
+	conn->flags &= ~CO_FL_UPSTREAM_PROXY_TUNNEL_RECV;
+	return 1;
+
+ not_ready:
+	DPRINTF(stderr, "HTTP TUNNEL RECV not_ready out\n");
 	return 0;
 
-	out_wait:
+ recv_abort:
+	DPRINTF(stderr, "HTTP TUNNEL RECV recv_abort out\n");
+	if (conn->err_code == CO_ER_NONE) {
+		conn->err_code = CO_ER_PROXY_CONNECT_RECV;
+	}
+	conn->flags |= (CO_FL_SOCK_RD_SH | CO_FL_SOCK_WR_SH);
+	goto fail;
+
+ fail:
+ 	DPRINTF(stderr, "HTTP TUNNEL RECV fail out\n");
+	conn->flags |= CO_FL_ERROR;
 	return 0;
 }
 
diff --git a/src/xprt_handshake.c b/src/xprt_handshake.c
index 8e14962bdb..1bc2e0b358 100644
--- a/src/xprt_handshake.c
+++ b/src/xprt_handshake.c
@@ -57,13 +57,22 @@ struct task *xprt_handshake_io_cb(struct task *t, void *bctx, unsigned int state
 		}
 
 	/* TODO: check flags to send optional headers like keep alive host etc */
-	if (conn->flags & CO_FL_UPSTREAM_PROXY_TUNNEL) {
-		if (!conn_send_proxy_tunnel_request(conn)) {
+	if (conn->flags & CO_FL_UPSTREAM_PROXY_TUNNEL_SEND) {
+		if (!conn_send_upstream_proxy_tunnel_request(conn)) {
+			ctx->xprt->subscribe(conn, ctx->xprt_ctx, SUB_RETRY_SEND,
+								 &ctx->wait_event);
+
+			goto out;
+		}
+	}
+
+	/* TODO: check flags to send optional headers like keep alive host etc */
+	if (conn->flags & CO_FL_UPSTREAM_PROXY_TUNNEL_RECV) {
+		if (!conn_recv_upstream_proxy_tunnel_response(conn)) {
 			ctx->xprt->subscribe(conn, ctx->xprt_ctx, SUB_RETRY_SEND,
 								 &ctx->wait_event);
 
 			goto out;
-		} else {
 		}
 	}
 
-- 
2.34.1

From bf4e7c44ed939a2a9e119ca9b13b46efe9d43ab9 Mon Sep 17 00:00:00 2001
From: Aleksandar Lazic <al-hapr...@none.at>
Date: Thu, 23 May 2024 23:52:58 +0200
Subject: [PATCH 1/4] FEATURE/MAJOR: Add upstream-proxy-tunnel feature

This commit makes it possible for HAProxy to reach
target server via a upstream http proxy.

This patch is based on the work of @brentcetinich
and refer to gh #1542
---
 include/haproxy/connection-t.h |  14 +++-
 include/haproxy/connection.h   |   3 +
 include/haproxy/server-t.h     |   8 +-
 include/haproxy/tcpcheck-t.h   |   1 +
 src/backend.c                  |   5 ++
 src/connection.c               |  90 +++++++++++++++++++++
 src/proto_quic.c               |   4 +
 src/proto_tcp.c                |   2 +
 src/server.c                   | 138 ++++++++++++++++++++-------------
 src/sock.c                     |   3 +
 src/tcpcheck.c                 |   3 +
 src/xprt_handshake.c           |  11 +++
 12 files changed, 225 insertions(+), 57 deletions(-)

diff --git a/include/haproxy/connection-t.h b/include/haproxy/connection-t.h
index 6ee0940be4..660c7bc7ba 100644
--- a/include/haproxy/connection-t.h
+++ b/include/haproxy/connection-t.h
@@ -132,8 +132,12 @@ enum {
 	CO_FL_ACCEPT_PROXY  = 0x02000000,  /* receive a valid PROXY protocol header */
 	CO_FL_ACCEPT_CIP    = 0x04000000,  /* receive a valid NetScaler Client IP header */
 
+	/*  STOLEN unused : 0x00000040, 0x00000080 */
+	CO_FL_UPSTREAM_PROXY_TUNNEL_SEND	= 0x00000040,  /* handshaking with upstream http proxy, going to send the handshake */
+	CO_FL_UPSTREAM_PROXY_TUNNEL_RECV = 0x00000080,  /* handshaking with upstream http proxy, going to check if handshake succeed */
+
 	/* below we have all handshake flags grouped into one */
-	CO_FL_HANDSHAKE     = CO_FL_SEND_PROXY | CO_FL_ACCEPT_PROXY | CO_FL_ACCEPT_CIP | CO_FL_SOCKS4_SEND | CO_FL_SOCKS4_RECV,
+	CO_FL_HANDSHAKE     = CO_FL_SEND_PROXY | CO_FL_ACCEPT_PROXY | CO_FL_ACCEPT_CIP | CO_FL_SOCKS4_SEND | CO_FL_SOCKS4_RECV | CO_FL_UPSTREAM_PROXY_TUNNEL_SEND,
 	CO_FL_WAIT_XPRT     = CO_FL_WAIT_L4_CONN | CO_FL_HANDSHAKE | CO_FL_WAIT_L6_CONN,
 
 	CO_FL_SSL_WAIT_HS   = 0x08000000,  /* wait for an SSL handshake to complete */
@@ -155,6 +159,10 @@ enum {
 
 	/* below we have all SOCKS handshake flags grouped into one */
 	CO_FL_SOCKS4        = CO_FL_SOCKS4_SEND | CO_FL_SOCKS4_RECV,
+
+	/* below we have all upstream http proxy tunnel handshake flags grouped into one */
+	CO_FL_UPSTREAM_PROXY_TUNNEL        = CO_FL_UPSTREAM_PROXY_TUNNEL_SEND | CO_FL_UPSTREAM_PROXY_TUNNEL_RECV,
+
 };
 
 /* This function is used to report flags in debugging tools. Please reflect
@@ -241,6 +249,8 @@ enum {
 	CO_ERR_SSL_FATAL,        /* SSL fatal error during a SSL_read or SSL_write */
 
 	CO_ER_REVERSE,           /* Error during reverse connect */
+
+	CO_ER_PROXY_CONNECT_SEND, /* Upstream http proxy write error during handshake */
 };
 
 /* error return codes for accept_conn() */
@@ -526,7 +536,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 (reused for SOCKS4) */
+	signed short send_proxy_ofs;  /* <0 = offset to (re)send from the end, >0 = send all (reused for SOCKS4 and upstream http proxy) */
 	unsigned int flags;           /* CO_FL_* */
 	const struct protocol *ctrl;  /* operations at the socket layer */
 	const struct xprt_ops *xprt;  /* operations at the transport layer */
diff --git a/include/haproxy/connection.h b/include/haproxy/connection.h
index 91ddcd6a53..3541000f38 100644
--- a/include/haproxy/connection.h
+++ b/include/haproxy/connection.h
@@ -74,6 +74,9 @@ int conn_ctrl_drain(struct connection *conn);
 int conn_send_socks4_proxy_request(struct connection *conn);
 int conn_recv_socks4_proxy_response(struct connection *conn);
 
+/* upstream http proxy handshake */
+int conn_send_upstream_proxy_tunnel_request(struct connection *conn);
+
 /* If we delayed the mux creation because we were waiting for the handshake, do it now */
 int conn_create_mux(struct connection *conn);
 int conn_notify_mux(struct connection *conn, int old_flags, int forced_wake);
diff --git a/include/haproxy/server-t.h b/include/haproxy/server-t.h
index d59ca40ef4..8b21e32229 100644
--- a/include/haproxy/server-t.h
+++ b/include/haproxy/server-t.h
@@ -154,6 +154,7 @@ enum srv_initaddr {
 #define SRV_F_NON_PURGEABLE 0x2000       /* this server cannot be removed at runtime */
 #define SRV_F_DEFSRV_USE_SSL 0x4000      /* default-server uses SSL */
 #define SRV_F_DELETED 0x8000             /* srv is deleted but not yet purged */
+#define SRV_F_UPSTREAM_PROXY_TUNNEL 0x10000  /* this server uses a upstream proxy tunnel with CONNECT method */
 
 /* configured server options for send-proxy (server->pp_opts) */
 #define SRV_PP_V1               0x0001   /* proxy protocol version 1 */
@@ -467,9 +468,10 @@ struct server {
 	struct guid_node guid;			/* GUID global tree node */
 
 	/* warning, these structs are huge, keep them at the bottom */
-	struct conn_src conn_src;               /* connection source settings */
-	struct sockaddr_storage addr;           /* the address to connect to, doesn't include the port */
-	struct sockaddr_storage socks4_addr;	/* the address of the SOCKS4 Proxy, including the port */
+	struct conn_src conn_src;                           /* connection source settings */
+	struct sockaddr_storage addr;                       /* the address to connect to, doesn't include the port */
+	struct sockaddr_storage socks4_addr;                /* the address of the SOCKS4 Proxy, including the port */
+	struct sockaddr_storage upstream_proxy_tunnel_addr; /* the address of the upstream PROXY for proxy tunnel, including the port */
 
 	EXTRA_COUNTERS(extra_counters);
 };
diff --git a/include/haproxy/tcpcheck-t.h b/include/haproxy/tcpcheck-t.h
index 22310eee05..ee861aaea0 100644
--- a/include/haproxy/tcpcheck-t.h
+++ b/include/haproxy/tcpcheck-t.h
@@ -36,6 +36,7 @@
 #define TCPCHK_OPT_IMPLICIT        0x0010  /* Implicit connect */
 #define TCPCHK_OPT_SOCKS4          0x0020  /* check the connection via socks4 proxy */
 #define TCPCHK_OPT_HAS_DATA        0x0040  /* data should be sent after connection */
+/* TODO: add upstream HTTP PROXY Check */
 
 enum tcpcheck_send_type {
 	TCPCHK_SEND_UNDEF = 0,  /* Send is not parsed. */
diff --git a/src/backend.c b/src/backend.c
index e5444988c2..69460583e1 100644
--- a/src/backend.c
+++ b/src/backend.c
@@ -1725,6 +1725,11 @@ int connect_server(struct stream *s)
 			srv_conn->flags |= CO_FL_SOCKS4;
 		}
 
+		if (srv && (srv->flags & SRV_F_UPSTREAM_PROXY_TUNNEL)) {
+			srv_conn->send_proxy_ofs = 1; /* TODO: maybe dont reuse this? */
+			srv_conn->flags |= CO_FL_UPSTREAM_PROXY_TUNNEL;
+		}
+
 #if defined(USE_OPENSSL) && defined(TLSEXT_TYPE_application_layer_protocol_negotiation)
 		/* if websocket stream, try to update connection ALPN. */
 		if (unlikely(s->flags & SF_WEBSOCKET) &&
diff --git a/src/connection.c b/src/connection.c
index 97af8f65e5..41c7594106 100644
--- a/src/connection.c
+++ b/src/connection.c
@@ -1783,6 +1783,96 @@ int conn_recv_socks4_proxy_response(struct connection *conn)
 	return 0;
 }
 
+/* TODO: use *TRACE* features from HAProxy instead of DPRINTF */
+int conn_send_proxy_tunnel_request(struct connection *conn) {
+
+	char addr[256];
+	int getaddr;
+	char proxy_connect[512]; /* fqdn + port (connect string) + headers */
+	int connect_length;
+
+	if (!conn_ctrl_ready(conn))
+		goto out_error;
+
+	if (!conn_get_dst(conn))
+		goto out_error;
+
+	getaddr = addr_to_str(conn->dst, addr, sizeof(addr));
+
+	/* 0  => the address family is not supported
+	 * -1 => is returned upon error
+	*/
+	if(getaddr == 0 || getaddr == -1 )
+		goto out_error;	
+
+	connect_length = snprintf(proxy_connect, sizeof(proxy_connect), "CONNECT %s:%u HTTP/1.1\r\n\r\n",
+							  addr,
+							  ntohs(get_net_port(conn->dst)));
+
+	if (conn->send_proxy_ofs > 0) {
+		/*
+		 * This is the first call to send the request
+		 */
+		conn->send_proxy_ofs = -(int) connect_length;
+	}
+
+	if (conn->send_proxy_ofs < 0) {
+		int ret = 0;
+
+		/* we are sending the htt-connect req_line here. If the data layer
+		 * has a pending write, we'll also set MSG_MORE.
+		 */
+		ret = conn_ctrl_send(
+				conn,
+				((char *) (proxy_connect)),
+				-conn->send_proxy_ofs,
+				(conn->subs && conn->subs->events & SUB_RETRY_SEND) ? CO_SFL_MSG_MORE : 0);
+
+		DPRINTF(stderr, "HTTP TUNNEL 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_UPSTREAM_PROXY_TUNNEL_SEND;
+
+
+	/* The connection is ready now, simply return and let the connection
+	 * handler notify upper layers if needed.
+	 */
+	conn->flags &= ~CO_FL_WAIT_L4_CONN;
+
+	/* This is for the proxy header so don't change */
+	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_PROXY_CONNECT_SEND;
+	}
+	return 0;
+
+	out_wait:
+	return 0;
+}
+
 /* registers proto mux list <list>. Modifies the list element! */
 void register_mux_proto(struct mux_proto_list *list)
 {
diff --git a/src/proto_quic.c b/src/proto_quic.c
index 93a24af4b4..cbf8c2c313 100644
--- a/src/proto_quic.c
+++ b/src/proto_quic.c
@@ -415,6 +415,10 @@ int quic_connect_server(struct connection *conn, int flags)
                 setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &global.tune.server_rcvbuf, sizeof(global.tune.server_rcvbuf));
 
 	addr = (conn->flags & CO_FL_SOCKS4) ? &srv->socks4_addr : conn->dst;
+
+	/* TODO: Can an upstream proxy be used for quic? */
+	addr = (conn->flags & CO_FL_UPSTREAM_PROXY_TUNNEL) ? &srv->upstream_proxy_tunnel_addr : conn->dst;
+
 	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 */
diff --git a/src/proto_tcp.c b/src/proto_tcp.c
index 63be775083..10bdb9b0df 100644
--- a/src/proto_tcp.c
+++ b/src/proto_tcp.c
@@ -453,6 +453,8 @@ int tcp_connect_server(struct connection *conn, int flags)
                 setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &global.tune.server_rcvbuf, sizeof(global.tune.server_rcvbuf));
 
 	addr = (conn->flags & CO_FL_SOCKS4) ? &srv->socks4_addr : conn->dst;
+	addr = (conn->flags & CO_FL_UPSTREAM_PROXY_TUNNEL) ? &srv->upstream_proxy_tunnel_addr : conn->dst;
+
 	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 */
diff --git a/src/server.c b/src/server.c
index 48984fe5c9..df8a2f8f9d 100644
--- a/src/server.c
+++ b/src/server.c
@@ -1915,6 +1915,38 @@ static int srv_parse_socks4(char **args, int *cur_arg,
 	return ERR_ALERT | ERR_FATAL;
 }
 
+/* Parse the "upstream-proxy-tunnel" server keyword */
+static int srv_parse_upstream_proxy_tunnel(char **args, int *cur_arg,
+								  struct proxy *curproxy, struct server *newsrv, char **err) {
+	char *errmsg;
+	int port_low, port_high;
+	struct sockaddr_storage *sk;
+
+	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, NULL, NULL, NULL,
+	                  &errmsg, NULL, NULL,
+	                  PA_O_RESOLVE | PA_O_PORT_OK | PA_O_PORT_MAND | PA_O_STREAM | PA_O_CONNECT);
+	if (!sk) {
+		memprintf(err, "'%s %s' : %s\n", args[*cur_arg], args[*cur_arg + 1], errmsg);
+		goto err;
+	}
+
+	newsrv->flags |= SRV_F_UPSTREAM_PROXY_TUNNEL;
+	newsrv->upstream_proxy_tunnel_addr = *sk;
+
+	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)
@@ -2263,54 +2295,55 @@ void srv_compute_all_admin_states(struct proxy *px)
  * Note: -1 as ->skip value means that the number of arguments are variable.
  */
 static struct srv_kw_list srv_kws = { "ALL", { }, {
-	{ "backup",               srv_parse_backup,               0,  1,  1 }, /* Flag as backup server */
-	{ "cookie",               srv_parse_cookie,               1,  1,  1 }, /* Assign a cookie to the server */
-	{ "disabled",             srv_parse_disabled,             0,  1,  1 }, /* Start the server in 'disabled' state */
-	{ "enabled",              srv_parse_enabled,              0,  1,  0 }, /* Start the server in 'enabled' state */
-	{ "error-limit",          srv_parse_error_limit,          1,  1,  1 }, /* Configure the consecutive count of check failures to consider a server on error */
-	{ "guid",                 srv_parse_guid,                 1,  0,  1 }, /* Set global unique ID of the server */
-	{ "ws",                   srv_parse_ws,                   1,  1,  1 }, /* websocket protocol */
-	{ "hash-key",             srv_parse_hash_key,             1,  1,  1 }, /* Configure how chash keys are computed */
-	{ "id",                   srv_parse_id,                   1,  0,  1 }, /* set id# of server */
-	{ "init-addr",            srv_parse_init_addr,            1,  1,  0 }, /* */
-	{ "log-bufsize",          srv_parse_log_bufsize,          1,  1,  0 }, /* Set the ring bufsize for log server (only for log backends) */
-	{ "log-proto",            srv_parse_log_proto,            1,  1,  0 }, /* Set the protocol for event messages, only relevant in a log or ring section */
-	{ "maxconn",              srv_parse_maxconn,              1,  1,  1 }, /* Set the max number of concurrent connection */
-	{ "maxqueue",             srv_parse_maxqueue,             1,  1,  1 }, /* Set the max number of connection to put in queue */
-	{ "max-reuse",            srv_parse_max_reuse,            1,  1,  0 }, /* Set the max number of requests on a connection, -1 means unlimited */
-	{ "minconn",              srv_parse_minconn,              1,  1,  1 }, /* Enable a dynamic maxconn limit */
-	{ "namespace",            srv_parse_namespace,            1,  1,  0 }, /* Namespace the server socket belongs to (if supported) */
-	{ "no-backup",            srv_parse_no_backup,            0,  1,  1 }, /* Flag as non-backup server */
-	{ "no-send-proxy",        srv_parse_no_send_proxy,        0,  1,  1 }, /* Disable use of PROXY V1 protocol */
-	{ "no-send-proxy-v2",     srv_parse_no_send_proxy_v2,     0,  1,  1 }, /* Disable use of PROXY V2 protocol */
-	{ "no-tfo",               srv_parse_no_tfo,               0,  1,  1 }, /* Disable use of TCP Fast Open */
-	{ "non-stick",            srv_parse_non_stick,            0,  1,  0 }, /* Disable stick-table persistence */
-	{ "observe",              srv_parse_observe,              1,  1,  1 }, /* Enables health adjusting based on observing communication with the server */
-	{ "on-error",             srv_parse_on_error,             1,  1,  1 }, /* Configure the action on check failure */
-	{ "on-marked-down",       srv_parse_on_marked_down,       1,  1,  1 }, /* Configure the action when a server is marked down */
-	{ "on-marked-up",         srv_parse_on_marked_up,         1,  1,  1 }, /* Configure the action when a server is marked up */
-	{ "pool-low-conn",        srv_parse_pool_low_conn,        1,  1,  1 }, /* Set the min number of orphan idle connecbefore being allowed to pick from other threads */
-	{ "pool-max-conn",        srv_parse_pool_max_conn,        1,  1,  1 }, /* Set the max number of orphan idle connections, -1 means unlimited */
-	{ "pool-purge-delay",     srv_parse_pool_purge_delay,     1,  1,  1 }, /* Set the time before we destroy orphan idle connections, defaults to 1s */
-	{ "proto",                srv_parse_proto,                1,  1,  1 }, /* Set the proto to use for all outgoing connections */
-	{ "proxy-v2-options",     srv_parse_proxy_v2_options,     1,  1,  1 }, /* options for send-proxy-v2 */
-	{ "redir",                srv_parse_redir,                1,  1,  0 }, /* Enable redirection mode */
-	{ "resolve-net",          srv_parse_resolve_net,          1,  1,  0 }, /* Set the preferred network range for name resolution */
-	{ "resolve-opts",         srv_parse_resolve_opts,         1,  1,  0 }, /* Set options for name resolution */
-	{ "resolve-prefer",       srv_parse_resolve_prefer,       1,  1,  0 }, /* Set the preferred family for name resolution */
-	{ "resolvers",            srv_parse_resolvers,            1,  1,  0 }, /* Configure the resolver to use for name resolution */
-	{ "send-proxy",           srv_parse_send_proxy,           0,  1,  1 }, /* Enforce use of PROXY V1 protocol */
-	{ "send-proxy-v2",        srv_parse_send_proxy_v2,        0,  1,  1 }, /* Enforce use of PROXY V2 protocol */
-	{ "set-proxy-v2-tlv-fmt", srv_parse_set_proxy_v2_tlv_fmt, 0,  1,  1 }, /* Set TLV of PROXY V2 protocol */
-	{ "shard",                srv_parse_shard,                1,  1,  1 }, /* Server shard (only in peers protocol context) */
-	{ "slowstart",            srv_parse_slowstart,            1,  1,  1 }, /* Set the warm-up timer for a previously failed server */
-	{ "source",               srv_parse_source,              -1,  1,  1 }, /* Set the source address to be used to connect to the server */
-	{ "stick",                srv_parse_stick,                0,  1,  0 }, /* Enable stick-table persistence */
-	{ "tfo",                  srv_parse_tfo,                  0,  1,  1 }, /* enable TCP Fast Open of server */
-	{ "track",                srv_parse_track,                1,  1,  1 }, /* Set the current state of the server, tracking another one */
-	{ "socks4",               srv_parse_socks4,               1,  1,  0 }, /* Set the socks4 proxy of the server*/
-	{ "usesrc",               srv_parse_usesrc,               0,  1,  1 }, /* safe-guard against usesrc without preceding <source> keyword */
-	{ "weight",               srv_parse_weight,               1,  1,  1 }, /* Set the load-balancing weight */
+	{ "backup",                srv_parse_backup,                0,  1,  1 }, /* Flag as backup server */
+	{ "cookie",                srv_parse_cookie,                1,  1,  1 }, /* Assign a cookie to the server */
+	{ "disabled",              srv_parse_disabled,              0,  1,  1 }, /* Start the server in 'disabled' state */
+	{ "enabled",               srv_parse_enabled,               0,  1,  0 }, /* Start the server in 'enabled' state */
+	{ "error-limit",           srv_parse_error_limit,           1,  1,  1 }, /* Configure the consecutive count of check failures to consider a server on error */
+	{ "guid",                  srv_parse_guid,                  1,  0,  1 }, /* Set global unique ID of the server */
+	{ "ws",                    srv_parse_ws,                    1,  1,  1 }, /* websocket protocol */
+	{ "hash-key",              srv_parse_hash_key,              1,  1,  1 }, /* Configure how chash keys are computed */
+	{ "id",                    srv_parse_id,                    1,  0,  1 }, /* set id# of server */
+	{ "init-addr",             srv_parse_init_addr,             1,  1,  0 }, /* */
+	{ "log-bufsize",           srv_parse_log_bufsize,           1,  1,  0 }, /* Set the ring bufsize for log server (only for log backends) */
+	{ "log-proto",             srv_parse_log_proto,             1,  1,  0 }, /* Set the protocol for event messages, only relevant in a log or ring section */
+	{ "maxconn",               srv_parse_maxconn,               1,  1,  1 }, /* Set the max number of concurrent connection */
+	{ "maxqueue",              srv_parse_maxqueue,              1,  1,  1 }, /* Set the max number of connection to put in queue */
+	{ "max-reuse",             srv_parse_max_reuse,             1,  1,  0 }, /* Set the max number of requests on a connection, -1 means unlimited */
+	{ "minconn",               srv_parse_minconn,               1,  1,  1 }, /* Enable a dynamic maxconn limit */
+	{ "namespace",             srv_parse_namespace,             1,  1,  0 }, /* Namespace the server socket belongs to (if supported) */
+	{ "no-backup",             srv_parse_no_backup,             0,  1,  1 }, /* Flag as non-backup server */
+	{ "no-send-proxy",         srv_parse_no_send_proxy,         0,  1,  1 }, /* Disable use of PROXY V1 protocol */
+	{ "no-send-proxy-v2",      srv_parse_no_send_proxy_v2,      0,  1,  1 }, /* Disable use of PROXY V2 protocol */
+	{ "no-tfo",                srv_parse_no_tfo,                0,  1,  1 }, /* Disable use of TCP Fast Open */
+	{ "non-stick",             srv_parse_non_stick,             0,  1,  0 }, /* Disable stick-table persistence */
+	{ "observe",               srv_parse_observe,               1,  1,  1 }, /* Enables health adjusting based on observing communication with the server */
+	{ "on-error",              srv_parse_on_error,              1,  1,  1 }, /* Configure the action on check failure */
+	{ "on-marked-down",        srv_parse_on_marked_down,        1,  1,  1 }, /* Configure the action when a server is marked down */
+	{ "on-marked-up",          srv_parse_on_marked_up,          1,  1,  1 }, /* Configure the action when a server is marked up */
+	{ "pool-low-conn",         srv_parse_pool_low_conn,         1,  1,  1 }, /* Set the min number of orphan idle connecbefore being allowed to pick from other threads */
+	{ "pool-max-conn",         srv_parse_pool_max_conn,         1,  1,  1 }, /* Set the max number of orphan idle connections, -1 means unlimited */
+	{ "pool-purge-delay",      srv_parse_pool_purge_delay,      1,  1,  1 }, /* Set the time before we destroy orphan idle connections, defaults to 1s */
+	{ "proto",                 srv_parse_proto,                 1,  1,  1 }, /* Set the proto to use for all outgoing connections */
+	{ "proxy-v2-options",      srv_parse_proxy_v2_options,      1,  1,  1 }, /* options for send-proxy-v2 */
+	{ "redir",                 srv_parse_redir,                 1,  1,  0 }, /* Enable redirection mode */
+	{ "resolve-net",           srv_parse_resolve_net,           1,  1,  0 }, /* Set the preferred network range for name resolution */
+	{ "resolve-opts",          srv_parse_resolve_opts,          1,  1,  0 }, /* Set options for name resolution */
+	{ "resolve-prefer",        srv_parse_resolve_prefer,        1,  1,  0 }, /* Set the preferred family for name resolution */
+	{ "resolvers",             srv_parse_resolvers,             1,  1,  0 }, /* Configure the resolver to use for name resolution */
+	{ "send-proxy",            srv_parse_send_proxy,            0,  1,  1 }, /* Enforce use of PROXY V1 protocol */
+	{ "send-proxy-v2",         srv_parse_send_proxy_v2,         0,  1,  1 }, /* Enforce use of PROXY V2 protocol */
+	{ "set-proxy-v2-tlv-fmt",  srv_parse_set_proxy_v2_tlv_fmt,  0,  1,  1 }, /* Set TLV of PROXY V2 protocol */
+	{ "shard",                 srv_parse_shard,                 1,  1,  1 }, /* Server shard (only in peers protocol context) */
+	{ "slowstart",             srv_parse_slowstart,             1,  1,  1 }, /* Set the warm-up timer for a previously failed server */
+	{ "source",                srv_parse_source,               -1,  1,  1 }, /* Set the source address to be used to connect to the server */
+	{ "stick",                 srv_parse_stick,                 0,  1,  0 }, /* Enable stick-table persistence */
+	{ "tfo",                   srv_parse_tfo,                   0,  1,  1 }, /* enable TCP Fast Open of server */
+	{ "track",                 srv_parse_track,                 1,  1,  1 }, /* Set the current state of the server, tracking another one */
+	{ "upstream-proxy-tunnel", srv_parse_upstream_proxy_tunnel, 1,  1,  0 }, /* Set the upstream proxy tunnel backend of the server*/
+	{ "socks4",                srv_parse_socks4,                1,  1,  0 }, /* Set the socks4 proxy of the server*/
+	{ "usesrc",                srv_parse_usesrc,                0,  1,  1 }, /* safe-guard against usesrc without preceding <source> keyword */
+	{ "weight",                srv_parse_weight,                1,  1,  1 }, /* Set the load-balancing weight */
 	{ NULL, NULL, 0 },
 }};
 
@@ -2813,10 +2846,11 @@ void srv_settings_cpy(struct server *srv, const struct server *src, int srv_tmpl
 	if (srv_tmpl)
 		srv->srvrq = src->srvrq;
 
-	srv->netns                    = src->netns;
-	srv->check.via_socks4         = src->check.via_socks4;
-	srv->socks4_addr              = src->socks4_addr;
-	srv->log_bufsize              = src->log_bufsize;
+	srv->netns                      = src->netns;
+	srv->check.via_socks4           = src->check.via_socks4;
+	srv->socks4_addr                = src->socks4_addr;
+	srv->log_bufsize                = src->log_bufsize;
+	srv->upstream_proxy_tunnel_addr = src->upstream_proxy_tunnel_addr;
 
 	LIST_INIT(&srv->pp_tlvs);
 
diff --git a/src/sock.c b/src/sock.c
index fba92bd7cf..8955af24dd 100644
--- a/src/sock.c
+++ b/src/sock.c
@@ -893,6 +893,9 @@ int sock_conn_check(struct connection *conn)
 	if ((conn->flags & CO_FL_SOCKS4) && obj_type(conn->target) == OBJ_TYPE_SERVER)
 		addr = &objt_server(conn->target)->socks4_addr;
 
+	if ((conn->flags & CO_FL_UPSTREAM_PROXY_TUNNEL) && obj_type(conn->target) == OBJ_TYPE_SERVER)
+		addr = &objt_server(conn->target)->upstream_proxy_tunnel_addr;
+
 	if (connect(fd, (const struct sockaddr *)addr, get_addr_len(addr)) == -1) {
 		if (errno == EALREADY || errno == EINPROGRESS)
 			goto wait;
diff --git a/src/tcpcheck.c b/src/tcpcheck.c
index b4f9590866..c98407baaa 100644
--- a/src/tcpcheck.c
+++ b/src/tcpcheck.c
@@ -1164,6 +1164,9 @@ enum tcpcheck_eval_ret tcpcheck_eval_connect(struct check *check, struct tcpchec
 		conn->flags |= CO_FL_SOCKS4;
 		TRACE_DEVEL("configure SOCKS4 proxy", CHK_EV_TCPCHK_CONN);
 	}
+
+	/* TODO: Add check via upstream http proxy */
+
 	else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.via_socks4 && (s->flags & SRV_F_SOCKS4_PROXY)) {
 		conn->send_proxy_ofs = 1;
 		conn->flags |= CO_FL_SOCKS4;
diff --git a/src/xprt_handshake.c b/src/xprt_handshake.c
index 33f775087a..8e14962bdb 100644
--- a/src/xprt_handshake.c
+++ b/src/xprt_handshake.c
@@ -56,6 +56,17 @@ struct task *xprt_handshake_io_cb(struct task *t, void *bctx, unsigned int state
 			goto out;
 		}
 
+	/* TODO: check flags to send optional headers like keep alive host etc */
+	if (conn->flags & CO_FL_UPSTREAM_PROXY_TUNNEL) {
+		if (!conn_send_proxy_tunnel_request(conn)) {
+			ctx->xprt->subscribe(conn, ctx->xprt_ctx, SUB_RETRY_SEND,
+								 &ctx->wait_event);
+
+			goto out;
+		} else {
+		}
+	}
+
 	if (conn->flags & CO_FL_ACCEPT_CIP)
 		if (!conn_recv_netscaler_cip(conn, CO_FL_ACCEPT_CIP)) {
 			ctx->xprt->subscribe(conn, ctx->xprt_ctx, SUB_RETRY_RECV,
-- 
2.34.1

Reply via email to