TurnPort should retry allocation with a new address on error STUN_ERROR_ALLOCATION_MISMATCH.
BUG=3570
R=juberti@webrtc.org, mallinath@webrtc.org
Review URL: https://webrtc-codereview.appspot.com/20999004
git-svn-id: http://webrtc.googlecode.com/svn/trunk/talk@7070 4adac7df-926f-26a2-2b94-8c16560cd09d
diff --git a/p2p/base/port.h b/p2p/base/port.h
index cccfdad..4893586 100644
--- a/p2p/base/port.h
+++ b/p2p/base/port.h
@@ -155,6 +155,7 @@
uint64 IceTiebreaker() const { return tiebreaker_; }
virtual bool SharedSocket() const { return shared_socket_; }
+ void ResetSharedSocket() { shared_socket_ = false; }
// The thread on which this port performs its I/O.
rtc::Thread* thread() { return thread_; }
diff --git a/p2p/base/turnport.cc b/p2p/base/turnport.cc
index 6ab0e2b..2908d71 100644
--- a/p2p/base/turnport.cc
+++ b/p2p/base/turnport.cc
@@ -51,6 +51,10 @@
static const size_t TURN_CHANNEL_HEADER_SIZE = 4U;
+// Retry at most twice (i.e. three different ALLOCATE requests) on
+// STUN_ERROR_ALLOCATION_MISMATCH error per rfc5766.
+static const size_t MAX_ALLOCATE_MISMATCH_RETRIES = 2;
+
inline bool IsTurnChannelData(uint16 msg_type) {
return ((msg_type & 0xC000) == 0x4000); // MSB are 0b01
}
@@ -188,7 +192,8 @@
request_manager_(thread),
next_channel_number_(TURN_CHANNEL_NUMBER_START),
connected_(false),
- server_priority_(server_priority) {
+ server_priority_(server_priority),
+ allocate_mismatch_retries_(0) {
request_manager_.SignalSendPacket.connect(this, &TurnPort::OnSendStunPacket);
}
@@ -212,7 +217,8 @@
request_manager_(thread),
next_channel_number_(TURN_CHANNEL_NUMBER_START),
connected_(false),
- server_priority_(server_priority) {
+ server_priority_(server_priority),
+ allocate_mismatch_retries_(0) {
request_manager_.SignalSendPacket.connect(this, &TurnPort::OnSendStunPacket);
}
@@ -271,6 +277,8 @@
}
bool TurnPort::CreateTurnClientSocket() {
+ ASSERT(!socket_ || SharedSocket());
+
if (server_address_.proto == PROTO_UDP && !SharedSocket()) {
socket_ = socket_factory()->CreateUdpSocket(
rtc::SocketAddress(ip(), 0), min_port(), max_port());
@@ -340,6 +348,29 @@
}
}
+void TurnPort::OnAllocateMismatch() {
+ if (allocate_mismatch_retries_ >= MAX_ALLOCATE_MISMATCH_RETRIES) {
+ LOG_J(LS_WARNING, this) << "Giving up on the port after "
+ << allocate_mismatch_retries_
+ << " retries for STUN_ERROR_ALLOCATION_MISMATCH";
+ OnAllocateError();
+ return;
+ }
+
+ LOG_J(LS_INFO, this) << "Allocating a new socket after "
+ << "STUN_ERROR_ALLOCATION_MISMATCH, retry = "
+ << allocate_mismatch_retries_ + 1;
+ if (SharedSocket()) {
+ ResetSharedSocket();
+ } else {
+ delete socket_;
+ }
+ socket_ = NULL;
+
+ PrepareAddress();
+ ++allocate_mismatch_retries_;
+}
+
Connection* TurnPort::CreateConnection(const Candidate& address,
CandidateOrigin origin) {
// TURN-UDP can only connect to UDP candidates.
@@ -580,6 +611,9 @@
if (message->message_id == MSG_ERROR) {
SignalPortError(this);
return;
+ } else if (message->message_id == MSG_ALLOCATE_MISMATCH) {
+ OnAllocateMismatch();
+ return;
}
Port::OnMessage(message);
@@ -844,6 +878,11 @@
case STUN_ERROR_TRY_ALTERNATE:
OnTryAlternate(response, error_code->code());
break;
+ case STUN_ERROR_ALLOCATION_MISMATCH:
+ // We must handle this error async because trying to delete the socket in
+ // OnErrorResponse will cause a deadlock on the socket.
+ port_->thread()->Post(port_, TurnPort::MSG_ALLOCATE_MISMATCH);
+ break;
default:
LOG_J(LS_WARNING, port_) << "Allocate response error, code="
<< error_code->code();
@@ -966,7 +1005,6 @@
}
void TurnRefreshRequest::OnErrorResponse(StunMessage* response) {
- // TODO(juberti): Handle 437 error response as a success.
const StunErrorCodeAttribute* error_code = response->GetErrorCode();
LOG_J(LS_WARNING, port_) << "Refresh response error, code="
<< error_code->code();
diff --git a/p2p/base/turnport.h b/p2p/base/turnport.h
index b9ec3b0..ab7d4e7 100644
--- a/p2p/base/turnport.h
+++ b/p2p/base/turnport.h
@@ -120,6 +120,12 @@
int error() const { return error_; }
+ void OnAllocateMismatch();
+
+ rtc::AsyncPacketSocket* socket() const {
+ return socket_;
+ }
+
// Signal with resolved server address.
// Parameters are port, server address and resolved server address.
// This signal will be sent only if server address is resolved successfully.
@@ -154,7 +160,10 @@
int server_priority);
private:
- enum { MSG_ERROR = MSG_FIRST_AVAILABLE };
+ enum {
+ MSG_ERROR = MSG_FIRST_AVAILABLE,
+ MSG_ALLOCATE_MISMATCH
+ };
typedef std::list<TurnEntry*> EntryList;
typedef std::map<rtc::Socket::Option, int> SocketOptionsMap;
@@ -230,6 +239,9 @@
// calculating the candidate priority.
int server_priority_;
+ // The number of retries made due to allocate mismatch error.
+ size_t allocate_mismatch_retries_;
+
friend class TurnEntry;
friend class TurnAllocateRequest;
friend class TurnRefreshRequest;
diff --git a/p2p/base/turnport_unittest.cc b/p2p/base/turnport_unittest.cc
index 14befa9..d895cbd 100644
--- a/p2p/base/turnport_unittest.cc
+++ b/p2p/base/turnport_unittest.cc
@@ -210,10 +210,13 @@
const cricket::ProtocolAddress& server_address) {
ASSERT(server_address.proto == cricket::PROTO_UDP);
- socket_.reset(socket_factory_.CreateUdpSocket(
- rtc::SocketAddress(kLocalAddr1.ipaddr(), 0), 0, 0));
- ASSERT_TRUE(socket_ != NULL);
- socket_->SignalReadPacket.connect(this, &TurnPortTest::OnSocketReadPacket);
+ if (!socket_) {
+ socket_.reset(socket_factory_.CreateUdpSocket(
+ rtc::SocketAddress(kLocalAddr1.ipaddr(), 0), 0, 0));
+ ASSERT_TRUE(socket_ != NULL);
+ socket_->SignalReadPacket.connect(
+ this, &TurnPortTest::OnSocketReadPacket);
+ }
cricket::RelayCredentials credentials(username, password);
turn_port_.reset(cricket::TurnPort::Create(
@@ -413,6 +416,80 @@
ASSERT_EQ(0U, turn_port_->Candidates().size());
}
+// Tests that a new local address is created after
+// STUN_ERROR_ALLOCATION_MISMATCH.
+TEST_F(TurnPortTest, TestTurnAllocateMismatch) {
+ // Do a normal allocation first.
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr);
+ turn_port_->PrepareAddress();
+ EXPECT_TRUE_WAIT(turn_ready_, kTimeout);
+ rtc::SocketAddress first_addr(turn_port_->socket()->GetLocalAddress());
+
+ // Forces the socket server to assign the same port.
+ ss_->SetNextPortForTesting(first_addr.port());
+
+ turn_ready_ = false;
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr);
+ turn_port_->PrepareAddress();
+
+ // Verifies that the new port has the same address.
+ EXPECT_EQ(first_addr, turn_port_->socket()->GetLocalAddress());
+
+ EXPECT_TRUE_WAIT(turn_ready_, kTimeout);
+
+ // Verifies that the new port has a different address now.
+ EXPECT_NE(first_addr, turn_port_->socket()->GetLocalAddress());
+}
+
+// Tests that a shared-socket-TurnPort creates its own socket after
+// STUN_ERROR_ALLOCATION_MISMATCH.
+TEST_F(TurnPortTest, TestSharedSocketAllocateMismatch) {
+ // Do a normal allocation first.
+ CreateSharedTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr);
+ turn_port_->PrepareAddress();
+ EXPECT_TRUE_WAIT(turn_ready_, kTimeout);
+ rtc::SocketAddress first_addr(turn_port_->socket()->GetLocalAddress());
+
+ turn_ready_ = false;
+ CreateSharedTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr);
+
+ // Verifies that the new port has the same address.
+ EXPECT_EQ(first_addr, turn_port_->socket()->GetLocalAddress());
+ EXPECT_TRUE(turn_port_->SharedSocket());
+
+ turn_port_->PrepareAddress();
+ EXPECT_TRUE_WAIT(turn_ready_, kTimeout);
+
+ // Verifies that the new port has a different address now.
+ EXPECT_NE(first_addr, turn_port_->socket()->GetLocalAddress());
+ EXPECT_FALSE(turn_port_->SharedSocket());
+}
+
+TEST_F(TurnPortTest, TestTurnTcpAllocateMismatch) {
+ turn_server_.AddInternalSocket(kTurnTcpIntAddr, cricket::PROTO_TCP);
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnTcpProtoAddr);
+
+ // Do a normal allocation first.
+ turn_port_->PrepareAddress();
+ EXPECT_TRUE_WAIT(turn_ready_, kTimeout);
+ rtc::SocketAddress first_addr(turn_port_->socket()->GetLocalAddress());
+
+ // Forces the socket server to assign the same port.
+ ss_->SetNextPortForTesting(first_addr.port());
+
+ turn_ready_ = false;
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnTcpProtoAddr);
+ turn_port_->PrepareAddress();
+
+ // Verifies that the new port has the same address.
+ EXPECT_EQ(first_addr, turn_port_->socket()->GetLocalAddress());
+
+ EXPECT_TRUE_WAIT(turn_ready_, kTimeout);
+
+ // Verifies that the new port has a different address now.
+ EXPECT_NE(first_addr, turn_port_->socket()->GetLocalAddress());
+}
+
// Do a TURN allocation and try to send a packet to it from the outside.
// The packet should be dropped. Then, try to send a packet from TURN to the
// outside. It should reach its destination. Finally, try again from the
diff --git a/p2p/base/turnserver.cc b/p2p/base/turnserver.cc
index 08c060d..dbcbcd4 100644
--- a/p2p/base/turnserver.cc
+++ b/p2p/base/turnserver.cc
@@ -62,9 +62,9 @@
return ((msg_type & 0xC000) == 0x4000);
}
-// IDs used for posted messages.
+// IDs used for posted messages for TurnServer::Allocation.
enum {
- MSG_TIMEOUT,
+ MSG_ALLOCATION_TIMEOUT,
};
// Encapsulates a TURN allocation.
@@ -608,7 +608,9 @@
InternalSocketMap::iterator iter = server_sockets_.find(socket);
if (iter != server_sockets_.end()) {
rtc::AsyncPacketSocket* socket = iter->first;
- delete socket;
+ // We must destroy the socket async to avoid invalidating the sigslot
+ // callback list iterator inside a sigslot callback.
+ rtc::Thread::Current()->Dispose(socket);
server_sockets_.erase(iter);
}
}
@@ -662,7 +664,7 @@
it != perms_.end(); ++it) {
delete *it;
}
- thread_->Clear(this, MSG_TIMEOUT);
+ thread_->Clear(this, MSG_ALLOCATION_TIMEOUT);
LOG_J(LS_INFO, this) << "Allocation destroyed";
}
@@ -707,7 +709,7 @@
// Figure out the lifetime and start the allocation timer.
int lifetime_secs = ComputeLifetime(msg);
- thread_->PostDelayed(lifetime_secs * 1000, this, MSG_TIMEOUT);
+ thread_->PostDelayed(lifetime_secs * 1000, this, MSG_ALLOCATION_TIMEOUT);
LOG_J(LS_INFO, this) << "Created allocation, lifetime=" << lifetime_secs;
@@ -734,8 +736,8 @@
int lifetime_secs = ComputeLifetime(msg);
// Reset the expiration timer.
- thread_->Clear(this, MSG_TIMEOUT);
- thread_->PostDelayed(lifetime_secs * 1000, this, MSG_TIMEOUT);
+ thread_->Clear(this, MSG_ALLOCATION_TIMEOUT);
+ thread_->PostDelayed(lifetime_secs * 1000, this, MSG_ALLOCATION_TIMEOUT);
LOG_J(LS_INFO, this) << "Refreshed allocation, lifetime=" << lifetime_secs;
@@ -963,7 +965,7 @@
}
void TurnServer::Allocation::OnMessage(rtc::Message* msg) {
- ASSERT(msg->message_id == MSG_TIMEOUT);
+ ASSERT(msg->message_id == MSG_ALLOCATION_TIMEOUT);
SignalDestroyed(this);
delete this;
}
@@ -988,16 +990,16 @@
}
TurnServer::Permission::~Permission() {
- thread_->Clear(this, MSG_TIMEOUT);
+ thread_->Clear(this, MSG_ALLOCATION_TIMEOUT);
}
void TurnServer::Permission::Refresh() {
- thread_->Clear(this, MSG_TIMEOUT);
- thread_->PostDelayed(kPermissionTimeout, this, MSG_TIMEOUT);
+ thread_->Clear(this, MSG_ALLOCATION_TIMEOUT);
+ thread_->PostDelayed(kPermissionTimeout, this, MSG_ALLOCATION_TIMEOUT);
}
void TurnServer::Permission::OnMessage(rtc::Message* msg) {
- ASSERT(msg->message_id == MSG_TIMEOUT);
+ ASSERT(msg->message_id == MSG_ALLOCATION_TIMEOUT);
SignalDestroyed(this);
delete this;
}
@@ -1009,16 +1011,16 @@
}
TurnServer::Channel::~Channel() {
- thread_->Clear(this, MSG_TIMEOUT);
+ thread_->Clear(this, MSG_ALLOCATION_TIMEOUT);
}
void TurnServer::Channel::Refresh() {
- thread_->Clear(this, MSG_TIMEOUT);
- thread_->PostDelayed(kChannelTimeout, this, MSG_TIMEOUT);
+ thread_->Clear(this, MSG_ALLOCATION_TIMEOUT);
+ thread_->PostDelayed(kChannelTimeout, this, MSG_ALLOCATION_TIMEOUT);
}
void TurnServer::Channel::OnMessage(rtc::Message* msg) {
- ASSERT(msg->message_id == MSG_TIMEOUT);
+ ASSERT(msg->message_id == MSG_ALLOCATION_TIMEOUT);
SignalDestroyed(this);
delete this;
}