This is using Java 8, squid 3.1.2, and the tyrus-standalone-client-1.12.jar
for the websocket implementation.
UPDATE:
It is still not working with Tyrus. However, it appears the problem is not with squid because when I make Chrome use the proxy and connect right here to SO, the connection to wss://qa.stackoverflow.com
goes through the squid proxy and works fine.
I am using the test program given in the accepted answer to javax.websocket client simple example to try to get a squid proxy running.
However, I've changed it to use the websockets.org echo server and so the URI was changed from wss://real.okcoin.cn:10440/websocket/okcoinapi
to wss://echo.websocket.org
.
When I don't use a proxy the test program works fine with wss://echo.websocket.org
and ws://echo.websocket.org
.
When I specify a JVM-wide proxy via -Dhttp.proxyHost
, -Dhttp.proxyPort
, -Dhttps.proxyHost
, and -Dhttps.proxyPort
the ws://
URI works fine but the wss://
one does not.
I have verified (with tcpdump) that traffic is going to the proxy, that the test program is sending a CONNECT
to the proxy, and that the proxy sends back a Connection established
. However, at that point nothing else happens:
# tcpdump -n -l -s 0 -S -X 'host 172.16.99.15'
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes
17:21:45.361023 IP 172.16.99.21.36521 > 172.16.99.15.3128: S 1720321489:1720321489(0) win 14600 <mss 1460,sackOK,timestamp 1135431 0,nop,wscale 5>
0x0000: 4500 003c 5696 4000 4006 c5e0 ac10 6315 E..<V.@[email protected].
0x0010: ac10 630f 8ea9 0c38 668a 05d1 0000 0000 ..c....8f.......
0x0020: a002 3908 1e74 0000 0204 05b4 0402 080a ..9..t..........
0x0030: 0011 5347 0000 0000 0103 0305 ..SG........
17:21:45.361763 IP 172.16.99.15.3128 > 172.16.99.21.36521: S 1626710395:1626710395(0) ack 1720321490 win 14480 <mss 1460,sackOK,timestamp 1127109 1135431,nop,wscale 7>
0x0000: 4500 003c 0000 4000 4006 1c77 ac10 630f E..<..@[email protected].
0x0010: ac10 6315 0c38 8ea9 60f5 a17b 668a 05d2 ..c..8..`..{f...
0x0020: a012 3890 613d 0000 0204 05b4 0402 080a ..8.a=..........
0x0030: 0011 32c5 0011 5347 0103 0307 ..2...SG....
17:21:45.361789 IP 172.16.99.21.36521 > 172.16.99.15.3128: . ack 1626710396 win 457 <nop,nop,timestamp 1135431 1127109>
0x0000: 4500 0034 5697 4000 4006 c5e7 ac10 6315 E..4V.@[email protected].
0x0010: ac10 630f 8ea9 0c38 668a 05d2 60f5 a17c ..c....8f...`..|
0x0020: 8010 01c9 1e6c 0000 0101 080a 0011 5347 .....l........SG
0x0030: 0011 32c5 ..2.
17:21:45.407930 IP 172.16.99.21.36521 > 172.16.99.15.3128: P 1720321490:1720321613(123) ack 1626710396 win 457 <nop,nop,timestamp 1135442 1127109>
0x0000: 4500 00af 5698 4000 4006 c56b ac10 6315 E...V.@[email protected].
0x0010: ac10 630f 8ea9 0c38 668a 05d2 60f5 a17c ..c....8f...`..|
0x0020: 8018 01c9 1ee7 0000 0101 080a 0011 5352 ..............SR
0x0030: 0011 32c5 434f 4e4e 4543 5420 6563 686f ..2.CONNECT.echo
0x0040: 2e77 6562 736f 636b 6574 2e6f 7267 3a34 .websocket.org:4
0x0050: 3433 2048 5454 502f 312e 310d 0a48 6f73 43.HTTP/1.1..Hos
0x0060: 743a 2065 6368 6f2e 7765 6273 6f63 6b65 t:.echo.websocke
0x0070: 742e 6f72 670d 0a50 726f 7879 2d43 6f6e t.org..Proxy-Con
0x0080: 6e65 6374 696f 6e3a 206b 6565 702d 616c nection:.keep-al
0x0090: 6976 650d 0a43 6f6e 6e65 6374 696f 6e3a ive..Connection:
0x00a0: 206b 6565 702d 616c 6976 650d 0a0d 0a .keep-alive....
17:21:45.408347 IP 172.16.99.15.3128 > 172.16.99.21.36521: . ack 1720321613 win 114 <nop,nop,timestamp 1127121 1135442>
0x0000: 4500 0034 93b9 4000 4006 88c5 ac10 630f E..4..@[email protected].
0x0010: ac10 6315 0c38 8ea9 60f5 a17c 668a 064d ..c..8..`..|f..M
0x0020: 8010 0072 c795 0000 0101 080a 0011 32d1 ...r..........2.
0x0030: 0011 5352 ..SR
17:21:45.423007 IP 172.16.99.15.3128 > 172.16.99.21.36521: P 1626710396:1626710435(39) ack 1720321613 win 114 <nop,nop,timestamp 1127124 1135442>
0x0000: 4500 005b 93ba 4000 4006 889d ac10 630f E..[..@[email protected].
0x0010: ac10 6315 0c38 8ea9 60f5 a17c 668a 064d ..c..8..`..|f..M
0x0020: 8018 0072 bdab 0000 0101 080a 0011 32d4 ...r..........2.
0x0030: 0011 5352 4854 5450 2f31 2e30 2032 3030 ..SRHTTP/1.0.200
0x0040: 2043 6f6e 6e65 6374 696f 6e20 6573 7461 .Connection.esta
0x0050: 626c 6973 6865 640d 0a0d 0a blished....
17:21:45.423041 IP 172.16.99.21.36521 > 172.16.99.15.3128: . ack 1626710435 win 457 <nop,nop,timestamp 1135446 1127124>
0x0000: 4500 0034 5699 4000 4006 c5e5 ac10 6315 E..4V.@[email protected].
0x0010: ac10 630f 8ea9 0c38 668a 064d 60f5 a1a3 ..c....8f..M`...
0x0020: 8010 01c9 1e6c 0000 0101 080a 0011 5356 .....l........SV
0x0030: 0011 32d4 ..2.
17:22:15.649132 IP 172.16.99.21.36521 > 172.16.99.15.3128: F 1720321613:1720321613(0) ack 1626710435 win 457 <nop,nop,timestamp 1143003 1127124>
0x0000: 4500 0034 569a 4000 4006 c5e4 ac10 6315 E..4V.@[email protected].
0x0010: ac10 630f 8ea9 0c38 668a 064d 60f5 a1a3 ..c....8f..M`...
0x0020: 8011 01c9 1e6c 0000 0101 080a 0011 70db .....l........p.
0x0030: 0011 32d4 ..2.
17:22:15.650241 IP 172.16.99.15.3128 > 172.16.99.21.36521: F 1626710435:1626710435(0) ack 1720321614 win 114 <nop,nop,timestamp 1134681 1143003>
0x0000: 4500 0034 93bb 4000 4006 88c3 ac10 630f E..4..@[email protected].
0x0010: ac10 6315 0c38 8ea9 60f5 a1a3 668a 064e ..c..8..`...f..N
0x0020: 8011 0072 8c5b 0000 0101 080a 0011 5059 ...r.[........PY
0x0030: 0011 70db ..p.
17:22:15.650255 IP 172.16.99.21.36521 > 172.16.99.15.3128: . ack 1626710436 win 457 <nop,nop,timestamp 1143003 1134681>
0x0000: 4500 0034 569b 4000 4006 c5e3 ac10 6315 E..4V.@[email protected].
0x0010: ac10 630f 8ea9 0c38 668a 064e 60f5 a1a4 ..c....8f..N`...
0x0020: 8010 01c9 1e6c 0000 0101 080a 0011 70db .....l........p.
0x0030: 0011 5059 ..PY
It's not that the proxy is having an issue with Java TLS connections. If I instead (still using Java 8) make a vanilla HTTPS connection (say to https://www.google.com
) through the proxy I see the same CONNECT
being sent by the client and the same Connection established
being sent back by the proxy, but then the TLS handshake, etc. proceed normally and the request/response finishes normally.
Frankly, I'm stumped as to what is going on (or rather not going on).
I was able to reproduce the issue 100% as described in the question. I used Squid version 3.5.17 (Windows 10 64bit),
a local Tomcat using a self-signed certificate with an "echo" web-socket and Java 7 (but I don't think Java 8 makes any difference).
Everything worked OK when I used org.glassfish.tyrus.bundles:tyrus-standalone-client:1.11
instead of 1.12
.
I decided to use the previous version when I found this bugfix:
2015/Nov/13: Grizzly transport was unable to open wss connection via HTTP proxy
(github-commit).
Version 1.11 was released Jun 2015 and version 1.12 Sep 2015, I expect the fix to be in version 1.13 (see also the pom history).
The solution for this question might be related (2015/Aug/21).
For reference, the output from 1.12 (there is a hard-coded timeout of 30 seconds
in org.glassfish.tyrus.container.grizzly.client.GrizzlyClientContainer.CLIENT_SOCKET_TIMEOUT
so you have to wait a while before the error appears).
05:07:25.514 [Grizzly(1) SelectorRunner] TRACE o.g.t.c.g.c.GrizzlyClientFilter - handleConnect Using SSLEngineImpl. Allow unsafe renegotiation: false Allow legacy hello messages: true Is initial handshake: true Is secure renegotiation: false 05:07:25.651 [Grizzly(1)] DEBUG o.g.t.c.g.c.GrizzlyClientFilter - handleRead websocket: null content-size=0 headers= HttpResponsePacket ( status=200 reason=Connection established protocol=HTTP/1.1 content-length=-1 committed=false headers=[] ) javax.websocket.DeploymentException: Connection to 'wss://127.0.0.1:8443/wstest/ws/echoAsyncAnnotation' failed. at org.glassfish.tyrus.container.grizzly.client.GrizzlyClientSocket._connect(GrizzlyClientSocket.java:398) at org.glassfish.tyrus.container.grizzly.client.GrizzlyClientSocket.access$000(GrizzlyClientSocket.java:103) at org.glassfish.tyrus.container.grizzly.client.GrizzlyClientSocket$1.call(GrizzlyClientSocket.java:235) at org.glassfish.tyrus.container.grizzly.client.GrizzlyClientSocket$1.call(GrizzlyClientSocket.java:231) at org.glassfish.tyrus.container.grizzly.client.GrizzlyClientSocket.connect(GrizzlyClientSocket.java:249) at org.glassfish.tyrus.container.grizzly.client.GrizzlyClientContainer.openClientSocket(GrizzlyClientContainer.java:95) at org.glassfish.tyrus.client.ClientManager$3$1.run(ClientManager.java:663) at org.glassfish.tyrus.client.ClientManager$3.run(ClientManager.java:712) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471) at java.util.concurrent.FutureTask.run(FutureTask.java:262) at org.glassfish.tyrus.client.ClientManager$SameThreadExecutorService.execute(ClientManager.java:866) at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:110) at org.glassfish.tyrus.client.ClientManager.connectToServer(ClientManager.java:511) at org.glassfish.tyrus.client.ClientManager.connectToServer(ClientManager.java:355) at nl.fw.wsclienttest.WsClient.echoDemo(WsClient.java:69) at nl.fw.wsclienttest.WsClient.main(WsClient.java:41)
And the code for the totally unsecure test-client I used:
import java.io.IOException;
import java.net.URI;
import java.security.KeyStore;
import java.security.cert.*;
import java.util.concurrent.*;
import javax.net.ssl.*;
import javax.websocket.*;
import org.glassfish.tyrus.client.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.bridge.SLF4JBridgeHandler;
public class WsClient {
static {
SLF4JBridgeHandler.removeHandlersForRootLogger();
SLF4JBridgeHandler.install();
}
private static final Logger log = LoggerFactory.getLogger(WsClient.class);
public static void main(String[] args) {
System.getProperties().put("javax.net.debug", "ssl,handshake,data,sslctx");
try {
new WsClient().echoDemo();
log.info("done");
} catch (Exception e) {
e.printStackTrace();
}
}
// localhost does not work via squid proxy
String destUri = "wss://127.0.0.1:8443/wstest/ws/echoAsyncAnnotation";
String proxyUri = "http://localhost:3128";
volatile Session wsSession = null;
void echoDemo() throws Exception {
final ClientEndpointConfig cec = ClientEndpointConfig.Builder.create().build();
ClientManager client = ClientManager.createClient();
client.getProperties().put(ClientProperties.PROXY_URI, proxyUri);
client.getProperties().put(ClientProperties.SHARED_CONTAINER, false);
SSLContext sslCtx = createAllTrustingContext();
SslEngineConfigurator sslEngineConfigurator = new SslEngineConfigurator(sslCtx, true, false, false);
sslEngineConfigurator.setHostVerificationEnabled(false); //skip host verification
client.getProperties().put(ClientProperties.SSL_ENGINE_CONFIGURATOR, sslEngineConfigurator);
// time-out is set to 30 seconds for all operations, so handshake timeout does not work ...
// see org.glassfish.tyrus.container.grizzly.client.GrizzlyClientContainer.CLIENT_SOCKET_TIMEOUT
client.getProperties().put(ClientProperties.HANDSHAKE_TIMEOUT, 3000);
final CountDownLatch messageLatch = new CountDownLatch(1);
client.connectToServer(new Endpoint() {
@Override
public void onOpen(final Session session, EndpointConfig config) {
try {
session.addMessageHandler(new MessageHandler.Whole<String>() {
@Override
public void onMessage(String message) {
log.info("Received message: {}", message);
wsSession = session;
messageLatch.countDown();
}
});
session.getBasicRemote().sendText("Hello world");
} catch (IOException e) {
e.printStackTrace();
}
}
}, cec, new URI(destUri));
try {
messageLatch.await(5, TimeUnit.SECONDS);
if (wsSession != null) {
wsSession.close();
}
} finally {
client.shutdown();
}
}
public static SSLContext createAllTrustingContext() throws Exception {
SSLContext ctx = SSLContext.getInstance("TLSv1.2");
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init((KeyStore) null, "changeit".toCharArray());
ctx.init(kmf.getKeyManagers(), new TrustManager[] { new TrustServerCertAlways() }, null);
return ctx;
}
static class TrustServerCertAlways implements X509TrustManager {
@Override public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
log.debug("Trusting all client certificates.");
}
@Override public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
log.debug("Trusting all server certificates.");
}
@Override public X509Certificate[] getAcceptedIssuers() {
log.debug("No accepted issuers.");
return null;
}
}
}