TooTallNate / Java-WebSocket

A barebones WebSocket client and server implementation written in 100% Java.
http://tootallnate.github.io/Java-WebSocket
MIT License
10.47k stars 2.57k forks source link

If Proxy is set, DNS should not be resolved by the client #1335

Open Snurppa opened 1 year ago

Snurppa commented 1 year ago

Describe the bug When client side is using proxy via WebSocketClient.setProxy, the default DnsResolver will force hostname resolution.

But I think when behind a proxy, the resolving of hostnames should be done by the proxy, not by the client, living in a possibly constrained network, which might not have access to public DNS.

We bumped into this in production when a customer was using a proxy and the host running the app had no access to DNS. Establishing the connection would crash in WebSocketClient.connect with java.net.UnknownHostException: public.example.com: Name or service not known.

To Reproduce Steps to reproduce the behavior:

  1. Setup proxy. I used mitmproxy running in host, the JVM app was running in Docker container.
  2. Configure the WebSocketClient to direct comms to that proxy. For me proxy host was host.docker.internal:8080 as I was running JVM inside a container. Also the target server was running in host OS, port 3200.
    • I was using Basic authentication in proxy and had set global java.net.Authenticator with java.net.Authenticator.setDefault so JVM will pass the credentials
  3. Depending on your level of commitment, start something like tcpflow to inspect what is happening, in this case between container and the host: docker run --net="container:<your-container-id-or-name>" byfcz/tcpflow -p -c
  4. Start the WS client
  5. You will see that when Java-WebSocket client issues CONNECT request to the proxy, it has already resolved the target's hostname host.docker.internal to IP address (the host IP from Docker perspective). In my case, host was resolved to 192.168.065.254:
    172.017.000.002.33974-192.168.065.254.08080: CONNECT 192.168.65.254:3200 HTTP/1.1
    User-Agent: Java/18.0.2
    Host: 192.168.65.254:3200              <------------------------------------
    Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
    Proxy-Connection: keep-alive
  6. Now when mitmproxy receives this, it can't reach 192.168.065.254. I had 127.0.0.1 host.docker.internal configured in /etc/hosts, so if it would have let the Host header be untouched, it would have worked. Now after some internal timeout mitmproxy responds with 502 Bad Gateway

Example application to reproduce the issue

Expected behavior

I was thinking, when a proxy is set, the client should not do DNS resolution by default?

We were able to patch the issue by calling setDnsResolver(null). After that, the Host header was left intact and the Proxy would take it and resolve it.

Should this null DnsResolver be default in the library, maybe done by the setProxy?

Debug log

Environment(please complete the following information):

Additional context JVM app running in container, using PROXY_HOST + PROXY_PORT and the java.net.Authenticator.getPasswordAuthentication implemented to return credentials + applied globally with java.net.Authenticator.setDefault. The exchange of the credentials works correctly between WebSocketClient and the Proxy server. Customer network where app was running needs to route everything through proxy, there is no public DNS available.

The issue is just the DNS resolution, either it crashes with UnknownHostException or the Host header might be wrong (in the case of using something like host.docker.internal at least).

PhilipRoman commented 1 year ago

Just to clarify, you can completely work around this by doing setDnsResolver(null) and then it works fine for you?

I just want to understand how high priority this issue is.

Snurppa commented 1 year ago

Just to clarify, you can completely work around this by doing setDnsResolver(null) and then it works fine for you?

I just want to understand how high priority this issue is.

Sorry, that was not clear enough. The answer is yes, setting setDnsResolver(null) is enough to fix the issue.

I guess the question is, should the same be done by default in the setProxy method or not 🤔