Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

netty: add soft Metadata size limit enforcement. #11603

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions netty/src/main/java/io/grpc/netty/AbstractNettyHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ abstract class AbstractNettyHandler extends GrpcHttp2ConnectionHandler {

private final int initialConnectionWindow;
private final FlowControlPinger flowControlPing;

protected final int maxHeaderListSize;
protected final int softLimitHeaderListSize;
private boolean autoTuneFlowControlOn;
private ChannelHandlerContext ctx;
private boolean initialWindowSent = false;
Expand All @@ -58,7 +59,9 @@ abstract class AbstractNettyHandler extends GrpcHttp2ConnectionHandler {
ChannelLogger negotiationLogger,
boolean autoFlowControl,
PingLimiter pingLimiter,
Ticker ticker) {
Ticker ticker,
int maxHeaderListSize,
int softLimitHeaderListSize) {
super(channelUnused, decoder, encoder, initialSettings, negotiationLogger);

// During a graceful shutdown, wait until all streams are closed.
Expand All @@ -73,6 +76,8 @@ abstract class AbstractNettyHandler extends GrpcHttp2ConnectionHandler {
}
this.flowControlPing = new FlowControlPinger(pingLimiter);
this.ticker = checkNotNull(ticker, "ticker");
this.maxHeaderListSize = maxHeaderListSize;
this.softLimitHeaderListSize = softLimitHeaderListSize;
}

@Override
Expand Down
132 changes: 111 additions & 21 deletions netty/src/main/java/io/grpc/netty/NettyChannelBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@
private boolean autoFlowControl = DEFAULT_AUTO_FLOW_CONTROL;
private int flowControlWindow = DEFAULT_FLOW_CONTROL_WINDOW;
private int maxHeaderListSize = GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE;
private int softLimitHeaderListSize = GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE;
private int maxInboundMessageSize = GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE;
private long keepAliveTimeNanos = KEEPALIVE_TIME_NANOS_DISABLED;
private long keepAliveTimeoutNanos = DEFAULT_KEEPALIVE_TIMEOUT_NANOS;
Expand Down Expand Up @@ -451,6 +452,42 @@
public NettyChannelBuilder maxInboundMetadataSize(int bytes) {
checkArgument(bytes > 0, "maxInboundMetadataSize must be > 0");
this.maxHeaderListSize = bytes;
// Clear the soft limit setting, by setting soft limit to maxInboundMetadataSize. The
// maxInboundMetadataSize will take precedence be applied before soft limit check.
this.softLimitHeaderListSize = bytes;
return this;

Check warning on line 458 in netty/src/main/java/io/grpc/netty/NettyChannelBuilder.java

View check run for this annotation

Codecov / codecov/patch

netty/src/main/java/io/grpc/netty/NettyChannelBuilder.java#L457-L458

Added lines #L457 - L458 were not covered by tests
}

/**
* Sets the size of metadata that advised to not exceed. When a metadata with size larger than the
* soft limit is encountered there will be a probability the RPC will fail. The chance of failing
* increases as the metadata size approaches the hard limit. {@code Integer.MAX_VALUE} disables
* the enforcement. The default is implementation-dependent, but is not generally less than 8 KiB
* and may be unlimited.
*
* <p>This is cumulative size of the metadata. The precise calculation is
* implementation-dependent, but implementations are encouraged to follow the calculation used
* for
* <a href="http://httpwg.org/specs/rfc7540.html#rfc.section.6.5.2">HTTP/2's
* SETTINGS_MAX_HEADER_LIST_SIZE</a>. It sums the bytes from each entry's key and value, plus 32
* bytes of overhead per entry.
*
* <P>Note: gRPC has approximately 600B internal Metadata.
*
* @param soft the soft size limit of received metadata
* @param max the hard size limit of received metadata
* @return this
* @throws IllegalArgumentException if soft and/or max is non-positive, or max smaller than
* soft
* @since 1.68.0
*/
@CanIgnoreReturnValue
public NettyChannelBuilder maxInboundMetadataSize(int soft, int max) {
checkArgument(soft > 0, "softLimitHeaderListSize must be > 0");
checkArgument(max > soft,
"maxInboundMetadataSize must be greater than softLimitHeaderListSize");
this.softLimitHeaderListSize = soft;
this.maxHeaderListSize = max;

Check warning on line 490 in netty/src/main/java/io/grpc/netty/NettyChannelBuilder.java

View check run for this annotation

Codecov / codecov/patch

netty/src/main/java/io/grpc/netty/NettyChannelBuilder.java#L489-L490

Added lines #L489 - L490 were not covered by tests
return this;
}

Expand Down Expand Up @@ -572,10 +609,22 @@

ProtocolNegotiator negotiator = protocolNegotiatorFactory.newNegotiator();
return new NettyTransportFactory(
negotiator, channelFactory, channelOptions,
eventLoopGroupPool, autoFlowControl, flowControlWindow, maxInboundMessageSize,
maxHeaderListSize, keepAliveTimeNanos, keepAliveTimeoutNanos, keepAliveWithoutCalls,
transportTracerFactory, localSocketPicker, useGetForSafeMethods, transportSocketType);
negotiator,
channelFactory,
channelOptions,
eventLoopGroupPool,
autoFlowControl,
flowControlWindow,
maxInboundMessageSize,
maxHeaderListSize,
softLimitHeaderListSize,
keepAliveTimeNanos,
keepAliveTimeoutNanos,
keepAliveWithoutCalls,
transportTracerFactory,
localSocketPicker,
useGetForSafeMethods,
transportSocketType);
}

@VisibleForTesting
Expand Down Expand Up @@ -709,6 +758,7 @@
private final int flowControlWindow;
private final int maxMessageSize;
private final int maxHeaderListSize;
private final int softLimitHeaderListSize;
private final long keepAliveTimeNanos;
private final AtomicBackoff keepAliveBackoff;
private final long keepAliveTimeoutNanos;
Expand All @@ -723,11 +773,20 @@
NettyTransportFactory(
ProtocolNegotiator protocolNegotiator,
ChannelFactory<? extends Channel> channelFactory,
Map<ChannelOption<?>, ?> channelOptions, ObjectPool<? extends EventLoopGroup> groupPool,
boolean autoFlowControl, int flowControlWindow, int maxMessageSize, int maxHeaderListSize,
long keepAliveTimeNanos, long keepAliveTimeoutNanos, boolean keepAliveWithoutCalls,
TransportTracer.Factory transportTracerFactory, LocalSocketPicker localSocketPicker,
boolean useGetForSafeMethods, Class<? extends SocketAddress> transportSocketType) {
Map<ChannelOption<?>, ?> channelOptions,
ObjectPool<? extends EventLoopGroup> groupPool,
boolean autoFlowControl,
int flowControlWindow,
int maxMessageSize,
int maxHeaderListSize,
int softLimitHeaderListSize,
long keepAliveTimeNanos,
long keepAliveTimeoutNanos,
boolean keepAliveWithoutCalls,
TransportTracer.Factory transportTracerFactory,
LocalSocketPicker localSocketPicker,
boolean useGetForSafeMethods,
Class<? extends SocketAddress> transportSocketType) {
this.protocolNegotiator = checkNotNull(protocolNegotiator, "protocolNegotiator");
this.channelFactory = channelFactory;
this.channelOptions = new HashMap<ChannelOption<?>, Object>(channelOptions);
Expand All @@ -737,6 +796,7 @@
this.flowControlWindow = flowControlWindow;
this.maxMessageSize = maxMessageSize;
this.maxHeaderListSize = maxHeaderListSize;
this.softLimitHeaderListSize = softLimitHeaderListSize;
this.keepAliveTimeNanos = keepAliveTimeNanos;
this.keepAliveBackoff = new AtomicBackoff("keepalive time nanos", keepAliveTimeNanos);
this.keepAliveTimeoutNanos = keepAliveTimeoutNanos;
Expand Down Expand Up @@ -773,13 +833,30 @@
};

// TODO(carl-mastrangelo): Pass channelLogger in.
NettyClientTransport transport = new NettyClientTransport(
serverAddress, channelFactory, channelOptions, group,
localNegotiator, autoFlowControl, flowControlWindow,
maxMessageSize, maxHeaderListSize, keepAliveTimeNanosState.get(), keepAliveTimeoutNanos,
keepAliveWithoutCalls, options.getAuthority(), options.getUserAgent(),
tooManyPingsRunnable, transportTracerFactory.create(), options.getEagAttributes(),
localSocketPicker, channelLogger, useGetForSafeMethods, Ticker.systemTicker());
NettyClientTransport transport =
new NettyClientTransport(
serverAddress,
channelFactory,
channelOptions,
group,
localNegotiator,
autoFlowControl,
flowControlWindow,
maxMessageSize,
maxHeaderListSize,
softLimitHeaderListSize,
keepAliveTimeNanosState.get(),
keepAliveTimeoutNanos,
keepAliveWithoutCalls,
options.getAuthority(),
options.getUserAgent(),
tooManyPingsRunnable,
transportTracerFactory.create(),
options.getEagAttributes(),
localSocketPicker,
channelLogger,
useGetForSafeMethods,
Ticker.systemTicker());
return transport;
}

Expand All @@ -795,11 +872,24 @@
if (result.error != null) {
return null;
}
ClientTransportFactory factory = new NettyTransportFactory(
result.negotiator.newNegotiator(), channelFactory, channelOptions, groupPool,
autoFlowControl, flowControlWindow, maxMessageSize, maxHeaderListSize, keepAliveTimeNanos,
keepAliveTimeoutNanos, keepAliveWithoutCalls, transportTracerFactory, localSocketPicker,
useGetForSafeMethods, transportSocketType);
ClientTransportFactory factory =
new NettyTransportFactory(
result.negotiator.newNegotiator(),
channelFactory,
channelOptions,
groupPool,
autoFlowControl,
flowControlWindow,
maxMessageSize,
maxHeaderListSize,
softLimitHeaderListSize,
keepAliveTimeNanos,
keepAliveTimeoutNanos,
keepAliveWithoutCalls,
transportTracerFactory,
localSocketPicker,
useGetForSafeMethods,
transportSocketType);
return new SwapChannelCredentialsResult(factory, result.callCredentials);
}

