p2p: avoid duplicate UDP allocations during gathering Like TCP, a running sequence that has not reached its UDP phase yet covers UDP even before its port exists. Bug: webrtc:566467875 Change-Id: I6b01f21a219fa26ce509ab9dda6cc180443c3562 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/506060 Reviewed-by: Tomas Gunnarsson <tommi@webrtc.org> Commit-Queue: Philipp Hancke <philipp.hancke@googlemail.com> Reviewed-by: Danil Chapovalov <danilchap@webrtc.org> Cr-Commit-Position: refs/heads/main@{#48742}
diff --git a/p2p/client/basic_port_allocator.cc b/p2p/client/basic_port_allocator.cc index 6c0828f..1950d73 100644 --- a/p2p/client/basic_port_allocator.cc +++ b/p2p/client/basic_port_allocator.cc
@@ -1320,18 +1320,14 @@ // already have a port of the corresponding type. Look for a port that // matches this AllocationSequence's network, is the right protocol, and // hasn't encountered an error. - // TODO(deadbeef): This doesn't take into account that there may be another - // AllocationSequence that's ABOUT to allocate a UDP port, but hasn't yet. - // This can happen if, say, there's a network change event right before an - // application-triggered ICE restart. Hopefully this problem will just go - // away if we get rid of the gathering "phases" though, which is planned. - // // // PORTALLOCATOR_DISABLE_UDP is used to disable a Port from gathering the host // candidate (and srflx candidate if Port::SharedSocket()), and we do not want // to disable the gathering of these candidates just becaue of an existing // Port over PROTO_UDP, namely a TurnPort over UDP. - if (absl::c_any_of(session_->ports_, + if ((state_ == kRunning && phase_ == PHASE_UDP && + !IsFlagSet(PORTALLOCATOR_DISABLE_UDP)) || + absl::c_any_of(session_->ports_, [this](const BasicPortAllocatorSession::PortData& p) { return !p.pruned() && p.port()->Network() == network_ && p.port()->GetProtocol() == PROTO_UDP &&
diff --git a/p2p/client/basic_port_allocator_unittest.cc b/p2p/client/basic_port_allocator_unittest.cc index f9a8465..24e7a1d 100644 --- a/p2p/client/basic_port_allocator_unittest.cc +++ b/p2p/client/basic_port_allocator_unittest.cc
@@ -1126,6 +1126,58 @@ 1); } +TEST_F(BasicPortAllocatorTest, + NetworkChangeBeforeFirstPhaseDoesNotDuplicateUdp) { + ResetWithNoServersOrNat(); + ASSERT_TRUE(CreateSession(ICE_CANDIDATE_COMPONENT_RTP)); + session_->StartGettingPorts(); + time_controller_.AdvanceTime(TimeDelta::Millis(1000)); + candidate_allocation_done_ = false; + + // Both notifications run DoAllocate() before the sequence created by the + // first one gets to its UDP phase. + AddInterface(kClientAddr); + network_manager_.NotifyNetworksChanged(); + ASSERT_TRUE(waiter_.Until([&] { return candidate_allocation_done_; })); + EXPECT_EQ(CountPorts(ports_, IceCandidateType::kHost, PROTO_UDP, kClientAddr), + 1); + EXPECT_EQ(CountPorts(ports_, IceCandidateType::kHost, PROTO_TCP, kClientAddr), + 1); +} + +TEST_F(BasicPortAllocatorTest, ConcurrentSessionsDoNotDuplicatePorts) { + constexpr int kNumSessions = 10; + ResetWithNoServersOrNat(); + AddInterface(kClientAddr); + + // Each session subscribes to the shared network manager, whose + // StartUpdating() signals all sessions. Start each one a task after the + // previous one so these signals hit the others at every stage of gathering. + std::vector<std::unique_ptr<PortAllocatorSession>> sessions; + absl::AnyInvocable<void()> start_next = [&] { + sessions.push_back(CreateSession("session", ICE_CANDIDATE_COMPONENT_RTP)); + sessions.back()->StartGettingPorts(); + if (sessions.size() < kNumSessions) { + thread_->PostTask([&] { start_next(); }); + } + }; + start_next(); + ASSERT_TRUE(waiter_.Until([&] { + return sessions.size() == kNumSessions && + absl::c_all_of(sessions, [](const auto& session) { + return session->CandidatesAllocationDone(); + }); + })); + + for (const auto& session : sessions) { + std::vector<PortInterface*> ports = session->ReadyPorts(); + EXPECT_EQ( + CountPorts(ports, IceCandidateType::kHost, PROTO_UDP, kClientAddr), 1); + EXPECT_EQ( + CountPorts(ports, IceCandidateType::kHost, PROTO_TCP, kClientAddr), 1); + } +} + // Test that when the same network interface is brought down and up, the // port allocator session will restart a new allocation sequence if // it is not stopped.