Skip to content

Commit 4f0d759

Browse files
authored
Use flyweights for TLS proxy decoder (#1485)
1 parent b82bbf2 commit 4f0d759

9 files changed

Lines changed: 349 additions & 55 deletions

File tree

runtime/binding-tls/src/main/java/io/aklivity/zilla/runtime/binding/tls/internal/stream/TlsProxyFactory.java

Lines changed: 65 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,7 @@
1818
import static io.aklivity.zilla.runtime.engine.buffer.BufferPool.NO_SLOT;
1919
import static io.aklivity.zilla.runtime.engine.concurrent.Signaler.NO_CANCEL_ID;
2020
import static java.lang.System.currentTimeMillis;
21-
import static java.nio.ByteOrder.BIG_ENDIAN;
2221
import static java.util.concurrent.TimeUnit.SECONDS;
23-
import static org.agrona.BitUtil.SIZE_OF_BYTE;
24-
import static org.agrona.BitUtil.SIZE_OF_SHORT;
2522

2623
import java.util.Optional;
2724
import java.util.function.Consumer;
@@ -36,7 +33,14 @@
3633
import io.aklivity.zilla.runtime.binding.tls.internal.config.TlsBindingConfig;
3734
import io.aklivity.zilla.runtime.binding.tls.internal.config.TlsRouteConfig;
3835
import io.aklivity.zilla.runtime.binding.tls.internal.types.OctetsFW;
36+
import io.aklivity.zilla.runtime.binding.tls.internal.types.codec.TlsExtensionFW;
37+
import io.aklivity.zilla.runtime.binding.tls.internal.types.codec.TlsExtensionsFW;
38+
import io.aklivity.zilla.runtime.binding.tls.internal.types.codec.TlsHandshakeClientHelloFW;
39+
import io.aklivity.zilla.runtime.binding.tls.internal.types.codec.TlsHandshakeMessageFW;
3940
import io.aklivity.zilla.runtime.binding.tls.internal.types.codec.TlsRecordFW;
41+
import io.aklivity.zilla.runtime.binding.tls.internal.types.codec.TlsServerNameExtensionFW;
42+
import io.aklivity.zilla.runtime.binding.tls.internal.types.codec.TlsServerNameTypeFW;
43+
import io.aklivity.zilla.runtime.binding.tls.internal.types.codec.TlsSniHostNameFW;
4044
import io.aklivity.zilla.runtime.binding.tls.internal.types.stream.AbortFW;
4145
import io.aklivity.zilla.runtime.binding.tls.internal.types.stream.BeginFW;
4246
import io.aklivity.zilla.runtime.binding.tls.internal.types.stream.DataFW;
@@ -92,6 +96,13 @@ public final class TlsProxyFactory implements TlsStreamFactory
9296
private final ResetFW.Builder resetRW = new ResetFW.Builder();
9397

9498
private final TlsRecordFW tlsRecordRO = new TlsRecordFW();
99+
private final TlsHandshakeMessageFW tlsHandshakeRO = new TlsHandshakeMessageFW();
100+
private final TlsHandshakeClientHelloFW tlsClientHelloRO = new TlsHandshakeClientHelloFW();
101+
private final TlsExtensionsFW tlsExtensionsRO = new TlsExtensionsFW();
102+
private final TlsExtensionFW tlsExtensionRO = new TlsExtensionFW();
103+
private final TlsServerNameExtensionFW tlsServerNameExRO = new TlsServerNameExtensionFW();
104+
private final TlsServerNameTypeFW tlsServerNameTypeRO = new TlsServerNameTypeFW();
105+
private final TlsSniHostNameFW tlsHostnameRO = new TlsSniHostNameFW();
95106

96107
private final TlsProxyDecoder decodeRecord = this::decodeRecord;
97108
private final TlsProxyDecoder decodeRecordBytes = this::decodeRecordBytes;
@@ -446,94 +457,93 @@ private int decodeRecord(
446457
break decode;
447458
}
448459

449-
DirectBuffer message = tlsRecord.payload().value();
450-
int messageProgress = 0;
451-
byte messageType = message.getByte(messageProgress++);
452-
int messageLength =
453-
(message.getByte(messageProgress++) & 0xff) << 16 |
454-
(message.getByte(messageProgress++) & 0xff) << 8 |
455-
(message.getByte(messageProgress++) & 0xff) << 0;
456-
457-
if (messageType != MESSAGE_TYPE_CLIENT_HELLO ||
458-
messageProgress + messageLength != message.capacity())
460+
TlsHandshakeMessageFW tlsHandshake = tlsRecord.payload().get(tlsHandshakeRO::tryWrap);
461+
if (tlsHandshake == null ||
462+
tlsHandshake.typeAndLength() >> 24 != MESSAGE_TYPE_CLIENT_HELLO ||
463+
(tlsHandshake.typeAndLength() & 0x00ff_ffff) + tlsHandshake.limit() > tlsRecord.limit())
459464
{
460465
proxy.doNetReset(traceId);
461466
proxy.decoder = decodeIgnoreAll;
462467
break decode;
463468
}
464469

465-
// skip version
466-
messageProgress += 2;
467-
468-
// skip random
469-
messageProgress += 32;
470-
471-
// skip session id
472-
messageProgress += SIZE_OF_BYTE + (message.getByte(messageProgress) & 0xff);
473-
474-
// cipher suites
475-
messageProgress += SIZE_OF_SHORT + (message.getShort(messageProgress, BIG_ENDIAN) & 0xffff);
476-
477-
// compress methods
478-
messageProgress += SIZE_OF_BYTE + (message.getByte(messageProgress) & 0xff);
479-
480-
int extensionsLength = message.getShort(messageProgress, BIG_ENDIAN) & 0xffff;
481-
messageProgress += SIZE_OF_SHORT;
482-
483-
if (messageProgress + extensionsLength != message.capacity())
470+
TlsHandshakeClientHelloFW tlsClientHello =
471+
tlsClientHelloRO.tryWrap(tlsRecord.buffer(), tlsHandshake.limit(), tlsRecord.limit());
472+
if (tlsClientHello == null ||
473+
tlsClientHello.limit() > tlsRecord.limit())
484474
{
485475
proxy.doNetReset(traceId);
486476
proxy.decoder = decodeIgnoreAll;
487477
break decode;
488478
}
489479

490480
String serverName = null;
491-
while (messageProgress < message.capacity())
492-
{
493-
int extensionType = message.getShort(messageProgress, BIG_ENDIAN) & 0xffff;
494-
messageProgress += SIZE_OF_SHORT;
495-
int extensionLength = message.getShort(messageProgress, BIG_ENDIAN) & 0xffff;
496-
messageProgress += SIZE_OF_SHORT;
497481

498-
if (messageProgress + extensionLength > message.capacity())
482+
if (tlsClientHello.limit() < tlsRecord.limit())
483+
{
484+
TlsExtensionsFW tlsExtensions =
485+
tlsExtensionsRO.tryWrap(tlsRecord.buffer(), tlsClientHello.limit(), tlsRecord.limit());
486+
if (tlsExtensions == null ||
487+
tlsExtensions.limit() != tlsRecord.limit())
499488
{
500489
proxy.doNetReset(traceId);
501490
proxy.decoder = decodeIgnoreAll;
502491
break decode;
503492
}
504493

505-
if (extensionType == EXTENSION_TYPE_SNI)
506-
{
507-
int sniLength = message.getShort(messageProgress, BIG_ENDIAN) & 0xffff;
508-
messageProgress += SIZE_OF_SHORT;
494+
DirectBuffer tlsExtensionsBuf = tlsExtensions.value().value();
495+
int tlsExtensionsLimit = tlsExtensions.length();
496+
int tlsExtensionsProgress = 0;
509497

510-
if (messageProgress + sniLength > message.capacity())
498+
while (tlsExtensionsProgress < tlsExtensionsLimit)
499+
{
500+
TlsExtensionFW tlsExtension =
501+
tlsExtensionRO.tryWrap(tlsExtensionsBuf, tlsExtensionsProgress, tlsExtensionsLimit);
502+
if (tlsExtension == null ||
503+
tlsExtension.limit() > tlsRecord.limit())
511504
{
512505
proxy.doNetReset(traceId);
513506
proxy.decoder = decodeIgnoreAll;
514507
break decode;
515508
}
516509

517-
int sniType = message.getByte(messageProgress++);
518-
if (sniType == SNI_TYPE_HOSTNAME)
510+
if (tlsExtension.type() == EXTENSION_TYPE_SNI)
519511
{
520-
int hostnameLength = message.getShort(messageProgress, BIG_ENDIAN) & 0xffff;
521-
messageProgress += SIZE_OF_SHORT;
512+
TlsServerNameExtensionFW tlsServerNameEx = tlsExtension.data().get(tlsServerNameExRO::tryWrap);
513+
if (tlsServerNameEx == null ||
514+
tlsServerNameEx.limit() > tlsRecord.limit())
515+
{
516+
proxy.doNetReset(traceId);
517+
proxy.decoder = decodeIgnoreAll;
518+
break decode;
519+
}
522520

523-
if (messageProgress + hostnameLength > message.capacity())
521+
int tlsServerNameTypeOffset = tlsServerNameEx.value().offset();
522+
TlsServerNameTypeFW tlsServerNameType =
523+
tlsServerNameTypeRO.tryWrap(tlsExtensionsBuf, tlsServerNameTypeOffset, tlsExtensionsLimit);
524+
if (tlsServerNameType == null)
524525
{
525526
proxy.doNetReset(traceId);
526527
proxy.decoder = decodeIgnoreAll;
527528
break decode;
528529
}
529530

530-
serverName = message.getStringWithoutLengthUtf8(messageProgress, hostnameLength);
531-
messageProgress += hostnameLength;
531+
if (tlsServerNameType.value() == SNI_TYPE_HOSTNAME)
532+
{
533+
TlsSniHostNameFW tlsHostname =
534+
tlsHostnameRO.tryWrap(tlsExtensionsBuf, tlsServerNameType.limit(), tlsExtensionsLimit);
535+
if (tlsHostname == null)
536+
{
537+
proxy.doNetReset(traceId);
538+
proxy.decoder = decodeIgnoreAll;
539+
break decode;
540+
}
541+
542+
serverName = tlsHostname.value().asString();
543+
}
532544
}
533-
}
534-
else
535-
{
536-
messageProgress += extensionLength;
545+
546+
tlsExtensionsProgress = tlsExtension.limit();
537547
}
538548
}
539549

runtime/binding-tls/src/main/zilla/protocol.idl

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,5 +51,66 @@ scope protocol
5151
uint16 length;
5252
octets[length] payload;
5353
}
54+
55+
struct TlsHandshakeMessage
56+
{
57+
int32 typeAndLength;
58+
}
59+
60+
struct TlsSessionId
61+
{
62+
uint8 length;
63+
octets[length] value;
64+
}
65+
66+
struct TlsCipherSuites
67+
{
68+
uint16 length;
69+
octets[length] value;
70+
}
71+
72+
struct TlsCompressionMethods
73+
{
74+
uint8 length;
75+
octets[length] value;
76+
}
77+
78+
struct TlsExtensions
79+
{
80+
uint16 length;
81+
octets[length] value;
82+
}
83+
84+
struct TlsExtension
85+
{
86+
uint16 type;
87+
uint16 length;
88+
octets[length] data;
89+
}
90+
91+
struct TlsServerNameExtension
92+
{
93+
uint16 length;
94+
octets[length] value;
95+
}
96+
97+
struct TlsServerNameType
98+
{
99+
uint8 value;
100+
}
101+
102+
struct TlsSniHostName
103+
{
104+
string16 value;
105+
}
106+
107+
struct TlsHandshakeClientHello
108+
{
109+
TlsProtocolVersion version;
110+
octets[32] random;
111+
TlsSessionId session;
112+
TlsCipherSuites suites;
113+
TlsCompressionMethods methods;
114+
}
54115
}
55116
}

