Encoding and Decoding of TCP candidates as defined in RFC 6544.

R=juberti@chromium.org, jiayl@webrtc.org, juberti@webrtc.org
BUG=2204

Review URL: https://webrtc-codereview.appspot.com/21479004

git-svn-id: http://webrtc.googlecode.com/svn/trunk@6857 4adac7df-926f-26a2-2b94-8c16560cd09d
This commit is contained in:
mallinath@webrtc.org 2014-08-08 22:29:20 +00:00
parent 8c01e59424
commit 2d60c5e8bc
12 changed files with 147 additions and 46 deletions

View File

@ -168,6 +168,7 @@ static const char kCandidateSrflx[] = "srflx";
// TODO: How to map the prflx with circket candidate type
// static const char kCandidatePrflx[] = "prflx";
static const char kCandidateRelay[] = "relay";
static const char kTcpCandidateType[] = "tcptype";
static const char kSdpDelimiterEqual = '=';
static const char kSdpDelimiterSpace = ' ';
@ -1042,6 +1043,25 @@ bool ParseCandidate(const std::string& message, Candidate* candidate,
++current_position;
}
// If this is a TCP candidate, it has additional extension as defined in
// RFC 6544.
std::string tcptype;
if (fields.size() >= (current_position + 2) &&
fields[current_position] == kTcpCandidateType) {
tcptype = fields[++current_position];
++current_position;
if (tcptype != cricket::TCPTYPE_ACTIVE_STR &&
tcptype != cricket::TCPTYPE_PASSIVE_STR &&
tcptype != cricket::TCPTYPE_SIMOPEN_STR) {
return ParseFailed(first_line, "Invalid TCP candidate type.", error);
}
if (protocol != cricket::PROTO_TCP) {
return ParseFailed(first_line, "Invalid non-TCP candidate", error);
}
}
// Extension
// Empty string as the candidate username and password.
// Will be updated later with the ice-ufrag and ice-pwd.
@ -1074,6 +1094,7 @@ bool ParseCandidate(const std::string& message, Candidate* candidate,
address, priority, username, password, candidate_type, network_name,
generation, foundation);
candidate->set_related_address(related_address);
candidate->set_tcptype(tcptype);
return true;
}
@ -1694,11 +1715,14 @@ void BuildCandidate(const std::vector<Candidate>& candidates,
InitAttrLine(kAttributeCandidate, &os);
os << kSdpDelimiterColon
<< it->foundation() << " " << it->component() << " "
<< it->protocol() << " " << it->priority() << " "
<< it->foundation() << " "
<< it->component() << " "
<< it->protocol() << " "
<< it->priority() << " "
<< it->address().ipaddr().ToString() << " "
<< it->address().PortAsString() << " "
<< kAttributeCandidateTyp << " " << type << " ";
<< kAttributeCandidateTyp << " "
<< type << " ";
// Related address
if (!it->related_address().IsNil()) {
@ -1708,6 +1732,17 @@ void BuildCandidate(const std::vector<Candidate>& candidates,
<< it->related_address().PortAsString() << " ";
}
if (it->protocol() == cricket::TCP_PROTOCOL_NAME) {
// In case of WebRTC, candidate must be always "active" only. That means
// it should have port number either 0 or 9.
ASSERT(it->address().port() == 0 ||
it->address().port() == cricket::DISCARD_PORT);
ASSERT(it->tcptype() == cricket::TCPTYPE_ACTIVE_STR);
// TODO(mallinath) : Uncomment below line once WebRTCSdp capable of
// parsing RFC 6544.
// os << kTcpCandidateType << " " << it->tcptype() << " ";
}
// Extensions
os << kAttributeCandidateGeneration << " " << it->generation();

View File

@ -354,6 +354,19 @@ static const char kRawCandidate[] =
static const char kSdpOneCandidate[] =
"a=candidate:a0+B/1 1 udp 2130706432 192.168.1.5 1234 typ host "
"generation 2\r\n";
static const char kSdpTcpActiveCandidate[] =
"candidate:a0+B/1 1 tcp 2130706432 192.168.1.5 9 typ host "
"tcptype active generation 2";
static const char kSdpTcpPassiveCandidate[] =
"candidate:a0+B/1 1 tcp 2130706432 192.168.1.5 9 typ host "
"tcptype passive generation 2";
static const char kSdpTcpSOCandidate[] =
"candidate:a0+B/1 1 tcp 2130706432 192.168.1.5 9 typ host "
"tcptype so generation 2";
static const char kSdpTcpInvalidCandidate[] =
"candidate:a0+B/1 1 tcp 2130706432 192.168.1.5 9 typ host "
"tcptype invalid generation 2";
// One candidate reference string.
static const char kSdpOneCandidateOldFormat[] =
@ -1611,6 +1624,22 @@ TEST_F(WebRtcSdpTest, SerializeCandidates) {
EXPECT_EQ(std::string(kRawCandidate), message);
}
// TODO(mallinath) : Enable this test once WebRTCSdp capable of parsing
// RFC 6544.
TEST_F(WebRtcSdpTest, DISABLED_SerializeTcpCandidates) {
Candidate candidate(
"", ICE_CANDIDATE_COMPONENT_RTP, "tcp",
rtc::SocketAddress("192.168.1.5", 9), kCandidatePriority,
"", "", LOCAL_PORT_TYPE,
"", kCandidateGeneration, kCandidateFoundation1);
candidate.set_tcptype(cricket::TCPTYPE_ACTIVE_STR);
rtc::scoped_ptr<IceCandidateInterface> jcandidate(
new JsepIceCandidate(std::string("audio_content_name"), 0, candidate));
std::string message = webrtc::SdpSerializeCandidate(*jcandidate);
EXPECT_EQ(std::string(kSdpTcpActiveCandidate), message);
}
TEST_F(WebRtcSdpTest, DeserializeSessionDescription) {
JsepSessionDescription jdesc(kDummyString);
// Deserialize
@ -1897,6 +1926,25 @@ TEST_F(WebRtcSdpTest, DeserializeCandidate) {
EXPECT_EQ(kDummyMid, jcandidate.sdp_mid());
EXPECT_EQ(kDummyIndex, jcandidate.sdp_mline_index());
EXPECT_TRUE(jcandidate.candidate().IsEquivalent(jcandidate_->candidate()));
sdp = kSdpTcpActiveCandidate;
EXPECT_TRUE(SdpDeserializeCandidate(sdp, &jcandidate));
// Make a cricket::Candidate equivalent to kSdpTcpCandidate string.
Candidate candidate(
"", ICE_CANDIDATE_COMPONENT_RTP, "tcp",
rtc::SocketAddress("192.168.1.5", 9), kCandidatePriority,
"", "", LOCAL_PORT_TYPE,
"", kCandidateGeneration, kCandidateFoundation1);
rtc::scoped_ptr<IceCandidateInterface> jcandidate_template(
new JsepIceCandidate(std::string("audio_content_name"), 0, candidate));
EXPECT_TRUE(jcandidate.candidate().IsEquivalent(
jcandidate_template->candidate()));
sdp = kSdpTcpPassiveCandidate;
EXPECT_TRUE(SdpDeserializeCandidate(sdp, &jcandidate));
sdp = kSdpTcpSOCandidate;
EXPECT_TRUE(SdpDeserializeCandidate(sdp, &jcandidate));
sdp = kSdpTcpInvalidCandidate;
EXPECT_FALSE(SdpDeserializeCandidate(sdp, &jcandidate));
}
// This test verifies the deserialization of candidate-attribute

View File

@ -139,6 +139,10 @@ class Candidate {
const rtc::SocketAddress & related_address) {
related_address_ = related_address;
}
const std::string& tcptype() const { return tcptype_; }
void set_tcptype(const std::string& tcptype){
tcptype_ = tcptype;
}
// Determines whether this candidate is equivalent to the given one.
bool IsEquivalent(const Candidate& c) const {
@ -217,6 +221,7 @@ class Candidate {
uint32 generation_;
std::string foundation_;
rtc::SocketAddress related_address_;
std::string tcptype_;
};
} // namespace cricket

View File

@ -147,6 +147,12 @@ bool StringToProto(const char* value, ProtocolType* proto) {
return false;
}
// RFC 6544, TCP candidate encoding rules.
const int DISCARD_PORT = 9;
const char TCPTYPE_ACTIVE_STR[] = "active";
const char TCPTYPE_PASSIVE_STR[] = "passive";
const char TCPTYPE_SIMOPEN_STR[] = "so";
// Foundation: An arbitrary string that is the same for two candidates
// that have the same type, base IP address, protocol (UDP, TCP,
// etc.), and STUN or TURN server. If any of these are different,
@ -250,26 +256,21 @@ void Port::AddAddress(const rtc::SocketAddress& address,
const rtc::SocketAddress& base_address,
const rtc::SocketAddress& related_address,
const std::string& protocol,
const std::string& type,
uint32 type_preference,
bool final) {
AddAddress(address, base_address, related_address, protocol,
type, type_preference, 0, final);
}
void Port::AddAddress(const rtc::SocketAddress& address,
const rtc::SocketAddress& base_address,
const rtc::SocketAddress& related_address,
const std::string& protocol,
const std::string& tcptype,
const std::string& type,
uint32 type_preference,
uint32 relay_preference,
bool final) {
if (protocol == TCP_PROTOCOL_NAME && type == LOCAL_PORT_TYPE) {
ASSERT(!tcptype.empty());
}
Candidate c;
c.set_id(rtc::CreateRandomString(8));
c.set_component(component_);
c.set_type(type);
c.set_protocol(protocol);
c.set_tcptype(tcptype);
c.set_address(address);
c.set_priority(c.GetPriority(type_preference, network_->preference(),
relay_preference));

View File

@ -61,6 +61,12 @@ extern const char UDP_PROTOCOL_NAME[];
extern const char TCP_PROTOCOL_NAME[];
extern const char SSLTCP_PROTOCOL_NAME[];
// RFC 6544, TCP candidate encoding rules.
extern const int DISCARD_PORT;
extern const char TCPTYPE_ACTIVE_STR[];
extern const char TCPTYPE_PASSIVE_STR[];
extern const char TCPTYPE_SIMOPEN_STR[];
// The length of time we wait before timing out readability on a connection.
const uint32 CONNECTION_READ_TIMEOUT = 30 * 1000; // 30 seconds
@ -308,18 +314,13 @@ class Port : public PortInterface, public rtc::MessageHandler,
};
void set_type(const std::string& type) { type_ = type; }
// Fills in the local address of the port.
void AddAddress(const rtc::SocketAddress& address,
const rtc::SocketAddress& base_address,
const rtc::SocketAddress& related_address,
const std::string& protocol, const std::string& type,
uint32 type_preference, bool final);
void AddAddress(const rtc::SocketAddress& address,
const rtc::SocketAddress& base_address,
const rtc::SocketAddress& related_address,
const std::string& protocol, const std::string& type,
uint32 type_preference, uint32 relay_preference, bool final);
const std::string& protocol, const std::string& tcptype,
const std::string& type, uint32 type_preference,
uint32 relay_preference, bool final);
// Adds the given connection to the list. (Deleting removes them.)
void AddConnection(Connection* conn);

View File

@ -146,22 +146,22 @@ class TestPort : public Port {
virtual void PrepareAddress() {
rtc::SocketAddress addr(ip(), min_port());
AddAddress(addr, addr, rtc::SocketAddress(), "udp", Type(),
ICE_TYPE_PREFERENCE_HOST, true);
AddAddress(addr, addr, rtc::SocketAddress(), "udp", "", Type(),
ICE_TYPE_PREFERENCE_HOST, 0, true);
}
// Exposed for testing candidate building.
void AddCandidateAddress(const rtc::SocketAddress& addr) {
AddAddress(addr, addr, rtc::SocketAddress(), "udp", Type(),
type_preference_, false);
AddAddress(addr, addr, rtc::SocketAddress(), "udp", "", Type(),
type_preference_, 0, false);
}
void AddCandidateAddress(const rtc::SocketAddress& addr,
const rtc::SocketAddress& base_address,
const std::string& type,
int type_preference,
bool final) {
AddAddress(addr, base_address, rtc::SocketAddress(), "udp", type,
type_preference, final);
AddAddress(addr, base_address, rtc::SocketAddress(), "udp", "", type,
type_preference, 0, final);
}
virtual Connection* CreateConnection(const Candidate& remote_candidate,

View File

@ -244,7 +244,8 @@ void RelayPort::SetReady() {
// This is due to as mapped address stun attribute is used for allocated
// address.
AddAddress(iter->address, iter->address, rtc::SocketAddress(),
proto_name, RELAY_PORT_TYPE, ICE_TYPE_PREFERENCE_RELAY, false);
proto_name, "", RELAY_PORT_TYPE,
ICE_TYPE_PREFERENCE_RELAY, 0, false);
}
ready_ = true;
SignalPortComplete(this);

View File

@ -289,8 +289,8 @@ int UDPPort::GetError() {
void UDPPort::OnLocalAddressReady(rtc::AsyncPacketSocket* socket,
const rtc::SocketAddress& address) {
AddAddress(address, address, rtc::SocketAddress(),
UDP_PROTOCOL_NAME, LOCAL_PORT_TYPE,
ICE_TYPE_PREFERENCE_HOST, false);
UDP_PROTOCOL_NAME, "", LOCAL_PORT_TYPE,
ICE_TYPE_PREFERENCE_HOST, 0, false);
MaybePrepareStunCandidate();
}
@ -394,8 +394,8 @@ void UDPPort::OnStunBindingRequestSucceeded(
// address then discarding the stun address.
// For STUN related address is local socket address.
AddAddress(stun_reflected_addr, socket_->GetLocalAddress(),
socket_->GetLocalAddress(), UDP_PROTOCOL_NAME,
STUN_PORT_TYPE, ICE_TYPE_PREFERENCE_SRFLX, false);
socket_->GetLocalAddress(), UDP_PROTOCOL_NAME, "",
STUN_PORT_TYPE, ICE_TYPE_PREFERENCE_SRFLX, 0, false);
}
MaybeSetPortCompleteOrError();
}

View File

@ -81,6 +81,13 @@ Connection* TCPPort::CreateConnection(const Candidate& address,
return NULL;
}
if (address.tcptype() == TCPTYPE_ACTIVE_STR ||
(address.tcptype().empty() && address.address().port() == 0)) {
// It's active only candidate, we should not try to create connections
// for these candidates.
return NULL;
}
// We can't accept TCP connections incoming on other ports
if (origin == ORIGIN_OTHER_PORT)
return NULL;
@ -115,23 +122,23 @@ void TCPPort::PrepareAddress() {
if (socket_) {
// If socket isn't bound yet the address will be added in
// OnAddressReady(). Socket may be in the CLOSED state if Listen()
// failed, we still want ot add the socket address.
// failed, we still want to add the socket address.
LOG(LS_VERBOSE) << "Preparing TCP address, current state: "
<< socket_->GetState();
if (socket_->GetState() == rtc::AsyncPacketSocket::STATE_BOUND ||
socket_->GetState() == rtc::AsyncPacketSocket::STATE_CLOSED)
AddAddress(socket_->GetLocalAddress(), socket_->GetLocalAddress(),
rtc::SocketAddress(),
TCP_PROTOCOL_NAME, LOCAL_PORT_TYPE,
ICE_TYPE_PREFERENCE_HOST_TCP, true);
TCP_PROTOCOL_NAME, TCPTYPE_PASSIVE_STR, LOCAL_PORT_TYPE,
ICE_TYPE_PREFERENCE_HOST_TCP, 0, true);
} else {
LOG_J(LS_INFO, this) << "Not listening due to firewall restrictions.";
// Note: We still add the address, since otherwise the remote side won't
// recognize our incoming TCP connections.
AddAddress(rtc::SocketAddress(ip(), 0),
rtc::SocketAddress(ip(), 0), rtc::SocketAddress(),
TCP_PROTOCOL_NAME, LOCAL_PORT_TYPE, ICE_TYPE_PREFERENCE_HOST_TCP,
true);
TCP_PROTOCOL_NAME, TCPTYPE_ACTIVE_STR, LOCAL_PORT_TYPE,
ICE_TYPE_PREFERENCE_HOST_TCP, 0, true);
}
}
@ -223,9 +230,9 @@ void TCPPort::OnReadyToSend(rtc::AsyncPacketSocket* socket) {
void TCPPort::OnAddressReady(rtc::AsyncPacketSocket* socket,
const rtc::SocketAddress& address) {
AddAddress(address, address, rtc::SocketAddress(), "tcp",
LOCAL_PORT_TYPE, ICE_TYPE_PREFERENCE_HOST_TCP,
true);
AddAddress(address, address, rtc::SocketAddress(),
TCP_PROTOCOL_NAME, TCPTYPE_PASSIVE_STR, LOCAL_PORT_TYPE,
ICE_TYPE_PREFERENCE_HOST_TCP, 0, true);
}
TCPConnection::TCPConnection(TCPPort* port, const Candidate& candidate,

View File

@ -34,6 +34,7 @@
#include "talk/p2p/base/constants.h"
#include "talk/p2p/base/sessionmanager.h"
#include "talk/p2p/base/parsing.h"
#include "talk/p2p/base/port.h"
#include "talk/p2p/base/transportchannelimpl.h"
#include "talk/xmllite/xmlelement.h"
#include "talk/xmpp/constants.h"
@ -438,11 +439,12 @@ bool Transport::VerifyCandidate(const Candidate& cand, std::string* error) {
// Disallow all ports below 1024, except for 80 and 443 on public addresses.
int port = cand.address().port();
if (port == 0) {
if (cand.protocol() == TCP_PROTOCOL_NAME &&
(cand.tcptype() == TCPTYPE_ACTIVE_STR || port == 0)) {
// Expected for active-only candidates per
// http://tools.ietf.org/html/rfc6544#section-4.5 so no error.
*error = "";
return false;
// Libjingle clients emit port 0, in "active" mode.
return true;
}
if (port < 1024) {
if ((port != 80) && (port != 443)) {

View File

@ -526,6 +526,7 @@ void TurnPort::OnAllocateSuccess(const rtc::SocketAddress& address,
address, // Base address.
stun_address, // Related address.
UDP_PROTOCOL_NAME,
"", // TCP canddiate type, empty for turn candidates.
RELAY_PORT_TYPE,
GetRelayPreference(server_address_.proto, server_address_.secure),
server_priority_,

View File

@ -73,8 +73,8 @@ class FakeStunPort : public StunPort {
// Just set external address and signal that we are done.
virtual void PrepareAddress() {
AddAddress(kExternalAddr, kExternalAddr, rtc::SocketAddress(), "udp",
STUN_PORT_TYPE, ICE_TYPE_PREFERENCE_SRFLX, true);
AddAddress(kExternalAddr, kExternalAddr, rtc::SocketAddress(), "udp", "",
STUN_PORT_TYPE, ICE_TYPE_PREFERENCE_SRFLX, 0, true);
SignalPortComplete(this);
}
};