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

Unable to connect to websocket server using wss:// #10744

Closed
iNviNho opened this issue Apr 19, 2024 · 5 comments
Closed

Unable to connect to websocket server using wss:// #10744

iNviNho opened this issue Apr 19, 2024 · 5 comments
Assignees

Comments

@iNviNho
Copy link

iNviNho commented Apr 19, 2024

Expected Behavior

When installing fresh Micronaut 4.4.0 application

and creating these 2 classes

package com.example;

import io.micronaut.websocket.WebSocketSession;
import io.micronaut.websocket.annotation.ClientWebSocket;
import io.micronaut.websocket.annotation.OnMessage;
import io.micronaut.websocket.annotation.OnOpen;


@ClientWebSocket
public class TalosClientWebsocket implements AutoCloseable {

    private WebSocketSession session;

    @OnOpen
    public void onOpen(WebSocketSession session) {
        this.session = session;
    }

    @OnMessage
    public void onMessage(String message) {
      System.out.println(message);
    }

    @Override
    public void close() {
      session.close();
    }
}

and

package com.example;


import io.micronaut.context.annotation.Context;
import io.micronaut.http.client.annotation.Client;
import io.micronaut.websocket.WebSocketClient;
import jakarta.inject.Singleton;
import reactor.core.publisher.Flux;

@Context
@Singleton
public class TalosClientWebsocketConnection {


  public TalosClientWebsocketConnection(
    @Client("wss://websocket-echo.com") final WebSocketClient webSocketClient
    ) {
    Flux.from(webSocketClient.connect(TalosClientWebsocket.class, "/"))
      .blockFirst()
    ;
  }
}

I expect application to connect to websocket server.

Actual Behaviour

The application fails with error

09:50:53.480 [main] ERROR io.micronaut.runtime.Micronaut - Error starting Micronaut server: Bean definition [com.example.TalosClientWebsocketConnection] could not be loaded: Error instantiating bean of type  [com.example.TalosClientWebsocketConnection]

Message: Error opening WebSocket client session: Abnormal Closure
Path Taken: new TalosClientWebsocketConnection(WebSocketClient webSocketClient)
io.micronaut.context.exceptions.BeanInstantiationException: Bean definition [com.example.TalosClientWebsocketConnection] could not be loaded: Error instantiating bean of type  [com.example.TalosClientWebsocketConnection]

Caused by: io.micronaut.websocket.exceptions.WebSocketClientException: Error opening WebSocket client session: Abnormal Closure
	at io.micronaut.http.client.netty.websocket.NettyWebSocketClientHandler.handleCloseReason(NettyWebSocketClientHandler.java:262)
	at io.micronaut.http.netty.websocket.AbstractNettyWebSocketHandler.handlerRemoved(AbstractNettyWebSocketHandler.java:255)
	at io.netty.channel.AbstractChannelHandlerContext.callHandlerRemoved(AbstractChannelHandlerContext.java:1138)
	at io.netty.channel.DefaultChannelPipeline.callHandlerRemoved0(DefaultChannelPipeline.java:637)
	at io.netty.channel.DefaultChannelPipeline.destroyDown(DefaultChannelPipeline.java:876)
	at io.netty.channel.DefaultChannelPipeline.destroyUp(DefaultChannelPipeline.java:844)
	at io.netty.channel.DefaultChannelPipeline.destroy(DefaultChannelPipeline.java:836)
	at io.netty.channel.DefaultChannelPipeline.access$700(DefaultChannelPipeline.java:46)
	at io.netty.channel.DefaultChannelPipeline$HeadContext.channelUnregistered(DefaultChannelPipeline.java:1392)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelUnregistered(AbstractChannelHandlerContext.java:215)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelUnregistered(AbstractChannelHandlerContext.java:195)
	at io.netty.channel.DefaultChannelPipeline.fireChannelUnregistered(DefaultChannelPipeline.java:821)
	at io.netty.channel.AbstractChannel$AbstractUnsafe$7.run(AbstractChannel.java:821)
	at io.netty.util.concurrent.AbstractEventExecutor.runTask(AbstractEventExecutor.java:173)
	at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:166)
	at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:470)
	at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:566)
	at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:997)
	at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
	at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
	at java.base/java.lang.Thread.run(Thread.java:1583)

it works correctly if I connect to non wss:// (e.g. ws://localhost:8080)

These are the debug logs from the netty

