Skip to content

Commit 297da38

Browse files
authored
Merge pull request #1630 from baranowb/UNDERTOW-2343_2.3.x
[UNDERTOW-2343] properly handle zealous RST frames in case client ca…
2 parents fcaa1e2 + 62e3094 commit 297da38

File tree

2 files changed

+349
-20
lines changed

2 files changed

+349
-20
lines changed

core/src/main/java/io/undertow/protocols/http2/Http2Channel.java

Lines changed: 47 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ public class Http2Channel extends AbstractFramedChannel<Http2Channel, AbstractHt
237237
*/
238238
private volatile int receiveWindowSize;
239239

240-
private final StreamCache sentRstStreams = new StreamCache();
240+
private final StreamCache resetStreamTracker = new StreamCache();
241241

242242

243243
public Http2Channel(StreamConnection connectedStreamChannel, String protocol, ByteBufferPool bufferPool, PooledByteBuffer data, boolean clientSide, boolean fromUpgrade, OptionMap settings) {
@@ -470,10 +470,10 @@ protected AbstractHttp2StreamSourceChannel createChannelImpl(FrameHeaderData fra
470470
//make sure it exists
471471
StreamHolder existing = currentStreams.get(frameParser.streamId);
472472
if (existing == null) {
473-
existing = sentRstStreams.find(frameParser.streamId);
473+
existing = resetStreamTracker.find(frameParser.streamId);
474474
}
475475
if(existing == null || existing.sourceClosed) {
476-
if (existing != null || sentRstStreams.find(frameParser.streamId) == null) {
476+
if (existing != null || resetStreamTracker.find(frameParser.streamId) == null) {
477477
sendGoAway(ERROR_PROTOCOL_ERROR);
478478
}
479479
frameData.close();
@@ -507,7 +507,7 @@ protected AbstractHttp2StreamSourceChannel createChannelImpl(FrameHeaderData fra
507507

508508
StreamHolder holder = currentStreams.get(frameParser.streamId);
509509
if(holder == null) {
510-
holder = sentRstStreams.find(frameParser.streamId);
510+
holder = resetStreamTracker.find(frameParser.streamId);
511511
if (holder != null) {
512512
holder.sourceChannel = (Http2StreamSourceChannel) channel;
513513
} else {
@@ -723,7 +723,7 @@ protected void handleBrokenSinkChannel(Throwable e) {
723723
@Override
724724
protected void closeSubChannels() {
725725
closeSubChannels(currentStreams);
726-
closeSubChannels(sentRstStreams.getStreamHolders());
726+
closeSubChannels(resetStreamTracker.getStreamHolders());
727727
}
728728

729729
private void closeSubChannels(Map<Integer, StreamHolder> streams) {
@@ -859,7 +859,7 @@ public void handleWindowUpdate(int streamId, int deltaWindowSize) throws IOExcep
859859
StreamHolder holder = currentStreams.get(streamId);
860860
Http2StreamSinkChannel stream = holder != null ? holder.sinkChannel : null;
861861
if (stream == null) {
862-
if (sentRstStreams.find(streamId) == null && isIdle(streamId)) {
862+
if (resetStreamTracker.find(streamId) == null && isIdle(streamId)) {
863863
sendGoAway(ERROR_PROTOCOL_ERROR);
864864
}
865865
} else {
@@ -1211,7 +1211,7 @@ public void sendRstStream(int streamId, int statusCode) {
12111211
//no point sending if the channel is closed
12121212
return;
12131213
}
1214-
sentRstStreams.store(streamId, handleRstStream(streamId, false));
1214+
handleRstStream(streamId, false);
12151215
if(UndertowLogger.REQUEST_IO_LOGGER.isDebugEnabled()) {
12161216
UndertowLogger.REQUEST_IO_LOGGER.debugf(new ClosedChannelException(), "Sending rststream on channel %s stream %s", this, streamId);
12171217
}
@@ -1222,6 +1222,7 @@ public void sendRstStream(int streamId, int statusCode) {
12221222
private StreamHolder handleRstStream(int streamId, boolean receivedRst) {
12231223
final StreamHolder holder = currentStreams.remove(streamId);
12241224
if(holder != null) {
1225+
resetStreamTracker.store(streamId, holder);
12251226
if(streamId % 2 == (isClient() ? 1 : 0)) {
12261227
sendConcurrentStreamsAtomicUpdater.getAndDecrement(this);
12271228
} else {
@@ -1234,24 +1235,46 @@ private StreamHolder handleRstStream(int streamId, boolean receivedRst) {
12341235
holder.sourceChannel.rstStream();
12351236
}
12361237
if (receivedRst) {
1237-
long currentTimeMillis = System.currentTimeMillis();
1238-
// reset the window tracking
1239-
if (currentTimeMillis - lastRstFrameMillis >= rstFramesTimeWindow) {
1240-
lastRstFrameMillis = currentTimeMillis;
1241-
receivedRstFramesPerWindow = 1;
1238+
if (holder.sinkChannel != null && holder.sourceChannel == null) {
1239+
//Server side originated, no input from client other than RST
1240+
//this can happen on page refresh when push happens, but client
1241+
//still has valid cache entry
1242+
holder.resetByPeer = receivedRst;
12421243
} else {
1243-
//
1244-
receivedRstFramesPerWindow ++;
1245-
if (receivedRstFramesPerWindow > maxRstFramesPerWindow) {
1246-
sendGoAway(Http2Channel.ERROR_ENHANCE_YOUR_CALM);
1247-
UndertowLogger.REQUEST_IO_LOGGER.debugf("Reached maximum number of rst frames %s during %s ms, sending GO_AWAY 11", maxRstFramesPerWindow, rstFramesTimeWindow);
1248-
}
1244+
handleRstWindow();
12491245
}
12501246
}
1247+
} else if(receivedRst){
1248+
final StreamHolder resetStream = resetStreamTracker.find(streamId);
1249+
if(resetStream != null && resetStream.resetByPeer) {
1250+
//This means other side reset stream at some point.
1251+
//depending on peer or network latency our frames might be late and
1252+
//cause other end to flare up with RST, this RST can be safely ignored.
1253+
//TODO: do we need to check error code?
1254+
} else {
1255+
handleRstWindow();
1256+
}
12511257
}
12521258
return holder;
12531259
}
12541260

1261+
private void handleRstWindow() {
1262+
long currentTimeMillis = System.currentTimeMillis();
1263+
// reset the window tracking
1264+
if (currentTimeMillis - lastRstFrameMillis >= rstFramesTimeWindow) {
1265+
lastRstFrameMillis = currentTimeMillis;
1266+
receivedRstFramesPerWindow = 1;
1267+
} else {
1268+
receivedRstFramesPerWindow++;
1269+
if (receivedRstFramesPerWindow > maxRstFramesPerWindow) {
1270+
sendGoAway(Http2Channel.ERROR_ENHANCE_YOUR_CALM);
1271+
UndertowLogger.REQUEST_IO_LOGGER.debugf(
1272+
"Reached maximum number of rst frames %s during %s ms, sending GO_AWAY 11",
1273+
maxRstFramesPerWindow, rstFramesTimeWindow);
1274+
}
1275+
}
1276+
}
1277+
12551278
/**
12561279
* Creates a response stream to respond to the initial HTTP upgrade
12571280
*
@@ -1286,7 +1309,7 @@ public boolean isThisGoneAway() {
12861309
Http2StreamSourceChannel removeStreamSource(int streamId) {
12871310
StreamHolder existing = currentStreams.get(streamId);
12881311
if (existing == null) {
1289-
existing = sentRstStreams.find(streamId);
1312+
existing = resetStreamTracker.find(streamId);
12901313
return existing == null? null : existing.sourceChannel;
12911314
}
12921315
existing.sourceClosed = true;
@@ -1306,7 +1329,7 @@ Http2StreamSourceChannel removeStreamSource(int streamId) {
13061329
Http2StreamSourceChannel getIncomingStream(int streamId) {
13071330
StreamHolder existing = currentStreams.get(streamId);
13081331
if(existing == null){
1309-
existing = sentRstStreams.find(streamId);
1332+
existing = resetStreamTracker.find(streamId);
13101333
if (existing == null) {
13111334
return null;
13121335
}
@@ -1351,6 +1374,10 @@ int getMaxHeaderListSize() {
13511374
private static final class StreamHolder {
13521375
boolean sourceClosed = false;
13531376
boolean sinkClosed = false;
1377+
/**
1378+
* This flag is set only in case of short lived server push that was reset by remote end.
1379+
*/
1380+
boolean resetByPeer = false;
13541381
Http2StreamSourceChannel sourceChannel;
13551382
Http2StreamSinkChannel sinkChannel;
13561383

0 commit comments

Comments
 (0)