View Javadoc
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    * https://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.netty.handler.codec.http2;
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.util.internal.ObjectUtil;
22  import io.netty.util.internal.logging.InternalLogger;
23  import io.netty.util.internal.logging.InternalLoggerFactory;
24  
25  /**
26   * {@link DecoratingHttp2ConnectionEncoder} which guards against a remote peer that will trigger a massive amount
27   * of control frames but will not consume our responses to these.
28   * This encoder will tear-down the connection once we reached the configured limit to reduce the risk of DDOS.
29   */
30  final class Http2ControlFrameLimitEncoder extends DecoratingHttp2ConnectionEncoder {
31      private static final InternalLogger logger = InternalLoggerFactory.getInstance(Http2ControlFrameLimitEncoder.class);
32  
33      private final int maxOutstandingControlFrames;
34      private final ChannelFutureListener outstandingControlFramesListener = new ChannelFutureListener() {
35          @Override
36          public void operationComplete(ChannelFuture future) {
37              outstandingControlFrames--;
38          }
39      };
40      private Http2LifecycleManager lifecycleManager;
41      private int outstandingControlFrames;
42      private boolean limitReached;
43  
44      Http2ControlFrameLimitEncoder(Http2ConnectionEncoder delegate, int maxOutstandingControlFrames) {
45          super(delegate);
46          this.maxOutstandingControlFrames = ObjectUtil.checkPositive(maxOutstandingControlFrames,
47                  "maxOutstandingControlFrames");
48      }
49  
50      @Override
51      public void lifecycleManager(Http2LifecycleManager lifecycleManager) {
52          this.lifecycleManager = lifecycleManager;
53          super.lifecycleManager(lifecycleManager);
54      }
55  
56      @Override
57      public ChannelFuture writeSettingsAck(ChannelHandlerContext ctx, ChannelPromise promise) {
58          ChannelPromise newPromise = handleOutstandingControlFrames(ctx, promise);
59          if (newPromise == null) {
60              return promise;
61          }
62          return super.writeSettingsAck(ctx, newPromise);
63      }
64  
65      @Override
66      public ChannelFuture writePing(ChannelHandlerContext ctx, boolean ack, long data, ChannelPromise promise) {
67          // Only apply the limit to ping acks.
68          if (ack) {
69              ChannelPromise newPromise = handleOutstandingControlFrames(ctx, promise);
70              if (newPromise == null) {
71                  return promise;
72              }
73              return super.writePing(ctx, ack, data, newPromise);
74          }
75          return super.writePing(ctx, ack, data, promise);
76      }
77  
78      @Override
79      public ChannelFuture writeRstStream(
80              ChannelHandlerContext ctx, int streamId, long errorCode, ChannelPromise promise) {
81          ChannelPromise newPromise = handleOutstandingControlFrames(ctx, promise);
82          if (newPromise == null) {
83              return promise;
84          }
85          return super.writeRstStream(ctx, streamId, errorCode, newPromise);
86      }
87  
88      private ChannelPromise handleOutstandingControlFrames(ChannelHandlerContext ctx, ChannelPromise promise) {
89          if (!limitReached) {
90              if (outstandingControlFrames == maxOutstandingControlFrames) {
91                  // Let's try to flush once as we may be able to flush some of the control frames.
92                  ctx.flush();
93              }
94              if (outstandingControlFrames == maxOutstandingControlFrames) {
95                  limitReached = true;
96                  Http2Exception exception = Http2Exception.connectionError(Http2Error.ENHANCE_YOUR_CALM,
97                          "Maximum number %d of outstanding control frames reached", maxOutstandingControlFrames);
98                  logger.info("Maximum number {} of outstanding control frames reached. Closing channel {}",
99                          maxOutstandingControlFrames, ctx.channel(), exception);
100 
101                 // First notify the Http2LifecycleManager and then close the connection.
102                 lifecycleManager.onError(ctx, true, exception);
103                 ctx.close();
104             }
105             outstandingControlFrames++;
106 
107             // We did not reach the limit yet, add the listener to decrement the number of outstanding control frames
108             // once the promise was completed
109             return promise.unvoid().addListener(outstandingControlFramesListener);
110         }
111         return promise;
112     }
113 }