netty: add soft Metadata size limit enforcement. (#11603) · grpc/grpc-java@735b3f3

@@ -104,6 +104,7 @@ public final class NettyChannelBuilder extends ForwardingChannelBuilder2<NettyCh

104104

private boolean autoFlowControl = DEFAULT_AUTO_FLOW_CONTROL;

105105

private int flowControlWindow = DEFAULT_FLOW_CONTROL_WINDOW;

106106

private int maxHeaderListSize = GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE;

107+

private int softLimitHeaderListSize = GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE;

107108

private int maxInboundMessageSize = GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE;

108109

private long keepAliveTimeNanos = KEEPALIVE_TIME_NANOS_DISABLED;

109110

private long keepAliveTimeoutNanos = DEFAULT_KEEPALIVE_TIMEOUT_NANOS;

@@ -452,6 +453,40 @@ public NettyChannelBuilder maxHeaderListSize(int maxHeaderListSize) {

452453

public NettyChannelBuilder maxInboundMetadataSize(int bytes) {

453454

checkArgument(bytes > 0, "maxInboundMetadataSize must be > 0");

454455

this.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;

455490

return this;

456491

}

457492

@@ -573,10 +608,22 @@ ClientTransportFactory buildTransportFactory() {

573608574609

ProtocolNegotiator negotiator = protocolNegotiatorFactory.newNegotiator();

575610

return 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

710757

private final int flowControlWindow;

711758

private final int maxMessageSize;

712759

private final int maxHeaderListSize;

760+

private final int softLimitHeaderListSize;

713761

private final long keepAliveTimeNanos;

714762

private final AtomicBackoff keepAliveBackoff;

715763

private final long keepAliveTimeoutNanos;

@@ -724,11 +772,20 @@ private static final class NettyTransportFactory implements ClientTransportFacto

724772

NettyTransportFactory(

725773

ProtocolNegotiator protocolNegotiator,

726774

ChannelFactory<? 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) {

732789

this.protocolNegotiator = checkNotNull(protocolNegotiator, "protocolNegotiator");

733790

this.channelFactory = channelFactory;

734791

this.channelOptions = new HashMap<ChannelOption<?>, Object>(channelOptions);

@@ -738,6 +795,7 @@ private static final class NettyTransportFactory implements ClientTransportFacto

738795

this.flowControlWindow = flowControlWindow;

739796

this.maxMessageSize = maxMessageSize;

740797

this.maxHeaderListSize = maxHeaderListSize;

798+

this.softLimitHeaderListSize = softLimitHeaderListSize;

741799

this.keepAliveTimeNanos = keepAliveTimeNanos;

742800

this.keepAliveBackoff = new AtomicBackoff("keepalive time nanos", keepAliveTimeNanos);

743801

this.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());

784859

return transport;

785860

}

786861

@@ -796,11 +871,24 @@ public SwapChannelCredentialsResult swapChannelCredentials(ChannelCredentials ch

796871

if (result.error != null) {

797872

return 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);

804892

return new SwapChannelCredentialsResult(factory, result.callCredentials);

805893

}

806894