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