diff --git a/ziti/src/main/kotlin/org/openziti/impl/ChannelImpl.kt b/ziti/src/main/kotlin/org/openziti/impl/ChannelImpl.kt index 6568f339..0dd76636 100644 --- a/ziti/src/main/kotlin/org/openziti/impl/ChannelImpl.kt +++ b/ziti/src/main/kotlin/org/openziti/impl/ChannelImpl.kt @@ -125,7 +125,8 @@ internal class ChannelImpl(val addr: String, val id: Identity, val apiSession: ( chState.value = Channel.State.Connecting val jobs = mutableListOf<Deferred<Unit>>() try { - peer = Transport.dial(addr, id.sslContext(), CONNECT_TIMEOUT) + peer = Transport.dial(addr, id.sslContext(), CONNECT_TIMEOUT, EDGE_APP_PROTOCOL) + d{ "connected with ${peer.applicationProtocol()}"} jobs += async { txer(peer) } jobs += async { rxer(peer) } @@ -310,5 +311,6 @@ internal class ChannelImpl(val addr: String, val id: Identity, val apiSession: ( companion object { const val CONNECT_TIMEOUT: Long = 20_000 + const val EDGE_APP_PROTOCOL = "ziti-edge" } } diff --git a/ziti/src/main/kotlin/org/openziti/net/Transport.kt b/ziti/src/main/kotlin/org/openziti/net/Transport.kt index 72a54c6b..8ec541a1 100644 --- a/ziti/src/main/kotlin/org/openziti/net/Transport.kt +++ b/ziti/src/main/kotlin/org/openziti/net/Transport.kt @@ -35,30 +35,46 @@ import java.nio.channels.SocketChannel import java.util.concurrent.CancellationException import java.util.concurrent.CompletableFuture import javax.net.ssl.SSLContext +import javax.net.ssl.SSLEngine internal interface Transport : Closeable { companion object { - suspend fun dial(address: String, ssl: SSLContext, timeout: Long): Transport { + suspend fun dial(address: String, ssl: SSLContext, timeout: Long, vararg protos: String): Transport { val url = URI.create(address) - val tls = TLS(url.host, url.port, ssl) + val tls = TLS(url.host, url.port, ssl, *protos) tls.connect(timeout) return tls } } + fun applicationProtocol(): String? fun isClosed(): Boolean suspend fun connect(timeout: Long) suspend fun write(buf: ByteBuffer) suspend fun read(buf: ByteBuffer, full: Boolean = true): Int - class TLS(host: String, port: Int, val sslContext: SSLContext) : Transport { + class TLS(host: String, port: Int, sslContext: SSLContext, vararg appProto: String) : Transport { lateinit var socket: AsynchronousTlsChannel - val addr = InetSocketAddress(InetAddress.getByName(host), port) + private val addr = InetSocketAddress(InetAddress.getByName(host), port) + private val sslEngine: SSLEngine + + init { + val sslParms = sslContext.defaultSSLParameters + sslParms.applicationProtocols = appProto + + sslEngine = sslContext.createSSLEngine(host, port).apply { + sslParameters = sslParms + useClientMode = true + } + } + + override fun applicationProtocol(): String? = sslEngine.applicationProtocol override suspend fun connect(timeout: Long) { - val connOp = connectAsync(addr, sslContext) + val connOp = connectAsync(addr, sslEngine) + sslEngine.handshakeStatus socket = kotlin.runCatching { withTimeout(timeout) { connOp.await() @@ -98,19 +114,19 @@ internal interface Transport : Closeable { companion object { val asyncGroup = AsynchronousTlsChannelGroup() - internal fun connectAsync(address: InetSocketAddress, ssl: SSLContext): Deferred<AsynchronousTlsChannel> { + internal fun connectAsync(address: InetSocketAddress, ssl: SSLEngine): Deferred<AsynchronousTlsChannel> { val deferred = CompletableDeferred<AsynchronousTlsChannel>() val sockCh = SocketChannel.open() - val sslEngine = ssl.createSSLEngine(address.hostString, address.port).apply { - useClientMode = true - } + val f = CompletableFuture.supplyAsync { sockCh.connect(address) - sockCh.configureBlocking(false) - val tlsCh = ClientTlsChannel.newBuilder(sockCh, SSLEngineWrapper(sslEngine)) + val tlsCh = ClientTlsChannel.newBuilder(sockCh, ssl) .withEncryptedBufferAllocator(HeapBufferAllocator()) .withPlainBufferAllocator(HeapBufferAllocator()) .build() + tlsCh.handshake() + + sockCh.configureBlocking(false) AsynchronousTlsChannel(asyncGroup, tlsCh, sockCh) } f.whenComplete { ch, ex -> diff --git a/ziti/src/test/kotlin/org/openziti/net/TransportTest.kt b/ziti/src/test/kotlin/org/openziti/net/TransportTest.kt index dcaa6659..0f390f7e 100644 --- a/ziti/src/test/kotlin/org/openziti/net/TransportTest.kt +++ b/ziti/src/test/kotlin/org/openziti/net/TransportTest.kt @@ -52,6 +52,35 @@ User-Agent: ziti/1.0.2 } } + @Test(timeout = 65000) + fun testALPN() { + runBlocking { + val t = Transport.dial("tls://google.com:443", + SSLContext.getDefault(), 61_000, "http/1.1", "foo", "bar") + + val applicationProtocol = t.applicationProtocol() + assertThat("alpn match", "http/1.1" == applicationProtocol) + + val req = """GET /robots.txt HTTP/1.1 +Accept: */* +Connection: keep-alive +Host: google.com +User-Agent: ziti/1.0.2 + +""" + t.write(StandardCharsets.UTF_8.encode(req)) + val respBuf = ByteBuffer.allocate(1024) + t.read(respBuf, false) + + respBuf.flip() + val resp = StandardCharsets.UTF_8.decode(respBuf) + println(resp) + val lines = resp.toString().reader().readLines() + assertThat(lines[0], CoreMatchers.startsWith("HTTP/1.1")) + t.close() + } + } + @Test(timeout = 20000, expected = SocketTimeoutException::class) fun testCancel() { runBlocking {