
A connection is currently deleted if it has not recevied anything for 30s. This patch adds a field trial that allows modifying this value if no pings are outstanding. The motivation for this is to experiment with pinging slower than once per 30s in order to save battery. Bug: webrtc:10282 Change-Id: I3272b9d68d44fc30379bd9a6c643db6b09766486 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/175005 Commit-Queue: Jonas Oreland <jonaso@webrtc.org> Reviewed-by: Harald Alvestrand <hta@webrtc.org> Cr-Commit-Position: refs/heads/master@{#31239}
3698 lines
143 KiB
C++
3698 lines
143 KiB
C++
/*
|
|
* Copyright 2004 The WebRTC Project Authors. All rights reserved.
|
|
*
|
|
* Use of this source code is governed by a BSD-style license
|
|
* that can be found in the LICENSE file in the root of the source
|
|
* tree. An additional intellectual property rights grant can be found
|
|
* in the file PATENTS. All contributing project authors may
|
|
* be found in the AUTHORS file in the root of the source tree.
|
|
*/
|
|
|
|
#include "p2p/base/port.h"
|
|
|
|
#include <string.h>
|
|
|
|
#include <cstdint>
|
|
#include <limits>
|
|
#include <list>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "absl/types/optional.h"
|
|
#include "api/candidate.h"
|
|
#include "api/packet_socket_factory.h"
|
|
#include "api/transport/stun.h"
|
|
#include "api/units/time_delta.h"
|
|
#include "p2p/base/basic_packet_socket_factory.h"
|
|
#include "p2p/base/p2p_constants.h"
|
|
#include "p2p/base/port_allocator.h"
|
|
#include "p2p/base/port_interface.h"
|
|
#include "p2p/base/stun_port.h"
|
|
#include "p2p/base/stun_server.h"
|
|
#include "p2p/base/tcp_port.h"
|
|
#include "p2p/base/test_stun_server.h"
|
|
#include "p2p/base/test_turn_server.h"
|
|
#include "p2p/base/transport_description.h"
|
|
#include "p2p/base/turn_port.h"
|
|
#include "p2p/base/turn_server.h"
|
|
#include "p2p/client/relay_port_factory_interface.h"
|
|
#include "rtc_base/arraysize.h"
|
|
#include "rtc_base/async_packet_socket.h"
|
|
#include "rtc_base/async_socket.h"
|
|
#include "rtc_base/buffer.h"
|
|
#include "rtc_base/byte_buffer.h"
|
|
#include "rtc_base/checks.h"
|
|
#include "rtc_base/dscp.h"
|
|
#include "rtc_base/fake_clock.h"
|
|
#include "rtc_base/gunit.h"
|
|
#include "rtc_base/helpers.h"
|
|
#include "rtc_base/logging.h"
|
|
#include "rtc_base/nat_server.h"
|
|
#include "rtc_base/nat_socket_factory.h"
|
|
#include "rtc_base/nat_types.h"
|
|
#include "rtc_base/net_helper.h"
|
|
#include "rtc_base/network.h"
|
|
#include "rtc_base/network/sent_packet.h"
|
|
#include "rtc_base/network_constants.h"
|
|
#include "rtc_base/proxy_info.h"
|
|
#include "rtc_base/socket.h"
|
|
#include "rtc_base/socket_adapters.h"
|
|
#include "rtc_base/socket_address.h"
|
|
#include "rtc_base/third_party/sigslot/sigslot.h"
|
|
#include "rtc_base/thread.h"
|
|
#include "rtc_base/time_utils.h"
|
|
#include "rtc_base/virtual_socket_server.h"
|
|
#include "test/field_trial.h"
|
|
#include "test/gtest.h"
|
|
|
|
using rtc::AsyncPacketSocket;
|
|
using rtc::ByteBufferReader;
|
|
using rtc::ByteBufferWriter;
|
|
using rtc::NAT_ADDR_RESTRICTED;
|
|
using rtc::NAT_OPEN_CONE;
|
|
using rtc::NAT_PORT_RESTRICTED;
|
|
using rtc::NAT_SYMMETRIC;
|
|
using rtc::NATType;
|
|
using rtc::PacketSocketFactory;
|
|
using rtc::Socket;
|
|
using rtc::SocketAddress;
|
|
|
|
namespace cricket {
|
|
namespace {
|
|
|
|
constexpr int kDefaultTimeout = 3000;
|
|
constexpr int kShortTimeout = 1000;
|
|
constexpr int kMaxExpectedSimulatedRtt = 200;
|
|
const SocketAddress kLocalAddr1("192.168.1.2", 0);
|
|
const SocketAddress kLocalAddr2("192.168.1.3", 0);
|
|
const SocketAddress kNatAddr1("77.77.77.77", rtc::NAT_SERVER_UDP_PORT);
|
|
const SocketAddress kNatAddr2("88.88.88.88", rtc::NAT_SERVER_UDP_PORT);
|
|
const SocketAddress kStunAddr("99.99.99.1", STUN_SERVER_PORT);
|
|
const SocketAddress kTurnUdpIntAddr("99.99.99.4", STUN_SERVER_PORT);
|
|
const SocketAddress kTurnTcpIntAddr("99.99.99.4", 5010);
|
|
const SocketAddress kTurnUdpExtAddr("99.99.99.5", 0);
|
|
const RelayCredentials kRelayCredentials("test", "test");
|
|
|
|
// TODO(?): Update these when RFC5245 is completely supported.
|
|
// Magic value of 30 is from RFC3484, for IPv4 addresses.
|
|
const uint32_t kDefaultPrflxPriority = ICE_TYPE_PREFERENCE_PRFLX << 24 |
|
|
30 << 8 |
|
|
(256 - ICE_CANDIDATE_COMPONENT_DEFAULT);
|
|
|
|
constexpr int kTiebreaker1 = 11111;
|
|
constexpr int kTiebreaker2 = 22222;
|
|
|
|
const char* data = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
|
|
|
|
Candidate GetCandidate(Port* port) {
|
|
RTC_DCHECK_GE(port->Candidates().size(), 1);
|
|
return port->Candidates()[0];
|
|
}
|
|
|
|
SocketAddress GetAddress(Port* port) {
|
|
return GetCandidate(port).address();
|
|
}
|
|
|
|
std::unique_ptr<IceMessage> CopyStunMessage(const IceMessage& src) {
|
|
auto dst = std::make_unique<IceMessage>();
|
|
ByteBufferWriter buf;
|
|
src.Write(&buf);
|
|
ByteBufferReader read_buf(buf);
|
|
dst->Read(&read_buf);
|
|
return dst;
|
|
}
|
|
|
|
bool WriteStunMessage(const StunMessage& msg, ByteBufferWriter* buf) {
|
|
buf->Resize(0); // clear out any existing buffer contents
|
|
return msg.Write(buf);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
// Stub port class for testing STUN generation and processing.
|
|
class TestPort : public Port {
|
|
public:
|
|
TestPort(rtc::Thread* thread,
|
|
const std::string& type,
|
|
rtc::PacketSocketFactory* factory,
|
|
rtc::Network* network,
|
|
uint16_t min_port,
|
|
uint16_t max_port,
|
|
const std::string& username_fragment,
|
|
const std::string& password)
|
|
: Port(thread,
|
|
type,
|
|
factory,
|
|
network,
|
|
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.
|
|
rtc::BufferT<uint8_t>* 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() {
|
|
// Act as if the socket was bound to the best IP on the network, to the
|
|
// first port in the allowed range.
|
|
rtc::SocketAddress addr(Network()->GetBestIP(), min_port());
|
|
AddAddress(addr, addr, rtc::SocketAddress(), "udp", "", "", Type(),
|
|
ICE_TYPE_PREFERENCE_HOST, 0, "", true);
|
|
}
|
|
|
|
virtual bool SupportsProtocol(const std::string& protocol) const {
|
|
return true;
|
|
}
|
|
|
|
virtual ProtocolType GetProtocol() const { return PROTO_UDP; }
|
|
|
|
// Exposed for testing candidate building.
|
|
void AddCandidateAddress(const rtc::SocketAddress& addr) {
|
|
AddAddress(addr, addr, rtc::SocketAddress(), "udp", "", "", Type(),
|
|
type_preference_, 0, "", false);
|
|
}
|
|
void AddCandidateAddress(const rtc::SocketAddress& addr,
|
|
const rtc::SocketAddress& base_address,
|
|
const std::string& type,
|
|
int type_preference,
|
|
bool final) {
|
|
AddAddress(addr, base_address, rtc::SocketAddress(), "udp", "", "", type,
|
|
type_preference, 0, "", final);
|
|
}
|
|
|
|
virtual Connection* CreateConnection(const Candidate& remote_candidate,
|
|
CandidateOrigin origin) {
|
|
Connection* conn = new ProxyConnection(this, 0, remote_candidate);
|
|
AddOrReplaceConnection(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 rtc::SocketAddress& addr,
|
|
const rtc::PacketOptions& options,
|
|
bool payload) {
|
|
if (!payload) {
|
|
auto msg = std::make_unique<IceMessage>();
|
|
auto buf = std::make_unique<rtc::BufferT<uint8_t>>(
|
|
static_cast<const char*>(data), size);
|
|
ByteBufferReader read_buf(*buf);
|
|
if (!msg->Read(&read_buf)) {
|
|
return -1;
|
|
}
|
|
last_stun_buf_ = std::move(buf);
|
|
last_stun_msg_ = std::move(msg);
|
|
}
|
|
return static_cast<int>(size);
|
|
}
|
|
virtual int SetOption(rtc::Socket::Option opt, int value) { return 0; }
|
|
virtual int GetOption(rtc::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:
|
|
void OnSentPacket(rtc::AsyncPacketSocket* socket,
|
|
const rtc::SentPacket& sent_packet) {
|
|
PortInterface::SignalSentPacket(sent_packet);
|
|
}
|
|
std::unique_ptr<rtc::BufferT<uint8_t>> last_stun_buf_;
|
|
std::unique_ptr<IceMessage> last_stun_msg_;
|
|
int type_preference_ = 0;
|
|
};
|
|
|
|
static void SendPingAndReceiveResponse(Connection* lconn,
|
|
TestPort* lport,
|
|
Connection* rconn,
|
|
TestPort* rport,
|
|
rtc::ScopedFakeClock* clock,
|
|
int64_t ms) {
|
|
lconn->Ping(rtc::TimeMillis());
|
|
ASSERT_TRUE_WAIT(lport->last_stun_msg(), kDefaultTimeout);
|
|
ASSERT_TRUE(lport->last_stun_buf());
|
|
rconn->OnReadPacket(lport->last_stun_buf()->data<char>(),
|
|
lport->last_stun_buf()->size(), /* packet_time_us */ -1);
|
|
clock->AdvanceTime(webrtc::TimeDelta::Millis(ms));
|
|
ASSERT_TRUE_WAIT(rport->last_stun_msg(), kDefaultTimeout);
|
|
ASSERT_TRUE(rport->last_stun_buf());
|
|
lconn->OnReadPacket(rport->last_stun_buf()->data<char>(),
|
|
rport->last_stun_buf()->size(), /* packet_time_us */ -1);
|
|
}
|
|
|
|
class TestChannel : public sigslot::has_slots<> {
|
|
public:
|
|
// Takes ownership of |p1| (but not |p2|).
|
|
explicit TestChannel(std::unique_ptr<Port> p1) : port_(std::move(p1)) {
|
|
port_->SignalPortComplete.connect(this, &TestChannel::OnPortComplete);
|
|
port_->SignalUnknownAddress.connect(this, &TestChannel::OnUnknownAddress);
|
|
port_->SignalDestroyed.connect(this, &TestChannel::OnSrcPortDestroyed);
|
|
}
|
|
|
|
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() { port_->PrepareAddress(); }
|
|
void CreateConnection(const Candidate& remote_candidate) {
|
|
conn_ = port_->CreateConnection(remote_candidate, 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);
|
|
conn_->SignalDestroyed.connect(this, &TestChannel::OnDestroyed);
|
|
conn_->SignalReadyToSend.connect(this,
|
|
&TestChannel::OnConnectionReadyToSend);
|
|
connection_ready_to_send_ = false;
|
|
}
|
|
void OnConnectionStateChange(Connection* conn) {
|
|
if (conn->write_state() == Connection::STATE_WRITABLE) {
|
|
conn->set_use_candidate_attr(true);
|
|
nominated_ = true;
|
|
}
|
|
}
|
|
void AcceptConnection(const Candidate& remote_candidate) {
|
|
ASSERT_TRUE(remote_request_.get() != NULL);
|
|
Candidate c = remote_candidate;
|
|
c.set_address(remote_address_);
|
|
conn_ = port_->CreateConnection(c, Port::ORIGIN_MESSAGE);
|
|
conn_->SignalDestroyed.connect(this, &TestChannel::OnDestroyed);
|
|
conn_->SendStunBindingResponse(remote_request_.get());
|
|
remote_request_.reset();
|
|
}
|
|
void Ping() { Ping(0); }
|
|
void Ping(int64_t now) { conn_->Ping(now); }
|
|
void Stop() {
|
|
if (conn_) {
|
|
conn_->Destroy();
|
|
}
|
|
}
|
|
|
|
void OnPortComplete(Port* port) { complete_count_++; }
|
|
void SetIceMode(IceMode ice_mode) { ice_mode_ = ice_mode; }
|
|
|
|
int SendData(const char* data, size_t len) {
|
|
rtc::PacketOptions options;
|
|
return conn_->Send(data, len, options);
|
|
}
|
|
|
|
void OnUnknownAddress(PortInterface* port,
|
|
const SocketAddress& addr,
|
|
ProtocolType proto,
|
|
IceMessage* msg,
|
|
const std::string& rf,
|
|
bool /*port_muxed*/) {
|
|
ASSERT_EQ(port_.get(), port);
|
|
if (!remote_address_.IsNil()) {
|
|
ASSERT_EQ(remote_address_, addr);
|
|
}
|
|
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);
|
|
EXPECT_TRUE(priority_attr != NULL);
|
|
EXPECT_TRUE(mi_attr != NULL);
|
|
EXPECT_TRUE(fingerprint_attr != NULL);
|
|
remote_address_ = addr;
|
|
remote_request_ = CopyStunMessage(*msg);
|
|
remote_frag_ = rf;
|
|
}
|
|
|
|
void OnDestroyed(Connection* conn) {
|
|
ASSERT_EQ(conn_, conn);
|
|
RTC_LOG(INFO) << "OnDestroy connection " << conn << " deleted";
|
|
conn_ = NULL;
|
|
// When the connection is destroyed, also clear these fields so future
|
|
// connections are possible.
|
|
remote_request_.reset();
|
|
remote_address_.Clear();
|
|
}
|
|
|
|
void OnSrcPortDestroyed(PortInterface* port) {
|
|
Port* destroyed_src = port_.release();
|
|
ASSERT_EQ(destroyed_src, port);
|
|
}
|
|
|
|
Port* port() { return port_.get(); }
|
|
|
|
bool nominated() const { return nominated_; }
|
|
|
|
void set_connection_ready_to_send(bool ready) {
|
|
connection_ready_to_send_ = ready;
|
|
}
|
|
bool connection_ready_to_send() const { return connection_ready_to_send_; }
|
|
|
|
private:
|
|
// ReadyToSend will only issue after a Connection recovers from ENOTCONN
|
|
void OnConnectionReadyToSend(Connection* conn) {
|
|
ASSERT_EQ(conn, conn_);
|
|
connection_ready_to_send_ = true;
|
|
}
|
|
|
|
IceMode ice_mode_ = ICEMODE_FULL;
|
|
std::unique_ptr<Port> port_;
|
|
|
|
int complete_count_ = 0;
|
|
Connection* conn_ = nullptr;
|
|
SocketAddress remote_address_;
|
|
std::unique_ptr<StunMessage> remote_request_;
|
|
std::string remote_frag_;
|
|
bool nominated_ = false;
|
|
bool connection_ready_to_send_ = false;
|
|
};
|
|
|
|
class PortTest : public ::testing::Test, public sigslot::has_slots<> {
|
|
public:
|
|
PortTest()
|
|
: ss_(new rtc::VirtualSocketServer()),
|
|
main_(ss_.get()),
|
|
socket_factory_(rtc::Thread::Current()),
|
|
nat_factory1_(ss_.get(), kNatAddr1, SocketAddress()),
|
|
nat_factory2_(ss_.get(), kNatAddr2, SocketAddress()),
|
|
nat_socket_factory1_(&nat_factory1_),
|
|
nat_socket_factory2_(&nat_factory2_),
|
|
stun_server_(TestStunServer::Create(&main_, kStunAddr)),
|
|
turn_server_(&main_, kTurnUdpIntAddr, kTurnUdpExtAddr),
|
|
username_(rtc::CreateRandomString(ICE_UFRAG_LENGTH)),
|
|
password_(rtc::CreateRandomString(ICE_PWD_LENGTH)),
|
|
role_conflict_(false),
|
|
ports_destroyed_(0) {}
|
|
|
|
protected:
|
|
std::string password() { return password_; }
|
|
|
|
void TestLocalToLocal() {
|
|
auto port1 = CreateUdpPort(kLocalAddr1);
|
|
port1->SetIceRole(cricket::ICEROLE_CONTROLLING);
|
|
auto port2 = CreateUdpPort(kLocalAddr2);
|
|
port2->SetIceRole(cricket::ICEROLE_CONTROLLED);
|
|
TestConnectivity("udp", std::move(port1), "udp", std::move(port2), true,
|
|
true, true, true);
|
|
}
|
|
void TestLocalToStun(NATType ntype) {
|
|
auto port1 = CreateUdpPort(kLocalAddr1);
|
|
port1->SetIceRole(cricket::ICEROLE_CONTROLLING);
|
|
nat_server2_ = CreateNatServer(kNatAddr2, ntype);
|
|
auto port2 = CreateStunPort(kLocalAddr2, &nat_socket_factory2_);
|
|
port2->SetIceRole(cricket::ICEROLE_CONTROLLED);
|
|
TestConnectivity("udp", std::move(port1), StunName(ntype), std::move(port2),
|
|
ntype == NAT_OPEN_CONE, true, ntype != NAT_SYMMETRIC,
|
|
true);
|
|
}
|
|
void TestLocalToRelay(ProtocolType proto) {
|
|
auto port1 = CreateUdpPort(kLocalAddr1);
|
|
port1->SetIceRole(cricket::ICEROLE_CONTROLLING);
|
|
auto port2 = CreateRelayPort(kLocalAddr2, proto, PROTO_UDP);
|
|
port2->SetIceRole(cricket::ICEROLE_CONTROLLED);
|
|
TestConnectivity("udp", std::move(port1), RelayName(proto),
|
|
std::move(port2), false, true, true, true);
|
|
}
|
|
void TestStunToLocal(NATType ntype) {
|
|
nat_server1_ = CreateNatServer(kNatAddr1, ntype);
|
|
auto port1 = CreateStunPort(kLocalAddr1, &nat_socket_factory1_);
|
|
port1->SetIceRole(cricket::ICEROLE_CONTROLLING);
|
|
auto port2 = CreateUdpPort(kLocalAddr2);
|
|
port2->SetIceRole(cricket::ICEROLE_CONTROLLED);
|
|
TestConnectivity(StunName(ntype), std::move(port1), "udp", std::move(port2),
|
|
true, ntype != NAT_SYMMETRIC, true, true);
|
|
}
|
|
void TestStunToStun(NATType ntype1, NATType ntype2) {
|
|
nat_server1_ = CreateNatServer(kNatAddr1, ntype1);
|
|
auto port1 = CreateStunPort(kLocalAddr1, &nat_socket_factory1_);
|
|
port1->SetIceRole(cricket::ICEROLE_CONTROLLING);
|
|
nat_server2_ = CreateNatServer(kNatAddr2, ntype2);
|
|
auto port2 = CreateStunPort(kLocalAddr2, &nat_socket_factory2_);
|
|
port2->SetIceRole(cricket::ICEROLE_CONTROLLED);
|
|
TestConnectivity(StunName(ntype1), std::move(port1), StunName(ntype2),
|
|
std::move(port2), ntype2 == NAT_OPEN_CONE,
|
|
ntype1 != NAT_SYMMETRIC, ntype2 != NAT_SYMMETRIC,
|
|
ntype1 + ntype2 < (NAT_PORT_RESTRICTED + NAT_SYMMETRIC));
|
|
}
|
|
void TestStunToRelay(NATType ntype, ProtocolType proto) {
|
|
nat_server1_ = CreateNatServer(kNatAddr1, ntype);
|
|
auto port1 = CreateStunPort(kLocalAddr1, &nat_socket_factory1_);
|
|
port1->SetIceRole(cricket::ICEROLE_CONTROLLING);
|
|
auto port2 = CreateRelayPort(kLocalAddr2, proto, PROTO_UDP);
|
|
port2->SetIceRole(cricket::ICEROLE_CONTROLLED);
|
|
TestConnectivity(StunName(ntype), std::move(port1), RelayName(proto),
|
|
std::move(port2), false, ntype != NAT_SYMMETRIC, true,
|
|
true);
|
|
}
|
|
void TestTcpToTcp() {
|
|
auto port1 = CreateTcpPort(kLocalAddr1);
|
|
port1->SetIceRole(cricket::ICEROLE_CONTROLLING);
|
|
auto port2 = CreateTcpPort(kLocalAddr2);
|
|
port2->SetIceRole(cricket::ICEROLE_CONTROLLED);
|
|
TestConnectivity("tcp", std::move(port1), "tcp", std::move(port2), true,
|
|
false, true, true);
|
|
}
|
|
void TestTcpToRelay(ProtocolType proto) {
|
|
auto port1 = CreateTcpPort(kLocalAddr1);
|
|
port1->SetIceRole(cricket::ICEROLE_CONTROLLING);
|
|
auto port2 = CreateRelayPort(kLocalAddr2, proto, PROTO_TCP);
|
|
port2->SetIceRole(cricket::ICEROLE_CONTROLLED);
|
|
TestConnectivity("tcp", std::move(port1), RelayName(proto),
|
|
std::move(port2), false, false, true, true);
|
|
}
|
|
void TestSslTcpToRelay(ProtocolType proto) {
|
|
auto port1 = CreateTcpPort(kLocalAddr1);
|
|
port1->SetIceRole(cricket::ICEROLE_CONTROLLING);
|
|
auto port2 = CreateRelayPort(kLocalAddr2, proto, PROTO_SSLTCP);
|
|
port2->SetIceRole(cricket::ICEROLE_CONTROLLED);
|
|
TestConnectivity("ssltcp", std::move(port1), RelayName(proto),
|
|
std::move(port2), false, false, true, true);
|
|
}
|
|
|
|
rtc::Network* MakeNetwork(const SocketAddress& addr) {
|
|
networks_.emplace_back("unittest", "unittest", addr.ipaddr(), 32);
|
|
networks_.back().AddIP(addr.ipaddr());
|
|
return &networks_.back();
|
|
}
|
|
|
|
// helpers for above functions
|
|
std::unique_ptr<UDPPort> CreateUdpPort(const SocketAddress& addr) {
|
|
return CreateUdpPort(addr, &socket_factory_);
|
|
}
|
|
std::unique_ptr<UDPPort> CreateUdpPort(const SocketAddress& addr,
|
|
PacketSocketFactory* socket_factory) {
|
|
return UDPPort::Create(&main_, socket_factory, MakeNetwork(addr), 0, 0,
|
|
username_, password_, std::string(), true,
|
|
absl::nullopt);
|
|
}
|
|
std::unique_ptr<TCPPort> CreateTcpPort(const SocketAddress& addr) {
|
|
return CreateTcpPort(addr, &socket_factory_);
|
|
}
|
|
std::unique_ptr<TCPPort> CreateTcpPort(const SocketAddress& addr,
|
|
PacketSocketFactory* socket_factory) {
|
|
return TCPPort::Create(&main_, socket_factory, MakeNetwork(addr), 0, 0,
|
|
username_, password_, true);
|
|
}
|
|
std::unique_ptr<StunPort> CreateStunPort(const SocketAddress& addr,
|
|
rtc::PacketSocketFactory* factory) {
|
|
ServerAddresses stun_servers;
|
|
stun_servers.insert(kStunAddr);
|
|
return StunPort::Create(&main_, factory, MakeNetwork(addr), 0, 0, username_,
|
|
password_, stun_servers, std::string(),
|
|
absl::nullopt);
|
|
}
|
|
std::unique_ptr<Port> CreateRelayPort(const SocketAddress& addr,
|
|
ProtocolType int_proto,
|
|
ProtocolType ext_proto) {
|
|
return CreateTurnPort(addr, &socket_factory_, int_proto, ext_proto);
|
|
}
|
|
std::unique_ptr<TurnPort> CreateTurnPort(const SocketAddress& addr,
|
|
PacketSocketFactory* socket_factory,
|
|
ProtocolType int_proto,
|
|
ProtocolType ext_proto) {
|
|
SocketAddress server_addr =
|
|
int_proto == PROTO_TCP ? kTurnTcpIntAddr : kTurnUdpIntAddr;
|
|
return CreateTurnPort(addr, socket_factory, int_proto, ext_proto,
|
|
server_addr);
|
|
}
|
|
std::unique_ptr<TurnPort> CreateTurnPort(
|
|
const SocketAddress& addr,
|
|
PacketSocketFactory* socket_factory,
|
|
ProtocolType int_proto,
|
|
ProtocolType ext_proto,
|
|
const rtc::SocketAddress& server_addr) {
|
|
return TurnPort::Create(&main_, socket_factory, MakeNetwork(addr), 0, 0,
|
|
username_, password_,
|
|
ProtocolAddress(server_addr, int_proto),
|
|
kRelayCredentials, 0, "", {}, {}, nullptr, nullptr);
|
|
}
|
|
std::unique_ptr<rtc::NATServer> CreateNatServer(const SocketAddress& addr,
|
|
rtc::NATType type) {
|
|
return std::make_unique<rtc::NATServer>(type, ss_.get(), addr, 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(ProtocolType proto) {
|
|
switch (proto) {
|
|
case PROTO_UDP:
|
|
return "turn(udp)";
|
|
case PROTO_TCP:
|
|
return "turn(tcp)";
|
|
case PROTO_SSLTCP:
|
|
return "turn(ssltcp)";
|
|
case PROTO_TLS:
|
|
return "turn(tls)";
|
|
default:
|
|
return "turn(?)";
|
|
}
|
|
}
|
|
|
|
void TestCrossFamilyPorts(int type);
|
|
|
|
void ExpectPortsCanConnect(bool can_connect, Port* p1, Port* p2);
|
|
|
|
// This does all the work and then deletes |port1| and |port2|.
|
|
void TestConnectivity(const char* name1,
|
|
std::unique_ptr<Port> port1,
|
|
const char* name2,
|
|
std::unique_ptr<Port> port2,
|
|
bool accept,
|
|
bool same_addr1,
|
|
bool same_addr2,
|
|
bool possible);
|
|
|
|
// This connects the provided channels which have already started. |ch1|
|
|
// should have its Connection created (either through CreateConnection() or
|
|
// TCP reconnecting mechanism before entering this function.
|
|
void ConnectStartedChannels(TestChannel* ch1, TestChannel* ch2) {
|
|
ASSERT_TRUE(ch1->conn());
|
|
EXPECT_TRUE_WAIT(ch1->conn()->connected(),
|
|
kDefaultTimeout); // for TCP connect
|
|
ch1->Ping();
|
|
WAIT(!ch2->remote_address().IsNil(), kShortTimeout);
|
|
|
|
// Send a ping from dst to src.
|
|
ch2->AcceptConnection(GetCandidate(ch1->port()));
|
|
ch2->Ping();
|
|
EXPECT_EQ_WAIT(Connection::STATE_WRITABLE, ch2->conn()->write_state(),
|
|
kDefaultTimeout);
|
|
}
|
|
|
|
// This connects and disconnects the provided channels in the same sequence as
|
|
// TestConnectivity with all options set to |true|. It does not delete either
|
|
// channel.
|
|
void StartConnectAndStopChannels(TestChannel* ch1, TestChannel* ch2) {
|
|
// Acquire addresses.
|
|
ch1->Start();
|
|
ch2->Start();
|
|
|
|
ch1->CreateConnection(GetCandidate(ch2->port()));
|
|
ConnectStartedChannels(ch1, ch2);
|
|
|
|
// Destroy the connections.
|
|
ch1->Stop();
|
|
ch2->Stop();
|
|
}
|
|
|
|
// This disconnects both end's Connection and make sure ch2 ready for new
|
|
// connection.
|
|
void DisconnectTcpTestChannels(TestChannel* ch1, TestChannel* ch2) {
|
|
TCPConnection* tcp_conn1 = static_cast<TCPConnection*>(ch1->conn());
|
|
TCPConnection* tcp_conn2 = static_cast<TCPConnection*>(ch2->conn());
|
|
ASSERT_TRUE(
|
|
ss_->CloseTcpConnections(tcp_conn1->socket()->GetLocalAddress(),
|
|
tcp_conn2->socket()->GetLocalAddress()));
|
|
|
|
// Wait for both OnClose are delivered.
|
|
EXPECT_TRUE_WAIT(!ch1->conn()->connected(), kDefaultTimeout);
|
|
EXPECT_TRUE_WAIT(!ch2->conn()->connected(), kDefaultTimeout);
|
|
|
|
// Ensure redundant SignalClose events on TcpConnection won't break tcp
|
|
// reconnection. Chromium will fire SignalClose for all outstanding IPC
|
|
// packets during reconnection.
|
|
tcp_conn1->socket()->SignalClose(tcp_conn1->socket(), 0);
|
|
tcp_conn2->socket()->SignalClose(tcp_conn2->socket(), 0);
|
|
|
|
// Speed up destroying ch2's connection such that the test is ready to
|
|
// accept a new connection from ch1 before ch1's connection destroys itself.
|
|
ch2->conn()->Destroy();
|
|
EXPECT_TRUE_WAIT(ch2->conn() == NULL, kDefaultTimeout);
|
|
}
|
|
|
|
void TestTcpReconnect(bool ping_after_disconnected,
|
|
bool send_after_disconnected) {
|
|
auto port1 = CreateTcpPort(kLocalAddr1);
|
|
port1->SetIceRole(cricket::ICEROLE_CONTROLLING);
|
|
auto port2 = CreateTcpPort(kLocalAddr2);
|
|
port2->SetIceRole(cricket::ICEROLE_CONTROLLED);
|
|
|
|
port1->set_component(cricket::ICE_CANDIDATE_COMPONENT_DEFAULT);
|
|
port2->set_component(cricket::ICE_CANDIDATE_COMPONENT_DEFAULT);
|
|
|
|
// Set up channels and ensure both ports will be deleted.
|
|
TestChannel ch1(std::move(port1));
|
|
TestChannel ch2(std::move(port2));
|
|
EXPECT_EQ(0, ch1.complete_count());
|
|
EXPECT_EQ(0, ch2.complete_count());
|
|
|
|
ch1.Start();
|
|
ch2.Start();
|
|
ASSERT_EQ_WAIT(1, ch1.complete_count(), kDefaultTimeout);
|
|
ASSERT_EQ_WAIT(1, ch2.complete_count(), kDefaultTimeout);
|
|
|
|
// Initial connecting the channel, create connection on channel1.
|
|
ch1.CreateConnection(GetCandidate(ch2.port()));
|
|
ConnectStartedChannels(&ch1, &ch2);
|
|
|
|
// Shorten the timeout period.
|
|
const int kTcpReconnectTimeout = kDefaultTimeout;
|
|
static_cast<TCPConnection*>(ch1.conn())
|
|
->set_reconnection_timeout(kTcpReconnectTimeout);
|
|
static_cast<TCPConnection*>(ch2.conn())
|
|
->set_reconnection_timeout(kTcpReconnectTimeout);
|
|
|
|
EXPECT_FALSE(ch1.connection_ready_to_send());
|
|
EXPECT_FALSE(ch2.connection_ready_to_send());
|
|
|
|
// Once connected, disconnect them.
|
|
DisconnectTcpTestChannels(&ch1, &ch2);
|
|
|
|
if (send_after_disconnected || ping_after_disconnected) {
|
|
if (send_after_disconnected) {
|
|
// First SendData after disconnect should fail but will trigger
|
|
// reconnect.
|
|
EXPECT_EQ(-1, ch1.SendData(data, static_cast<int>(strlen(data))));
|
|
}
|
|
|
|
if (ping_after_disconnected) {
|
|
// Ping should trigger reconnect.
|
|
ch1.Ping();
|
|
}
|
|
|
|
// Wait for channel's outgoing TCPConnection connected.
|
|
EXPECT_TRUE_WAIT(ch1.conn()->connected(), kDefaultTimeout);
|
|
|
|
// Verify that we could still connect channels.
|
|
ConnectStartedChannels(&ch1, &ch2);
|
|
EXPECT_TRUE_WAIT(ch1.connection_ready_to_send(), kTcpReconnectTimeout);
|
|
// Channel2 is the passive one so a new connection is created during
|
|
// reconnect. This new connection should never have issued ENOTCONN
|
|
// hence the connection_ready_to_send() should be false.
|
|
EXPECT_FALSE(ch2.connection_ready_to_send());
|
|
} else {
|
|
EXPECT_EQ(ch1.conn()->write_state(), Connection::STATE_WRITABLE);
|
|
// Since the reconnection never happens, the connections should have been
|
|
// destroyed after the timeout.
|
|
EXPECT_TRUE_WAIT(!ch1.conn(), kTcpReconnectTimeout + kDefaultTimeout);
|
|
EXPECT_TRUE(!ch2.conn());
|
|
}
|
|
|
|
// Tear down and ensure that goes smoothly.
|
|
ch1.Stop();
|
|
ch2.Stop();
|
|
EXPECT_TRUE_WAIT(ch1.conn() == NULL, kDefaultTimeout);
|
|
EXPECT_TRUE_WAIT(ch2.conn() == NULL, kDefaultTimeout);
|
|
}
|
|
|
|
std::unique_ptr<IceMessage> CreateStunMessage(int type) {
|
|
auto msg = std::make_unique<IceMessage>();
|
|
msg->SetType(type);
|
|
msg->SetTransactionID("TESTTESTTEST");
|
|
return msg;
|
|
}
|
|
std::unique_ptr<IceMessage> CreateStunMessageWithUsername(
|
|
int type,
|
|
const std::string& username) {
|
|
std::unique_ptr<IceMessage> msg = CreateStunMessage(type);
|
|
msg->AddAttribute(std::make_unique<StunByteStringAttribute>(
|
|
STUN_ATTR_USERNAME, username));
|
|
return msg;
|
|
}
|
|
std::unique_ptr<TestPort> CreateTestPort(const rtc::SocketAddress& addr,
|
|
const std::string& username,
|
|
const std::string& password) {
|
|
auto port =
|
|
std::make_unique<TestPort>(&main_, "test", &socket_factory_,
|
|
MakeNetwork(addr), 0, 0, username, password);
|
|
port->SignalRoleConflict.connect(this, &PortTest::OnRoleConflict);
|
|
return port;
|
|
}
|
|
std::unique_ptr<TestPort> CreateTestPort(const rtc::SocketAddress& addr,
|
|
const std::string& username,
|
|
const std::string& password,
|
|
cricket::IceRole role,
|
|
int tiebreaker) {
|
|
auto port = CreateTestPort(addr, username, password);
|
|
port->SetIceRole(role);
|
|
port->SetIceTiebreaker(tiebreaker);
|
|
return port;
|
|
}
|
|
// Overload to create a test port given an rtc::Network directly.
|
|
std::unique_ptr<TestPort> CreateTestPort(rtc::Network* network,
|
|
const std::string& username,
|
|
const std::string& password) {
|
|
auto port = std::make_unique<TestPort>(&main_, "test", &socket_factory_,
|
|
network, 0, 0, username, password);
|
|
port->SignalRoleConflict.connect(this, &PortTest::OnRoleConflict);
|
|
return port;
|
|
}
|
|
|
|
void OnRoleConflict(PortInterface* port) { role_conflict_ = true; }
|
|
bool role_conflict() const { return role_conflict_; }
|
|
|
|
void ConnectToSignalDestroyed(PortInterface* port) {
|
|
port->SignalDestroyed.connect(this, &PortTest::OnDestroyed);
|
|
}
|
|
|
|
void OnDestroyed(PortInterface* port) { ++ports_destroyed_; }
|
|
int ports_destroyed() const { return ports_destroyed_; }
|
|
|
|
rtc::BasicPacketSocketFactory* nat_socket_factory1() {
|
|
return &nat_socket_factory1_;
|
|
}
|
|
|
|
rtc::VirtualSocketServer* vss() { return ss_.get(); }
|
|
|
|
private:
|
|
// When a "create port" helper method is called with an IP, we create a
|
|
// Network with that IP and add it to this list. Using a list instead of a
|
|
// vector so that when it grows, pointers aren't invalidated.
|
|
std::list<rtc::Network> networks_;
|
|
std::unique_ptr<rtc::VirtualSocketServer> ss_;
|
|
rtc::AutoSocketServerThread main_;
|
|
rtc::BasicPacketSocketFactory socket_factory_;
|
|
std::unique_ptr<rtc::NATServer> nat_server1_;
|
|
std::unique_ptr<rtc::NATServer> nat_server2_;
|
|
rtc::NATSocketFactory nat_factory1_;
|
|
rtc::NATSocketFactory nat_factory2_;
|
|
rtc::BasicPacketSocketFactory nat_socket_factory1_;
|
|
rtc::BasicPacketSocketFactory nat_socket_factory2_;
|
|
std::unique_ptr<TestStunServer> stun_server_;
|
|
TestTurnServer turn_server_;
|
|
std::string username_;
|
|
std::string password_;
|
|
bool role_conflict_;
|
|
int ports_destroyed_;
|
|
};
|
|
|
|
void PortTest::TestConnectivity(const char* name1,
|
|
std::unique_ptr<Port> port1,
|
|
const char* name2,
|
|
std::unique_ptr<Port> port2,
|
|
bool accept,
|
|
bool same_addr1,
|
|
bool same_addr2,
|
|
bool possible) {
|
|
rtc::ScopedFakeClock clock;
|
|
RTC_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 and ensure both ports will be deleted.
|
|
TestChannel ch1(std::move(port1));
|
|
TestChannel ch2(std::move(port2));
|
|
EXPECT_EQ(0, ch1.complete_count());
|
|
EXPECT_EQ(0, ch2.complete_count());
|
|
|
|
// Acquire addresses.
|
|
ch1.Start();
|
|
ch2.Start();
|
|
ASSERT_EQ_SIMULATED_WAIT(1, ch1.complete_count(), kDefaultTimeout, clock);
|
|
ASSERT_EQ_SIMULATED_WAIT(1, ch2.complete_count(), kDefaultTimeout, clock);
|
|
|
|
// Send a ping from src to dst. This may or may not make it.
|
|
ch1.CreateConnection(GetCandidate(ch2.port()));
|
|
ASSERT_TRUE(ch1.conn() != NULL);
|
|
EXPECT_TRUE_SIMULATED_WAIT(ch1.conn()->connected(), kDefaultTimeout,
|
|
clock); // for TCP connect
|
|
ch1.Ping();
|
|
SIMULATED_WAIT(!ch2.remote_address().IsNil(), kShortTimeout, clock);
|
|
|
|
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(), ch1.port()->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(ch1.port()));
|
|
EXPECT_TRUE(same_addr2);
|
|
|
|
// Send a ping from dst to src.
|
|
ch2.AcceptConnection(GetCandidate(ch1.port()));
|
|
ASSERT_TRUE(ch2.conn() != NULL);
|
|
ch2.Ping();
|
|
EXPECT_EQ_SIMULATED_WAIT(Connection::STATE_WRITABLE,
|
|
ch2.conn()->write_state(), kDefaultTimeout, clock);
|
|
} 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(GetCandidate(ch1.port()));
|
|
ASSERT_TRUE(ch2.conn() != NULL);
|
|
ch2.Ping();
|
|
SIMULATED_WAIT(ch2.conn()->write_state() == Connection::STATE_WRITABLE,
|
|
kShortTimeout, clock);
|
|
|
|
if (same_addr1 && same_addr2) {
|
|
// The new ping got back to the source.
|
|
EXPECT_TRUE(ch1.conn()->receiving());
|
|
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_SIMULATED_WAIT(Connection::STATE_WRITABLE,
|
|
ch1.conn()->write_state(), kDefaultTimeout,
|
|
clock);
|
|
}
|
|
} 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_SIMULATED_WAIT(!ch2.remote_address().IsNil(), kDefaultTimeout,
|
|
clock);
|
|
EXPECT_FALSE(ch2.conn()->receiving());
|
|
EXPECT_TRUE(ch1.remote_address().IsNil());
|
|
|
|
// Pick up the actual address and establish the connection.
|
|
ch2.AcceptConnection(GetCandidate(ch1.port()));
|
|
ASSERT_TRUE(ch2.conn() != NULL);
|
|
ch2.Ping();
|
|
EXPECT_EQ_SIMULATED_WAIT(Connection::STATE_WRITABLE,
|
|
ch2.conn()->write_state(), kDefaultTimeout,
|
|
clock);
|
|
} 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_FALSE(ch1.conn()->receiving());
|
|
|
|
// Update our address and complete the connection.
|
|
ch1.AcceptConnection(GetCandidate(ch2.port()));
|
|
ch1.Ping();
|
|
EXPECT_EQ_SIMULATED_WAIT(Connection::STATE_WRITABLE,
|
|
ch1.conn()->write_state(), kDefaultTimeout,
|
|
clock);
|
|
} 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();
|
|
SIMULATED_WAIT(!ch2.remote_address().IsNil(), kShortTimeout, clock);
|
|
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_TRUE(ch1.conn()->receiving());
|
|
EXPECT_EQ(Connection::STATE_WRITABLE, ch1.conn()->write_state());
|
|
EXPECT_TRUE(ch2.conn()->receiving());
|
|
EXPECT_EQ(Connection::STATE_WRITABLE, ch2.conn()->write_state());
|
|
} else {
|
|
EXPECT_FALSE(ch1.conn()->receiving());
|
|
EXPECT_NE(Connection::STATE_WRITABLE, ch1.conn()->write_state());
|
|
EXPECT_FALSE(ch2.conn()->receiving());
|
|
EXPECT_NE(Connection::STATE_WRITABLE, ch2.conn()->write_state());
|
|
}
|
|
|
|
// Tear down and ensure that goes smoothly.
|
|
ch1.Stop();
|
|
ch2.Stop();
|
|
EXPECT_TRUE_SIMULATED_WAIT(ch1.conn() == NULL, kDefaultTimeout, clock);
|
|
EXPECT_TRUE_SIMULATED_WAIT(ch2.conn() == NULL, kDefaultTimeout, clock);
|
|
}
|
|
|
|
class FakePacketSocketFactory : public rtc::PacketSocketFactory {
|
|
public:
|
|
FakePacketSocketFactory()
|
|
: next_udp_socket_(NULL), next_server_tcp_socket_(NULL) {}
|
|
~FakePacketSocketFactory() override {}
|
|
|
|
AsyncPacketSocket* CreateUdpSocket(const SocketAddress& address,
|
|
uint16_t min_port,
|
|
uint16_t max_port) override {
|
|
EXPECT_TRUE(next_udp_socket_ != NULL);
|
|
AsyncPacketSocket* result = next_udp_socket_;
|
|
next_udp_socket_ = NULL;
|
|
return result;
|
|
}
|
|
|
|
AsyncPacketSocket* CreateServerTcpSocket(const SocketAddress& local_address,
|
|
uint16_t min_port,
|
|
uint16_t max_port,
|
|
int opts) override {
|
|
EXPECT_TRUE(next_server_tcp_socket_ != NULL);
|
|
AsyncPacketSocket* result = next_server_tcp_socket_;
|
|
next_server_tcp_socket_ = NULL;
|
|
return result;
|
|
}
|
|
|
|
AsyncPacketSocket* CreateClientTcpSocket(
|
|
const SocketAddress& local_address,
|
|
const SocketAddress& remote_address,
|
|
const rtc::ProxyInfo& proxy_info,
|
|
const std::string& user_agent,
|
|
const rtc::PacketSocketTcpOptions& opts) override {
|
|
EXPECT_TRUE(next_client_tcp_socket_.has_value());
|
|
AsyncPacketSocket* result = *next_client_tcp_socket_;
|
|
next_client_tcp_socket_ = nullptr;
|
|
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;
|
|
}
|
|
rtc::AsyncResolverInterface* CreateAsyncResolver() override { return NULL; }
|
|
|
|
private:
|
|
AsyncPacketSocket* next_udp_socket_;
|
|
AsyncPacketSocket* next_server_tcp_socket_;
|
|
absl::optional<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 local_address_; }
|
|
|
|
// Returns remote address. Returns zeroes if this is not a client TCP socket.
|
|
virtual SocketAddress GetRemoteAddress() const { return remote_address_; }
|
|
|
|
// Send a packet.
|
|
virtual int Send(const void* pv,
|
|
size_t cb,
|
|
const rtc::PacketOptions& options) {
|
|
if (error_ == 0) {
|
|
return static_cast<int>(cb);
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
virtual int SendTo(const void* pv,
|
|
size_t cb,
|
|
const SocketAddress& addr,
|
|
const rtc::PacketOptions& options) {
|
|
if (error_ == 0) {
|
|
return static_cast<int>(cb);
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
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) { error_ = error; }
|
|
|
|
void set_state(State state) { state_ = state; }
|
|
|
|
SocketAddress local_address_;
|
|
SocketAddress remote_address_;
|
|
|
|
private:
|
|
int error_ = 0;
|
|
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);
|
|
}
|
|
|
|
// Flaky: https://code.google.com/p/webrtc/issues/detail?id=3316.
|
|
TEST_F(PortTest, DISABLED_TestLocalToTurn) {
|
|
TestLocalToRelay(PROTO_UDP);
|
|
}
|
|
|
|
// 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, PROTO_UDP);
|
|
}
|
|
|
|
// 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, PROTO_UDP);
|
|
}
|
|
|
|
// 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, PROTO_UDP);
|
|
}
|
|
|
|
// 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, PROTO_UDP);
|
|
}
|
|
|
|
// Outbound TCP -> XXXX
|
|
TEST_F(PortTest, TestTcpToTcp) {
|
|
TestTcpToTcp();
|
|
}
|
|
|
|
TEST_F(PortTest, TestTcpReconnectOnSendPacket) {
|
|
TestTcpReconnect(false /* ping */, true /* send */);
|
|
}
|
|
|
|
TEST_F(PortTest, TestTcpReconnectOnPing) {
|
|
TestTcpReconnect(true /* ping */, false /* send */);
|
|
}
|
|
|
|
TEST_F(PortTest, TestTcpReconnectTimeout) {
|
|
TestTcpReconnect(false /* ping */, false /* send */);
|
|
}
|
|
|
|
// Test when TcpConnection never connects, the OnClose() will be called to
|
|
// destroy the connection.
|
|
TEST_F(PortTest, TestTcpNeverConnect) {
|
|
auto port1 = CreateTcpPort(kLocalAddr1);
|
|
port1->SetIceRole(cricket::ICEROLE_CONTROLLING);
|
|
port1->set_component(cricket::ICE_CANDIDATE_COMPONENT_DEFAULT);
|
|
|
|
// Set up a channel and ensure the port will be deleted.
|
|
TestChannel ch1(std::move(port1));
|
|
EXPECT_EQ(0, ch1.complete_count());
|
|
|
|
ch1.Start();
|
|
ASSERT_EQ_WAIT(1, ch1.complete_count(), kDefaultTimeout);
|
|
|
|
std::unique_ptr<rtc::AsyncSocket> server(
|
|
vss()->CreateAsyncSocket(kLocalAddr2.family(), SOCK_STREAM));
|
|
// Bind but not listen.
|
|
EXPECT_EQ(0, server->Bind(kLocalAddr2));
|
|
|
|
Candidate c = GetCandidate(ch1.port());
|
|
c.set_address(server->GetLocalAddress());
|
|
|
|
ch1.CreateConnection(c);
|
|
EXPECT_TRUE(ch1.conn());
|
|
EXPECT_TRUE_WAIT(!ch1.conn(), kDefaultTimeout); // for TCP connect
|
|
}
|
|
|
|
/* 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);
|
|
}
|
|
*/
|
|
|
|
// Test that a connection will be dead and deleted if
|
|
// i) it has never received anything for MIN_CONNECTION_LIFETIME milliseconds
|
|
// since it was created, or
|
|
// ii) it has not received anything for DEAD_CONNECTION_RECEIVE_TIMEOUT
|
|
// milliseconds since last receiving.
|
|
TEST_F(PortTest, TestConnectionDead) {
|
|
TestChannel ch1(CreateUdpPort(kLocalAddr1));
|
|
TestChannel ch2(CreateUdpPort(kLocalAddr2));
|
|
// Acquire address.
|
|
ch1.Start();
|
|
ch2.Start();
|
|
ASSERT_EQ_WAIT(1, ch1.complete_count(), kDefaultTimeout);
|
|
ASSERT_EQ_WAIT(1, ch2.complete_count(), kDefaultTimeout);
|
|
|
|
// Test case that the connection has never received anything.
|
|
int64_t before_created = rtc::TimeMillis();
|
|
ch1.CreateConnection(GetCandidate(ch2.port()));
|
|
int64_t after_created = rtc::TimeMillis();
|
|
Connection* conn = ch1.conn();
|
|
ASSERT_NE(conn, nullptr);
|
|
// It is not dead if it is after MIN_CONNECTION_LIFETIME but not pruned.
|
|
conn->UpdateState(after_created + MIN_CONNECTION_LIFETIME + 1);
|
|
rtc::Thread::Current()->ProcessMessages(0);
|
|
EXPECT_TRUE(ch1.conn() != nullptr);
|
|
// It is not dead if it is before MIN_CONNECTION_LIFETIME and pruned.
|
|
conn->UpdateState(before_created + MIN_CONNECTION_LIFETIME - 1);
|
|
conn->Prune();
|
|
rtc::Thread::Current()->ProcessMessages(0);
|
|
EXPECT_TRUE(ch1.conn() != nullptr);
|
|
// It will be dead after MIN_CONNECTION_LIFETIME and pruned.
|
|
conn->UpdateState(after_created + MIN_CONNECTION_LIFETIME + 1);
|
|
EXPECT_TRUE_WAIT(ch1.conn() == nullptr, kDefaultTimeout);
|
|
|
|
// Test case that the connection has received something.
|
|
// Create a connection again and receive a ping.
|
|
ch1.CreateConnection(GetCandidate(ch2.port()));
|
|
conn = ch1.conn();
|
|
ASSERT_NE(conn, nullptr);
|
|
int64_t before_last_receiving = rtc::TimeMillis();
|
|
conn->ReceivedPing();
|
|
int64_t after_last_receiving = rtc::TimeMillis();
|
|
// The connection will be dead after DEAD_CONNECTION_RECEIVE_TIMEOUT
|
|
conn->UpdateState(before_last_receiving + DEAD_CONNECTION_RECEIVE_TIMEOUT -
|
|
1);
|
|
rtc::Thread::Current()->ProcessMessages(100);
|
|
EXPECT_TRUE(ch1.conn() != nullptr);
|
|
conn->UpdateState(after_last_receiving + DEAD_CONNECTION_RECEIVE_TIMEOUT + 1);
|
|
EXPECT_TRUE_WAIT(ch1.conn() == nullptr, kDefaultTimeout);
|
|
}
|
|
|
|
TEST_F(PortTest, TestConnectionDeadWithDeadConnectionTimeout) {
|
|
TestChannel ch1(CreateUdpPort(kLocalAddr1));
|
|
TestChannel ch2(CreateUdpPort(kLocalAddr2));
|
|
// Acquire address.
|
|
ch1.Start();
|
|
ch2.Start();
|
|
ASSERT_EQ_WAIT(1, ch1.complete_count(), kDefaultTimeout);
|
|
ASSERT_EQ_WAIT(1, ch2.complete_count(), kDefaultTimeout);
|
|
|
|
// Note: set field trials manually since they are parsed by
|
|
// P2PTransportChannel but P2PTransportChannel is not used in this test.
|
|
IceFieldTrials field_trials;
|
|
field_trials.dead_connection_timeout_ms = 90000;
|
|
|
|
// Create a connection again and receive a ping.
|
|
ch1.CreateConnection(GetCandidate(ch2.port()));
|
|
auto conn = ch1.conn();
|
|
conn->SetIceFieldTrials(&field_trials);
|
|
|
|
ASSERT_NE(conn, nullptr);
|
|
int64_t before_last_receiving = rtc::TimeMillis();
|
|
conn->ReceivedPing();
|
|
int64_t after_last_receiving = rtc::TimeMillis();
|
|
// The connection will be dead after 90s
|
|
conn->UpdateState(before_last_receiving + 90000 - 1);
|
|
rtc::Thread::Current()->ProcessMessages(100);
|
|
EXPECT_TRUE(ch1.conn() != nullptr);
|
|
conn->UpdateState(after_last_receiving + 90000 + 1);
|
|
EXPECT_TRUE_WAIT(ch1.conn() == nullptr, kDefaultTimeout);
|
|
}
|
|
|
|
TEST_F(PortTest, TestConnectionDeadOutstandingPing) {
|
|
auto port1 = CreateUdpPort(kLocalAddr1);
|
|
port1->SetIceRole(cricket::ICEROLE_CONTROLLING);
|
|
port1->SetIceTiebreaker(kTiebreaker1);
|
|
auto port2 = CreateUdpPort(kLocalAddr2);
|
|
port2->SetIceRole(cricket::ICEROLE_CONTROLLED);
|
|
port2->SetIceTiebreaker(kTiebreaker2);
|
|
|
|
TestChannel ch1(std::move(port1));
|
|
TestChannel ch2(std::move(port2));
|
|
// Acquire address.
|
|
ch1.Start();
|
|
ch2.Start();
|
|
ASSERT_EQ_WAIT(1, ch1.complete_count(), kDefaultTimeout);
|
|
ASSERT_EQ_WAIT(1, ch2.complete_count(), kDefaultTimeout);
|
|
|
|
// Note: set field trials manually since they are parsed by
|
|
// P2PTransportChannel but P2PTransportChannel is not used in this test.
|
|
IceFieldTrials field_trials;
|
|
field_trials.dead_connection_timeout_ms = 360000;
|
|
|
|
// Create a connection again and receive a ping and then send
|
|
// a ping and keep it outstanding.
|
|
ch1.CreateConnection(GetCandidate(ch2.port()));
|
|
auto conn = ch1.conn();
|
|
conn->SetIceFieldTrials(&field_trials);
|
|
|
|
ASSERT_NE(conn, nullptr);
|
|
conn->ReceivedPing();
|
|
int64_t send_ping_timestamp = rtc::TimeMillis();
|
|
conn->Ping(send_ping_timestamp);
|
|
|
|
// The connection will be dead 30s after the ping was sent.
|
|
conn->UpdateState(send_ping_timestamp + DEAD_CONNECTION_RECEIVE_TIMEOUT - 1);
|
|
rtc::Thread::Current()->ProcessMessages(100);
|
|
EXPECT_TRUE(ch1.conn() != nullptr);
|
|
conn->UpdateState(send_ping_timestamp + DEAD_CONNECTION_RECEIVE_TIMEOUT + 1);
|
|
EXPECT_TRUE_WAIT(ch1.conn() == nullptr, kDefaultTimeout);
|
|
}
|
|
|
|
// 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, TestLocalToLocalStandard) {
|
|
auto port1 = CreateUdpPort(kLocalAddr1);
|
|
port1->SetIceRole(cricket::ICEROLE_CONTROLLING);
|
|
port1->SetIceTiebreaker(kTiebreaker1);
|
|
auto port2 = CreateUdpPort(kLocalAddr2);
|
|
port2->SetIceRole(cricket::ICEROLE_CONTROLLED);
|
|
port2->SetIceTiebreaker(kTiebreaker2);
|
|
// Same parameters as TestLocalToLocal above.
|
|
TestConnectivity("udp", std::move(port1), "udp", std::move(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, TestLoopbackCall) {
|
|
auto lport = CreateTestPort(kLocalAddr1, "lfrag", "lpass");
|
|
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, kDefaultTimeout);
|
|
IceMessage* msg = lport->last_stun_msg();
|
|
EXPECT_EQ(STUN_BINDING_REQUEST, msg->type());
|
|
conn->OnReadPacket(lport->last_stun_buf()->data<char>(),
|
|
lport->last_stun_buf()->size(), /* packet_time_us */ -1);
|
|
ASSERT_TRUE_WAIT(lport->last_stun_msg() != NULL, kDefaultTimeout);
|
|
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 receiving.
|
|
Connection* conn1 =
|
|
lport->CreateConnection(lport->Candidates()[1], Port::ORIGIN_MESSAGE);
|
|
conn1->Ping(0);
|
|
|
|
ASSERT_TRUE_WAIT(lport->last_stun_msg() != NULL, kDefaultTimeout);
|
|
msg = lport->last_stun_msg();
|
|
EXPECT_EQ(STUN_BINDING_REQUEST, msg->type());
|
|
std::unique_ptr<IceMessage> modified_req(
|
|
CreateStunMessage(STUN_BINDING_REQUEST));
|
|
const StunByteStringAttribute* username_attr =
|
|
msg->GetByteString(STUN_ATTR_USERNAME);
|
|
modified_req->AddAttribute(std::make_unique<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(std::make_unique<StunUInt64Attribute>(
|
|
STUN_ATTR_ICE_CONTROLLING, kTiebreaker1 - 1));
|
|
modified_req->AddMessageIntegrity("lpass");
|
|
modified_req->AddFingerprint();
|
|
|
|
lport->Reset();
|
|
auto buf = std::make_unique<ByteBufferWriter>();
|
|
WriteStunMessage(*modified_req, buf.get());
|
|
conn1->OnReadPacket(buf->Data(), buf->Length(), /* packet_time_us */ -1);
|
|
ASSERT_TRUE_WAIT(lport->last_stun_msg() != NULL, kDefaultTimeout);
|
|
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) {
|
|
auto lport = CreateTestPort(kLocalAddr1, "lfrag", "lpass");
|
|
lport->SetIceRole(cricket::ICEROLE_CONTROLLING);
|
|
lport->SetIceTiebreaker(kTiebreaker1);
|
|
auto rport = CreateTestPort(kLocalAddr2, "rfrag", "rpass");
|
|
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, kDefaultTimeout);
|
|
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<char>(),
|
|
rport->last_stun_buf()->size(), /* packet_time_us */ -1);
|
|
|
|
ASSERT_TRUE_WAIT(lport->last_stun_msg() != NULL, kDefaultTimeout);
|
|
EXPECT_EQ(STUN_BINDING_RESPONSE, lport->last_stun_msg()->type());
|
|
EXPECT_TRUE(role_conflict());
|
|
}
|
|
|
|
TEST_F(PortTest, TestTcpNoDelay) {
|
|
auto port1 = CreateTcpPort(kLocalAddr1);
|
|
port1->SetIceRole(cricket::ICEROLE_CONTROLLING);
|
|
int option_value = -1;
|
|
int success = port1->GetOption(rtc::Socket::OPT_NODELAY, &option_value);
|
|
ASSERT_EQ(0, success); // GetOption() should complete successfully w/ 0
|
|
ASSERT_EQ(1, option_value);
|
|
}
|
|
|
|
TEST_F(PortTest, TestDelayedBindingUdp) {
|
|
FakeAsyncPacketSocket* socket = new FakeAsyncPacketSocket();
|
|
FakePacketSocketFactory socket_factory;
|
|
|
|
socket_factory.set_next_udp_socket(socket);
|
|
auto 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);
|
|
auto 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());
|
|
}
|
|
|
|
TEST_F(PortTest, TestDisableInterfaceOfTcpPort) {
|
|
FakeAsyncPacketSocket* lsocket = new FakeAsyncPacketSocket();
|
|
FakeAsyncPacketSocket* rsocket = new FakeAsyncPacketSocket();
|
|
FakePacketSocketFactory socket_factory;
|
|
|
|
socket_factory.set_next_server_tcp_socket(lsocket);
|
|
auto lport = CreateTcpPort(kLocalAddr1, &socket_factory);
|
|
|
|
socket_factory.set_next_server_tcp_socket(rsocket);
|
|
auto rport = CreateTcpPort(kLocalAddr2, &socket_factory);
|
|
|
|
lsocket->set_state(AsyncPacketSocket::STATE_BINDING);
|
|
lsocket->SignalAddressReady(lsocket, kLocalAddr1);
|
|
rsocket->set_state(AsyncPacketSocket::STATE_BINDING);
|
|
rsocket->SignalAddressReady(rsocket, kLocalAddr2);
|
|
|
|
lport->SetIceRole(cricket::ICEROLE_CONTROLLING);
|
|
lport->SetIceTiebreaker(kTiebreaker1);
|
|
rport->SetIceRole(cricket::ICEROLE_CONTROLLED);
|
|
rport->SetIceTiebreaker(kTiebreaker2);
|
|
|
|
lport->PrepareAddress();
|
|
rport->PrepareAddress();
|
|
ASSERT_FALSE(rport->Candidates().empty());
|
|
|
|
// A client socket.
|
|
FakeAsyncPacketSocket* socket = new FakeAsyncPacketSocket();
|
|
socket->local_address_ = kLocalAddr1;
|
|
socket->remote_address_ = kLocalAddr2;
|
|
socket_factory.set_next_client_tcp_socket(socket);
|
|
Connection* lconn =
|
|
lport->CreateConnection(rport->Candidates()[0], Port::ORIGIN_MESSAGE);
|
|
ASSERT_NE(lconn, nullptr);
|
|
socket->SignalConnect(socket);
|
|
lconn->Ping(0);
|
|
|
|
// Now disconnect the client socket...
|
|
socket->SignalClose(socket, 1);
|
|
|
|
// And prevent new sockets from being created.
|
|
socket_factory.set_next_client_tcp_socket(nullptr);
|
|
|
|
// Test that Ping() does not cause SEGV.
|
|
lconn->Ping(0);
|
|
}
|
|
|
|
void PortTest::TestCrossFamilyPorts(int type) {
|
|
FakePacketSocketFactory factory;
|
|
std::unique_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] = CreateUdpPort(addresses[i], &factory);
|
|
} else if (type == SOCK_STREAM) {
|
|
factory.set_next_server_tcp_socket(socket);
|
|
ports[i] = 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);
|
|
}
|
|
|
|
void PortTest::ExpectPortsCanConnect(bool can_connect, Port* p1, Port* p2) {
|
|
Connection* c = p1->CreateConnection(GetCandidate(p2), Port::ORIGIN_MESSAGE);
|
|
if (can_connect) {
|
|
EXPECT_FALSE(NULL == c);
|
|
EXPECT_EQ(1U, p1->connections().size());
|
|
} else {
|
|
EXPECT_TRUE(NULL == c);
|
|
EXPECT_EQ(0U, p1->connections().size());
|
|
}
|
|
}
|
|
|
|
TEST_F(PortTest, TestUdpV6CrossTypePorts) {
|
|
FakePacketSocketFactory factory;
|
|
std::unique_ptr<Port> ports[4];
|
|
SocketAddress addresses[4] = {
|
|
SocketAddress("2001:db8::1", 0), SocketAddress("fe80::1", 0),
|
|
SocketAddress("fe80::2", 0), SocketAddress("::1", 0)};
|
|
for (int i = 0; i < 4; i++) {
|
|
FakeAsyncPacketSocket* socket = new FakeAsyncPacketSocket();
|
|
factory.set_next_udp_socket(socket);
|
|
ports[i] = CreateUdpPort(addresses[i], &factory);
|
|
socket->set_state(AsyncPacketSocket::STATE_BINDING);
|
|
socket->SignalAddressReady(socket, addresses[i]);
|
|
ports[i]->PrepareAddress();
|
|
}
|
|
|
|
Port* standard = ports[0].get();
|
|
Port* link_local1 = ports[1].get();
|
|
Port* link_local2 = ports[2].get();
|
|
Port* localhost = ports[3].get();
|
|
|
|
ExpectPortsCanConnect(false, link_local1, standard);
|
|
ExpectPortsCanConnect(false, standard, link_local1);
|
|
ExpectPortsCanConnect(false, link_local1, localhost);
|
|
ExpectPortsCanConnect(false, localhost, link_local1);
|
|
|
|
ExpectPortsCanConnect(true, link_local1, link_local2);
|
|
ExpectPortsCanConnect(true, localhost, standard);
|
|
ExpectPortsCanConnect(true, standard, localhost);
|
|
}
|
|
|
|
// This test verifies DSCP value set through SetOption interface can be
|
|
// get through DefaultDscpValue.
|
|
TEST_F(PortTest, TestDefaultDscpValue) {
|
|
int dscp;
|
|
auto udpport = CreateUdpPort(kLocalAddr1);
|
|
EXPECT_EQ(0, udpport->SetOption(rtc::Socket::OPT_DSCP, rtc::DSCP_CS6));
|
|
EXPECT_EQ(0, udpport->GetOption(rtc::Socket::OPT_DSCP, &dscp));
|
|
auto tcpport = CreateTcpPort(kLocalAddr1);
|
|
EXPECT_EQ(0, tcpport->SetOption(rtc::Socket::OPT_DSCP, rtc::DSCP_AF31));
|
|
EXPECT_EQ(0, tcpport->GetOption(rtc::Socket::OPT_DSCP, &dscp));
|
|
EXPECT_EQ(rtc::DSCP_AF31, dscp);
|
|
auto stunport = CreateStunPort(kLocalAddr1, nat_socket_factory1());
|
|
EXPECT_EQ(0, stunport->SetOption(rtc::Socket::OPT_DSCP, rtc::DSCP_AF41));
|
|
EXPECT_EQ(0, stunport->GetOption(rtc::Socket::OPT_DSCP, &dscp));
|
|
EXPECT_EQ(rtc::DSCP_AF41, dscp);
|
|
auto turnport1 =
|
|
CreateTurnPort(kLocalAddr1, nat_socket_factory1(), PROTO_UDP, PROTO_UDP);
|
|
// Socket is created in PrepareAddress.
|
|
turnport1->PrepareAddress();
|
|
EXPECT_EQ(0, turnport1->SetOption(rtc::Socket::OPT_DSCP, rtc::DSCP_CS7));
|
|
EXPECT_EQ(0, turnport1->GetOption(rtc::Socket::OPT_DSCP, &dscp));
|
|
EXPECT_EQ(rtc::DSCP_CS7, dscp);
|
|
// This will verify correct value returned without the socket.
|
|
auto turnport2 =
|
|
CreateTurnPort(kLocalAddr1, nat_socket_factory1(), PROTO_UDP, PROTO_UDP);
|
|
EXPECT_EQ(0, turnport2->SetOption(rtc::Socket::OPT_DSCP, rtc::DSCP_CS6));
|
|
EXPECT_EQ(0, turnport2->GetOption(rtc::Socket::OPT_DSCP, &dscp));
|
|
EXPECT_EQ(rtc::DSCP_CS6, dscp);
|
|
}
|
|
|
|
// Test sending STUN messages.
|
|
TEST_F(PortTest, TestSendStunMessage) {
|
|
auto lport = CreateTestPort(kLocalAddr1, "lfrag", "lpass");
|
|
auto rport = CreateTestPort(kLocalAddr2, "rfrag", "rpass");
|
|
lport->SetIceRole(cricket::ICEROLE_CONTROLLING);
|
|
lport->SetIceTiebreaker(kTiebreaker1);
|
|
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, kDefaultTimeout);
|
|
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<char>(), lport->last_stun_buf()->size(),
|
|
"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<char>(), lport->last_stun_buf()->size()));
|
|
|
|
// 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.
|
|
std::unique_ptr<IceMessage> request = CopyStunMessage(*msg);
|
|
|
|
// Receive the BINDING-REQUEST and respond with BINDING-RESPONSE.
|
|
rconn->OnReadPacket(lport->last_stun_buf()->data<char>(),
|
|
lport->last_stun_buf()->size(), /* packet_time_us */ -1);
|
|
msg = rport->last_stun_msg();
|
|
ASSERT_TRUE(msg != NULL);
|
|
EXPECT_EQ(STUN_BINDING_RESPONSE, msg->type());
|
|
// Received a BINDING-RESPONSE.
|
|
lconn->OnReadPacket(rport->last_stun_buf()->data<char>(),
|
|
rport->last_stun_buf()->size(), /* packet_time_us */ -1);
|
|
// Verify the STUN Stats.
|
|
EXPECT_EQ(1U, lconn->stats().sent_ping_requests_total);
|
|
EXPECT_EQ(1U, lconn->stats().sent_ping_requests_before_first_response);
|
|
EXPECT_EQ(1U, lconn->stats().recv_ping_responses);
|
|
EXPECT_EQ(1U, rconn->stats().recv_ping_requests);
|
|
EXPECT_EQ(1U, rconn->stats().sent_ping_responses);
|
|
|
|
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<char>(), rport->last_stun_buf()->size(),
|
|
"rpass"));
|
|
EXPECT_TRUE(msg->GetUInt32(STUN_ATTR_FINGERPRINT) != NULL);
|
|
EXPECT_TRUE(StunMessage::ValidateFingerprint(
|
|
lport->last_stun_buf()->data<char>(), lport->last_stun_buf()->size()));
|
|
// 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<char>(), rport->last_stun_buf()->size(),
|
|
"rpass"));
|
|
EXPECT_TRUE(msg->GetUInt32(STUN_ATTR_FINGERPRINT) != NULL);
|
|
EXPECT_TRUE(StunMessage::ValidateFingerprint(
|
|
lport->last_stun_buf()->data<char>(), lport->last_stun_buf()->size()));
|
|
// 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, kDefaultTimeout);
|
|
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 = CopyStunMessage(*msg);
|
|
lconn->OnReadPacket(rport->last_stun_buf()->data<char>(),
|
|
rport->last_stun_buf()->size(), /* packet_time_us */ -1);
|
|
msg = lport->last_stun_msg();
|
|
// Receive the BINDING-RESPONSE.
|
|
rconn->OnReadPacket(lport->last_stun_buf()->data<char>(),
|
|
lport->last_stun_buf()->size(), /* packet_time_us */ -1);
|
|
|
|
// Verify the Stun ping stats.
|
|
EXPECT_EQ(3U, rconn->stats().sent_ping_requests_total);
|
|
EXPECT_EQ(3U, rconn->stats().sent_ping_requests_before_first_response);
|
|
EXPECT_EQ(1U, rconn->stats().recv_ping_responses);
|
|
EXPECT_EQ(1U, lconn->stats().sent_ping_responses);
|
|
EXPECT_EQ(1U, lconn->stats().recv_ping_requests);
|
|
// Ping after receiver the first response
|
|
rconn->Ping(0);
|
|
rconn->Ping(0);
|
|
EXPECT_EQ(5U, rconn->stats().sent_ping_requests_total);
|
|
EXPECT_EQ(3U, rconn->stats().sent_ping_requests_before_first_response);
|
|
|
|
// 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, TestNomination) {
|
|
auto lport = CreateTestPort(kLocalAddr1, "lfrag", "lpass");
|
|
auto rport = CreateTestPort(kLocalAddr2, "rfrag", "rpass");
|
|
lport->SetIceRole(cricket::ICEROLE_CONTROLLING);
|
|
lport->SetIceTiebreaker(kTiebreaker1);
|
|
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);
|
|
|
|
// |lconn| is controlling, |rconn| is controlled.
|
|
uint32_t nomination = 1234;
|
|
lconn->set_nomination(nomination);
|
|
|
|
EXPECT_FALSE(lconn->nominated());
|
|
EXPECT_FALSE(rconn->nominated());
|
|
EXPECT_EQ(lconn->nominated(), lconn->stats().nominated);
|
|
EXPECT_EQ(rconn->nominated(), rconn->stats().nominated);
|
|
|
|
// Send ping (including the nomination value) from |lconn| to |rconn|. This
|
|
// should set the remote nomination of |rconn|.
|
|
lconn->Ping(0);
|
|
ASSERT_TRUE_WAIT(lport->last_stun_msg(), kDefaultTimeout);
|
|
ASSERT_TRUE(lport->last_stun_buf());
|
|
rconn->OnReadPacket(lport->last_stun_buf()->data<char>(),
|
|
lport->last_stun_buf()->size(), /* packet_time_us */ -1);
|
|
EXPECT_EQ(nomination, rconn->remote_nomination());
|
|
EXPECT_FALSE(lconn->nominated());
|
|
EXPECT_TRUE(rconn->nominated());
|
|
EXPECT_EQ(lconn->nominated(), lconn->stats().nominated);
|
|
EXPECT_EQ(rconn->nominated(), rconn->stats().nominated);
|
|
|
|
// This should result in an acknowledgment sent back from |rconn| to |lconn|,
|
|
// updating the acknowledged nomination of |lconn|.
|
|
ASSERT_TRUE_WAIT(rport->last_stun_msg(), kDefaultTimeout);
|
|
ASSERT_TRUE(rport->last_stun_buf());
|
|
lconn->OnReadPacket(rport->last_stun_buf()->data<char>(),
|
|
rport->last_stun_buf()->size(), /* packet_time_us */ -1);
|
|
EXPECT_EQ(nomination, lconn->acked_nomination());
|
|
EXPECT_TRUE(lconn->nominated());
|
|
EXPECT_TRUE(rconn->nominated());
|
|
EXPECT_EQ(lconn->nominated(), lconn->stats().nominated);
|
|
EXPECT_EQ(rconn->nominated(), rconn->stats().nominated);
|
|
}
|
|
|
|
TEST_F(PortTest, TestRoundTripTime) {
|
|
rtc::ScopedFakeClock clock;
|
|
|
|
auto lport = CreateTestPort(kLocalAddr1, "lfrag", "lpass");
|
|
auto rport = CreateTestPort(kLocalAddr2, "rfrag", "rpass");
|
|
lport->SetIceRole(cricket::ICEROLE_CONTROLLING);
|
|
lport->SetIceTiebreaker(kTiebreaker1);
|
|
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);
|
|
|
|
EXPECT_EQ(0u, lconn->stats().total_round_trip_time_ms);
|
|
EXPECT_FALSE(lconn->stats().current_round_trip_time_ms);
|
|
|
|
SendPingAndReceiveResponse(lconn, lport.get(), rconn, rport.get(), &clock,
|
|
10);
|
|
EXPECT_EQ(10u, lconn->stats().total_round_trip_time_ms);
|
|
ASSERT_TRUE(lconn->stats().current_round_trip_time_ms);
|
|
EXPECT_EQ(10u, *lconn->stats().current_round_trip_time_ms);
|
|
|
|
SendPingAndReceiveResponse(lconn, lport.get(), rconn, rport.get(), &clock,
|
|
20);
|
|
EXPECT_EQ(30u, lconn->stats().total_round_trip_time_ms);
|
|
ASSERT_TRUE(lconn->stats().current_round_trip_time_ms);
|
|
EXPECT_EQ(20u, *lconn->stats().current_round_trip_time_ms);
|
|
|
|
SendPingAndReceiveResponse(lconn, lport.get(), rconn, rport.get(), &clock,
|
|
30);
|
|
EXPECT_EQ(60u, lconn->stats().total_round_trip_time_ms);
|
|
ASSERT_TRUE(lconn->stats().current_round_trip_time_ms);
|
|
EXPECT_EQ(30u, *lconn->stats().current_round_trip_time_ms);
|
|
}
|
|
|
|
TEST_F(PortTest, TestUseCandidateAttribute) {
|
|
auto lport = CreateTestPort(kLocalAddr1, "lfrag", "lpass");
|
|
auto rport = CreateTestPort(kLocalAddr2, "rfrag", "rpass");
|
|
lport->SetIceRole(cricket::ICEROLE_CONTROLLING);
|
|
lport->SetIceTiebreaker(kTiebreaker1);
|
|
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, kDefaultTimeout);
|
|
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);
|
|
}
|
|
|
|
// Tests that when the network type changes, the network cost of the port will
|
|
// change, the network cost of the local candidates will change. Also tests that
|
|
// the remote network costs are updated with the stun binding requests.
|
|
TEST_F(PortTest, TestNetworkCostChange) {
|
|
rtc::Network* test_network = MakeNetwork(kLocalAddr1);
|
|
auto lport = CreateTestPort(test_network, "lfrag", "lpass");
|
|
auto rport = CreateTestPort(test_network, "rfrag", "rpass");
|
|
lport->SetIceRole(cricket::ICEROLE_CONTROLLING);
|
|
lport->SetIceTiebreaker(kTiebreaker1);
|
|
rport->SetIceRole(cricket::ICEROLE_CONTROLLED);
|
|
rport->SetIceTiebreaker(kTiebreaker2);
|
|
lport->PrepareAddress();
|
|
rport->PrepareAddress();
|
|
|
|
// Default local port cost is rtc::kNetworkCostUnknown.
|
|
EXPECT_EQ(rtc::kNetworkCostUnknown, lport->network_cost());
|
|
ASSERT_TRUE(!lport->Candidates().empty());
|
|
for (const cricket::Candidate& candidate : lport->Candidates()) {
|
|
EXPECT_EQ(rtc::kNetworkCostUnknown, candidate.network_cost());
|
|
}
|
|
|
|
// Change the network type to wifi.
|
|
test_network->set_type(rtc::ADAPTER_TYPE_WIFI);
|
|
EXPECT_EQ(rtc::kNetworkCostLow, lport->network_cost());
|
|
for (const cricket::Candidate& candidate : lport->Candidates()) {
|
|
EXPECT_EQ(rtc::kNetworkCostLow, candidate.network_cost());
|
|
}
|
|
|
|
// Add a connection and then change the network type.
|
|
Connection* lconn =
|
|
lport->CreateConnection(rport->Candidates()[0], Port::ORIGIN_MESSAGE);
|
|
// Change the network type to cellular.
|
|
test_network->set_type(rtc::ADAPTER_TYPE_CELLULAR);
|
|
EXPECT_EQ(rtc::kNetworkCostHigh, lport->network_cost());
|
|
for (const cricket::Candidate& candidate : lport->Candidates()) {
|
|
EXPECT_EQ(rtc::kNetworkCostHigh, candidate.network_cost());
|
|
}
|
|
|
|
test_network->set_type(rtc::ADAPTER_TYPE_WIFI);
|
|
Connection* rconn =
|
|
rport->CreateConnection(lport->Candidates()[0], Port::ORIGIN_MESSAGE);
|
|
test_network->set_type(rtc::ADAPTER_TYPE_CELLULAR);
|
|
lconn->Ping(0);
|
|
// The rconn's remote candidate cost is rtc::kNetworkCostLow, but the ping
|
|
// contains an attribute of network cost of rtc::kNetworkCostHigh. Once the
|
|
// message is handled in rconn, The rconn's remote candidate will have cost
|
|
// rtc::kNetworkCostHigh;
|
|
EXPECT_EQ(rtc::kNetworkCostLow, rconn->remote_candidate().network_cost());
|
|
ASSERT_TRUE_WAIT(lport->last_stun_msg() != NULL, kDefaultTimeout);
|
|
IceMessage* msg = lport->last_stun_msg();
|
|
EXPECT_EQ(STUN_BINDING_REQUEST, msg->type());
|
|
// Pass the binding request to rport.
|
|
rconn->OnReadPacket(lport->last_stun_buf()->data<char>(),
|
|
lport->last_stun_buf()->size(), /* packet_time_us */ -1);
|
|
// Wait until rport sends the response and then check the remote network cost.
|
|
ASSERT_TRUE_WAIT(rport->last_stun_msg() != NULL, kDefaultTimeout);
|
|
EXPECT_EQ(rtc::kNetworkCostHigh, rconn->remote_candidate().network_cost());
|
|
}
|
|
|
|
TEST_F(PortTest, TestNetworkInfoAttribute) {
|
|
rtc::Network* test_network = MakeNetwork(kLocalAddr1);
|
|
auto lport = CreateTestPort(test_network, "lfrag", "lpass");
|
|
auto rport = CreateTestPort(test_network, "rfrag", "rpass");
|
|
lport->SetIceRole(cricket::ICEROLE_CONTROLLING);
|
|
lport->SetIceTiebreaker(kTiebreaker1);
|
|
rport->SetIceRole(cricket::ICEROLE_CONTROLLED);
|
|
rport->SetIceTiebreaker(kTiebreaker2);
|
|
|
|
uint16_t lnetwork_id = 9;
|
|
lport->Network()->set_id(lnetwork_id);
|
|
// Send a fake ping from lport to rport.
|
|
lport->PrepareAddress();
|
|
rport->PrepareAddress();
|
|
Connection* lconn =
|
|
lport->CreateConnection(rport->Candidates()[0], Port::ORIGIN_MESSAGE);
|
|
lconn->Ping(0);
|
|
ASSERT_TRUE_WAIT(lport->last_stun_msg() != NULL, kDefaultTimeout);
|
|
IceMessage* msg = lport->last_stun_msg();
|
|
const StunUInt32Attribute* network_info_attr =
|
|
msg->GetUInt32(STUN_ATTR_NETWORK_INFO);
|
|
ASSERT_TRUE(network_info_attr != NULL);
|
|
uint32_t network_info = network_info_attr->value();
|
|
EXPECT_EQ(lnetwork_id, network_info >> 16);
|
|
// Default network has unknown type and cost kNetworkCostUnknown.
|
|
EXPECT_EQ(rtc::kNetworkCostUnknown, network_info & 0xFFFF);
|
|
|
|
// Set the network type to be cellular so its cost will be kNetworkCostHigh.
|
|
// Send a fake ping from rport to lport.
|
|
test_network->set_type(rtc::ADAPTER_TYPE_CELLULAR);
|
|
uint16_t rnetwork_id = 8;
|
|
rport->Network()->set_id(rnetwork_id);
|
|
Connection* rconn =
|
|
rport->CreateConnection(lport->Candidates()[0], Port::ORIGIN_MESSAGE);
|
|
rconn->Ping(0);
|
|
ASSERT_TRUE_WAIT(rport->last_stun_msg() != NULL, kDefaultTimeout);
|
|
msg = rport->last_stun_msg();
|
|
network_info_attr = msg->GetUInt32(STUN_ATTR_NETWORK_INFO);
|
|
ASSERT_TRUE(network_info_attr != NULL);
|
|
network_info = network_info_attr->value();
|
|
EXPECT_EQ(rnetwork_id, network_info >> 16);
|
|
EXPECT_EQ(rtc::kNetworkCostHigh, network_info & 0xFFFF);
|
|
}
|
|
|
|
// Test handling STUN messages.
|
|
TEST_F(PortTest, TestHandleStunMessage) {
|
|
// Our port will act as the "remote" port.
|
|
auto port = CreateTestPort(kLocalAddr2, "rfrag", "rpass");
|
|
|
|
std::unique_ptr<IceMessage> in_msg, out_msg;
|
|
auto buf = std::make_unique<ByteBufferWriter>();
|
|
rtc::SocketAddress addr(kLocalAddr1);
|
|
std::string username;
|
|
|
|
// BINDING-REQUEST from local to remote with valid ICE username,
|
|
// MESSAGE-INTEGRITY, and FINGERPRINT.
|
|
in_msg = CreateStunMessageWithUsername(STUN_BINDING_REQUEST, "rfrag:lfrag");
|
|
in_msg->AddMessageIntegrity("rpass");
|
|
in_msg->AddFingerprint();
|
|
WriteStunMessage(*in_msg, buf.get());
|
|
EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, &out_msg,
|
|
&username));
|
|
EXPECT_TRUE(out_msg.get() != NULL);
|
|
EXPECT_EQ("lfrag", username);
|
|
|
|
// BINDING-RESPONSE without username, with MESSAGE-INTEGRITY and FINGERPRINT.
|
|
in_msg = CreateStunMessage(STUN_BINDING_RESPONSE);
|
|
in_msg->AddAttribute(std::make_unique<StunXorAddressAttribute>(
|
|
STUN_ATTR_XOR_MAPPED_ADDRESS, kLocalAddr2));
|
|
in_msg->AddMessageIntegrity("rpass");
|
|
in_msg->AddFingerprint();
|
|
WriteStunMessage(*in_msg, buf.get());
|
|
EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, &out_msg,
|
|
&username));
|
|
EXPECT_TRUE(out_msg.get() != NULL);
|
|
EXPECT_EQ("", username);
|
|
|
|
// BINDING-ERROR-RESPONSE without username, with error, M-I, and FINGERPRINT.
|
|
in_msg = CreateStunMessage(STUN_BINDING_ERROR_RESPONSE);
|
|
in_msg->AddAttribute(std::make_unique<StunErrorCodeAttribute>(
|
|
STUN_ATTR_ERROR_CODE, STUN_ERROR_SERVER_ERROR,
|
|
STUN_ERROR_REASON_SERVER_ERROR));
|
|
in_msg->AddFingerprint();
|
|
WriteStunMessage(*in_msg, buf.get());
|
|
EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, &out_msg,
|
|
&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 ICE binding requests with missing or incorrect usernames.
|
|
TEST_F(PortTest, TestHandleStunMessageBadUsername) {
|
|
auto port = CreateTestPort(kLocalAddr2, "rfrag", "rpass");
|
|
|
|
std::unique_ptr<IceMessage> in_msg, out_msg;
|
|
auto buf = std::make_unique<ByteBufferWriter>();
|
|
rtc::SocketAddress addr(kLocalAddr1);
|
|
std::string username;
|
|
|
|
// BINDING-REQUEST with no username.
|
|
in_msg = CreateStunMessage(STUN_BINDING_REQUEST);
|
|
in_msg->AddMessageIntegrity("rpass");
|
|
in_msg->AddFingerprint();
|
|
WriteStunMessage(*in_msg, buf.get());
|
|
EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, &out_msg,
|
|
&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 = CreateStunMessageWithUsername(STUN_BINDING_REQUEST, "");
|
|
in_msg->AddMessageIntegrity("rpass");
|
|
in_msg->AddFingerprint();
|
|
WriteStunMessage(*in_msg, buf.get());
|
|
EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, &out_msg,
|
|
&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 = CreateStunMessageWithUsername(STUN_BINDING_REQUEST, "rfra");
|
|
in_msg->AddMessageIntegrity("rpass");
|
|
in_msg->AddFingerprint();
|
|
WriteStunMessage(*in_msg, buf.get());
|
|
EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, &out_msg,
|
|
&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 = CreateStunMessageWithUsername(STUN_BINDING_REQUEST, "lfrag:rfrag");
|
|
in_msg->AddMessageIntegrity("rpass");
|
|
in_msg->AddFingerprint();
|
|
WriteStunMessage(*in_msg, buf.get());
|
|
EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, &out_msg,
|
|
&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 = CreateStunMessageWithUsername(STUN_BINDING_REQUEST, "abcd:efgh");
|
|
in_msg->AddMessageIntegrity("rpass");
|
|
in_msg->AddFingerprint();
|
|
WriteStunMessage(*in_msg, buf.get());
|
|
EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, &out_msg,
|
|
&username));
|
|
EXPECT_TRUE(out_msg.get() == NULL);
|
|
EXPECT_EQ("", username);
|
|
EXPECT_EQ(STUN_ERROR_UNAUTHORIZED, port->last_stun_error_code());
|
|
}
|
|
|
|
// Test handling STUN messages with missing or malformed M-I.
|
|
TEST_F(PortTest, TestHandleStunMessageBadMessageIntegrity) {
|
|
// Our port will act as the "remote" port.
|
|
auto port = CreateTestPort(kLocalAddr2, "rfrag", "rpass");
|
|
|
|
std::unique_ptr<IceMessage> in_msg, out_msg;
|
|
auto buf = std::make_unique<ByteBufferWriter>();
|
|
rtc::SocketAddress addr(kLocalAddr1);
|
|
std::string username;
|
|
|
|
// BINDING-REQUEST from local to remote with valid ICE username and
|
|
// FINGERPRINT, but no MESSAGE-INTEGRITY.
|
|
in_msg = CreateStunMessageWithUsername(STUN_BINDING_REQUEST, "rfrag:lfrag");
|
|
in_msg->AddFingerprint();
|
|
WriteStunMessage(*in_msg, buf.get());
|
|
EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, &out_msg,
|
|
&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 = CreateStunMessageWithUsername(STUN_BINDING_REQUEST, "rfrag:lfrag");
|
|
in_msg->AddMessageIntegrity("invalid");
|
|
in_msg->AddFingerprint();
|
|
WriteStunMessage(*in_msg, buf.get());
|
|
EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, &out_msg,
|
|
&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 with missing or malformed FINGERPRINT.
|
|
TEST_F(PortTest, TestHandleStunMessageBadFingerprint) {
|
|
// Our port will act as the "remote" port.
|
|
auto port = CreateTestPort(kLocalAddr2, "rfrag", "rpass");
|
|
|
|
std::unique_ptr<IceMessage> in_msg, out_msg;
|
|
auto buf = std::make_unique<ByteBufferWriter>();
|
|
rtc::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 = CreateStunMessageWithUsername(STUN_BINDING_REQUEST, "rfrag:lfrag");
|
|
in_msg->AddMessageIntegrity("rpass");
|
|
WriteStunMessage(*in_msg, buf.get());
|
|
EXPECT_FALSE(port->GetStunMessage(buf->Data(), buf->Length(), addr, &out_msg,
|
|
&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, buf.get());
|
|
EXPECT_FALSE(port->GetStunMessage(buf->Data(), buf->Length(), addr, &out_msg,
|
|
&username));
|
|
EXPECT_EQ(0, port->last_stun_error_code());
|
|
|
|
// Valid BINDING-RESPONSE, except no FINGERPRINT.
|
|
in_msg = CreateStunMessage(STUN_BINDING_RESPONSE);
|
|
in_msg->AddAttribute(std::make_unique<StunXorAddressAttribute>(
|
|
STUN_ATTR_XOR_MAPPED_ADDRESS, kLocalAddr2));
|
|
in_msg->AddMessageIntegrity("rpass");
|
|
WriteStunMessage(*in_msg, buf.get());
|
|
EXPECT_FALSE(port->GetStunMessage(buf->Data(), buf->Length(), addr, &out_msg,
|
|
&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, buf.get());
|
|
EXPECT_FALSE(port->GetStunMessage(buf->Data(), buf->Length(), addr, &out_msg,
|
|
&username));
|
|
EXPECT_EQ(0, port->last_stun_error_code());
|
|
|
|
// Valid BINDING-ERROR-RESPONSE, except no FINGERPRINT.
|
|
in_msg = CreateStunMessage(STUN_BINDING_ERROR_RESPONSE);
|
|
in_msg->AddAttribute(std::make_unique<StunErrorCodeAttribute>(
|
|
STUN_ATTR_ERROR_CODE, STUN_ERROR_SERVER_ERROR,
|
|
STUN_ERROR_REASON_SERVER_ERROR));
|
|
in_msg->AddMessageIntegrity("rpass");
|
|
WriteStunMessage(*in_msg, buf.get());
|
|
EXPECT_FALSE(port->GetStunMessage(buf->Data(), buf->Length(), addr, &out_msg,
|
|
&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, buf.get());
|
|
EXPECT_FALSE(port->GetStunMessage(buf->Data(), buf->Length(), addr, &out_msg,
|
|
&username));
|
|
EXPECT_EQ(0, port->last_stun_error_code());
|
|
}
|
|
|
|
// Test handling a STUN message with unknown attributes in the
|
|
// "comprehension-required" range. Should respond with an error with the
|
|
// unknown attributes' IDs.
|
|
TEST_F(PortTest,
|
|
TestHandleStunRequestWithUnknownComprehensionRequiredAttribute) {
|
|
// Our port will act as the "remote" port.
|
|
std::unique_ptr<TestPort> port(CreateTestPort(kLocalAddr2, "rfrag", "rpass"));
|
|
|
|
std::unique_ptr<IceMessage> in_msg, out_msg;
|
|
auto buf = std::make_unique<ByteBufferWriter>();
|
|
rtc::SocketAddress addr(kLocalAddr1);
|
|
std::string username;
|
|
|
|
// Build ordinary message with valid ufrag/pass.
|
|
in_msg = CreateStunMessageWithUsername(STUN_BINDING_REQUEST, "rfrag:lfrag");
|
|
in_msg->AddMessageIntegrity("rpass");
|
|
// Add a couple attributes with ID in comprehension-required range.
|
|
in_msg->AddAttribute(StunAttribute::CreateUInt32(0x7777));
|
|
in_msg->AddAttribute(StunAttribute::CreateUInt32(0x4567));
|
|
// ... And one outside the range.
|
|
in_msg->AddAttribute(StunAttribute::CreateUInt32(0xdead));
|
|
in_msg->AddFingerprint();
|
|
WriteStunMessage(*in_msg, buf.get());
|
|
ASSERT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, &out_msg,
|
|
&username));
|
|
IceMessage* error_response = port->last_stun_msg();
|
|
ASSERT_NE(nullptr, error_response);
|
|
|
|
// Verify that the "unknown attribute" error response has the right error
|
|
// code, and includes an attribute that lists out the unrecognized attribute
|
|
// types.
|
|
EXPECT_EQ(STUN_ERROR_UNKNOWN_ATTRIBUTE, error_response->GetErrorCodeValue());
|
|
const StunUInt16ListAttribute* unknown_attributes =
|
|
error_response->GetUnknownAttributes();
|
|
ASSERT_NE(nullptr, unknown_attributes);
|
|
ASSERT_EQ(2u, unknown_attributes->Size());
|
|
EXPECT_EQ(0x7777, unknown_attributes->GetType(0));
|
|
EXPECT_EQ(0x4567, unknown_attributes->GetType(1));
|
|
}
|
|
|
|
// Similar to the above, but with a response instead of a request. In this
|
|
// case the response should just be ignored and transaction treated is failed.
|
|
TEST_F(PortTest,
|
|
TestHandleStunResponseWithUnknownComprehensionRequiredAttribute) {
|
|
// Generic setup.
|
|
auto lport = CreateTestPort(kLocalAddr1, "lfrag", "lpass");
|
|
lport->SetIceRole(cricket::ICEROLE_CONTROLLING);
|
|
auto rport = CreateTestPort(kLocalAddr2, "rfrag", "rpass");
|
|
rport->SetIceRole(cricket::ICEROLE_CONTROLLED);
|
|
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);
|
|
|
|
// Send request.
|
|
lconn->Ping(0);
|
|
ASSERT_TRUE_WAIT(lport->last_stun_msg() != NULL, kDefaultTimeout);
|
|
rconn->OnReadPacket(lport->last_stun_buf()->data<char>(),
|
|
lport->last_stun_buf()->size(), /* packet_time_us */ -1);
|
|
|
|
// Intercept request and add comprehension required attribute.
|
|
ASSERT_TRUE_WAIT(rport->last_stun_msg() != NULL, kDefaultTimeout);
|
|
auto modified_response = rport->last_stun_msg()->Clone();
|
|
modified_response->AddAttribute(StunAttribute::CreateUInt32(0x7777));
|
|
modified_response->RemoveAttribute(STUN_ATTR_FINGERPRINT);
|
|
modified_response->AddFingerprint();
|
|
ByteBufferWriter buf;
|
|
WriteStunMessage(*modified_response, &buf);
|
|
lconn->OnReadPacket(buf.Data(), buf.Length(), /* packet_time_us */ -1);
|
|
// Response should have been ignored, leaving us unwritable still.
|
|
EXPECT_FALSE(lconn->writable());
|
|
}
|
|
|
|
// Similar to the above, but with an indication. As with a response, it should
|
|
// just be ignored.
|
|
TEST_F(PortTest,
|
|
TestHandleStunIndicationWithUnknownComprehensionRequiredAttribute) {
|
|
// Generic set up.
|
|
auto lport = CreateTestPort(kLocalAddr2, "lfrag", "lpass");
|
|
lport->SetIceRole(cricket::ICEROLE_CONTROLLING);
|
|
auto rport = CreateTestPort(kLocalAddr2, "rfrag", "rpass");
|
|
rport->SetIceRole(cricket::ICEROLE_CONTROLLED);
|
|
lport->PrepareAddress();
|
|
rport->PrepareAddress();
|
|
ASSERT_FALSE(lport->Candidates().empty());
|
|
ASSERT_FALSE(rport->Candidates().empty());
|
|
Connection* lconn =
|
|
lport->CreateConnection(rport->Candidates()[0], Port::ORIGIN_MESSAGE);
|
|
|
|
// Generate indication with comprehension required attribute and verify it
|
|
// doesn't update last_ping_received.
|
|
auto in_msg = CreateStunMessage(STUN_BINDING_INDICATION);
|
|
in_msg->AddAttribute(StunAttribute::CreateUInt32(0x7777));
|
|
in_msg->AddFingerprint();
|
|
ByteBufferWriter buf;
|
|
WriteStunMessage(*in_msg, &buf);
|
|
lconn->OnReadPacket(buf.Data(), buf.Length(), /* packet_time_us */ -1);
|
|
EXPECT_EQ(0u, lconn->last_ping_received());
|
|
}
|
|
|
|
// Test handling of STUN binding indication messages . STUN binding
|
|
// indications are allowed only to the connection which is in read mode.
|
|
TEST_F(PortTest, TestHandleStunBindingIndication) {
|
|
auto lport = CreateTestPort(kLocalAddr2, "lfrag", "lpass");
|
|
lport->SetIceRole(cricket::ICEROLE_CONTROLLING);
|
|
lport->SetIceTiebreaker(kTiebreaker1);
|
|
|
|
// Verifying encoding and decoding STUN indication message.
|
|
std::unique_ptr<IceMessage> in_msg, out_msg;
|
|
std::unique_ptr<ByteBufferWriter> buf(new ByteBufferWriter());
|
|
rtc::SocketAddress addr(kLocalAddr1);
|
|
std::string username;
|
|
|
|
in_msg = CreateStunMessage(STUN_BINDING_INDICATION);
|
|
in_msg->AddFingerprint();
|
|
WriteStunMessage(*in_msg, buf.get());
|
|
EXPECT_TRUE(lport->GetStunMessage(buf->Data(), buf->Length(), addr, &out_msg,
|
|
&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.
|
|
auto rport = CreateTestPort(kLocalAddr2, "rfrag", "rpass");
|
|
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, kDefaultTimeout);
|
|
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<char>(),
|
|
rport->last_stun_buf()->size(), /* packet_time_us */ -1);
|
|
ASSERT_TRUE_WAIT(lport->last_stun_msg() != NULL, kDefaultTimeout);
|
|
EXPECT_EQ(STUN_BINDING_RESPONSE, lport->last_stun_msg()->type());
|
|
int64_t last_ping_received1 = lconn->last_ping_received();
|
|
|
|
// Adding a delay of 100ms.
|
|
rtc::Thread::Current()->ProcessMessages(100);
|
|
// Pinging lconn using stun indication message.
|
|
lconn->OnReadPacket(buf->Data(), buf->Length(), /* packet_time_us */ -1);
|
|
int64_t last_ping_received2 = lconn->last_ping_received();
|
|
EXPECT_GT(last_ping_received2, last_ping_received1);
|
|
}
|
|
|
|
TEST_F(PortTest, TestComputeCandidatePriority) {
|
|
auto 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_t expected_priority_v4 = 1509957199U;
|
|
uint32_t expected_priority_v6 = 1509959759U;
|
|
uint32_t expected_priority_ula = 1509962319U;
|
|
uint32_t expected_priority_v4mapped = expected_priority_v4;
|
|
uint32_t expected_priority_v4compat = 1509949775U;
|
|
uint32_t expected_priority_6to4 = 1509954639U;
|
|
uint32_t expected_priority_teredo = 1509952079U;
|
|
uint32_t expected_priority_sitelocal = 1509949775U;
|
|
uint32_t 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());
|
|
}
|
|
|
|
// 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) {
|
|
auto 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) {
|
|
std::unique_ptr<rtc::NATServer> nat_server(
|
|
CreateNatServer(kNatAddr1, NAT_OPEN_CONE));
|
|
auto udpport1 = CreateUdpPort(kLocalAddr1);
|
|
udpport1->PrepareAddress();
|
|
auto udpport2 = CreateUdpPort(kLocalAddr1);
|
|
udpport2->PrepareAddress();
|
|
EXPECT_EQ(udpport1->Candidates()[0].foundation(),
|
|
udpport2->Candidates()[0].foundation());
|
|
auto tcpport1 = CreateTcpPort(kLocalAddr1);
|
|
tcpport1->PrepareAddress();
|
|
auto tcpport2 = CreateTcpPort(kLocalAddr1);
|
|
tcpport2->PrepareAddress();
|
|
EXPECT_EQ(tcpport1->Candidates()[0].foundation(),
|
|
tcpport2->Candidates()[0].foundation());
|
|
auto stunport = CreateStunPort(kLocalAddr1, nat_socket_factory1());
|
|
stunport->PrepareAddress();
|
|
ASSERT_EQ_WAIT(1U, stunport->Candidates().size(), kDefaultTimeout);
|
|
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());
|
|
// Verifying TURN candidate foundation.
|
|
auto turnport1 =
|
|
CreateTurnPort(kLocalAddr1, nat_socket_factory1(), PROTO_UDP, PROTO_UDP);
|
|
turnport1->PrepareAddress();
|
|
ASSERT_EQ_WAIT(1U, turnport1->Candidates().size(), kDefaultTimeout);
|
|
EXPECT_NE(udpport1->Candidates()[0].foundation(),
|
|
turnport1->Candidates()[0].foundation());
|
|
EXPECT_NE(udpport2->Candidates()[0].foundation(),
|
|
turnport1->Candidates()[0].foundation());
|
|
EXPECT_NE(stunport->Candidates()[0].foundation(),
|
|
turnport1->Candidates()[0].foundation());
|
|
auto turnport2 =
|
|
CreateTurnPort(kLocalAddr1, nat_socket_factory1(), PROTO_UDP, PROTO_UDP);
|
|
turnport2->PrepareAddress();
|
|
ASSERT_EQ_WAIT(1U, turnport2->Candidates().size(), kDefaultTimeout);
|
|
EXPECT_EQ(turnport1->Candidates()[0].foundation(),
|
|
turnport2->Candidates()[0].foundation());
|
|
|
|
// Running a second turn server, to get different base IP address.
|
|
SocketAddress kTurnUdpIntAddr2("99.99.98.4", STUN_SERVER_PORT);
|
|
SocketAddress kTurnUdpExtAddr2("99.99.98.5", 0);
|
|
TestTurnServer turn_server2(rtc::Thread::Current(), kTurnUdpIntAddr2,
|
|
kTurnUdpExtAddr2);
|
|
auto turnport3 = CreateTurnPort(kLocalAddr1, nat_socket_factory1(), PROTO_UDP,
|
|
PROTO_UDP, kTurnUdpIntAddr2);
|
|
turnport3->PrepareAddress();
|
|
ASSERT_EQ_WAIT(1U, turnport3->Candidates().size(), kDefaultTimeout);
|
|
EXPECT_NE(turnport3->Candidates()[0].foundation(),
|
|
turnport2->Candidates()[0].foundation());
|
|
|
|
// Start a TCP turn server, and check that two turn candidates have
|
|
// different foundations if their relay protocols are different.
|
|
TestTurnServer turn_server3(rtc::Thread::Current(), kTurnTcpIntAddr,
|
|
kTurnUdpExtAddr, PROTO_TCP);
|
|
auto turnport4 =
|
|
CreateTurnPort(kLocalAddr1, nat_socket_factory1(), PROTO_TCP, PROTO_UDP);
|
|
turnport4->PrepareAddress();
|
|
ASSERT_EQ_WAIT(1U, turnport4->Candidates().size(), kDefaultTimeout);
|
|
EXPECT_NE(turnport2->Candidates()[0].foundation(),
|
|
turnport4->Candidates()[0].foundation());
|
|
}
|
|
|
|
// This test verifies the related addresses of different types of
|
|
// ICE candiates.
|
|
TEST_F(PortTest, TestCandidateRelatedAddress) {
|
|
auto nat_server = CreateNatServer(kNatAddr1, NAT_OPEN_CONE);
|
|
auto 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.
|
|
auto stunport = CreateStunPort(kLocalAddr1, nat_socket_factory1());
|
|
stunport->PrepareAddress();
|
|
ASSERT_EQ_WAIT(1U, stunport->Candidates().size(), kDefaultTimeout);
|
|
// 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 TURN candidate.
|
|
// For TURN related address must be equal to the mapped address.
|
|
auto turnport =
|
|
CreateTurnPort(kLocalAddr1, nat_socket_factory1(), PROTO_UDP, PROTO_UDP);
|
|
turnport->PrepareAddress();
|
|
ASSERT_EQ_WAIT(1U, turnport->Candidates().size(), kDefaultTimeout);
|
|
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, TestCandidatePriority) {
|
|
cricket::Candidate cand1;
|
|
cand1.set_priority(3);
|
|
cricket::Candidate cand2;
|
|
cand2.set_priority(1);
|
|
EXPECT_TRUE(cand1.priority() > cand2.priority());
|
|
}
|
|
|
|
// Test the Connection priority is calculated correctly.
|
|
TEST_F(PortTest, TestConnectionPriority) {
|
|
auto lport = CreateTestPort(kLocalAddr1, "lfrag", "lpass");
|
|
lport->set_type_preference(cricket::ICE_TYPE_PREFERENCE_HOST);
|
|
auto rport = CreateTestPort(kLocalAddr2, "rfrag", "rpass");
|
|
rport->set_type_preference(cricket::ICE_TYPE_PREFERENCE_RELAY_UDP);
|
|
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(WEBRTC_WIN)
|
|
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(WEBRTC_WIN)
|
|
EXPECT_EQ(0x2001EE9FC003D0AU, rconn->priority());
|
|
#else
|
|
EXPECT_EQ(0x2001EE9FC003D0ALLU, rconn->priority());
|
|
#endif
|
|
}
|
|
|
|
// Note that UpdateState takes into account the estimated RTT, and the
|
|
// correctness of using |kMaxExpectedSimulatedRtt| as an upper bound of RTT in
|
|
// the following tests depends on the link rate and the delay distriubtion
|
|
// configured in VirtualSocketServer::AddPacketToNetwork. The tests below use
|
|
// the default setup where the RTT is deterministically one, which generates an
|
|
// estimate given by |MINIMUM_RTT| = 100.
|
|
TEST_F(PortTest, TestWritableState) {
|
|
rtc::ScopedFakeClock clock;
|
|
auto port1 = CreateUdpPort(kLocalAddr1);
|
|
port1->SetIceRole(cricket::ICEROLE_CONTROLLING);
|
|
auto port2 = CreateUdpPort(kLocalAddr2);
|
|
port2->SetIceRole(cricket::ICEROLE_CONTROLLED);
|
|
|
|
// Set up channels.
|
|
TestChannel ch1(std::move(port1));
|
|
TestChannel ch2(std::move(port2));
|
|
|
|
// Acquire addresses.
|
|
ch1.Start();
|
|
ch2.Start();
|
|
ASSERT_EQ_SIMULATED_WAIT(1, ch1.complete_count(), kDefaultTimeout, clock);
|
|
ASSERT_EQ_SIMULATED_WAIT(1, ch2.complete_count(), kDefaultTimeout, clock);
|
|
|
|
// Send a ping from src to dst.
|
|
ch1.CreateConnection(GetCandidate(ch2.port()));
|
|
ASSERT_TRUE(ch1.conn() != NULL);
|
|
EXPECT_EQ(Connection::STATE_WRITE_INIT, ch1.conn()->write_state());
|
|
// for TCP connect
|
|
EXPECT_TRUE_SIMULATED_WAIT(ch1.conn()->connected(), kDefaultTimeout, clock);
|
|
ch1.Ping();
|
|
SIMULATED_WAIT(!ch2.remote_address().IsNil(), kShortTimeout, clock);
|
|
|
|
// Data should be sendable before the connection is accepted.
|
|
char data[] = "abcd";
|
|
int data_size = arraysize(data);
|
|
rtc::PacketOptions options;
|
|
EXPECT_EQ(data_size, ch1.conn()->Send(data, data_size, options));
|
|
|
|
// Accept the connection to return the binding response, transition to
|
|
// writable, and allow data to be sent.
|
|
ch2.AcceptConnection(GetCandidate(ch1.port()));
|
|
EXPECT_EQ_SIMULATED_WAIT(Connection::STATE_WRITABLE,
|
|
ch1.conn()->write_state(), kDefaultTimeout, clock);
|
|
EXPECT_EQ(data_size, ch1.conn()->Send(data, data_size, options));
|
|
|
|
// 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_t i = 1; i <= CONNECTION_WRITE_CONNECT_FAILURES; ++i) {
|
|
ch1.Ping(i);
|
|
}
|
|
int unreliable_timeout_delay =
|
|
CONNECTION_WRITE_CONNECT_TIMEOUT + kMaxExpectedSimulatedRtt;
|
|
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, options));
|
|
|
|
// And now allow the other side to process the pings and send binding
|
|
// responses.
|
|
EXPECT_EQ_SIMULATED_WAIT(Connection::STATE_WRITABLE,
|
|
ch1.conn()->write_state(), kDefaultTimeout, clock);
|
|
// Wait long enough for a full timeout (past however long we've already
|
|
// waited).
|
|
for (uint32_t i = 1; i <= CONNECTION_WRITE_CONNECT_FAILURES; ++i) {
|
|
ch1.Ping(unreliable_timeout_delay + i);
|
|
}
|
|
ch1.conn()->UpdateState(unreliable_timeout_delay + CONNECTION_WRITE_TIMEOUT +
|
|
kMaxExpectedSimulatedRtt);
|
|
EXPECT_EQ(Connection::STATE_WRITE_TIMEOUT, ch1.conn()->write_state());
|
|
|
|
// Even if the connection has timed out, the Connection shouldn't block
|
|
// the sending of data.
|
|
EXPECT_EQ(data_size, ch1.conn()->Send(data, data_size, options));
|
|
|
|
ch1.Stop();
|
|
ch2.Stop();
|
|
}
|
|
|
|
// Test writability states using the configured threshold value to replace
|
|
// the default value given by |CONNECTION_WRITE_CONNECT_TIMEOUT| and
|
|
// |CONNECTION_WRITE_CONNECT_FAILURES|.
|
|
TEST_F(PortTest, TestWritableStateWithConfiguredThreshold) {
|
|
rtc::ScopedFakeClock clock;
|
|
auto port1 = CreateUdpPort(kLocalAddr1);
|
|
port1->SetIceRole(cricket::ICEROLE_CONTROLLING);
|
|
auto port2 = CreateUdpPort(kLocalAddr2);
|
|
port2->SetIceRole(cricket::ICEROLE_CONTROLLED);
|
|
|
|
// Set up channels.
|
|
TestChannel ch1(std::move(port1));
|
|
TestChannel ch2(std::move(port2));
|
|
|
|
// Acquire addresses.
|
|
ch1.Start();
|
|
ch2.Start();
|
|
ASSERT_EQ_SIMULATED_WAIT(1, ch1.complete_count(), kDefaultTimeout, clock);
|
|
ASSERT_EQ_SIMULATED_WAIT(1, ch2.complete_count(), kDefaultTimeout, clock);
|
|
|
|
// Send a ping from src to dst.
|
|
ch1.CreateConnection(GetCandidate(ch2.port()));
|
|
ASSERT_TRUE(ch1.conn() != NULL);
|
|
ch1.Ping();
|
|
SIMULATED_WAIT(!ch2.remote_address().IsNil(), kShortTimeout, clock);
|
|
|
|
// Accept the connection to return the binding response, transition to
|
|
// writable, and allow data to be sent.
|
|
ch2.AcceptConnection(GetCandidate(ch1.port()));
|
|
EXPECT_EQ_SIMULATED_WAIT(Connection::STATE_WRITABLE,
|
|
ch1.conn()->write_state(), kDefaultTimeout, clock);
|
|
|
|
ch1.conn()->set_unwritable_timeout(1000);
|
|
ch1.conn()->set_unwritable_min_checks(3);
|
|
// Send two checks.
|
|
ch1.Ping(1);
|
|
ch1.Ping(2);
|
|
// We have not reached the timeout nor have we sent the minimum number of
|
|
// checks to change the state to Unreliable.
|
|
ch1.conn()->UpdateState(999);
|
|
EXPECT_EQ(Connection::STATE_WRITABLE, ch1.conn()->write_state());
|
|
// We have not sent the minimum number of checks without responses.
|
|
ch1.conn()->UpdateState(1000 + kMaxExpectedSimulatedRtt);
|
|
EXPECT_EQ(Connection::STATE_WRITABLE, ch1.conn()->write_state());
|
|
// Last ping after which the candidate pair should become Unreliable after
|
|
// timeout.
|
|
ch1.Ping(3);
|
|
// We have not reached the timeout.
|
|
ch1.conn()->UpdateState(999);
|
|
EXPECT_EQ(Connection::STATE_WRITABLE, ch1.conn()->write_state());
|
|
// We should be in the state Unreliable now.
|
|
ch1.conn()->UpdateState(1000 + kMaxExpectedSimulatedRtt);
|
|
EXPECT_EQ(Connection::STATE_WRITE_UNRELIABLE, ch1.conn()->write_state());
|
|
|
|
ch1.Stop();
|
|
ch2.Stop();
|
|
}
|
|
|
|
TEST_F(PortTest, TestTimeoutForNeverWritable) {
|
|
auto port1 = CreateUdpPort(kLocalAddr1);
|
|
port1->SetIceRole(cricket::ICEROLE_CONTROLLING);
|
|
auto port2 = CreateUdpPort(kLocalAddr2);
|
|
port2->SetIceRole(cricket::ICEROLE_CONTROLLED);
|
|
|
|
// Set up channels.
|
|
TestChannel ch1(std::move(port1));
|
|
TestChannel ch2(std::move(port2));
|
|
|
|
// Acquire addresses.
|
|
ch1.Start();
|
|
ch2.Start();
|
|
|
|
ch1.CreateConnection(GetCandidate(ch2.port()));
|
|
ASSERT_TRUE(ch1.conn() != NULL);
|
|
EXPECT_EQ(Connection::STATE_WRITE_INIT, ch1.conn()->write_state());
|
|
|
|
// Attempt to go directly to write timeout.
|
|
for (uint32_t i = 1; i <= CONNECTION_WRITE_CONNECT_FAILURES; ++i) {
|
|
ch1.Ping(i);
|
|
}
|
|
ch1.conn()->UpdateState(CONNECTION_WRITE_TIMEOUT + kMaxExpectedSimulatedRtt);
|
|
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) {
|
|
auto ice_full_port =
|
|
CreateTestPort(kLocalAddr1, "lfrag", "lpass",
|
|
cricket::ICEROLE_CONTROLLING, kTiebreaker1);
|
|
auto* ice_full_port_ptr = ice_full_port.get();
|
|
|
|
auto ice_lite_port = CreateTestPort(
|
|
kLocalAddr2, "rfrag", "rpass", cricket::ICEROLE_CONTROLLED, kTiebreaker2);
|
|
// Setup TestChannel. This behaves like FULL mode client.
|
|
TestChannel ch1(std::move(ice_full_port));
|
|
ch1.SetIceMode(ICEMODE_FULL);
|
|
|
|
// Start gathering candidates.
|
|
ch1.Start();
|
|
ice_lite_port->PrepareAddress();
|
|
|
|
ASSERT_EQ_WAIT(1, ch1.complete_count(), kDefaultTimeout);
|
|
ASSERT_FALSE(ice_lite_port->Candidates().empty());
|
|
|
|
ch1.CreateConnection(GetCandidate(ice_lite_port.get()));
|
|
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_ptr->last_stun_msg() != NULL, kDefaultTimeout);
|
|
IceMessage* msg = ice_full_port_ptr->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.
|
|
auto* con = ice_lite_port->CreateConnection(
|
|
ice_full_port_ptr->Candidates()[0], cricket::Port::ORIGIN_MESSAGE);
|
|
std::unique_ptr<IceMessage> request = CopyStunMessage(*msg);
|
|
con->SendStunBindingResponse(request.get());
|
|
|
|
// Feeding the respone message from litemode to the full mode connection.
|
|
ch1.conn()->OnReadPacket(ice_lite_port->last_stun_buf()->data<char>(),
|
|
ice_lite_port->last_stun_buf()->size(),
|
|
/* packet_time_us */ -1);
|
|
// Verifying full mode connection becomes writable from the response.
|
|
EXPECT_EQ_WAIT(Connection::STATE_WRITABLE, ch1.conn()->write_state(),
|
|
kDefaultTimeout);
|
|
EXPECT_TRUE_WAIT(ch1.nominated(), kDefaultTimeout);
|
|
|
|
// Clear existing stun messsages. Otherwise we will process old stun
|
|
// message right after we send ping.
|
|
ice_full_port_ptr->Reset();
|
|
// Send ping. This must have USE_CANDIDATE_ATTR.
|
|
ch1.Ping();
|
|
ASSERT_TRUE_WAIT(ice_full_port_ptr->last_stun_msg() != NULL, kDefaultTimeout);
|
|
msg = ice_full_port_ptr->last_stun_msg();
|
|
EXPECT_TRUE(msg->GetByteString(STUN_ATTR_USE_CANDIDATE) != NULL);
|
|
ch1.Stop();
|
|
}
|
|
|
|
namespace {
|
|
|
|
// Utility function for testing goog ping.
|
|
absl::optional<int> GetSupportedGoogPingVersion(const StunMessage* msg) {
|
|
auto goog_misc = msg->GetUInt16List(STUN_ATTR_GOOG_MISC_INFO);
|
|
if (goog_misc == nullptr) {
|
|
return absl::nullopt;
|
|
}
|
|
|
|
if (msg->type() == STUN_BINDING_REQUEST) {
|
|
if (goog_misc->Size() <
|
|
static_cast<int>(cricket::IceGoogMiscInfoBindingRequestAttributeIndex::
|
|
SUPPORT_GOOG_PING_VERSION)) {
|
|
return absl::nullopt;
|
|
}
|
|
|
|
return goog_misc->GetType(
|
|
static_cast<int>(cricket::IceGoogMiscInfoBindingRequestAttributeIndex::
|
|
SUPPORT_GOOG_PING_VERSION));
|
|
}
|
|
|
|
if (msg->type() == STUN_BINDING_RESPONSE) {
|
|
if (goog_misc->Size() <
|
|
static_cast<int>(cricket::IceGoogMiscInfoBindingResponseAttributeIndex::
|
|
SUPPORT_GOOG_PING_VERSION)) {
|
|
return absl::nullopt;
|
|
}
|
|
|
|
return goog_misc->GetType(
|
|
static_cast<int>(cricket::IceGoogMiscInfoBindingResponseAttributeIndex::
|
|
SUPPORT_GOOG_PING_VERSION));
|
|
}
|
|
return absl::nullopt;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
class GoogPingTest
|
|
: public PortTest,
|
|
public ::testing::WithParamInterface<std::pair<bool, bool>> {};
|
|
|
|
// This test verifies the announce/enable on/off behavior
|
|
TEST_P(GoogPingTest, TestGoogPingAnnounceEnable) {
|
|
IceFieldTrials trials;
|
|
trials.announce_goog_ping = GetParam().first;
|
|
trials.enable_goog_ping = GetParam().second;
|
|
RTC_LOG(LS_INFO) << "Testing combination: "
|
|
" announce: "
|
|
<< trials.announce_goog_ping
|
|
<< " enable:" << trials.enable_goog_ping;
|
|
|
|
auto port1_unique =
|
|
CreateTestPort(kLocalAddr1, "lfrag", "lpass",
|
|
cricket::ICEROLE_CONTROLLING, kTiebreaker1);
|
|
auto* port1 = port1_unique.get();
|
|
auto port2 = CreateTestPort(kLocalAddr2, "rfrag", "rpass",
|
|
cricket::ICEROLE_CONTROLLED, kTiebreaker2);
|
|
|
|
TestChannel ch1(std::move(port1_unique));
|
|
// Block usage of STUN_ATTR_USE_CANDIDATE so that
|
|
// ch1.conn() will sent GOOG_PING_REQUEST directly.
|
|
// This only makes test a bit shorter...
|
|
ch1.SetIceMode(ICEMODE_LITE);
|
|
// Start gathering candidates.
|
|
ch1.Start();
|
|
port2->PrepareAddress();
|
|
|
|
ASSERT_EQ_WAIT(1, ch1.complete_count(), kDefaultTimeout);
|
|
ASSERT_FALSE(port2->Candidates().empty());
|
|
|
|
ch1.CreateConnection(GetCandidate(port2.get()));
|
|
ASSERT_TRUE(ch1.conn() != NULL);
|
|
EXPECT_EQ(Connection::STATE_WRITE_INIT, ch1.conn()->write_state());
|
|
ch1.conn()->SetIceFieldTrials(&trials);
|
|
|
|
// Send ping.
|
|
ch1.Ping();
|
|
|
|
ASSERT_TRUE_WAIT(port1->last_stun_msg() != NULL, kDefaultTimeout);
|
|
const IceMessage* request1 = port1->last_stun_msg();
|
|
|
|
ASSERT_EQ(trials.enable_goog_ping,
|
|
GetSupportedGoogPingVersion(request1) &&
|
|
GetSupportedGoogPingVersion(request1) >= kGoogPingVersion);
|
|
|
|
auto* con = port2->CreateConnection(port1->Candidates()[0],
|
|
cricket::Port::ORIGIN_MESSAGE);
|
|
con->SetIceFieldTrials(&trials);
|
|
|
|
con->SendStunBindingResponse(request1);
|
|
|
|
// Then check the response matches the settings.
|
|
const auto* response = port2->last_stun_msg();
|
|
EXPECT_EQ(response->type(), STUN_BINDING_RESPONSE);
|
|
EXPECT_EQ(trials.enable_goog_ping && trials.announce_goog_ping,
|
|
GetSupportedGoogPingVersion(response) &&
|
|
GetSupportedGoogPingVersion(response) >= kGoogPingVersion);
|
|
|
|
// Feeding the respone message back.
|
|
ch1.conn()->OnReadPacket(port2->last_stun_buf()->data<char>(),
|
|
port2->last_stun_buf()->size(),
|
|
/* packet_time_us */ -1);
|
|
|
|
port1->Reset();
|
|
port2->Reset();
|
|
|
|
ch1.Ping();
|
|
ASSERT_TRUE_WAIT(port1->last_stun_msg() != NULL, kDefaultTimeout);
|
|
const IceMessage* request2 = port1->last_stun_msg();
|
|
|
|
// It should be a GOOG_PING if both of these are TRUE
|
|
if (trials.announce_goog_ping && trials.enable_goog_ping) {
|
|
ASSERT_EQ(request2->type(), GOOG_PING_REQUEST);
|
|
con->SendGoogPingResponse(request2);
|
|
} else {
|
|
ASSERT_EQ(request2->type(), STUN_BINDING_REQUEST);
|
|
// If we sent a BINDING with enable, and we got a reply that
|
|
// didn't contain announce, the next ping should not contain
|
|
// the enable again.
|
|
ASSERT_FALSE(GetSupportedGoogPingVersion(request2).has_value());
|
|
con->SendStunBindingResponse(request2);
|
|
}
|
|
|
|
const auto* response2 = port2->last_stun_msg();
|
|
ASSERT_TRUE(response2 != nullptr);
|
|
|
|
// It should be a GOOG_PING_RESPONSE if both of these are TRUE
|
|
if (trials.announce_goog_ping && trials.enable_goog_ping) {
|
|
ASSERT_EQ(response2->type(), GOOG_PING_RESPONSE);
|
|
} else {
|
|
ASSERT_EQ(response2->type(), STUN_BINDING_RESPONSE);
|
|
}
|
|
|
|
ch1.Stop();
|
|
}
|
|
|
|
// This test if a someone send a STUN_BINDING with unsupported version
|
|
// (kGoogPingVersion == 0)
|
|
TEST_F(PortTest, TestGoogPingUnsupportedVersionInStunBinding) {
|
|
IceFieldTrials trials;
|
|
trials.announce_goog_ping = true;
|
|
trials.enable_goog_ping = true;
|
|
|
|
auto port1_unique =
|
|
CreateTestPort(kLocalAddr1, "lfrag", "lpass",
|
|
cricket::ICEROLE_CONTROLLING, kTiebreaker1);
|
|
auto* port1 = port1_unique.get();
|
|
auto port2 = CreateTestPort(kLocalAddr2, "rfrag", "rpass",
|
|
cricket::ICEROLE_CONTROLLED, kTiebreaker2);
|
|
|
|
TestChannel ch1(std::move(port1_unique));
|
|
// Block usage of STUN_ATTR_USE_CANDIDATE so that
|
|
// ch1.conn() will sent GOOG_PING_REQUEST directly.
|
|
// This only makes test a bit shorter...
|
|
ch1.SetIceMode(ICEMODE_LITE);
|
|
// Start gathering candidates.
|
|
ch1.Start();
|
|
port2->PrepareAddress();
|
|
|
|
ASSERT_EQ_WAIT(1, ch1.complete_count(), kDefaultTimeout);
|
|
ASSERT_FALSE(port2->Candidates().empty());
|
|
|
|
ch1.CreateConnection(GetCandidate(port2.get()));
|
|
ASSERT_TRUE(ch1.conn() != NULL);
|
|
EXPECT_EQ(Connection::STATE_WRITE_INIT, ch1.conn()->write_state());
|
|
ch1.conn()->SetIceFieldTrials(&trials);
|
|
|
|
// Send ping.
|
|
ch1.Ping();
|
|
|
|
ASSERT_TRUE_WAIT(port1->last_stun_msg() != NULL, kDefaultTimeout);
|
|
const IceMessage* request1 = port1->last_stun_msg();
|
|
|
|
ASSERT_TRUE(GetSupportedGoogPingVersion(request1) &&
|
|
GetSupportedGoogPingVersion(request1) >= kGoogPingVersion);
|
|
|
|
// Modify the STUN message request1 to send GetSupportedGoogPingVersion == 0
|
|
auto modified_request1 = request1->Clone();
|
|
ASSERT_TRUE(modified_request1->RemoveAttribute(STUN_ATTR_GOOG_MISC_INFO) !=
|
|
nullptr);
|
|
ASSERT_TRUE(modified_request1->RemoveAttribute(STUN_ATTR_MESSAGE_INTEGRITY) !=
|
|
nullptr);
|
|
{
|
|
auto list =
|
|
StunAttribute::CreateUInt16ListAttribute(STUN_ATTR_GOOG_MISC_INFO);
|
|
list->AddTypeAtIndex(
|
|
static_cast<uint16_t>(
|
|
cricket::IceGoogMiscInfoBindingRequestAttributeIndex::
|
|
SUPPORT_GOOG_PING_VERSION),
|
|
/* version */ 0);
|
|
modified_request1->AddAttribute(std::move(list));
|
|
modified_request1->AddMessageIntegrity("rpass");
|
|
}
|
|
auto* con = port2->CreateConnection(port1->Candidates()[0],
|
|
cricket::Port::ORIGIN_MESSAGE);
|
|
con->SetIceFieldTrials(&trials);
|
|
|
|
con->SendStunBindingResponse(modified_request1.get());
|
|
|
|
// Then check the response matches the settings.
|
|
const auto* response = port2->last_stun_msg();
|
|
EXPECT_EQ(response->type(), STUN_BINDING_RESPONSE);
|
|
EXPECT_FALSE(GetSupportedGoogPingVersion(response));
|
|
|
|
ch1.Stop();
|
|
}
|
|
|
|
// This test if a someone send a STUN_BINDING_RESPONSE with unsupported version
|
|
// (kGoogPingVersion == 0)
|
|
TEST_F(PortTest, TestGoogPingUnsupportedVersionInStunBindingResponse) {
|
|
IceFieldTrials trials;
|
|
trials.announce_goog_ping = true;
|
|
trials.enable_goog_ping = true;
|
|
|
|
auto port1_unique =
|
|
CreateTestPort(kLocalAddr1, "lfrag", "lpass",
|
|
cricket::ICEROLE_CONTROLLING, kTiebreaker1);
|
|
auto* port1 = port1_unique.get();
|
|
auto port2 = CreateTestPort(kLocalAddr2, "rfrag", "rpass",
|
|
cricket::ICEROLE_CONTROLLED, kTiebreaker2);
|
|
|
|
TestChannel ch1(std::move(port1_unique));
|
|
// Block usage of STUN_ATTR_USE_CANDIDATE so that
|
|
// ch1.conn() will sent GOOG_PING_REQUEST directly.
|
|
// This only makes test a bit shorter...
|
|
ch1.SetIceMode(ICEMODE_LITE);
|
|
// Start gathering candidates.
|
|
ch1.Start();
|
|
port2->PrepareAddress();
|
|
|
|
ASSERT_EQ_WAIT(1, ch1.complete_count(), kDefaultTimeout);
|
|
ASSERT_FALSE(port2->Candidates().empty());
|
|
|
|
ch1.CreateConnection(GetCandidate(port2.get()));
|
|
ASSERT_TRUE(ch1.conn() != NULL);
|
|
EXPECT_EQ(Connection::STATE_WRITE_INIT, ch1.conn()->write_state());
|
|
ch1.conn()->SetIceFieldTrials(&trials);
|
|
|
|
// Send ping.
|
|
ch1.Ping();
|
|
|
|
ASSERT_TRUE_WAIT(port1->last_stun_msg() != NULL, kDefaultTimeout);
|
|
const IceMessage* request1 = port1->last_stun_msg();
|
|
|
|
ASSERT_TRUE(GetSupportedGoogPingVersion(request1) &&
|
|
GetSupportedGoogPingVersion(request1) >= kGoogPingVersion);
|
|
|
|
auto* con = port2->CreateConnection(port1->Candidates()[0],
|
|
cricket::Port::ORIGIN_MESSAGE);
|
|
con->SetIceFieldTrials(&trials);
|
|
|
|
con->SendStunBindingResponse(request1);
|
|
|
|
// Then check the response matches the settings.
|
|
const auto* response = port2->last_stun_msg();
|
|
EXPECT_EQ(response->type(), STUN_BINDING_RESPONSE);
|
|
EXPECT_TRUE(GetSupportedGoogPingVersion(response));
|
|
|
|
// Modify the STUN message response to contain GetSupportedGoogPingVersion ==
|
|
// 0
|
|
auto modified_response = response->Clone();
|
|
ASSERT_TRUE(modified_response->RemoveAttribute(STUN_ATTR_GOOG_MISC_INFO) !=
|
|
nullptr);
|
|
ASSERT_TRUE(modified_response->RemoveAttribute(STUN_ATTR_MESSAGE_INTEGRITY) !=
|
|
nullptr);
|
|
ASSERT_TRUE(modified_response->RemoveAttribute(STUN_ATTR_FINGERPRINT) !=
|
|
nullptr);
|
|
{
|
|
auto list =
|
|
StunAttribute::CreateUInt16ListAttribute(STUN_ATTR_GOOG_MISC_INFO);
|
|
list->AddTypeAtIndex(
|
|
static_cast<uint16_t>(
|
|
cricket::IceGoogMiscInfoBindingResponseAttributeIndex::
|
|
SUPPORT_GOOG_PING_VERSION),
|
|
/* version */ 0);
|
|
modified_response->AddAttribute(std::move(list));
|
|
modified_response->AddMessageIntegrity("rpass");
|
|
modified_response->AddFingerprint();
|
|
}
|
|
|
|
rtc::ByteBufferWriter buf;
|
|
modified_response->Write(&buf);
|
|
|
|
// Feeding the modified respone message back.
|
|
ch1.conn()->OnReadPacket(buf.Data(), buf.Length(), /* packet_time_us */ -1);
|
|
|
|
port1->Reset();
|
|
port2->Reset();
|
|
|
|
ch1.Ping();
|
|
ASSERT_TRUE_WAIT(port1->last_stun_msg() != NULL, kDefaultTimeout);
|
|
|
|
// This should now be a STUN_BINDING...without a kGoogPingVersion
|
|
const IceMessage* request2 = port1->last_stun_msg();
|
|
EXPECT_EQ(request2->type(), STUN_BINDING_REQUEST);
|
|
EXPECT_FALSE(GetSupportedGoogPingVersion(request2));
|
|
|
|
ch1.Stop();
|
|
}
|
|
|
|
INSTANTIATE_TEST_SUITE_P(GoogPingTest,
|
|
GoogPingTest,
|
|
// test all combinations of <announce, enable> pairs.
|
|
::testing::Values(std::make_pair(false, false),
|
|
std::make_pair(true, false),
|
|
std::make_pair(false, true),
|
|
std::make_pair(true, true)));
|
|
|
|
// This test checks that a change in attributes falls back to STUN_BINDING
|
|
TEST_F(PortTest, TestChangeInAttributeMakesGoogPingFallsbackToStunBinding) {
|
|
IceFieldTrials trials;
|
|
trials.announce_goog_ping = true;
|
|
trials.enable_goog_ping = true;
|
|
|
|
auto port1_unique =
|
|
CreateTestPort(kLocalAddr1, "lfrag", "lpass",
|
|
cricket::ICEROLE_CONTROLLING, kTiebreaker1);
|
|
auto* port1 = port1_unique.get();
|
|
auto port2 = CreateTestPort(kLocalAddr2, "rfrag", "rpass",
|
|
cricket::ICEROLE_CONTROLLED, kTiebreaker2);
|
|
|
|
TestChannel ch1(std::move(port1_unique));
|
|
// Block usage of STUN_ATTR_USE_CANDIDATE so that
|
|
// ch1.conn() will sent GOOG_PING_REQUEST directly.
|
|
// This only makes test a bit shorter...
|
|
ch1.SetIceMode(ICEMODE_LITE);
|
|
// Start gathering candidates.
|
|
ch1.Start();
|
|
port2->PrepareAddress();
|
|
|
|
ASSERT_EQ_WAIT(1, ch1.complete_count(), kDefaultTimeout);
|
|
ASSERT_FALSE(port2->Candidates().empty());
|
|
|
|
ch1.CreateConnection(GetCandidate(port2.get()));
|
|
ASSERT_TRUE(ch1.conn() != nullptr);
|
|
EXPECT_EQ(Connection::STATE_WRITE_INIT, ch1.conn()->write_state());
|
|
ch1.conn()->SetIceFieldTrials(&trials);
|
|
|
|
// Send ping.
|
|
ch1.Ping();
|
|
|
|
ASSERT_TRUE_WAIT(port1->last_stun_msg() != NULL, kDefaultTimeout);
|
|
const IceMessage* msg = port1->last_stun_msg();
|
|
auto* con = port2->CreateConnection(port1->Candidates()[0],
|
|
cricket::Port::ORIGIN_MESSAGE);
|
|
con->SetIceFieldTrials(&trials);
|
|
|
|
// Feed the message into the connection.
|
|
con->SendStunBindingResponse(msg);
|
|
|
|
// The check reply wrt to settings.
|
|
const auto* response = port2->last_stun_msg();
|
|
ASSERT_EQ(response->type(), STUN_BINDING_RESPONSE);
|
|
ASSERT_TRUE(GetSupportedGoogPingVersion(response) >= kGoogPingVersion);
|
|
|
|
// Feeding the respone message back.
|
|
ch1.conn()->OnReadPacket(port2->last_stun_buf()->data<char>(),
|
|
port2->last_stun_buf()->size(),
|
|
/* packet_time_us */ -1);
|
|
|
|
port1->Reset();
|
|
port2->Reset();
|
|
|
|
ch1.Ping();
|
|
ASSERT_TRUE_WAIT(port1->last_stun_msg() != NULL, kDefaultTimeout);
|
|
const IceMessage* msg2 = port1->last_stun_msg();
|
|
|
|
// It should be a GOOG_PING if both of these are TRUE
|
|
ASSERT_EQ(msg2->type(), GOOG_PING_REQUEST);
|
|
con->SendGoogPingResponse(msg2);
|
|
|
|
const auto* response2 = port2->last_stun_msg();
|
|
ASSERT_TRUE(response2 != nullptr);
|
|
|
|
// It should be a GOOG_PING_RESPONSE.
|
|
ASSERT_EQ(response2->type(), GOOG_PING_RESPONSE);
|
|
|
|
// And now the third ping.
|
|
port1->Reset();
|
|
port2->Reset();
|
|
|
|
// Modify the message to be sent.
|
|
ch1.conn()->set_use_candidate_attr(!ch1.conn()->use_candidate_attr());
|
|
|
|
ch1.Ping();
|
|
ASSERT_TRUE_WAIT(port1->last_stun_msg() != NULL, kDefaultTimeout);
|
|
const IceMessage* msg3 = port1->last_stun_msg();
|
|
|
|
// It should be a STUN_BINDING_REQUEST
|
|
ASSERT_EQ(msg3->type(), STUN_BINDING_REQUEST);
|
|
|
|
ch1.Stop();
|
|
}
|
|
|
|
// This test that an error response fall back to STUN_BINDING.
|
|
TEST_F(PortTest, TestErrorResponseMakesGoogPingFallBackToStunBinding) {
|
|
IceFieldTrials trials;
|
|
trials.announce_goog_ping = true;
|
|
trials.enable_goog_ping = true;
|
|
|
|
auto port1_unique =
|
|
CreateTestPort(kLocalAddr1, "lfrag", "lpass",
|
|
cricket::ICEROLE_CONTROLLING, kTiebreaker1);
|
|
auto* port1 = port1_unique.get();
|
|
auto port2 = CreateTestPort(kLocalAddr2, "rfrag", "rpass",
|
|
cricket::ICEROLE_CONTROLLED, kTiebreaker2);
|
|
|
|
TestChannel ch1(std::move(port1_unique));
|
|
// Block usage of STUN_ATTR_USE_CANDIDATE so that
|
|
// ch1.conn() will sent GOOG_PING_REQUEST directly.
|
|
// This only makes test a bit shorter...
|
|
ch1.SetIceMode(ICEMODE_LITE);
|
|
// Start gathering candidates.
|
|
ch1.Start();
|
|
port2->PrepareAddress();
|
|
|
|
ASSERT_EQ_WAIT(1, ch1.complete_count(), kDefaultTimeout);
|
|
ASSERT_FALSE(port2->Candidates().empty());
|
|
|
|
ch1.CreateConnection(GetCandidate(port2.get()));
|
|
ASSERT_TRUE(ch1.conn() != NULL);
|
|
EXPECT_EQ(Connection::STATE_WRITE_INIT, ch1.conn()->write_state());
|
|
ch1.conn()->SetIceFieldTrials(&trials);
|
|
|
|
// Send ping.
|
|
ch1.Ping();
|
|
|
|
ASSERT_TRUE_WAIT(port1->last_stun_msg() != NULL, kDefaultTimeout);
|
|
const IceMessage* msg = port1->last_stun_msg();
|
|
auto* con = port2->CreateConnection(port1->Candidates()[0],
|
|
cricket::Port::ORIGIN_MESSAGE);
|
|
con->SetIceFieldTrials(&trials);
|
|
|
|
// Feed the message into the connection.
|
|
con->SendStunBindingResponse(msg);
|
|
|
|
// The check reply wrt to settings.
|
|
const auto* response = port2->last_stun_msg();
|
|
ASSERT_EQ(response->type(), STUN_BINDING_RESPONSE);
|
|
ASSERT_TRUE(GetSupportedGoogPingVersion(response) >= kGoogPingVersion);
|
|
|
|
// Feeding the respone message back.
|
|
ch1.conn()->OnReadPacket(port2->last_stun_buf()->data<char>(),
|
|
port2->last_stun_buf()->size(),
|
|
/* packet_time_us */ -1);
|
|
|
|
port1->Reset();
|
|
port2->Reset();
|
|
|
|
ch1.Ping();
|
|
ASSERT_TRUE_WAIT(port1->last_stun_msg() != NULL, kDefaultTimeout);
|
|
const IceMessage* msg2 = port1->last_stun_msg();
|
|
|
|
// It should be a GOOG_PING.
|
|
ASSERT_EQ(msg2->type(), GOOG_PING_REQUEST);
|
|
con->SendGoogPingResponse(msg2);
|
|
|
|
const auto* response2 = port2->last_stun_msg();
|
|
ASSERT_TRUE(response2 != nullptr);
|
|
|
|
// It should be a GOOG_PING_RESPONSE.
|
|
ASSERT_EQ(response2->type(), GOOG_PING_RESPONSE);
|
|
|
|
// But rather than the RESPONSE...feedback an error.
|
|
StunMessage error_response;
|
|
error_response.SetType(GOOG_PING_ERROR_RESPONSE);
|
|
error_response.SetTransactionID(response2->transaction_id());
|
|
error_response.AddMessageIntegrity32("rpass");
|
|
rtc::ByteBufferWriter buf;
|
|
error_response.Write(&buf);
|
|
|
|
ch1.conn()->OnReadPacket(buf.Data(), buf.Length(),
|
|
/* packet_time_us */ -1);
|
|
|
|
// And now the third ping...this should be a binding.
|
|
port1->Reset();
|
|
port2->Reset();
|
|
|
|
ch1.Ping();
|
|
ASSERT_TRUE_WAIT(port1->last_stun_msg() != NULL, kDefaultTimeout);
|
|
const IceMessage* msg3 = port1->last_stun_msg();
|
|
|
|
// It should be a STUN_BINDING_REQUEST
|
|
ASSERT_EQ(msg3->type(), STUN_BINDING_REQUEST);
|
|
|
|
ch1.Stop();
|
|
}
|
|
|
|
// This test case verifies that both the controlling port and the controlled
|
|
// port will time out after connectivity is lost, if they are not marked as
|
|
// "keep alive until pruned."
|
|
TEST_F(PortTest, TestPortTimeoutIfNotKeptAlive) {
|
|
rtc::ScopedFakeClock clock;
|
|
int timeout_delay = 100;
|
|
auto port1 = CreateUdpPort(kLocalAddr1);
|
|
ConnectToSignalDestroyed(port1.get());
|
|
port1->set_timeout_delay(timeout_delay); // milliseconds
|
|
port1->SetIceRole(cricket::ICEROLE_CONTROLLING);
|
|
port1->SetIceTiebreaker(kTiebreaker1);
|
|
|
|
auto port2 = CreateUdpPort(kLocalAddr2);
|
|
ConnectToSignalDestroyed(port2.get());
|
|
port2->set_timeout_delay(timeout_delay); // milliseconds
|
|
port2->SetIceRole(cricket::ICEROLE_CONTROLLED);
|
|
port2->SetIceTiebreaker(kTiebreaker2);
|
|
|
|
// Set up channels and ensure both ports will be deleted.
|
|
TestChannel ch1(std::move(port1));
|
|
TestChannel ch2(std::move(port2));
|
|
|
|
// Simulate a connection that succeeds, and then is destroyed.
|
|
StartConnectAndStopChannels(&ch1, &ch2);
|
|
// After the connection is destroyed, the port will be destroyed because
|
|
// none of them is marked as "keep alive until pruned.
|
|
EXPECT_EQ_SIMULATED_WAIT(2, ports_destroyed(), 110, clock);
|
|
}
|
|
|
|
// Test that if after all connection are destroyed, new connections are created
|
|
// and destroyed again, ports won't be destroyed until a timeout period passes
|
|
// after the last set of connections are all destroyed.
|
|
TEST_F(PortTest, TestPortTimeoutAfterNewConnectionCreatedAndDestroyed) {
|
|
rtc::ScopedFakeClock clock;
|
|
int timeout_delay = 100;
|
|
auto port1 = CreateUdpPort(kLocalAddr1);
|
|
ConnectToSignalDestroyed(port1.get());
|
|
port1->set_timeout_delay(timeout_delay); // milliseconds
|
|
port1->SetIceRole(cricket::ICEROLE_CONTROLLING);
|
|
port1->SetIceTiebreaker(kTiebreaker1);
|
|
|
|
auto port2 = CreateUdpPort(kLocalAddr2);
|
|
ConnectToSignalDestroyed(port2.get());
|
|
port2->set_timeout_delay(timeout_delay); // milliseconds
|
|
|
|
port2->SetIceRole(cricket::ICEROLE_CONTROLLED);
|
|
port2->SetIceTiebreaker(kTiebreaker2);
|
|
|
|
// Set up channels and ensure both ports will be deleted.
|
|
TestChannel ch1(std::move(port1));
|
|
TestChannel ch2(std::move(port2));
|
|
|
|
// Simulate a connection that succeeds, and then is destroyed.
|
|
StartConnectAndStopChannels(&ch1, &ch2);
|
|
SIMULATED_WAIT(ports_destroyed() > 0, 80, clock);
|
|
EXPECT_EQ(0, ports_destroyed());
|
|
|
|
// Start the second set of connection and destroy them.
|
|
ch1.CreateConnection(GetCandidate(ch2.port()));
|
|
ch2.CreateConnection(GetCandidate(ch1.port()));
|
|
ch1.Stop();
|
|
ch2.Stop();
|
|
|
|
SIMULATED_WAIT(ports_destroyed() > 0, 80, clock);
|
|
EXPECT_EQ(0, ports_destroyed());
|
|
|
|
// The ports on both sides should be destroyed after timeout.
|
|
EXPECT_TRUE_SIMULATED_WAIT(ports_destroyed() == 2, 30, clock);
|
|
}
|
|
|
|
// This test case verifies that neither the controlling port nor the controlled
|
|
// port will time out after connectivity is lost if they are marked as "keep
|
|
// alive until pruned". They will time out after they are pruned.
|
|
TEST_F(PortTest, TestPortNotTimeoutUntilPruned) {
|
|
rtc::ScopedFakeClock clock;
|
|
int timeout_delay = 100;
|
|
auto port1 = CreateUdpPort(kLocalAddr1);
|
|
ConnectToSignalDestroyed(port1.get());
|
|
port1->set_timeout_delay(timeout_delay); // milliseconds
|
|
port1->SetIceRole(cricket::ICEROLE_CONTROLLING);
|
|
port1->SetIceTiebreaker(kTiebreaker1);
|
|
|
|
auto port2 = CreateUdpPort(kLocalAddr2);
|
|
ConnectToSignalDestroyed(port2.get());
|
|
port2->set_timeout_delay(timeout_delay); // milliseconds
|
|
port2->SetIceRole(cricket::ICEROLE_CONTROLLED);
|
|
port2->SetIceTiebreaker(kTiebreaker2);
|
|
// The connection must not be destroyed before a connection is attempted.
|
|
EXPECT_EQ(0, ports_destroyed());
|
|
|
|
port1->set_component(cricket::ICE_CANDIDATE_COMPONENT_DEFAULT);
|
|
port2->set_component(cricket::ICE_CANDIDATE_COMPONENT_DEFAULT);
|
|
|
|
// Set up channels and keep the port alive.
|
|
TestChannel ch1(std::move(port1));
|
|
TestChannel ch2(std::move(port2));
|
|
// Simulate a connection that succeeds, and then is destroyed. But ports
|
|
// are kept alive. Ports won't be destroyed.
|
|
StartConnectAndStopChannels(&ch1, &ch2);
|
|
ch1.port()->KeepAliveUntilPruned();
|
|
ch2.port()->KeepAliveUntilPruned();
|
|
SIMULATED_WAIT(ports_destroyed() > 0, 150, clock);
|
|
EXPECT_EQ(0, ports_destroyed());
|
|
|
|
// If they are pruned now, they will be destroyed right away.
|
|
ch1.port()->Prune();
|
|
ch2.port()->Prune();
|
|
// The ports on both sides should be destroyed after timeout.
|
|
EXPECT_TRUE_SIMULATED_WAIT(ports_destroyed() == 2, 1, clock);
|
|
}
|
|
|
|
TEST_F(PortTest, TestSupportsProtocol) {
|
|
auto udp_port = CreateUdpPort(kLocalAddr1);
|
|
EXPECT_TRUE(udp_port->SupportsProtocol(UDP_PROTOCOL_NAME));
|
|
EXPECT_FALSE(udp_port->SupportsProtocol(TCP_PROTOCOL_NAME));
|
|
|
|
auto stun_port = CreateStunPort(kLocalAddr1, nat_socket_factory1());
|
|
EXPECT_TRUE(stun_port->SupportsProtocol(UDP_PROTOCOL_NAME));
|
|
EXPECT_FALSE(stun_port->SupportsProtocol(TCP_PROTOCOL_NAME));
|
|
|
|
auto tcp_port = CreateTcpPort(kLocalAddr1);
|
|
EXPECT_TRUE(tcp_port->SupportsProtocol(TCP_PROTOCOL_NAME));
|
|
EXPECT_TRUE(tcp_port->SupportsProtocol(SSLTCP_PROTOCOL_NAME));
|
|
EXPECT_FALSE(tcp_port->SupportsProtocol(UDP_PROTOCOL_NAME));
|
|
|
|
auto turn_port =
|
|
CreateTurnPort(kLocalAddr1, nat_socket_factory1(), PROTO_UDP, PROTO_UDP);
|
|
EXPECT_TRUE(turn_port->SupportsProtocol(UDP_PROTOCOL_NAME));
|
|
EXPECT_FALSE(turn_port->SupportsProtocol(TCP_PROTOCOL_NAME));
|
|
}
|
|
|
|
// Test that SetIceParameters updates the component, ufrag and password
|
|
// on both the port itself and its candidates.
|
|
TEST_F(PortTest, TestSetIceParameters) {
|
|
auto port = CreateTestPort(kLocalAddr1, "ufrag1", "password1");
|
|
port->PrepareAddress();
|
|
EXPECT_EQ(1UL, port->Candidates().size());
|
|
port->SetIceParameters(1, "ufrag2", "password2");
|
|
EXPECT_EQ(1, port->component());
|
|
EXPECT_EQ("ufrag2", port->username_fragment());
|
|
EXPECT_EQ("password2", port->password());
|
|
const Candidate& candidate = port->Candidates()[0];
|
|
EXPECT_EQ(1, candidate.component());
|
|
EXPECT_EQ("ufrag2", candidate.username());
|
|
EXPECT_EQ("password2", candidate.password());
|
|
}
|
|
|
|
TEST_F(PortTest, TestAddConnectionWithSameAddress) {
|
|
auto port = CreateTestPort(kLocalAddr1, "ufrag1", "password1");
|
|
port->PrepareAddress();
|
|
EXPECT_EQ(1u, port->Candidates().size());
|
|
rtc::SocketAddress address("1.1.1.1", 5000);
|
|
cricket::Candidate candidate(1, "udp", address, 0, "", "", "relay", 0, "");
|
|
cricket::Connection* conn1 =
|
|
port->CreateConnection(candidate, Port::ORIGIN_MESSAGE);
|
|
cricket::Connection* conn_in_use = port->GetConnection(address);
|
|
EXPECT_EQ(conn1, conn_in_use);
|
|
EXPECT_EQ(0u, conn_in_use->remote_candidate().generation());
|
|
|
|
// Creating with a candidate with the same address again will get us a
|
|
// different connection with the new candidate.
|
|
candidate.set_generation(2);
|
|
cricket::Connection* conn2 =
|
|
port->CreateConnection(candidate, Port::ORIGIN_MESSAGE);
|
|
EXPECT_NE(conn1, conn2);
|
|
conn_in_use = port->GetConnection(address);
|
|
EXPECT_EQ(conn2, conn_in_use);
|
|
EXPECT_EQ(2u, conn_in_use->remote_candidate().generation());
|
|
|
|
// Make sure the new connection was not deleted.
|
|
rtc::Thread::Current()->ProcessMessages(300);
|
|
EXPECT_TRUE(port->GetConnection(address) != nullptr);
|
|
}
|
|
|
|
// TODO(webrtc:11463) : Move Connection tests into separate unit test
|
|
// splitting out shared test code as needed.
|
|
|
|
class ConnectionTest : public PortTest {
|
|
public:
|
|
ConnectionTest() {
|
|
lport_ = CreateTestPort(kLocalAddr1, "lfrag", "lpass");
|
|
rport_ = CreateTestPort(kLocalAddr2, "rfrag", "rpass");
|
|
lport_->SetIceRole(cricket::ICEROLE_CONTROLLING);
|
|
lport_->SetIceTiebreaker(kTiebreaker1);
|
|
rport_->SetIceRole(cricket::ICEROLE_CONTROLLED);
|
|
rport_->SetIceTiebreaker(kTiebreaker2);
|
|
|
|
lport_->PrepareAddress();
|
|
rport_->PrepareAddress();
|
|
}
|
|
|
|
rtc::ScopedFakeClock clock_;
|
|
int num_state_changes_ = 0;
|
|
|
|
Connection* CreateConnection(IceRole role) {
|
|
Connection* conn;
|
|
if (role == cricket::ICEROLE_CONTROLLING) {
|
|
conn = lport_->CreateConnection(rport_->Candidates()[0],
|
|
Port::ORIGIN_MESSAGE);
|
|
} else {
|
|
conn = rport_->CreateConnection(lport_->Candidates()[0],
|
|
Port::ORIGIN_MESSAGE);
|
|
}
|
|
conn->SignalStateChange.connect(this,
|
|
&ConnectionTest::OnConnectionStateChange);
|
|
return conn;
|
|
}
|
|
|
|
void SendPingAndCaptureReply(Connection* lconn,
|
|
Connection* rconn,
|
|
int64_t ms,
|
|
rtc::BufferT<uint8_t>* reply) {
|
|
TestPort* lport =
|
|
lconn->PortForTest() == lport_.get() ? lport_.get() : rport_.get();
|
|
TestPort* rport =
|
|
rconn->PortForTest() == rport_.get() ? rport_.get() : lport_.get();
|
|
lconn->Ping(rtc::TimeMillis());
|
|
ASSERT_TRUE_WAIT(lport->last_stun_msg(), kDefaultTimeout);
|
|
ASSERT_TRUE(lport->last_stun_buf());
|
|
rconn->OnReadPacket(lport->last_stun_buf()->data<char>(),
|
|
lport->last_stun_buf()->size(),
|
|
/* packet_time_us */ -1);
|
|
clock_.AdvanceTime(webrtc::TimeDelta::Millis(ms));
|
|
ASSERT_TRUE_WAIT(rport->last_stun_msg(), kDefaultTimeout);
|
|
ASSERT_TRUE(rport->last_stun_buf());
|
|
*reply = std::move(*rport->last_stun_buf());
|
|
}
|
|
|
|
void SendPingAndReceiveResponse(Connection* lconn,
|
|
Connection* rconn,
|
|
int64_t ms) {
|
|
rtc::BufferT<uint8_t> reply;
|
|
SendPingAndCaptureReply(lconn, rconn, ms, &reply);
|
|
lconn->OnReadPacket(reply.data<char>(), reply.size(),
|
|
/* packet_time_us */ -1);
|
|
}
|
|
|
|
void OnConnectionStateChange(Connection* connection) { num_state_changes_++; }
|
|
|
|
private:
|
|
std::unique_ptr<TestPort> lport_;
|
|
std::unique_ptr<TestPort> rport_;
|
|
};
|
|
|
|
TEST_F(ConnectionTest, ConnectionForgetLearnedState) {
|
|
Connection* lconn = CreateConnection(ICEROLE_CONTROLLING);
|
|
Connection* rconn = CreateConnection(ICEROLE_CONTROLLED);
|
|
|
|
EXPECT_FALSE(lconn->writable());
|
|
EXPECT_FALSE(lconn->receiving());
|
|
EXPECT_TRUE(std::isnan(lconn->GetRttEstimate().GetAverage()));
|
|
EXPECT_EQ(lconn->GetRttEstimate().GetVariance(),
|
|
std::numeric_limits<double>::infinity());
|
|
|
|
SendPingAndReceiveResponse(lconn, rconn, 10);
|
|
|
|
EXPECT_TRUE(lconn->writable());
|
|
EXPECT_TRUE(lconn->receiving());
|
|
EXPECT_EQ(lconn->GetRttEstimate().GetAverage(), 10);
|
|
EXPECT_EQ(lconn->GetRttEstimate().GetVariance(),
|
|
std::numeric_limits<double>::infinity());
|
|
|
|
SendPingAndReceiveResponse(lconn, rconn, 11);
|
|
|
|
EXPECT_TRUE(lconn->writable());
|
|
EXPECT_TRUE(lconn->receiving());
|
|
EXPECT_NEAR(lconn->GetRttEstimate().GetAverage(), 10, 0.5);
|
|
EXPECT_LT(lconn->GetRttEstimate().GetVariance(),
|
|
std::numeric_limits<double>::infinity());
|
|
|
|
lconn->ForgetLearnedState();
|
|
|
|
EXPECT_FALSE(lconn->writable());
|
|
EXPECT_FALSE(lconn->receiving());
|
|
EXPECT_TRUE(std::isnan(lconn->GetRttEstimate().GetAverage()));
|
|
EXPECT_EQ(lconn->GetRttEstimate().GetVariance(),
|
|
std::numeric_limits<double>::infinity());
|
|
}
|
|
|
|
TEST_F(ConnectionTest, ConnectionForgetLearnedStateDiscardsPendingPings) {
|
|
Connection* lconn = CreateConnection(ICEROLE_CONTROLLING);
|
|
Connection* rconn = CreateConnection(ICEROLE_CONTROLLED);
|
|
|
|
SendPingAndReceiveResponse(lconn, rconn, 10);
|
|
|
|
EXPECT_TRUE(lconn->writable());
|
|
EXPECT_TRUE(lconn->receiving());
|
|
|
|
rtc::BufferT<uint8_t> reply;
|
|
SendPingAndCaptureReply(lconn, rconn, 10, &reply);
|
|
|
|
lconn->ForgetLearnedState();
|
|
|
|
EXPECT_FALSE(lconn->writable());
|
|
EXPECT_FALSE(lconn->receiving());
|
|
|
|
lconn->OnReadPacket(reply.data<char>(), reply.size(),
|
|
/* packet_time_us */ -1);
|
|
|
|
// That reply was discarded due to the ForgetLearnedState() while it was
|
|
// outstanding.
|
|
EXPECT_FALSE(lconn->writable());
|
|
EXPECT_FALSE(lconn->receiving());
|
|
|
|
// But sending a new ping and getting a reply works.
|
|
SendPingAndReceiveResponse(lconn, rconn, 11);
|
|
EXPECT_TRUE(lconn->writable());
|
|
EXPECT_TRUE(lconn->receiving());
|
|
}
|
|
|
|
TEST_F(ConnectionTest, ConnectionForgetLearnedStateDoesNotTriggerStateChange) {
|
|
Connection* lconn = CreateConnection(ICEROLE_CONTROLLING);
|
|
Connection* rconn = CreateConnection(ICEROLE_CONTROLLED);
|
|
|
|
EXPECT_EQ(num_state_changes_, 0);
|
|
SendPingAndReceiveResponse(lconn, rconn, 10);
|
|
|
|
EXPECT_TRUE(lconn->writable());
|
|
EXPECT_TRUE(lconn->receiving());
|
|
EXPECT_EQ(num_state_changes_, 2);
|
|
|
|
lconn->ForgetLearnedState();
|
|
|
|
EXPECT_FALSE(lconn->writable());
|
|
EXPECT_FALSE(lconn->receiving());
|
|
EXPECT_EQ(num_state_changes_, 2);
|
|
}
|
|
|
|
} // namespace cricket
|