Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Is it possible to relay the request to another proxy? #210

Open
cosmozhang1995 opened this issue Feb 9, 2022 · 2 comments
Open

Is it possible to relay the request to another proxy? #210

cosmozhang1995 opened this issue Feb 9, 2022 · 2 comments

Comments

@cosmozhang1995
Copy link

cosmozhang1995 commented Feb 9, 2022

For example we have a client C, an HTTPS site S, and two nginx proxy servers A and B:

  1. Client C sends HTTPS request to proxy server A.
  2. A proxies the request through another proxy server B.
  3. B proxies the request to the real site server S.

This case may be similar to issue #118, but I think they are different.


I expect the network topo should be like this:

C ------------ CONNECT S -----------> A ------------ CONNECT S -----------> B ---- establish TCP connection ----> S
|                                     |                                     |                                     |
| ------- https client hello -------> | ------- https client hello -------> | ------- https client hello -------> |
|                                     |                                     |                                     |
| <------ https server hello -------- | <------ https server hello -------- | <------ https server hello -------- |
|                                     |                                     |                                     |
|               ......                |               ......                |               ......                |

So I configured like the following:

Proxy A (192.168.130.7 a.proxy.example.com):

upstream proxy_B {
	server 192.168.130.1:8080;
	keepalive 2000;
}

server {
	listen 8080 default_server;
	listen [::]:8080 default_server;

	server_name a.proxy.example.com;

	proxy_connect;
	proxy_connect_allow		all;
	proxy_connect_connect_timeout	10s;
	proxy_connect_read_timeout	10s;
	proxy_connect_send_timeout	10s;
	proxy_connect_address		192.168.130.1:8080;

	location / {
		proxy_pass http://proxy_B;
		proxy_set_header Host $host;
	}
}

Proxy B (192.168.130.1 b.proxy.example.com):

server {
	resolver 8.8.8.8;

	listen 8080 default_server;
	listen [::]:8080 default_server;

	server_name b.proxy.example.com;

	proxy_connect;
	proxy_connect_allow		443 8443;
	proxy_connect_connect_timeout	10s;
	proxy_connect_read_timeout	10s;
	proxy_connect_send_timeout	10s;

	location / {
		proxy_pass $scheme://$host$request_uri;
	}
}

Then test on client C like this:

curl "https://server.example.com/" -x "http://a.proxy.example.com:8080" -vvv

But the request failed:

*   Trying 192.168.130.7:8080...
* TCP_NODELAY set
* Connected to a.proxy.example.com (192.168.130.7) port 8080 (#0)
* allocate connect buffer!
* Establish HTTP proxy tunnel to server.example.com:443
> CONNECT server.example.com:443 HTTP/1.1
> Host: server.example.com:443
> User-Agent: curl/7.68.0
> Proxy-Connection: Keep-Alive
>
< HTTP/1.1 200 Connection Established
< Proxy-agent: nginx
<
* Proxy replied 200 to CONNECT request
* CONNECT phase completed!
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/certs/ca-certificates.crt
  CApath: /etc/ssl/certs
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* CONNECT phase completed!
* CONNECT phase completed!
* error:1408F10B:SSL routines:ssl3_get_record:wrong version number
* Closing connection 0
curl: (35) error:1408F10B:SSL routines:ssl3_get_record:wrong version number

While, directly call server through proxy B is OK:

curl "https://server.example.com/" -x "http://b.proxy.example.com:8080"

It seems like the actually network flow is like this:

C ------------ CONNECT S -----------> A ---- establish TCP connection ----> B ---- establish TCP connection ----> S
|                                     |                                     |                                     |
| ------- https client hello -------> | ------- https client hello -------> |                                     |
|                                     |  (expect plain HTTP request line)   |                                     |
|                                     |                                     |                                     |
| <---- plain HTTP 400 response ----- | <---- plain HTTP 400 response ----- |                                     |
|     (expect https server hello)     |                                     |                                     |

It seems that, proxy A received CONNECT request from C, but only established connection to B. And the following HTTPS client HELLO request sent by C to A were simply forwarded to B. However, B expected a plain HTTP request, so it couldn't recognize the binary HELLO request, and responded a plain HTTP 400 error response to A. And A simply forwarded the response to C. C treated it as an HTTPS server HELLO response and tried to parse the TLS negotiation information from the packet. Obviously it ended up with a failure.

In my opinion, issue #118 is a little different from this case. In that case, squid server (corresponding to B in our case) acts as a real server but not a proxy server. The http content is retrieved from S and decrypted on B, and then re-encrypted and sent through A to C. There is only one proxy server A in fact.


On the other hand, commenting out the proxy_connect_address line is also not the desired solution. As it seems to cause A to try to establish TCP connection with S by itself, and then forward the following packets to S directly. If DNS resolver is not configured, A will be unable to resolve the address of S. And thus unable to establish the connection, which lead to a 502 error.


The question is, is there any way for A to simply forward the CONNECT request to B and leave the connection-establishing job to the later, and to forward the following packets to B and let B forward them to S?

@cosmozhang1995
Copy link
Author

Here is a solution: Use nginx stream module on A, instead of using the http proxy module.

stream {
	server {
		listen 8080;
		proxy_pass b.proxy.example.com:8080;
	}
}

@sorbing
Copy link

sorbing commented Mar 23, 2022

I am also trying to connect: [Client] -> [Nginx forward proxy + Cache] -> [Proxy Server].
Unfortunately stream.server as upstream not support cache ability.
Is it possible to specify: proxy_pass http://external_forward_proxy_upstream;.
Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants