netty: add soft Metadata size limit enforcement. (#11603) · grpc/grpc-java@735b3f3
@@ -104,6 +104,7 @@ public final class NettyChannelBuilder extends ForwardingChannelBuilder2<NettyCh
104104private boolean autoFlowControl = DEFAULT_AUTO_FLOW_CONTROL;
105105private int flowControlWindow = DEFAULT_FLOW_CONTROL_WINDOW;
106106private int maxHeaderListSize = GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE;
107+private int softLimitHeaderListSize = GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE;
107108private int maxInboundMessageSize = GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE;
108109private long keepAliveTimeNanos = KEEPALIVE_TIME_NANOS_DISABLED;
109110private long keepAliveTimeoutNanos = DEFAULT_KEEPALIVE_TIMEOUT_NANOS;
@@ -452,6 +453,40 @@ public NettyChannelBuilder maxHeaderListSize(int maxHeaderListSize) {
452453public NettyChannelBuilder maxInboundMetadataSize(int bytes) {
453454checkArgument(bytes > 0, "maxInboundMetadataSize must be > 0");
454455this.maxHeaderListSize = bytes;
456+// Clear the soft limit setting, by setting soft limit to maxInboundMetadataSize. The
457+// maxInboundMetadataSize will take precedence be applied before soft limit check.
458+this.softLimitHeaderListSize = bytes;
459+return this;
460+ }
461+462+/**
463+ * Sets the size of metadata that clients are advised to not exceed. When a metadata with size
464+ * larger than the soft limit is encountered there will be a probability the RPC will fail. The
465+ * chance of failing increases as the metadata size approaches the hard limit.
466+ * {@code Integer.MAX_VALUE} disables the enforcement. The default is implementation-dependent,
467+ * but is not generally less than 8 KiB and may be unlimited.
468+ *
469+ * <p>This is cumulative size of the metadata. The precise calculation is
470+ * implementation-dependent, but implementations are encouraged to follow the calculation used
471+ * for
472+ * <a href="http://httpwg.org/specs/rfc7540.html#rfc.section.6.5.2">HTTP/2's
473+ * SETTINGS_MAX_HEADER_LIST_SIZE</a>. It sums the bytes from each entry's key and value, plus 32
474+ * bytes of overhead per entry.
475+ *
476+ * @param soft the soft size limit of received metadata
477+ * @param max the hard size limit of received metadata
478+ * @return this
479+ * @throws IllegalArgumentException if soft and/or max is non-positive, or max smaller than
480+ * soft
481+ * @since 1.68.0
482+ */
483+@CanIgnoreReturnValue
484+public NettyChannelBuilder maxInboundMetadataSize(int soft, int max) {
485+checkArgument(soft > 0, "softLimitHeaderListSize must be > 0");
486+checkArgument(max > soft,
487+"maxInboundMetadataSize must be greater than softLimitHeaderListSize");
488+this.softLimitHeaderListSize = soft;
489+this.maxHeaderListSize = max;
455490return this;
456491 }
457492@@ -573,10 +608,22 @@ ClientTransportFactory buildTransportFactory() {
573608574609ProtocolNegotiator negotiator = protocolNegotiatorFactory.newNegotiator();
575610return new NettyTransportFactory(
576-negotiator, channelFactory, channelOptions,
577-eventLoopGroupPool, autoFlowControl, flowControlWindow, maxInboundMessageSize,
578-maxHeaderListSize, keepAliveTimeNanos, keepAliveTimeoutNanos, keepAliveWithoutCalls,
579-transportTracerFactory, localSocketPicker, useGetForSafeMethods, transportSocketType);
611+negotiator,
612+channelFactory,
613+channelOptions,
614+eventLoopGroupPool,
615+autoFlowControl,
616+flowControlWindow,
617+maxInboundMessageSize,
618+maxHeaderListSize,
619+softLimitHeaderListSize,
620+keepAliveTimeNanos,
621+keepAliveTimeoutNanos,
622+keepAliveWithoutCalls,
623+transportTracerFactory,
624+localSocketPicker,
625+useGetForSafeMethods,
626+transportSocketType);
580627 }
581628582629@VisibleForTesting
@@ -710,6 +757,7 @@ private static final class NettyTransportFactory implements ClientTransportFacto
710757private final int flowControlWindow;
711758private final int maxMessageSize;
712759private final int maxHeaderListSize;
760+private final int softLimitHeaderListSize;
713761private final long keepAliveTimeNanos;
714762private final AtomicBackoff keepAliveBackoff;
715763private final long keepAliveTimeoutNanos;
@@ -724,11 +772,20 @@ private static final class NettyTransportFactory implements ClientTransportFacto
724772NettyTransportFactory(
725773ProtocolNegotiator protocolNegotiator,
726774ChannelFactory<? extends Channel> channelFactory,
727-Map<ChannelOption<?>, ?> channelOptions, ObjectPool<? extends EventLoopGroup> groupPool,
728-boolean autoFlowControl, int flowControlWindow, int maxMessageSize, int maxHeaderListSize,
729-long keepAliveTimeNanos, long keepAliveTimeoutNanos, boolean keepAliveWithoutCalls,
730-TransportTracer.Factory transportTracerFactory, LocalSocketPicker localSocketPicker,
731-boolean useGetForSafeMethods, Class<? extends SocketAddress> transportSocketType) {
775+Map<ChannelOption<?>, ?> channelOptions,
776+ObjectPool<? extends EventLoopGroup> groupPool,
777+boolean autoFlowControl,
778+int flowControlWindow,
779+int maxMessageSize,
780+int maxHeaderListSize,
781+int softLimitHeaderListSize,
782+long keepAliveTimeNanos,
783+long keepAliveTimeoutNanos,
784+boolean keepAliveWithoutCalls,
785+TransportTracer.Factory transportTracerFactory,
786+LocalSocketPicker localSocketPicker,
787+boolean useGetForSafeMethods,
788+Class<? extends SocketAddress> transportSocketType) {
732789this.protocolNegotiator = checkNotNull(protocolNegotiator, "protocolNegotiator");
733790this.channelFactory = channelFactory;
734791this.channelOptions = new HashMap<ChannelOption<?>, Object>(channelOptions);
@@ -738,6 +795,7 @@ private static final class NettyTransportFactory implements ClientTransportFacto
738795this.flowControlWindow = flowControlWindow;
739796this.maxMessageSize = maxMessageSize;
740797this.maxHeaderListSize = maxHeaderListSize;
798+this.softLimitHeaderListSize = softLimitHeaderListSize;
741799this.keepAliveTimeNanos = keepAliveTimeNanos;
742800this.keepAliveBackoff = new AtomicBackoff("keepalive time nanos", keepAliveTimeNanos);
743801this.keepAliveTimeoutNanos = keepAliveTimeoutNanos;
@@ -774,13 +832,30 @@ public void run() {
774832 };
775833776834// TODO(carl-mastrangelo): Pass channelLogger in.
777-NettyClientTransport transport = new NettyClientTransport(
778-serverAddress, channelFactory, channelOptions, group,
779-localNegotiator, autoFlowControl, flowControlWindow,
780-maxMessageSize, maxHeaderListSize, keepAliveTimeNanosState.get(), keepAliveTimeoutNanos,
781-keepAliveWithoutCalls, options.getAuthority(), options.getUserAgent(),
782-tooManyPingsRunnable, transportTracerFactory.create(), options.getEagAttributes(),
783-localSocketPicker, channelLogger, useGetForSafeMethods, Ticker.systemTicker());
835+NettyClientTransport transport =
836+new NettyClientTransport(
837+serverAddress,
838+channelFactory,
839+channelOptions,
840+group,
841+localNegotiator,
842+autoFlowControl,
843+flowControlWindow,
844+maxMessageSize,
845+maxHeaderListSize,
846+softLimitHeaderListSize,
847+keepAliveTimeNanosState.get(),
848+keepAliveTimeoutNanos,
849+keepAliveWithoutCalls,
850+options.getAuthority(),
851+options.getUserAgent(),
852+tooManyPingsRunnable,
853+transportTracerFactory.create(),
854+options.getEagAttributes(),
855+localSocketPicker,
856+channelLogger,
857+useGetForSafeMethods,
858+Ticker.systemTicker());
784859return transport;
785860 }
786861@@ -796,11 +871,24 @@ public SwapChannelCredentialsResult swapChannelCredentials(ChannelCredentials ch
796871if (result.error != null) {
797872return null;
798873 }
799-ClientTransportFactory factory = new NettyTransportFactory(
800-result.negotiator.newNegotiator(), channelFactory, channelOptions, groupPool,
801-autoFlowControl, flowControlWindow, maxMessageSize, maxHeaderListSize, keepAliveTimeNanos,
802-keepAliveTimeoutNanos, keepAliveWithoutCalls, transportTracerFactory, localSocketPicker,
803-useGetForSafeMethods, transportSocketType);
874+ClientTransportFactory factory =
875+new NettyTransportFactory(
876+result.negotiator.newNegotiator(),
877+channelFactory,
878+channelOptions,
879+groupPool,
880+autoFlowControl,
881+flowControlWindow,
882+maxMessageSize,
883+maxHeaderListSize,
884+softLimitHeaderListSize,
885+keepAliveTimeNanos,
886+keepAliveTimeoutNanos,
887+keepAliveWithoutCalls,
888+transportTracerFactory,
889+localSocketPicker,
890+useGetForSafeMethods,
891+transportSocketType);
804892return new SwapChannelCredentialsResult(factory, result.callCredentials);
805893 }
806894