netty: add soft Metadata size limit enforcement. (#11603)

This commit is contained in:
Ran 2024-10-28 10:25:17 -07:00 committed by GitHub
parent fe350cfd50
commit 735b3f3fe6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 722 additions and 264 deletions

View File

@ -42,7 +42,8 @@ abstract class AbstractNettyHandler extends GrpcHttp2ConnectionHandler {
private final int initialConnectionWindow; private final int initialConnectionWindow;
private final FlowControlPinger flowControlPing; private final FlowControlPinger flowControlPing;
protected final int maxHeaderListSize;
protected final int softLimitHeaderListSize;
private boolean autoTuneFlowControlOn; private boolean autoTuneFlowControlOn;
private ChannelHandlerContext ctx; private ChannelHandlerContext ctx;
private boolean initialWindowSent = false; private boolean initialWindowSent = false;
@ -58,7 +59,9 @@ abstract class AbstractNettyHandler extends GrpcHttp2ConnectionHandler {
ChannelLogger negotiationLogger, ChannelLogger negotiationLogger,
boolean autoFlowControl, boolean autoFlowControl,
PingLimiter pingLimiter, PingLimiter pingLimiter,
Ticker ticker) { Ticker ticker,
int maxHeaderListSize,
int softLimitHeaderListSize) {
super(channelUnused, decoder, encoder, initialSettings, negotiationLogger); super(channelUnused, decoder, encoder, initialSettings, negotiationLogger);
// During a graceful shutdown, wait until all streams are closed. // During a graceful shutdown, wait until all streams are closed.
@ -73,6 +76,8 @@ abstract class AbstractNettyHandler extends GrpcHttp2ConnectionHandler {
} }
this.flowControlPing = new FlowControlPinger(pingLimiter); this.flowControlPing = new FlowControlPinger(pingLimiter);
this.ticker = checkNotNull(ticker, "ticker"); this.ticker = checkNotNull(ticker, "ticker");
this.maxHeaderListSize = maxHeaderListSize;
this.softLimitHeaderListSize = softLimitHeaderListSize;
} }
@Override @Override

View File