runtime/binding-tls/src/test/java/io/aklivity/zilla/runtime/binding/tls/internal/streams/ProxyIT.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,16 @@ public class ProxyIT
4848
@Rule
4949
public final TestRule chain = outerRule(engine).around(k3po).around(timeout);
5050

51+
@Test
52+
@Configuration("proxy.yaml")
53+
@Specification({
54+
"${proxy}/client/client.hello.without.ext/client",
55+
"${proxy}/server/client.hello.without.ext/server" })
56+
public void shouldProxyClientHelloWithoutExt() throws Exception
57+
{
58+
k3po.finish();
59+
}
60+
5161
@Test
5262
@Configuration("proxy.sni.yaml")
5363
@Specification({
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#
2+
# Copyright 2021-2024 Aklivity Inc.
3+
#
4+
# Aklivity licenses this file to you under the Apache License,
5+
# version 2.0 (the "License"); you may not use this file except in compliance
6+
# with the License. You may obtain a copy of the License at:
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
# License for the specific language governing permissions and limitations
14+
# under the License.
15+
#
16+
17+
---
18+
name: test
19+
bindings:
20+
net0:
21+
type: tls
22+
kind: proxy
23+
exit: net1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#
2+
# Copyright 2021-2024 Aklivity Inc.
3+
#
4+
# Aklivity licenses this file to you under the Apache License,
5+
# version 2.0 (the "License"); you may not use this file except in compliance
6+
# with the License. You may obtain a copy of the License at:
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
# License for the specific language governing permissions and limitations
14+
# under the License.
15+
#
16+
17+
connect "zilla://streams/net0"
18+
option zilla:window 65536
19+
option zilla:transmission "duplex"
20+
option zilla:byteorder "network"
21+
22+
connected
23+
24+
write [0x16] # handshake record
25+
[0x03] [0x03] # version 1.2
26+
45s # length
27+
[0x01] # client hello message
28+
[0x00 0x00 0x29] # length
29+
[0x03] [0x03] # version 1.2
30+
[0x00 0x00 0x00 0x00] # random
31+
[0x00 0x00 0x00 0x00]
32+
[0x00 0x00 0x00 0x00]
33+
[0x00 0x00 0x00 0x00]
34+
[0x00 0x00 0x00 0x00]
35+
[0x00 0x00 0x00 0x00]
36+
[0x00 0x00 0x00 0x00]
37+
[0x00 0x00 0x00 0x00]
38+
[0x00] # legacy session id
39+
2s # cipher suites
40+
[0xc0 0x30] # TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
41+
[0x01] # legacy compression methods
42+
[0x00] # null
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#
2+
# Copyright 2021-2024 Aklivity Inc.
3+
#
4+
# Aklivity licenses this file to you under the Apache License,
5+
# version 2.0 (the "License"); you may not use this file except in compliance
6+
# with the License. You may obtain a copy of the License at:
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
# License for the specific language governing permissions and limitations
14+
# under the License.
15+
#
16+
17+
accept "zilla://streams/net0"
18+
option zilla:window 65536
19+
option zilla:transmission "duplex"
20+
option zilla:byteorder "network"
21+
accepted
22+
23+
connected
24+
25+
read [0x16] # handshake record
26+
[0x03] [0x03] # version 1.2
27+
45s # length
28+
[0x01] # client hello message
29+
[0x00 0x00 0x29] # length
30+
[0x03] [0x03] # version 1.2
31+
[0x00 0x00 0x00 0x00] # random
32+
[0x00 0x00 0x00 0x00]
33+
[0x00 0x00 0x00 0x00]
34+
[0x00 0x00 0x00 0x00]
35+
[0x00 0x00 0x00 0x00]
36+
[0x00 0x00 0x00 0x00]
37+
[0x00 0x00 0x00 0x00]
38+
[0x00 0x00 0x00 0x00]
39+
[0x00] # legacy session id
40+
2s # cipher suites
41+
[0xc0 0x30] # TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
42+
[0x01] # legacy compression methods
43+
[0x00] # null
44+

0 commit comments

Comments
 (0)