Summary
OpcTcpClientTransport invokes config.getChannelPipelineCustomizer() during channel
initialization. A customizer that needs to see raw wire bytes — e.g. a Netty
PcapWriteHandler for capture — naturally calls pipeline.addFirst(...) so it sits
at position 0, before any of the UASC codecs.
After the Hello/Acknowledge handshake completes, UascClientAcknowledgeHandler adds
the new UascClientMessageHandler at position 0 unconditionally
(UascClientAcknowledgeHandler.java:257):
ctx.pipeline().addFirst(messageHandler);
This displaces any customizer handler that was at the head. Because
UascClientMessageHandler is a ByteToMessageCodec, it consumes inbound ByteBufs
before they reach the displaced customizer handler — so a pcap capture that worked
during Hello/Ack goes silent for the rest of the session, capturing only the
handshake.
The same problem applies to outbound: customizer handlers were positioned to see
encoded bytes after the codec runs, but with the new codec in front of them, they
see un-encoded objects.
Asymmetry with the server side
UascServerHelloHandler.onHello already does this correctly
(UascServerHelloHandler.java:212-213):
ctx.pipeline().addLast(asymmetricHandler);
ctx.pipeline().remove(this);
New handler goes to the tail; the existing customizer handler at the head stays
there and continues to see raw wire bytes. Server-side captures work correctly.
Reproducer
- Build a customizer that calls
pipeline.addFirst(\"pcap\", new PcapWriteHandler(...)).
- Pass it to
OpcTcpClientTransportConfigBuilder.setChannelPipelineCustomizer(...).
- Connect with the resulting client.
- Inspect the resulting pcap: only the Hello + Acknowledge messages are present;
OpenSecureChannelRequest and everything after it is missing.
Proposed fix
UascClientMessageHandler only needs to be before DelegatingUascResponseHandler
in the inbound chain so that decoded objects flow into the response handler. That's a
relative ordering, not "must be at position 0". Use addBefore(...) against the
response handler instead of addFirst(...):
// UascClientAcknowledgeHandler.onAcknowledge, replace:
// ctx.pipeline().addFirst(messageHandler);
// with something like:
ChannelHandlerContext responseCtx =
ctx.pipeline().context(DelegatingUascResponseHandler.class);
if (responseCtx != null) {
ctx.pipeline().addBefore(responseCtx.name(), null, messageHandler);
} else {
// Fallback: shouldn't happen given normal pipeline construction.
ctx.pipeline().addFirst(messageHandler);
}
Resulting pipeline (with a customizer-installed pcap at the head):
[pcap, UascClientMessageHandler, DelegatingUascResponseHandler, UascClientAcknowledgeHandler]
After UascClientAcknowledgeHandler removes itself, this matches the symmetric form
of the server pipeline and the customizer handler keeps seeing raw wire bytes.
Environment
- Milo 1.1.3
- Netty 4.1.127.Final
Summary
OpcTcpClientTransportinvokesconfig.getChannelPipelineCustomizer()during channelinitialization. A customizer that needs to see raw wire bytes — e.g. a Netty
PcapWriteHandlerfor capture — naturally callspipeline.addFirst(...)so it sitsat position 0, before any of the UASC codecs.
After the Hello/Acknowledge handshake completes,
UascClientAcknowledgeHandleraddsthe new
UascClientMessageHandlerat position 0 unconditionally(
UascClientAcknowledgeHandler.java:257):This displaces any customizer handler that was at the head. Because
UascClientMessageHandleris aByteToMessageCodec, it consumes inboundByteBufsbefore they reach the displaced customizer handler — so a pcap capture that worked
during Hello/Ack goes silent for the rest of the session, capturing only the
handshake.
The same problem applies to outbound: customizer handlers were positioned to see
encoded bytes after the codec runs, but with the new codec in front of them, they
see un-encoded objects.
Asymmetry with the server side
UascServerHelloHandler.onHelloalready does this correctly(
UascServerHelloHandler.java:212-213):New handler goes to the tail; the existing customizer handler at the head stays
there and continues to see raw wire bytes. Server-side captures work correctly.
Reproducer
pipeline.addFirst(\"pcap\", new PcapWriteHandler(...)).OpcTcpClientTransportConfigBuilder.setChannelPipelineCustomizer(...).OpenSecureChannelRequestand everything after it is missing.Proposed fix
UascClientMessageHandleronly needs to be beforeDelegatingUascResponseHandlerin the inbound chain so that decoded objects flow into the response handler. That's a
relative ordering, not "must be at position 0". Use
addBefore(...)against theresponse handler instead of
addFirst(...):Resulting pipeline (with a customizer-installed
pcapat the head):After
UascClientAcknowledgeHandlerremoves itself, this matches the symmetric formof the server pipeline and the customizer handler keeps seeing raw wire bytes.
Environment