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
This commit is contained in:
guoweis@webrtc.org
2014-08-26 21:37:49 +00:00
parent dc926a000e
commit 7087857afd
8 changed files with 264 additions and 4 deletions

View File

@@ -41,6 +41,7 @@ using rtc::ByteBuffer;
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 @@ StunAttributeValueType StunMessage::GetAttributeValueType(int type) const {
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;

View File

@@ -63,7 +63,7 @@ enum StunAttributeType {
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 @@ enum StunErrorCode {
};
// 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[];

View File

@@ -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 @@ namespace cricket {
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 @@ class TestTurnServer : public TurnAuthInterface {
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();

View File

@@ -78,6 +78,7 @@ class TurnAllocateRequest : public StunRequest {
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 @@ void TurnPort::PrepareAddress() {
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 @@ void TurnPort::OnReadyToSend(rtc::AsyncPacketSocket* socket) {
}
}
// 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 @@ void TurnAllocateRequest::OnErrorResponse(StunMessage* response) {
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 @@ void TurnAllocateRequest::OnAuthChallenge(StunMessage* response, int code) {
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) {

View File

@@ -30,6 +30,7 @@
#include <stdio.h>
#include <list>
#include <set>
#include <string>
#include "talk/p2p/base/port.h"
@@ -157,6 +158,7 @@ class TurnPort : public Port {
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 @@ class TurnPort : public Port {
}
}
bool SetAlternateServer(const rtc::SocketAddress& address);
void ResolveTurnAddress(const rtc::SocketAddress& address);
void OnResolveResult(rtc::AsyncResolverInterface* resolver);
@@ -207,6 +210,7 @@ class TurnPort : public Port {
ProtocolAddress server_address_;
RelayCredentials credentials_;
AttemptedServerSet attempted_server_addresses_;
rtc::AsyncPacketSocket* socket_;
SocketOptionsMap socket_options_;

View File

@@ -64,6 +64,8 @@ static const SocketAddress kTurnUdpIntAddr("99.99.99.3",
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 @@ TEST_F(TurnPortTest, TestTurnTlsTcpConnectionFails) {
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 @@ TEST_F(TurnPortTest, TestResolverShutdown) {
EXPECT_EQ(last_fd_count, GetFDCount());
}
#endif

View File

@@ -208,6 +208,7 @@ TurnServer::TurnServer(rtc::Thread* thread)
: thread_(thread),
nonce_key_(rtc::CreateRandomString(kNonceKeySize)),
auth_hook_(NULL),
redirect_hook_(NULL),
enable_otu_nonce_(false) {
}
@@ -316,6 +317,15 @@ void TurnServer::HandleStunMessage(Connection* conn, const char* data,
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 @@ void TurnServer::HandleStunMessage(Connection* conn, const char* data,
}
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 @@ void TurnServer::SendErrorResponseWithRealmAndNonce(
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.

View File

@@ -63,6 +63,14 @@ class TurnAuthInterface {
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 @@ class TurnServer : public sigslot::has_slots<> {
// 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 @@ class TurnServer : public sigslot::has_slots<> {
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 @@ class TurnServer : public sigslot::has_slots<> {
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_;
};