| /* |
| * libjingle |
| * Copyright 2004 Google Inc. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright notice, |
| * this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright notice, |
| * this list of conditions and the following disclaimer in the documentation |
| * and/or other materials provided with the distribution. |
| * 3. The name of the author may not be used to endorse or promote products |
| * derived from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED |
| * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF |
| * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO |
| * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; |
| * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, |
| * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR |
| * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
| * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "talk/base/crc32.h" |
| #include "talk/base/gunit.h" |
| #include "talk/base/helpers.h" |
| #include "talk/base/logging.h" |
| #include "talk/base/natserver.h" |
| #include "talk/base/natsocketfactory.h" |
| #include "talk/base/physicalsocketserver.h" |
| #include "talk/base/scoped_ptr.h" |
| #include "talk/base/socketaddress.h" |
| #include "talk/base/stringutils.h" |
| #include "talk/base/thread.h" |
| #include "talk/base/virtualsocketserver.h" |
| #include "talk/p2p/base/basicpacketsocketfactory.h" |
| #include "talk/p2p/base/portproxy.h" |
| #include "talk/p2p/base/relayport.h" |
| #include "talk/p2p/base/stunport.h" |
| #include "talk/p2p/base/tcpport.h" |
| #include "talk/p2p/base/testrelayserver.h" |
| #include "talk/p2p/base/teststunserver.h" |
| #include "talk/p2p/base/testturnserver.h" |
| #include "talk/p2p/base/transport.h" |
| #include "talk/p2p/base/turnport.h" |
| |
| using talk_base::AsyncPacketSocket; |
| using talk_base::ByteBuffer; |
| using talk_base::NATType; |
| using talk_base::NAT_OPEN_CONE; |
| using talk_base::NAT_ADDR_RESTRICTED; |
| using talk_base::NAT_PORT_RESTRICTED; |
| using talk_base::NAT_SYMMETRIC; |
| using talk_base::PacketSocketFactory; |
| using talk_base::scoped_ptr; |
| using talk_base::Socket; |
| using talk_base::SocketAddress; |
| using namespace cricket; |
| |
| static const int kTimeout = 1000; |
| static const SocketAddress kLocalAddr1("192.168.1.2", 0); |
| static const SocketAddress kLocalAddr2("192.168.1.3", 0); |
| static const SocketAddress kNatAddr1("77.77.77.77", talk_base::NAT_SERVER_PORT); |
| static const SocketAddress kNatAddr2("88.88.88.88", talk_base::NAT_SERVER_PORT); |
| static const SocketAddress kStunAddr("99.99.99.1", STUN_SERVER_PORT); |
| static const SocketAddress kRelayUdpIntAddr("99.99.99.2", 5000); |
| static const SocketAddress kRelayUdpExtAddr("99.99.99.3", 5001); |
| static const SocketAddress kRelayTcpIntAddr("99.99.99.2", 5002); |
| static const SocketAddress kRelayTcpExtAddr("99.99.99.3", 5003); |
| static const SocketAddress kRelaySslTcpIntAddr("99.99.99.2", 5004); |
| static const SocketAddress kRelaySslTcpExtAddr("99.99.99.3", 5005); |
| static const SocketAddress kTurnUdpIntAddr("99.99.99.4", STUN_SERVER_PORT); |
| static const SocketAddress kTurnUdpExtAddr("99.99.99.5", 0); |
| static const RelayCredentials kRelayCredentials("test", "test"); |
| |
| // TODO: Update these when RFC5245 is completely supported. |
| // Magic value of 30 is from RFC3484, for IPv4 addresses. |
| static const uint32 kDefaultPrflxPriority = ICE_TYPE_PREFERENCE_PRFLX << 24 | |
| 30 << 8 | (256 - ICE_CANDIDATE_COMPONENT_DEFAULT); |
| static const int STUN_ERROR_BAD_REQUEST_AS_GICE = |
| STUN_ERROR_BAD_REQUEST / 256 * 100 + STUN_ERROR_BAD_REQUEST % 256; |
| static const int STUN_ERROR_UNAUTHORIZED_AS_GICE = |
| STUN_ERROR_UNAUTHORIZED / 256 * 100 + STUN_ERROR_UNAUTHORIZED % 256; |
| static const int STUN_ERROR_SERVER_ERROR_AS_GICE = |
| STUN_ERROR_SERVER_ERROR / 256 * 100 + STUN_ERROR_SERVER_ERROR % 256; |
| |
| static const int kTiebreaker1 = 11111; |
| static const int kTiebreaker2 = 22222; |
| |
| static Candidate GetCandidate(Port* port) { |
| assert(port->Candidates().size() == 1); |
| return port->Candidates()[0]; |
| } |
| |
| static SocketAddress GetAddress(Port* port) { |
| return GetCandidate(port).address(); |
| } |
| |
| static IceMessage* CopyStunMessage(const IceMessage* src) { |
| IceMessage* dst = new IceMessage(); |
| ByteBuffer buf; |
| src->Write(&buf); |
| dst->Read(&buf); |
| return dst; |
| } |
| |
| static bool WriteStunMessage(const StunMessage* msg, ByteBuffer* buf) { |
| buf->Resize(0); // clear out any existing buffer contents |
| return msg->Write(buf); |
| } |
| |
| // Stub port class for testing STUN generation and processing. |
| class TestPort : public Port { |
| public: |
| TestPort(talk_base::Thread* thread, const std::string& type, |
| talk_base::PacketSocketFactory* factory, talk_base::Network* network, |
| const talk_base::IPAddress& ip, int min_port, int max_port, |
| const std::string& username_fragment, const std::string& password) |
| : Port(thread, type, factory, network, ip, |
| min_port, max_port, username_fragment, password) { |
| } |
| ~TestPort() {} |
| |
| // Expose GetStunMessage so that we can test it. |
| using cricket::Port::GetStunMessage; |
| |
| // The last StunMessage that was sent on this Port. |
| // TODO: Make these const; requires changes to SendXXXXResponse. |
| ByteBuffer* last_stun_buf() { return last_stun_buf_.get(); } |
| IceMessage* last_stun_msg() { return last_stun_msg_.get(); } |
| int last_stun_error_code() { |
| int code = 0; |
| if (last_stun_msg_) { |
| const StunErrorCodeAttribute* error_attr = last_stun_msg_->GetErrorCode(); |
| if (error_attr) { |
| code = error_attr->code(); |
| } |
| } |
| return code; |
| } |
| |
| virtual void PrepareAddress() { |
| talk_base::SocketAddress addr(ip(), min_port()); |
| AddAddress(addr, addr, "udp", Type(), ICE_TYPE_PREFERENCE_HOST, true); |
| } |
| |
| // Exposed for testing candidate building. |
| void AddCandidateAddress(const talk_base::SocketAddress& addr) { |
| AddAddress(addr, addr, "udp", Type(), type_preference_, false); |
| } |
| void AddCandidateAddress(const talk_base::SocketAddress& addr, |
| const talk_base::SocketAddress& base_address, |
| const std::string& type, |
| int type_preference, |
| bool final) { |
| AddAddress(addr, base_address, "udp", type, |
| type_preference, final); |
| } |
| |
| virtual Connection* CreateConnection(const Candidate& remote_candidate, |
| CandidateOrigin origin) { |
| Connection* conn = new ProxyConnection(this, 0, remote_candidate); |
| AddConnection(conn); |
| // Set use-candidate attribute flag as this will add USE-CANDIDATE attribute |
| // in STUN binding requests. |
| conn->set_use_candidate_attr(true); |
| return conn; |
| } |
| virtual int SendTo( |
| const void* data, size_t size, const talk_base::SocketAddress& addr, |
| talk_base::DiffServCodePoint dscp, bool payload) { |
| if (!payload) { |
| IceMessage* msg = new IceMessage; |
| ByteBuffer* buf = new ByteBuffer(static_cast<const char*>(data), size); |
| ByteBuffer::ReadPosition pos(buf->GetReadPosition()); |
| if (!msg->Read(buf)) { |
| delete msg; |
| delete buf; |
| return -1; |
| } |
| buf->SetReadPosition(pos); |
| last_stun_buf_.reset(buf); |
| last_stun_msg_.reset(msg); |
| } |
| return static_cast<int>(size); |
| } |
| virtual int SetOption(talk_base::Socket::Option opt, int value) { |
| return 0; |
| } |
| virtual int GetOption(talk_base::Socket::Option opt, int* value) { |
| return -1; |
| } |
| virtual int GetError() { |
| return 0; |
| } |
| void Reset() { |
| last_stun_buf_.reset(); |
| last_stun_msg_.reset(); |
| } |
| void set_type_preference(int type_preference) { |
| type_preference_ = type_preference; |
| } |
| |
| private: |
| talk_base::scoped_ptr<ByteBuffer> last_stun_buf_; |
| talk_base::scoped_ptr<IceMessage> last_stun_msg_; |
| int type_preference_; |
| }; |
| |
| class TestChannel : public sigslot::has_slots<> { |
| public: |
| TestChannel(Port* p1, Port* p2) |
| : ice_mode_(ICEMODE_FULL), src_(p1), dst_(p2), complete_count_(0), |
| conn_(NULL), remote_request_(), nominated_(false) { |
| src_->SignalPortComplete.connect( |
| this, &TestChannel::OnPortComplete); |
| src_->SignalUnknownAddress.connect(this, &TestChannel::OnUnknownAddress); |
| } |
| |
| int complete_count() { return complete_count_; } |
| Connection* conn() { return conn_; } |
| const SocketAddress& remote_address() { return remote_address_; } |
| const std::string remote_fragment() { return remote_frag_; } |
| |
| void Start() { |
| src_->PrepareAddress(); |
| } |
| void CreateConnection() { |
| conn_ = src_->CreateConnection(GetCandidate(dst_), Port::ORIGIN_MESSAGE); |
| IceMode remote_ice_mode = |
| (ice_mode_ == ICEMODE_FULL) ? ICEMODE_LITE : ICEMODE_FULL; |
| conn_->set_remote_ice_mode(remote_ice_mode); |
| conn_->set_use_candidate_attr(remote_ice_mode == ICEMODE_FULL); |
| conn_->SignalStateChange.connect( |
| this, &TestChannel::OnConnectionStateChange); |
| } |
| void OnConnectionStateChange(Connection* conn) { |
| if (conn->write_state() == Connection::STATE_WRITABLE) { |
| conn->set_use_candidate_attr(true); |
| nominated_ = true; |
| } |
| } |
| void AcceptConnection() { |
| ASSERT_TRUE(remote_request_.get() != NULL); |
| Candidate c = GetCandidate(dst_); |
| c.set_address(remote_address_); |
| conn_ = src_->CreateConnection(c, Port::ORIGIN_MESSAGE); |
| src_->SendBindingResponse(remote_request_.get(), remote_address_); |
| remote_request_.reset(); |
| } |
| void Ping() { |
| Ping(0); |
| } |
| void Ping(uint32 now) { |
| conn_->Ping(now); |
| } |
| void Stop() { |
| conn_->SignalDestroyed.connect(this, &TestChannel::OnDestroyed); |
| conn_->Destroy(); |
| } |
| |
| void OnPortComplete(Port* port) { |
| complete_count_++; |
| } |
| void SetIceMode(IceMode ice_mode) { |
| ice_mode_ = ice_mode; |
| } |
| |
| void OnUnknownAddress(PortInterface* port, const SocketAddress& addr, |
| ProtocolType proto, |
| IceMessage* msg, const std::string& rf, |
| bool /*port_muxed*/) { |
| ASSERT_EQ(src_.get(), port); |
| if (!remote_address_.IsNil()) { |
| ASSERT_EQ(remote_address_, addr); |
| } |
| // MI and PRIORITY attribute should be present in ping requests when port |
| // is in ICEPROTO_RFC5245 mode. |
| const cricket::StunUInt32Attribute* priority_attr = |
| msg->GetUInt32(STUN_ATTR_PRIORITY); |
| const cricket::StunByteStringAttribute* mi_attr = |
| msg->GetByteString(STUN_ATTR_MESSAGE_INTEGRITY); |
| const cricket::StunUInt32Attribute* fingerprint_attr = |
| msg->GetUInt32(STUN_ATTR_FINGERPRINT); |
| if (src_->IceProtocol() == cricket::ICEPROTO_RFC5245) { |
| EXPECT_TRUE(priority_attr != NULL); |
| EXPECT_TRUE(mi_attr != NULL); |
| EXPECT_TRUE(fingerprint_attr != NULL); |
| } else { |
| EXPECT_TRUE(priority_attr == NULL); |
| EXPECT_TRUE(mi_attr == NULL); |
| EXPECT_TRUE(fingerprint_attr == NULL); |
| } |
| remote_address_ = addr; |
| remote_request_.reset(CopyStunMessage(msg)); |
| remote_frag_ = rf; |
| } |
| |
| void OnDestroyed(Connection* conn) { |
| ASSERT_EQ(conn_, conn); |
| conn_ = NULL; |
| } |
| |
| bool nominated() const { return nominated_; } |
| |
| private: |
| IceMode ice_mode_; |
| talk_base::scoped_ptr<Port> src_; |
| Port* dst_; |
| |
| int complete_count_; |
| Connection* conn_; |
| SocketAddress remote_address_; |
| talk_base::scoped_ptr<StunMessage> remote_request_; |
| std::string remote_frag_; |
| bool nominated_; |
| }; |
| |
| class PortTest : public testing::Test, public sigslot::has_slots<> { |
| public: |
| PortTest() |
| : main_(talk_base::Thread::Current()), |
| pss_(new talk_base::PhysicalSocketServer), |
| ss_(new talk_base::VirtualSocketServer(pss_.get())), |
| ss_scope_(ss_.get()), |
| network_("unittest", "unittest", talk_base::IPAddress(INADDR_ANY), 32), |
| socket_factory_(talk_base::Thread::Current()), |
| nat_factory1_(ss_.get(), kNatAddr1), |
| nat_factory2_(ss_.get(), kNatAddr2), |
| nat_socket_factory1_(&nat_factory1_), |
| nat_socket_factory2_(&nat_factory2_), |
| stun_server_(main_, kStunAddr), |
| turn_server_(main_, kTurnUdpIntAddr, kTurnUdpExtAddr), |
| relay_server_(main_, kRelayUdpIntAddr, kRelayUdpExtAddr, |
| kRelayTcpIntAddr, kRelayTcpExtAddr, |
| kRelaySslTcpIntAddr, kRelaySslTcpExtAddr), |
| username_(talk_base::CreateRandomString(ICE_UFRAG_LENGTH)), |
| password_(talk_base::CreateRandomString(ICE_PWD_LENGTH)), |
| ice_protocol_(cricket::ICEPROTO_GOOGLE), |
| role_conflict_(false) { |
| network_.AddIP(talk_base::IPAddress(INADDR_ANY)); |
| } |
| |
| protected: |
| static void SetUpTestCase() { |
| // Ensure the RNG is inited. |
| talk_base::InitRandom(NULL, 0); |
| } |
| |
| void TestLocalToLocal() { |
| Port* port1 = CreateUdpPort(kLocalAddr1); |
| Port* port2 = CreateUdpPort(kLocalAddr2); |
| TestConnectivity("udp", port1, "udp", port2, true, true, true, true); |
| } |
| void TestLocalToStun(NATType ntype) { |
| Port* port1 = CreateUdpPort(kLocalAddr1); |
| nat_server2_.reset(CreateNatServer(kNatAddr2, ntype)); |
| Port* port2 = CreateStunPort(kLocalAddr2, &nat_socket_factory2_); |
| TestConnectivity("udp", port1, StunName(ntype), port2, |
| ntype == NAT_OPEN_CONE, true, |
| ntype != NAT_SYMMETRIC, true); |
| } |
| void TestLocalToRelay(RelayType rtype, ProtocolType proto) { |
| Port* port1 = CreateUdpPort(kLocalAddr1); |
| Port* port2 = CreateRelayPort(kLocalAddr2, rtype, proto, PROTO_UDP); |
| TestConnectivity("udp", port1, RelayName(rtype, proto), port2, |
| rtype == RELAY_GTURN, true, true, true); |
| } |
| void TestStunToLocal(NATType ntype) { |
| nat_server1_.reset(CreateNatServer(kNatAddr1, ntype)); |
| Port* port1 = CreateStunPort(kLocalAddr1, &nat_socket_factory1_); |
| Port* port2 = CreateUdpPort(kLocalAddr2); |
| TestConnectivity(StunName(ntype), port1, "udp", port2, |
| true, ntype != NAT_SYMMETRIC, true, true); |
| } |
| void TestStunToStun(NATType ntype1, NATType ntype2) { |
| nat_server1_.reset(CreateNatServer(kNatAddr1, ntype1)); |
| Port* port1 = CreateStunPort(kLocalAddr1, &nat_socket_factory1_); |
| nat_server2_.reset(CreateNatServer(kNatAddr2, ntype2)); |
| Port* port2 = CreateStunPort(kLocalAddr2, &nat_socket_factory2_); |
| TestConnectivity(StunName(ntype1), port1, StunName(ntype2), port2, |
| ntype2 == NAT_OPEN_CONE, |
| ntype1 != NAT_SYMMETRIC, ntype2 != NAT_SYMMETRIC, |
| ntype1 + ntype2 < (NAT_PORT_RESTRICTED + NAT_SYMMETRIC)); |
| } |
| void TestStunToRelay(NATType ntype, RelayType rtype, ProtocolType proto) { |
| nat_server1_.reset(CreateNatServer(kNatAddr1, ntype)); |
| Port* port1 = CreateStunPort(kLocalAddr1, &nat_socket_factory1_); |
| Port* port2 = CreateRelayPort(kLocalAddr2, rtype, proto, PROTO_UDP); |
| TestConnectivity(StunName(ntype), port1, RelayName(rtype, proto), port2, |
| rtype == RELAY_GTURN, ntype != NAT_SYMMETRIC, true, true); |
| } |
| void TestTcpToTcp() { |
| Port* port1 = CreateTcpPort(kLocalAddr1); |
| Port* port2 = CreateTcpPort(kLocalAddr2); |
| TestConnectivity("tcp", port1, "tcp", port2, true, false, true, true); |
| } |
| void TestTcpToRelay(RelayType rtype, ProtocolType proto) { |
| Port* port1 = CreateTcpPort(kLocalAddr1); |
| Port* port2 = CreateRelayPort(kLocalAddr2, rtype, proto, PROTO_TCP); |
| TestConnectivity("tcp", port1, RelayName(rtype, proto), port2, |
| rtype == RELAY_GTURN, false, true, true); |
| } |
| void TestSslTcpToRelay(RelayType rtype, ProtocolType proto) { |
| Port* port1 = CreateTcpPort(kLocalAddr1); |
| Port* port2 = CreateRelayPort(kLocalAddr2, rtype, proto, PROTO_SSLTCP); |
| TestConnectivity("ssltcp", port1, RelayName(rtype, proto), port2, |
| rtype == RELAY_GTURN, false, true, true); |
| } |
| |
| // helpers for above functions |
| UDPPort* CreateUdpPort(const SocketAddress& addr) { |
| return CreateUdpPort(addr, &socket_factory_); |
| } |
| UDPPort* CreateUdpPort(const SocketAddress& addr, |
| PacketSocketFactory* socket_factory) { |
| UDPPort* port = UDPPort::Create(main_, socket_factory, &network_, |
| addr.ipaddr(), 0, 0, username_, password_); |
| port->SetIceProtocolType(ice_protocol_); |
| return port; |
| } |
| TCPPort* CreateTcpPort(const SocketAddress& addr) { |
| TCPPort* port = CreateTcpPort(addr, &socket_factory_); |
| port->SetIceProtocolType(ice_protocol_); |
| return port; |
| } |
| TCPPort* CreateTcpPort(const SocketAddress& addr, |
| PacketSocketFactory* socket_factory) { |
| TCPPort* port = TCPPort::Create(main_, socket_factory, &network_, |
| addr.ipaddr(), 0, 0, username_, password_, |
| true); |
| port->SetIceProtocolType(ice_protocol_); |
| return port; |
| } |
| StunPort* CreateStunPort(const SocketAddress& addr, |
| talk_base::PacketSocketFactory* factory) { |
| StunPort* port = StunPort::Create(main_, factory, &network_, |
| addr.ipaddr(), 0, 0, |
| username_, password_, kStunAddr); |
| port->SetIceProtocolType(ice_protocol_); |
| return port; |
| } |
| Port* CreateRelayPort(const SocketAddress& addr, RelayType rtype, |
| ProtocolType int_proto, ProtocolType ext_proto) { |
| if (rtype == RELAY_TURN) { |
| return CreateTurnPort(addr, &socket_factory_, int_proto, ext_proto); |
| } else { |
| return CreateGturnPort(addr, int_proto, ext_proto); |
| } |
| } |
| TurnPort* CreateTurnPort(const SocketAddress& addr, |
| PacketSocketFactory* socket_factory, |
| ProtocolType int_proto, ProtocolType ext_proto) { |
| TurnPort* port = TurnPort::Create(main_, socket_factory, &network_, |
| addr.ipaddr(), 0, 0, |
| username_, password_, ProtocolAddress( |
| kTurnUdpIntAddr, PROTO_UDP), |
| kRelayCredentials); |
| port->SetIceProtocolType(ice_protocol_); |
| return port; |
| } |
| RelayPort* CreateGturnPort(const SocketAddress& addr, |
| ProtocolType int_proto, ProtocolType ext_proto) { |
| RelayPort* port = CreateGturnPort(addr); |
| SocketAddress addrs[] = |
| { kRelayUdpIntAddr, kRelayTcpIntAddr, kRelaySslTcpIntAddr }; |
| port->AddServerAddress(ProtocolAddress(addrs[int_proto], int_proto)); |
| return port; |
| } |
| RelayPort* CreateGturnPort(const SocketAddress& addr) { |
| RelayPort* port = RelayPort::Create(main_, &socket_factory_, &network_, |
| addr.ipaddr(), 0, 0, |
| username_, password_); |
| // TODO: Add an external address for ext_proto, so that the |
| // other side can connect to this port using a non-UDP protocol. |
| port->SetIceProtocolType(ice_protocol_); |
| return port; |
| } |
| talk_base::NATServer* CreateNatServer(const SocketAddress& addr, |
| talk_base::NATType type) { |
| return new talk_base::NATServer(type, ss_.get(), addr, ss_.get(), addr); |
| } |
| static const char* StunName(NATType type) { |
| switch (type) { |
| case NAT_OPEN_CONE: return "stun(open cone)"; |
| case NAT_ADDR_RESTRICTED: return "stun(addr restricted)"; |
| case NAT_PORT_RESTRICTED: return "stun(port restricted)"; |
| case NAT_SYMMETRIC: return "stun(symmetric)"; |
| default: return "stun(?)"; |
| } |
| } |
| static const char* RelayName(RelayType type, ProtocolType proto) { |
| if (type == RELAY_TURN) { |
| switch (proto) { |
| case PROTO_UDP: return "turn(udp)"; |
| case PROTO_TCP: return "turn(tcp)"; |
| case PROTO_SSLTCP: return "turn(ssltcp)"; |
| default: return "turn(?)"; |
| } |
| } else { |
| switch (proto) { |
| case PROTO_UDP: return "gturn(udp)"; |
| case PROTO_TCP: return "gturn(tcp)"; |
| case PROTO_SSLTCP: return "gturn(ssltcp)"; |
| default: return "gturn(?)"; |
| } |
| } |
| } |
| |
| void TestCrossFamilyPorts(int type); |
| |
| // this does all the work |
| void TestConnectivity(const char* name1, Port* port1, |
| const char* name2, Port* port2, |
| bool accept, bool same_addr1, |
| bool same_addr2, bool possible); |
| |
| void SetIceProtocolType(cricket::IceProtocolType protocol) { |
| ice_protocol_ = protocol; |
| } |
| |
| IceMessage* CreateStunMessage(int type) { |
| IceMessage* msg = new IceMessage(); |
| msg->SetType(type); |
| msg->SetTransactionID("TESTTESTTEST"); |
| return msg; |
| } |
| IceMessage* CreateStunMessageWithUsername(int type, |
| const std::string& username) { |
| IceMessage* msg = CreateStunMessage(type); |
| msg->AddAttribute( |
| new StunByteStringAttribute(STUN_ATTR_USERNAME, username)); |
| return msg; |
| } |
| TestPort* CreateTestPort(const talk_base::SocketAddress& addr, |
| const std::string& username, |
| const std::string& password) { |
| TestPort* port = new TestPort(main_, "test", &socket_factory_, &network_, |
| addr.ipaddr(), 0, 0, username, password); |
| port->SignalRoleConflict.connect(this, &PortTest::OnRoleConflict); |
| return port; |
| } |
| TestPort* CreateTestPort(const talk_base::SocketAddress& addr, |
| const std::string& username, |
| const std::string& password, |
| cricket::IceProtocolType type, |
| cricket::IceRole role, |
| int tiebreaker) { |
| TestPort* port = CreateTestPort(addr, username, password); |
| port->SetIceProtocolType(type); |
| port->SetIceRole(role); |
| port->SetIceTiebreaker(tiebreaker); |
| return port; |
| } |
| |
| void OnRoleConflict(PortInterface* port) { |
| role_conflict_ = true; |
| } |
| bool role_conflict() const { return role_conflict_; } |
| |
| talk_base::BasicPacketSocketFactory* nat_socket_factory1() { |
| return &nat_socket_factory1_; |
| } |
| |
| private: |
| talk_base::Thread* main_; |
| talk_base::scoped_ptr<talk_base::PhysicalSocketServer> pss_; |
| talk_base::scoped_ptr<talk_base::VirtualSocketServer> ss_; |
| talk_base::SocketServerScope ss_scope_; |
| talk_base::Network network_; |
| talk_base::BasicPacketSocketFactory socket_factory_; |
| talk_base::scoped_ptr<talk_base::NATServer> nat_server1_; |
| talk_base::scoped_ptr<talk_base::NATServer> nat_server2_; |
| talk_base::NATSocketFactory nat_factory1_; |
| talk_base::NATSocketFactory nat_factory2_; |
| talk_base::BasicPacketSocketFactory nat_socket_factory1_; |
| talk_base::BasicPacketSocketFactory nat_socket_factory2_; |
| TestStunServer stun_server_; |
| TestTurnServer turn_server_; |
| TestRelayServer relay_server_; |
| std::string username_; |
| std::string password_; |
| cricket::IceProtocolType ice_protocol_; |
| bool role_conflict_; |
| }; |
| |
| void PortTest::TestConnectivity(const char* name1, Port* port1, |
| const char* name2, Port* port2, |
| bool accept, bool same_addr1, |
| bool same_addr2, bool possible) { |
| LOG(LS_INFO) << "Test: " << name1 << " to " << name2 << ": "; |
| port1->set_component(cricket::ICE_CANDIDATE_COMPONENT_DEFAULT); |
| port2->set_component(cricket::ICE_CANDIDATE_COMPONENT_DEFAULT); |
| |
| // Set up channels. |
| TestChannel ch1(port1, port2); |
| TestChannel ch2(port2, port1); |
| EXPECT_EQ(0, ch1.complete_count()); |
| EXPECT_EQ(0, ch2.complete_count()); |
| |
| // Acquire addresses. |
| ch1.Start(); |
| ch2.Start(); |
| ASSERT_EQ_WAIT(1, ch1.complete_count(), kTimeout); |
| ASSERT_EQ_WAIT(1, ch2.complete_count(), kTimeout); |
| |
| // Send a ping from src to dst. This may or may not make it. |
| ch1.CreateConnection(); |
| ASSERT_TRUE(ch1.conn() != NULL); |
| EXPECT_TRUE_WAIT(ch1.conn()->connected(), kTimeout); // for TCP connect |
| ch1.Ping(); |
| WAIT(!ch2.remote_address().IsNil(), kTimeout); |
| |
| if (accept) { |
| // We are able to send a ping from src to dst. This is the case when |
| // sending to UDP ports and cone NATs. |
| EXPECT_TRUE(ch1.remote_address().IsNil()); |
| EXPECT_EQ(ch2.remote_fragment(), port1->username_fragment()); |
| |
| // Ensure the ping came from the same address used for src. |
| // This is the case unless the source NAT was symmetric. |
| if (same_addr1) EXPECT_EQ(ch2.remote_address(), GetAddress(port1)); |
| EXPECT_TRUE(same_addr2); |
| |
| // Send a ping from dst to src. |
| ch2.AcceptConnection(); |
| ASSERT_TRUE(ch2.conn() != NULL); |
| ch2.Ping(); |
| EXPECT_EQ_WAIT(Connection::STATE_WRITABLE, ch2.conn()->write_state(), |
| kTimeout); |
| } else { |
| // We can't send a ping from src to dst, so flip it around. This will happen |
| // when the destination NAT is addr/port restricted or symmetric. |
| EXPECT_TRUE(ch1.remote_address().IsNil()); |
| EXPECT_TRUE(ch2.remote_address().IsNil()); |
| |
| // Send a ping from dst to src. Again, this may or may not make it. |
| ch2.CreateConnection(); |
| ASSERT_TRUE(ch2.conn() != NULL); |
| ch2.Ping(); |
| WAIT(ch2.conn()->write_state() == Connection::STATE_WRITABLE, kTimeout); |
| |
| if (same_addr1 && same_addr2) { |
| // The new ping got back to the source. |
| EXPECT_EQ(Connection::STATE_READABLE, ch1.conn()->read_state()); |
| EXPECT_EQ(Connection::STATE_WRITABLE, ch2.conn()->write_state()); |
| |
| // First connection may not be writable if the first ping did not get |
| // through. So we will have to do another. |
| if (ch1.conn()->write_state() == Connection::STATE_WRITE_INIT) { |
| ch1.Ping(); |
| EXPECT_EQ_WAIT(Connection::STATE_WRITABLE, ch1.conn()->write_state(), |
| kTimeout); |
| } |
| } else if (!same_addr1 && possible) { |
| // The new ping went to the candidate address, but that address was bad. |
| // This will happen when the source NAT is symmetric. |
| EXPECT_TRUE(ch1.remote_address().IsNil()); |
| EXPECT_TRUE(ch2.remote_address().IsNil()); |
| |
| // However, since we have now sent a ping to the source IP, we should be |
| // able to get a ping from it. This gives us the real source address. |
| ch1.Ping(); |
| EXPECT_TRUE_WAIT(!ch2.remote_address().IsNil(), kTimeout); |
| EXPECT_EQ(Connection::STATE_READ_INIT, ch2.conn()->read_state()); |
| EXPECT_TRUE(ch1.remote_address().IsNil()); |
| |
| // Pick up the actual address and establish the connection. |
| ch2.AcceptConnection(); |
| ASSERT_TRUE(ch2.conn() != NULL); |
| ch2.Ping(); |
| EXPECT_EQ_WAIT(Connection::STATE_WRITABLE, ch2.conn()->write_state(), |
| kTimeout); |
| } else if (!same_addr2 && possible) { |
| // The new ping came in, but from an unexpected address. This will happen |
| // when the destination NAT is symmetric. |
| EXPECT_FALSE(ch1.remote_address().IsNil()); |
| EXPECT_EQ(Connection::STATE_READ_INIT, ch1.conn()->read_state()); |
| |
| // Update our address and complete the connection. |
| ch1.AcceptConnection(); |
| ch1.Ping(); |
| EXPECT_EQ_WAIT(Connection::STATE_WRITABLE, ch1.conn()->write_state(), |
| kTimeout); |
| } else { // (!possible) |
| // There should be s no way for the pings to reach each other. Check it. |
| EXPECT_TRUE(ch1.remote_address().IsNil()); |
| EXPECT_TRUE(ch2.remote_address().IsNil()); |
| ch1.Ping(); |
| WAIT(!ch2.remote_address().IsNil(), kTimeout); |
| EXPECT_TRUE(ch1.remote_address().IsNil()); |
| EXPECT_TRUE(ch2.remote_address().IsNil()); |
| } |
| } |
| |
| // Everything should be good, unless we know the situation is impossible. |
| ASSERT_TRUE(ch1.conn() != NULL); |
| ASSERT_TRUE(ch2.conn() != NULL); |
| if (possible) { |
| EXPECT_EQ(Connection::STATE_READABLE, ch1.conn()->read_state()); |
| EXPECT_EQ(Connection::STATE_WRITABLE, ch1.conn()->write_state()); |
| EXPECT_EQ(Connection::STATE_READABLE, ch2.conn()->read_state()); |
| EXPECT_EQ(Connection::STATE_WRITABLE, ch2.conn()->write_state()); |
| } else { |
| EXPECT_NE(Connection::STATE_READABLE, ch1.conn()->read_state()); |
| EXPECT_NE(Connection::STATE_WRITABLE, ch1.conn()->write_state()); |
| EXPECT_NE(Connection::STATE_READABLE, ch2.conn()->read_state()); |
| EXPECT_NE(Connection::STATE_WRITABLE, ch2.conn()->write_state()); |
| } |
| |
| // Tear down and ensure that goes smoothly. |
| ch1.Stop(); |
| ch2.Stop(); |
| EXPECT_TRUE_WAIT(ch1.conn() == NULL, kTimeout); |
| EXPECT_TRUE_WAIT(ch2.conn() == NULL, kTimeout); |
| } |
| |
| class FakePacketSocketFactory : public talk_base::PacketSocketFactory { |
| public: |
| FakePacketSocketFactory() |
| : next_udp_socket_(NULL), |
| next_server_tcp_socket_(NULL), |
| next_client_tcp_socket_(NULL) { |
| } |
| virtual ~FakePacketSocketFactory() { } |
| |
| virtual AsyncPacketSocket* CreateUdpSocket( |
| const SocketAddress& address, int min_port, int max_port) { |
| EXPECT_TRUE(next_udp_socket_ != NULL); |
| AsyncPacketSocket* result = next_udp_socket_; |
| next_udp_socket_ = NULL; |
| return result; |
| } |
| |
| virtual AsyncPacketSocket* CreateServerTcpSocket( |
| const SocketAddress& local_address, int min_port, int max_port, |
| int opts) { |
| EXPECT_TRUE(next_server_tcp_socket_ != NULL); |
| AsyncPacketSocket* result = next_server_tcp_socket_; |
| next_server_tcp_socket_ = NULL; |
| return result; |
| } |
| |
| // TODO: |proxy_info| and |user_agent| should be set |
| // per-factory and not when socket is created. |
| virtual AsyncPacketSocket* CreateClientTcpSocket( |
| const SocketAddress& local_address, const SocketAddress& remote_address, |
| const talk_base::ProxyInfo& proxy_info, |
| const std::string& user_agent, int opts) { |
| EXPECT_TRUE(next_client_tcp_socket_ != NULL); |
| AsyncPacketSocket* result = next_client_tcp_socket_; |
| next_client_tcp_socket_ = NULL; |
| return result; |
| } |
| |
| void set_next_udp_socket(AsyncPacketSocket* next_udp_socket) { |
| next_udp_socket_ = next_udp_socket; |
| } |
| void set_next_server_tcp_socket(AsyncPacketSocket* next_server_tcp_socket) { |
| next_server_tcp_socket_ = next_server_tcp_socket; |
| } |
| void set_next_client_tcp_socket(AsyncPacketSocket* next_client_tcp_socket) { |
| next_client_tcp_socket_ = next_client_tcp_socket; |
| } |
| |
| private: |
| AsyncPacketSocket* next_udp_socket_; |
| AsyncPacketSocket* next_server_tcp_socket_; |
| AsyncPacketSocket* next_client_tcp_socket_; |
| }; |
| |
| class FakeAsyncPacketSocket : public AsyncPacketSocket { |
| public: |
| // Returns current local address. Address may be set to NULL if the |
| // socket is not bound yet (GetState() returns STATE_BINDING). |
| virtual SocketAddress GetLocalAddress() const { |
| return SocketAddress(); |
| } |
| |
| // Returns remote address. Returns zeroes if this is not a client TCP socket. |
| virtual SocketAddress GetRemoteAddress() const { |
| return SocketAddress(); |
| } |
| |
| // Send a packet. |
| virtual int Send(const void *pv, size_t cb, |
| talk_base::DiffServCodePoint dscp) { |
| return static_cast<int>(cb); |
| } |
| virtual int SendTo(const void *pv, size_t cb, const SocketAddress& addr, |
| talk_base::DiffServCodePoint dscp) { |
| return static_cast<int>(cb); |
| } |
| virtual int Close() { |
| return 0; |
| } |
| |
| virtual State GetState() const { return state_; } |
| virtual int GetOption(Socket::Option opt, int* value) { return 0; } |
| virtual int SetOption(Socket::Option opt, int value) { return 0; } |
| virtual int GetError() const { return 0; } |
| virtual void SetError(int error) { } |
| |
| void set_state(State state) { state_ = state; } |
| |
| private: |
| State state_; |
| }; |
| |
| // Local -> XXXX |
| TEST_F(PortTest, TestLocalToLocal) { |
| TestLocalToLocal(); |
| } |
| |
| TEST_F(PortTest, TestLocalToConeNat) { |
| TestLocalToStun(NAT_OPEN_CONE); |
| } |
| |
| TEST_F(PortTest, TestLocalToARNat) { |
| TestLocalToStun(NAT_ADDR_RESTRICTED); |
| } |
| |
| TEST_F(PortTest, TestLocalToPRNat) { |
| TestLocalToStun(NAT_PORT_RESTRICTED); |
| } |
| |
| TEST_F(PortTest, TestLocalToSymNat) { |
| TestLocalToStun(NAT_SYMMETRIC); |
| } |
| |
| TEST_F(PortTest, TestLocalToTurn) { |
| TestLocalToRelay(RELAY_TURN, PROTO_UDP); |
| } |
| |
| TEST_F(PortTest, TestLocalToGturn) { |
| TestLocalToRelay(RELAY_GTURN, PROTO_UDP); |
| } |
| |
| TEST_F(PortTest, TestLocalToTcpGturn) { |
| TestLocalToRelay(RELAY_GTURN, PROTO_TCP); |
| } |
| |
| TEST_F(PortTest, TestLocalToSslTcpGturn) { |
| TestLocalToRelay(RELAY_GTURN, PROTO_SSLTCP); |
| } |
| |
| // Cone NAT -> XXXX |
| TEST_F(PortTest, TestConeNatToLocal) { |
| TestStunToLocal(NAT_OPEN_CONE); |
| } |
| |
| TEST_F(PortTest, TestConeNatToConeNat) { |
| TestStunToStun(NAT_OPEN_CONE, NAT_OPEN_CONE); |
| } |
| |
| TEST_F(PortTest, TestConeNatToARNat) { |
| TestStunToStun(NAT_OPEN_CONE, NAT_ADDR_RESTRICTED); |
| } |
| |
| TEST_F(PortTest, TestConeNatToPRNat) { |
| TestStunToStun(NAT_OPEN_CONE, NAT_PORT_RESTRICTED); |
| } |
| |
| TEST_F(PortTest, TestConeNatToSymNat) { |
| TestStunToStun(NAT_OPEN_CONE, NAT_SYMMETRIC); |
| } |
| |
| TEST_F(PortTest, TestConeNatToTurn) { |
| TestStunToRelay(NAT_OPEN_CONE, RELAY_TURN, PROTO_UDP); |
| } |
| |
| TEST_F(PortTest, TestConeNatToGturn) { |
| TestStunToRelay(NAT_OPEN_CONE, RELAY_GTURN, PROTO_UDP); |
| } |
| |
| TEST_F(PortTest, TestConeNatToTcpGturn) { |
| TestStunToRelay(NAT_OPEN_CONE, RELAY_GTURN, PROTO_TCP); |
| } |
| |
| // Address-restricted NAT -> XXXX |
| TEST_F(PortTest, TestARNatToLocal) { |
| TestStunToLocal(NAT_ADDR_RESTRICTED); |
| } |
| |
| TEST_F(PortTest, TestARNatToConeNat) { |
| TestStunToStun(NAT_ADDR_RESTRICTED, NAT_OPEN_CONE); |
| } |
| |
| TEST_F(PortTest, TestARNatToARNat) { |
| TestStunToStun(NAT_ADDR_RESTRICTED, NAT_ADDR_RESTRICTED); |
| } |
| |
| TEST_F(PortTest, TestARNatToPRNat) { |
| TestStunToStun(NAT_ADDR_RESTRICTED, NAT_PORT_RESTRICTED); |
| } |
| |
| TEST_F(PortTest, TestARNatToSymNat) { |
| TestStunToStun(NAT_ADDR_RESTRICTED, NAT_SYMMETRIC); |
| } |
| |
| TEST_F(PortTest, TestARNatToTurn) { |
| TestStunToRelay(NAT_ADDR_RESTRICTED, RELAY_TURN, PROTO_UDP); |
| } |
| |
| TEST_F(PortTest, TestARNatToGturn) { |
| TestStunToRelay(NAT_ADDR_RESTRICTED, RELAY_GTURN, PROTO_UDP); |
| } |
| |
| TEST_F(PortTest, TestARNATNatToTcpGturn) { |
| TestStunToRelay(NAT_ADDR_RESTRICTED, RELAY_GTURN, PROTO_TCP); |
| } |
| |
| // Port-restricted NAT -> XXXX |
| TEST_F(PortTest, TestPRNatToLocal) { |
| TestStunToLocal(NAT_PORT_RESTRICTED); |
| } |
| |
| TEST_F(PortTest, TestPRNatToConeNat) { |
| TestStunToStun(NAT_PORT_RESTRICTED, NAT_OPEN_CONE); |
| } |
| |
| TEST_F(PortTest, TestPRNatToARNat) { |
| TestStunToStun(NAT_PORT_RESTRICTED, NAT_ADDR_RESTRICTED); |
| } |
| |
| TEST_F(PortTest, TestPRNatToPRNat) { |
| TestStunToStun(NAT_PORT_RESTRICTED, NAT_PORT_RESTRICTED); |
| } |
| |
| TEST_F(PortTest, TestPRNatToSymNat) { |
| // Will "fail" |
| TestStunToStun(NAT_PORT_RESTRICTED, NAT_SYMMETRIC); |
| } |
| |
| TEST_F(PortTest, TestPRNatToTurn) { |
| TestStunToRelay(NAT_PORT_RESTRICTED, RELAY_TURN, PROTO_UDP); |
| } |
| |
| TEST_F(PortTest, TestPRNatToGturn) { |
| TestStunToRelay(NAT_PORT_RESTRICTED, RELAY_GTURN, PROTO_UDP); |
| } |
| |
| TEST_F(PortTest, TestPRNatToTcpGturn) { |
| TestStunToRelay(NAT_PORT_RESTRICTED, RELAY_GTURN, PROTO_TCP); |
| } |
| |
| // Symmetric NAT -> XXXX |
| TEST_F(PortTest, TestSymNatToLocal) { |
| TestStunToLocal(NAT_SYMMETRIC); |
| } |
| |
| TEST_F(PortTest, TestSymNatToConeNat) { |
| TestStunToStun(NAT_SYMMETRIC, NAT_OPEN_CONE); |
| } |
| |
| TEST_F(PortTest, TestSymNatToARNat) { |
| TestStunToStun(NAT_SYMMETRIC, NAT_ADDR_RESTRICTED); |
| } |
| |
| TEST_F(PortTest, TestSymNatToPRNat) { |
| // Will "fail" |
| TestStunToStun(NAT_SYMMETRIC, NAT_PORT_RESTRICTED); |
| } |
| |
| TEST_F(PortTest, TestSymNatToSymNat) { |
| // Will "fail" |
| TestStunToStun(NAT_SYMMETRIC, NAT_SYMMETRIC); |
| } |
| |
| TEST_F(PortTest, TestSymNatToTurn) { |
| TestStunToRelay(NAT_SYMMETRIC, RELAY_TURN, PROTO_UDP); |
| } |
| |
| TEST_F(PortTest, TestSymNatToGturn) { |
| TestStunToRelay(NAT_SYMMETRIC, RELAY_GTURN, PROTO_UDP); |
| } |
| |
| TEST_F(PortTest, TestSymNatToTcpGturn) { |
| TestStunToRelay(NAT_SYMMETRIC, RELAY_GTURN, PROTO_TCP); |
| } |
| |
| // Outbound TCP -> XXXX |
| TEST_F(PortTest, TestTcpToTcp) { |
| TestTcpToTcp(); |
| } |
| |
| /* TODO: Enable these once testrelayserver can accept external TCP. |
| TEST_F(PortTest, TestTcpToTcpRelay) { |
| TestTcpToRelay(PROTO_TCP); |
| } |
| |
| TEST_F(PortTest, TestTcpToSslTcpRelay) { |
| TestTcpToRelay(PROTO_SSLTCP); |
| } |
| */ |
| |
| // Outbound SSLTCP -> XXXX |
| /* TODO: Enable these once testrelayserver can accept external SSL. |
| TEST_F(PortTest, TestSslTcpToTcpRelay) { |
| TestSslTcpToRelay(PROTO_TCP); |
| } |
| |
| TEST_F(PortTest, TestSslTcpToSslTcpRelay) { |
| TestSslTcpToRelay(PROTO_SSLTCP); |
| } |
| */ |
| |
| // This test case verifies standard ICE features in STUN messages. Currently it |
| // verifies Message Integrity attribute in STUN messages and username in STUN |
| // binding request will have colon (":") between remote and local username. |
| TEST_F(PortTest, TestLocalToLocalAsIce) { |
| SetIceProtocolType(cricket::ICEPROTO_RFC5245); |
| UDPPort* port1 = CreateUdpPort(kLocalAddr1); |
| port1->SetIceRole(cricket::ICEROLE_CONTROLLING); |
| port1->SetIceTiebreaker(kTiebreaker1); |
| ASSERT_EQ(cricket::ICEPROTO_RFC5245, port1->IceProtocol()); |
| UDPPort* port2 = CreateUdpPort(kLocalAddr2); |
| port2->SetIceRole(cricket::ICEROLE_CONTROLLED); |
| port2->SetIceTiebreaker(kTiebreaker2); |
| ASSERT_EQ(cricket::ICEPROTO_RFC5245, port2->IceProtocol()); |
| // Same parameters as TestLocalToLocal above. |
| TestConnectivity("udp", port1, "udp", port2, true, true, true, true); |
| } |
| |
| // This test is trying to validate a successful and failure scenario in a |
| // loopback test when protocol is RFC5245. For success IceTiebreaker, username |
| // should remain equal to the request generated by the port and role of port |
| // must be in controlling. |
| TEST_F(PortTest, TestLoopbackCallAsIce) { |
| talk_base::scoped_ptr<TestPort> lport( |
| CreateTestPort(kLocalAddr1, "lfrag", "lpass")); |
| lport->SetIceProtocolType(ICEPROTO_RFC5245); |
| lport->SetIceRole(cricket::ICEROLE_CONTROLLING); |
| lport->SetIceTiebreaker(kTiebreaker1); |
| lport->PrepareAddress(); |
| ASSERT_FALSE(lport->Candidates().empty()); |
| Connection* conn = lport->CreateConnection(lport->Candidates()[0], |
| Port::ORIGIN_MESSAGE); |
| conn->Ping(0); |
| |
| ASSERT_TRUE_WAIT(lport->last_stun_msg() != NULL, 1000); |
| IceMessage* msg = lport->last_stun_msg(); |
| EXPECT_EQ(STUN_BINDING_REQUEST, msg->type()); |
| conn->OnReadPacket(lport->last_stun_buf()->Data(), |
| lport->last_stun_buf()->Length()); |
| ASSERT_TRUE_WAIT(lport->last_stun_msg() != NULL, 1000); |
| msg = lport->last_stun_msg(); |
| EXPECT_EQ(STUN_BINDING_RESPONSE, msg->type()); |
| |
| // If the tiebreaker value is different from port, we expect a error |
| // response. |
| lport->Reset(); |
| lport->AddCandidateAddress(kLocalAddr2); |
| // Creating a different connection as |conn| is in STATE_READABLE. |
| Connection* conn1 = lport->CreateConnection(lport->Candidates()[1], |
| Port::ORIGIN_MESSAGE); |
| conn1->Ping(0); |
| |
| ASSERT_TRUE_WAIT(lport->last_stun_msg() != NULL, 1000); |
| msg = lport->last_stun_msg(); |
| EXPECT_EQ(STUN_BINDING_REQUEST, msg->type()); |
| talk_base::scoped_ptr<IceMessage> modified_req( |
| CreateStunMessage(STUN_BINDING_REQUEST)); |
| const StunByteStringAttribute* username_attr = msg->GetByteString( |
| STUN_ATTR_USERNAME); |
| modified_req->AddAttribute(new StunByteStringAttribute( |
| STUN_ATTR_USERNAME, username_attr->GetString())); |
| // To make sure we receive error response, adding tiebreaker less than |
| // what's present in request. |
| modified_req->AddAttribute(new StunUInt64Attribute( |
| STUN_ATTR_ICE_CONTROLLING, kTiebreaker1 - 1)); |
| modified_req->AddMessageIntegrity("lpass"); |
| modified_req->AddFingerprint(); |
| |
| lport->Reset(); |
| talk_base::scoped_ptr<ByteBuffer> buf(new ByteBuffer()); |
| WriteStunMessage(modified_req.get(), buf.get()); |
| conn1->OnReadPacket(buf->Data(), buf->Length()); |
| ASSERT_TRUE_WAIT(lport->last_stun_msg() != NULL, 1000); |
| msg = lport->last_stun_msg(); |
| EXPECT_EQ(STUN_BINDING_ERROR_RESPONSE, msg->type()); |
| } |
| |
| // This test verifies role conflict signal is received when there is |
| // conflict in the role. In this case both ports are in controlling and |
| // |rport| has higher tiebreaker value than |lport|. Since |lport| has lower |
| // value of tiebreaker, when it receives ping request from |rport| it will |
| // send role conflict signal. |
| TEST_F(PortTest, TestIceRoleConflict) { |
| talk_base::scoped_ptr<TestPort> lport( |
| CreateTestPort(kLocalAddr1, "lfrag", "lpass")); |
| lport->SetIceProtocolType(ICEPROTO_RFC5245); |
| lport->SetIceRole(cricket::ICEROLE_CONTROLLING); |
| lport->SetIceTiebreaker(kTiebreaker1); |
| talk_base::scoped_ptr<TestPort> rport( |
| CreateTestPort(kLocalAddr2, "rfrag", "rpass")); |
| rport->SetIceProtocolType(ICEPROTO_RFC5245); |
| rport->SetIceRole(cricket::ICEROLE_CONTROLLING); |
| rport->SetIceTiebreaker(kTiebreaker2); |
| |
| lport->PrepareAddress(); |
| rport->PrepareAddress(); |
| ASSERT_FALSE(lport->Candidates().empty()); |
| ASSERT_FALSE(rport->Candidates().empty()); |
| Connection* lconn = lport->CreateConnection(rport->Candidates()[0], |
| Port::ORIGIN_MESSAGE); |
| Connection* rconn = rport->CreateConnection(lport->Candidates()[0], |
| Port::ORIGIN_MESSAGE); |
| rconn->Ping(0); |
| |
| ASSERT_TRUE_WAIT(rport->last_stun_msg() != NULL, 1000); |
| IceMessage* msg = rport->last_stun_msg(); |
| EXPECT_EQ(STUN_BINDING_REQUEST, msg->type()); |
| // Send rport binding request to lport. |
| lconn->OnReadPacket(rport->last_stun_buf()->Data(), |
| rport->last_stun_buf()->Length()); |
| |
| ASSERT_TRUE_WAIT(lport->last_stun_msg() != NULL, 1000); |
| EXPECT_EQ(STUN_BINDING_RESPONSE, lport->last_stun_msg()->type()); |
| EXPECT_TRUE(role_conflict()); |
| } |
| |
| TEST_F(PortTest, TestTcpNoDelay) { |
| TCPPort* port1 = CreateTcpPort(kLocalAddr1); |
| int option_value = -1; |
| int success = port1->GetOption(talk_base::Socket::OPT_NODELAY, |
| &option_value); |
| ASSERT_EQ(0, success); // GetOption() should complete successfully w/ 0 |
| ASSERT_EQ(1, option_value); |
| delete port1; |
| } |
| |
| TEST_F(PortTest, TestDelayedBindingUdp) { |
| FakeAsyncPacketSocket *socket = new FakeAsyncPacketSocket(); |
| FakePacketSocketFactory socket_factory; |
| |
| socket_factory.set_next_udp_socket(socket); |
| scoped_ptr<UDPPort> port( |
| CreateUdpPort(kLocalAddr1, &socket_factory)); |
| |
| socket->set_state(AsyncPacketSocket::STATE_BINDING); |
| port->PrepareAddress(); |
| |
| EXPECT_EQ(0U, port->Candidates().size()); |
| socket->SignalAddressReady(socket, kLocalAddr2); |
| |
| EXPECT_EQ(1U, port->Candidates().size()); |
| } |
| |
| TEST_F(PortTest, TestDelayedBindingTcp) { |
| FakeAsyncPacketSocket *socket = new FakeAsyncPacketSocket(); |
| FakePacketSocketFactory socket_factory; |
| |
| socket_factory.set_next_server_tcp_socket(socket); |
| scoped_ptr<TCPPort> port( |
| CreateTcpPort(kLocalAddr1, &socket_factory)); |
| |
| socket->set_state(AsyncPacketSocket::STATE_BINDING); |
| port->PrepareAddress(); |
| |
| EXPECT_EQ(0U, port->Candidates().size()); |
| socket->SignalAddressReady(socket, kLocalAddr2); |
| |
| EXPECT_EQ(1U, port->Candidates().size()); |
| } |
| |
| void PortTest::TestCrossFamilyPorts(int type) { |
| FakePacketSocketFactory factory; |
| scoped_ptr<Port> ports[4]; |
| SocketAddress addresses[4] = {SocketAddress("192.168.1.3", 0), |
| SocketAddress("192.168.1.4", 0), |
| SocketAddress("2001:db8::1", 0), |
| SocketAddress("2001:db8::2", 0)}; |
| for (int i = 0; i < 4; i++) { |
| FakeAsyncPacketSocket *socket = new FakeAsyncPacketSocket(); |
| if (type == SOCK_DGRAM) { |
| factory.set_next_udp_socket(socket); |
| ports[i].reset(CreateUdpPort(addresses[i], &factory)); |
| } else if (type == SOCK_STREAM) { |
| factory.set_next_server_tcp_socket(socket); |
| ports[i].reset(CreateTcpPort(addresses[i], &factory)); |
| } |
| socket->set_state(AsyncPacketSocket::STATE_BINDING); |
| socket->SignalAddressReady(socket, addresses[i]); |
| ports[i]->PrepareAddress(); |
| } |
| |
| // IPv4 Port, connects to IPv6 candidate and then to IPv4 candidate. |
| if (type == SOCK_STREAM) { |
| FakeAsyncPacketSocket* clientsocket = new FakeAsyncPacketSocket(); |
| factory.set_next_client_tcp_socket(clientsocket); |
| } |
| Connection* c = ports[0]->CreateConnection(GetCandidate(ports[2].get()), |
| Port::ORIGIN_MESSAGE); |
| EXPECT_TRUE(NULL == c); |
| EXPECT_EQ(0U, ports[0]->connections().size()); |
| c = ports[0]->CreateConnection(GetCandidate(ports[1].get()), |
| Port::ORIGIN_MESSAGE); |
| EXPECT_FALSE(NULL == c); |
| EXPECT_EQ(1U, ports[0]->connections().size()); |
| |
| // IPv6 Port, connects to IPv4 candidate and to IPv6 candidate. |
| if (type == SOCK_STREAM) { |
| FakeAsyncPacketSocket* clientsocket = new FakeAsyncPacketSocket(); |
| factory.set_next_client_tcp_socket(clientsocket); |
| } |
| c = ports[2]->CreateConnection(GetCandidate(ports[0].get()), |
| Port::ORIGIN_MESSAGE); |
| EXPECT_TRUE(NULL == c); |
| EXPECT_EQ(0U, ports[2]->connections().size()); |
| c = ports[2]->CreateConnection(GetCandidate(ports[3].get()), |
| Port::ORIGIN_MESSAGE); |
| EXPECT_FALSE(NULL == c); |
| EXPECT_EQ(1U, ports[2]->connections().size()); |
| } |
| |
| TEST_F(PortTest, TestSkipCrossFamilyTcp) { |
| TestCrossFamilyPorts(SOCK_STREAM); |
| } |
| |
| TEST_F(PortTest, TestSkipCrossFamilyUdp) { |
| TestCrossFamilyPorts(SOCK_DGRAM); |
| } |
| |
| // This test verifies DSCP value set through SetOption interface can be |
| // get through DefaultDscpValue. |
| TEST_F(PortTest, TestDefaultDscpValue) { |
| talk_base::scoped_ptr<UDPPort> udpport(CreateUdpPort(kLocalAddr1)); |
| udpport->SetOption(talk_base::Socket::OPT_DSCP, talk_base::DSCP_CS6); |
| EXPECT_EQ(talk_base::DSCP_CS6, udpport->DefaultDscpValue()); |
| talk_base::scoped_ptr<TCPPort> tcpport(CreateTcpPort(kLocalAddr1)); |
| tcpport->SetOption(talk_base::Socket::OPT_DSCP, talk_base::DSCP_AF31); |
| EXPECT_EQ(talk_base::DSCP_AF31, tcpport->DefaultDscpValue()); |
| talk_base::scoped_ptr<StunPort> stunport( |
| CreateStunPort(kLocalAddr1, nat_socket_factory1())); |
| stunport->SetOption(talk_base::Socket::OPT_DSCP, talk_base::DSCP_AF41); |
| EXPECT_EQ(talk_base::DSCP_AF41, stunport->DefaultDscpValue()); |
| talk_base::scoped_ptr<TurnPort> turnport(CreateTurnPort( |
| kLocalAddr1, nat_socket_factory1(), PROTO_UDP, PROTO_UDP)); |
| turnport->SetOption(talk_base::Socket::OPT_DSCP, talk_base::DSCP_CS7); |
| EXPECT_EQ(talk_base::DSCP_CS7, turnport->DefaultDscpValue()); |
| // TODO(mallinath) - Test DSCP through GetOption. |
| } |
| |
| // Test sending STUN messages in GICE format. |
| TEST_F(PortTest, TestSendStunMessageAsGice) { |
| talk_base::scoped_ptr<TestPort> lport( |
| CreateTestPort(kLocalAddr1, "lfrag", "lpass")); |
| talk_base::scoped_ptr<TestPort> rport( |
| CreateTestPort(kLocalAddr2, "rfrag", "rpass")); |
| lport->SetIceProtocolType(ICEPROTO_GOOGLE); |
| rport->SetIceProtocolType(ICEPROTO_GOOGLE); |
| |
| // Send a fake ping from lport to rport. |
| lport->PrepareAddress(); |
| rport->PrepareAddress(); |
| ASSERT_FALSE(rport->Candidates().empty()); |
| Connection* conn = lport->CreateConnection(rport->Candidates()[0], |
| Port::ORIGIN_MESSAGE); |
| rport->CreateConnection(lport->Candidates()[0], Port::ORIGIN_MESSAGE); |
| conn->Ping(0); |
| |
| // Check that it's a proper BINDING-REQUEST. |
| ASSERT_TRUE_WAIT(lport->last_stun_msg() != NULL, 1000); |
| IceMessage* msg = lport->last_stun_msg(); |
| EXPECT_EQ(STUN_BINDING_REQUEST, msg->type()); |
| EXPECT_FALSE(msg->IsLegacy()); |
| const StunByteStringAttribute* username_attr = msg->GetByteString( |
| STUN_ATTR_USERNAME); |
| ASSERT_TRUE(username_attr != NULL); |
| EXPECT_EQ("rfraglfrag", username_attr->GetString()); |
| EXPECT_TRUE(msg->GetByteString(STUN_ATTR_MESSAGE_INTEGRITY) == NULL); |
| EXPECT_TRUE(msg->GetByteString(STUN_ATTR_PRIORITY) == NULL); |
| EXPECT_TRUE(msg->GetByteString(STUN_ATTR_FINGERPRINT) == NULL); |
| |
| // Save a copy of the BINDING-REQUEST for use below. |
| talk_base::scoped_ptr<IceMessage> request(CopyStunMessage(msg)); |
| |
| // Respond with a BINDING-RESPONSE. |
| rport->SendBindingResponse(request.get(), lport->Candidates()[0].address()); |
| msg = rport->last_stun_msg(); |
| ASSERT_TRUE(msg != NULL); |
| EXPECT_EQ(STUN_BINDING_RESPONSE, msg->type()); |
| EXPECT_FALSE(msg->IsLegacy()); |
| username_attr = msg->GetByteString(STUN_ATTR_USERNAME); |
| ASSERT_TRUE(username_attr != NULL); // GICE has a username in the response. |
| EXPECT_EQ("rfraglfrag", username_attr->GetString()); |
| const StunAddressAttribute* addr_attr = msg->GetAddress( |
| STUN_ATTR_MAPPED_ADDRESS); |
| ASSERT_TRUE(addr_attr != NULL); |
| EXPECT_EQ(lport->Candidates()[0].address(), addr_attr->GetAddress()); |
| EXPECT_TRUE(msg->GetByteString(STUN_ATTR_XOR_MAPPED_ADDRESS) == NULL); |
| EXPECT_TRUE(msg->GetByteString(STUN_ATTR_MESSAGE_INTEGRITY) == NULL); |
| EXPECT_TRUE(msg->GetByteString(STUN_ATTR_PRIORITY) == NULL); |
| EXPECT_TRUE(msg->GetByteString(STUN_ATTR_FINGERPRINT) == NULL); |
| |
| // Respond with a BINDING-ERROR-RESPONSE. This wouldn't happen in real life, |
| // but we can do it here. |
| rport->SendBindingErrorResponse(request.get(), |
| rport->Candidates()[0].address(), |
| STUN_ERROR_SERVER_ERROR, |
| STUN_ERROR_REASON_SERVER_ERROR); |
| msg = rport->last_stun_msg(); |
| ASSERT_TRUE(msg != NULL); |
| EXPECT_EQ(STUN_BINDING_ERROR_RESPONSE, msg->type()); |
| EXPECT_FALSE(msg->IsLegacy()); |
| username_attr = msg->GetByteString(STUN_ATTR_USERNAME); |
| ASSERT_TRUE(username_attr != NULL); // GICE has a username in the response. |
| EXPECT_EQ("rfraglfrag", username_attr->GetString()); |
| const StunErrorCodeAttribute* error_attr = msg->GetErrorCode(); |
| ASSERT_TRUE(error_attr != NULL); |
| // The GICE wire format for error codes is incorrect. |
| EXPECT_EQ(STUN_ERROR_SERVER_ERROR_AS_GICE, error_attr->code()); |
| EXPECT_EQ(STUN_ERROR_SERVER_ERROR / 256, error_attr->eclass()); |
| EXPECT_EQ(STUN_ERROR_SERVER_ERROR % 256, error_attr->number()); |
| EXPECT_EQ(std::string(STUN_ERROR_REASON_SERVER_ERROR), error_attr->reason()); |
| EXPECT_TRUE(msg->GetByteString(STUN_ATTR_PRIORITY) == NULL); |
| EXPECT_TRUE(msg->GetByteString(STUN_ATTR_MESSAGE_INTEGRITY) == NULL); |
| EXPECT_TRUE(msg->GetByteString(STUN_ATTR_FINGERPRINT) == NULL); |
| } |
| |
| // Test sending STUN messages in ICE format. |
| TEST_F(PortTest, TestSendStunMessageAsIce) { |
| talk_base::scoped_ptr<TestPort> lport( |
| CreateTestPort(kLocalAddr1, "lfrag", "lpass")); |
| talk_base::scoped_ptr<TestPort> rport( |
| CreateTestPort(kLocalAddr2, "rfrag", "rpass")); |
| lport->SetIceProtocolType(ICEPROTO_RFC5245); |
| lport->SetIceRole(cricket::ICEROLE_CONTROLLING); |
| lport->SetIceTiebreaker(kTiebreaker1); |
| rport->SetIceProtocolType(ICEPROTO_RFC5245); |
| rport->SetIceRole(cricket::ICEROLE_CONTROLLED); |
| rport->SetIceTiebreaker(kTiebreaker2); |
| |
| // Send a fake ping from lport to rport. |
| lport->PrepareAddress(); |
| rport->PrepareAddress(); |
| ASSERT_FALSE(rport->Candidates().empty()); |
| Connection* lconn = lport->CreateConnection( |
| rport->Candidates()[0], Port::ORIGIN_MESSAGE); |
| Connection* rconn = rport->CreateConnection( |
| lport->Candidates()[0], Port::ORIGIN_MESSAGE); |
| lconn->Ping(0); |
| |
| // Check that it's a proper BINDING-REQUEST. |
| ASSERT_TRUE_WAIT(lport->last_stun_msg() != NULL, 1000); |
| IceMessage* msg = lport->last_stun_msg(); |
| EXPECT_EQ(STUN_BINDING_REQUEST, msg->type()); |
| EXPECT_FALSE(msg->IsLegacy()); |
| const StunByteStringAttribute* username_attr = |
| msg->GetByteString(STUN_ATTR_USERNAME); |
| ASSERT_TRUE(username_attr != NULL); |
| const StunUInt32Attribute* priority_attr = msg->GetUInt32(STUN_ATTR_PRIORITY); |
| ASSERT_TRUE(priority_attr != NULL); |
| EXPECT_EQ(kDefaultPrflxPriority, priority_attr->value()); |
| EXPECT_EQ("rfrag:lfrag", username_attr->GetString()); |
| EXPECT_TRUE(msg->GetByteString(STUN_ATTR_MESSAGE_INTEGRITY) != NULL); |
| EXPECT_TRUE(StunMessage::ValidateMessageIntegrity( |
| lport->last_stun_buf()->Data(), lport->last_stun_buf()->Length(), |
| "rpass")); |
| const StunUInt64Attribute* ice_controlling_attr = |
| msg->GetUInt64(STUN_ATTR_ICE_CONTROLLING); |
| ASSERT_TRUE(ice_controlling_attr != NULL); |
| EXPECT_EQ(lport->IceTiebreaker(), ice_controlling_attr->value()); |
| EXPECT_TRUE(msg->GetByteString(STUN_ATTR_ICE_CONTROLLED) == NULL); |
| EXPECT_TRUE(msg->GetByteString(STUN_ATTR_USE_CANDIDATE) != NULL); |
| EXPECT_TRUE(msg->GetUInt32(STUN_ATTR_FINGERPRINT) != NULL); |
| EXPECT_TRUE(StunMessage::ValidateFingerprint( |
| lport->last_stun_buf()->Data(), lport->last_stun_buf()->Length())); |
| |
| // Request should not include ping count. |
| ASSERT_TRUE(msg->GetUInt32(STUN_ATTR_RETRANSMIT_COUNT) == NULL); |
| |
| // Save a copy of the BINDING-REQUEST for use below. |
| talk_base::scoped_ptr<IceMessage> request(CopyStunMessage(msg)); |
| |
| // Respond with a BINDING-RESPONSE. |
| rport->SendBindingResponse(request.get(), lport->Candidates()[0].address()); |
| msg = rport->last_stun_msg(); |
| ASSERT_TRUE(msg != NULL); |
| EXPECT_EQ(STUN_BINDING_RESPONSE, msg->type()); |
| |
| |
| EXPECT_FALSE(msg->IsLegacy()); |
| const StunAddressAttribute* addr_attr = msg->GetAddress( |
| STUN_ATTR_XOR_MAPPED_ADDRESS); |
| ASSERT_TRUE(addr_attr != NULL); |
| EXPECT_EQ(lport->Candidates()[0].address(), addr_attr->GetAddress()); |
| EXPECT_TRUE(msg->GetByteString(STUN_ATTR_MESSAGE_INTEGRITY) != NULL); |
| EXPECT_TRUE(StunMessage::ValidateMessageIntegrity( |
| rport->last_stun_buf()->Data(), rport->last_stun_buf()->Length(), |
| "rpass")); |
| EXPECT_TRUE(msg->GetUInt32(STUN_ATTR_FINGERPRINT) != NULL); |
| EXPECT_TRUE(StunMessage::ValidateFingerprint( |
| lport->last_stun_buf()->Data(), lport->last_stun_buf()->Length())); |
| // No USERNAME or PRIORITY in ICE responses. |
| EXPECT_TRUE(msg->GetByteString(STUN_ATTR_USERNAME) == NULL); |
| EXPECT_TRUE(msg->GetByteString(STUN_ATTR_PRIORITY) == NULL); |
| EXPECT_TRUE(msg->GetByteString(STUN_ATTR_MAPPED_ADDRESS) == NULL); |
| EXPECT_TRUE(msg->GetByteString(STUN_ATTR_ICE_CONTROLLING) == NULL); |
| EXPECT_TRUE(msg->GetByteString(STUN_ATTR_ICE_CONTROLLED) == NULL); |
| EXPECT_TRUE(msg->GetByteString(STUN_ATTR_USE_CANDIDATE) == NULL); |
| |
| // Response should not include ping count. |
| ASSERT_TRUE(msg->GetUInt32(STUN_ATTR_RETRANSMIT_COUNT) == NULL); |
| |
| // Respond with a BINDING-ERROR-RESPONSE. This wouldn't happen in real life, |
| // but we can do it here. |
| rport->SendBindingErrorResponse(request.get(), |
| lport->Candidates()[0].address(), |
| STUN_ERROR_SERVER_ERROR, |
| STUN_ERROR_REASON_SERVER_ERROR); |
| msg = rport->last_stun_msg(); |
| ASSERT_TRUE(msg != NULL); |
| EXPECT_EQ(STUN_BINDING_ERROR_RESPONSE, msg->type()); |
| EXPECT_FALSE(msg->IsLegacy()); |
| const StunErrorCodeAttribute* error_attr = msg->GetErrorCode(); |
| ASSERT_TRUE(error_attr != NULL); |
| EXPECT_EQ(STUN_ERROR_SERVER_ERROR, error_attr->code()); |
| EXPECT_EQ(std::string(STUN_ERROR_REASON_SERVER_ERROR), error_attr->reason()); |
| EXPECT_TRUE(msg->GetByteString(STUN_ATTR_MESSAGE_INTEGRITY) != NULL); |
| EXPECT_TRUE(StunMessage::ValidateMessageIntegrity( |
| rport->last_stun_buf()->Data(), rport->last_stun_buf()->Length(), |
| "rpass")); |
| EXPECT_TRUE(msg->GetUInt32(STUN_ATTR_FINGERPRINT) != NULL); |
| EXPECT_TRUE(StunMessage::ValidateFingerprint( |
| lport->last_stun_buf()->Data(), lport->last_stun_buf()->Length())); |
| // No USERNAME with ICE. |
| EXPECT_TRUE(msg->GetByteString(STUN_ATTR_USERNAME) == NULL); |
| EXPECT_TRUE(msg->GetByteString(STUN_ATTR_PRIORITY) == NULL); |
| |
| // Testing STUN binding requests from rport --> lport, having ICE_CONTROLLED |
| // and (incremented) RETRANSMIT_COUNT attributes. |
| rport->Reset(); |
| rport->set_send_retransmit_count_attribute(true); |
| rconn->Ping(0); |
| rconn->Ping(0); |
| rconn->Ping(0); |
| ASSERT_TRUE_WAIT(rport->last_stun_msg() != NULL, 1000); |
| msg = rport->last_stun_msg(); |
| EXPECT_EQ(STUN_BINDING_REQUEST, msg->type()); |
| const StunUInt64Attribute* ice_controlled_attr = |
| msg->GetUInt64(STUN_ATTR_ICE_CONTROLLED); |
| ASSERT_TRUE(ice_controlled_attr != NULL); |
| EXPECT_EQ(rport->IceTiebreaker(), ice_controlled_attr->value()); |
| EXPECT_TRUE(msg->GetByteString(STUN_ATTR_USE_CANDIDATE) == NULL); |
| |
| // Request should include ping count. |
| const StunUInt32Attribute* retransmit_attr = |
| msg->GetUInt32(STUN_ATTR_RETRANSMIT_COUNT); |
| ASSERT_TRUE(retransmit_attr != NULL); |
| EXPECT_EQ(2U, retransmit_attr->value()); |
| |
| // Respond with a BINDING-RESPONSE. |
| request.reset(CopyStunMessage(msg)); |
| lport->SendBindingResponse(request.get(), rport->Candidates()[0].address()); |
| msg = lport->last_stun_msg(); |
| |
| // Response should include same ping count. |
| retransmit_attr = msg->GetUInt32(STUN_ATTR_RETRANSMIT_COUNT); |
| ASSERT_TRUE(retransmit_attr != NULL); |
| EXPECT_EQ(2U, retransmit_attr->value()); |
| } |
| |
| TEST_F(PortTest, TestUseCandidateAttribute) { |
| talk_base::scoped_ptr<TestPort> lport( |
| CreateTestPort(kLocalAddr1, "lfrag", "lpass")); |
| talk_base::scoped_ptr<TestPort> rport( |
| CreateTestPort(kLocalAddr2, "rfrag", "rpass")); |
| lport->SetIceProtocolType(ICEPROTO_RFC5245); |
| lport->SetIceRole(cricket::ICEROLE_CONTROLLING); |
| lport->SetIceTiebreaker(kTiebreaker1); |
| rport->SetIceProtocolType(ICEPROTO_RFC5245); |
| rport->SetIceRole(cricket::ICEROLE_CONTROLLED); |
| rport->SetIceTiebreaker(kTiebreaker2); |
| |
| // Send a fake ping from lport to rport. |
| lport->PrepareAddress(); |
| rport->PrepareAddress(); |
| ASSERT_FALSE(rport->Candidates().empty()); |
| Connection* lconn = lport->CreateConnection( |
| rport->Candidates()[0], Port::ORIGIN_MESSAGE); |
| lconn->Ping(0); |
| ASSERT_TRUE_WAIT(lport->last_stun_msg() != NULL, 1000); |
| IceMessage* msg = lport->last_stun_msg(); |
| const StunUInt64Attribute* ice_controlling_attr = |
| msg->GetUInt64(STUN_ATTR_ICE_CONTROLLING); |
| ASSERT_TRUE(ice_controlling_attr != NULL); |
| const StunByteStringAttribute* use_candidate_attr = msg->GetByteString( |
| STUN_ATTR_USE_CANDIDATE); |
| ASSERT_TRUE(use_candidate_attr != NULL); |
| } |
| |
| // Test handling STUN messages in GICE format. |
| TEST_F(PortTest, TestHandleStunMessageAsGice) { |
| // Our port will act as the "remote" port. |
| talk_base::scoped_ptr<TestPort> port( |
| CreateTestPort(kLocalAddr2, "rfrag", "rpass")); |
| port->SetIceProtocolType(ICEPROTO_GOOGLE); |
| |
| talk_base::scoped_ptr<IceMessage> in_msg, out_msg; |
| talk_base::scoped_ptr<ByteBuffer> buf(new ByteBuffer()); |
| talk_base::SocketAddress addr(kLocalAddr1); |
| std::string username; |
| |
| // BINDING-REQUEST from local to remote with valid GICE username and no M-I. |
| in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_REQUEST, |
| "rfraglfrag")); |
| WriteStunMessage(in_msg.get(), buf.get()); |
| EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, |
| out_msg.accept(), &username)); |
| EXPECT_TRUE(out_msg.get() != NULL); // Succeeds, since this is GICE. |
| EXPECT_EQ("lfrag", username); |
| |
| // Add M-I; should be ignored and rest of message parsed normally. |
| in_msg->AddMessageIntegrity("password"); |
| WriteStunMessage(in_msg.get(), buf.get()); |
| EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, |
| out_msg.accept(), &username)); |
| EXPECT_TRUE(out_msg.get() != NULL); |
| EXPECT_EQ("lfrag", username); |
| |
| // BINDING-RESPONSE with username, as done in GICE. Should succeed. |
| in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_RESPONSE, |
| "rfraglfrag")); |
| in_msg->AddAttribute( |
| new StunAddressAttribute(STUN_ATTR_MAPPED_ADDRESS, kLocalAddr2)); |
| WriteStunMessage(in_msg.get(), buf.get()); |
| EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, |
| out_msg.accept(), &username)); |
| EXPECT_TRUE(out_msg.get() != NULL); |
| EXPECT_EQ("", username); |
| |
| // BINDING-RESPONSE without username. Should be tolerated as well. |
| in_msg.reset(CreateStunMessage(STUN_BINDING_RESPONSE)); |
| in_msg->AddAttribute( |
| new StunAddressAttribute(STUN_ATTR_MAPPED_ADDRESS, kLocalAddr2)); |
| WriteStunMessage(in_msg.get(), buf.get()); |
| EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, |
| out_msg.accept(), &username)); |
| EXPECT_TRUE(out_msg.get() != NULL); |
| EXPECT_EQ("", username); |
| |
| // BINDING-ERROR-RESPONSE with username and error code. |
| in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_ERROR_RESPONSE, |
| "rfraglfrag")); |
| in_msg->AddAttribute(new StunErrorCodeAttribute(STUN_ATTR_ERROR_CODE, |
| STUN_ERROR_SERVER_ERROR_AS_GICE, STUN_ERROR_REASON_SERVER_ERROR)); |
| WriteStunMessage(in_msg.get(), buf.get()); |
| EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, |
| out_msg.accept(), &username)); |
| ASSERT_TRUE(out_msg.get() != NULL); |
| EXPECT_EQ("", username); |
| ASSERT_TRUE(out_msg->GetErrorCode() != NULL); |
| // GetStunMessage doesn't unmunge the GICE error code (happens downstream). |
| EXPECT_EQ(STUN_ERROR_SERVER_ERROR_AS_GICE, out_msg->GetErrorCode()->code()); |
| EXPECT_EQ(std::string(STUN_ERROR_REASON_SERVER_ERROR), |
| out_msg->GetErrorCode()->reason()); |
| } |
| |
| // Test handling STUN messages in ICE format. |
| TEST_F(PortTest, TestHandleStunMessageAsIce) { |
| // Our port will act as the "remote" port. |
| talk_base::scoped_ptr<TestPort> port( |
| CreateTestPort(kLocalAddr2, "rfrag", "rpass")); |
| port->SetIceProtocolType(ICEPROTO_RFC5245); |
| |
| talk_base::scoped_ptr<IceMessage> in_msg, out_msg; |
| talk_base::scoped_ptr<ByteBuffer> buf(new ByteBuffer()); |
| talk_base::SocketAddress addr(kLocalAddr1); |
| std::string username; |
| |
| // BINDING-REQUEST from local to remote with valid ICE username, |
| // MESSAGE-INTEGRITY, and FINGERPRINT. |
| in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_REQUEST, |
| "rfrag:lfrag")); |
| in_msg->AddMessageIntegrity("rpass"); |
| in_msg->AddFingerprint(); |
| WriteStunMessage(in_msg.get(), buf.get()); |
| EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, |
| out_msg.accept(), &username)); |
| EXPECT_TRUE(out_msg.get() != NULL); |
| EXPECT_EQ("lfrag", username); |
| |
| // BINDING-RESPONSE without username, with MESSAGE-INTEGRITY and FINGERPRINT. |
| in_msg.reset(CreateStunMessage(STUN_BINDING_RESPONSE)); |
| in_msg->AddAttribute( |
| new StunXorAddressAttribute(STUN_ATTR_XOR_MAPPED_ADDRESS, kLocalAddr2)); |
| in_msg->AddMessageIntegrity("rpass"); |
| in_msg->AddFingerprint(); |
| WriteStunMessage(in_msg.get(), buf.get()); |
| EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, |
| out_msg.accept(), &username)); |
| EXPECT_TRUE(out_msg.get() != NULL); |
| EXPECT_EQ("", username); |
| |
| // BINDING-ERROR-RESPONSE without username, with error, M-I, and FINGERPRINT. |
| in_msg.reset(CreateStunMessage(STUN_BINDING_ERROR_RESPONSE)); |
| in_msg->AddAttribute(new StunErrorCodeAttribute(STUN_ATTR_ERROR_CODE, |
| STUN_ERROR_SERVER_ERROR, STUN_ERROR_REASON_SERVER_ERROR)); |
| in_msg->AddFingerprint(); |
| WriteStunMessage(in_msg.get(), buf.get()); |
| EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, |
| out_msg.accept(), &username)); |
| EXPECT_TRUE(out_msg.get() != NULL); |
| EXPECT_EQ("", username); |
| ASSERT_TRUE(out_msg->GetErrorCode() != NULL); |
| EXPECT_EQ(STUN_ERROR_SERVER_ERROR, out_msg->GetErrorCode()->code()); |
| EXPECT_EQ(std::string(STUN_ERROR_REASON_SERVER_ERROR), |
| out_msg->GetErrorCode()->reason()); |
| } |
| |
| // Tests handling of GICE binding requests with missing or incorrect usernames. |
| TEST_F(PortTest, TestHandleStunMessageAsGiceBadUsername) { |
| talk_base::scoped_ptr<TestPort> port( |
| CreateTestPort(kLocalAddr2, "rfrag", "rpass")); |
| port->SetIceProtocolType(ICEPROTO_GOOGLE); |
| |
| talk_base::scoped_ptr<IceMessage> in_msg, out_msg; |
| talk_base::scoped_ptr<ByteBuffer> buf(new ByteBuffer()); |
| talk_base::SocketAddress addr(kLocalAddr1); |
| std::string username; |
| |
| // BINDING-REQUEST with no username. |
| in_msg.reset(CreateStunMessage(STUN_BINDING_REQUEST)); |
| WriteStunMessage(in_msg.get(), buf.get()); |
| EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, |
| out_msg.accept(), &username)); |
| EXPECT_TRUE(out_msg.get() == NULL); |
| EXPECT_EQ("", username); |
| EXPECT_EQ(STUN_ERROR_BAD_REQUEST_AS_GICE, port->last_stun_error_code()); |
| |
| // BINDING-REQUEST with empty username. |
| in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_REQUEST, "")); |
| WriteStunMessage(in_msg.get(), buf.get()); |
| EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, |
| out_msg.accept(), &username)); |
| EXPECT_TRUE(out_msg.get() == NULL); |
| EXPECT_EQ("", username); |
| EXPECT_EQ(STUN_ERROR_UNAUTHORIZED_AS_GICE, port->last_stun_error_code()); |
| |
| // BINDING-REQUEST with too-short username. |
| in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_REQUEST, "lfra")); |
| WriteStunMessage(in_msg.get(), buf.get()); |
| EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, |
| out_msg.accept(), &username)); |
| EXPECT_TRUE(out_msg.get() == NULL); |
| EXPECT_EQ("", username); |
| EXPECT_EQ(STUN_ERROR_UNAUTHORIZED_AS_GICE, port->last_stun_error_code()); |
| |
| // BINDING-REQUEST with reversed username. |
| in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_REQUEST, |
| "lfragrfrag")); |
| WriteStunMessage(in_msg.get(), buf.get()); |
| EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, |
| out_msg.accept(), &username)); |
| EXPECT_TRUE(out_msg.get() == NULL); |
| EXPECT_EQ("", username); |
| EXPECT_EQ(STUN_ERROR_UNAUTHORIZED_AS_GICE, port->last_stun_error_code()); |
| |
| // BINDING-REQUEST with garbage username. |
| in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_REQUEST, |
| "abcdefgh")); |
| WriteStunMessage(in_msg.get(), buf.get()); |
| EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, |
| out_msg.accept(), &username)); |
| EXPECT_TRUE(out_msg.get() == NULL); |
| EXPECT_EQ("", username); |
| EXPECT_EQ(STUN_ERROR_UNAUTHORIZED_AS_GICE, port->last_stun_error_code()); |
| } |
| |
| // Tests handling of ICE binding requests with missing or incorrect usernames. |
| TEST_F(PortTest, TestHandleStunMessageAsIceBadUsername) { |
| talk_base::scoped_ptr<TestPort> port( |
| CreateTestPort(kLocalAddr2, "rfrag", "rpass")); |
| port->SetIceProtocolType(ICEPROTO_RFC5245); |
| |
| talk_base::scoped_ptr<IceMessage> in_msg, out_msg; |
| talk_base::scoped_ptr<ByteBuffer> buf(new ByteBuffer()); |
| talk_base::SocketAddress addr(kLocalAddr1); |
| std::string username; |
| |
| // BINDING-REQUEST with no username. |
| in_msg.reset(CreateStunMessage(STUN_BINDING_REQUEST)); |
| in_msg->AddMessageIntegrity("rpass"); |
| in_msg->AddFingerprint(); |
| WriteStunMessage(in_msg.get(), buf.get()); |
| EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, |
| out_msg.accept(), &username)); |
| EXPECT_TRUE(out_msg.get() == NULL); |
| EXPECT_EQ("", username); |
| EXPECT_EQ(STUN_ERROR_BAD_REQUEST, port->last_stun_error_code()); |
| |
| // BINDING-REQUEST with empty username. |
| in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_REQUEST, "")); |
| in_msg->AddMessageIntegrity("rpass"); |
| in_msg->AddFingerprint(); |
| WriteStunMessage(in_msg.get(), buf.get()); |
| EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, |
| out_msg.accept(), &username)); |
| EXPECT_TRUE(out_msg.get() == NULL); |
| EXPECT_EQ("", username); |
| EXPECT_EQ(STUN_ERROR_UNAUTHORIZED, port->last_stun_error_code()); |
| |
| // BINDING-REQUEST with too-short username. |
| in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_REQUEST, "rfra")); |
| in_msg->AddMessageIntegrity("rpass"); |
| in_msg->AddFingerprint(); |
| WriteStunMessage(in_msg.get(), buf.get()); |
| EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, |
| out_msg.accept(), &username)); |
| EXPECT_TRUE(out_msg.get() == NULL); |
| EXPECT_EQ("", username); |
| EXPECT_EQ(STUN_ERROR_UNAUTHORIZED, port->last_stun_error_code()); |
| |
| // BINDING-REQUEST with reversed username. |
| in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_REQUEST, |
| "lfrag:rfrag")); |
| in_msg->AddMessageIntegrity("rpass"); |
| in_msg->AddFingerprint(); |
| WriteStunMessage(in_msg.get(), buf.get()); |
| EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, |
| out_msg.accept(), &username)); |
| EXPECT_TRUE(out_msg.get() == NULL); |
| EXPECT_EQ("", username); |
| EXPECT_EQ(STUN_ERROR_UNAUTHORIZED, port->last_stun_error_code()); |
| |
| // BINDING-REQUEST with garbage username. |
| in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_REQUEST, |
| "abcd:efgh")); |
| in_msg->AddMessageIntegrity("rpass"); |
| in_msg->AddFingerprint(); |
| WriteStunMessage(in_msg.get(), buf.get()); |
| EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, |
| out_msg.accept(), &username)); |
| EXPECT_TRUE(out_msg.get() == NULL); |
| EXPECT_EQ("", username); |
| EXPECT_EQ(STUN_ERROR_UNAUTHORIZED, port->last_stun_error_code()); |
| } |
| |
| // Test handling STUN messages (as ICE) with missing or malformed M-I. |
| TEST_F(PortTest, TestHandleStunMessageAsIceBadMessageIntegrity) { |
| // Our port will act as the "remote" port. |
| talk_base::scoped_ptr<TestPort> port( |
| CreateTestPort(kLocalAddr2, "rfrag", "rpass")); |
| port->SetIceProtocolType(ICEPROTO_RFC5245); |
| |
| talk_base::scoped_ptr<IceMessage> in_msg, out_msg; |
| talk_base::scoped_ptr<ByteBuffer> buf(new ByteBuffer()); |
| talk_base::SocketAddress addr(kLocalAddr1); |
| std::string username; |
| |
| // BINDING-REQUEST from local to remote with valid ICE username and |
| // FINGERPRINT, but no MESSAGE-INTEGRITY. |
| in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_REQUEST, |
| "rfrag:lfrag")); |
| in_msg->AddFingerprint(); |
| WriteStunMessage(in_msg.get(), buf.get()); |
| EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, |
| out_msg.accept(), &username)); |
| EXPECT_TRUE(out_msg.get() == NULL); |
| EXPECT_EQ("", username); |
| EXPECT_EQ(STUN_ERROR_BAD_REQUEST, port->last_stun_error_code()); |
| |
| // BINDING-REQUEST from local to remote with valid ICE username and |
| // FINGERPRINT, but invalid MESSAGE-INTEGRITY. |
| in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_REQUEST, |
| "rfrag:lfrag")); |
| in_msg->AddMessageIntegrity("invalid"); |
| in_msg->AddFingerprint(); |
| WriteStunMessage(in_msg.get(), buf.get()); |
| EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, |
| out_msg.accept(), &username)); |
| EXPECT_TRUE(out_msg.get() == NULL); |
| EXPECT_EQ("", username); |
| EXPECT_EQ(STUN_ERROR_UNAUTHORIZED, port->last_stun_error_code()); |
| |
| // TODO: BINDING-RESPONSES and BINDING-ERROR-RESPONSES are checked |
| // by the Connection, not the Port, since they require the remote username. |
| // Change this test to pass in data via Connection::OnReadPacket instead. |
| } |
| |
| // Test handling STUN messages (as ICE) with missing or malformed FINGERPRINT. |
| TEST_F(PortTest, TestHandleStunMessageAsIceBadFingerprint) { |
| // Our port will act as the "remote" port. |
| talk_base::scoped_ptr<TestPort> port( |
| CreateTestPort(kLocalAddr2, "rfrag", "rpass")); |
| port->SetIceProtocolType(ICEPROTO_RFC5245); |
| |
| talk_base::scoped_ptr<IceMessage> in_msg, out_msg; |
| talk_base::scoped_ptr<ByteBuffer> buf(new ByteBuffer()); |
| talk_base::SocketAddress addr(kLocalAddr1); |
| std::string username; |
| |
| // BINDING-REQUEST from local to remote with valid ICE username and |
| // MESSAGE-INTEGRITY, but no FINGERPRINT; GetStunMessage should fail. |
| in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_REQUEST, |
| "rfrag:lfrag")); |
| in_msg->AddMessageIntegrity("rpass"); |
| WriteStunMessage(in_msg.get(), buf.get()); |
| EXPECT_FALSE(port->GetStunMessage(buf->Data(), buf->Length(), addr, |
| out_msg.accept(), &username)); |
| EXPECT_EQ(0, port->last_stun_error_code()); |
| |
| // Now, add a fingerprint, but munge the message so it's not valid. |
| in_msg->AddFingerprint(); |
| in_msg->SetTransactionID("TESTTESTBADD"); |
| WriteStunMessage(in_msg.get(), buf.get()); |
| EXPECT_FALSE(port->GetStunMessage(buf->Data(), buf->Length(), addr, |
| out_msg.accept(), &username)); |
| EXPECT_EQ(0, port->last_stun_error_code()); |
| |
| // Valid BINDING-RESPONSE, except no FINGERPRINT. |
| in_msg.reset(CreateStunMessage(STUN_BINDING_RESPONSE)); |
| in_msg->AddAttribute( |
| new StunXorAddressAttribute(STUN_ATTR_XOR_MAPPED_ADDRESS, kLocalAddr2)); |
| in_msg->AddMessageIntegrity("rpass"); |
| WriteStunMessage(in_msg.get(), buf.get()); |
| EXPECT_FALSE(port->GetStunMessage(buf->Data(), buf->Length(), addr, |
| out_msg.accept(), &username)); |
| EXPECT_EQ(0, port->last_stun_error_code()); |
| |
| // Now, add a fingerprint, but munge the message so it's not valid. |
| in_msg->AddFingerprint(); |
| in_msg->SetTransactionID("TESTTESTBADD"); |
| WriteStunMessage(in_msg.get(), buf.get()); |
| EXPECT_FALSE(port->GetStunMessage(buf->Data(), buf->Length(), addr, |
| out_msg.accept(), &username)); |
| EXPECT_EQ(0, port->last_stun_error_code()); |
| |
| // Valid BINDING-ERROR-RESPONSE, except no FINGERPRINT. |
| in_msg.reset(CreateStunMessage(STUN_BINDING_ERROR_RESPONSE)); |
| in_msg->AddAttribute(new StunErrorCodeAttribute(STUN_ATTR_ERROR_CODE, |
| STUN_ERROR_SERVER_ERROR, STUN_ERROR_REASON_SERVER_ERROR)); |
| in_msg->AddMessageIntegrity("rpass"); |
| WriteStunMessage(in_msg.get(), buf.get()); |
| EXPECT_FALSE(port->GetStunMessage(buf->Data(), buf->Length(), addr, |
| out_msg.accept(), &username)); |
| EXPECT_EQ(0, port->last_stun_error_code()); |
| |
| // Now, add a fingerprint, but munge the message so it's not valid. |
| in_msg->AddFingerprint(); |
| in_msg->SetTransactionID("TESTTESTBADD"); |
| WriteStunMessage(in_msg.get(), buf.get()); |
| EXPECT_FALSE(port->GetStunMessage(buf->Data(), buf->Length(), addr, |
| out_msg.accept(), &username)); |
| EXPECT_EQ(0, port->last_stun_error_code()); |
| } |
| |
| // Test handling of STUN binding indication messages (as ICE). STUN binding |
| // indications are allowed only to the connection which is in read mode. |
| TEST_F(PortTest, TestHandleStunBindingIndication) { |
| talk_base::scoped_ptr<TestPort> lport( |
| CreateTestPort(kLocalAddr2, "lfrag", "lpass")); |
| lport->SetIceProtocolType(ICEPROTO_RFC5245); |
| lport->SetIceRole(cricket::ICEROLE_CONTROLLING); |
| lport->SetIceTiebreaker(kTiebreaker1); |
| |
| // Verifying encoding and decoding STUN indication message. |
| talk_base::scoped_ptr<IceMessage> in_msg, out_msg; |
| talk_base::scoped_ptr<ByteBuffer> buf(new ByteBuffer()); |
| talk_base::SocketAddress addr(kLocalAddr1); |
| std::string username; |
| |
| in_msg.reset(CreateStunMessage(STUN_BINDING_INDICATION)); |
| in_msg->AddFingerprint(); |
| WriteStunMessage(in_msg.get(), buf.get()); |
| EXPECT_TRUE(lport->GetStunMessage(buf->Data(), buf->Length(), addr, |
| out_msg.accept(), &username)); |
| EXPECT_TRUE(out_msg.get() != NULL); |
| EXPECT_EQ(out_msg->type(), STUN_BINDING_INDICATION); |
| EXPECT_EQ("", username); |
| |
| // Verify connection can handle STUN indication and updates |
| // last_ping_received. |
| talk_base::scoped_ptr<TestPort> rport( |
| CreateTestPort(kLocalAddr2, "rfrag", "rpass")); |
| rport->SetIceProtocolType(ICEPROTO_RFC5245); |
| rport->SetIceRole(cricket::ICEROLE_CONTROLLED); |
| rport->SetIceTiebreaker(kTiebreaker2); |
| |
| lport->PrepareAddress(); |
| rport->PrepareAddress(); |
| ASSERT_FALSE(lport->Candidates().empty()); |
| ASSERT_FALSE(rport->Candidates().empty()); |
| |
| Connection* lconn = lport->CreateConnection(rport->Candidates()[0], |
| Port::ORIGIN_MESSAGE); |
| Connection* rconn = rport->CreateConnection(lport->Candidates()[0], |
| Port::ORIGIN_MESSAGE); |
| rconn->Ping(0); |
| |
| ASSERT_TRUE_WAIT(rport->last_stun_msg() != NULL, 1000); |
| IceMessage* msg = rport->last_stun_msg(); |
| EXPECT_EQ(STUN_BINDING_REQUEST, msg->type()); |
| // Send rport binding request to lport. |
| lconn->OnReadPacket(rport->last_stun_buf()->Data(), |
| rport->last_stun_buf()->Length()); |
| ASSERT_TRUE_WAIT(lport->last_stun_msg() != NULL, 1000); |
| EXPECT_EQ(STUN_BINDING_RESPONSE, lport->last_stun_msg()->type()); |
| uint32 last_ping_received1 = lconn->last_ping_received(); |
| |
| // Adding a delay of 100ms. |
| talk_base::Thread::Current()->ProcessMessages(100); |
| // Pinging lconn using stun indication message. |
| lconn->OnReadPacket(buf->Data(), buf->Length()); |
| uint32 last_ping_received2 = lconn->last_ping_received(); |
| EXPECT_GT(last_ping_received2, last_ping_received1); |
| } |
| |
| TEST_F(PortTest, TestComputeCandidatePriority) { |
| talk_base::scoped_ptr<TestPort> port( |
| CreateTestPort(kLocalAddr1, "name", "pass")); |
| port->set_type_preference(90); |
| port->set_component(177); |
| port->AddCandidateAddress(SocketAddress("192.168.1.4", 1234)); |
| port->AddCandidateAddress(SocketAddress("2001:db8::1234", 1234)); |
| port->AddCandidateAddress(SocketAddress("fc12:3456::1234", 1234)); |
| port->AddCandidateAddress(SocketAddress("::ffff:192.168.1.4", 1234)); |
| port->AddCandidateAddress(SocketAddress("::192.168.1.4", 1234)); |
| port->AddCandidateAddress(SocketAddress("2002::1234:5678", 1234)); |
| port->AddCandidateAddress(SocketAddress("2001::1234:5678", 1234)); |
| port->AddCandidateAddress(SocketAddress("fecf::1234:5678", 1234)); |
| port->AddCandidateAddress(SocketAddress("3ffe::1234:5678", 1234)); |
| // These should all be: |
| // (90 << 24) | ([rfc3484 pref value] << 8) | (256 - 177) |
| uint32 expected_priority_v4 = 1509957199U; |
| uint32 expected_priority_v6 = 1509959759U; |
| uint32 expected_priority_ula = 1509962319U; |
| uint32 expected_priority_v4mapped = expected_priority_v4; |
| uint32 expected_priority_v4compat = 1509949775U; |
| uint32 expected_priority_6to4 = 1509954639U; |
| uint32 expected_priority_teredo = 1509952079U; |
| uint32 expected_priority_sitelocal = 1509949775U; |
| uint32 expected_priority_6bone = 1509949775U; |
| ASSERT_EQ(expected_priority_v4, port->Candidates()[0].priority()); |
| ASSERT_EQ(expected_priority_v6, port->Candidates()[1].priority()); |
| ASSERT_EQ(expected_priority_ula, port->Candidates()[2].priority()); |
| ASSERT_EQ(expected_priority_v4mapped, port->Candidates()[3].priority()); |
| ASSERT_EQ(expected_priority_v4compat, port->Candidates()[4].priority()); |
| ASSERT_EQ(expected_priority_6to4, port->Candidates()[5].priority()); |
| ASSERT_EQ(expected_priority_teredo, port->Candidates()[6].priority()); |
| ASSERT_EQ(expected_priority_sitelocal, port->Candidates()[7].priority()); |
| ASSERT_EQ(expected_priority_6bone, port->Candidates()[8].priority()); |
| } |
| |
| TEST_F(PortTest, TestPortProxyProperties) { |
| talk_base::scoped_ptr<TestPort> port( |
| CreateTestPort(kLocalAddr1, "name", "pass")); |
| port->SetIceRole(cricket::ICEROLE_CONTROLLING); |
| port->SetIceTiebreaker(kTiebreaker1); |
| |
| // Create a proxy port. |
| talk_base::scoped_ptr<PortProxy> proxy(new PortProxy()); |
| proxy->set_impl(port.get()); |
| EXPECT_EQ(port->Type(), proxy->Type()); |
| EXPECT_EQ(port->Network(), proxy->Network()); |
| EXPECT_EQ(port->GetIceRole(), proxy->GetIceRole()); |
| EXPECT_EQ(port->IceTiebreaker(), proxy->IceTiebreaker()); |
| } |
| |
| // In the case of shared socket, one port may be shared by local and stun. |
| // Test that candidates with different types will have different foundation. |
| TEST_F(PortTest, TestFoundation) { |
| talk_base::scoped_ptr<TestPort> testport( |
| CreateTestPort(kLocalAddr1, "name", "pass")); |
| testport->AddCandidateAddress(kLocalAddr1, kLocalAddr1, |
| LOCAL_PORT_TYPE, |
| cricket::ICE_TYPE_PREFERENCE_HOST, false); |
| testport->AddCandidateAddress(kLocalAddr2, kLocalAddr1, |
| STUN_PORT_TYPE, |
| cricket::ICE_TYPE_PREFERENCE_SRFLX, true); |
| EXPECT_NE(testport->Candidates()[0].foundation(), |
| testport->Candidates()[1].foundation()); |
| } |
| |
| // This test verifies the foundation of different types of ICE candidates. |
| TEST_F(PortTest, TestCandidateFoundation) { |
| talk_base::scoped_ptr<talk_base::NATServer> nat_server( |
| CreateNatServer(kNatAddr1, NAT_OPEN_CONE)); |
| talk_base::scoped_ptr<UDPPort> udpport1(CreateUdpPort(kLocalAddr1)); |
| udpport1->PrepareAddress(); |
| talk_base::scoped_ptr<UDPPort> udpport2(CreateUdpPort(kLocalAddr1)); |
| udpport2->PrepareAddress(); |
| EXPECT_EQ(udpport1->Candidates()[0].foundation(), |
| udpport2->Candidates()[0].foundation()); |
| talk_base::scoped_ptr<TCPPort> tcpport1(CreateTcpPort(kLocalAddr1)); |
| tcpport1->PrepareAddress(); |
| talk_base::scoped_ptr<TCPPort> tcpport2(CreateTcpPort(kLocalAddr1)); |
| tcpport2->PrepareAddress(); |
| EXPECT_EQ(tcpport1->Candidates()[0].foundation(), |
| tcpport2->Candidates()[0].foundation()); |
| talk_base::scoped_ptr<Port> stunport( |
| CreateStunPort(kLocalAddr1, nat_socket_factory1())); |
| stunport->PrepareAddress(); |
| ASSERT_EQ_WAIT(1U, stunport->Candidates().size(), kTimeout); |
| EXPECT_NE(tcpport1->Candidates()[0].foundation(), |
| stunport->Candidates()[0].foundation()); |
| EXPECT_NE(tcpport2->Candidates()[0].foundation(), |
| stunport->Candidates()[0].foundation()); |
| EXPECT_NE(udpport1->Candidates()[0].foundation(), |
| stunport->Candidates()[0].foundation()); |
| EXPECT_NE(udpport2->Candidates()[0].foundation(), |
| stunport->Candidates()[0].foundation()); |
| // Verify GTURN candidate foundation. |
| talk_base::scoped_ptr<RelayPort> relayport( |
| CreateGturnPort(kLocalAddr1)); |
| relayport->AddServerAddress( |
| cricket::ProtocolAddress(kRelayUdpIntAddr, cricket::PROTO_UDP)); |
| relayport->PrepareAddress(); |
| ASSERT_EQ_WAIT(1U, relayport->Candidates().size(), kTimeout); |
| EXPECT_NE(udpport1->Candidates()[0].foundation(), |
| relayport->Candidates()[0].foundation()); |
| EXPECT_NE(udpport2->Candidates()[0].foundation(), |
| relayport->Candidates()[0].foundation()); |
| // Verifying TURN candidate foundation. |
| talk_base::scoped_ptr<Port> turnport(CreateTurnPort( |
| kLocalAddr1, nat_socket_factory1(), PROTO_UDP, PROTO_UDP)); |
| turnport->PrepareAddress(); |
| ASSERT_EQ_WAIT(1U, turnport->Candidates().size(), kTimeout); |
| EXPECT_NE(udpport1->Candidates()[0].foundation(), |
| turnport->Candidates()[0].foundation()); |
| EXPECT_NE(udpport2->Candidates()[0].foundation(), |
| turnport->Candidates()[0].foundation()); |
| EXPECT_NE(stunport->Candidates()[0].foundation(), |
| turnport->Candidates()[0].foundation()); |
| } |
| |
| // This test verifies the related addresses of different types of |
| // ICE candiates. |
| TEST_F(PortTest, TestCandidateRelatedAddress) { |
| talk_base::scoped_ptr<talk_base::NATServer> nat_server( |
| CreateNatServer(kNatAddr1, NAT_OPEN_CONE)); |
| talk_base::scoped_ptr<UDPPort> udpport(CreateUdpPort(kLocalAddr1)); |
| udpport->PrepareAddress(); |
| // For UDPPort, related address will be empty. |
| EXPECT_TRUE(udpport->Candidates()[0].related_address().IsNil()); |
| // Testing related address for stun candidates. |
| // For stun candidate related address must be equal to the base |
| // socket address. |
| talk_base::scoped_ptr<StunPort> stunport( |
| CreateStunPort(kLocalAddr1, nat_socket_factory1())); |
| stunport->PrepareAddress(); |
| ASSERT_EQ_WAIT(1U, stunport->Candidates().size(), kTimeout); |
| // Check STUN candidate address. |
| EXPECT_EQ(stunport->Candidates()[0].address().ipaddr(), |
| kNatAddr1.ipaddr()); |
| // Check STUN candidate related address. |
| EXPECT_EQ(stunport->Candidates()[0].related_address(), |
| stunport->GetLocalAddress()); |
| // Verifying the related address for the GTURN candidates. |
| // NOTE: In case of GTURN related address will be equal to the mapped |
| // address, but address(mapped) will not be XOR. |
| talk_base::scoped_ptr<RelayPort> relayport( |
| CreateGturnPort(kLocalAddr1)); |
| relayport->AddServerAddress( |
| cricket::ProtocolAddress(kRelayUdpIntAddr, cricket::PROTO_UDP)); |
| relayport->PrepareAddress(); |
| ASSERT_EQ_WAIT(1U, relayport->Candidates().size(), kTimeout); |
| // For Gturn related address is set to "0.0.0.0:0" |
| EXPECT_EQ(talk_base::SocketAddress(), |
| relayport->Candidates()[0].related_address()); |
| // Verifying the related address for TURN candidate. |
| // For TURN related address must be equal to the mapped address. |
| talk_base::scoped_ptr<Port> turnport(CreateTurnPort( |
| kLocalAddr1, nat_socket_factory1(), PROTO_UDP, PROTO_UDP)); |
| turnport->PrepareAddress(); |
| ASSERT_EQ_WAIT(1U, turnport->Candidates().size(), kTimeout); |
| EXPECT_EQ(kTurnUdpExtAddr.ipaddr(), |
| turnport->Candidates()[0].address().ipaddr()); |
| EXPECT_EQ(kNatAddr1.ipaddr(), |
| turnport->Candidates()[0].related_address().ipaddr()); |
| } |
| |
| // Test priority value overflow handling when preference is set to 3. |
| TEST_F(PortTest, TestCandidatePreference) { |
| cricket::Candidate cand1; |
| cand1.set_preference(3); |
| cricket::Candidate cand2; |
| cand2.set_preference(1); |
| EXPECT_TRUE(cand1.preference() > cand2.preference()); |
| } |
| |
| // Test the Connection priority is calculated correctly. |
| TEST_F(PortTest, TestConnectionPriority) { |
| talk_base::scoped_ptr<TestPort> lport( |
| CreateTestPort(kLocalAddr1, "lfrag", "lpass")); |
| lport->set_type_preference(cricket::ICE_TYPE_PREFERENCE_HOST); |
| talk_base::scoped_ptr<TestPort> rport( |
| CreateTestPort(kLocalAddr2, "rfrag", "rpass")); |
| rport->set_type_preference(cricket::ICE_TYPE_PREFERENCE_RELAY); |
| lport->set_component(123); |
| lport->AddCandidateAddress(SocketAddress("192.168.1.4", 1234)); |
| rport->set_component(23); |
| rport->AddCandidateAddress(SocketAddress("10.1.1.100", 1234)); |
| |
| EXPECT_EQ(0x7E001E85U, lport->Candidates()[0].priority()); |
| EXPECT_EQ(0x2001EE9U, rport->Candidates()[0].priority()); |
| |
| // RFC 5245 |
| // pair priority = 2^32*MIN(G,D) + 2*MAX(G,D) + (G>D?1:0) |
| lport->SetIceRole(cricket::ICEROLE_CONTROLLING); |
| rport->SetIceRole(cricket::ICEROLE_CONTROLLED); |
| Connection* lconn = lport->CreateConnection( |
| rport->Candidates()[0], Port::ORIGIN_MESSAGE); |
| #if defined(WIN32) |
| EXPECT_EQ(0x2001EE9FC003D0BU, lconn->priority()); |
| #else |
| EXPECT_EQ(0x2001EE9FC003D0BLLU, lconn->priority()); |
| #endif |
| |
| lport->SetIceRole(cricket::ICEROLE_CONTROLLED); |
| rport->SetIceRole(cricket::ICEROLE_CONTROLLING); |
| Connection* rconn = rport->CreateConnection( |
| lport->Candidates()[0], Port::ORIGIN_MESSAGE); |
| #if defined(WIN32) |
| EXPECT_EQ(0x2001EE9FC003D0AU, rconn->priority()); |
| #else |
| EXPECT_EQ(0x2001EE9FC003D0ALLU, rconn->priority()); |
| #endif |
| } |
| |
| TEST_F(PortTest, TestWritableState) { |
| UDPPort* port1 = CreateUdpPort(kLocalAddr1); |
| UDPPort* port2 = CreateUdpPort(kLocalAddr2); |
| |
| // Set up channels. |
| TestChannel ch1(port1, port2); |
| TestChannel ch2(port2, port1); |
| |
| // Acquire addresses. |
| ch1.Start(); |
| ch2.Start(); |
| ASSERT_EQ_WAIT(1, ch1.complete_count(), kTimeout); |
| ASSERT_EQ_WAIT(1, ch2.complete_count(), kTimeout); |
| |
| // Send a ping from src to dst. |
| ch1.CreateConnection(); |
| ASSERT_TRUE(ch1.conn() != NULL); |
| EXPECT_EQ(Connection::STATE_WRITE_INIT, ch1.conn()->write_state()); |
| EXPECT_TRUE_WAIT(ch1.conn()->connected(), kTimeout); // for TCP connect |
| ch1.Ping(); |
| WAIT(!ch2.remote_address().IsNil(), kTimeout); |
| |
| // Data should be unsendable until the connection is accepted. |
| char data[] = "abcd"; |
| int data_size = ARRAY_SIZE(data); |
| EXPECT_EQ(SOCKET_ERROR, |
| ch1.conn()->Send(data, data_size, talk_base::DSCP_NO_CHANGE)); |
| |
| // Accept the connection to return the binding response, transition to |
| // writable, and allow data to be sent. |
| ch2.AcceptConnection(); |
| EXPECT_EQ_WAIT(Connection::STATE_WRITABLE, ch1.conn()->write_state(), |
| kTimeout); |
| EXPECT_EQ(data_size, |
| ch1.conn()->Send(data, data_size, talk_base::DSCP_NO_CHANGE)); |
| |
| // Ask the connection to update state as if enough time has passed to lose |
| // full writability and 5 pings went unresponded to. We'll accomplish the |
| // latter by sending pings but not pumping messages. |
| for (uint32 i = 1; i <= CONNECTION_WRITE_CONNECT_FAILURES; ++i) { |
| ch1.Ping(i); |
| } |
| uint32 unreliable_timeout_delay = CONNECTION_WRITE_CONNECT_TIMEOUT + 500u; |
| ch1.conn()->UpdateState(unreliable_timeout_delay); |
| EXPECT_EQ(Connection::STATE_WRITE_UNRELIABLE, ch1.conn()->write_state()); |
| |
| // Data should be able to be sent in this state. |
| EXPECT_EQ(data_size, |
| ch1.conn()->Send(data, data_size, talk_base::DSCP_NO_CHANGE)); |
| |
| // And now allow the other side to process the pings and send binding |
| // responses. |
| EXPECT_EQ_WAIT(Connection::STATE_WRITABLE, ch1.conn()->write_state(), |
| kTimeout); |
| |
| // Wait long enough for a full timeout (past however long we've already |
| // waited). |
| for (uint32 i = 1; i <= CONNECTION_WRITE_CONNECT_FAILURES; ++i) { |
| ch1.Ping(unreliable_timeout_delay + i); |
| } |
| ch1.conn()->UpdateState(unreliable_timeout_delay + CONNECTION_WRITE_TIMEOUT + |
| 500u); |
| EXPECT_EQ(Connection::STATE_WRITE_TIMEOUT, ch1.conn()->write_state()); |
| |
| // Now that the connection has completely timed out, data send should fail. |
| EXPECT_EQ(SOCKET_ERROR, |
| ch1.conn()->Send(data, data_size, talk_base::DSCP_NO_CHANGE)); |
| |
| ch1.Stop(); |
| ch2.Stop(); |
| } |
| |
| TEST_F(PortTest, TestTimeoutForNeverWritable) { |
| UDPPort* port1 = CreateUdpPort(kLocalAddr1); |
| UDPPort* port2 = CreateUdpPort(kLocalAddr2); |
| |
| // Set up channels. |
| TestChannel ch1(port1, port2); |
| TestChannel ch2(port2, port1); |
| |
| // Acquire addresses. |
| ch1.Start(); |
| ch2.Start(); |
| |
| ch1.CreateConnection(); |
| ASSERT_TRUE(ch1.conn() != NULL); |
| EXPECT_EQ(Connection::STATE_WRITE_INIT, ch1.conn()->write_state()); |
| |
| // Attempt to go directly to write timeout. |
| for (uint32 i = 1; i <= CONNECTION_WRITE_CONNECT_FAILURES; ++i) { |
| ch1.Ping(i); |
| } |
| ch1.conn()->UpdateState(CONNECTION_WRITE_TIMEOUT + 500u); |
| EXPECT_EQ(Connection::STATE_WRITE_TIMEOUT, ch1.conn()->write_state()); |
| } |
| |
| // This test verifies the connection setup between ICEMODE_FULL |
| // and ICEMODE_LITE. |
| // In this test |ch1| behaves like FULL mode client and we have created |
| // port which responds to the ping message just like LITE client. |
| TEST_F(PortTest, TestIceLiteConnectivity) { |
| TestPort* ice_full_port = CreateTestPort( |
| kLocalAddr1, "lfrag", "lpass", cricket::ICEPROTO_RFC5245, |
| cricket::ICEROLE_CONTROLLING, kTiebreaker1); |
| |
| talk_base::scoped_ptr<TestPort> ice_lite_port(CreateTestPort( |
| kLocalAddr2, "rfrag", "rpass", cricket::ICEPROTO_RFC5245, |
| cricket::ICEROLE_CONTROLLED, kTiebreaker2)); |
| // Setup TestChannel. This behaves like FULL mode client. |
| TestChannel ch1(ice_full_port, ice_lite_port.get()); |
| ch1.SetIceMode(ICEMODE_FULL); |
| |
| // Start gathering candidates. |
| ch1.Start(); |
| ice_lite_port->PrepareAddress(); |
| |
| ASSERT_EQ_WAIT(1, ch1.complete_count(), kTimeout); |
| ASSERT_FALSE(ice_lite_port->Candidates().empty()); |
| |
| ch1.CreateConnection(); |
| ASSERT_TRUE(ch1.conn() != NULL); |
| EXPECT_EQ(Connection::STATE_WRITE_INIT, ch1.conn()->write_state()); |
| |
| // Send ping from full mode client. |
| // This ping must not have USE_CANDIDATE_ATTR. |
| ch1.Ping(); |
| |
| // Verify stun ping is without USE_CANDIDATE_ATTR. Getting message directly |
| // from port. |
| ASSERT_TRUE_WAIT(ice_full_port->last_stun_msg() != NULL, 1000); |
| IceMessage* msg = ice_full_port->last_stun_msg(); |
| EXPECT_TRUE(msg->GetByteString(STUN_ATTR_USE_CANDIDATE) == NULL); |
| |
| // Respond with a BINDING-RESPONSE from litemode client. |
| // NOTE: Ideally we should't create connection at this stage from lite |
| // port, as it should be done only after receiving ping with USE_CANDIDATE. |
| // But we need a connection to send a response message. |
| ice_lite_port->CreateConnection( |
| ice_full_port->Candidates()[0], cricket::Port::ORIGIN_MESSAGE); |
| talk_base::scoped_ptr<IceMessage> request(CopyStunMessage(msg)); |
| ice_lite_port->SendBindingResponse( |
| request.get(), ice_full_port->Candidates()[0].address()); |
| |
| // Feeding the respone message from litemode to the full mode connection. |
| ch1.conn()->OnReadPacket(ice_lite_port->last_stun_buf()->Data(), |
| ice_lite_port->last_stun_buf()->Length()); |
| // Verifying full mode connection becomes writable from the response. |
| EXPECT_EQ_WAIT(Connection::STATE_WRITABLE, ch1.conn()->write_state(), |
| kTimeout); |
| EXPECT_TRUE_WAIT(ch1.nominated(), kTimeout); |
| |
| // Clear existing stun messsages. Otherwise we will process old stun |
| // message right after we send ping. |
| ice_full_port->Reset(); |
| // Send ping. This must have USE_CANDIDATE_ATTR. |
| ch1.Ping(); |
| ASSERT_TRUE_WAIT(ice_full_port->last_stun_msg() != NULL, 1000); |
| msg = ice_full_port->last_stun_msg(); |
| EXPECT_TRUE(msg->GetByteString(STUN_ATTR_USE_CANDIDATE) != NULL); |
| ch1.Stop(); |
| } |