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}
This commit is contained in:
Jonas Oreland 2018-03-28 08:00:50 +02:00 committed by Commit Bot
parent 95e7dbb7c7
commit c99dc31501
3 changed files with 114 additions and 6 deletions

View File

@ -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);
}

View File

@ -107,6 +107,10 @@ class TurnPort : public Port {
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 @@ 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<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 @@ 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<TurnEntry*> EntryList;

View File

@ -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<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 @@ 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.