View Javadoc
1   /*
2    * Copyright 2025 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.ChannelHandlerContext;
19  import io.netty.channel.ChannelPromise;
20  import io.netty.util.internal.logging.InternalLogger;
21  import io.netty.util.internal.logging.InternalLoggerFactory;
22  
23  import java.util.concurrent.TimeUnit;
24  
25  /**
26   * {@link DecoratingHttp2ConnectionEncoder} which guards against a remote peer that will trigger a massive amount
27   * of RST frames on an existing connection.
28   * This encoder will tear-down the connection once we reached the configured limit to reduce the risk of DDOS.
29   */
30  final class Http2MaxRstFrameLimitEncoder extends DecoratingHttp2ConnectionEncoder {
31      private static final InternalLogger logger = InternalLoggerFactory.getInstance(Http2MaxRstFrameLimitEncoder.class);
32  
33      private final long nanosPerWindow;
34      private final int maxRstFramesPerWindow;
35      private long lastRstFrameNano = System.nanoTime();
36      private int sendRstInWindow;
37      private Http2LifecycleManager lifecycleManager;
38  
39      Http2MaxRstFrameLimitEncoder(Http2ConnectionEncoder delegate, int maxRstFramesPerWindow, int secondsPerWindow) {
40          super(delegate);
41          this.maxRstFramesPerWindow = maxRstFramesPerWindow;
42          this.nanosPerWindow = TimeUnit.SECONDS.toNanos(secondsPerWindow);
43      }
44  
45      @Override
46      public void lifecycleManager(Http2LifecycleManager lifecycleManager) {
47          this.lifecycleManager = lifecycleManager;
48          super.lifecycleManager(lifecycleManager);
49      }
50  
51      @Override
52      public ChannelFuture writeRstStream(ChannelHandlerContext ctx, int streamId, long errorCode,
53                                          ChannelPromise promise) {
54          ChannelFuture future = super.writeRstStream(ctx, streamId, errorCode, promise);
55          if (countRstFrameErrorCode(errorCode)) {
56              long currentNano = System.nanoTime();
57              if (currentNano - lastRstFrameNano >= nanosPerWindow) {
58                  lastRstFrameNano = currentNano;
59                  sendRstInWindow = 1;
60              } else {
61                  sendRstInWindow++;
62                  if (sendRstInWindow > maxRstFramesPerWindow) {
63                      Http2Exception exception = Http2Exception.connectionError(Http2Error.ENHANCE_YOUR_CALM,
64                              "Maximum number %d of RST frames frames reached within %d seconds", maxRstFramesPerWindow,
65                              TimeUnit.NANOSECONDS.toSeconds(nanosPerWindow));
66  
67                      logger.debug("{} Maximum number {} of RST frames reached within {} seconds, " +
68                                      "closing connection with {} error", ctx.channel(), maxRstFramesPerWindow,
69                              TimeUnit.NANOSECONDS.toSeconds(nanosPerWindow), exception.error(),
70                              exception);
71                      // First notify the Http2LifecycleManager and then close the connection.
72                      lifecycleManager.onError(ctx, true, exception);
73                      ctx.close();
74                  }
75              }
76          }
77  
78          return future;
79      }
80  
81      private boolean countRstFrameErrorCode(long errorCode) {
82          // Don't count CANCEL and NO_ERROR as these might be ok.
83          return errorCode != Http2Error.CANCEL.code() && errorCode != Http2Error.NO_ERROR.code();
84      }
85  }