-
Notifications
You must be signed in to change notification settings - Fork 927
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Limit max reset frames to mitigate HTTP/2 RST floods (#5232)
Motivation: To mitigate against the "HTTP/2 Rapid Reset" attack, it is recommended that HTTP/2 servers should close connections that exceed the concurrent stream limit. Reference: - https://blog.cloudflare.com/technical-breakdown-http2-rapid-reset-ddos-attack/ - https://www.cve.org/CVERecord?id=CVE-2023-44487 - netty/netty@58f75f6#diff-82f568a075ff63e9727ce8622f3a2b1553099182edf1fd0b4f857226252b05adR47 Modifications: - Add `ServerBuilder.http2MaxRestFramesPerWindow()` option and `-Dcom.linecorp.armeria.defaultHttp2MaxResetFramesPerMinute<integer>` property to limit the maximum allowed RST frames. - If not set, 400 RST frames per minute are allowed by default. - Bump Netty version to 4.1.100 from 4.1.96 Result: You can now protect your server against DDOS caused by RST floods. ```java Server .builder() .http2MaxResetFramesPerWindow(100, 10) .build(); ```
- Loading branch information
Showing
12 changed files
with
222 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
95 changes: 95 additions & 0 deletions
95
core/src/test/java/com/linecorp/armeria/server/MaxResetFramesTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
/* | ||
* Copyright 2023 LINE Corporation | ||
* | ||
* LINE Corporation licenses this file to you under the Apache License, | ||
* version 2.0 (the "License"); you may not use this file except in compliance | ||
* with the License. You may obtain a copy of the License at: | ||
* | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||
* License for the specific language governing permissions and limitations | ||
* under the License. | ||
*/ | ||
|
||
package com.linecorp.armeria.server; | ||
|
||
import static com.google.common.collect.ImmutableList.toImmutableList; | ||
import static org.assertj.core.api.Assertions.assertThat; | ||
import static org.awaitility.Awaitility.await; | ||
|
||
import java.util.List; | ||
import java.util.concurrent.CompletableFuture; | ||
import java.util.stream.IntStream; | ||
|
||
import org.junit.jupiter.api.Test; | ||
import org.junit.jupiter.api.extension.RegisterExtension; | ||
|
||
import com.spotify.futures.CompletableFutures; | ||
|
||
import com.linecorp.armeria.client.ClientFactory; | ||
import com.linecorp.armeria.client.CountingConnectionPoolListener; | ||
import com.linecorp.armeria.client.WebClient; | ||
import com.linecorp.armeria.common.AggregatedHttpResponse; | ||
import com.linecorp.armeria.common.HttpMethod; | ||
import com.linecorp.armeria.common.HttpObject; | ||
import com.linecorp.armeria.common.HttpRequest; | ||
import com.linecorp.armeria.common.HttpResponse; | ||
import com.linecorp.armeria.common.RequestHeaders; | ||
import com.linecorp.armeria.common.SessionProtocol; | ||
import com.linecorp.armeria.common.stream.StreamMessage; | ||
import com.linecorp.armeria.testing.junit5.server.ServerExtension; | ||
|
||
class MaxResetFramesTest { | ||
@RegisterExtension | ||
static final ServerExtension server = new ServerExtension() { | ||
@Override | ||
protected void configure(ServerBuilder sb) { | ||
sb.idleTimeoutMillis(0); | ||
sb.http2MaxResetFramesPerWindow(10, 60); | ||
sb.service("/", (ctx, req) -> { | ||
return HttpResponse.of(req.aggregate().thenApply(unused -> HttpResponse.of(200))); | ||
}); | ||
} | ||
}; | ||
|
||
@Test | ||
void shouldCloseConnectionWhenExceedingMaxResetFrames() { | ||
final CountingConnectionPoolListener listener = new CountingConnectionPoolListener(); | ||
try (ClientFactory factory = ClientFactory.builder() | ||
.connectionPoolListener(listener) | ||
.idleTimeoutMillis(0) | ||
.build()) { | ||
final WebClient client = WebClient.builder(server.uri(SessionProtocol.H2C)) | ||
.factory(factory) | ||
.build(); | ||
final List<CompletableFuture<AggregatedHttpResponse>> futures = | ||
IntStream.range(0, 11) | ||
.mapToObj(unused -> HttpRequest.of(RequestHeaders.of(HttpMethod.POST, "/"), | ||
StreamMessage.of(InvalidHttpObject.INSTANCE))) | ||
.map(client::execute) | ||
.map(HttpResponse::aggregate) | ||
.collect(toImmutableList()); | ||
|
||
CompletableFutures.successfulAsList(futures, cause -> null).join(); | ||
assertThat(listener.opened()).isEqualTo(1); | ||
await().untilAsserted(() -> assertThat(listener.closed()).isEqualTo(1)); | ||
} | ||
} | ||
|
||
/** | ||
* {@link WebClient} resets a stream when it receives an invalid {@link HttpObject} from | ||
* {@link HttpRequest}. | ||
*/ | ||
private enum InvalidHttpObject implements HttpObject { | ||
|
||
INSTANCE; | ||
|
||
@Override | ||
public boolean isEndOfStream() { | ||
return false; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters