Skip to content

Commit

Permalink
Add debug logging for overhead count
Browse files Browse the repository at this point in the history
  • Loading branch information
markt-asf committed Jun 15, 2021
1 parent c6e65dd commit 30cae12
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 17 deletions.
3 changes: 3 additions & 0 deletions java/org/apache/coyote/http2/Http2Protocol.java
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ public class Http2Protocol implements UpgradeProtocol {
static final int DEFAULT_MAX_CONCURRENT_STREAM_EXECUTION = 20;

static final int DEFAULT_OVERHEAD_COUNT_FACTOR = 1;
// Not currently configurable. This makes the practical limit for
// overheadCountFactor to be 2.
static final int DEFAULT_OVERHEAD_REDUCTION_FACTOR = -1;
static final int DEFAULT_OVERHEAD_CONTINUATION_THRESHOLD = 1024;
static final int DEFAULT_OVERHEAD_DATA_THRESHOLD = 1024;
static final int DEFAULT_OVERHEAD_WINDOW_UPDATE_THRESHOLD = 1024;
Expand Down
70 changes: 53 additions & 17 deletions java/org/apache/coyote/http2/Http2UpgradeHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -750,7 +750,7 @@ void writeBody(Stream stream, ByteBuffer data, int len, boolean finished) throws
Integer.toString(len), Boolean.valueOf(finished)));
}

reduceOverheadCount();
reduceOverheadCount(FrameType.DATA);

// Need to check this now since sending end of stream will change this.
boolean writeable = stream.canWrite();
Expand Down Expand Up @@ -1391,13 +1391,49 @@ protected final int getWeight() {
}


private void reduceOverheadCount() {
overheadCount.decrementAndGet();
private void reduceOverheadCount(FrameType frameType) {
// A non-overhead frame reduces the overhead count by
// Http2Protocol.DEFAULT_OVERHEAD_REDUCTION_FACTOR. A simple browser
// request is likely to have one non-overhead frame (HEADERS) and one
// overhead frame (REPRIORITISE). With the default settings the overhead
// count will remain unchanged for each simple request.
// Requests and responses with bodies will create additional
// non-overhead frames, further reducing the overhead count.
updateOverheadCount(frameType, Http2Protocol.DEFAULT_OVERHEAD_REDUCTION_FACTOR);
}


private void increaseOverheadCount() {
overheadCount.addAndGet(getProtocol().getOverheadCountFactor());
private void increaseOverheadCount(FrameType frameType) {
// An overhead frame increases the overhead count by
// overheadCountFactor. By default, this means an overhead frame
// increases the overhead count by 1. A simple browser request is likely
// to have one non-overhead frame (HEADERS) and one overhead frame
// (REPRIORITISE). With the default settings the overhead count will
// remain unchanged for each simple request.
updateOverheadCount(frameType, getProtocol().getOverheadCountFactor());
}


private void increaseOverheadCount(FrameType frameType, int increment) {
// Overhead frames that indicate inefficient (and potentially malicious)
// use of small frames trigger an increase that is inversely
// proportional to size. The default threshold for all three potential
// areas for abuse (HEADERS, DATA, WINDOW_UPDATE) is 1024 bytes. Frames
// with sizes smaller than this will trigger an increase of
// threshold/size.
// DATA and WINDOW_UPDATE take an average over the last two non-final
// frames to allow for client buffering schemes that can result in some
// small DATA payloads.
updateOverheadCount(frameType, increment);
}


private void updateOverheadCount(FrameType frameType, int increment) {
long newOverheadCount = overheadCount.addAndGet(increment);
if (log.isDebugEnabled()) {
log.debug(sm.getString("upgradeHandler.overheadChange",
connectionId, getIdAsString(), frameType.name(), Long.valueOf(newOverheadCount)));
}
}


Expand Down Expand Up @@ -1456,7 +1492,7 @@ public HpackDecoder getHpackDecoder() {
@Override
public ByteBuffer startRequestBodyFrame(int streamId, int payloadSize, boolean endOfStream) throws Http2Exception {
// DATA frames reduce the overhead count ...
reduceOverheadCount();
reduceOverheadCount(FrameType.DATA);

// .. but lots of small payloads are inefficient so that will increase
// the overhead count unless it is the final DATA frame where small
Expand All @@ -1475,7 +1511,7 @@ public ByteBuffer startRequestBodyFrame(int streamId, int payloadSize, boolean e
average = 1;
}
if (average < overheadThreshold) {
overheadCount.addAndGet(overheadThreshold / average);
increaseOverheadCount(FrameType.DATA, overheadThreshold / average);
}
}

Expand Down Expand Up @@ -1557,7 +1593,7 @@ public HeaderEmitter headersStart(int streamId, boolean headersEndStream)
log.debug(sm.getString("upgradeHandler.noNewStreams",
connectionId, Integer.toString(streamId)));
}
reduceOverheadCount();
reduceOverheadCount(FrameType.HEADERS);
// Stateless so a static can be used to save on GC
return HEADER_SINK;
}
Expand Down Expand Up @@ -1585,7 +1621,7 @@ public void reprioritise(int streamId, int parentStreamId,
getConnectionId(), Integer.valueOf(streamId)), Http2Error.PROTOCOL_ERROR);
}