Expand Down
48 changes: 44 additions & 4 deletions netty/src/main/java/io/grpc/netty/NettyClientHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@
boolean autoFlowControl,
int flowControlWindow,
int maxHeaderListSize,
int softLimitHeaderListSize,
Supplier<Stopwatch> stopwatchFactory,
Runnable tooManyPingsRunnable,
TransportTracer transportTracer,
Expand Down Expand Up @@ -171,6 +172,7 @@
autoFlowControl,
flowControlWindow,
maxHeaderListSize,
softLimitHeaderListSize,
stopwatchFactory,
tooManyPingsRunnable,
transportTracer,
Expand All @@ -190,6 +192,7 @@
boolean autoFlowControl,
int flowControlWindow,
int maxHeaderListSize,
int softLimitHeaderListSize,
Supplier<Stopwatch> stopwatchFactory,
Runnable tooManyPingsRunnable,
TransportTracer transportTracer,
Expand All @@ -202,6 +205,8 @@
Preconditions.checkNotNull(lifecycleManager, "lifecycleManager");
Preconditions.checkArgument(flowControlWindow > 0, "flowControlWindow must be positive");
Preconditions.checkArgument(maxHeaderListSize > 0, "maxHeaderListSize must be positive");
Preconditions.checkArgument(softLimitHeaderListSize > 0,
"softLimitHeaderListSize must be positive");
Preconditions.checkNotNull(stopwatchFactory, "stopwatchFactory");
Preconditions.checkNotNull(tooManyPingsRunnable, "tooManyPingsRunnable");
Preconditions.checkNotNull(eagAttributes, "eagAttributes");
Expand Down Expand Up @@ -247,7 +252,9 @@
authority,
autoFlowControl,
pingCounter,
ticker);
ticker,
maxHeaderListSize,
softLimitHeaderListSize);
}

private NettyClientHandler(
Expand All @@ -264,9 +271,20 @@
String authority,
boolean autoFlowControl,
PingLimiter pingLimiter,
Ticker ticker) {
super(/* channelUnused= */ null, decoder, encoder, settings,
negotiationLogger, autoFlowControl, pingLimiter, ticker);
Ticker ticker,
int maxHeaderListSize,
int softLimitHeaderListSize) {
super(
/* channelUnused= */ null,
decoder,
encoder,
settings,
negotiationLogger,
autoFlowControl,
pingLimiter,
ticker,
maxHeaderListSize,
softLimitHeaderListSize);
this.lifecycleManager = lifecycleManager;
this.keepAliveManager = keepAliveManager;
this.stopwatchFactory = stopwatchFactory;
Expand Down Expand Up @@ -380,6 +398,28 @@
if (streamId != Http2CodecUtil.HTTP_UPGRADE_STREAM_ID) {
NettyClientStream.TransportState stream = clientStream(requireHttp2Stream(streamId));
PerfMark.event("NettyClientHandler.onHeadersRead", stream.tag());
// check metadata size vs soft limit
int h2HeadersSize = Utils.getH2HeadersSize(headers);
boolean shouldFail =
Utils.shouldRejectOnMetadataSizeSoftLimitExceeded(
h2HeadersSize, softLimitHeaderListSize, maxHeaderListSize);
if (shouldFail && endStream) {
throw Status.RESOURCE_EXHAUSTED
.withDescription(
String.format(

Check warning on line 409 in netty/src/main/java/io/grpc/netty/NettyClientHandler.java

View check run for this annotation

Codecov / codecov/patch

netty/src/main/java/io/grpc/netty/NettyClientHandler.java#L407-L409

Added lines #L407 - L409 were not covered by tests
"Server Status + Trailers of size %d exceeded Metadata size soft limit: %d",
h2HeadersSize,
softLimitHeaderListSize))
.asRuntimeException();

Check warning on line 413 in netty/src/main/java/io/grpc/netty/NettyClientHandler.java

View check run for this annotation

Codecov / codecov/patch

netty/src/main/java/io/grpc/netty/NettyClientHandler.java#L411-L413

Added lines #L411 - L413 were not covered by tests
} else if (shouldFail) {
throw Status.RESOURCE_EXHAUSTED
.withDescription(
String.format(

Check warning on line 417 in netty/src/main/java/io/grpc/netty/NettyClientHandler.java

View check run for this annotation

Codecov / codecov/patch

netty/src/main/java/io/grpc/netty/NettyClientHandler.java#L415-L417

Added lines #L415 - L417 were not covered by tests
"Server Headers of size %d exceeded Metadata size soft limit: %d",
h2HeadersSize,
softLimitHeaderListSize))
.asRuntimeException();

Check warning on line 421 in netty/src/main/java/io/grpc/netty/NettyClientHandler.java

View check run for this annotation

Codecov / codecov/patch

netty/src/main/java/io/grpc/netty/NettyClientHandler.java#L419-L421

Added lines #L419 - L421 were not covered by tests
}
stream.transportHeadersReceived(headers, endStream);
}

Expand Down
Loading