• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2019 The Netty Project
3  *
4  * The Netty Project licenses this file to you under the Apache License, version 2.0 (the
5  * "License"); you may not use this file except in compliance with the License. You may obtain a
6  * copy of the License at:
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software distributed under the License
11  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12  * or implied. See the License for the specific language governing permissions and limitations under
13  * the License.
14  */
15 package io.grpc.netty;
16 
17 import io.netty.channel.ChannelFuture;
18 import io.netty.channel.ChannelFutureListener;
19 import io.netty.channel.ChannelHandlerContext;
20 import io.netty.channel.ChannelPromise;
21 import io.netty.handler.codec.http2.DecoratingHttp2ConnectionEncoder;
22 import io.netty.handler.codec.http2.Http2ConnectionEncoder;
23 import io.netty.handler.codec.http2.Http2Error;
24 import io.netty.handler.codec.http2.Http2Exception;
25 import io.netty.handler.codec.http2.Http2LifecycleManager;
26 import io.netty.util.internal.ObjectUtil;
27 import io.netty.util.internal.logging.InternalLogger;
28 import io.netty.util.internal.logging.InternalLoggerFactory;
29 
30 /**
31  * {@link DecoratingHttp2ConnectionEncoder} which guards against a remote peer that will trigger a massive amount
32  * of control frames but will not consume our responses to these.
33  * This encoder will tear-down the connection once we reached the configured limit to reduce the risk of DDOS.
34  */
35 final class Http2ControlFrameLimitEncoder extends DecoratingHttp2ConnectionEncoder {
36     private static final InternalLogger logger = InternalLoggerFactory.getInstance(Http2ControlFrameLimitEncoder.class);
37 
38     private final int maxOutstandingControlFrames;
39     private final ChannelFutureListener outstandingControlFramesListener = new ChannelFutureListener() {
40         @Override
41         public void operationComplete(ChannelFuture future) {
42             outstandingControlFrames--;
43         }
44     };
45     private Http2LifecycleManager lifecycleManager;
46     private int outstandingControlFrames;
47     private boolean limitReached;
48 
Http2ControlFrameLimitEncoder(Http2ConnectionEncoder delegate, int maxOutstandingControlFrames)49     Http2ControlFrameLimitEncoder(Http2ConnectionEncoder delegate, int maxOutstandingControlFrames) {
50         super(delegate);
51         this.maxOutstandingControlFrames = ObjectUtil.checkPositive(maxOutstandingControlFrames,
52                 "maxOutstandingControlFrames");
53     }
54 
55     @Override
lifecycleManager(Http2LifecycleManager lifecycleManager)56     public void lifecycleManager(Http2LifecycleManager lifecycleManager) {
57         this.lifecycleManager = lifecycleManager;
58         super.lifecycleManager(lifecycleManager);
59     }
60 
61     @Override
writeSettingsAck(ChannelHandlerContext ctx, ChannelPromise promise)62     public ChannelFuture writeSettingsAck(ChannelHandlerContext ctx, ChannelPromise promise) {
63         ChannelPromise newPromise = handleOutstandingControlFrames(ctx, promise);
64         if (newPromise == null) {
65             return promise;
66         }
67         return super.writeSettingsAck(ctx, newPromise);
68     }
69 
70     @Override
writePing(ChannelHandlerContext ctx, boolean ack, long data, ChannelPromise promise)71     public ChannelFuture writePing(ChannelHandlerContext ctx, boolean ack, long data, ChannelPromise promise) {
72         // Only apply the limit to ping acks.
73         if (ack) {
74             ChannelPromise newPromise = handleOutstandingControlFrames(ctx, promise);
75             if (newPromise == null) {
76                 return promise;
77             }
78             return super.writePing(ctx, ack, data, newPromise);
79         }
80         return super.writePing(ctx, ack, data, promise);
81     }
82 
83     @Override
writeRstStream( ChannelHandlerContext ctx, int streamId, long errorCode, ChannelPromise promise)84     public ChannelFuture writeRstStream(
85             ChannelHandlerContext ctx, int streamId, long errorCode, ChannelPromise promise) {
86         ChannelPromise newPromise = handleOutstandingControlFrames(ctx, promise);
87         if (newPromise == null) {
88             return promise;
89         }
90         return super.writeRstStream(ctx, streamId, errorCode, newPromise);
91     }
92 
handleOutstandingControlFrames(ChannelHandlerContext ctx, ChannelPromise promise)93     private ChannelPromise handleOutstandingControlFrames(ChannelHandlerContext ctx, ChannelPromise promise) {
94         if (!limitReached) {
95             if (outstandingControlFrames == maxOutstandingControlFrames) {
96                 // Let's try to flush once as we may be able to flush some of the control frames.
97                 ctx.flush();
98             }
99             if (outstandingControlFrames == maxOutstandingControlFrames) {
100                 limitReached = true;
101                 Http2Exception exception = Http2Exception.connectionError(Http2Error.ENHANCE_YOUR_CALM,
102                         "Maximum number %d of outstanding control frames reached", maxOutstandingControlFrames);
103                 logger.info("Maximum number {} of outstanding control frames reached. Closing channel {}",
104                         maxOutstandingControlFrames, ctx.channel(), exception);
105 
106                 // First notify the Http2LifecycleManager and then close the connection.
107                 lifecycleManager.onError(ctx, true, exception);
108                 ctx.close();
109             }
110             outstandingControlFrames++;
111 
112             // We did not reach the limit yet, add the listener to decrement the number of outstanding control frames
113             // once the promise was completed
114             return promise.unvoid().addListener(outstandingControlFramesListener);
115         }
116         return promise;
117     }
118 }
119