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