09:53:26.410 [main] DEBUG i.n.u.i.l.InternalLoggerFactory - Using SLF4J as the default logging framework
09:53:26.411 [main] DEBUG io.netty.util.ResourceLeakDetector - -Dio.netty.leakDetection.level: simple
09:53:26.411 [main] DEBUG io.netty.util.ResourceLeakDetector - -Dio.netty.leakDetection.targetRecords: 4
09:53:26.478 [main] DEBUG i.n.c.MultithreadEventLoopGroup - -Dio.netty.eventLoopThreads: 20
09:53:26.482 [main] DEBUG i.n.u.concurrent.GlobalEventExecutor - -Dio.netty.globalEventExecutor.quietPeriodSeconds: 1
09:53:26.484 [main] DEBUG i.n.u.i.InternalThreadLocalMap - -Dio.netty.threadLocalMap.stringBuilder.initialSize: 1024
09:53:26.484 [main] DEBUG i.n.u.i.InternalThreadLocalMap - -Dio.netty.threadLocalMap.stringBuilder.maxSize: 4096
09:53:26.492 [main] DEBUG i.n.util.internal.PlatformDependent0 - -Dio.netty.noUnsafe: false
09:53:26.492 [main] DEBUG i.n.util.internal.PlatformDependent0 - Java version: 21
09:53:26.493 [main] DEBUG i.n.util.internal.PlatformDependent0 - sun.misc.Unsafe.theUnsafe: available
09:53:26.493 [main] DEBUG i.n.util.internal.PlatformDependent0 - sun.misc.Unsafe.copyMemory: available
09:53:26.493 [main] DEBUG i.n.util.internal.PlatformDependent0 - sun.misc.Unsafe.storeFence: available
09:53:26.493 [main] DEBUG i.n.util.internal.PlatformDependent0 - java.nio.Buffer.address: available
09:53:26.493 [main] DEBUG i.n.util.internal.PlatformDependent0 - direct buffer constructor: unavailable: Reflective setAccessible(true) disabled
09:53:26.494 [main] DEBUG i.n.util.internal.PlatformDependent0 - java.nio.Bits.unaligned: available, true
09:53:26.494 [main] DEBUG i.n.util.internal.PlatformDependent0 - jdk.internal.misc.Unsafe.allocateUninitializedArray(int): unavailable: class io.netty.util.internal.PlatformDependent0$7 cannot access class jdk.internal.misc.Unsafe (in module java.base) because module java.base does not export jdk.internal.misc to unnamed module @4232c52b
09:53:26.494 [main] DEBUG i.n.util.internal.PlatformDependent0 - java.nio.DirectByteBuffer.<init>(long, {int,long}): unavailable
09:53:26.494 [main] DEBUG i.n.util.internal.PlatformDependent - sun.misc.Unsafe: available
09:53:26.494 [main] DEBUG i.n.util.internal.PlatformDependent - -Dio.netty.tmpdir: /var/folders/4w/px_kfrk55b9dr69m1l0n0ym00000gn/T (java.io.tmpdir)
09:53:26.494 [main] DEBUG i.n.util.internal.PlatformDependent - -Dio.netty.bitMode: 64 (sun.arch.data.model)
09:53:26.495 [main] DEBUG i.n.util.internal.PlatformDependent - Platform: MacOS
09:53:26.495 [main] DEBUG i.n.util.internal.PlatformDependent - -Dio.netty.maxDirectMemory: -1 bytes
09:53:26.495 [main] DEBUG i.n.util.internal.PlatformDependent - -Dio.netty.uninitializedArrayAllocationThreshold: -1
09:53:26.495 [main] DEBUG io.netty.util.internal.CleanerJava9 - java.nio.ByteBuffer.cleaner(): available
09:53:26.495 [main] DEBUG i.n.util.internal.PlatformDependent - -Dio.netty.noPreferDirect: false
09:53:26.495 [main] DEBUG io.netty.channel.nio.NioEventLoop - -Dio.netty.noKeySetOptimization: false
09:53:26.495 [main] DEBUG io.netty.channel.nio.NioEventLoop - -Dio.netty.selectorAutoRebuildThreshold: 512
09:53:26.498 [main] DEBUG i.n.util.internal.PlatformDependent - org.jctools-core.MpscChunkedArrayQueue: available
09:53:26.515 [main] DEBUG io.netty.handler.ssl.OpenSsl - netty-tcnative not in the classpath; OpenSslEngine will be unavailable.
09:53:26.619 [main] DEBUG io.netty.handler.ssl.JdkSslContext - Default protocols (JDK): [TLSv1.3, TLSv1.2] 
09:53:26.619 [main] DEBUG io.netty.handler.ssl.JdkSslContext - Default cipher suites (JDK): [TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_AES_128_GCM_SHA256, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_256_CBC_SHA, TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384]
09:53:26.657 [main] DEBUG io.netty.channel.DefaultChannelId - -Dio.netty.processId: 56637 (auto-detected)
09:53:26.658 [main] DEBUG io.netty.util.NetUtil - -Djava.net.preferIPv4Stack: false
09:53:26.658 [main] DEBUG io.netty.util.NetUtil - -Djava.net.preferIPv6Addresses: false
09:53:26.661 [main] DEBUG i.netty.util.NetUtilInitializations - Loopback interface: lo0 (lo0, 0:0:0:0:0:0:0:1%lo0)
09:53:26.662 [main] DEBUG io.netty.util.NetUtil - Failed to get SOMAXCONN from sysctl and file /proc/sys/net/core/somaxconn. Default: 128
09:53:26.663 [main] DEBUG io.netty.channel.DefaultChannelId - -Dio.netty.machineId: f8:4d:89:ff:fe:93:80:c4 (auto-detected)
09:53:26.675 [main] DEBUG i.n.buffer.PooledByteBufAllocator - -Dio.netty.allocator.numHeapArenas: 20
09:53:26.675 [main] DEBUG i.n.buffer.PooledByteBufAllocator - -Dio.netty.allocator.numDirectArenas: 20
09:53:26.675 [main] DEBUG i.n.buffer.PooledByteBufAllocator - -Dio.netty.allocator.pageSize: 8192
09:53:26.675 [main] DEBUG i.n.buffer.PooledByteBufAllocator - -Dio.netty.allocator.maxOrder: 3
09:53:26.675 [main] DEBUG i.n.buffer.PooledByteBufAllocator - -Dio.netty.allocator.chunkSize: 65536
09:53:26.675 [main] DEBUG i.n.buffer.PooledByteBufAllocator - -Dio.netty.allocator.smallCacheSize: 256
09:53:26.675 [main] DEBUG i.n.buffer.PooledByteBufAllocator - -Dio.netty.allocator.normalCacheSize: 64
09:53:26.675 [main] DEBUG i.n.buffer.PooledByteBufAllocator - -Dio.netty.allocator.maxCachedBufferCapacity: 32768
09:53:26.675 [main] DEBUG i.n.buffer.PooledByteBufAllocator - -Dio.netty.allocator.cacheTrimInterval: 8192
09:53:26.675 [main] DEBUG i.n.buffer.PooledByteBufAllocator - -Dio.netty.allocator.cacheTrimIntervalMillis: 0
09:53:26.675 [main] DEBUG i.n.buffer.PooledByteBufAllocator - -Dio.netty.allocator.useCacheForAllThreads: false
09:53:26.675 [main] DEBUG i.n.buffer.PooledByteBufAllocator - -Dio.netty.allocator.maxCachedByteBuffersPerChunk: 1023
09:53:26.679 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.allocator.type: pooled
09:53:26.679 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.threadLocalDirectBufferSize: 0
09:53:26.679 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.maxThreadLocalCharBufferSize: 16384
09:53:26.683 [main] DEBUG i.n.b.ChannelInitializerExtensions - -Dio.netty.bootstrap.extensions: null
09:53:26.703 [default-nioEventLoopGroup-1-1] DEBUG io.netty.buffer.AbstractByteBuf - -Dio.netty.buffer.checkAccessible: true
09:53:26.703 [default-nioEventLoopGroup-1-1] DEBUG io.netty.buffer.AbstractByteBuf - -Dio.netty.buffer.checkBounds: true
09:53:26.704 [default-nioEventLoopGroup-1-1] DEBUG i.n.util.ResourceLeakDetectorFactory - Loaded default ResourceLeakDetector: io.netty.util.ResourceLeakDetector@4d315413
09:53:26.713 [default-nioEventLoopGroup-1-1] DEBUG i.n.h.c.compression.ZlibCodecFactory - -Dio.netty.noJdkZlibDecoder: false
09:53:26.713 [default-nioEventLoopGroup-1-1] DEBUG i.n.h.c.compression.ZlibCodecFactory - -Dio.netty.noJdkZlibEncoder: false
09:53:26.882 [default-nioEventLoopGroup-1-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.maxCapacityPerThread: 4096
09:53:26.882 [default-nioEventLoopGroup-1-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.ratio: 8
09:53:26.882 [default-nioEventLoopGroup-1-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.chunkSize: 32
09:53:26.882 [default-nioEventLoopGroup-1-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.blocking: false
09:53:26.882 [default-nioEventLoopGroup-1-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.batchFastThreadLocalOnly: true
09:53:26.887 [default-nioEventLoopGroup-1-1] DEBUG i.n.h.c.h.w.WebSocketClientHandshaker13 - WebSocket version 13 client handshake key: xWELLURO9uS4JZjTBIaXyw==, expected response: F5CLHu3C96X3B2xBs7T3e7vKb2M=
09:53:27.048 [default-nioEventLoopGroup-1-1] DEBUG io.netty.handler.ssl.SslHandler - [id: 0xd776b5e6, L:/192.168.100.43:57120 - R:websocket-echo.com/198.12.65.76:443] HANDSHAKEN: protocol:TLSv1.3 cipher suite:TLS_AES_128_GCM_SHA256

Steps To Reproduce

Install fresh micronaut 4.4.0 application

with these dependencies

dependencies {
annotationProcessor("io.micronaut:micronaut-http-validation")
annotationProcessor("io.micronaut.serde:micronaut-serde-processor")
implementation("io.micronaut:micronaut-websocket")
implementation("io.micronaut.reactor:micronaut-reactor-http-client")
implementation("io.micronaut.serde:micronaut-serde-jackson")
compileOnly("io.micronaut:micronaut-http-client")
runtimeOnly("ch.qos.logback:logback-classic")
testImplementation("io.micronaut:micronaut-http-client")
}



### Environment Information

- JDK 21
- MacOS

### Example Application

https://github.com/iNviNho/micronaut-wss-issue

### Version

4.4.0
@iNviNho
Copy link
Author

iNviNho commented Apr 22, 2024

The same issue occurs in Mironaut version 4.3.6 so it looks like it is not purely related to 4.4.0 version ⚠️

Are we doing something wrong or do we miss some configuration for WSS?

@jeremyg484
Copy link
Contributor

The same issue occurs in Mironaut version 4.3.6 so it looks like it is not purely related to 4.4.0 version ⚠️

Are we doing something wrong or do we miss some configuration for WSS?

@iNviNho I don't think (yet) that you're doing anything wrong. I have been investigating and was able to reproduce it in multiple Micronaut 4.x versions as well. It seems to be specific to the SSL implementation. I will update as I learn more.

@jeremyg484
Copy link
Contributor

jeremyg484 commented Apr 22, 2024

@iNviNho I have tracked it down to an issue with our ALPN implementation. We do not currently support WebSockets over an HTTP 2 connection (as far as I know, Netty does not yet have an official implementation of RFC8441), but we are advertising support for HTTP 2 in the TLS handshake when making the initial connection to the endpoint. I believe that is what is causing the server to drop the connection.

You can work around it for now by setting micronaut.http.client.alpn-modes=http/1.1.

I will need to implement a fix to force the WebSocket client to only advertise HTTP 1.1 in the TLS handshake.

@iNviNho
Copy link
Author

iNviNho commented Apr 23, 2024

Thank you @jeremyg484 .

Setting

micronaut:
  http:
    client:
      alpn-modes: http/1.1

didn't work but setting alpnMode on the @client already makes a difference

@Client(value = "talos-ws", alpnModes = "http/1.1")

after that we got the error

invalid WebSocket Extension handshake for "permessage-deflate; server_no_context_takeover; client_no_context_takeover"

which we fixed by enabling compression

compression:
      enabled: false

and now everything works as expected 🍾

Thank you so far for your assistance!

@jeremyg484
Copy link
Contributor

Note that alpn-modes is an array, so I believe in YAML it would need to be:

micronaut:
  http:
    client:
      alpn-modes: [http/1.1]

jeremyg484 added a commit that referenced this issue Apr 23, 2024
Connection manager is updated to use a separate SSLContext for
WebSocket connections that will only advertise http/1.1 in the list
of supported protocols in the ALPN section of the TLS handshake.

WebSocket is not currently supported over HTTP 2 connections, thus
if an HTTP 2 connection is established through ALPN, the subsequent
upgrade to the WebSocket protocol would fail.

This resolves #10744
yawkat pushed a commit that referenced this issue Apr 25, 2024
* Force secure WebSocket connections to use http/1.1

Connection manager is updated to use a separate SSLContext for
WebSocket connections that will only advertise http/1.1 in the list
of supported protocols in the ALPN section of the TLS handshake.

WebSocket is not currently supported over HTTP 2 connections, thus
if an HTTP 2 connection is established through ALPN, the subsequent
upgrade to the WebSocket protocol would fail.

This resolves #10744

* Enable ALPN with Websocket and ensure SSLContext released.

* Lazily create websocket SSL context per connection

* Use lazily intialized field for WebSocket SSL context.

* Actually initialize in the initializer.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
No open projects
Status: Done
Development

No branches or pull requests

2 participants