implement handling ALTERNATE-SERVER response from turn protocol as
specified in RFC 5766, also created 2 test cases for both the normal
redirection case as well as when a pingpong situation happens, the
allocation should fail
BUG=1986 TURN ALTERNATE-SERVER support
R=juberti@webrtc.org
Review URL: https://webrtc-codereview.appspot.com/21249004
git-svn-id: http://webrtc.googlecode.com/svn/trunk@6985 4adac7df-926f-26a2-2b94-8c16560cd09d
diff --git a/talk/p2p/base/stun.cc b/talk/p2p/base/stun.cc
index be96b76..061fd9a 100644
--- a/talk/p2p/base/stun.cc
+++ b/talk/p2p/base/stun.cc
@@ -41,6 +41,7 @@
namespace cricket {
+const char STUN_ERROR_REASON_TRY_ALTERNATE_SERVER[] = "Try Alternate Server";
const char STUN_ERROR_REASON_BAD_REQUEST[] = "Bad Request";
const char STUN_ERROR_REASON_UNAUTHORIZED[] = "Unauthorized";
const char STUN_ERROR_REASON_FORBIDDEN[] = "Forbidden";
@@ -401,7 +402,7 @@
case STUN_ATTR_NONCE: return STUN_VALUE_BYTE_STRING;
case STUN_ATTR_XOR_MAPPED_ADDRESS: return STUN_VALUE_XOR_ADDRESS;
case STUN_ATTR_SOFTWARE: return STUN_VALUE_BYTE_STRING;
- case STUN_ATTR_ALTERNATE_SERVER: return STUN_VALUE_BYTE_STRING;
+ case STUN_ATTR_ALTERNATE_SERVER: return STUN_VALUE_ADDRESS;
case STUN_ATTR_FINGERPRINT: return STUN_VALUE_UINT32;
case STUN_ATTR_RETRANSMIT_COUNT: return STUN_VALUE_UINT32;
default: return STUN_VALUE_UNKNOWN;
diff --git a/talk/p2p/base/stun.h b/talk/p2p/base/stun.h
index b22b51e..c4f522b 100644
--- a/talk/p2p/base/stun.h
+++ b/talk/p2p/base/stun.h
@@ -63,7 +63,7 @@
STUN_ATTR_NONCE = 0x0015, // ByteString
STUN_ATTR_XOR_MAPPED_ADDRESS = 0x0020, // XorAddress
STUN_ATTR_SOFTWARE = 0x8022, // ByteString
- STUN_ATTR_ALTERNATE_SERVER = 0x8023, // ByteString
+ STUN_ATTR_ALTERNATE_SERVER = 0x8023, // Address
STUN_ATTR_FINGERPRINT = 0x8028, // UInt32
STUN_ATTR_RETRANSMIT_COUNT = 0xFF00 // UInt32
};
@@ -104,6 +104,7 @@
};
// Strings for the error codes above.
+extern const char STUN_ERROR_REASON_TRY_ALTERNATE_SERVER[];
extern const char STUN_ERROR_REASON_BAD_REQUEST[];
extern const char STUN_ERROR_REASON_UNAUTHORIZED[];
extern const char STUN_ERROR_REASON_UNKNOWN_ATTRIBUTE[];
diff --git a/talk/p2p/base/testturnserver.h b/talk/p2p/base/testturnserver.h
index e2c0ccb..6c30afe 100644
--- a/talk/p2p/base/testturnserver.h
+++ b/talk/p2p/base/testturnserver.h
@@ -29,6 +29,7 @@
#define TALK_P2P_BASE_TESTTURNSERVER_H_
#include <string>
+#include <vector>
#include "talk/p2p/base/basicpacketsocketfactory.h"
#include "talk/p2p/base/stun.h"
@@ -41,6 +42,27 @@
static const char kTestRealm[] = "example.org";
static const char kTestSoftware[] = "TestTurnServer";
+class TestTurnRedirector : public TurnRedirectInterface {
+ public:
+ explicit TestTurnRedirector(const std::vector<rtc::SocketAddress>& addresses)
+ : alternate_server_addresses_(addresses),
+ iter_(alternate_server_addresses_.begin()) {
+ }
+
+ virtual bool ShouldRedirect(const rtc::SocketAddress&,
+ rtc::SocketAddress* out) {
+ if (!out || iter_ == alternate_server_addresses_.end()) {
+ return false;
+ }
+ *out = *iter_++;
+ return true;
+ }
+
+ private:
+ const std::vector<rtc::SocketAddress>& alternate_server_addresses_;
+ std::vector<rtc::SocketAddress>::const_iterator iter_;
+};
+
class TestTurnServer : public TurnAuthInterface {
public:
TestTurnServer(rtc::Thread* thread,
@@ -61,6 +83,10 @@
TurnServer* server() { return &server_; }
+ void set_redirect_hook(TurnRedirectInterface* redirect_hook) {
+ server_.set_redirect_hook(redirect_hook);
+ }
+
void AddInternalSocket(const rtc::SocketAddress& int_addr,
ProtocolType proto) {
rtc::Thread* thread = rtc::Thread::Current();
diff --git a/talk/p2p/base/turnport.cc b/talk/p2p/base/turnport.cc
index 3faacd1..6ab0e2b 100644
--- a/talk/p2p/base/turnport.cc
+++ b/talk/p2p/base/turnport.cc
@@ -78,6 +78,7 @@
private:
// Handles authentication challenge from the server.
void OnAuthChallenge(StunMessage* response, int code);
+ void OnTryAlternate(StunMessage* response, int code);
void OnUnknownAttribute(StunMessage* response);
TurnPort* port_;
@@ -253,6 +254,9 @@
return;
}
+ // Insert the current address to prevent redirection pingpong.
+ attempted_server_addresses_.insert(server_address_.address);
+
LOG_J(LS_INFO, this) << "Trying to connect to TURN server via "
<< ProtoToString(server_address_.proto) << " @ "
<< server_address_.address.ToSensitiveString();
@@ -458,6 +462,38 @@
}
}
+
+// Update current server address port with the alternate server address port.
+bool TurnPort::SetAlternateServer(const rtc::SocketAddress& address) {
+ // Check if we have seen this address before and reject if we did.
+ AttemptedServerSet::iterator iter = attempted_server_addresses_.find(address);
+ if (iter != attempted_server_addresses_.end()) {
+ LOG_J(LS_WARNING, this) << "Redirection to ["
+ << address.ToSensitiveString()
+ << "] ignored, allocation failed.";
+ return false;
+ }
+
+ // If protocol family of server address doesn't match with local, return.
+ if (!IsCompatibleAddress(address)) {
+ LOG(LS_WARNING) << "Server IP address family does not match with "
+ << "local host address family type";
+ return false;
+ }
+
+ LOG_J(LS_INFO, this) << "Redirecting from TURN server ["
+ << server_address_.address.ToSensitiveString()
+ << "] to TURN server ["
+ << address.ToSensitiveString()
+ << "]";
+ server_address_ = ProtocolAddress(address, server_address_.proto,
+ server_address_.secure);
+
+ // Insert the current address to prevent redirection pingpong.
+ attempted_server_addresses_.insert(server_address_.address);
+ return true;
+}
+
void TurnPort::ResolveTurnAddress(const rtc::SocketAddress& address) {
if (resolver_)
return;
@@ -805,6 +841,9 @@
case STUN_ERROR_UNAUTHORIZED: // Unauthrorized.
OnAuthChallenge(response, error_code->code());
break;
+ case STUN_ERROR_TRY_ALTERNATE:
+ OnTryAlternate(response, error_code->code());
+ break;
default:
LOG_J(LS_WARNING, port_) << "Allocate response error, code="
<< error_code->code();
@@ -849,6 +888,57 @@
port_->SendRequest(new TurnAllocateRequest(port_), 0);
}
+void TurnAllocateRequest::OnTryAlternate(StunMessage* response, int code) {
+ // TODO(guoweis): Currently, we only support UDP redirect
+ if (port_->server_address().proto != PROTO_UDP) {
+ LOG_J(LS_WARNING, port_) << "Receiving 300 Alternate Server on non-UDP "
+ << "allocating request from ["
+ << port_->server_address().address.ToSensitiveString()
+ << "], failed as currently not supported";
+ port_->OnAllocateError();
+ return;
+ }
+
+ // According to RFC 5389 section 11, there are use cases where
+ // authentication of response is not possible, we're not validating
+ // message integrity.
+
+ // Get the alternate server address attribute value.
+ const StunAddressAttribute* alternate_server_attr =
+ response->GetAddress(STUN_ATTR_ALTERNATE_SERVER);
+ if (!alternate_server_attr) {
+ LOG_J(LS_WARNING, port_) << "Missing STUN_ATTR_ALTERNATE_SERVER "
+ << "attribute in try alternate error response";
+ port_->OnAllocateError();
+ return;
+ }
+ if (!port_->SetAlternateServer(alternate_server_attr->GetAddress())) {
+ port_->OnAllocateError();
+ return;
+ }
+
+ // Check the attributes.
+ const StunByteStringAttribute* realm_attr =
+ response->GetByteString(STUN_ATTR_REALM);
+ if (realm_attr) {
+ LOG_J(LS_INFO, port_) << "Applying STUN_ATTR_REALM attribute in "
+ << "try alternate error response.";
+ port_->set_realm(realm_attr->GetString());
+ }
+
+ const StunByteStringAttribute* nonce_attr =
+ response->GetByteString(STUN_ATTR_NONCE);
+ if (nonce_attr) {
+ LOG_J(LS_INFO, port_) << "Applying STUN_ATTR_NONCE attribute in "
+ << "try alternate error response.";
+ port_->set_nonce(nonce_attr->GetString());
+ }
+
+ // Send another allocate request to alternate server,
+ // with the received realm and nonce values.
+ port_->SendRequest(new TurnAllocateRequest(port_), 0);
+}
+
TurnRefreshRequest::TurnRefreshRequest(TurnPort* port)
: StunRequest(new TurnMessage()),
port_(port) {
diff --git a/talk/p2p/base/turnport.h b/talk/p2p/base/turnport.h
index d73b11d..b9ec3b0 100644
--- a/talk/p2p/base/turnport.h
+++ b/talk/p2p/base/turnport.h
@@ -30,6 +30,7 @@
#include <stdio.h>
#include <list>
+#include <set>
#include <string>
#include "talk/p2p/base/port.h"
@@ -157,6 +158,7 @@
typedef std::list<TurnEntry*> EntryList;
typedef std::map<rtc::Socket::Option, int> SocketOptionsMap;
+ typedef std::set<rtc::SocketAddress> AttemptedServerSet;
virtual void OnMessage(rtc::Message* pmsg);
@@ -170,6 +172,7 @@
}
}
+ bool SetAlternateServer(const rtc::SocketAddress& address);
void ResolveTurnAddress(const rtc::SocketAddress& address);
void OnResolveResult(rtc::AsyncResolverInterface* resolver);
@@ -207,6 +210,7 @@
ProtocolAddress server_address_;
RelayCredentials credentials_;
+ AttemptedServerSet attempted_server_addresses_;
rtc::AsyncPacketSocket* socket_;
SocketOptionsMap socket_options_;
diff --git a/talk/p2p/base/turnport_unittest.cc b/talk/p2p/base/turnport_unittest.cc
index 44dc64f..14befa9 100644
--- a/talk/p2p/base/turnport_unittest.cc
+++ b/talk/p2p/base/turnport_unittest.cc
@@ -64,6 +64,8 @@
static const SocketAddress kTurnTcpIntAddr("99.99.99.4",
cricket::TURN_SERVER_PORT);
static const SocketAddress kTurnUdpExtAddr("99.99.99.5", 0);
+static const SocketAddress kTurnAlternateUdpIntAddr(
+ "99.99.99.6", cricket::TURN_SERVER_PORT);
static const SocketAddress kTurnUdpIPv6IntAddr(
"2400:4030:1:2c00:be30:abcd:efab:cdef", cricket::TURN_SERVER_PORT);
static const SocketAddress kTurnUdpIPv6ExtAddr(
@@ -445,6 +447,103 @@
ASSERT_EQ(0U, turn_port_->Candidates().size());
}
+// Test try-alternate-server feature.
+TEST_F(TurnPortTest, TestTurnAlternateServer) {
+ std::vector<rtc::SocketAddress> redirect_addresses;
+ redirect_addresses.push_back(kTurnAlternateUdpIntAddr);
+
+ cricket::TestTurnRedirector redirector(redirect_addresses);
+ turn_server_.AddInternalSocket(kTurnAlternateUdpIntAddr,
+ cricket::PROTO_UDP);
+ turn_server_.set_redirect_hook(&redirector);
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr);
+
+ // Retrieve the address before we run the state machine.
+ const SocketAddress old_addr = turn_port_->server_address().address;
+
+ turn_port_->PrepareAddress();
+ EXPECT_TRUE_WAIT(turn_ready_, kTimeout);
+ // Retrieve the address again, the turn port's address should be
+ // changed.
+ const SocketAddress new_addr = turn_port_->server_address().address;
+ EXPECT_NE(old_addr, new_addr);
+ ASSERT_EQ(1U, turn_port_->Candidates().size());
+ EXPECT_EQ(kTurnUdpExtAddr.ipaddr(),
+ turn_port_->Candidates()[0].address().ipaddr());
+ EXPECT_NE(0, turn_port_->Candidates()[0].address().port());
+}
+
+// Test that we fail when we redirect to an address different from
+// current IP family.
+TEST_F(TurnPortTest, TestTurnAlternateServerV4toV6) {
+ std::vector<rtc::SocketAddress> redirect_addresses;
+ redirect_addresses.push_back(kTurnUdpIPv6IntAddr);
+
+ cricket::TestTurnRedirector redirector(redirect_addresses);
+ turn_server_.AddInternalSocket(kTurnAlternateUdpIntAddr,
+ cricket::PROTO_UDP);
+ turn_server_.set_redirect_hook(&redirector);
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr);
+ turn_port_->PrepareAddress();
+ EXPECT_TRUE_WAIT(turn_error_, kTimeout);
+}
+
+// Test that we fail to handle alternate-server response over TCP protocol.
+TEST_F(TurnPortTest, TestTurnAlternateServerTcp) {
+ std::vector<rtc::SocketAddress> redirect_addresses;
+ redirect_addresses.push_back(kTurnAlternateUdpIntAddr);
+
+ cricket::TestTurnRedirector redirector(redirect_addresses);
+ turn_server_.set_redirect_hook(&redirector);
+ turn_server_.AddInternalSocket(kTurnTcpIntAddr, cricket::PROTO_TCP);
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnTcpProtoAddr);
+
+ turn_server_.AddInternalSocket(kTurnAlternateUdpIntAddr, cricket::PROTO_TCP);
+ turn_port_->PrepareAddress();
+ EXPECT_TRUE_WAIT(turn_error_, kTimeout);
+}
+
+// Test try-alternate-server catches the case of pingpong.
+TEST_F(TurnPortTest, TestTurnAlternateServerPingPong) {
+ std::vector<rtc::SocketAddress> redirect_addresses;
+ redirect_addresses.push_back(kTurnAlternateUdpIntAddr);
+ redirect_addresses.push_back(kTurnUdpIntAddr);
+
+ cricket::TestTurnRedirector redirector(redirect_addresses);
+
+ turn_server_.AddInternalSocket(kTurnAlternateUdpIntAddr,
+ cricket::PROTO_UDP);
+ turn_server_.set_redirect_hook(&redirector);
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr);
+
+ turn_port_->PrepareAddress();
+ EXPECT_TRUE_WAIT(turn_error_, kTimeout);
+ ASSERT_EQ(0U, turn_port_->Candidates().size());
+ rtc::SocketAddress address;
+ // Verify that we have exhausted all alternate servers instead of
+ // failure caused by other errors.
+ EXPECT_FALSE(redirector.ShouldRedirect(address, &address));
+}
+
+// Test try-alternate-server catch the case of repeated server.
+TEST_F(TurnPortTest, TestTurnAlternateServerDetectRepetition) {
+ std::vector<rtc::SocketAddress> redirect_addresses;
+ redirect_addresses.push_back(kTurnAlternateUdpIntAddr);
+ redirect_addresses.push_back(kTurnAlternateUdpIntAddr);
+
+ cricket::TestTurnRedirector redirector(redirect_addresses);
+
+ turn_server_.AddInternalSocket(kTurnAlternateUdpIntAddr,
+ cricket::PROTO_UDP);
+ turn_server_.set_redirect_hook(&redirector);
+ CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr);
+
+ turn_port_->PrepareAddress();
+ EXPECT_TRUE_WAIT(turn_error_, kTimeout);
+ ASSERT_EQ(0U, turn_port_->Candidates().size());
+}
+
+
// Run TurnConnectionTest with one-time-use nonce feature.
// Here server will send a 438 STALE_NONCE error message for
// every TURN transaction.
@@ -515,4 +614,3 @@
EXPECT_EQ(last_fd_count, GetFDCount());
}
#endif
-
diff --git a/talk/p2p/base/turnserver.cc b/talk/p2p/base/turnserver.cc
index abc065a..08c060d 100644
--- a/talk/p2p/base/turnserver.cc
+++ b/talk/p2p/base/turnserver.cc
@@ -208,6 +208,7 @@
: thread_(thread),
nonce_key_(rtc::CreateRandomString(kNonceKeySize)),
auth_hook_(NULL),
+ redirect_hook_(NULL),
enable_otu_nonce_(false) {
}
@@ -316,6 +317,15 @@
return;
}
+ if (redirect_hook_ != NULL && msg.type() == STUN_ALLOCATE_REQUEST) {
+ rtc::SocketAddress address;
+ if (redirect_hook_->ShouldRedirect(conn->src(), &address)) {
+ SendErrorResponseWithAlternateServer(
+ conn, &msg, address);
+ return;
+ }
+ }
+
// Look up the key that we'll use to validate the M-I. If we have an
// existing allocation, the key will already be cached.
Allocation* allocation = FindAllocation(conn);
@@ -334,7 +344,6 @@
}
if (!allocation && msg.type() == STUN_ALLOCATE_REQUEST) {
- // This is a new allocate request.
HandleAllocateRequest(conn, &msg, key);
} else if (allocation &&
(msg.type() != STUN_ALLOCATE_REQUEST ||
@@ -551,6 +560,17 @@
SendStun(conn, &resp);
}
+void TurnServer::SendErrorResponseWithAlternateServer(
+ Connection* conn, const StunMessage* msg,
+ const rtc::SocketAddress& addr) {
+ TurnMessage resp;
+ InitErrorResponse(msg, STUN_ERROR_TRY_ALTERNATE,
+ STUN_ERROR_REASON_TRY_ALTERNATE_SERVER, &resp);
+ VERIFY(resp.AddAttribute(new StunAddressAttribute(
+ STUN_ATTR_ALTERNATE_SERVER, addr)));
+ SendStun(conn, &resp);
+}
+
void TurnServer::SendStun(Connection* conn, StunMessage* msg) {
rtc::ByteBuffer buf;
// Add a SOFTWARE attribute if one is set.
diff --git a/talk/p2p/base/turnserver.h b/talk/p2p/base/turnserver.h
index 4798232..553d00c 100644
--- a/talk/p2p/base/turnserver.h
+++ b/talk/p2p/base/turnserver.h
@@ -63,6 +63,14 @@
std::string* key) = 0;
};
+// An interface enables Turn Server to control redirection behavior.
+class TurnRedirectInterface {
+ public:
+ virtual bool ShouldRedirect(const rtc::SocketAddress& address,
+ rtc::SocketAddress* out) = 0;
+ virtual ~TurnRedirectInterface() {}
+};
+
// The core TURN server class. Give it a socket to listen on via
// AddInternalServerSocket, and a factory to create external sockets via
// SetExternalSocketFactory, and it's ready to go.
@@ -83,6 +91,10 @@
// Sets the authentication callback; does not take ownership.
void set_auth_hook(TurnAuthInterface* auth_hook) { auth_hook_ = auth_hook; }
+ void set_redirect_hook(TurnRedirectInterface* redirect_hook) {
+ redirect_hook_ = redirect_hook;
+ }
+
void set_enable_otu_nonce(bool enable) { enable_otu_nonce_ = enable; }
// Starts listening for packets from internal clients.
@@ -155,6 +167,11 @@
const StunMessage* req,
int code,
const std::string& reason);
+
+ void SendErrorResponseWithAlternateServer(Connection* conn,
+ const StunMessage* req,
+ const rtc::SocketAddress& addr);
+
void SendStun(Connection* conn, StunMessage* msg);
void Send(Connection* conn, const rtc::ByteBuffer& buf);
@@ -171,14 +188,17 @@
std::string realm_;
std::string software_;
TurnAuthInterface* auth_hook_;
+ TurnRedirectInterface* redirect_hook_;
// otu - one-time-use. Server will respond with 438 if it's
// sees the same nonce in next transaction.
bool enable_otu_nonce_;
+
InternalSocketMap server_sockets_;
ServerSocketMap server_listen_sockets_;
rtc::scoped_ptr<rtc::PacketSocketFactory>
external_socket_factory_;
rtc::SocketAddress external_addr_;
+
AllocationMap allocations_;
};