5

We have a Java 17 Quarkus application compiled to a native executable (ubi-quarkus-native-image:22.2-java17), running in Kubernetes. It is connecting to an old integration server with SSL, making a HTTP request. Our application creates a SSLSocket using a socket factory and connects it; the request is successful. When it is done, we call SSLSocket#close. This works too, but on the other side they log this:

12:51:16 : ssl_debug(58): Shutting down SSL layer...
12:51:16 : ssl_debug(58): Sending alert: Alert Warning: close notify
12:51:16 : ssl_debug(58): Read 15328 bytes in 3 records, 15214 bytes net, 5071 average.
12:51:16 : ssl_debug(58): Wrote 175 bytes in 1 records, 152 bytes net, 152 average.
12:51:16 : ssl_debug(58): Closing transport...
12:51:16 : ssl_debug(58): Waiting on peer close notify...
12:51:16 : ssl_debug(58): Received alert message: Alert Warning: user canceled
12:51:16 : ssl_debug(58): Exception reading SSL message: iaik.security.ssl.SSLException: Peer sent alert: Alert Warning: user canceled
12:51:16 : ssl_debug(58): Closing transport...
12:51:16 : ssl_debug(58): Transport closed.

As far as I can understand they should get a close notify alert from us and we should get a close notify alert from them. Unfortunately our side sends a user canceled alert (code 90). It is only a warning, but they treat it as an error and throw away the entire request, even though they have answered.

I have found a way to circumvent this. If I call shutdownOutput on the socket before close, things work. However, according to properly close SSLSocket this is wrong and it really feels like a dirty workaround.

I can't get the other vendor to fix the error handling in their application. Is there a nicer way to avoid sending the user canceled alert? Is it a bug in the SSL implementation, or is it expected?

The socket has been flushed before close in both cases and reading from it (making sure there is no pending inbound data) makes no difference. Waiting for up to a minute before closing makes no difference either.

EDIT: it appears other servers fail with the workaround and require the final alerts to close cleanly, so it would be really nice to solve this.

EDIT: the code here is trivial, but I will add it as requested.

try (var socket = connect(url, httpChannel)) {
   // Successfully make request here
   // The workaround below fixes the problem
   //socket.shutdownOutput();
} // close called on SSLSocket here

The code in connect is (simplified):

var plainSocket = new Socket();
plainSocket.connect(new InetSocketAddress(hostName, port), (int) connectionTimeout.toMillis());
plainSocket.setSoTimeout((int) connectionTimeout.toMillis());
var secureSocket = (SSLSocket) getSocketFactory(keyManagers, trustManagers, sslContextProtocol).createSocket(plainSocket, hostName, port, true);
var params = secureSocket.getSSLParameters();
params.setEndpointIdentificationAlgorithm("HTTPS");
secureSocket.setSSLParameters(params); // Optional but usually on
secureSocket.setUseClientMode(true);
secureSocket.setSoTimeout((int) readTimeout.toMillis());
secureSocket.startHandshake(); // Optional, but usually on
return secureSocket;

In the other end (where we act as server), we do a serverSocket#accept and call close on the request socket in a finally block, optionally with a similar socket#shutdownOutput first to work around the problem.

