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.