@ -104,6 +104,7 @@ public final class NettyChannelBuilder extends ForwardingChannelBuilder2<NettyCh
private boolean autoFlowControl = DEFAULT_AUTO_FLOW_CONTROL; private boolean autoFlowControl = DEFAULT_AUTO_FLOW_CONTROL;
private int flowControlWindow = DEFAULT_FLOW_CONTROL_WINDOW; private int flowControlWindow = DEFAULT_FLOW_CONTROL_WINDOW;
private int maxHeaderListSize = GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE; 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 int maxInboundMessageSize = GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE;
private long keepAliveTimeNanos = KEEPALIVE_TIME_NANOS_DISABLED; private long keepAliveTimeNanos = KEEPALIVE_TIME_NANOS_DISABLED;
private long keepAliveTimeoutNanos = DEFAULT_KEEPALIVE_TIMEOUT_NANOS; private long keepAliveTimeoutNanos = DEFAULT_KEEPALIVE_TIMEOUT_NANOS;
@ -452,6 +453,40 @@ public final class NettyChannelBuilder extends ForwardingChannelBuilder2<NettyCh
public NettyChannelBuilder maxInboundMetadataSize(int bytes) { public NettyChannelBuilder maxInboundMetadataSize(int bytes) {
checkArgument(bytes > 0, "maxInboundMetadataSize must be > 0"); checkArgument(bytes > 0, "maxInboundMetadataSize must be > 0");
this.maxHeaderListSize = bytes; 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;
}
/**
* Sets the size of metadata that clients are 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.
*
* @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;
return this; return this;
} }
@ -573,10 +608,22 @@ public final class NettyChannelBuilder extends ForwardingChannelBuilder2<NettyCh
ProtocolNegotiator negotiator = protocolNegotiatorFactory.newNegotiator(); ProtocolNegotiator negotiator = protocolNegotiatorFactory.newNegotiator();
return new NettyTransportFactory( return new NettyTransportFactory(
negotiator, channelFactory, channelOptions, negotiator,
eventLoopGroupPool, autoFlowControl, flowControlWindow, maxInboundMessageSize, channelFactory,
maxHeaderListSize, keepAliveTimeNanos, keepAliveTimeoutNanos, keepAliveWithoutCalls, channelOptions,
transportTracerFactory, localSocketPicker, useGetForSafeMethods, transportSocketType); eventLoopGroupPool,
autoFlowControl,
flowControlWindow,
maxInboundMessageSize,
maxHeaderListSize,
softLimitHeaderListSize,
keepAliveTimeNanos,
keepAliveTimeoutNanos,
keepAliveWithoutCalls,
transportTracerFactory,
localSocketPicker,
useGetForSafeMethods,
transportSocketType);
} }
@VisibleForTesting @VisibleForTesting
@ -710,6 +757,7 @@ public final class NettyChannelBuilder extends ForwardingChannelBuilder2<NettyCh
private final int flowControlWindow; private final int flowControlWindow;
private final int maxMessageSize; private final int maxMessageSize;
private final int maxHeaderListSize; private final int maxHeaderListSize;
private final int softLimitHeaderListSize;
private final long keepAliveTimeNanos; private final long keepAliveTimeNanos;
private final AtomicBackoff keepAliveBackoff; private final AtomicBackoff keepAliveBackoff;
private final long keepAliveTimeoutNanos; private final long keepAliveTimeoutNanos;
@ -724,11 +772,20 @@ public final class NettyChannelBuilder extends ForwardingChannelBuilder2<NettyCh
NettyTransportFactory( NettyTransportFactory(
ProtocolNegotiator protocolNegotiator, ProtocolNegotiator protocolNegotiator,
ChannelFactory<? extends Channel> channelFactory, ChannelFactory<? extends Channel> channelFactory,
Map<ChannelOption<?>, ?> channelOptions, ObjectPool<? extends EventLoopGroup> groupPool, Map<ChannelOption<?>, ?> channelOptions,
boolean autoFlowControl, int flowControlWindow, int maxMessageSize, int maxHeaderListSize, ObjectPool<? extends EventLoopGroup> groupPool,
long keepAliveTimeNanos, long keepAliveTimeoutNanos, boolean keepAliveWithoutCalls, boolean autoFlowControl,
TransportTracer.Factory transportTracerFactory, LocalSocketPicker localSocketPicker, int flowControlWindow,
boolean useGetForSafeMethods, Class<? extends SocketAddress> transportSocketType) { 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.protocolNegotiator = checkNotNull(protocolNegotiator, "protocolNegotiator");
this.channelFactory = channelFactory; this.channelFactory = channelFactory;
this.channelOptions = new HashMap<ChannelOption<?>, Object>(channelOptions); this.channelOptions = new HashMap<ChannelOption<?>, Object>(channelOptions);
@ -738,6 +795,7 @@ public final class NettyChannelBuilder extends ForwardingChannelBuilder2<NettyCh
this.flowControlWindow = flowControlWindow; this.flowControlWindow = flowControlWindow;
this.maxMessageSize = maxMessageSize; this.maxMessageSize = maxMessageSize;
this.maxHeaderListSize = maxHeaderListSize; this.maxHeaderListSize = maxHeaderListSize;
this.softLimitHeaderListSize = softLimitHeaderListSize;
this.keepAliveTimeNanos = keepAliveTimeNanos; this.keepAliveTimeNanos = keepAliveTimeNanos;
this.keepAliveBackoff = new AtomicBackoff("keepalive time nanos", keepAliveTimeNanos); this.keepAliveBackoff = new AtomicBackoff("keepalive time nanos", keepAliveTimeNanos);
this.keepAliveTimeoutNanos = keepAliveTimeoutNanos; this.keepAliveTimeoutNanos = keepAliveTimeoutNanos;
@ -774,13 +832,30 @@ public final class NettyChannelBuilder extends ForwardingChannelBuilder2<NettyCh
}; };
// TODO(carl-mastrangelo): Pass channelLogger in. // TODO(carl-mastrangelo): Pass channelLogger in.
NettyClientTransport transport = new NettyClientTransport( NettyClientTransport transport =
serverAddress, channelFactory, channelOptions, group, new NettyClientTransport(
localNegotiator, autoFlowControl, flowControlWindow, serverAddress,
maxMessageSize, maxHeaderListSize, keepAliveTimeNanosState.get(), keepAliveTimeoutNanos, channelFactory,
keepAliveWithoutCalls, options.getAuthority(), options.getUserAgent(), channelOptions,
tooManyPingsRunnable, transportTracerFactory.create(), options.getEagAttributes(), group,
localSocketPicker, channelLogger, useGetForSafeMethods, Ticker.systemTicker()); 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; return transport;
} }
@ -796,11 +871,24 @@ public final class NettyChannelBuilder extends ForwardingChannelBuilder2<NettyCh
if (result.error != null) { if (result.error != null) {
return null; return null;
} }
ClientTransportFactory factory = new NettyTransportFactory( ClientTransportFactory factory =
result.negotiator.newNegotiator(), channelFactory, channelOptions, groupPool, new NettyTransportFactory(
autoFlowControl, flowControlWindow, maxMessageSize, maxHeaderListSize, keepAliveTimeNanos, result.negotiator.newNegotiator(),
keepAliveTimeoutNanos, keepAliveWithoutCalls, transportTracerFactory, localSocketPicker, channelFactory,
useGetForSafeMethods, transportSocketType); channelOptions,
groupPool,
autoFlowControl,
flowControlWindow,
maxMessageSize,
maxHeaderListSize,
softLimitHeaderListSize,
keepAliveTimeNanos,
keepAliveTimeoutNanos,
keepAliveWithoutCalls,
transportTracerFactory,
localSocketPicker,
useGetForSafeMethods,
transportSocketType);
return new SwapChannelCredentialsResult(factory, result.callCredentials); return new SwapChannelCredentialsResult(factory, result.callCredentials);
} }

View File

@ -142,6 +142,7 @@ class NettyClientHandler extends AbstractNettyHandler {
boolean autoFlowControl, boolean autoFlowControl,
int flowControlWindow, int flowControlWindow,
int maxHeaderListSize, int maxHeaderListSize,
int softLimitHeaderListSize,
Supplier<Stopwatch> stopwatchFactory, Supplier<Stopwatch> stopwatchFactory,
Runnable tooManyPingsRunnable, Runnable tooManyPingsRunnable,
TransportTracer transportTracer, TransportTracer transportTracer,
@ -171,6 +172,7 @@ class NettyClientHandler extends AbstractNettyHandler {
autoFlowControl, autoFlowControl,
flowControlWindow, flowControlWindow,
maxHeaderListSize, maxHeaderListSize,
softLimitHeaderListSize,
stopwatchFactory, stopwatchFactory,
tooManyPingsRunnable, tooManyPingsRunnable,
transportTracer, transportTracer,
@ -190,6 +192,7 @@ class NettyClientHandler extends AbstractNettyHandler {
boolean autoFlowControl, boolean autoFlowControl,
int flowControlWindow, int flowControlWindow,
int maxHeaderListSize, int maxHeaderListSize,
int softLimitHeaderListSize,
Supplier<Stopwatch> stopwatchFactory, Supplier<Stopwatch> stopwatchFactory,
Runnable tooManyPingsRunnable, Runnable tooManyPingsRunnable,
TransportTracer transportTracer, TransportTracer transportTracer,
@ -202,6 +205,8 @@ class NettyClientHandler extends AbstractNettyHandler {
Preconditions.checkNotNull(lifecycleManager, "lifecycleManager"); Preconditions.checkNotNull(lifecycleManager, "lifecycleManager");
Preconditions.checkArgument(flowControlWindow > 0, "flowControlWindow must be positive"); Preconditions.checkArgument(flowControlWindow > 0, "flowControlWindow must be positive");
Preconditions.checkArgument(maxHeaderListSize > 0, "maxHeaderListSize 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(stopwatchFactory, "stopwatchFactory");
Preconditions.checkNotNull(tooManyPingsRunnable, "tooManyPingsRunnable"); Preconditions.checkNotNull(tooManyPingsRunnable, "tooManyPingsRunnable");
Preconditions.checkNotNull(eagAttributes, "eagAttributes"); Preconditions.checkNotNull(eagAttributes, "eagAttributes");
@ -247,7 +252,9 @@ class NettyClientHandler extends AbstractNettyHandler {
authority, authority,
autoFlowControl, autoFlowControl,
pingCounter, pingCounter,
ticker); ticker,
maxHeaderListSize,
softLimitHeaderListSize);
} }
private NettyClientHandler( private NettyClientHandler(
@ -264,9 +271,20 @@ class NettyClientHandler extends AbstractNettyHandler {
String authority, String authority,
boolean autoFlowControl, boolean autoFlowControl,
PingLimiter pingLimiter, PingLimiter pingLimiter,
Ticker ticker) { Ticker ticker,
super(/* channelUnused= */ null, decoder, encoder, settings, int maxHeaderListSize,
negotiationLogger, autoFlowControl, pingLimiter, ticker); int softLimitHeaderListSize) {
super(
/* channelUnused= */ null,
decoder,
encoder,
settings,
negotiationLogger,
autoFlowControl,
pingLimiter,
ticker,
maxHeaderListSize,
softLimitHeaderListSize);
this.lifecycleManager = lifecycleManager; this.lifecycleManager = lifecycleManager;
this.keepAliveManager = keepAliveManager; this.keepAliveManager = keepAliveManager;
this.stopwatchFactory = stopwatchFactory; this.stopwatchFactory = stopwatchFactory;
@ -380,6 +398,28 @@ class NettyClientHandler extends AbstractNettyHandler {
if (streamId != Http2CodecUtil.HTTP_UPGRADE_STREAM_ID) { if (streamId != Http2CodecUtil.HTTP_UPGRADE_STREAM_ID) {
NettyClientStream.TransportState stream = clientStream(requireHttp2Stream(streamId)); NettyClientStream.TransportState stream = clientStream(requireHttp2Stream(streamId));
PerfMark.event("NettyClientHandler.onHeadersRead", stream.tag()); 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) {
stream.transportReportStatus(Status.RESOURCE_EXHAUSTED
.withDescription(
String.format(
"Server Status + Trailers of size %d exceeded Metadata size soft limit: %d",
h2HeadersSize,
softLimitHeaderListSize)), true, new Metadata());
return;
} else if (shouldFail) {
stream.transportReportStatus(Status.RESOURCE_EXHAUSTED
.withDescription(
String.format(
"Server Headers of size %d exceeded Metadata size soft limit: %d",
h2HeadersSize,
softLimitHeaderListSize)), true, new Metadata());
return;
}
stream.transportHeadersReceived(headers, endStream); stream.transportHeadersReceived(headers, endStream);
} }

View File

@ -83,6 +83,7 @@ class NettyClientTransport implements ConnectionClientTransport {
private final int flowControlWindow; private final int flowControlWindow;
private final int maxMessageSize; private final int maxMessageSize;
private final int maxHeaderListSize; private final int maxHeaderListSize;
private final int softLimitHeaderListSize;
private KeepAliveManager keepAliveManager; private KeepAliveManager keepAliveManager;
private final long keepAliveTimeNanos; private final long keepAliveTimeNanos;
private final long keepAliveTimeoutNanos; private final long keepAliveTimeoutNanos;
@ -106,15 +107,28 @@ class NettyClientTransport implements ConnectionClientTransport {
private final Ticker ticker; private final Ticker ticker;
NettyClientTransport( NettyClientTransport(
SocketAddress address, ChannelFactory<? extends Channel> channelFactory, SocketAddress address,
Map<ChannelOption<?>, ?> channelOptions, EventLoopGroup group, ChannelFactory<? extends Channel> channelFactory,
ProtocolNegotiator negotiator, boolean autoFlowControl, int flowControlWindow, Map<ChannelOption<?>, ?> channelOptions,
int maxMessageSize, int maxHeaderListSize, EventLoopGroup group,
long keepAliveTimeNanos, long keepAliveTimeoutNanos, ProtocolNegotiator negotiator,
boolean keepAliveWithoutCalls, String authority, @Nullable String userAgent, boolean autoFlowControl,
Runnable tooManyPingsRunnable, TransportTracer transportTracer, Attributes eagAttributes, int flowControlWindow,
LocalSocketPicker localSocketPicker, ChannelLogger channelLogger, int maxMessageSize,
boolean useGetForSafeMethods, Ticker ticker) { int maxHeaderListSize,
int softLimitHeaderListSize,
long keepAliveTimeNanos,
long keepAliveTimeoutNanos,
boolean keepAliveWithoutCalls,
String authority,
@Nullable String userAgent,
Runnable tooManyPingsRunnable,
TransportTracer transportTracer,
Attributes eagAttributes,
LocalSocketPicker localSocketPicker,
ChannelLogger channelLogger,
boolean useGetForSafeMethods,
Ticker ticker) {
this.negotiator = Preconditions.checkNotNull(negotiator, "negotiator"); this.negotiator = Preconditions.checkNotNull(negotiator, "negotiator");
this.negotiationScheme = this.negotiator.scheme(); this.negotiationScheme = this.negotiator.scheme();
@ -126,6 +140,7 @@ class NettyClientTransport implements ConnectionClientTransport {
this.flowControlWindow = flowControlWindow; this.flowControlWindow = flowControlWindow;
this.maxMessageSize = maxMessageSize; this.maxMessageSize = maxMessageSize;
this.maxHeaderListSize = maxHeaderListSize; this.maxHeaderListSize = maxHeaderListSize;
this.softLimitHeaderListSize = softLimitHeaderListSize;
this.keepAliveTimeNanos = keepAliveTimeNanos; this.keepAliveTimeNanos = keepAliveTimeNanos;
this.keepAliveTimeoutNanos = keepAliveTimeoutNanos; this.keepAliveTimeoutNanos = keepAliveTimeoutNanos;
this.keepAliveWithoutCalls = keepAliveWithoutCalls; this.keepAliveWithoutCalls = keepAliveWithoutCalls;
@ -220,18 +235,19 @@ class NettyClientTransport implements ConnectionClientTransport {
} }
handler = NettyClientHandler.newHandler( handler = NettyClientHandler.newHandler(
lifecycleManager, lifecycleManager,
keepAliveManager, keepAliveManager,
autoFlowControl, autoFlowControl,
flowControlWindow, flowControlWindow,
maxHeaderListSize, maxHeaderListSize,
GrpcUtil.STOPWATCH_SUPPLIER, softLimitHeaderListSize,
tooManyPingsRunnable, GrpcUtil.STOPWATCH_SUPPLIER,
transportTracer, tooManyPingsRunnable,
eagAttributes, transportTracer,
authorityString, eagAttributes,
channelLogger, authorityString,
ticker); channelLogger,
ticker);
ChannelHandler negotiationHandler = negotiator.newHandler(handler); ChannelHandler negotiationHandler = negotiator.newHandler(handler);

View File

@ -92,6 +92,7 @@ class NettyServer implements InternalServer, InternalWithLogId {
private final int flowControlWindow; private final int flowControlWindow;
private final int maxMessageSize; private final int maxMessageSize;
private final int maxHeaderListSize; private final int maxHeaderListSize;
private final int softLimitHeaderListSize;
private final long keepAliveTimeInNanos; private final long keepAliveTimeInNanos;
private final long keepAliveTimeoutInNanos; private final long keepAliveTimeoutInNanos;
private final long maxConnectionIdleInNanos; private final long maxConnectionIdleInNanos;
@ -123,9 +124,14 @@ class NettyServer implements InternalServer, InternalWithLogId {
ProtocolNegotiator protocolNegotiator, ProtocolNegotiator protocolNegotiator,
List<? extends ServerStreamTracer.Factory> streamTracerFactories, List<? extends ServerStreamTracer.Factory> streamTracerFactories,
TransportTracer.Factory transportTracerFactory, TransportTracer.Factory transportTracerFactory,
int maxStreamsPerConnection, boolean autoFlowControl, int flowControlWindow, int maxStreamsPerConnection,
int maxMessageSize, int maxHeaderListSize, boolean autoFlowControl,
long keepAliveTimeInNanos, long keepAliveTimeoutInNanos, int flowControlWindow,
int maxMessageSize,
int maxHeaderListSize,
int softLimitHeaderListSize,
long keepAliveTimeInNanos,
long keepAliveTimeoutInNanos,
long maxConnectionIdleInNanos, long maxConnectionIdleInNanos,
long maxConnectionAgeInNanos, long maxConnectionAgeGraceInNanos, long maxConnectionAgeInNanos, long maxConnectionAgeGraceInNanos,
boolean permitKeepAliveWithoutCalls, long permitKeepAliveTimeInNanos, boolean permitKeepAliveWithoutCalls, long permitKeepAliveTimeInNanos,
@ -152,6 +158,7 @@ class NettyServer implements InternalServer, InternalWithLogId {
this.flowControlWindow = flowControlWindow; this.flowControlWindow = flowControlWindow;
this.maxMessageSize = maxMessageSize; this.maxMessageSize = maxMessageSize;
this.maxHeaderListSize = maxHeaderListSize; this.maxHeaderListSize = maxHeaderListSize;
this.softLimitHeaderListSize = softLimitHeaderListSize;
this.keepAliveTimeInNanos = keepAliveTimeInNanos; this.keepAliveTimeInNanos = keepAliveTimeInNanos;
this.keepAliveTimeoutInNanos = keepAliveTimeoutInNanos; this.keepAliveTimeoutInNanos = keepAliveTimeoutInNanos;
this.maxConnectionIdleInNanos = maxConnectionIdleInNanos; this.maxConnectionIdleInNanos = maxConnectionIdleInNanos;
@ -243,28 +250,29 @@ class NettyServer implements InternalServer, InternalWithLogId {
(long) ((.9D + Math.random() * .2D) * maxConnectionAgeInNanos); (long) ((.9D + Math.random() * .2D) * maxConnectionAgeInNanos);
} }
NettyServerTransport transport = NettyServerTransport transport =
new NettyServerTransport( new NettyServerTransport(
ch, ch,
channelDone, channelDone,
protocolNegotiator, protocolNegotiator,
streamTracerFactories, streamTracerFactories,
transportTracerFactory.create(), transportTracerFactory.create(),
maxStreamsPerConnection, maxStreamsPerConnection,
autoFlowControl, autoFlowControl,
flowControlWindow, flowControlWindow,
maxMessageSize, maxMessageSize,
maxHeaderListSize, maxHeaderListSize,
keepAliveTimeInNanos, softLimitHeaderListSize,
keepAliveTimeoutInNanos, keepAliveTimeInNanos,
maxConnectionIdleInNanos, keepAliveTimeoutInNanos,
maxConnectionAgeInNanos, maxConnectionIdleInNanos,
maxConnectionAgeGraceInNanos, maxConnectionAgeInNanos,
permitKeepAliveWithoutCalls, maxConnectionAgeGraceInNanos,
permitKeepAliveTimeInNanos, permitKeepAliveWithoutCalls,
maxRstCount, permitKeepAliveTimeInNanos,
maxRstPeriodNanos, maxRstCount,
eagAttributes); maxRstPeriodNanos,
eagAttributes);
ServerTransportListener transportListener; ServerTransportListener transportListener;
// This is to order callbacks on the listener, not to guard access to channel. // This is to order callbacks on the listener, not to guard access to channel.
synchronized (NettyServer.this) { synchronized (NettyServer.this) {

View File

@ -105,6 +105,7 @@ public final class NettyServerBuilder extends ForwardingServerBuilder<NettyServe
private int flowControlWindow = DEFAULT_FLOW_CONTROL_WINDOW; private int flowControlWindow = DEFAULT_FLOW_CONTROL_WINDOW;
private int maxMessageSize = DEFAULT_MAX_MESSAGE_SIZE; private int maxMessageSize = DEFAULT_MAX_MESSAGE_SIZE;
private int maxHeaderListSize = GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE; private int maxHeaderListSize = GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE;
private int softLimitHeaderListSize = GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE;
private long keepAliveTimeInNanos = DEFAULT_SERVER_KEEPALIVE_TIME_NANOS; private long keepAliveTimeInNanos = DEFAULT_SERVER_KEEPALIVE_TIME_NANOS;
private long keepAliveTimeoutInNanos = DEFAULT_SERVER_KEEPALIVE_TIMEOUT_NANOS; private long keepAliveTimeoutInNanos = DEFAULT_SERVER_KEEPALIVE_TIMEOUT_NANOS;
private long maxConnectionIdleInNanos = MAX_CONNECTION_IDLE_NANOS_DISABLED; private long maxConnectionIdleInNanos = MAX_CONNECTION_IDLE_NANOS_DISABLED;
@ -492,6 +493,39 @@ public final class NettyServerBuilder extends ForwardingServerBuilder<NettyServe
public NettyServerBuilder maxInboundMetadataSize(int bytes) { public NettyServerBuilder maxInboundMetadataSize(int bytes) {
checkArgument(bytes > 0, "maxInboundMetadataSize must be positive: %s", bytes); checkArgument(bytes > 0, "maxInboundMetadataSize must be positive: %s", bytes);
this.maxHeaderListSize = bytes; this.maxHeaderListSize = bytes;
// Clear the soft limit setting, by setting soft limit to maxInboundMetadataSize. The
// maxInboundMetadataSize will take precedence over soft limit check.
this.softLimitHeaderListSize = bytes;
return this;
}
/**
* Sets the size of metadata that clients are 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.
*
* @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 NettyServerBuilder maxInboundMetadataSize(int soft, int max) {
checkArgument(soft > 0, "softLimitHeaderListSize must be positive: %s", soft);
checkArgument(max > soft,
"maxInboundMetadataSize: %s must be greater than softLimitHeaderListSize: %s", max, soft);
this.softLimitHeaderListSize = soft;
this.maxHeaderListSize = max;
return this; return this;
} }
@ -677,14 +711,33 @@ public final class NettyServerBuilder extends ForwardingServerBuilder<NettyServe
this.serverImplBuilder.getExecutorPool()); this.serverImplBuilder.getExecutorPool());
return new NettyServer( return new NettyServer(
listenAddresses, channelFactory, channelOptions, childChannelOptions, listenAddresses,
bossEventLoopGroupPool, workerEventLoopGroupPool, forceHeapBuffer, negotiator, channelFactory,
streamTracerFactories, transportTracerFactory, maxConcurrentCallsPerConnection, channelOptions,
autoFlowControl, flowControlWindow, maxMessageSize, maxHeaderListSize, childChannelOptions,
keepAliveTimeInNanos, keepAliveTimeoutInNanos, bossEventLoopGroupPool,
maxConnectionIdleInNanos, maxConnectionAgeInNanos, workerEventLoopGroupPool,
maxConnectionAgeGraceInNanos, permitKeepAliveWithoutCalls, permitKeepAliveTimeInNanos, forceHeapBuffer,
maxRstCount, maxRstPeriodNanos, eagAttributes, this.serverImplBuilder.getChannelz()); negotiator,
streamTracerFactories,
transportTracerFactory,
maxConcurrentCallsPerConnection,
autoFlowControl,
flowControlWindow,
maxMessageSize,
maxHeaderListSize,
softLimitHeaderListSize,
keepAliveTimeInNanos,
keepAliveTimeoutInNanos,
maxConnectionIdleInNanos,
maxConnectionAgeInNanos,
maxConnectionAgeGraceInNanos,
permitKeepAliveWithoutCalls,
permitKeepAliveTimeInNanos,
maxRstCount,
maxRstPeriodNanos,
eagAttributes,
this.serverImplBuilder.getChannelz());
} }
@VisibleForTesting @VisibleForTesting

View File

@ -152,7 +152,6 @@ class NettyServerHandler extends AbstractNettyHandler {
private int rstCount; private int rstCount;
private long lastRstNanoTime; private long lastRstNanoTime;
static NettyServerHandler newHandler( static NettyServerHandler newHandler(
ServerTransportListener transportListener, ServerTransportListener transportListener,
ChannelPromise channelUnused, ChannelPromise channelUnused,
@ -162,6 +161,7 @@ class NettyServerHandler extends AbstractNettyHandler {
boolean autoFlowControl, boolean autoFlowControl,
int flowControlWindow, int flowControlWindow,
int maxHeaderListSize, int maxHeaderListSize,
int softLimitHeaderListSize,
int maxMessageSize, int maxMessageSize,
long keepAliveTimeInNanos, long keepAliveTimeInNanos,
long keepAliveTimeoutInNanos, long keepAliveTimeoutInNanos,
@ -192,6 +192,7 @@ class NettyServerHandler extends AbstractNettyHandler {
autoFlowControl, autoFlowControl,
flowControlWindow, flowControlWindow,
maxHeaderListSize, maxHeaderListSize,
softLimitHeaderListSize,
maxMessageSize, maxMessageSize,
keepAliveTimeInNanos, keepAliveTimeInNanos,
keepAliveTimeoutInNanos, keepAliveTimeoutInNanos,
@ -217,6 +218,7 @@ class NettyServerHandler extends AbstractNettyHandler {
boolean autoFlowControl, boolean autoFlowControl,
int flowControlWindow, int flowControlWindow,
int maxHeaderListSize, int maxHeaderListSize,
int softLimitHeaderListSize,
int maxMessageSize, int maxMessageSize,
long keepAliveTimeInNanos, long keepAliveTimeInNanos,
long keepAliveTimeoutInNanos, long keepAliveTimeoutInNanos,
@ -234,6 +236,9 @@ class NettyServerHandler extends AbstractNettyHandler {
flowControlWindow); flowControlWindow);
Preconditions.checkArgument(maxHeaderListSize > 0, "maxHeaderListSize must be positive: %s", Preconditions.checkArgument(maxHeaderListSize > 0, "maxHeaderListSize must be positive: %s",
maxHeaderListSize); maxHeaderListSize);
Preconditions.checkArgument(
softLimitHeaderListSize > 0, "softLimitHeaderListSize must be positive: %s",
softLimitHeaderListSize);
Preconditions.checkArgument(maxMessageSize > 0, "maxMessageSize must be positive: %s", Preconditions.checkArgument(maxMessageSize > 0, "maxMessageSize must be positive: %s",
maxMessageSize); maxMessageSize);
@ -273,7 +278,10 @@ class NettyServerHandler extends AbstractNettyHandler {
transportTracer, transportTracer,
decoder, encoder, settings, decoder, encoder, settings,
maxMessageSize, maxMessageSize,
keepAliveTimeInNanos, keepAliveTimeoutInNanos, maxHeaderListSize,
softLimitHeaderListSize,
keepAliveTimeInNanos,
keepAliveTimeoutInNanos,
maxConnectionIdleInNanos, maxConnectionIdleInNanos,
maxConnectionAgeInNanos, maxConnectionAgeGraceInNanos, maxConnectionAgeInNanos, maxConnectionAgeGraceInNanos,
keepAliveEnforcer, keepAliveEnforcer,
@ -293,6 +301,8 @@ class NettyServerHandler extends AbstractNettyHandler {
Http2ConnectionEncoder encoder, Http2ConnectionEncoder encoder,
Http2Settings settings, Http2Settings settings,
int maxMessageSize, int maxMessageSize,
int maxHeaderListSize,
int softLimitHeaderListSize,
long keepAliveTimeInNanos, long keepAliveTimeInNanos,
long keepAliveTimeoutInNanos, long keepAliveTimeoutInNanos,
long maxConnectionIdleInNanos, long maxConnectionIdleInNanos,
@ -304,8 +314,17 @@ class NettyServerHandler extends AbstractNettyHandler {
long maxRstPeriodNanos, long maxRstPeriodNanos,
Attributes eagAttributes, Attributes eagAttributes,
Ticker ticker) { Ticker ticker) {
super(channelUnused, decoder, encoder, settings, new ServerChannelLogger(), super(
autoFlowControl, null, ticker); channelUnused,
decoder,
encoder,
settings,
new ServerChannelLogger(),
autoFlowControl,
null,
ticker,
maxHeaderListSize,
softLimitHeaderListSize);
final MaxConnectionIdleManager maxConnectionIdleManager; final MaxConnectionIdleManager maxConnectionIdleManager;
if (maxConnectionIdleInNanos == MAX_CONNECTION_IDLE_NANOS_DISABLED) { if (maxConnectionIdleInNanos == MAX_CONNECTION_IDLE_NANOS_DISABLED) {
@ -470,6 +489,16 @@ class NettyServerHandler extends AbstractNettyHandler {
return; return;
} }
int h2HeadersSize = Utils.getH2HeadersSize(headers);
if (Utils.shouldRejectOnMetadataSizeSoftLimitExceeded(
h2HeadersSize, softLimitHeaderListSize, maxHeaderListSize)) {
respondWithHttpError(ctx, streamId, 431, Status.Code.RESOURCE_EXHAUSTED, String.format(
"Client Headers of size %d exceeded Metadata size soft limit: %d",
h2HeadersSize,
softLimitHeaderListSize));
return;
}
if (!teWarningLogged && !TE_TRAILERS.contentEquals(headers.get(TE_HEADER))) { if (!teWarningLogged && !TE_TRAILERS.contentEquals(headers.get(TE_HEADER))) {
logger.warning(String.format("Expected header TE: %s, but %s is received. This means " logger.warning(String.format("Expected header TE: %s, but %s is received. This means "
+ "some intermediate proxy may not support trailers", + "some intermediate proxy may not support trailers",

View File

@ -70,6 +70,7 @@ class NettyServerTransport implements ServerTransport {
private final int flowControlWindow; private final int flowControlWindow;
private final int maxMessageSize; private final int maxMessageSize;
private final int maxHeaderListSize; private final int maxHeaderListSize;
private final int softLimitHeaderListSize;
private final long keepAliveTimeInNanos; private final long keepAliveTimeInNanos;
private final long keepAliveTimeoutInNanos; private final long keepAliveTimeoutInNanos;
private final long maxConnectionIdleInNanos; private final long maxConnectionIdleInNanos;
@ -94,6 +95,7 @@ class NettyServerTransport implements ServerTransport {
int flowControlWindow, int flowControlWindow,
int maxMessageSize, int maxMessageSize,
int maxHeaderListSize, int maxHeaderListSize,
int softLimitHeaderListSize,
long keepAliveTimeInNanos, long keepAliveTimeInNanos,
long keepAliveTimeoutInNanos, long keepAliveTimeoutInNanos,
long maxConnectionIdleInNanos, long maxConnectionIdleInNanos,
@ -115,6 +117,7 @@ class NettyServerTransport implements ServerTransport {
this.flowControlWindow = flowControlWindow; this.flowControlWindow = flowControlWindow;
this.maxMessageSize = maxMessageSize; this.maxMessageSize = maxMessageSize;
this.maxHeaderListSize = maxHeaderListSize; this.maxHeaderListSize = maxHeaderListSize;
this.softLimitHeaderListSize = softLimitHeaderListSize;
this.keepAliveTimeInNanos = keepAliveTimeInNanos; this.keepAliveTimeInNanos = keepAliveTimeInNanos;
this.keepAliveTimeoutInNanos = keepAliveTimeoutInNanos; this.keepAliveTimeoutInNanos = keepAliveTimeoutInNanos;
this.maxConnectionIdleInNanos = maxConnectionIdleInNanos; this.maxConnectionIdleInNanos = maxConnectionIdleInNanos;
@ -275,6 +278,7 @@ class NettyServerTransport implements ServerTransport {
autoFlowControl, autoFlowControl,
flowControlWindow, flowControlWindow,
maxHeaderListSize, maxHeaderListSize,
softLimitHeaderListSize,
maxMessageSize, maxMessageSize,
keepAliveTimeInNanos, keepAliveTimeInNanos,
keepAliveTimeoutInNanos, keepAliveTimeoutInNanos,

View File

@ -23,6 +23,7 @@ import static io.grpc.internal.TransportFrameUtil.toRawSerializedHeaders;
import static io.netty.channel.ChannelOption.SO_LINGER; import static io.netty.channel.ChannelOption.SO_LINGER;
import static io.netty.channel.ChannelOption.SO_TIMEOUT; import static io.netty.channel.ChannelOption.SO_TIMEOUT;
import static io.netty.util.CharsetUtil.UTF_8; import static io.netty.util.CharsetUtil.UTF_8;
import static java.nio.charset.StandardCharsets.US_ASCII;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
@ -91,7 +92,9 @@ class Utils {
= new DefaultEventLoopGroupResource(1, "grpc-nio-boss-ELG", EventLoopGroupType.NIO); = new DefaultEventLoopGroupResource(1, "grpc-nio-boss-ELG", EventLoopGroupType.NIO);
public static final Resource<EventLoopGroup> NIO_WORKER_EVENT_LOOP_GROUP public static final Resource<EventLoopGroup> NIO_WORKER_EVENT_LOOP_GROUP
= new DefaultEventLoopGroupResource(0, "grpc-nio-worker-ELG", EventLoopGroupType.NIO); = new DefaultEventLoopGroupResource(0, "grpc-nio-worker-ELG", EventLoopGroupType.NIO);
private static final int HEADER_ENTRY_OVERHEAD = 32;
private static final byte[] binaryHeaderSuffixBytes =
Metadata.BINARY_HEADER_SUFFIX.getBytes(US_ASCII);
public static final Resource<EventLoopGroup> DEFAULT_BOSS_EVENT_LOOP_GROUP; public static final Resource<EventLoopGroup> DEFAULT_BOSS_EVENT_LOOP_GROUP;
public static final Resource<EventLoopGroup> DEFAULT_WORKER_EVENT_LOOP_GROUP; public static final Resource<EventLoopGroup> DEFAULT_WORKER_EVENT_LOOP_GROUP;
@ -195,6 +198,61 @@ class Utils {
return InternalMetadata.newMetadata(convertHeadersToArray(http2Headers)); return InternalMetadata.newMetadata(convertHeadersToArray(http2Headers));
} }
public static int getH2HeadersSize(Http2Headers http2Headers) {
if (http2Headers instanceof GrpcHttp2InboundHeaders) {
GrpcHttp2InboundHeaders h = (GrpcHttp2InboundHeaders) http2Headers;
int size = 0;
for (int i = 0; i < h.numHeaders(); i++) {
size += h.namesAndValues()[2 * i].length;
size +=
maybeAddBinaryHeaderOverhead(h.namesAndValues()[2 * i], h.namesAndValues()[2 * i + 1]);
size += HEADER_ENTRY_OVERHEAD;
}
return size;
}
// the binary header is not decoded yet, no need to add overhead.
int size = 0;
for (Map.Entry<CharSequence, CharSequence> entry : http2Headers) {
size += entry.getKey().length();
size += entry.getValue().length();
size += HEADER_ENTRY_OVERHEAD;
}
return size;
}
private static int maybeAddBinaryHeaderOverhead(byte[] name, byte[] value) {
if (endsWith(name, binaryHeaderSuffixBytes)) {
return value.length * 4 / 3;
}
return value.length;
}
private static boolean endsWith(byte[] bytes, byte[] suffix) {
if (bytes == null || suffix == null || bytes.length < suffix.length) {
return false;
}
for (int i = 0; i < suffix.length; i++) {
if (bytes[bytes.length - suffix.length + i] != suffix[i]) {
return false;
}
}
return true;
}
public static boolean shouldRejectOnMetadataSizeSoftLimitExceeded(
int h2HeadersSize, int softLimitHeaderListSize, int maxHeaderListSize) {
if (h2HeadersSize < softLimitHeaderListSize) {
return false;
}
double failProbability =
(double) (h2HeadersSize - softLimitHeaderListSize) / (double) (maxHeaderListSize
- softLimitHeaderListSize);
return Math.random() < failProbability;
}
@CheckReturnValue @CheckReturnValue
private static byte[][] convertHeadersToArray(Http2Headers http2Headers) { private static byte[][] convertHeadersToArray(Http2Headers http2Headers) {
// The Netty AsciiString class is really just a wrapper around a byte[] and supports // The Netty AsciiString class is really just a wrapper around a byte[] and supports

View File

@ -47,6 +47,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyNoMoreInteractions;
import com.google.common.base.Stopwatch; import com.google.common.base.Stopwatch;
import com.google.common.base.Strings;
import com.google.common.base.Supplier; import com.google.common.base.Supplier;
import com.google.common.base.Ticker; import com.google.common.base.Ticker;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
@ -122,6 +123,7 @@ public class NettyClientHandlerTest extends NettyHandlerTestBase<NettyClientHand
private Http2Headers grpcHeaders; private Http2Headers grpcHeaders;
private long nanoTime; // backs a ticker, for testing ping round-trip time measurement private long nanoTime; // backs a ticker, for testing ping round-trip time measurement
private int maxHeaderListSize = Integer.MAX_VALUE; private int maxHeaderListSize = Integer.MAX_VALUE;
private int softLimitHeaderListSize = Integer.MAX_VALUE;
private int streamId = STREAM_ID; private int streamId = STREAM_ID;
private ClientTransportLifecycleManager lifecycleManager; private ClientTransportLifecycleManager lifecycleManager;
private KeepAliveManager mockKeepAliveManager = null; private KeepAliveManager mockKeepAliveManager = null;
@ -217,6 +219,36 @@ public class NettyClientHandlerTest extends NettyHandlerTestBase<NettyClientHand
channelRead(serializedSettings); channelRead(serializedSettings);
} }
@Test
@SuppressWarnings("InlineMeInliner")
public void sendLargerThanSoftLimitHeaderMayFail() throws Exception {
maxHeaderListSize = 8000;
softLimitHeaderListSize = 2000;
manualSetUp();
createStream();
// total head size of 7999, soft limit = 2000 and max = 8000.
// This header has 5999/6000 chance to be rejected.
Http2Headers headers = new DefaultHttp2Headers()
.scheme(HTTPS)
.authority(as("www.fake.com"))
.path(as("/fakemethod"))
.method(HTTP_METHOD)
.add(as("auth"), as("sometoken"))
.add(CONTENT_TYPE_HEADER, CONTENT_TYPE_GRPC)
.add(TE_HEADER, TE_TRAILERS)
.add("large-field", Strings.repeat("a", 7620)); // String.repeat() requires Java 11
ByteBuf headersFrame = headersFrame(STREAM_ID, headers);
channelRead(headersFrame);
ArgumentCaptor<Status> statusArgumentCaptor = ArgumentCaptor.forClass(Status.class);
verify(streamListener).closed(statusArgumentCaptor.capture(), eq(PROCESSED),
any(Metadata.class));
assertThat(statusArgumentCaptor.getValue().getCode()).isEqualTo(Status.Code.RESOURCE_EXHAUSTED);
assertThat(statusArgumentCaptor.getValue().getDescription()).contains(
"exceeded Metadata size soft limit");
}
@Test @Test
public void cancelBufferedStreamShouldChangeClientStreamStatus() throws Exception { public void cancelBufferedStreamShouldChangeClientStreamStatus() throws Exception {
// Force the stream to be buffered. // Force the stream to be buffered.
@ -946,6 +978,7 @@ public class NettyClientHandlerTest extends NettyHandlerTestBase<NettyClientHand
false, false,
flowControlWindow, flowControlWindow,
maxHeaderListSize, maxHeaderListSize,
softLimitHeaderListSize,
stopwatchSupplier, stopwatchSupplier,
tooManyPingsRunnable, tooManyPingsRunnable,
transportTracer, transportTracer,

View File

@ -205,12 +205,30 @@ public class NettyClientTransportTest {
// set SO_LINGER option // set SO_LINGER option
int soLinger = 123; int soLinger = 123;
channelOptions.put(ChannelOption.SO_LINGER, soLinger); channelOptions.put(ChannelOption.SO_LINGER, soLinger);
NettyClientTransport transport = new NettyClientTransport( NettyClientTransport transport =
address, new ReflectiveChannelFactory<>(NioSocketChannel.class), channelOptions, group, new NettyClientTransport(
newNegotiator(), false, DEFAULT_WINDOW_SIZE, DEFAULT_MAX_MESSAGE_SIZE, address,
GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE, KEEPALIVE_TIME_NANOS_DISABLED, 1L, false, authority, new ReflectiveChannelFactory<>(NioSocketChannel.class),
null /* user agent */, tooManyPingsRunnable, new TransportTracer(), Attributes.EMPTY, channelOptions,
new SocketPicker(), new FakeChannelLogger(), false, Ticker.systemTicker()); group,
newNegotiator(),
false,
DEFAULT_WINDOW_SIZE,
DEFAULT_MAX_MESSAGE_SIZE,
GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE,
GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE,
KEEPALIVE_TIME_NANOS_DISABLED,
1L,
false,
authority,
null /* user agent */,
tooManyPingsRunnable,
new TransportTracer(),
Attributes.EMPTY,
new SocketPicker(),
new FakeChannelLogger(),
false,
Ticker.systemTicker());
transports.add(transport); transports.add(transport);
callMeMaybe(transport.start(clientTransportListener)); callMeMaybe(transport.start(clientTransportListener));
@ -454,13 +472,30 @@ public class NettyClientTransportTest {
public void failingToConstructChannelShouldFailGracefully() throws Exception { public void failingToConstructChannelShouldFailGracefully() throws Exception {
address = TestUtils.testServerAddress(new InetSocketAddress(12345)); address = TestUtils.testServerAddress(new InetSocketAddress(12345));
authority = GrpcUtil.authorityFromHostAndPort(address.getHostString(), address.getPort()); authority = GrpcUtil.authorityFromHostAndPort(address.getHostString(), address.getPort());
NettyClientTransport transport = new NettyClientTransport( NettyClientTransport transport =
address, new ReflectiveChannelFactory<>(CantConstructChannel.class), new NettyClientTransport(
new HashMap<ChannelOption<?>, Object>(), group, address,
newNegotiator(), false, DEFAULT_WINDOW_SIZE, DEFAULT_MAX_MESSAGE_SIZE, new ReflectiveChannelFactory<>(CantConstructChannel.class),
GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE, KEEPALIVE_TIME_NANOS_DISABLED, 1, false, authority, new HashMap<ChannelOption<?>, Object>(),
null, tooManyPingsRunnable, new TransportTracer(), Attributes.EMPTY, new SocketPicker(), group,
new FakeChannelLogger(), false, Ticker.systemTicker()); newNegotiator(),
false,
DEFAULT_WINDOW_SIZE,
DEFAULT_MAX_MESSAGE_SIZE,
GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE,
GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE,
KEEPALIVE_TIME_NANOS_DISABLED,
1,
false,
authority,
null,
tooManyPingsRunnable,
new TransportTracer(),
Attributes.EMPTY,
new SocketPicker(),
new FakeChannelLogger(),
false,
Ticker.systemTicker());
transports.add(transport); transports.add(transport);
// Should not throw // Should not throw
@ -814,13 +849,30 @@ public class NettyClientTransportTest {
if (!enableKeepAlive) { if (!enableKeepAlive) {
keepAliveTimeNano = KEEPALIVE_TIME_NANOS_DISABLED; keepAliveTimeNano = KEEPALIVE_TIME_NANOS_DISABLED;
} }
NettyClientTransport transport = new NettyClientTransport( NettyClientTransport transport =
address, channelFactory, new HashMap<ChannelOption<?>, Object>(), group, new NettyClientTransport(
negotiator, false, DEFAULT_WINDOW_SIZE, maxMsgSize, maxHeaderListSize, address,
keepAliveTimeNano, keepAliveTimeoutNano, channelFactory,
false, authority, userAgent, tooManyPingsRunnable, new HashMap<ChannelOption<?>, Object>(),
new TransportTracer(), eagAttributes, new SocketPicker(), new FakeChannelLogger(), false, group,
Ticker.systemTicker()); negotiator,
false,
DEFAULT_WINDOW_SIZE,
maxMsgSize,
maxHeaderListSize,
maxHeaderListSize,
keepAliveTimeNano,
keepAliveTimeoutNano,
false,
authority,
userAgent,
tooManyPingsRunnable,
new TransportTracer(),
eagAttributes,
new SocketPicker(),
new FakeChannelLogger(),
false,
Ticker.systemTicker());
transports.add(transport); transports.add(transport);
return transport; return transport;
} }
@ -830,22 +882,35 @@ public class NettyClientTransportTest {
} }
private void startServer(int maxStreamsPerConnection, int maxHeaderListSize) throws IOException { private void startServer(int maxStreamsPerConnection, int maxHeaderListSize) throws IOException {
server = new NettyServer( server =
TestUtils.testServerAddresses(new InetSocketAddress(0)), new NettyServer(
new ReflectiveChannelFactory<>(NioServerSocketChannel.class), TestUtils.testServerAddresses(new InetSocketAddress(0)),
new HashMap<ChannelOption<?>, Object>(), new ReflectiveChannelFactory<>(NioServerSocketChannel.class),
new HashMap<ChannelOption<?>, Object>(), new HashMap<ChannelOption<?>, Object>(),
new FixedObjectPool<>(group), new FixedObjectPool<>(group), false, negotiator, new HashMap<ChannelOption<?>, Object>(),
Collections.<ServerStreamTracer.Factory>emptyList(), new FixedObjectPool<>(group),
TransportTracer.getDefaultFactory(), new FixedObjectPool<>(group),
maxStreamsPerConnection, false,
false, negotiator,
DEFAULT_WINDOW_SIZE, DEFAULT_MAX_MESSAGE_SIZE, maxHeaderListSize, Collections.<ServerStreamTracer.Factory>emptyList(),
DEFAULT_SERVER_KEEPALIVE_TIME_NANOS, DEFAULT_SERVER_KEEPALIVE_TIMEOUT_NANOS, TransportTracer.getDefaultFactory(),
MAX_CONNECTION_IDLE_NANOS_DISABLED, maxStreamsPerConnection,
MAX_CONNECTION_AGE_NANOS_DISABLED, MAX_CONNECTION_AGE_GRACE_NANOS_INFINITE, true, 0, false,
MAX_RST_COUNT_DISABLED, 0, Attributes.EMPTY, DEFAULT_WINDOW_SIZE,
channelz); DEFAULT_MAX_MESSAGE_SIZE,
maxHeaderListSize,
maxHeaderListSize,
DEFAULT_SERVER_KEEPALIVE_TIME_NANOS,
DEFAULT_SERVER_KEEPALIVE_TIMEOUT_NANOS,
MAX_CONNECTION_IDLE_NANOS_DISABLED,
MAX_CONNECTION_AGE_NANOS_DISABLED,
MAX_CONNECTION_AGE_GRACE_NANOS_INFINITE,
true,
0,
MAX_RST_COUNT_DISABLED,
0,
Attributes.EMPTY,
channelz);
server.start(serverListener); server.start(serverListener);
address = TestUtils.testServerAddress((InetSocketAddress) server.getListenSocketAddress()); address = TestUtils.testServerAddress((InetSocketAddress) server.getListenSocketAddress());
authority = GrpcUtil.authorityFromHostAndPort(address.getHostString(), address.getPort()); authority = GrpcUtil.authorityFromHostAndPort(address.getHostString(), address.getPort());

View File

@ -100,6 +100,22 @@ public class NettyServerBuilderTest {
builder.maxInboundMetadataSize(0); builder.maxInboundMetadataSize(0);
} }
@Test
public void failIfSoftInboundMetadataSizeNonPositive() {
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("softLimitHeaderListSize must be positive");
builder.maxInboundMetadataSize(0, 100);
}
@Test
public void failIfMaxInboundMetadataSizeSmallerThanSoft() {
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("must be greater than softLimitHeaderListSize");
builder.maxInboundMetadataSize(100, 80);
}
@Test @Test
public void failIfMaxConnectionIdleNegative() { public void failIfMaxConnectionIdleNegative() {
thrown.expect(IllegalArgumentException.class); thrown.expect(IllegalArgumentException.class);

View File

@ -141,6 +141,7 @@ public class NettyServerHandlerTest extends NettyHandlerTestBase<NettyServerHand
private int maxConcurrentStreams = Integer.MAX_VALUE; private int maxConcurrentStreams = Integer.MAX_VALUE;
private int maxHeaderListSize = Integer.MAX_VALUE; private int maxHeaderListSize = Integer.MAX_VALUE;
private int softLimitHeaderListSize = Integer.MAX_VALUE;
private boolean permitKeepAliveWithoutCalls = true; private boolean permitKeepAliveWithoutCalls = true;
private long permitKeepAliveTimeInNanos = 0; private long permitKeepAliveTimeInNanos = 0;
private long maxConnectionIdleInNanos = MAX_CONNECTION_IDLE_NANOS_DISABLED; private long maxConnectionIdleInNanos = MAX_CONNECTION_IDLE_NANOS_DISABLED;
@ -1363,6 +1364,7 @@ public class NettyServerHandlerTest extends NettyHandlerTestBase<NettyServerHand
autoFlowControl, autoFlowControl,
flowControlWindow, flowControlWindow,
maxHeaderListSize, maxHeaderListSize,
softLimitHeaderListSize,
DEFAULT_MAX_MESSAGE_SIZE, DEFAULT_MAX_MESSAGE_SIZE,
keepAliveTimeInNanos, keepAliveTimeInNanos,
keepAliveTimeoutInNanos, keepAliveTimeoutInNanos,

View File

@ -133,29 +133,35 @@ public class NettyServerTest {
} }
NoHandlerProtocolNegotiator protocolNegotiator = new NoHandlerProtocolNegotiator(); NoHandlerProtocolNegotiator protocolNegotiator = new NoHandlerProtocolNegotiator();
NettyServer ns = new NettyServer( NettyServer ns =
Arrays.asList(addr), new NettyServer(
new ReflectiveChannelFactory<>(NioServerSocketChannel.class), Arrays.asList(addr),
new HashMap<ChannelOption<?>, Object>(), new ReflectiveChannelFactory<>(NioServerSocketChannel.class),
new HashMap<ChannelOption<?>, Object>(), new HashMap<ChannelOption<?>, Object>(),
new FixedObjectPool<>(eventLoop), new HashMap<ChannelOption<?>, Object>(),
new FixedObjectPool<>(eventLoop), new FixedObjectPool<>(eventLoop),
false, new FixedObjectPool<>(eventLoop),
protocolNegotiator, false,
Collections.<ServerStreamTracer.Factory>emptyList(), protocolNegotiator,
TransportTracer.getDefaultFactory(), Collections.<ServerStreamTracer.Factory>emptyList(),
1, // ignore TransportTracer.getDefaultFactory(),
false, // ignore 1, // ignore
1, // ignore false, // ignore
1, // ignore 1, // ignore
1, // ignore 1, // ignore
1, // ignore 1, // ignore
1, 1, // ignore 1, // ignore
1, 1, // ignore 1, // ignore
true, 0, // ignore 1,
0, 0, // ignore 1, // ignore
Attributes.EMPTY, 1,
channelz); 1, // ignore
true,
0, // ignore
0,
0, // ignore
Attributes.EMPTY,
channelz);
final SettableFuture<Void> serverShutdownCalled = SettableFuture.create(); final SettableFuture<Void> serverShutdownCalled = SettableFuture.create();
ns.start(new ServerListener() { ns.start(new ServerListener() {
@Override @Override
@ -184,29 +190,35 @@ public class NettyServerTest {
InetSocketAddress addr1 = new InetSocketAddress(0); InetSocketAddress addr1 = new InetSocketAddress(0);
InetSocketAddress addr2 = new InetSocketAddress(0); InetSocketAddress addr2 = new InetSocketAddress(0);
NettyServer ns = new NettyServer( NettyServer ns =
Arrays.asList(addr1, addr2), new NettyServer(
new ReflectiveChannelFactory<>(NioServerSocketChannel.class), Arrays.asList(addr1, addr2),
new HashMap<ChannelOption<?>, Object>(), new ReflectiveChannelFactory<>(NioServerSocketChannel.class),
new HashMap<ChannelOption<?>, Object>(), new HashMap<ChannelOption<?>, Object>(),
new FixedObjectPool<>(eventLoop), new HashMap<ChannelOption<?>, Object>(),
new FixedObjectPool<>(eventLoop), new FixedObjectPool<>(eventLoop),
false, new FixedObjectPool<>(eventLoop),
ProtocolNegotiators.plaintext(), false,
Collections.<ServerStreamTracer.Factory>emptyList(), ProtocolNegotiators.plaintext(),
TransportTracer.getDefaultFactory(), Collections.<ServerStreamTracer.Factory>emptyList(),
1, // ignore TransportTracer.getDefaultFactory(),
false, // ignore 1, // ignore
1, // ignore false, // ignore
1, // ignore 1, // ignore
1, // ignore 1, // ignore
1, // ignore 1, // ignore
1, 1, // ignore 1, // ignore
1, 1, // ignore 1, // ignore
true, 0, // ignore 1,
0, 0, // ignore 1, // ignore
Attributes.EMPTY, 1,
channelz); 1, // ignore
true,
0, // ignore
0,
0, // ignore
Attributes.EMPTY,
channelz);
final SettableFuture<Void> shutdownCompleted = SettableFuture.create(); final SettableFuture<Void> shutdownCompleted = SettableFuture.create();
ns.start(new ServerListener() { ns.start(new ServerListener() {
@Override @Override
@ -258,29 +270,35 @@ public class NettyServerTest {
InetSocketAddress addr2 = new InetSocketAddress(0); InetSocketAddress addr2 = new InetSocketAddress(0);
final CountDownLatch allPortsConnectedCountDown = new CountDownLatch(2); final CountDownLatch allPortsConnectedCountDown = new CountDownLatch(2);
NettyServer ns = new NettyServer( NettyServer ns =
Arrays.asList(addr1, addr2), new NettyServer(
new ReflectiveChannelFactory<>(NioServerSocketChannel.class), Arrays.asList(addr1, addr2),
new HashMap<ChannelOption<?>, Object>(), new ReflectiveChannelFactory<>(NioServerSocketChannel.class),
new HashMap<ChannelOption<?>, Object>(), new HashMap<ChannelOption<?>, Object>(),
new FixedObjectPool<>(eventLoop), new HashMap<ChannelOption<?>, Object>(),
new FixedObjectPool<>(eventLoop), new FixedObjectPool<>(eventLoop),
false, new FixedObjectPool<>(eventLoop),
ProtocolNegotiators.plaintext(), false,
Collections.<ServerStreamTracer.Factory>emptyList(), ProtocolNegotiators.plaintext(),
TransportTracer.getDefaultFactory(), Collections.<ServerStreamTracer.Factory>emptyList(),
1, // ignore TransportTracer.getDefaultFactory(),
false, // ignore 1, // ignore
1, // ignore false, // ignore
1, // ignore 1, // ignore
1, // ignore 1, // ignore
1, // ignore 1, // ignore
1, 1, // ignore 1, // ignore
1, 1, // ignore 1, // ignore
true, 0, // ignore 1,
0, 0, // ignore 1, // ignore
Attributes.EMPTY, 1,
channelz); 1, // ignore
true,
0, // ignore
0,
0, // ignore
Attributes.EMPTY,
channelz);
final SettableFuture<Void> shutdownCompleted = SettableFuture.create(); final SettableFuture<Void> shutdownCompleted = SettableFuture.create();
ns.start(new ServerListener() { ns.start(new ServerListener() {
@Override @Override
@ -320,29 +338,35 @@ public class NettyServerTest {
public void getPort_notStarted() { public void getPort_notStarted() {
InetSocketAddress addr = new InetSocketAddress(0); InetSocketAddress addr = new InetSocketAddress(0);
List<InetSocketAddress> addresses = Collections.singletonList(addr); List<InetSocketAddress> addresses = Collections.singletonList(addr);
NettyServer ns = new NettyServer( NettyServer ns =
addresses, new NettyServer(
new ReflectiveChannelFactory<>(NioServerSocketChannel.class), addresses,
new HashMap<ChannelOption<?>, Object>(), new ReflectiveChannelFactory<>(NioServerSocketChannel.class),
new HashMap<ChannelOption<?>, Object>(), new HashMap<ChannelOption<?>, Object>(),
new FixedObjectPool<>(eventLoop), new HashMap<ChannelOption<?>, Object>(),
new FixedObjectPool<>(eventLoop), new FixedObjectPool<>(eventLoop),
false, new FixedObjectPool<>(eventLoop),
ProtocolNegotiators.plaintext(), false,
Collections.<ServerStreamTracer.Factory>emptyList(), ProtocolNegotiators.plaintext(),
TransportTracer.getDefaultFactory(), Collections.<ServerStreamTracer.Factory>emptyList(),
1, // ignore TransportTracer.getDefaultFactory(),
false, // ignore 1, // ignore
1, // ignore false, // ignore
1, // ignore 1, // ignore
1, // ignore 1, // ignore
1, // ignore 1, // ignore
1, 1, // ignore 1, // ignore
1, 1, // ignore 1, // ignore
true, 0, // ignore 1,
0, 0, // ignore 1, // ignore
Attributes.EMPTY, 1,
channelz); 1, // ignore
true,
0, // ignore
0,
0, // ignore
Attributes.EMPTY,
channelz);
assertThat(ns.getListenSocketAddress()).isEqualTo(addr); assertThat(ns.getListenSocketAddress()).isEqualTo(addr);
assertThat(ns.getListenSocketAddresses()).isEqualTo(addresses); assertThat(ns.getListenSocketAddresses()).isEqualTo(addresses);
@ -395,29 +419,35 @@ public class NettyServerTest {
.build(); .build();
TestProtocolNegotiator protocolNegotiator = new TestProtocolNegotiator(); TestProtocolNegotiator protocolNegotiator = new TestProtocolNegotiator();
InetSocketAddress addr = new InetSocketAddress(0); InetSocketAddress addr = new InetSocketAddress(0);
NettyServer ns = new NettyServer( NettyServer ns =
Arrays.asList(addr), new NettyServer(
new ReflectiveChannelFactory<>(NioServerSocketChannel.class), Arrays.asList(addr),
new HashMap<ChannelOption<?>, Object>(), new ReflectiveChannelFactory<>(NioServerSocketChannel.class),
childChannelOptions, new HashMap<ChannelOption<?>, Object>(),
new FixedObjectPool<>(eventLoop), childChannelOptions,
new FixedObjectPool<>(eventLoop), new FixedObjectPool<>(eventLoop),
false, new FixedObjectPool<>(eventLoop),
protocolNegotiator, false,
Collections.<ServerStreamTracer.Factory>emptyList(), protocolNegotiator,
TransportTracer.getDefaultFactory(), Collections.<ServerStreamTracer.Factory>emptyList(),
1, // ignore TransportTracer.getDefaultFactory(),
false, // ignore 1, // ignore
1, // ignore false, // ignore
1, // ignore 1, // ignore
1, // ignore 1, // ignore
1, // ignore 1, // ignore
1, 1, // ignore 1, // ignore
1, 1, // ignore 1, // ignore
true, 0, // ignore 1,
0, 0, // ignore 1, // ignore
eagAttributes, 1,
channelz); 1, // ignore
true,
0, // ignore
0,
0, // ignore
eagAttributes,
channelz);
ns.start(new ServerListener() { ns.start(new ServerListener() {
@Override @Override
public ServerTransportListener transportCreated(ServerTransport transport) { public ServerTransportListener transportCreated(ServerTransport transport) {
@ -443,29 +473,35 @@ public class NettyServerTest {
@Test @Test
public void channelzListenSocket() throws Exception { public void channelzListenSocket() throws Exception {
InetSocketAddress addr = new InetSocketAddress(0); InetSocketAddress addr = new InetSocketAddress(0);
NettyServer ns = new NettyServer( NettyServer ns =
Arrays.asList(addr), new NettyServer(
new ReflectiveChannelFactory<>(NioServerSocketChannel.class), Arrays.asList(addr),
new HashMap<ChannelOption<?>, Object>(), new ReflectiveChannelFactory<>(NioServerSocketChannel.class),
new HashMap<ChannelOption<?>, Object>(), new HashMap<ChannelOption<?>, Object>(),
new FixedObjectPool<>(eventLoop), new HashMap<ChannelOption<?>, Object>(),
new FixedObjectPool<>(eventLoop), new FixedObjectPool<>(eventLoop),
false, new FixedObjectPool<>(eventLoop),
ProtocolNegotiators.plaintext(), false,
Collections.<ServerStreamTracer.Factory>emptyList(), ProtocolNegotiators.plaintext(),
TransportTracer.getDefaultFactory(), Collections.<ServerStreamTracer.Factory>emptyList(),
1, // ignore TransportTracer.getDefaultFactory(),
false, // ignore 1, // ignore
1, // ignore false, // ignore
1, // ignore 1, // ignore
1, // ignore 1, // ignore
1, // ignore 1, // ignore
1, 1, // ignore 1, // ignore
1, 1, // ignore 1, // ignore
true, 0, // ignore 1,
0, 0, // ignore 1, // ignore
Attributes.EMPTY, 1,
channelz); 1, // ignore
true,
0, // ignore
0,
0, // ignore
Attributes.EMPTY,
channelz);
final SettableFuture<Void> shutdownCompleted = SettableFuture.create(); final SettableFuture<Void> shutdownCompleted = SettableFuture.create();
ns.start(new ServerListener() { ns.start(new ServerListener() {
@Override @Override
@ -603,10 +639,15 @@ public class NettyServerTest {
1, // ignore 1, // ignore
1, // ignore 1, // ignore
1, // ignore 1, // ignore
1, 1, // ignore 1, // ignore
1, 1, // ignore 1,
true, 0, // ignore 1, // ignore
0, 0, // ignore 1,
1, // ignore
true,
0, // ignore
0,
0, // ignore
Attributes.EMPTY, Attributes.EMPTY,
channelz); channelz);
} }