From c99dc31501c5697483248d06b1568b11c8afa7ba Mon Sep 17 00:00:00 2001 From: Jonas Oreland Date: Wed, 28 Mar 2018 08:00:50 +0200 Subject: [PATCH] Add ability to release TURN allocation gracefully This patch adds TurnPort::Release that release a TURN allocation by sending a REFRESH with lifetime 0 without destroying the object. This allows for graceful shutdown of a TurnPort that can e.g be used for mobility. Bug: webtrc:9067 Change-Id: I1e4d9232ae08d6fe14f5612f776a541c03c3beec Reviewed-on: https://webrtc-review.googlesource.com/64722 Commit-Queue: Jonas Oreland Reviewed-by: Taylor Brandstetter Cr-Commit-Position: refs/heads/master@{#22666} --- p2p/base/turnport.cc | 33 ++++++++++++--- p2p/base/turnport.h | 12 +++++- p2p/base/turnport_unittest.cc | 75 +++++++++++++++++++++++++++++++++++ 3 files changed, 114 insertions(+), 6 deletions(-) diff --git a/p2p/base/turnport.cc b/p2p/base/turnport.cc index 78746ea9b5..e718bb19de 100644 --- a/p2p/base/turnport.cc +++ b/p2p/base/turnport.cc @@ -266,9 +266,7 @@ TurnPort::~TurnPort() { // release the allocation by sending a refresh with // lifetime 0. if (ready()) { - TurnRefreshRequest bye(this); - bye.set_lifetime(0); - SendRequest(&bye, 0); + Release(); } while (!entries_.empty()) { @@ -841,6 +839,18 @@ void TurnPort::HandleRefreshError() { } } +void TurnPort::Release() { + // Remove any pending refresh requests. + request_manager_.Clear(); + + // Send refresh with lifetime 0. + TurnRefreshRequest* req = new TurnRefreshRequest(this); + req->set_lifetime(0); + SendRequest(req, 0); + + state_ = STATE_RECEIVEONLY; +} + void TurnPort::Close() { if (!ready()) { OnAllocateError(); @@ -852,6 +862,8 @@ void TurnPort::Close() { for (auto kv : connections()) { kv.second->Destroy(); } + + SignalTurnPortClosed(this); } void TurnPort::OnMessage(rtc::Message* message) { @@ -882,6 +894,9 @@ void TurnPort::OnMessage(rtc::Message* message) { PrepareAddress(); } break; + case MSG_ALLOCATION_RELEASED: + Close(); + break; default: Port::OnMessage(message); } @@ -1430,8 +1445,16 @@ void TurnRefreshRequest::OnResponse(StunMessage* response) { return; } - // Schedule a refresh based on the returned lifetime value. - port_->ScheduleRefresh(lifetime_attr->value()); + if (lifetime_attr->value() > 0) { + // Schedule a refresh based on the returned lifetime value. + port_->ScheduleRefresh(lifetime_attr->value()); + } else { + // If we scheduled a refresh with lifetime 0, we're releasing this + // allocation; see TurnPort::Release. + port_->thread()->Post(RTC_FROM_HERE, port_, + TurnPort::MSG_ALLOCATION_RELEASED); + } + port_->SignalTurnRefreshResult(port_, TURN_SUCCESS_RESULT_CODE); } diff --git a/p2p/base/turnport.h b/p2p/base/turnport.h index 798afd31fd..7ebb5f1af6 100644 --- a/p2p/base/turnport.h +++ b/p2p/base/turnport.h @@ -107,6 +107,10 @@ class TurnPort : public Port { virtual std::vector GetTlsAlpnProtocols() const; virtual std::vector GetTlsEllipticCurves() const; + // Release a TURN allocation by sending a refresh with lifetime 0. + // Sets state to STATE_RECEIVEONLY. + void Release(); + void PrepareAddress() override; Connection* CreateConnection(const Candidate& c, PortInterface::CandidateOrigin origin) override; @@ -161,6 +165,11 @@ class TurnPort : public Port { const rtc::SocketAddress&, const rtc::SocketAddress&> SignalResolvedServerAddress; + // Signal when TurnPort is closed, + // e.g remote socket closed (TCP) + // or receiveing a REFRESH response with lifetime 0. + sigslot::signal1 SignalTurnPortClosed; + // All public methods/signals below are for testing only. sigslot::signal2 SignalTurnRefreshResult; sigslot::signal3 @@ -217,7 +226,8 @@ class TurnPort : public Port { MSG_ALLOCATE_ERROR = MSG_FIRST_AVAILABLE, MSG_ALLOCATE_MISMATCH, MSG_TRY_ALTERNATE_SERVER, - MSG_REFRESH_ERROR + MSG_REFRESH_ERROR, + MSG_ALLOCATION_RELEASED }; typedef std::list EntryList; diff --git a/p2p/base/turnport_unittest.cc b/p2p/base/turnport_unittest.cc index d68beafdc3..cf3bb1de5b 100644 --- a/p2p/base/turnport_unittest.cc +++ b/p2p/base/turnport_unittest.cc @@ -154,6 +154,8 @@ class TurnPortTest : public testing::Test, turn_error_(false), turn_unknown_address_(false), turn_create_permission_success_(false), + turn_port_closed_(false), + turn_port_destroyed_(false), udp_ready_(false), test_finish_(false) { // Some code uses "last received time == 0" to represent "nothing received @@ -210,6 +212,13 @@ class TurnPortTest : public testing::Test, turn_port_->HandleIncomingPacket(socket, data, size, remote_addr, packet_time); } + void OnTurnPortClosed(TurnPort* port) { + turn_port_closed_ = true; + } + void OnTurnPortDestroyed(PortInterface* port) { + turn_port_destroyed_ = true; + } + rtc::AsyncSocket* CreateServerSocket(const SocketAddress addr) { rtc::AsyncSocket* socket = ss_->CreateAsyncSocket(SOCK_STREAM); EXPECT_GE(socket->Bind(addr), 0); @@ -316,6 +325,10 @@ class TurnPortTest : public testing::Test, &TurnPortTest::OnTurnCreatePermissionResult); turn_port_->SignalTurnRefreshResult.connect( this, &TurnPortTest::OnTurnRefreshResult); + turn_port_->SignalTurnPortClosed.connect( + this, &TurnPortTest::OnTurnPortClosed); + turn_port_->SignalDestroyed.connect( + this, &TurnPortTest::OnTurnPortDestroyed); } void CreateUdpPort() { CreateUdpPort(kLocalAddr2); } @@ -677,6 +690,48 @@ class TurnPortTest : public testing::Test, kSimulatedRtt, fake_clock_); } + // Test that the TURN allocation is released by sending a refresh request + // with lifetime 0 when Release is called. + void TestTurnGracefulReleaseAllocation(ProtocolType protocol_type) { + PrepareTurnAndUdpPorts(protocol_type); + + // Create connections and send pings. + Connection* conn1 = turn_port_->CreateConnection( + udp_port_->Candidates()[0], Port::ORIGIN_MESSAGE); + Connection* conn2 = udp_port_->CreateConnection( + turn_port_->Candidates()[0], Port::ORIGIN_MESSAGE); + ASSERT_TRUE(conn1 != NULL); + ASSERT_TRUE(conn2 != NULL); + conn1->SignalReadPacket.connect(static_cast(this), + &TurnPortTest::OnTurnReadPacket); + conn2->SignalReadPacket.connect(static_cast(this), + &TurnPortTest::OnUdpReadPacket); + conn1->Ping(0); + EXPECT_EQ_SIMULATED_WAIT(Connection::STATE_WRITABLE, conn1->write_state(), + kSimulatedRtt * 2, fake_clock_); + conn2->Ping(0); + EXPECT_EQ_SIMULATED_WAIT(Connection::STATE_WRITABLE, conn2->write_state(), + kSimulatedRtt * 2, fake_clock_); + + // Send some data from Udp to TurnPort. + unsigned char buf[256] = { 0 }; + conn2->Send(buf, sizeof(buf), options); + + // Now release the TurnPort allocation. + // This will send a REFRESH with lifetime 0 to server. + turn_port_->Release(); + + // Wait for the TurnPort to signal closed. + ASSERT_TRUE_SIMULATED_WAIT(turn_port_closed_, kSimulatedRtt, fake_clock_); + + // But the data should have arrived first. + ASSERT_EQ(1ul, turn_packets_.size()); + EXPECT_EQ(sizeof(buf), turn_packets_[0].size()); + + // The allocation is released at server. + EXPECT_EQ(0U, turn_server_.server()->allocations().size()); + } + protected: rtc::ScopedFakeClock fake_clock_; // When a "create port" helper method is called with an IP, we create a @@ -694,6 +749,8 @@ class TurnPortTest : public testing::Test, bool turn_error_; bool turn_unknown_address_; bool turn_create_permission_success_; + bool turn_port_closed_; + bool turn_port_destroyed_; bool udp_ready_; bool test_finish_; bool turn_refresh_success_ = false; @@ -1451,6 +1508,24 @@ TEST_F(TurnPortTest, TestTurnTLSReleaseAllocation) { TestTurnReleaseAllocation(PROTO_TLS); } +TEST_F(TurnPortTest, TestTurnUDPGracefulReleaseAllocation) { + turn_server_.AddInternalSocket(kTurnTcpIntAddr, PROTO_UDP); + CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr); + TestTurnGracefulReleaseAllocation(PROTO_UDP); +} + +TEST_F(TurnPortTest, TestTurnTCPGracefulReleaseAllocation) { + turn_server_.AddInternalSocket(kTurnTcpIntAddr, PROTO_TCP); + CreateTurnPort(kTurnUsername, kTurnPassword, kTurnTcpProtoAddr); + TestTurnGracefulReleaseAllocation(PROTO_TCP); +} + +TEST_F(TurnPortTest, TestTurnTLSGracefulReleaseAllocation) { + turn_server_.AddInternalSocket(kTurnTcpIntAddr, PROTO_TLS); + CreateTurnPort(kTurnUsername, kTurnPassword, kTurnTlsProtoAddr); + TestTurnGracefulReleaseAllocation(PROTO_TLS); +} + // Test that nothing bad happens if we try to create a connection to the same // remote address twice. Previously there was a bug that caused this to hit a // DCHECK.