Internal change
PiperOrigin-RevId: 400073226
diff --git a/quic/core/quic_dispatcher.cc b/quic/core/quic_dispatcher.cc
index 7bf4576..ad17281 100644
--- a/quic/core/quic_dispatcher.cc
+++ b/quic/core/quic_dispatcher.cc
@@ -499,12 +499,54 @@
server_connection_id, expected_server_connection_id_length);
}
+namespace {
+inline bool IsSourceUdpPortBlocked(uint16_t port) {
+ // TODO(dschinazi) make this function constexpr when we remove flag
+ // protection.
+ if (!GetQuicReloadableFlag(quic_blocked_ports)) {
+ return port == 0;
+ }
+ QUIC_RELOADABLE_FLAG_COUNT(quic_blocked_ports);
+ // These UDP source ports have been observed in large scale denial of service
+ // attacks and are not expected to ever carry user traffic, they are therefore
+ // blocked as a safety measure. See draft-ietf-quic-applicability for details.
+ constexpr uint16_t blocked_ports[] = {
+ 0, // We cannot send to port 0 so drop that source port.
+ 17, // Quote of the Day, can loop with QUIC.
+ 19, // Chargen, can loop with QUIC.
+ 53, // DNS, vulnerable to reflection attacks.
+ 111, // Portmap.
+ 123, // NTP, vulnerable to reflection attacks.
+ 137, // NETBIOS Name Service,
+ 128, // NETBIOS Datagram Service
+ 161, // SNMP.
+ 389, // CLDAP.
+ 500, // IKE, can loop with QUIC.
+ 1900, // SSDP, vulnerable to reflection attacks.
+ 5353, // mDNS, vulnerable to reflection attacks.
+ 11211, // memcache, vulnerable to reflection attacks.
+ // This list MUST be sorted in increasing order.
+ };
+ constexpr size_t num_blocked_ports = ABSL_ARRAYSIZE(blocked_ports);
+ constexpr uint16_t highest_blocked_port =
+ blocked_ports[num_blocked_ports - 1];
+ if (QUICHE_PREDICT_TRUE(port > highest_blocked_port)) {
+ // Early-return to skip comparisons for the majority of traffic.
+ return false;
+ }
+ for (size_t i = 0; i < num_blocked_ports; i++) {
+ if (port == blocked_ports[i]) {
+ return true;
+ }
+ }
+ return false;
+}
+} // namespace
+
bool QuicDispatcher::MaybeDispatchPacket(
const ReceivedPacketInfo& packet_info) {
- // Port zero is only allowed for unidirectional UDP, so is disallowed by QUIC.
- // Given that we can't even send a reply rejecting the packet, just drop the
- // packet.
- if (packet_info.peer_address.port() == 0) {
+ if (IsSourceUdpPortBlocked(packet_info.peer_address.port())) {
+ // Silently drop the received packet.
return true;
}
diff --git a/quic/core/quic_dispatcher_test.cc b/quic/core/quic_dispatcher_test.cc
index 2851ff5..4fc2a93 100644
--- a/quic/core/quic_dispatcher_test.cc
+++ b/quic/core/quic_dispatcher_test.cc
@@ -1207,6 +1207,43 @@
"data");
}
+TEST_P(QuicDispatcherTestAllVersions, ProcessPacketWithBlockedPort) {
+ SetQuicReloadableFlag(quic_blocked_ports, true);
+ CreateTimeWaitListManager();
+
+ QuicSocketAddress client_address(QuicIpAddress::Loopback4(), 17);
+
+ // dispatcher_ should drop this packet.
+ EXPECT_CALL(*dispatcher_, CreateQuicSession(TestConnectionId(1), _,
+ client_address, _, _, _))
+ .Times(0);
+ EXPECT_CALL(*time_wait_list_manager_, ProcessPacket(_, _, _, _, _, _))
+ .Times(0);
+ EXPECT_CALL(*time_wait_list_manager_, AddConnectionIdToTimeWait(_, _))
+ .Times(0);
+ ProcessPacket(client_address, TestConnectionId(1), /*has_version_flag=*/true,
+ "data");
+}
+
+TEST_P(QuicDispatcherTestAllVersions, ProcessPacketWithNonBlockedPort) {
+ CreateTimeWaitListManager();
+
+ // Port 443 must not be blocked because it might be useful for proxies to send
+ // proxied traffic with source port 443 as that allows building a full QUIC
+ // proxy using a single UDP socket.
+ QuicSocketAddress client_address(QuicIpAddress::Loopback4(), 443);
+
+ // dispatcher_ should not drop this packet.
+ EXPECT_CALL(*dispatcher_,
+ CreateQuicSession(TestConnectionId(1), _, client_address,
+ Eq(ExpectedAlpn()), _, _))
+ .WillOnce(Return(ByMove(CreateSession(
+ dispatcher_.get(), config_, TestConnectionId(1), client_address,
+ &mock_helper_, &mock_alarm_factory_, &crypto_config_,
+ QuicDispatcherPeer::GetCache(dispatcher_.get()), &session1_))));
+ ProcessFirstFlight(client_address, TestConnectionId(1));
+}
+
TEST_P(QuicDispatcherTestAllVersions,
DropPacketWithKnownVersionAndInvalidShortInitialConnectionId) {
if (!version_.AllowsVariableLengthConnectionIds()) {
@@ -2437,7 +2474,7 @@
// A bunch of non-CHLO should be buffered upon arrival.
size_t kNumConnections = kMaxConnectionsWithoutCHLO + 1;
for (size_t i = 1; i <= kNumConnections; ++i) {
- QuicSocketAddress client_address(QuicIpAddress::Loopback4(), i);
+ QuicSocketAddress client_address(QuicIpAddress::Loopback4(), 20000 + i);
QuicConnectionId conn_id = TestConnectionId(i);
EXPECT_CALL(*dispatcher_,
ShouldCreateOrBufferPacketForConnection(
@@ -2455,7 +2492,7 @@
kNumConnections);
// Process CHLOs to create session for these connections.
for (size_t i = 1; i <= kNumConnections; ++i) {
- QuicSocketAddress client_address(QuicIpAddress::Loopback4(), i);
+ QuicSocketAddress client_address(QuicIpAddress::Loopback4(), 20000 + i);
QuicConnectionId conn_id = TestConnectionId(i);
if (i == kNumConnections) {
EXPECT_CALL(*dispatcher_,
diff --git a/quic/core/quic_flags_list.h b/quic/core/quic_flags_list.h
index ac7efde..70e8e8e 100644
--- a/quic/core/quic_flags_list.h
+++ b/quic/core/quic_flags_list.h
@@ -119,6 +119,8 @@
QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_send_timestamps, false)
// When true, QuicDispatcher will silently drop QUIC packets that have invalid flags.
QUIC_FLAG(FLAGS_quic_restart_flag_quic_drop_invalid_flags, true)
+// When true, QuicDispatcher will silently drop incoming packets whose UDP source port is on the blocklist.
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_blocked_ports, false)
// When true, defaults to BBR congestion control instead of Cubic.
QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_default_to_bbr, false)
// When true, prevents QUIC\'s PacingSender from generating bursts when the congestion controller is CWND limited and not pacing limited.