Class Http3ConnectUdpHandler

java.lang.Object
io.netty.channel.ChannelHandlerAdapter
io.netty.channel.ChannelInboundHandlerAdapter
org.mockserver.netty.http3.Http3ConnectUdpHandler
All Implemented Interfaces:
io.netty.channel.ChannelHandler, io.netty.channel.ChannelInboundHandler

public class Http3ConnectUdpHandler extends io.netty.channel.ChannelInboundHandlerAdapter
HTTP/3 CONNECT-UDP (MASQUE, RFC 9298) relay handler.

This handler intercepts HTTP/3 extended CONNECT requests with :protocol = connect-udp when the http3ConnectUdpEnabled configuration flag is set to true. It is inserted into the QUIC stream pipeline before the regular Http3MockServerHandler so that CONNECT-UDP requests are handled here and non-CONNECT requests pass through to the normal mock pipeline.

Relay design:

  1. The handler recognises an extended CONNECT request by checking :method = CONNECT and :protocol = connect-udp.
  2. The target authority is parsed from the :authority pseudo-header (host:port).
  3. A UDP NioDatagramChannel is opened and connected to the target address.
  4. The handler responds with 200 OK to indicate the tunnel is established.
  5. Subsequent HTTP/3 DATA frames received on the QUIC stream are forwarded as UDP datagrams to the target. Each DATA frame payload is sent as one UDP datagram (simple 1:1 mapping without capsule framing -- suitable for testing).
  6. UDP datagrams received from the target are forwarded back to the client as HTTP/3 DATA frames on the same QUIC stream.
  7. When the QUIC stream is closed (input closed, error, or handler removal), the UDP channel is closed to release resources.

Plain CONNECT requests (without :protocol) are passed through to the next handler in the pipeline (the mock handler) -- MockServer is not a TCP forward proxy.

Destination restriction (SSRF mitigation): before a tunnel is established the requested target is subjected to two checks:

  1. an optional allowlist (http3ConnectUdpAllowedTargets) — when non-empty, only targets whose host or host:port matches an allowlist entry may be relayed; everything else is refused with 403;
  2. the same private-network / metadata block the forward proxy uses (forwardProxyBlockPrivateNetworks via InetAddressValidator) — when enabled, a target resolving to a loopback, link-local, RFC 1918 / RFC 4193 private, wildcard, or cloud-metadata address is refused with 403.
With an empty allowlist and forwardProxyBlockPrivateNetworks=false (both defaults) behaviour is unchanged — the relay is unrestricted — so existing experimental users are unaffected unless they opt in to a restriction.

Framing note: a production MASQUE proxy would use the HTTP Datagram / Capsule Protocol (RFC 9297) framing. This implementation uses a simplified 1:1 DATA-frame-to-UDP-datagram mapping which is sufficient for testing and mock-server usage. The Capsule Protocol adds a context ID varint prefix; clients that require it should be adapted accordingly.

See Also:
  • Nested Class Summary

    Nested classes/interfaces inherited from interface io.netty.channel.ChannelHandler

    io.netty.channel.ChannelHandler.Sharable
  • Constructor Summary

    Constructors
    Constructor
    Description
    Legacy/test constructor: no configuration, so no allowlist is enforced and the SSRF block falls back to global ConfigurationProperties (default off).
     
  • Method Summary

    Modifier and Type
    Method
    Description
    void
    channelInactive(io.netty.channel.ChannelHandlerContext ctx)
     
    void
    channelRead(io.netty.channel.ChannelHandlerContext ctx, Object msg)
     
    void
    exceptionCaught(io.netty.channel.ChannelHandlerContext ctx, Throwable cause)
     
    void
    handlerRemoved(io.netty.channel.ChannelHandlerContext ctx)
     

    Methods inherited from class io.netty.channel.ChannelInboundHandlerAdapter

    channelActive, channelReadComplete, channelRegistered, channelUnregistered, channelWritabilityChanged, userEventTriggered

    Methods inherited from class io.netty.channel.ChannelHandlerAdapter

    ensureNotSharable, handlerAdded, isSharable

    Methods inherited from class java.lang.Object

    clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

    Methods inherited from interface io.netty.channel.ChannelHandler

    handlerAdded
  • Constructor Details

    • Http3ConnectUdpHandler

      public Http3ConnectUdpHandler()
      Legacy/test constructor: no configuration, so no allowlist is enforced and the SSRF block falls back to global ConfigurationProperties (default off).
    • Http3ConnectUdpHandler

      public Http3ConnectUdpHandler(Configuration configuration)
      Parameters:
      configuration - the MockServer configuration used to resolve the CONNECT-UDP allowlist and the SSRF private-network block
  • Method Details

    • channelRead

      public void channelRead(io.netty.channel.ChannelHandlerContext ctx, Object msg) throws Exception
      Specified by:
      channelRead in interface io.netty.channel.ChannelInboundHandler
      Overrides:
      channelRead in class io.netty.channel.ChannelInboundHandlerAdapter
      Throws:
      Exception
    • channelInactive

      public void channelInactive(io.netty.channel.ChannelHandlerContext ctx) throws Exception
      Specified by:
      channelInactive in interface io.netty.channel.ChannelInboundHandler
      Overrides:
      channelInactive in class io.netty.channel.ChannelInboundHandlerAdapter
      Throws:
      Exception
    • handlerRemoved

      public void handlerRemoved(io.netty.channel.ChannelHandlerContext ctx) throws Exception
      Specified by:
      handlerRemoved in interface io.netty.channel.ChannelHandler
      Overrides:
      handlerRemoved in class io.netty.channel.ChannelHandlerAdapter
      Throws:
      Exception
    • exceptionCaught

      public void exceptionCaught(io.netty.channel.ChannelHandlerContext ctx, Throwable cause)
      Specified by:
      exceptionCaught in interface io.netty.channel.ChannelHandler
      Specified by:
      exceptionCaught in interface io.netty.channel.ChannelInboundHandler
      Overrides:
      exceptionCaught in class io.netty.channel.ChannelInboundHandlerAdapter