Merge pull request #7637 from eclipse/jetty-10.0.x-7625-http3-settings

Fixes #7625 - HTTP/3 error against www.google.com
This commit is contained in:
Simone Bordet 2022-02-23 09:56:29 +01:00 committed by GitHub
commit ec5f05fc10
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 182 additions and 7 deletions

View File

@ -92,6 +92,11 @@ public class ClientHTTP3Session extends ClientProtocolSession
return decoder; return decoder;
} }
public QpackEncoder getQpackEncoder()
{
return encoder;
}
public HTTP3SessionClient getSessionClient() public HTTP3SessionClient getSessionClient()
{ {
return session; return session;

View File

@ -146,4 +146,24 @@ public class HTTP3SessionClient extends HTTP3Session implements Session.Client
return GoAwayFrame.CLIENT_GRACEFUL; return GoAwayFrame.CLIENT_GRACEFUL;
return super.newGoAwayFrame(graceful); return super.newGoAwayFrame(graceful);
} }
@Override
protected void onSettingMaxTableCapacity(long value)
{
getProtocolSession().getQpackEncoder().setCapacity((int)value);
}
@Override
protected void onSettingMaxFieldSectionSize(long value)
{
getProtocolSession().getQpackDecoder().setMaxHeaderSize((int)value);
}
@Override
protected void onSettingMaxBlockedStreams(long value)
{
ClientHTTP3Session session = getProtocolSession();
session.getQpackDecoder().setMaxBlockedStreams((int)value);
session.getQpackEncoder().setMaxBlockedStreams((int)value);
}
} }

View File