increaseOverheadCount();
increaseOverheadCount(FrameType.PRIORITY);

AbstractNonZeroStream abstractNonZeroStream = getAbstractNonZeroStream(streamId);
if (abstractNonZeroStream == null) {
Expand All @@ -1611,9 +1647,9 @@ public void headersContinue(int payloadSize, boolean endOfHeaders) {
if (payloadSize < overheadThreshold) {
if (payloadSize == 0) {
// Avoid division by zero
overheadCount.addAndGet(overheadThreshold);
increaseOverheadCount(FrameType.HEADERS, overheadThreshold);
} else {
overheadCount.addAndGet(overheadThreshold / payloadSize);
increaseOverheadCount(FrameType.HEADERS, overheadThreshold / payloadSize);
}
}
}
Expand All @@ -1633,13 +1669,13 @@ public void headersEnd(int streamId) throws Http2Exception {
if (localSettings.getMaxConcurrentStreams() < activeRemoteStreamCount.incrementAndGet()) {
setConnectionTimeoutForStreamCount(activeRemoteStreamCount.decrementAndGet());
// Ignoring maxConcurrentStreams increases the overhead count
increaseOverheadCount();
increaseOverheadCount(FrameType.HEADERS);
throw new StreamException(sm.getString("upgradeHandler.tooManyRemoteStreams",
Long.toString(localSettings.getMaxConcurrentStreams())),
Http2Error.REFUSED_STREAM, streamId);
}
// Valid new stream reduces the overhead count
reduceOverheadCount();
reduceOverheadCount(FrameType.HEADERS);

processStreamOnContainerThread(stream);
}
Expand Down Expand Up @@ -1677,7 +1713,7 @@ public void reset(int streamId, long errorCode) throws Http2Exception {
@Override
public void setting(Setting setting, long value) throws ConnectionException {

increaseOverheadCount();
increaseOverheadCount(FrameType.SETTINGS);

// Possible with empty settings frame
if (setting == null) {
Expand Down Expand Up @@ -1726,7 +1762,7 @@ public void settingsEnd(boolean ack) throws IOException {
@Override
public void pingReceive(byte[] payload, boolean ack) throws IOException {
if (!ack) {
increaseOverheadCount();
increaseOverheadCount(FrameType.PING);
}
pingManager.receivePing(payload, ack);
}
Expand Down Expand Up @@ -1762,7 +1798,7 @@ public void incrementWindowSize(int streamId, int increment) throws Http2Excepti
// Check for small increments which are inefficient
if (average < overheadThreshold) {
// The smaller the increment, the larger the overhead
overheadCount.addAndGet(overheadThreshold / average);
increaseOverheadCount(FrameType.WINDOW_UPDATE, overheadThreshold / average);
}

incrementWindowSize(increment);
Expand All @@ -1776,7 +1812,7 @@ public void incrementWindowSize(int streamId, int increment) throws Http2Excepti
BacklogTracker tracker = backLogStreams.get(stream);
if (tracker == null || increment < tracker.getRemainingReservation()) {
// The smaller the increment, the larger the overhead
overheadCount.addAndGet(overheadThreshold / average);
increaseOverheadCount(FrameType.WINDOW_UPDATE, overheadThreshold / average);
}
}

Expand Down
1 change: 1 addition & 0 deletions java/org/apache/coyote/http2/LocalStrings.properties
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ upgradeHandler.invalidPreface=Connection [{0}], Invalid connection preface
upgradeHandler.ioerror=Connection [{0}]
upgradeHandler.noAllocation=Connection [{0}], Stream [{1}], Timeout waiting for allocation
upgradeHandler.noNewStreams=Connection [{0}], Stream [{1}], Stream ignored as no new streams are permitted on this connection
upgradeHandler.overheadChange=Connection [{0}], Stream [{1}], Frame type [{2}] resulted in new overhead count of [{3}]
upgradeHandler.pause.entry=Connection [{0}] Pausing
upgradeHandler.pingFailed=Connection [{0}] Failed to send ping to client
upgradeHandler.prefaceReceived=Connection [{0}], Connection preface received from client
Expand Down
5 changes: 5 additions & 0 deletions webapps/docs/changelog.xml
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,11 @@
from Tomcat are larger than <code>overheadWindowUpdateThreshold</code>.
(markt)
</fix>
<add>
Add additional debug logging to track the current state of the HTTP/2
overhead count that Tomcat uses to detect and close potentially
malicious connections. (markt)
</add>
</changelog>
</subsection>
<subsection name="Other">
Expand Down

0 comments on commit 30cae12

Please sign in to comment.