ewramner
  • 5,810
  • 2
  • 17
  • 33
  • The output shows the server closes the connection, and the client answers with user_canceled. Give the server a chance to not be the closure initiator by using persistent connections : use HTTP 1.1 or Connection: Keep-Alive header. – Eugène Adell May 04 '23 at 08:53
  • 2
    Might be [this known bug](https://github.com/openjdk/jdk/pull/7664) which should be fixed in current versions of OpenJDK. Maybe you need to upgrade. – Steffen Ullrich May 04 '23 at 11:31
  • @EugèneAdell a persistent connection is not an option here. The question is how to close gracefully, not how to avoid closing. – ewramner May 04 '23 at 12:41
  • @SteffenUllrich it sounds like the known bug except that I don't think our connection is half closed. Good suggestion. We are on the latest 2.x.x Quarkus version by now and still have the issue, so I need to check what version includes the fix. – ewramner May 04 '23 at 12:44
  • @SteffenUllrich we are on Java Version 17.0.7+7 and the fix should have been backported to 17.0.6. In other words we should have the fix already. – ewramner May 04 '23 at 12:55
  • I meant : find the way to let the client be the closure initiator instead of the the server. I suppose it's either HTTP 1.0 or Connection: Close header use here, that is responsible of what the log is showing. Change to persistent connection in order to be able to call the client's close, not to reuse the connection itself. More clear ? Where did I say we would avoid closing ? – Eugène Adell May 04 '23 at 13:46
  • @EugèneAdell in our case we do want to close the connection here. The client is not a web browser and does not support keep-alive. If we don't close nothing happens (we waited a minute, see question). We need to close, but gracefully. – ewramner May 04 '23 at 14:00
  • 1
    Please read your debug trace carefully, it shows the server is closing, then waiting for the client to close (which is not happening cleanly due to a bug probably). You should try to get the opposite behavior by letting the client read the server answer, close, then wait for the server closing. Only way to do that in HTTP (and you are using HTTP right ?), is to use a persistent connection, even if you just send one single request and close after it. We don't care if your client doesn't support keep-alive, we are just faking it to let the server have a chance to not close after answering. GL. – Eugène Adell May 04 '23 at 14:52
  • @EugèneAdell it sounds as the opposite of what we want. The current "Connection: Close" header tells the client that it should close. If we use keep-alive the client will not "read the answer, close and wait for the server to close". That is what it should do now. With keep-alive it should "read the answer, then keep the connection open for future use", right? We don't want that. We want the server to be able to close the connection, preventing resource leaks, regardless of what the client is doing. – ewramner May 05 '23 at 07:53
  • 1
    @ewramner: EugèneAdell is talking about the header send from the client to the server, while you talk about the header send from server to client. If the client says that it does not support keep-alive (i.e. explicit Connection: close or default when using HTTP/1.0) then the server will close the connection after the response is done. If the client announces support for keep-alive (default with HTTP/1.1 or with explicit Connection: keep-alive) then the server __might__ keep the connection open in the hope for another request so that close will be initiated by the client. – Steffen Ullrich May 05 '23 at 07:59
  • @SteffenUllrich I thought he meant the header from the server, which we can set. We have no control over the client and the client is sending "Connection: Close". – ewramner May 05 '23 at 10:50
  • *"We have no control over the client"* - this is unexpected. Because in the question you say *"Our application creates a SSLSocket using a socket factory and connects it"* which I would assume to refer to the client side (client connects, server accepts) – Steffen Ullrich May 05 '23 at 10:56
  • @SteffenUllrich you perfectly reformulated the idea, thanks, maybe it's better explained now. – Eugène Adell May 05 '23 at 11:14
  • @ewramner We are on a dev forum and most of questions (not all) are related to coding. Do you have any code to show ? – Eugène Adell May 05 '23 at 11:18
  • @EugèneAdell you are completely correct, I'm confused. We have this problem both when we are acting as client, calling the other server, and when we are acting as server and they call us. As client, we could ask for a keep-alive, but (while I can try) it will make no difference, because these servers do not support keep-alives and will close anyway. We need to gracefully close the socket on the SSL layer somehow regardless of what happens on the higher levels. – ewramner May 05 '23 at 13:11
  • @EugèneAdell I have added some code. Unfortunately, the interesting part happens inside SSLSocket as far as I can tell. The request is done and the only thing left is closing the connection without errors. – ewramner May 05 '23 at 13:25
  • Thanks for the edits. If we are sure the server doesn't support connection persistence then the word **might** above now means it's not the case. If you implemented the server code, maybe you can change that. What I suggest, but I'm unsure you can do that in your case, is to switch to another JSSE Provider (e.g. IBM JSSE2) and see how it behaves (probably not better). Moving to an OpenSSL based JSSE seems possible but not a 2 lines answer. @SteffenUllrich did you try that ? – Eugène Adell May 05 '23 at 18:08
  • Changing JSSE provider might help, but please note that this is a native Quarkus application. I don't have a clue how that works with GraalVM in native mode, if it is even possible. – ewramner May 06 '23 at 10:24
  • @SteffenUllrich while it didn't help, I think your bug is the closest to a solution I've seen. If you make it an answer before the time runs out I'll award you the bounty. – ewramner May 08 '23 at 16:44

0 Answers0