From 2d60c5e8bcac85e9388e093bae91ecc829eabcea Mon Sep 17 00:00:00 2001 From: "mallinath@webrtc.org" Date: Fri, 8 Aug 2014 22:29:20 +0000 Subject: [PATCH] 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 --- talk/app/webrtc/webrtcsdp.cc | 41 ++++++++++++++-- talk/app/webrtc/webrtcsdp_unittest.cc | 48 +++++++++++++++++++ talk/p2p/base/candidate.h | 5 ++ talk/p2p/base/port.cc | 23 ++++----- talk/p2p/base/port.h | 17 +++---- talk/p2p/base/port_unittest.cc | 12 ++--- talk/p2p/base/relayport.cc | 3 +- talk/p2p/base/stunport.cc | 8 ++-- talk/p2p/base/tcpport.cc | 23 +++++---- talk/p2p/base/transport.cc | 8 ++-- talk/p2p/base/turnport.cc | 1 + .../client/connectivitychecker_unittest.cc | 4 +- 12 files changed, 147 insertions(+), 46 deletions(-) diff --git a/talk/app/webrtc/webrtcsdp.cc b/talk/app/webrtc/webrtcsdp.cc index 4f774a702..64f8e5e2c 100644 --- a/talk/app/webrtc/webrtcsdp.cc +++ b/talk/app/webrtc/webrtcsdp.cc @@ -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& 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& 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(); diff --git a/talk/app/webrtc/webrtcsdp_unittest.cc b/talk/app/webrtc/webrtcsdp_unittest.cc index 71ebe0d85..f0546f80a 100644 --- a/talk/app/webrtc/webrtcsdp_unittest.cc +++ b/talk/app/webrtc/webrtcsdp_unittest.cc @@ -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 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 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 diff --git a/talk/p2p/base/candidate.h b/talk/p2p/base/candidate.h index 56174bdcc..9c1198d0c 100644 --- a/talk/p2p/base/candidate.h +++ b/talk/p2p/base/candidate.h @@ -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 diff --git a/talk/p2p/base/port.cc b/talk/p2p/base/port.cc index 0d3a5cdd9..42f7a9581 100644 --- a/talk/p2p/base/port.cc +++ b/talk/p2p/base/port.cc @@ -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)); diff --git a/talk/p2p/base/port.h b/talk/p2p/base/port.h index 0071a03b3..a417c0a02 100644 --- a/talk/p2p/base/port.h +++ b/talk/p2p/base/port.h @@ -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); diff --git a/talk/p2p/base/port_unittest.cc b/talk/p2p/base/port_unittest.cc index e4f37a9e0..9b9568cae 100644 --- a/talk/p2p/base/port_unittest.cc +++ b/talk/p2p/base/port_unittest.cc @@ -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, diff --git a/talk/p2p/base/relayport.cc b/talk/p2p/base/relayport.cc index 78bf65a81..9de08971c 100644 --- a/talk/p2p/base/relayport.cc +++ b/talk/p2p/base/relayport.cc @@ -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); diff --git a/talk/p2p/base/stunport.cc b/talk/p2p/base/stunport.cc index 57c7850c8..2ed0fa759 100644 --- a/talk/p2p/base/stunport.cc +++ b/talk/p2p/base/stunport.cc @@ -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(); } diff --git a/talk/p2p/base/tcpport.cc b/talk/p2p/base/tcpport.cc index f6d9ae6ec..74d566d0f 100644 --- a/talk/p2p/base/tcpport.cc +++ b/talk/p2p/base/tcpport.cc @@ -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, diff --git a/talk/p2p/base/transport.cc b/talk/p2p/base/transport.cc index 825142a13..6ca3628c3 100644 --- a/talk/p2p/base/transport.cc +++ b/talk/p2p/base/transport.cc @@ -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)) { diff --git a/talk/p2p/base/turnport.cc b/talk/p2p/base/turnport.cc index 7255a2b58..3fa68297b 100644 --- a/talk/p2p/base/turnport.cc +++ b/talk/p2p/base/turnport.cc @@ -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_, diff --git a/talk/p2p/client/connectivitychecker_unittest.cc b/talk/p2p/client/connectivitychecker_unittest.cc index d1a652545..51bdfef2d 100644 --- a/talk/p2p/client/connectivitychecker_unittest.cc +++ b/talk/p2p/client/connectivitychecker_unittest.cc @@ -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); } };