Skip to content

Client pipeline customizer handlers are displaced after Hello/Acknowledge handshake #1740

@kevinherron

Description

@kevinherron

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

  1. Build a customizer that calls pipeline.addFirst(\"pcap\", new PcapWriteHandler(...)).
  2. Pass it to OpcTcpClientTransportConfigBuilder.setChannelPipelineCustomizer(...).
  3. Connect with the resulting client.
  4. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions