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:
parent
95e7dbb7c7
commit
c99dc31501
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user