@ -17,11 +17,18 @@ import java.util.Map;
public class SettingsFrame extends Frame public class SettingsFrame extends Frame
{ {
public static final long MAX_TABLE_CAPACITY = 0x01;
public static final long MAX_FIELD_SECTION_SIZE = 0x06; public static final long MAX_FIELD_SECTION_SIZE = 0x06;
public static final long MAX_BLOCKED_STREAMS = 0x07;
public static boolean isReserved(long key) public static boolean isReserved(long key)
{ {
return key >= 0 && key <= 5; if (key == MAX_TABLE_CAPACITY ||
key == MAX_FIELD_SECTION_SIZE ||
key == MAX_BLOCKED_STREAMS)
return false;
// Other HTTP/2 settings are reserved and must not be sent/received.
return key >= 0x00 && key <= 0x05;
} }
private final Map<Long, Long> settings; private final Map<Long, Long> settings;

View File

@ -371,9 +371,32 @@ public abstract class HTTP3Session extends ContainerLifeCycle implements Session
{ {
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug("received {} on {}", frame, this); LOG.debug("received {} on {}", frame, this);
frame.getSettings().forEach((key, value) ->
{
if (key == SettingsFrame.MAX_TABLE_CAPACITY)
onSettingMaxTableCapacity(value);
else if (key == SettingsFrame.MAX_FIELD_SECTION_SIZE)
onSettingMaxFieldSectionSize(value);
else if (key == SettingsFrame.MAX_BLOCKED_STREAMS)
onSettingMaxBlockedStreams(value);
});
notifySettings(frame); notifySettings(frame);
} }
protected void onSettingMaxTableCapacity(long value)
{
}
protected void onSettingMaxFieldSectionSize(long value)
{
}
protected void onSettingMaxBlockedStreams(long value)
{
}
private void notifySettings(SettingsFrame frame) private void notifySettings(SettingsFrame frame)
{ {
try try

View File

@ -50,7 +50,8 @@ public class QpackDecoder implements Dumpable
private final List<EncodedFieldSection> _encodedFieldSections = new ArrayList<>(); private final List<EncodedFieldSection> _encodedFieldSections = new ArrayList<>();
private final NBitIntegerParser _integerDecoder = new NBitIntegerParser(); private final NBitIntegerParser _integerDecoder = new NBitIntegerParser();
private final InstructionHandler _instructionHandler = new InstructionHandler(); private final InstructionHandler _instructionHandler = new InstructionHandler();
private final int _maxHeaderSize; private int _maxHeaderSize;
private int _maxBlockedStreams;
private static class MetaDataNotification private static class MetaDataNotification
{ {
@ -87,6 +88,27 @@ public class QpackDecoder implements Dumpable
return _context; return _context;
} }
public int getMaxHeaderSize()
{
return _maxHeaderSize;
}
public void setMaxHeaderSize(int maxHeaderSize)
{
_maxHeaderSize = maxHeaderSize;
}
public int getMaxBlockedStreams()
{
// TODO: implement logic about blocked streams by calling this method.
return _maxBlockedStreams;
}
public void setMaxBlockedStreams(int maxBlockedStreams)
{
_maxBlockedStreams = maxBlockedStreams;
}
public interface Handler public interface Handler
{ {
void onMetaData(long streamId, MetaData metadata); void onMetaData(long streamId, MetaData metadata);
@ -110,7 +132,8 @@ public class QpackDecoder implements Dumpable
LOG.debug("Decoding: streamId={}, buffer={}", streamId, BufferUtil.toDetailString(buffer)); LOG.debug("Decoding: streamId={}, buffer={}", streamId, BufferUtil.toDetailString(buffer));
// If the buffer is big, don't even think about decoding it // If the buffer is big, don't even think about decoding it
if (buffer.remaining() > _maxHeaderSize) int maxHeaderSize = getMaxHeaderSize();
if (buffer.remaining() > maxHeaderSize)
throw new QpackException.SessionException(QPACK_DECOMPRESSION_FAILED, "header_too_large"); throw new QpackException.SessionException(QPACK_DECOMPRESSION_FAILED, "header_too_large");
_integerDecoder.setPrefix(8); _integerDecoder.setPrefix(8);
@ -139,7 +162,7 @@ public class QpackDecoder implements Dumpable
// Decode it straight away if we can, otherwise add it to the list of EncodedFieldSections. // Decode it straight away if we can, otherwise add it to the list of EncodedFieldSections.
if (requiredInsertCount <= insertCount) if (requiredInsertCount <= insertCount)
{ {
MetaData metaData = encodedFieldSection.decode(_context, _maxHeaderSize); MetaData metaData = encodedFieldSection.decode(_context, maxHeaderSize);
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug("Decoded: streamId={}, metadata={}", streamId, metaData); LOG.debug("Decoded: streamId={}, metadata={}", streamId, metaData);
_metaDataNotifications.add(new MetaDataNotification(streamId, metaData, handler)); _metaDataNotifications.add(new MetaDataNotification(streamId, metaData, handler));

View File

@ -91,7 +91,7 @@ public class QpackEncoder implements Dumpable
private final List<Instruction> _instructions = new ArrayList<>(); private final List<Instruction> _instructions = new ArrayList<>();
private final Instruction.Handler _handler; private final Instruction.Handler _handler;
private final QpackContext _context; private final QpackContext _context;
private final int _maxBlockedStreams; private int _maxBlockedStreams;
private final Map<Long, StreamInfo> _streamInfoMap = new HashMap<>(); private final Map<Long, StreamInfo> _streamInfoMap = new HashMap<>();
private final EncoderInstructionParser _parser; private final EncoderInstructionParser _parser;
private final InstructionHandler _instructionHandler = new InstructionHandler(); private final InstructionHandler _instructionHandler = new InstructionHandler();
@ -106,6 +106,21 @@ public class QpackEncoder implements Dumpable
_parser = new EncoderInstructionParser(_instructionHandler); _parser = new EncoderInstructionParser(_instructionHandler);
} }
public int getMaxBlockedStreams()
{
return _maxBlockedStreams;
}
public void setMaxBlockedStreams(int maxBlockedStreams)
{
_maxBlockedStreams = maxBlockedStreams;
}
public int getCapacity()
{
return _context.getDynamicTable().getCapacity();
}
/** /**
* Set the capacity of the DynamicTable and send a instruction to set the capacity on the remote Decoder. * Set the capacity of the DynamicTable and send a instruction to set the capacity on the remote Decoder.
* *
@ -411,7 +426,7 @@ public class QpackEncoder implements Dumpable
return true; return true;
} }
if (_blockedStreams < _maxBlockedStreams) if (_blockedStreams < getMaxBlockedStreams())
{ {
_blockedStreams++; _blockedStreams++;
sectionInfo.block(); sectionInfo.block();

View File

@ -95,6 +95,26 @@ public class HTTP3SessionServer extends HTTP3Session implements Session.Server
return super.newGoAwayFrame(graceful); return super.newGoAwayFrame(graceful);
} }
@Override
protected void onSettingMaxTableCapacity(long value)
{
getProtocolSession().getQpackEncoder().setCapacity((int)value);
}
@Override
protected void onSettingMaxFieldSectionSize(long value)
{
getProtocolSession().getQpackDecoder().setMaxHeaderSize((int)value);
}
@Override
protected void onSettingMaxBlockedStreams(long value)
{
ServerHTTP3Session session = getProtocolSession();
session.getQpackDecoder().setMaxBlockedStreams((int)value);
session.getQpackEncoder().setMaxBlockedStreams((int)value);
}
private void notifyAccept() private void notifyAccept()
{ {
Server.Listener listener = getListener(); Server.Listener listener = getListener();

View File

@ -91,6 +91,11 @@ public class ServerHTTP3Session extends ServerProtocolSession
return decoder; return decoder;
} }
public QpackEncoder getQpackEncoder()
{
return encoder;
}
public HTTP3SessionServer getSessionServer() public HTTP3SessionServer getSessionServer()
{ {
return session; return session;

View File

@ -14,6 +14,7 @@
package org.eclipse.jetty.http3.tests; package org.eclipse.jetty.http3.tests;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.AbstractMap;
import java.util.Map; import java.util.Map;
import java.util.Random; import java.util.Random;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
@ -34,6 +35,7 @@ import org.eclipse.jetty.http3.frames.SettingsFrame;
import org.eclipse.jetty.http3.internal.HTTP3ErrorCode; import org.eclipse.jetty.http3.internal.HTTP3ErrorCode;
import org.eclipse.jetty.http3.internal.HTTP3Session; import org.eclipse.jetty.http3.internal.HTTP3Session;
import org.eclipse.jetty.http3.server.AbstractHTTP3ServerConnectionFactory; import org.eclipse.jetty.http3.server.AbstractHTTP3ServerConnectionFactory;
import org.eclipse.jetty.http3.server.internal.HTTP3SessionServer;
import org.eclipse.jetty.quic.client.ClientQuicSession; import org.eclipse.jetty.quic.client.ClientQuicSession;
import org.eclipse.jetty.quic.common.QuicSession; import org.eclipse.jetty.quic.common.QuicSession;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -41,6 +43,7 @@ import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource; import org.junit.jupiter.params.provider.ValueSource;
import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
@ -90,6 +93,59 @@ public class ClientServerTest extends AbstractClientServerTest
assertTrue(clientSettingsLatch.await(5, TimeUnit.SECONDS)); assertTrue(clientSettingsLatch.await(5, TimeUnit.SECONDS));
} }
@Test
public void testSettings() throws Exception
{
Map.Entry<Long, Long> maxTableCapacity = new AbstractMap.SimpleEntry<>(SettingsFrame.MAX_TABLE_CAPACITY, 1024L);
Map.Entry<Long, Long> maxHeaderSize = new AbstractMap.SimpleEntry<>(SettingsFrame.MAX_FIELD_SECTION_SIZE, 2048L);
Map.Entry<Long, Long> maxBlockedStreams = new AbstractMap.SimpleEntry<>(SettingsFrame.MAX_BLOCKED_STREAMS, 16L);
CountDownLatch settingsLatch = new CountDownLatch(2);
AtomicReference<HTTP3SessionServer> serverSessionRef = new AtomicReference<>();
start(new Session.Server.Listener()
{
@Override
public Map<Long, Long> onPreface(Session session)
{
serverSessionRef.set((HTTP3SessionServer)session);
return Map.ofEntries(maxTableCapacity, maxHeaderSize, maxBlockedStreams);
}
@Override
public void onSettings(Session session, SettingsFrame frame)
{
settingsLatch.countDown();
}
});
HTTP3SessionClient clientSession = (HTTP3SessionClient)newSession(new Session.Client.Listener()
{
@Override
public Map<Long, Long> onPreface(Session session)
{
return Map.ofEntries(maxTableCapacity, maxHeaderSize, maxBlockedStreams);
}
@Override
public void onSettings(Session session, SettingsFrame frame)
{
settingsLatch.countDown();
}
});
assertTrue(settingsLatch.await(5, TimeUnit.SECONDS));
HTTP3SessionServer serverSession = serverSessionRef.get();
assertEquals(maxTableCapacity.getValue(), serverSession.getProtocolSession().getQpackEncoder().getCapacity());
assertEquals(maxBlockedStreams.getValue(), serverSession.getProtocolSession().getQpackEncoder().getMaxBlockedStreams());
assertEquals(maxBlockedStreams.getValue(), serverSession.getProtocolSession().getQpackDecoder().getMaxBlockedStreams());
assertEquals(maxHeaderSize.getValue(), serverSession.getProtocolSession().getQpackDecoder().getMaxHeaderSize());
assertEquals(maxTableCapacity.getValue(), clientSession.getProtocolSession().getQpackEncoder().getCapacity());
assertEquals(maxBlockedStreams.getValue(), clientSession.getProtocolSession().getQpackEncoder().getMaxBlockedStreams());
assertEquals(maxBlockedStreams.getValue(), clientSession.getProtocolSession().getQpackDecoder().getMaxBlockedStreams());
assertEquals(maxHeaderSize.getValue(), clientSession.getProtocolSession().getQpackDecoder().getMaxHeaderSize());
}
@Test @Test
public void testGETThenResponseWithoutContent() throws Exception public void testGETThenResponseWithoutContent() throws Exception
{ {

View File

@ -44,8 +44,9 @@ public class ExternalServerTest
client.start(); client.start();
try try
{ {
HostPort hostPort = new HostPort("google.com:443");
// HostPort hostPort = new HostPort("nghttp2.org:4433"); // HostPort hostPort = new HostPort("nghttp2.org:4433");
HostPort hostPort = new HostPort("quic.tech:8443"); // HostPort hostPort = new HostPort("quic.tech:8443");
// HostPort hostPort = new HostPort("h2o.examp1e.net:443"); // HostPort hostPort = new HostPort("h2o.examp1e.net:443");
// HostPort hostPort = new HostPort("test.privateoctopus.com:4433"); // HostPort hostPort = new HostPort("test.privateoctopus.com:4433");
Session.Client session = client.connect(new InetSocketAddress(hostPort.getHost(), hostPort.getPort()), new Session.Client.Listener() {}) Session.Client session = client.connect(new InetSocketAddress(hostPort.getHost(), hostPort.getPort()), new Session.Client.Listener() {})