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 <jonaso@webrtc.org>
Reviewed-by: Taylor Brandstetter <deadbeef@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#22666}
diff --git a/p2p/base/turnport.cc b/p2p/base/turnport.cc
index 78746ea..e718bb1 100644
--- a/p2p/base/turnport.cc
+++ b/p2p/base/turnport.cc
@@ -266,9 +266,7 @@
// 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::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 @@
for (auto kv : connections()) {
kv.second->Destroy();
}
+
+ SignalTurnPortClosed(this);
}
void TurnPort::OnMessage(rtc::Message* message) {
@@ -882,6 +894,9 @@
PrepareAddress();
}
break;
+ case MSG_ALLOCATION_RELEASED:
+ Close();
+ break;
default:
Port::OnMessage(message);
}
@@ -1430,8 +1445,16 @@
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 798afd3..7ebb5f1 100644
--- a/p2p/base/turnport.h
+++ b/p2p/base/turnport.h
@@ -107,6 +107,10 @@
virtual std::vector<std::string> GetTlsAlpnProtocols() const;
virtual std::vector<std::string> 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 @@
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<TurnPort*> SignalTurnPortClosed;
+
// All public methods/signals below are for testing only.
sigslot::signal2<TurnPort*, int> SignalTurnRefreshResult;
sigslot::signal3<TurnPort*, const rtc::SocketAddress&, int>
@@ -217,7 +226,8 @@
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<TurnEntry*> EntryList;
diff --git a/p2p/base/turnport_unittest.cc b/p2p/base/turnport_unittest.cc
index d68beaf..cf3bb1d 100644
--- a/p2p/base/turnport_unittest.cc
+++ b/p2p/base/turnport_unittest.cc
@@ -154,6 +154,8 @@
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 @@
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 @@
&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 @@
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<TurnPortTest*>(this),
+ &TurnPortTest::OnTurnReadPacket);
+ conn2->SignalReadPacket.connect(static_cast<TurnPortTest*>(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 @@
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 @@
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.