Delete gturn support
Delete enum RelayType and classes RelayPort and RelayServer. See also PSA: https://groups.google.com/forum/?#!msg/discuss-webrtc/0ROpUXpw3Gs/eikIN-eEBwAJROpUXpw3Gs/eikIN-eEBwAJ Bug: webrtc:10998 Change-Id: I1eab760dc73df9156cd1224cf99ad4a4c12ed882 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/154522 Reviewed-by: Justin Uberti <juberti@webrtc.org> Reviewed-by: Tommi <tommi@webrtc.org> Commit-Queue: Niels Moller <nisse@webrtc.org> Cr-Commit-Position: refs/heads/master@{#29677}
This commit is contained in:
parent
9dda1b3a48
commit
191e38fb47
@ -47,7 +47,6 @@ group("examples") {
|
||||
if (is_linux || is_win) {
|
||||
deps += [
|
||||
":peerconnection_server",
|
||||
":relayserver",
|
||||
":stunserver",
|
||||
":turnserver",
|
||||
]
|
||||
@ -740,19 +739,6 @@ if (is_linux || is_win) {
|
||||
"//third_party/abseil-cpp/absl/flags:usage",
|
||||
]
|
||||
}
|
||||
rtc_executable("relayserver") {
|
||||
testonly = true
|
||||
sources = [
|
||||
"relayserver/relayserver_main.cc",
|
||||
]
|
||||
deps = [
|
||||
"../p2p:p2p_server_utils",
|
||||
"../p2p:rtc_p2p",
|
||||
"../pc:rtc_pc",
|
||||
"../rtc_base",
|
||||
"../rtc_base:rtc_base_approved",
|
||||
]
|
||||
}
|
||||
rtc_executable("turnserver") {
|
||||
testonly = true
|
||||
sources = [
|
||||
|
||||
@ -1,66 +0,0 @@
|
||||
/*
|
||||
* 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 <iostream> // NOLINT
|
||||
#include <memory>
|
||||
|
||||
#include "p2p/base/relay_server.h"
|
||||
#include "rtc_base/async_udp_socket.h"
|
||||
#include "rtc_base/socket_address.h"
|
||||
#include "rtc_base/socket_server.h"
|
||||
#include "rtc_base/thread.h"
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if (argc != 3) {
|
||||
std::cerr << "usage: relayserver internal-address external-address"
|
||||
<< std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
rtc::SocketAddress int_addr;
|
||||
if (!int_addr.FromString(argv[1])) {
|
||||
std::cerr << "Unable to parse IP address: " << argv[1];
|
||||
return 1;
|
||||
}
|
||||
|
||||
rtc::SocketAddress ext_addr;
|
||||
if (!ext_addr.FromString(argv[2])) {
|
||||
std::cerr << "Unable to parse IP address: " << argv[2];
|
||||
return 1;
|
||||
}
|
||||
|
||||
rtc::Thread* pthMain = rtc::Thread::Current();
|
||||
|
||||
std::unique_ptr<rtc::AsyncUDPSocket> int_socket(
|
||||
rtc::AsyncUDPSocket::Create(pthMain->socketserver(), int_addr));
|
||||
if (!int_socket) {
|
||||
std::cerr << "Failed to create a UDP socket bound at" << int_addr.ToString()
|
||||
<< std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::unique_ptr<rtc::AsyncUDPSocket> ext_socket(
|
||||
rtc::AsyncUDPSocket::Create(pthMain->socketserver(), ext_addr));
|
||||
if (!ext_socket) {
|
||||
std::cerr << "Failed to create a UDP socket bound at" << ext_addr.ToString()
|
||||
<< std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
cricket::RelayServer server(pthMain);
|
||||
server.AddInternalSocket(int_socket.get());
|
||||
server.AddExternalSocket(ext_socket.get());
|
||||
|
||||
std::cout << "Listening internally at " << int_addr.ToString() << std::endl;
|
||||
std::cout << "Listening externally at " << ext_addr.ToString() << std::endl;
|
||||
|
||||
pthMain->Run();
|
||||
return 0;
|
||||
}
|
||||
@ -70,8 +70,6 @@ rtc_library("rtc_p2p") {
|
||||
"base/pseudo_tcp.h",
|
||||
"base/regathering_controller.cc",
|
||||
"base/regathering_controller.h",
|
||||
"base/relay_port.cc",
|
||||
"base/relay_port.h",
|
||||
"base/stun_port.cc",
|
||||
"base/stun_port.h",
|
||||
"base/stun_request.cc",
|
||||
@ -160,7 +158,6 @@ if (rtc_include_tests) {
|
||||
"base/fake_packet_transport.h",
|
||||
"base/mock_async_resolver.h",
|
||||
"base/mock_ice_transport.h",
|
||||
"base/test_relay_server.h",
|
||||
"base/test_stun_server.cc",
|
||||
"base/test_stun_server.h",
|
||||
"base/test_turn_customizer.h",
|
||||
@ -200,8 +197,6 @@ if (rtc_include_tests) {
|
||||
"base/port_unittest.cc",
|
||||
"base/pseudo_tcp_unittest.cc",
|
||||
"base/regathering_controller_unittest.cc",
|
||||
"base/relay_port_unittest.cc",
|
||||
"base/relay_server_unittest.cc",
|
||||
"base/stun_port_unittest.cc",
|
||||
"base/stun_request_unittest.cc",
|
||||
"base/stun_server_unittest.cc",
|
||||
@ -244,8 +239,6 @@ if (rtc_include_tests) {
|
||||
rtc_library("p2p_server_utils") {
|
||||
testonly = true
|
||||
sources = [
|
||||
"base/relay_server.cc",
|
||||
"base/relay_server.h",
|
||||
"base/stun_server.cc",
|
||||
"base/stun_server.h",
|
||||
"base/turn_server.cc",
|
||||
|
||||
@ -19,7 +19,6 @@
|
||||
#include "p2p/base/ice_transport_internal.h"
|
||||
#include "p2p/base/mock_async_resolver.h"
|
||||
#include "p2p/base/packet_transport_internal.h"
|
||||
#include "p2p/base/test_relay_server.h"
|
||||
#include "p2p/base/test_stun_server.h"
|
||||
#include "p2p/base/test_turn_server.h"
|
||||
#include "p2p/client/basic_port_allocator.h"
|
||||
@ -156,7 +155,7 @@ cricket::BasicPortAllocator* CreateBasicPortAllocator(
|
||||
const cricket::ServerAddresses& stun_servers,
|
||||
const rtc::SocketAddress& turn_server_udp,
|
||||
const rtc::SocketAddress& turn_server_tcp) {
|
||||
cricket::RelayServerConfig turn_server(cricket::RELAY_TURN);
|
||||
cricket::RelayServerConfig turn_server;
|
||||
turn_server.credentials = kRelayCredentials;
|
||||
if (!turn_server_udp.IsNil()) {
|
||||
turn_server.ports.push_back(
|
||||
@ -2578,7 +2577,7 @@ TEST_F(P2PTransportChannelMultihomedTest, TestFailoverControllingSide) {
|
||||
TEST_F(P2PTransportChannelMultihomedTest, TestFailoverWithManyConnections) {
|
||||
rtc::ScopedFakeClock clock;
|
||||
test_turn_server()->AddInternalSocket(kTurnTcpIntAddr, PROTO_TCP);
|
||||
RelayServerConfig turn_server(RELAY_TURN);
|
||||
RelayServerConfig turn_server;
|
||||
turn_server.credentials = kRelayCredentials;
|
||||
turn_server.ports.push_back(ProtocolAddress(kTurnTcpIntAddr, PROTO_TCP));
|
||||
GetAllocator(0)->AddTurnServer(turn_server);
|
||||
@ -4757,7 +4756,7 @@ TEST_F(P2PTransportChannelMostLikelyToWorkFirstTest,
|
||||
TEST_F(P2PTransportChannelMostLikelyToWorkFirstTest, TestTcpTurn) {
|
||||
// Add a Tcp Turn server.
|
||||
turn_server()->AddInternalSocket(kTurnTcpIntAddr, PROTO_TCP);
|
||||
RelayServerConfig config(RELAY_TURN);
|
||||
RelayServerConfig config;
|
||||
config.credentials = kRelayCredentials;
|
||||
config.ports.push_back(ProtocolAddress(kTurnTcpIntAddr, PROTO_TCP));
|
||||
allocator()->AddTurnServer(config);
|
||||
|
||||
@ -57,11 +57,6 @@ extern const char TCPTYPE_ACTIVE_STR[];
|
||||
extern const char TCPTYPE_PASSIVE_STR[];
|
||||
extern const char TCPTYPE_SIMOPEN_STR[];
|
||||
|
||||
enum RelayType {
|
||||
RELAY_GTURN, // Legacy google relay service.
|
||||
RELAY_TURN // Standard (TURN) relay service.
|
||||
};
|
||||
|
||||
enum IcePriorityValue {
|
||||
ICE_TYPE_PREFERENCE_RELAY_TLS = 0,
|
||||
ICE_TYPE_PREFERENCE_RELAY_TCP = 1,
|
||||
|
||||
@ -20,13 +20,13 @@
|
||||
|
||||
namespace cricket {
|
||||
|
||||
RelayServerConfig::RelayServerConfig(RelayType type) : type(type) {}
|
||||
RelayServerConfig::RelayServerConfig() {}
|
||||
|
||||
RelayServerConfig::RelayServerConfig(const rtc::SocketAddress& address,
|
||||
const std::string& username,
|
||||
const std::string& password,
|
||||
ProtocolType proto)
|
||||
: type(RELAY_TURN), credentials(username, password) {
|
||||
: credentials(username, password) {
|
||||
ports.push_back(ProtocolAddress(address, proto));
|
||||
}
|
||||
|
||||
|
||||
@ -149,7 +149,7 @@ struct RelayCredentials {
|
||||
typedef std::vector<ProtocolAddress> PortList;
|
||||
// TODO(deadbeef): Rename to TurnServerConfig.
|
||||
struct RTC_EXPORT RelayServerConfig {
|
||||
explicit RelayServerConfig(RelayType type);
|
||||
RelayServerConfig();
|
||||
RelayServerConfig(const rtc::SocketAddress& address,
|
||||
const std::string& username,
|
||||
const std::string& password,
|
||||
@ -170,12 +170,11 @@ struct RTC_EXPORT RelayServerConfig {
|
||||
~RelayServerConfig();
|
||||
|
||||
bool operator==(const RelayServerConfig& o) const {
|
||||
return type == o.type && ports == o.ports && credentials == o.credentials &&
|
||||
return ports == o.ports && credentials == o.credentials &&
|
||||
priority == o.priority;
|
||||
}
|
||||
bool operator!=(const RelayServerConfig& o) const { return !(*this == o); }
|
||||
|
||||
RelayType type;
|
||||
PortList ports;
|
||||
RelayCredentials credentials;
|
||||
int priority = 0;
|
||||
|
||||
@ -27,12 +27,10 @@
|
||||
#include "p2p/base/p2p_constants.h"
|
||||
#include "p2p/base/port_allocator.h"
|
||||
#include "p2p/base/port_interface.h"
|
||||
#include "p2p/base/relay_port.h"
|
||||
#include "p2p/base/stun.h"
|
||||
#include "p2p/base/stun_port.h"
|
||||
#include "p2p/base/stun_server.h"
|
||||
#include "p2p/base/tcp_port.h"
|
||||
#include "p2p/base/test_relay_server.h"
|
||||
#include "p2p/base/test_stun_server.h"
|
||||
#include "p2p/base/test_turn_server.h"
|
||||
#include "p2p/base/transport_description.h"
|
||||
@ -90,12 +88,6 @@ 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 kRelayUdpIntAddr("99.99.99.2", 5000);
|
||||
const SocketAddress kRelayUdpExtAddr("99.99.99.3", 5001);
|
||||
const SocketAddress kRelayTcpIntAddr("99.99.99.2", 5002);
|
||||
const SocketAddress kRelayTcpExtAddr("99.99.99.3", 5003);
|
||||
const SocketAddress kRelaySslTcpIntAddr("99.99.99.2", 5004);
|
||||
const SocketAddress kRelaySslTcpExtAddr("99.99.99.3", 5005);
|
||||
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);
|
||||
@ -112,8 +104,6 @@ constexpr int kTiebreaker2 = 22222;
|
||||
|
||||
const char* data = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
|
||||
|
||||
constexpr int kGturnUserNameLength = 16;
|
||||
|
||||
Candidate GetCandidate(Port* port) {
|
||||
RTC_DCHECK_GE(port->Candidates().size(), 1);
|
||||
return port->Candidates()[0];
|
||||
@ -410,13 +400,6 @@ class PortTest : public ::testing::Test, public sigslot::has_slots<> {
|
||||
nat_socket_factory2_(&nat_factory2_),
|
||||
stun_server_(TestStunServer::Create(&main_, kStunAddr)),
|
||||
turn_server_(&main_, kTurnUdpIntAddr, kTurnUdpExtAddr),
|
||||
relay_server_(&main_,
|
||||
kRelayUdpIntAddr,
|
||||
kRelayUdpExtAddr,
|
||||
kRelayTcpIntAddr,
|
||||
kRelayTcpExtAddr,
|
||||
kRelaySslTcpIntAddr,
|
||||
kRelaySslTcpExtAddr),
|
||||
username_(rtc::CreateRandomString(ICE_UFRAG_LENGTH)),
|
||||
password_(rtc::CreateRandomString(ICE_PWD_LENGTH)),
|
||||
role_conflict_(false),
|
||||
@ -441,13 +424,13 @@ class PortTest : public ::testing::Test, public sigslot::has_slots<> {
|
||||
ntype == NAT_OPEN_CONE, true, ntype != NAT_SYMMETRIC,
|
||||
true);
|
||||
}
|
||||
void TestLocalToRelay(RelayType rtype, ProtocolType proto) {
|
||||
void TestLocalToRelay(ProtocolType proto) {
|
||||
auto port1 = CreateUdpPort(kLocalAddr1);
|
||||
port1->SetIceRole(cricket::ICEROLE_CONTROLLING);
|
||||
auto port2 = CreateRelayPort(kLocalAddr2, rtype, proto, PROTO_UDP);
|
||||
auto port2 = CreateRelayPort(kLocalAddr2, proto, PROTO_UDP);
|
||||
port2->SetIceRole(cricket::ICEROLE_CONTROLLED);
|
||||
TestConnectivity("udp", std::move(port1), RelayName(rtype, proto),
|
||||
std::move(port2), rtype == RELAY_GTURN, true, true, true);
|
||||
TestConnectivity("udp", std::move(port1), RelayName(proto),
|
||||
std::move(port2), false, true, true, true);
|
||||
}
|
||||
void TestStunToLocal(NATType ntype) {
|
||||
nat_server1_ = CreateNatServer(kNatAddr1, ntype);
|
||||
@ -470,15 +453,15 @@ class PortTest : public ::testing::Test, public sigslot::has_slots<> {
|
||||
ntype1 != NAT_SYMMETRIC, ntype2 != NAT_SYMMETRIC,
|
||||
ntype1 + ntype2 < (NAT_PORT_RESTRICTED + NAT_SYMMETRIC));
|
||||
}
|
||||
void TestStunToRelay(NATType ntype, RelayType rtype, ProtocolType proto) {
|
||||
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, rtype, proto, PROTO_UDP);
|
||||
auto port2 = CreateRelayPort(kLocalAddr2, proto, PROTO_UDP);
|
||||
port2->SetIceRole(cricket::ICEROLE_CONTROLLED);
|
||||
TestConnectivity(StunName(ntype), std::move(port1), RelayName(rtype, proto),
|
||||
std::move(port2), rtype == RELAY_GTURN,
|
||||
ntype != NAT_SYMMETRIC, true, true);
|
||||
TestConnectivity(StunName(ntype), std::move(port1), RelayName(proto),
|
||||
std::move(port2), false, ntype != NAT_SYMMETRIC, true,
|
||||
true);
|
||||
}
|
||||
void TestTcpToTcp() {
|
||||
auto port1 = CreateTcpPort(kLocalAddr1);
|
||||
@ -488,21 +471,21 @@ class PortTest : public ::testing::Test, public sigslot::has_slots<> {
|
||||
TestConnectivity("tcp", std::move(port1), "tcp", std::move(port2), true,
|
||||
false, true, true);
|
||||
}
|
||||
void TestTcpToRelay(RelayType rtype, ProtocolType proto) {
|
||||
void TestTcpToRelay(ProtocolType proto) {
|
||||
auto port1 = CreateTcpPort(kLocalAddr1);
|
||||
port1->SetIceRole(cricket::ICEROLE_CONTROLLING);
|
||||
auto port2 = CreateRelayPort(kLocalAddr2, rtype, proto, PROTO_TCP);
|
||||
auto port2 = CreateRelayPort(kLocalAddr2, proto, PROTO_TCP);
|
||||
port2->SetIceRole(cricket::ICEROLE_CONTROLLED);
|
||||
TestConnectivity("tcp", std::move(port1), RelayName(rtype, proto),
|
||||
std::move(port2), rtype == RELAY_GTURN, false, true, true);
|
||||
TestConnectivity("tcp", std::move(port1), RelayName(proto),
|
||||
std::move(port2), false, false, true, true);
|
||||
}
|
||||
void TestSslTcpToRelay(RelayType rtype, ProtocolType proto) {
|
||||
void TestSslTcpToRelay(ProtocolType proto) {
|
||||
auto port1 = CreateTcpPort(kLocalAddr1);
|
||||
port1->SetIceRole(cricket::ICEROLE_CONTROLLING);
|
||||
auto port2 = CreateRelayPort(kLocalAddr2, rtype, proto, PROTO_SSLTCP);
|
||||
auto port2 = CreateRelayPort(kLocalAddr2, proto, PROTO_SSLTCP);
|
||||
port2->SetIceRole(cricket::ICEROLE_CONTROLLED);
|
||||
TestConnectivity("ssltcp", std::move(port1), RelayName(rtype, proto),
|
||||
std::move(port2), rtype == RELAY_GTURN, false, true, true);
|
||||
TestConnectivity("ssltcp", std::move(port1), RelayName(proto),
|
||||
std::move(port2), false, false, true, true);
|
||||
}
|
||||
|
||||
rtc::Network* MakeNetwork(const SocketAddress& addr) {
|
||||
@ -538,14 +521,9 @@ class PortTest : public ::testing::Test, public sigslot::has_slots<> {
|
||||
absl::nullopt);
|
||||
}
|
||||
std::unique_ptr<Port> CreateRelayPort(const SocketAddress& addr,
|
||||
RelayType rtype,
|
||||
ProtocolType int_proto,
|
||||
ProtocolType ext_proto) {
|
||||
if (rtype == RELAY_TURN) {
|
||||
return CreateTurnPort(addr, &socket_factory_, int_proto, ext_proto);
|
||||
} else {
|
||||
return CreateGturnPort(addr, int_proto, ext_proto);
|
||||
}
|
||||
return CreateTurnPort(addr, &socket_factory_, int_proto, ext_proto);
|
||||
}
|
||||
std::unique_ptr<TurnPort> CreateTurnPort(const SocketAddress& addr,
|
||||
PacketSocketFactory* socket_factory,
|
||||
@ -567,24 +545,6 @@ class PortTest : public ::testing::Test, public sigslot::has_slots<> {
|
||||
ProtocolAddress(server_addr, int_proto),
|
||||
kRelayCredentials, 0, "", {}, {}, nullptr, nullptr);
|
||||
}
|
||||
std::unique_ptr<RelayPort> CreateGturnPort(const SocketAddress& addr,
|
||||
ProtocolType int_proto,
|
||||
ProtocolType ext_proto) {
|
||||
std::unique_ptr<RelayPort> port = CreateGturnPort(addr);
|
||||
SocketAddress addrs[] = {kRelayUdpIntAddr, kRelayTcpIntAddr,
|
||||
kRelaySslTcpIntAddr};
|
||||
port->AddServerAddress(ProtocolAddress(addrs[int_proto], int_proto));
|
||||
return port;
|
||||
}
|
||||
std::unique_ptr<RelayPort> CreateGturnPort(const SocketAddress& addr) {
|
||||
// TODO(pthatcher): Remove GTURN.
|
||||
// Generate a username with length of 16 for Gturn only.
|
||||
std::string username = rtc::CreateRandomString(kGturnUserNameLength);
|
||||
return RelayPort::Create(&main_, &socket_factory_, MakeNetwork(addr), 0, 0,
|
||||
username, password_);
|
||||
// TODO(?): Add an external address for ext_proto, so that the
|
||||
// other side can connect to this port using a non-UDP protocol.
|
||||
}
|
||||
std::unique_ptr<rtc::NATServer> CreateNatServer(const SocketAddress& addr,
|
||||
rtc::NATType type) {
|
||||
return std::make_unique<rtc::NATServer>(type, ss_.get(), addr, addr,
|
||||
@ -604,33 +564,18 @@ class PortTest : public ::testing::Test, public sigslot::has_slots<> {
|
||||
return "stun(?)";
|
||||
}
|
||||
}
|
||||
static const char* RelayName(RelayType type, ProtocolType proto) {
|
||||
if (type == RELAY_TURN) {
|
||||
switch (proto) {
|
||||
case PROTO_UDP:
|
||||
return "turn(udp)";
|
||||
case PROTO_TCP:
|
||||
return "turn(tcp)";
|
||||
case PROTO_SSLTCP:
|
||||
return "turn(ssltcp)";
|
||||
case PROTO_TLS:
|
||||
return "turn(tls)";
|
||||
default:
|
||||
return "turn(?)";
|
||||
}
|
||||
} else {
|
||||
switch (proto) {
|
||||
case PROTO_UDP:
|
||||
return "gturn(udp)";
|
||||
case PROTO_TCP:
|
||||
return "gturn(tcp)";
|
||||
case PROTO_SSLTCP:
|
||||
return "gturn(ssltcp)";
|
||||
case PROTO_TLS:
|
||||
return "gturn(tls)";
|
||||
default:
|
||||
return "gturn(?)";
|
||||
}
|
||||
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(?)";
|
||||
}
|
||||
}
|
||||
|
||||
@ -856,7 +801,6 @@ class PortTest : public ::testing::Test, public sigslot::has_slots<> {
|
||||
rtc::BasicPacketSocketFactory nat_socket_factory2_;
|
||||
std::unique_ptr<TestStunServer> stun_server_;
|
||||
TestTurnServer turn_server_;
|
||||
TestRelayServer relay_server_;
|
||||
std::string username_;
|
||||
std::string password_;
|
||||
bool role_conflict_;
|
||||
@ -1120,19 +1064,7 @@ TEST_F(PortTest, TestLocalToSymNat) {
|
||||
|
||||
// Flaky: https://code.google.com/p/webrtc/issues/detail?id=3316.
|
||||
TEST_F(PortTest, DISABLED_TestLocalToTurn) {
|
||||
TestLocalToRelay(RELAY_TURN, PROTO_UDP);
|
||||
}
|
||||
|
||||
TEST_F(PortTest, TestLocalToGturn) {
|
||||
TestLocalToRelay(RELAY_GTURN, PROTO_UDP);
|
||||
}
|
||||
|
||||
TEST_F(PortTest, TestLocalToTcpGturn) {
|
||||
TestLocalToRelay(RELAY_GTURN, PROTO_TCP);
|
||||
}
|
||||
|
||||
TEST_F(PortTest, TestLocalToSslTcpGturn) {
|
||||
TestLocalToRelay(RELAY_GTURN, PROTO_SSLTCP);
|
||||
TestLocalToRelay(PROTO_UDP);
|
||||
}
|
||||
|
||||
// Cone NAT -> XXXX
|
||||
@ -1157,15 +1089,7 @@ TEST_F(PortTest, TestConeNatToSymNat) {
|
||||
}
|
||||
|
||||
TEST_F(PortTest, TestConeNatToTurn) {
|
||||
TestStunToRelay(NAT_OPEN_CONE, RELAY_TURN, PROTO_UDP);
|
||||
}
|
||||
|
||||
TEST_F(PortTest, TestConeNatToGturn) {
|
||||
TestStunToRelay(NAT_OPEN_CONE, RELAY_GTURN, PROTO_UDP);
|
||||
}
|
||||
|
||||
TEST_F(PortTest, TestConeNatToTcpGturn) {
|
||||
TestStunToRelay(NAT_OPEN_CONE, RELAY_GTURN, PROTO_TCP);
|
||||
TestStunToRelay(NAT_OPEN_CONE, PROTO_UDP);
|
||||
}
|
||||
|
||||
// Address-restricted NAT -> XXXX
|
||||
@ -1190,15 +1114,7 @@ TEST_F(PortTest, TestARNatToSymNat) {
|
||||
}
|
||||
|
||||
TEST_F(PortTest, TestARNatToTurn) {
|
||||
TestStunToRelay(NAT_ADDR_RESTRICTED, RELAY_TURN, PROTO_UDP);
|
||||
}
|
||||
|
||||
TEST_F(PortTest, TestARNatToGturn) {
|
||||
TestStunToRelay(NAT_ADDR_RESTRICTED, RELAY_GTURN, PROTO_UDP);
|
||||
}
|
||||
|
||||
TEST_F(PortTest, TestARNATNatToTcpGturn) {
|
||||
TestStunToRelay(NAT_ADDR_RESTRICTED, RELAY_GTURN, PROTO_TCP);
|
||||
TestStunToRelay(NAT_ADDR_RESTRICTED, PROTO_UDP);
|
||||
}
|
||||
|
||||
// Port-restricted NAT -> XXXX
|
||||
@ -1224,15 +1140,7 @@ TEST_F(PortTest, TestPRNatToSymNat) {
|
||||
}
|
||||
|
||||
TEST_F(PortTest, TestPRNatToTurn) {
|
||||
TestStunToRelay(NAT_PORT_RESTRICTED, RELAY_TURN, PROTO_UDP);
|
||||
}
|
||||
|
||||
TEST_F(PortTest, TestPRNatToGturn) {
|
||||
TestStunToRelay(NAT_PORT_RESTRICTED, RELAY_GTURN, PROTO_UDP);
|
||||
}
|
||||
|
||||
TEST_F(PortTest, TestPRNatToTcpGturn) {
|
||||
TestStunToRelay(NAT_PORT_RESTRICTED, RELAY_GTURN, PROTO_TCP);
|
||||
TestStunToRelay(NAT_PORT_RESTRICTED, PROTO_UDP);
|
||||
}
|
||||
|
||||
// Symmetric NAT -> XXXX
|
||||
@ -1259,15 +1167,7 @@ TEST_F(PortTest, TestSymNatToSymNat) {
|
||||
}
|
||||
|
||||
TEST_F(PortTest, TestSymNatToTurn) {
|
||||
TestStunToRelay(NAT_SYMMETRIC, RELAY_TURN, PROTO_UDP);
|
||||
}
|
||||
|
||||
TEST_F(PortTest, TestSymNatToGturn) {
|
||||
TestStunToRelay(NAT_SYMMETRIC, RELAY_GTURN, PROTO_UDP);
|
||||
}
|
||||
|
||||
TEST_F(PortTest, TestSymNatToTcpGturn) {
|
||||
TestStunToRelay(NAT_SYMMETRIC, RELAY_GTURN, PROTO_TCP);
|
||||
TestStunToRelay(NAT_SYMMETRIC, PROTO_UDP);
|
||||
}
|
||||
|
||||
// Outbound TCP -> XXXX
|
||||
@ -2394,16 +2294,6 @@ TEST_F(PortTest, TestCandidateFoundation) {
|
||||
stunport->Candidates()[0].foundation());
|
||||
EXPECT_NE(udpport2->Candidates()[0].foundation(),
|
||||
stunport->Candidates()[0].foundation());
|
||||
// Verify GTURN candidate foundation.
|
||||
auto relayport = CreateGturnPort(kLocalAddr1);
|
||||
relayport->AddServerAddress(
|
||||
cricket::ProtocolAddress(kRelayUdpIntAddr, cricket::PROTO_UDP));
|
||||
relayport->PrepareAddress();
|
||||
ASSERT_EQ_WAIT(1U, relayport->Candidates().size(), kDefaultTimeout);
|
||||
EXPECT_NE(udpport1->Candidates()[0].foundation(),
|
||||
relayport->Candidates()[0].foundation());
|
||||
EXPECT_NE(udpport2->Candidates()[0].foundation(),
|
||||
relayport->Candidates()[0].foundation());
|
||||
// Verifying TURN candidate foundation.
|
||||
auto turnport1 =
|
||||
CreateTurnPort(kLocalAddr1, nat_socket_factory1(), PROTO_UDP, PROTO_UDP);
|
||||
@ -2465,16 +2355,6 @@ TEST_F(PortTest, TestCandidateRelatedAddress) {
|
||||
// Check STUN candidate related address.
|
||||
EXPECT_EQ(stunport->Candidates()[0].related_address(),
|
||||
stunport->GetLocalAddress());
|
||||
// Verifying the related address for the GTURN candidates.
|
||||
// NOTE: In case of GTURN related address will be equal to the mapped
|
||||
// address, but address(mapped) will not be XOR.
|
||||
auto relayport = CreateGturnPort(kLocalAddr1);
|
||||
relayport->AddServerAddress(
|
||||
cricket::ProtocolAddress(kRelayUdpIntAddr, cricket::PROTO_UDP));
|
||||
relayport->PrepareAddress();
|
||||
ASSERT_EQ_WAIT(1U, relayport->Candidates().size(), kDefaultTimeout);
|
||||
// For Gturn related address is set to "0.0.0.0:0"
|
||||
EXPECT_EQ(rtc::SocketAddress(), relayport->Candidates()[0].related_address());
|
||||
// Verifying the related address for TURN candidate.
|
||||
// For TURN related address must be equal to the mapped address.
|
||||
auto turnport =
|
||||
|
||||
@ -63,7 +63,7 @@ class RegatheringControllerTest : public ::testing::Test,
|
||||
void InitializeAndGatherOnce() {
|
||||
cricket::ServerAddresses stun_servers;
|
||||
stun_servers.insert(kStunAddr);
|
||||
cricket::RelayServerConfig turn_server(cricket::RELAY_TURN);
|
||||
cricket::RelayServerConfig turn_server;
|
||||
turn_server.credentials = kRelayCredentials;
|
||||
turn_server.ports.push_back(
|
||||
cricket::ProtocolAddress(kTurnUdpIntAddr, cricket::PROTO_UDP));
|
||||
|
||||
@ -1,860 +0,0 @@
|
||||
/*
|
||||
* 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/relay_port.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "p2p/base/connection.h"
|
||||
#include "p2p/base/stun.h"
|
||||
#include "p2p/base/stun_request.h"
|
||||
#include "rtc_base/async_packet_socket.h"
|
||||
#include "rtc_base/byte_buffer.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/dscp.h"
|
||||
#include "rtc_base/location.h"
|
||||
#include "rtc_base/logging.h"
|
||||
#include "rtc_base/message_handler.h"
|
||||
#include "rtc_base/message_queue.h"
|
||||
#include "rtc_base/net_helper.h"
|
||||
#include "rtc_base/proxy_info.h"
|
||||
#include "rtc_base/time_utils.h"
|
||||
|
||||
namespace cricket {
|
||||
|
||||
static const int kMessageConnectTimeout = 1;
|
||||
static const int kKeepAliveDelay = 10 * 60 * 1000;
|
||||
static const int kRetryTimeout = 50 * 1000; // ICE says 50 secs
|
||||
// How long to wait for a socket to connect to remote host in milliseconds
|
||||
// before trying another connection.
|
||||
static const int kSoftConnectTimeoutMs = 3 * 1000;
|
||||
|
||||
// Handles a connection to one address/port/protocol combination for a
|
||||
// particular RelayEntry.
|
||||
class RelayConnection : public sigslot::has_slots<> {
|
||||
public:
|
||||
RelayConnection(const ProtocolAddress* protocol_address,
|
||||
rtc::AsyncPacketSocket* socket,
|
||||
rtc::Thread* thread);
|
||||
~RelayConnection() override;
|
||||
rtc::AsyncPacketSocket* socket() const { return socket_; }
|
||||
|
||||
const ProtocolAddress* protocol_address() { return protocol_address_; }
|
||||
|
||||
rtc::SocketAddress GetAddress() const { return protocol_address_->address; }
|
||||
|
||||
ProtocolType GetProtocol() const { return protocol_address_->proto; }
|
||||
|
||||
int SetSocketOption(rtc::Socket::Option opt, int value);
|
||||
|
||||
// Validates a response to a STUN allocate request.
|
||||
bool CheckResponse(StunMessage* msg);
|
||||
|
||||
// Sends data to the relay server.
|
||||
int Send(const void* pv, size_t cb, const rtc::PacketOptions& options);
|
||||
|
||||
// Sends a STUN allocate request message to the relay server.
|
||||
void SendAllocateRequest(RelayEntry* entry, int delay);
|
||||
|
||||
// Return the latest error generated by the socket.
|
||||
int GetError() { return socket_->GetError(); }
|
||||
|
||||
// Called on behalf of a StunRequest to write data to the socket. This is
|
||||
// already STUN intended for the server, so no wrapping is necessary.
|
||||
void OnSendPacket(const void* data, size_t size, StunRequest* req);
|
||||
|
||||
private:
|
||||
rtc::AsyncPacketSocket* socket_;
|
||||
const ProtocolAddress* protocol_address_;
|
||||
StunRequestManager* request_manager_;
|
||||
rtc::DiffServCodePoint dscp_;
|
||||
};
|
||||
|
||||
// Manages a number of connections to the relayserver, one for each
|
||||
// available protocol. We aim to use each connection for only a
|
||||
// specific destination address so that we can avoid wrapping every
|
||||
// packet in a STUN send / data indication.
|
||||
class RelayEntry : public rtc::MessageHandler, public sigslot::has_slots<> {
|
||||
public:
|
||||
RelayEntry(RelayPort* port, const rtc::SocketAddress& ext_addr);
|
||||
~RelayEntry() override;
|
||||
|
||||
RelayPort* port() { return port_; }
|
||||
|
||||
const rtc::SocketAddress& address() const { return ext_addr_; }
|
||||
void set_address(const rtc::SocketAddress& addr) { ext_addr_ = addr; }
|
||||
|
||||
bool connected() const { return connected_; }
|
||||
bool locked() const { return locked_; }
|
||||
|
||||
// Returns the last error on the socket of this entry.
|
||||
int GetError();
|
||||
|
||||
// Returns the most preferred connection of the given
|
||||
// ones. Connections are rated based on protocol in the order of:
|
||||
// UDP, TCP and SSLTCP, where UDP is the most preferred protocol
|
||||
static RelayConnection* GetBestConnection(RelayConnection* conn1,
|
||||
RelayConnection* conn2);
|
||||
|
||||
// Sends the STUN requests to the server to initiate this connection.
|
||||
void Connect();
|
||||
|
||||
// Called when this entry becomes connected. The address given is the one
|
||||
// exposed to the outside world on the relay server.
|
||||
void OnConnect(const rtc::SocketAddress& mapped_addr,
|
||||
RelayConnection* socket);
|
||||
|
||||
// Sends a packet to the given destination address using the socket of this
|
||||
// entry. This will wrap the packet in STUN if necessary.
|
||||
int SendTo(const void* data,
|
||||
size_t size,
|
||||
const rtc::SocketAddress& addr,
|
||||
const rtc::PacketOptions& options);
|
||||
|
||||
// Schedules a keep-alive allocate request.
|
||||
void ScheduleKeepAlive();
|
||||
|
||||
void SetServerIndex(size_t sindex) { server_index_ = sindex; }
|
||||
|
||||
// Sets this option on the socket of each connection.
|
||||
int SetSocketOption(rtc::Socket::Option opt, int value);
|
||||
|
||||
size_t ServerIndex() const { return server_index_; }
|
||||
|
||||
// Try a different server address
|
||||
void HandleConnectFailure(rtc::AsyncPacketSocket* socket);
|
||||
|
||||
// Implementation of the MessageHandler Interface.
|
||||
void OnMessage(rtc::Message* pmsg) override;
|
||||
|
||||
private:
|
||||
RelayPort* port_;
|
||||
rtc::SocketAddress ext_addr_;
|
||||
size_t server_index_;
|
||||
bool connected_;
|
||||
bool locked_;
|
||||
RelayConnection* current_connection_;
|
||||
|
||||
// Called when a TCP connection is established or fails
|
||||
void OnSocketConnect(rtc::AsyncPacketSocket* socket);
|
||||
void OnSocketClose(rtc::AsyncPacketSocket* socket, int error);
|
||||
|
||||
// Called when a packet is received on this socket.
|
||||
void OnReadPacket(rtc::AsyncPacketSocket* socket,
|
||||
const char* data,
|
||||
size_t size,
|
||||
const rtc::SocketAddress& remote_addr,
|
||||
const int64_t& packet_time_us);
|
||||
|
||||
void OnSentPacket(rtc::AsyncPacketSocket* socket,
|
||||
const rtc::SentPacket& sent_packet);
|
||||
|
||||
// Called when the socket is currently able to send.
|
||||
void OnReadyToSend(rtc::AsyncPacketSocket* socket);
|
||||
|
||||
// Sends the given data on the socket to the server with no wrapping. This
|
||||
// returns the number of bytes written or -1 if an error occurred.
|
||||
int SendPacket(const void* data,
|
||||
size_t size,
|
||||
const rtc::PacketOptions& options);
|
||||
};
|
||||
|
||||
// Handles an allocate request for a particular RelayEntry.
|
||||
class AllocateRequest : public StunRequest {
|
||||
public:
|
||||
AllocateRequest(RelayEntry* entry, RelayConnection* connection);
|
||||
~AllocateRequest() override = default;
|
||||
|
||||
void Prepare(StunMessage* request) override;
|
||||
|
||||
void OnSent() override;
|
||||
int resend_delay() override;
|
||||
|
||||
void OnResponse(StunMessage* response) override;
|
||||
void OnErrorResponse(StunMessage* response) override;
|
||||
void OnTimeout() override;
|
||||
|
||||
private:
|
||||
RelayEntry* entry_;
|
||||
RelayConnection* connection_;
|
||||
int64_t start_time_;
|
||||
};
|
||||
|
||||
RelayPort::RelayPort(rtc::Thread* thread,
|
||||
rtc::PacketSocketFactory* factory,
|
||||
rtc::Network* network,
|
||||
uint16_t min_port,
|
||||
uint16_t max_port,
|
||||
const std::string& username,
|
||||
const std::string& password)
|
||||
: Port(thread,
|
||||
RELAY_PORT_TYPE,
|
||||
factory,
|
||||
network,
|
||||
min_port,
|
||||
max_port,
|
||||
username,
|
||||
password),
|
||||
ready_(false),
|
||||
error_(0) {
|
||||
entries_.push_back(new RelayEntry(this, rtc::SocketAddress()));
|
||||
// TODO(?): set local preference value for TCP based candidates.
|
||||
}
|
||||
|
||||
RelayPort::~RelayPort() {
|
||||
for (size_t i = 0; i < entries_.size(); ++i)
|
||||
delete entries_[i];
|
||||
thread()->Clear(this);
|
||||
}
|
||||
|
||||
void RelayPort::AddServerAddress(const ProtocolAddress& addr) {
|
||||
// Since HTTP proxies usually only allow 443,
|
||||
// let's up the priority on PROTO_SSLTCP
|
||||
if (addr.proto == PROTO_SSLTCP && (proxy().type == rtc::PROXY_HTTPS ||
|
||||
proxy().type == rtc::PROXY_UNKNOWN)) {
|
||||
server_addr_.push_front(addr);
|
||||
} else {
|
||||
server_addr_.push_back(addr);
|
||||
}
|
||||
}
|
||||
|
||||
void RelayPort::AddExternalAddress(const ProtocolAddress& addr) {
|
||||
std::string proto_name = ProtoToString(addr.proto);
|
||||
for (std::vector<ProtocolAddress>::iterator it = external_addr_.begin();
|
||||
it != external_addr_.end(); ++it) {
|
||||
if ((it->address == addr.address) && (it->proto == addr.proto)) {
|
||||
RTC_LOG(INFO) << "Redundant relay address: " << proto_name << " @ "
|
||||
<< addr.address.ToSensitiveString();
|
||||
return;
|
||||
}
|
||||
}
|
||||
external_addr_.push_back(addr);
|
||||
}
|
||||
|
||||
void RelayPort::SetReady() {
|
||||
if (!ready_) {
|
||||
std::vector<ProtocolAddress>::iterator iter;
|
||||
for (iter = external_addr_.begin(); iter != external_addr_.end(); ++iter) {
|
||||
std::string proto_name = ProtoToString(iter->proto);
|
||||
// In case of Gturn, related address is set to null socket address.
|
||||
// This is due to as mapped address stun attribute is used for allocated
|
||||
// address.
|
||||
AddAddress(iter->address, iter->address, rtc::SocketAddress(), proto_name,
|
||||
proto_name, "", RELAY_PORT_TYPE, ICE_TYPE_PREFERENCE_RELAY_UDP,
|
||||
0, "", false);
|
||||
}
|
||||
ready_ = true;
|
||||
SignalPortComplete(this);
|
||||
}
|
||||
}
|
||||
|
||||
const ProtocolAddress* RelayPort::ServerAddress(size_t index) const {
|
||||
if (index < server_addr_.size())
|
||||
return &server_addr_[index];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool RelayPort::HasMagicCookie(const char* data, size_t size) {
|
||||
if (size < 24 + sizeof(TURN_MAGIC_COOKIE_VALUE)) {
|
||||
return false;
|
||||
} else {
|
||||
return memcmp(data + 24, TURN_MAGIC_COOKIE_VALUE,
|
||||
sizeof(TURN_MAGIC_COOKIE_VALUE)) == 0;
|
||||
}
|
||||
}
|
||||
|
||||
void RelayPort::PrepareAddress() {
|
||||
// We initiate a connect on the first entry. If this completes, it will fill
|
||||
// in the server address as the address of this port.
|
||||
RTC_DCHECK(entries_.size() == 1);
|
||||
entries_[0]->Connect();
|
||||
ready_ = false;
|
||||
}
|
||||
|
||||
Connection* RelayPort::CreateConnection(const Candidate& address,
|
||||
CandidateOrigin origin) {
|
||||
// We only create conns to non-udp sockets if they are incoming on this port
|
||||
if ((address.protocol() != UDP_PROTOCOL_NAME) &&
|
||||
(origin != ORIGIN_THIS_PORT)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// We don't support loopback on relays
|
||||
if (address.type() == Type()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!IsCompatibleAddress(address.address())) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t index = 0;
|
||||
for (size_t i = 0; i < Candidates().size(); ++i) {
|
||||
const Candidate& local = Candidates()[i];
|
||||
if (local.protocol() == address.protocol()) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Connection* conn = new ProxyConnection(this, index, address);
|
||||
AddOrReplaceConnection(conn);
|
||||
return conn;
|
||||
}
|
||||
|
||||
int RelayPort::SendTo(const void* data,
|
||||
size_t size,
|
||||
const rtc::SocketAddress& addr,
|
||||
const rtc::PacketOptions& options,
|
||||
bool payload) {
|
||||
// Try to find an entry for this specific address. Note that the first entry
|
||||
// created was not given an address initially, so it can be set to the first
|
||||
// address that comes along.
|
||||
RelayEntry* entry = 0;
|
||||
|
||||
for (size_t i = 0; i < entries_.size(); ++i) {
|
||||
if (entries_[i]->address().IsNil() && payload) {
|
||||
entry = entries_[i];
|
||||
entry->set_address(addr);
|
||||
break;
|
||||
} else if (entries_[i]->address() == addr) {
|
||||
entry = entries_[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If we did not find one, then we make a new one. This will not be useable
|
||||
// until it becomes connected, however.
|
||||
if (!entry && payload) {
|
||||
entry = new RelayEntry(this, addr);
|
||||
if (!entries_.empty()) {
|
||||
entry->SetServerIndex(entries_[0]->ServerIndex());
|
||||
}
|
||||
entry->Connect();
|
||||
entries_.push_back(entry);
|
||||
}
|
||||
|
||||
// If the entry is connected, then we can send on it (though wrapping may
|
||||
// still be necessary). Otherwise, we can't yet use this connection, so we
|
||||
// default to the first one.
|
||||
if (!entry || !entry->connected()) {
|
||||
RTC_DCHECK(!entries_.empty());
|
||||
entry = entries_[0];
|
||||
if (!entry->connected()) {
|
||||
error_ = ENOTCONN;
|
||||
return SOCKET_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
// Send the actual contents to the server using the usual mechanism.
|
||||
rtc::PacketOptions modified_options(options);
|
||||
CopyPortInformationToPacketInfo(&modified_options.info_signaled_after_sent);
|
||||
int sent = entry->SendTo(data, size, addr, modified_options);
|
||||
if (sent <= 0) {
|
||||
RTC_DCHECK(sent < 0);
|
||||
error_ = entry->GetError();
|
||||
return SOCKET_ERROR;
|
||||
}
|
||||
// The caller of the function is expecting the number of user data bytes,
|
||||
// rather than the size of the packet.
|
||||
return static_cast<int>(size);
|
||||
}
|
||||
|
||||
int RelayPort::SetOption(rtc::Socket::Option opt, int value) {
|
||||
int result = 0;
|
||||
for (size_t i = 0; i < entries_.size(); ++i) {
|
||||
if (entries_[i]->SetSocketOption(opt, value) < 0) {
|
||||
result = -1;
|
||||
error_ = entries_[i]->GetError();
|
||||
}
|
||||
}
|
||||
options_.push_back(OptionValue(opt, value));
|
||||
return result;
|
||||
}
|
||||
|
||||
int RelayPort::GetOption(rtc::Socket::Option opt, int* value) {
|
||||
std::vector<OptionValue>::iterator it;
|
||||
for (it = options_.begin(); it < options_.end(); ++it) {
|
||||
if (it->first == opt) {
|
||||
*value = it->second;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return SOCKET_ERROR;
|
||||
}
|
||||
|
||||
int RelayPort::GetError() {
|
||||
return error_;
|
||||
}
|
||||
|
||||
bool RelayPort::SupportsProtocol(const std::string& protocol) const {
|
||||
// Relay port may create both TCP and UDP connections.
|
||||
return true;
|
||||
}
|
||||
|
||||
ProtocolType RelayPort::GetProtocol() const {
|
||||
// We shouldn't be using RelayPort, but we need to provide an implementation
|
||||
// here.
|
||||
return PROTO_UDP;
|
||||
}
|
||||
|
||||
void RelayPort::OnReadPacket(const char* data,
|
||||
size_t size,
|
||||
const rtc::SocketAddress& remote_addr,
|
||||
ProtocolType proto,
|
||||
int64_t packet_time_us) {
|
||||
if (Connection* conn = GetConnection(remote_addr)) {
|
||||
conn->OnReadPacket(data, size, packet_time_us);
|
||||
} else {
|
||||
Port::OnReadPacket(data, size, remote_addr, proto);
|
||||
}
|
||||
}
|
||||
|
||||
RelayConnection::RelayConnection(const ProtocolAddress* protocol_address,
|
||||
rtc::AsyncPacketSocket* socket,
|
||||
rtc::Thread* thread)
|
||||
: socket_(socket),
|
||||
protocol_address_(protocol_address),
|
||||
dscp_(rtc::DSCP_NO_CHANGE) {
|
||||
request_manager_ = new StunRequestManager(thread);
|
||||
request_manager_->SignalSendPacket.connect(this,
|
||||
&RelayConnection::OnSendPacket);
|
||||
}
|
||||
|
||||
RelayConnection::~RelayConnection() {
|
||||
delete request_manager_;
|
||||
delete socket_;
|
||||
}
|
||||
|
||||
int RelayConnection::SetSocketOption(rtc::Socket::Option opt, int value) {
|
||||
if (opt == rtc::Socket::OPT_DSCP) {
|
||||
dscp_ = static_cast<rtc::DiffServCodePoint>(value);
|
||||
}
|
||||
if (socket_) {
|
||||
return socket_->SetOption(opt, value);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool RelayConnection::CheckResponse(StunMessage* msg) {
|
||||
return request_manager_->CheckResponse(msg);
|
||||
}
|
||||
|
||||
void RelayConnection::OnSendPacket(const void* data,
|
||||
size_t size,
|
||||
StunRequest* req) {
|
||||
rtc::PacketOptions options(dscp_);
|
||||
int sent = socket_->SendTo(data, size, GetAddress(), options);
|
||||
if (sent <= 0) {
|
||||
RTC_LOG(LS_VERBOSE) << "OnSendPacket: failed sending to "
|
||||
<< GetAddress().ToSensitiveString()
|
||||
<< strerror(socket_->GetError());
|
||||
RTC_DCHECK(sent < 0);
|
||||
}
|
||||
}
|
||||
|
||||
int RelayConnection::Send(const void* pv,
|
||||
size_t cb,
|
||||
const rtc::PacketOptions& options) {
|
||||
return socket_->SendTo(pv, cb, GetAddress(), options);
|
||||
}
|
||||
|
||||
void RelayConnection::SendAllocateRequest(RelayEntry* entry, int delay) {
|
||||
request_manager_->SendDelayed(new AllocateRequest(entry, this), delay);
|
||||
}
|
||||
|
||||
RelayEntry::RelayEntry(RelayPort* port, const rtc::SocketAddress& ext_addr)
|
||||
: port_(port),
|
||||
ext_addr_(ext_addr),
|
||||
server_index_(0),
|
||||
connected_(false),
|
||||
locked_(false),
|
||||
current_connection_(NULL) {}
|
||||
|
||||
RelayEntry::~RelayEntry() {
|
||||
// Remove all RelayConnections and dispose sockets.
|
||||
delete current_connection_;
|
||||
current_connection_ = NULL;
|
||||
}
|
||||
|
||||
void RelayEntry::Connect() {
|
||||
// If we're already connected, return.
|
||||
if (connected_)
|
||||
return;
|
||||
|
||||
// If we've exhausted all options, bail out.
|
||||
const ProtocolAddress* ra = port()->ServerAddress(server_index_);
|
||||
if (!ra) {
|
||||
RTC_LOG(LS_WARNING) << "No more relay addresses left to try";
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove any previous connection.
|
||||
if (current_connection_) {
|
||||
port()->thread()->Dispose(current_connection_);
|
||||
current_connection_ = NULL;
|
||||
}
|
||||
|
||||
// Try to set up our new socket.
|
||||
RTC_LOG(LS_INFO) << "Connecting to relay via " << ProtoToString(ra->proto)
|
||||
<< " @ " << ra->address.ToSensitiveString();
|
||||
|
||||
rtc::AsyncPacketSocket* socket = NULL;
|
||||
|
||||
if (ra->proto == PROTO_UDP) {
|
||||
// UDP sockets are simple.
|
||||
socket = port_->socket_factory()->CreateUdpSocket(
|
||||
rtc::SocketAddress(port_->Network()->GetBestIP(), 0), port_->min_port(),
|
||||
port_->max_port());
|
||||
} else if (ra->proto == PROTO_TCP || ra->proto == PROTO_SSLTCP) {
|
||||
int opts = (ra->proto == PROTO_SSLTCP)
|
||||
? rtc::PacketSocketFactory::OPT_TLS_FAKE
|
||||
: 0;
|
||||
rtc::PacketSocketTcpOptions tcp_opts;
|
||||
tcp_opts.opts = opts;
|
||||
socket = port_->socket_factory()->CreateClientTcpSocket(
|
||||
rtc::SocketAddress(port_->Network()->GetBestIP(), 0), ra->address,
|
||||
port_->proxy(), port_->user_agent(), tcp_opts);
|
||||
} else {
|
||||
RTC_LOG(LS_WARNING) << "Unknown protocol: " << ra->proto;
|
||||
}
|
||||
|
||||
// If we failed to get a socket, move on to the next protocol.
|
||||
if (!socket) {
|
||||
RTC_LOG(LS_WARNING) << "Socket creation failed";
|
||||
port()->thread()->Post(RTC_FROM_HERE, this, kMessageConnectTimeout);
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, create the new connection and configure any socket options.
|
||||
socket->SignalReadPacket.connect(this, &RelayEntry::OnReadPacket);
|
||||
socket->SignalSentPacket.connect(this, &RelayEntry::OnSentPacket);
|
||||
socket->SignalReadyToSend.connect(this, &RelayEntry::OnReadyToSend);
|
||||
current_connection_ = new RelayConnection(ra, socket, port()->thread());
|
||||
for (size_t i = 0; i < port_->options().size(); ++i) {
|
||||
current_connection_->SetSocketOption(port_->options()[i].first,
|
||||
port_->options()[i].second);
|
||||
}
|
||||
|
||||
// If we're trying UDP, start binding requests.
|
||||
// If we're trying TCP, wait for connection with a fixed timeout.
|
||||
if ((ra->proto == PROTO_TCP) || (ra->proto == PROTO_SSLTCP)) {
|
||||
socket->SignalClose.connect(this, &RelayEntry::OnSocketClose);
|
||||
socket->SignalConnect.connect(this, &RelayEntry::OnSocketConnect);
|
||||
port()->thread()->PostDelayed(RTC_FROM_HERE, kSoftConnectTimeoutMs, this,
|
||||
kMessageConnectTimeout);
|
||||
} else {
|
||||
current_connection_->SendAllocateRequest(this, 0);
|
||||
}
|
||||
}
|
||||
|
||||
int RelayEntry::GetError() {
|
||||
if (current_connection_ != NULL) {
|
||||
return current_connection_->GetError();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
RelayConnection* RelayEntry::GetBestConnection(RelayConnection* conn1,
|
||||
RelayConnection* conn2) {
|
||||
return conn1->GetProtocol() <= conn2->GetProtocol() ? conn1 : conn2;
|
||||
}
|
||||
|
||||
void RelayEntry::OnConnect(const rtc::SocketAddress& mapped_addr,
|
||||
RelayConnection* connection) {
|
||||
// We are connected, notify our parent.
|
||||
ProtocolType proto = PROTO_UDP;
|
||||
RTC_LOG(INFO) << "Relay allocate succeeded: " << ProtoToString(proto) << " @ "
|
||||
<< mapped_addr.ToSensitiveString();
|
||||
connected_ = true;
|
||||
|
||||
port_->AddExternalAddress(ProtocolAddress(mapped_addr, proto));
|
||||
port_->SetReady();
|
||||
}
|
||||
|
||||
int RelayEntry::SendTo(const void* data,
|
||||
size_t size,
|
||||
const rtc::SocketAddress& addr,
|
||||
const rtc::PacketOptions& options) {
|
||||
// If this connection is locked to the address given, then we can send the
|
||||
// packet with no wrapper.
|
||||
if (locked_ && (ext_addr_ == addr))
|
||||
return SendPacket(data, size, options);
|
||||
|
||||
// Otherwise, we must wrap the given data in a STUN SEND request so that we
|
||||
// can communicate the destination address to the server.
|
||||
//
|
||||
// Note that we do not use a StunRequest here. This is because there is
|
||||
// likely no reason to resend this packet. If it is late, we just drop it.
|
||||
// The next send to this address will try again.
|
||||
|
||||
RelayMessage request;
|
||||
request.SetType(STUN_SEND_REQUEST);
|
||||
|
||||
auto magic_cookie_attr =
|
||||
StunAttribute::CreateByteString(STUN_ATTR_MAGIC_COOKIE);
|
||||
magic_cookie_attr->CopyBytes(TURN_MAGIC_COOKIE_VALUE,
|
||||
sizeof(TURN_MAGIC_COOKIE_VALUE));
|
||||
request.AddAttribute(std::move(magic_cookie_attr));
|
||||
|
||||
auto username_attr = StunAttribute::CreateByteString(STUN_ATTR_USERNAME);
|
||||
username_attr->CopyBytes(port_->username_fragment().c_str(),
|
||||
port_->username_fragment().size());
|
||||
request.AddAttribute(std::move(username_attr));
|
||||
|
||||
auto addr_attr = StunAttribute::CreateAddress(STUN_ATTR_DESTINATION_ADDRESS);
|
||||
addr_attr->SetIP(addr.ipaddr());
|
||||
addr_attr->SetPort(addr.port());
|
||||
request.AddAttribute(std::move(addr_attr));
|
||||
|
||||
// Attempt to lock
|
||||
if (ext_addr_ == addr) {
|
||||
auto options_attr = StunAttribute::CreateUInt32(STUN_ATTR_OPTIONS);
|
||||
options_attr->SetValue(0x1);
|
||||
request.AddAttribute(std::move(options_attr));
|
||||
}
|
||||
|
||||
auto data_attr = StunAttribute::CreateByteString(STUN_ATTR_DATA);
|
||||
data_attr->CopyBytes(data, size);
|
||||
request.AddAttribute(std::move(data_attr));
|
||||
|
||||
// TODO(?): compute the HMAC.
|
||||
|
||||
rtc::ByteBufferWriter buf;
|
||||
request.Write(&buf);
|
||||
|
||||
return SendPacket(buf.Data(), buf.Length(), options);
|
||||
}
|
||||
|
||||
void RelayEntry::ScheduleKeepAlive() {
|
||||
if (current_connection_) {
|
||||
current_connection_->SendAllocateRequest(this, kKeepAliveDelay);
|
||||
}
|
||||
}
|
||||
|
||||
int RelayEntry::SetSocketOption(rtc::Socket::Option opt, int value) {
|
||||
// Set the option on all available sockets.
|
||||
int socket_error = 0;
|
||||
if (current_connection_) {
|
||||
socket_error = current_connection_->SetSocketOption(opt, value);
|
||||
}
|
||||
return socket_error;
|
||||
}
|
||||
|
||||
void RelayEntry::HandleConnectFailure(rtc::AsyncPacketSocket* socket) {
|
||||
// Make sure it's the current connection that has failed, it might
|
||||
// be an old socked that has not yet been disposed.
|
||||
if (!socket ||
|
||||
(current_connection_ && socket == current_connection_->socket())) {
|
||||
if (current_connection_)
|
||||
port()->SignalConnectFailure(current_connection_->protocol_address());
|
||||
|
||||
// Try to connect to the next server address.
|
||||
server_index_ += 1;
|
||||
Connect();
|
||||
}
|
||||
}
|
||||
|
||||
void RelayEntry::OnMessage(rtc::Message* pmsg) {
|
||||
RTC_DCHECK(pmsg->message_id == kMessageConnectTimeout);
|
||||
if (current_connection_) {
|
||||
const ProtocolAddress* ra = current_connection_->protocol_address();
|
||||
RTC_LOG(LS_WARNING) << "Relay " << ra->proto << " connection to "
|
||||
<< ra->address.ToSensitiveString() << " timed out";
|
||||
|
||||
// Currently we connect to each server address in sequence. If we
|
||||
// have more addresses to try, treat this is an error and move on to
|
||||
// the next address, otherwise give this connection more time and
|
||||
// await the real timeout.
|
||||
//
|
||||
// TODO(?): Connect to servers in parallel to speed up connect time
|
||||
// and to avoid giving up too early.
|
||||
port_->SignalSoftTimeout(ra);
|
||||
HandleConnectFailure(current_connection_->socket());
|
||||
} else {
|
||||
HandleConnectFailure(NULL);
|
||||
}
|
||||
}
|
||||
|
||||
void RelayEntry::OnSocketConnect(rtc::AsyncPacketSocket* socket) {
|
||||
RTC_LOG(INFO) << "relay tcp connected to "
|
||||
<< socket->GetRemoteAddress().ToSensitiveString();
|
||||
if (current_connection_ != NULL) {
|
||||
current_connection_->SendAllocateRequest(this, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void RelayEntry::OnSocketClose(rtc::AsyncPacketSocket* socket, int error) {
|
||||
RTC_LOG_ERR_EX(LERROR, error) << "Relay connection failed: socket closed";
|
||||
HandleConnectFailure(socket);
|
||||
}
|
||||
|
||||
void RelayEntry::OnReadPacket(rtc::AsyncPacketSocket* socket,
|
||||
const char* data,
|
||||
size_t size,
|
||||
const rtc::SocketAddress& remote_addr,
|
||||
const int64_t& packet_time_us) {
|
||||
// RTC_DCHECK(remote_addr == port_->server_addr());
|
||||
// TODO(?): are we worried about this?
|
||||
|
||||
if (current_connection_ == NULL || socket != current_connection_->socket()) {
|
||||
// This packet comes from an unknown address.
|
||||
RTC_LOG(WARNING) << "Dropping packet: unknown address";
|
||||
return;
|
||||
}
|
||||
|
||||
// If the magic cookie is not present, then this is an unwrapped packet sent
|
||||
// by the server, The actual remote address is the one we recorded.
|
||||
if (!port_->HasMagicCookie(data, size)) {
|
||||
if (locked_) {
|
||||
port_->OnReadPacket(data, size, ext_addr_, PROTO_UDP, packet_time_us);
|
||||
} else {
|
||||
RTC_LOG(WARNING) << "Dropping packet: entry not locked";
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
rtc::ByteBufferReader buf(data, size);
|
||||
RelayMessage msg;
|
||||
if (!msg.Read(&buf)) {
|
||||
RTC_LOG(INFO) << "Incoming packet was not STUN";
|
||||
return;
|
||||
}
|
||||
|
||||
// The incoming packet should be a STUN ALLOCATE response, SEND response, or
|
||||
// DATA indication.
|
||||
if (current_connection_->CheckResponse(&msg)) {
|
||||
return;
|
||||
} else if (msg.type() == STUN_SEND_RESPONSE) {
|
||||
if (const StunUInt32Attribute* options_attr =
|
||||
msg.GetUInt32(STUN_ATTR_OPTIONS)) {
|
||||
if (options_attr->value() & 0x1) {
|
||||
locked_ = true;
|
||||
}
|
||||
}
|
||||
return;
|
||||
} else if (msg.type() != STUN_DATA_INDICATION) {
|
||||
RTC_LOG(INFO) << "Received BAD stun type from server: " << msg.type();
|
||||
return;
|
||||
}
|
||||
|
||||
// This must be a data indication.
|
||||
|
||||
const StunAddressAttribute* addr_attr =
|
||||
msg.GetAddress(STUN_ATTR_SOURCE_ADDRESS2);
|
||||
if (!addr_attr) {
|
||||
RTC_LOG(INFO) << "Data indication has no source address";
|
||||
return;
|
||||
} else if (addr_attr->family() != 1) {
|
||||
RTC_LOG(INFO) << "Source address has bad family";
|
||||
return;
|
||||
}
|
||||
|
||||
rtc::SocketAddress remote_addr2(addr_attr->ipaddr(), addr_attr->port());
|
||||
|
||||
const StunByteStringAttribute* data_attr = msg.GetByteString(STUN_ATTR_DATA);
|
||||
if (!data_attr) {
|
||||
RTC_LOG(INFO) << "Data indication has no data";
|
||||
return;
|
||||
}
|
||||
|
||||
// Process the actual data and remote address in the normal manner.
|
||||
port_->OnReadPacket(data_attr->bytes(), data_attr->length(), remote_addr2,
|
||||
PROTO_UDP, packet_time_us);
|
||||
}
|
||||
|
||||
void RelayEntry::OnSentPacket(rtc::AsyncPacketSocket* socket,
|
||||
const rtc::SentPacket& sent_packet) {
|
||||
port_->OnSentPacket(socket, sent_packet);
|
||||
}
|
||||
|
||||
void RelayEntry::OnReadyToSend(rtc::AsyncPacketSocket* socket) {
|
||||
if (connected()) {
|
||||
port_->OnReadyToSend();
|
||||
}
|
||||
}
|
||||
|
||||
int RelayEntry::SendPacket(const void* data,
|
||||
size_t size,
|
||||
const rtc::PacketOptions& options) {
|
||||
int sent = 0;
|
||||
if (current_connection_) {
|
||||
// We are connected, no need to send packets anywere else than to
|
||||
// the current connection.
|
||||
sent = current_connection_->Send(data, size, options);
|
||||
}
|
||||
return sent;
|
||||
}
|
||||
|
||||
AllocateRequest::AllocateRequest(RelayEntry* entry, RelayConnection* connection)
|
||||
: StunRequest(new RelayMessage()), entry_(entry), connection_(connection) {
|
||||
start_time_ = rtc::TimeMillis();
|
||||
}
|
||||
|
||||
void AllocateRequest::Prepare(StunMessage* request) {
|
||||
request->SetType(STUN_ALLOCATE_REQUEST);
|
||||
|
||||
auto username_attr = StunAttribute::CreateByteString(STUN_ATTR_USERNAME);
|
||||
username_attr->CopyBytes(entry_->port()->username_fragment().c_str(),
|
||||
entry_->port()->username_fragment().size());
|
||||
request->AddAttribute(std::move(username_attr));
|
||||
}
|
||||
|
||||
void AllocateRequest::OnSent() {
|
||||
count_ += 1;
|
||||
if (count_ == 5)
|
||||
timeout_ = true;
|
||||
}
|
||||
|
||||
int AllocateRequest::resend_delay() {
|
||||
if (count_ == 0) {
|
||||
return 0;
|
||||
}
|
||||
return 100 * std::max(1 << (count_ - 1), 2);
|
||||
}
|
||||
|
||||
void AllocateRequest::OnResponse(StunMessage* response) {
|
||||
const StunAddressAttribute* addr_attr =
|
||||
response->GetAddress(STUN_ATTR_MAPPED_ADDRESS);
|
||||
if (!addr_attr) {
|
||||
RTC_LOG(INFO) << "Allocate response missing mapped address.";
|
||||
} else if (addr_attr->family() != 1) {
|
||||
RTC_LOG(INFO) << "Mapped address has bad family";
|
||||
} else {
|
||||
rtc::SocketAddress addr(addr_attr->ipaddr(), addr_attr->port());
|
||||
entry_->OnConnect(addr, connection_);
|
||||
}
|
||||
|
||||
// We will do a keep-alive regardless of whether this request suceeds.
|
||||
// This should have almost no impact on network usage.
|
||||
entry_->ScheduleKeepAlive();
|
||||
}
|
||||
|
||||
void AllocateRequest::OnErrorResponse(StunMessage* response) {
|
||||
const StunErrorCodeAttribute* attr = response->GetErrorCode();
|
||||
if (!attr) {
|
||||
RTC_LOG(LS_ERROR) << "Missing allocate response error code.";
|
||||
} else {
|
||||
RTC_LOG(INFO) << "Allocate error response: code=" << attr->code()
|
||||
<< " reason=" << attr->reason();
|
||||
}
|
||||
|
||||
if (rtc::TimeMillis() - start_time_ <= kRetryTimeout)
|
||||
entry_->ScheduleKeepAlive();
|
||||
}
|
||||
|
||||
void AllocateRequest::OnTimeout() {
|
||||
RTC_LOG(INFO) << "Allocate request timed out";
|
||||
entry_->HandleConnectFailure(connection_->socket());
|
||||
}
|
||||
|
||||
} // namespace cricket
|
||||
@ -1,118 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef P2P_BASE_RELAY_PORT_H_
|
||||
#define P2P_BASE_RELAY_PORT_H_
|
||||
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/memory/memory.h"
|
||||
#include "p2p/base/port.h"
|
||||
#include "p2p/base/stun_request.h"
|
||||
|
||||
namespace cricket {
|
||||
|
||||
class RelayEntry;
|
||||
class RelayConnection;
|
||||
|
||||
// Communicates using an allocated port on the relay server. For each
|
||||
// remote candidate that we try to send data to a RelayEntry instance
|
||||
// is created. The RelayEntry will try to reach the remote destination
|
||||
// by connecting to all available server addresses in a pre defined
|
||||
// order with a small delay in between. When a connection is
|
||||
// successful all other connection attempts are aborted.
|
||||
class RelayPort : public Port {
|
||||
public:
|
||||
typedef std::pair<rtc::Socket::Option, int> OptionValue;
|
||||
|
||||
// RelayPort doesn't yet do anything fancy in the ctor.
|
||||
static std::unique_ptr<RelayPort> Create(rtc::Thread* thread,
|
||||
rtc::PacketSocketFactory* factory,
|
||||
rtc::Network* network,
|
||||
uint16_t min_port,
|
||||
uint16_t max_port,
|
||||
const std::string& username,
|
||||
const std::string& password) {
|
||||
// Using `new` to access a non-public constructor.
|
||||
return absl::WrapUnique(new RelayPort(thread, factory, network, min_port,
|
||||
max_port, username, password));
|
||||
}
|
||||
~RelayPort() override;
|
||||
|
||||
void AddServerAddress(const ProtocolAddress& addr);
|
||||
void AddExternalAddress(const ProtocolAddress& addr);
|
||||
|
||||
const std::vector<OptionValue>& options() const { return options_; }
|
||||
bool HasMagicCookie(const char* data, size_t size);
|
||||
|
||||
void PrepareAddress() override;
|
||||
Connection* CreateConnection(const Candidate& address,
|
||||
CandidateOrigin origin) override;
|
||||
int SetOption(rtc::Socket::Option opt, int value) override;
|
||||
int GetOption(rtc::Socket::Option opt, int* value) override;
|
||||
int GetError() override;
|
||||
bool SupportsProtocol(const std::string& protocol) const override;
|
||||
ProtocolType GetProtocol() const override;
|
||||
|
||||
const ProtocolAddress* ServerAddress(size_t index) const;
|
||||
bool IsReady() { return ready_; }
|
||||
|
||||
// Used for testing.
|
||||
sigslot::signal1<const ProtocolAddress*> SignalConnectFailure;
|
||||
sigslot::signal1<const ProtocolAddress*> SignalSoftTimeout;
|
||||
|
||||
protected:
|
||||
RelayPort(rtc::Thread* thread,
|
||||
rtc::PacketSocketFactory* factory,
|
||||
rtc::Network*,
|
||||
uint16_t min_port,
|
||||
uint16_t max_port,
|
||||
const std::string& username,
|
||||
const std::string& password);
|
||||
bool Init();
|
||||
|
||||
void SetReady();
|
||||
|
||||
int SendTo(const void* data,
|
||||
size_t size,
|
||||
const rtc::SocketAddress& addr,
|
||||
const rtc::PacketOptions& options,
|
||||
bool payload) override;
|
||||
|
||||
// Dispatches the given packet to the port or connection as appropriate.
|
||||
void OnReadPacket(const char* data,
|
||||
size_t size,
|
||||
const rtc::SocketAddress& remote_addr,
|
||||
ProtocolType proto,
|
||||
int64_t packet_time_us);
|
||||
|
||||
// The OnSentPacket callback is left empty here since they are handled by
|
||||
// RelayEntry.
|
||||
void OnSentPacket(rtc::AsyncPacketSocket* socket,
|
||||
const rtc::SentPacket& sent_packet) override {}
|
||||
|
||||
private:
|
||||
friend class RelayEntry;
|
||||
|
||||
std::deque<ProtocolAddress> server_addr_;
|
||||
std::vector<ProtocolAddress> external_addr_;
|
||||
bool ready_;
|
||||
std::vector<RelayEntry*> entries_;
|
||||
std::vector<OptionValue> options_;
|
||||
int error_;
|
||||
};
|
||||
|
||||
} // namespace cricket
|
||||
|
||||
#endif // P2P_BASE_RELAY_PORT_H_
|
||||
@ -1,272 +0,0 @@
|
||||
/*
|
||||
* Copyright 2009 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/relay_port.h"
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
|
||||
#include "p2p/base/basic_packet_socket_factory.h"
|
||||
#include "p2p/base/relay_server.h"
|
||||
#include "rtc_base/gunit.h"
|
||||
#include "rtc_base/helpers.h"
|
||||
#include "rtc_base/logging.h"
|
||||
#include "rtc_base/socket_adapters.h"
|
||||
#include "rtc_base/socket_address.h"
|
||||
#include "rtc_base/ssl_adapter.h"
|
||||
#include "rtc_base/thread.h"
|
||||
#include "rtc_base/virtual_socket_server.h"
|
||||
|
||||
using rtc::SocketAddress;
|
||||
|
||||
static const SocketAddress kLocalAddress = SocketAddress("192.168.1.2", 0);
|
||||
static const SocketAddress kRelayUdpAddr = SocketAddress("99.99.99.1", 5000);
|
||||
static const SocketAddress kRelayTcpAddr = SocketAddress("99.99.99.2", 5001);
|
||||
static const SocketAddress kRelaySslAddr = SocketAddress("99.99.99.3", 443);
|
||||
static const SocketAddress kRelayExtAddr = SocketAddress("99.99.99.3", 5002);
|
||||
|
||||
static const int kTimeoutMs = 1000;
|
||||
static const int kMaxTimeoutMs = 5000;
|
||||
|
||||
// Tests connecting a RelayPort to a fake relay server
|
||||
// (cricket::RelayServer) using all currently available protocols. The
|
||||
// network layer is faked out by using a VirtualSocketServer for
|
||||
// creating sockets. The test will monitor the current state of the
|
||||
// RelayPort and created sockets by listening for signals such as,
|
||||
// SignalConnectFailure, SignalConnectTimeout, SignalSocketClosed and
|
||||
// SignalReadPacket.
|
||||
class RelayPortTest : public ::testing::Test, public sigslot::has_slots<> {
|
||||
public:
|
||||
RelayPortTest()
|
||||
: virtual_socket_server_(new rtc::VirtualSocketServer()),
|
||||
main_(virtual_socket_server_.get()),
|
||||
network_("unittest", "unittest", kLocalAddress.ipaddr(), 32),
|
||||
socket_factory_(rtc::Thread::Current()),
|
||||
username_(rtc::CreateRandomString(16)),
|
||||
password_(rtc::CreateRandomString(16)),
|
||||
relay_port_(cricket::RelayPort::Create(&main_,
|
||||
&socket_factory_,
|
||||
&network_,
|
||||
0,
|
||||
0,
|
||||
username_,
|
||||
password_)),
|
||||
relay_server_(new cricket::RelayServer(&main_)) {
|
||||
network_.AddIP(kLocalAddress.ipaddr());
|
||||
}
|
||||
|
||||
void OnReadPacket(rtc::AsyncPacketSocket* socket,
|
||||
const char* /* data */,
|
||||
size_t /* size */,
|
||||
const rtc::SocketAddress& /* remote_addr */,
|
||||
const int64_t& /* packet_time_us */) {
|
||||
received_packet_count_[socket]++;
|
||||
}
|
||||
|
||||
void OnConnectFailure(const cricket::ProtocolAddress* addr) {
|
||||
failed_connections_.push_back(*addr);
|
||||
}
|
||||
|
||||
void OnSoftTimeout(const cricket::ProtocolAddress* addr) {
|
||||
soft_timedout_connections_.push_back(*addr);
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void SetUp() {
|
||||
// The relay server needs an external socket to work properly.
|
||||
rtc::AsyncUDPSocket* ext_socket = CreateAsyncUdpSocket(kRelayExtAddr);
|
||||
relay_server_->AddExternalSocket(ext_socket);
|
||||
|
||||
// Listen for failures.
|
||||
relay_port_->SignalConnectFailure.connect(this,
|
||||
&RelayPortTest::OnConnectFailure);
|
||||
|
||||
// Listen for soft timeouts.
|
||||
relay_port_->SignalSoftTimeout.connect(this, &RelayPortTest::OnSoftTimeout);
|
||||
}
|
||||
|
||||
// Udp has the highest 'goodness' value of the three different
|
||||
// protocols used for connecting to the relay server. As soon as
|
||||
// PrepareAddress is called, the RelayPort will start trying to
|
||||
// connect to the given UDP address. As soon as a response to the
|
||||
// sent STUN allocate request message has been received, the
|
||||
// RelayPort will consider the connection to be complete and will
|
||||
// abort any other connection attempts.
|
||||
void TestConnectUdp() {
|
||||
// Add a UDP socket to the relay server.
|
||||
rtc::AsyncUDPSocket* internal_udp_socket =
|
||||
CreateAsyncUdpSocket(kRelayUdpAddr);
|
||||
rtc::AsyncSocket* server_socket = CreateServerSocket(kRelayTcpAddr);
|
||||
|
||||
relay_server_->AddInternalSocket(internal_udp_socket);
|
||||
relay_server_->AddInternalServerSocket(server_socket, cricket::PROTO_TCP);
|
||||
|
||||
// Now add our relay addresses to the relay port and let it start.
|
||||
relay_port_->AddServerAddress(
|
||||
cricket::ProtocolAddress(kRelayUdpAddr, cricket::PROTO_UDP));
|
||||
relay_port_->AddServerAddress(
|
||||
cricket::ProtocolAddress(kRelayTcpAddr, cricket::PROTO_TCP));
|
||||
relay_port_->PrepareAddress();
|
||||
|
||||
// Should be connected.
|
||||
EXPECT_TRUE_WAIT(relay_port_->IsReady(), kTimeoutMs);
|
||||
|
||||
// Make sure that we are happy with UDP, ie. not continuing with
|
||||
// TCP, SSLTCP, etc.
|
||||
WAIT(relay_server_->HasConnection(kRelayTcpAddr), kTimeoutMs);
|
||||
|
||||
// Should have only one connection.
|
||||
EXPECT_EQ(1, relay_server_->GetConnectionCount());
|
||||
|
||||
// Should be the UDP address.
|
||||
EXPECT_TRUE(relay_server_->HasConnection(kRelayUdpAddr));
|
||||
}
|
||||
|
||||
// TCP has the second best 'goodness' value, and as soon as UDP
|
||||
// connection has failed, the RelayPort will attempt to connect via
|
||||
// TCP. Here we add a fake UDP address together with a real TCP
|
||||
// address to simulate an UDP failure. As soon as UDP has failed the
|
||||
// RelayPort will try the TCP adress and succed.
|
||||
void TestConnectTcp() {
|
||||
// Create a fake UDP address for relay port to simulate a failure.
|
||||
cricket::ProtocolAddress fake_protocol_address =
|
||||
cricket::ProtocolAddress(kRelayUdpAddr, cricket::PROTO_UDP);
|
||||
|
||||
// Create a server socket for the RelayServer.
|
||||
rtc::AsyncSocket* server_socket = CreateServerSocket(kRelayTcpAddr);
|
||||
relay_server_->AddInternalServerSocket(server_socket, cricket::PROTO_TCP);
|
||||
|
||||
// Add server addresses to the relay port and let it start.
|
||||
relay_port_->AddServerAddress(
|
||||
cricket::ProtocolAddress(fake_protocol_address));
|
||||
relay_port_->AddServerAddress(
|
||||
cricket::ProtocolAddress(kRelayTcpAddr, cricket::PROTO_TCP));
|
||||
relay_port_->PrepareAddress();
|
||||
|
||||
EXPECT_FALSE(relay_port_->IsReady());
|
||||
|
||||
// Should have timed out in 200 + 200 + 400 + 800 + 1600 ms = 3200ms.
|
||||
// Add some margin of error for slow bots.
|
||||
// TODO(deadbeef): Use simulated clock instead of just increasing timeouts
|
||||
// to fix flaky tests.
|
||||
EXPECT_TRUE_WAIT(HasFailed(&fake_protocol_address), 5000);
|
||||
|
||||
// Wait until relayport is ready.
|
||||
EXPECT_TRUE_WAIT(relay_port_->IsReady(), kMaxTimeoutMs);
|
||||
|
||||
// Should have only one connection.
|
||||
EXPECT_EQ(1, relay_server_->GetConnectionCount());
|
||||
|
||||
// Should be the TCP address.
|
||||
EXPECT_TRUE(relay_server_->HasConnection(kRelayTcpAddr));
|
||||
}
|
||||
|
||||
void TestConnectSslTcp() {
|
||||
// Create a fake TCP address for relay port to simulate a failure.
|
||||
// We skip UDP here since transition from UDP to TCP has been
|
||||
// tested above.
|
||||
cricket::ProtocolAddress fake_protocol_address =
|
||||
cricket::ProtocolAddress(kRelayTcpAddr, cricket::PROTO_TCP);
|
||||
|
||||
// Create a ssl server socket for the RelayServer.
|
||||
rtc::AsyncSocket* ssl_server_socket = CreateServerSocket(kRelaySslAddr);
|
||||
relay_server_->AddInternalServerSocket(ssl_server_socket,
|
||||
cricket::PROTO_SSLTCP);
|
||||
|
||||
// Create a tcp server socket that listens on the fake address so
|
||||
// the relay port can attempt to connect to it.
|
||||
std::unique_ptr<rtc::AsyncSocket> tcp_server_socket(
|
||||
CreateServerSocket(kRelayTcpAddr));
|
||||
|
||||
// Add server addresses to the relay port and let it start.
|
||||
relay_port_->AddServerAddress(fake_protocol_address);
|
||||
relay_port_->AddServerAddress(
|
||||
cricket::ProtocolAddress(kRelaySslAddr, cricket::PROTO_SSLTCP));
|
||||
relay_port_->PrepareAddress();
|
||||
EXPECT_FALSE(relay_port_->IsReady());
|
||||
|
||||
// Should have timed out in 3000 ms(relayport.cc, kSoftConnectTimeoutMs).
|
||||
EXPECT_TRUE_WAIT_MARGIN(HasTimedOut(&fake_protocol_address), 3000, 100);
|
||||
|
||||
// Wait until relayport is ready.
|
||||
EXPECT_TRUE_WAIT(relay_port_->IsReady(), kMaxTimeoutMs);
|
||||
|
||||
// Should have only one connection.
|
||||
EXPECT_EQ(1, relay_server_->GetConnectionCount());
|
||||
|
||||
// Should be the SSLTCP address.
|
||||
EXPECT_TRUE(relay_server_->HasConnection(kRelaySslAddr));
|
||||
}
|
||||
|
||||
private:
|
||||
rtc::AsyncUDPSocket* CreateAsyncUdpSocket(const SocketAddress addr) {
|
||||
rtc::AsyncSocket* socket =
|
||||
virtual_socket_server_->CreateAsyncSocket(AF_INET, SOCK_DGRAM);
|
||||
rtc::AsyncUDPSocket* packet_socket =
|
||||
rtc::AsyncUDPSocket::Create(socket, addr);
|
||||
EXPECT_TRUE(packet_socket != NULL);
|
||||
packet_socket->SignalReadPacket.connect(this, &RelayPortTest::OnReadPacket);
|
||||
return packet_socket;
|
||||
}
|
||||
|
||||
rtc::AsyncSocket* CreateServerSocket(const SocketAddress addr) {
|
||||
rtc::AsyncSocket* socket =
|
||||
virtual_socket_server_->CreateAsyncSocket(AF_INET, SOCK_STREAM);
|
||||
EXPECT_GE(socket->Bind(addr), 0);
|
||||
EXPECT_GE(socket->Listen(5), 0);
|
||||
return socket;
|
||||
}
|
||||
|
||||
bool HasFailed(cricket::ProtocolAddress* addr) {
|
||||
for (size_t i = 0; i < failed_connections_.size(); i++) {
|
||||
if (failed_connections_[i].address == addr->address &&
|
||||
failed_connections_[i].proto == addr->proto) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HasTimedOut(cricket::ProtocolAddress* addr) {
|
||||
for (size_t i = 0; i < soft_timedout_connections_.size(); i++) {
|
||||
if (soft_timedout_connections_[i].address == addr->address &&
|
||||
soft_timedout_connections_[i].proto == addr->proto) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
typedef std::map<rtc::AsyncPacketSocket*, int> PacketMap;
|
||||
|
||||
std::unique_ptr<rtc::VirtualSocketServer> virtual_socket_server_;
|
||||
rtc::AutoSocketServerThread main_;
|
||||
rtc::Network network_;
|
||||
rtc::BasicPacketSocketFactory socket_factory_;
|
||||
std::string username_;
|
||||
std::string password_;
|
||||
std::unique_ptr<cricket::RelayPort> relay_port_;
|
||||
std::unique_ptr<cricket::RelayServer> relay_server_;
|
||||
std::vector<cricket::ProtocolAddress> failed_connections_;
|
||||
std::vector<cricket::ProtocolAddress> soft_timedout_connections_;
|
||||
PacketMap received_packet_count_;
|
||||
};
|
||||
|
||||
TEST_F(RelayPortTest, ConnectUdp) {
|
||||
TestConnectUdp();
|
||||
}
|
||||
|
||||
TEST_F(RelayPortTest, ConnectTcp) {
|
||||
TestConnectTcp();
|
||||
}
|
||||
|
||||
TEST_F(RelayPortTest, ConnectSslTcp) {
|
||||
TestConnectSslTcp();
|
||||
}
|
||||
@ -1,741 +0,0 @@
|
||||
/*
|
||||
* 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/relay_server.h"
|
||||
|
||||
#ifdef WEBRTC_POSIX
|
||||
#include <errno.h>
|
||||
#endif // WEBRTC_POSIX
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
#include "absl/algorithm/container.h"
|
||||
#include "rtc_base/async_tcp_socket.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/helpers.h"
|
||||
#include "rtc_base/logging.h"
|
||||
#include "rtc_base/numerics/safe_conversions.h"
|
||||
#include "rtc_base/server_socket_adapters.h"
|
||||
|
||||
namespace cricket {
|
||||
|
||||
// By default, we require a ping every 90 seconds.
|
||||
const int MAX_LIFETIME = 15 * 60 * 1000;
|
||||
|
||||
// The number of bytes in each of the usernames we use.
|
||||
const uint32_t USERNAME_LENGTH = 16;
|
||||
|
||||
// Calls SendTo on the given socket and logs any bad results.
|
||||
void Send(rtc::AsyncPacketSocket* socket,
|
||||
const char* bytes,
|
||||
size_t size,
|
||||
const rtc::SocketAddress& addr) {
|
||||
rtc::PacketOptions options;
|
||||
int result = socket->SendTo(bytes, size, addr, options);
|
||||
if (result < static_cast<int>(size)) {
|
||||
RTC_LOG(LS_ERROR) << "SendTo wrote only " << result << " of " << size
|
||||
<< " bytes";
|
||||
} else if (result < 0) {
|
||||
RTC_LOG_ERR(LS_ERROR) << "SendTo";
|
||||
}
|
||||
}
|
||||
|
||||
// Sends the given STUN message on the given socket.
|
||||
void SendStun(const StunMessage& msg,
|
||||
rtc::AsyncPacketSocket* socket,
|
||||
const rtc::SocketAddress& addr) {
|
||||
rtc::ByteBufferWriter buf;
|
||||
msg.Write(&buf);
|
||||
Send(socket, buf.Data(), buf.Length(), addr);
|
||||
}
|
||||
|
||||
// Constructs a STUN error response and sends it on the given socket.
|
||||
void SendStunError(const StunMessage& msg,
|
||||
rtc::AsyncPacketSocket* socket,
|
||||
const rtc::SocketAddress& remote_addr,
|
||||
int error_code,
|
||||
const char* error_desc,
|
||||
const std::string& magic_cookie) {
|
||||
RelayMessage err_msg;
|
||||
err_msg.SetType(GetStunErrorResponseType(msg.type()));
|
||||
err_msg.SetTransactionID(msg.transaction_id());
|
||||
|
||||
auto magic_cookie_attr =
|
||||
StunAttribute::CreateByteString(cricket::STUN_ATTR_MAGIC_COOKIE);
|
||||
if (magic_cookie.size() == 0) {
|
||||
magic_cookie_attr->CopyBytes(cricket::TURN_MAGIC_COOKIE_VALUE,
|
||||
sizeof(cricket::TURN_MAGIC_COOKIE_VALUE));
|
||||
} else {
|
||||
magic_cookie_attr->CopyBytes(magic_cookie.c_str(), magic_cookie.size());
|
||||
}
|
||||
err_msg.AddAttribute(std::move(magic_cookie_attr));
|
||||
|
||||
auto err_code = StunAttribute::CreateErrorCode();
|
||||
err_code->SetClass(error_code / 100);
|
||||
err_code->SetNumber(error_code % 100);
|
||||
err_code->SetReason(error_desc);
|
||||
err_msg.AddAttribute(std::move(err_code));
|
||||
|
||||
SendStun(err_msg, socket, remote_addr);
|
||||
}
|
||||
|
||||
RelayServer::RelayServer(rtc::Thread* thread)
|
||||
: thread_(thread), random_(rtc::SystemTimeNanos()), log_bindings_(true) {}
|
||||
|
||||
RelayServer::~RelayServer() {
|
||||
// Deleting the binding will cause it to be removed from the map.
|
||||
while (!bindings_.empty())
|
||||
delete bindings_.begin()->second;
|
||||
for (size_t i = 0; i < internal_sockets_.size(); ++i)
|
||||
delete internal_sockets_[i];
|
||||
for (size_t i = 0; i < external_sockets_.size(); ++i)
|
||||
delete external_sockets_[i];
|
||||
for (size_t i = 0; i < removed_sockets_.size(); ++i)
|
||||
delete removed_sockets_[i];
|
||||
while (!server_sockets_.empty()) {
|
||||
rtc::AsyncSocket* socket = server_sockets_.begin()->first;
|
||||
server_sockets_.erase(server_sockets_.begin()->first);
|
||||
delete socket;
|
||||
}
|
||||
}
|
||||
|
||||
void RelayServer::AddInternalSocket(rtc::AsyncPacketSocket* socket) {
|
||||
RTC_DCHECK(!absl::c_linear_search(internal_sockets_, socket));
|
||||
internal_sockets_.push_back(socket);
|
||||
socket->SignalReadPacket.connect(this, &RelayServer::OnInternalPacket);
|
||||
}
|
||||
|
||||
void RelayServer::RemoveInternalSocket(rtc::AsyncPacketSocket* socket) {
|
||||
auto iter = absl::c_find(internal_sockets_, socket);
|
||||
RTC_DCHECK(iter != internal_sockets_.end());
|
||||
internal_sockets_.erase(iter);
|
||||
removed_sockets_.push_back(socket);
|
||||
socket->SignalReadPacket.disconnect(this);
|
||||
}
|
||||
|
||||
void RelayServer::AddExternalSocket(rtc::AsyncPacketSocket* socket) {
|
||||
RTC_DCHECK(!absl::c_linear_search(external_sockets_, socket));
|
||||
external_sockets_.push_back(socket);
|
||||
socket->SignalReadPacket.connect(this, &RelayServer::OnExternalPacket);
|
||||
}
|
||||
|
||||
void RelayServer::RemoveExternalSocket(rtc::AsyncPacketSocket* socket) {
|
||||
auto iter = absl::c_find(external_sockets_, socket);
|
||||
RTC_DCHECK(iter != external_sockets_.end());
|
||||
external_sockets_.erase(iter);
|
||||
removed_sockets_.push_back(socket);
|
||||
socket->SignalReadPacket.disconnect(this);
|
||||
}
|
||||
|
||||
void RelayServer::AddInternalServerSocket(rtc::AsyncSocket* socket,
|
||||
cricket::ProtocolType proto) {
|
||||
RTC_DCHECK(server_sockets_.end() == server_sockets_.find(socket));
|
||||
server_sockets_[socket] = proto;
|
||||
socket->SignalReadEvent.connect(this, &RelayServer::OnReadEvent);
|
||||
}
|
||||
|
||||
void RelayServer::RemoveInternalServerSocket(rtc::AsyncSocket* socket) {
|
||||
auto iter = server_sockets_.find(socket);
|
||||
RTC_DCHECK(iter != server_sockets_.end());
|
||||
server_sockets_.erase(iter);
|
||||
socket->SignalReadEvent.disconnect(this);
|
||||
}
|
||||
|
||||
int RelayServer::GetConnectionCount() const {
|
||||
return static_cast<int>(connections_.size());
|
||||
}
|
||||
|
||||
rtc::SocketAddressPair RelayServer::GetConnection(int connection) const {
|
||||
int i = 0;
|
||||
for (const auto& entry : connections_) {
|
||||
if (i == connection) {
|
||||
return entry.second->addr_pair();
|
||||
}
|
||||
++i;
|
||||
}
|
||||
return rtc::SocketAddressPair();
|
||||
}
|
||||
|
||||
bool RelayServer::HasConnection(const rtc::SocketAddress& address) const {
|
||||
for (const auto& entry : connections_) {
|
||||
if (entry.second->addr_pair().destination() == address) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void RelayServer::OnReadEvent(rtc::AsyncSocket* socket) {
|
||||
RTC_DCHECK(server_sockets_.find(socket) != server_sockets_.end());
|
||||
AcceptConnection(socket);
|
||||
}
|
||||
|
||||
void RelayServer::OnInternalPacket(rtc::AsyncPacketSocket* socket,
|
||||
const char* bytes,
|
||||
size_t size,
|
||||
const rtc::SocketAddress& remote_addr,
|
||||
const int64_t& /* packet_time_us */) {
|
||||
// Get the address of the connection we just received on.
|
||||
rtc::SocketAddressPair ap(remote_addr, socket->GetLocalAddress());
|
||||
RTC_DCHECK(!ap.destination().IsNil());
|
||||
|
||||
// If this did not come from an existing connection, it should be a STUN
|
||||
// allocate request.
|
||||
auto piter = connections_.find(ap);
|
||||
if (piter == connections_.end()) {
|
||||
HandleStunAllocate(bytes, size, ap, socket);
|
||||
return;
|
||||
}
|
||||
|
||||
RelayServerConnection* int_conn = piter->second;
|
||||
|
||||
// Handle STUN requests to the server itself.
|
||||
if (int_conn->binding()->HasMagicCookie(bytes, size)) {
|
||||
HandleStun(int_conn, bytes, size);
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, this is a non-wrapped packet that we are to forward. Make sure
|
||||
// that this connection has been locked. (Otherwise, we would not know what
|
||||
// address to forward to.)
|
||||
if (!int_conn->locked()) {
|
||||
RTC_LOG(LS_WARNING) << "Dropping packet: connection not locked";
|
||||
return;
|
||||
}
|
||||
|
||||
// Forward this to the destination address into the connection.
|
||||
RelayServerConnection* ext_conn = int_conn->binding()->GetExternalConnection(
|
||||
int_conn->default_destination());
|
||||
if (ext_conn && ext_conn->locked()) {
|
||||
// TODO(?): Check the HMAC.
|
||||
ext_conn->Send(bytes, size);
|
||||
} else {
|
||||
// This happens very often and is not an error.
|
||||
RTC_LOG(LS_INFO) << "Dropping packet: no external connection";
|
||||
}
|
||||
}
|
||||
|
||||
void RelayServer::OnExternalPacket(rtc::AsyncPacketSocket* socket,
|
||||
const char* bytes,
|
||||
size_t size,
|
||||
const rtc::SocketAddress& remote_addr,
|
||||
const int64_t& /* packet_time_us */) {
|
||||
// Get the address of the connection we just received on.
|
||||
rtc::SocketAddressPair ap(remote_addr, socket->GetLocalAddress());
|
||||
RTC_DCHECK(!ap.destination().IsNil());
|
||||
|
||||
// If this connection already exists, then forward the traffic.
|
||||
auto piter = connections_.find(ap);
|
||||
if (piter != connections_.end()) {
|
||||
// TODO(?): Check the HMAC.
|
||||
RelayServerConnection* ext_conn = piter->second;
|
||||
RelayServerConnection* int_conn =
|
||||
ext_conn->binding()->GetInternalConnection(
|
||||
ext_conn->addr_pair().source());
|
||||
RTC_DCHECK(int_conn != NULL);
|
||||
int_conn->Send(bytes, size, ext_conn->addr_pair().source());
|
||||
ext_conn->Lock(); // allow outgoing packets
|
||||
return;
|
||||
}
|
||||
|
||||
// The first packet should always be a STUN / TURN packet. If it isn't, then
|
||||
// we should just ignore this packet.
|
||||
RelayMessage msg;
|
||||
rtc::ByteBufferReader buf(bytes, size);
|
||||
if (!msg.Read(&buf)) {
|
||||
RTC_LOG(LS_WARNING) << "Dropping packet: first packet not STUN";
|
||||
return;
|
||||
}
|
||||
|
||||
// The initial packet should have a username (which identifies the binding).
|
||||
const StunByteStringAttribute* username_attr =
|
||||
msg.GetByteString(STUN_ATTR_USERNAME);
|
||||
if (!username_attr) {
|
||||
RTC_LOG(LS_WARNING) << "Dropping packet: no username";
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t length =
|
||||
std::min(static_cast<uint32_t>(username_attr->length()), USERNAME_LENGTH);
|
||||
std::string username(username_attr->bytes(), length);
|
||||
// TODO(?): Check the HMAC.
|
||||
|
||||
// The binding should already be present.
|
||||
auto biter = bindings_.find(username);
|
||||
if (biter == bindings_.end()) {
|
||||
RTC_LOG(LS_WARNING) << "Dropping packet: no binding with username";
|
||||
return;
|
||||
}
|
||||
|
||||
// Add this authenticted connection to the binding.
|
||||
RelayServerConnection* ext_conn =
|
||||
new RelayServerConnection(biter->second, ap, socket);
|
||||
ext_conn->binding()->AddExternalConnection(ext_conn);
|
||||
AddConnection(ext_conn);
|
||||
|
||||
// We always know where external packets should be forwarded, so we can lock
|
||||
// them from the beginning.
|
||||
ext_conn->Lock();
|
||||
|
||||
// Send this message on the appropriate internal connection.
|
||||
RelayServerConnection* int_conn = ext_conn->binding()->GetInternalConnection(
|
||||
ext_conn->addr_pair().source());
|
||||
RTC_DCHECK(int_conn != NULL);
|
||||
int_conn->Send(bytes, size, ext_conn->addr_pair().source());
|
||||
}
|
||||
|
||||
bool RelayServer::HandleStun(const char* bytes,
|
||||
size_t size,
|
||||
const rtc::SocketAddress& remote_addr,
|
||||
rtc::AsyncPacketSocket* socket,
|
||||
std::string* username,
|
||||
StunMessage* msg) {
|
||||
// Parse this into a stun message. Eat the message if this fails.
|
||||
rtc::ByteBufferReader buf(bytes, size);
|
||||
if (!msg->Read(&buf)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The initial packet should have a username (which identifies the binding).
|
||||
const StunByteStringAttribute* username_attr =
|
||||
msg->GetByteString(STUN_ATTR_USERNAME);
|
||||
if (!username_attr) {
|
||||
SendStunError(*msg, socket, remote_addr, 432, "Missing Username", "");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Record the username if requested.
|
||||
if (username)
|
||||
username->append(username_attr->bytes(), username_attr->length());
|
||||
|
||||
// TODO(?): Check for unknown attributes (<= 0x7fff)
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void RelayServer::HandleStunAllocate(const char* bytes,
|
||||
size_t size,
|
||||
const rtc::SocketAddressPair& ap,
|
||||
rtc::AsyncPacketSocket* socket) {
|
||||
// Make sure this is a valid STUN request.
|
||||
RelayMessage request;
|
||||
std::string username;
|
||||
if (!HandleStun(bytes, size, ap.source(), socket, &username, &request))
|
||||
return;
|
||||
|
||||
// Make sure this is a an allocate request.
|
||||
if (request.type() != STUN_ALLOCATE_REQUEST) {
|
||||
SendStunError(request, socket, ap.source(), 600, "Operation Not Supported",
|
||||
"");
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO(?): Check the HMAC.
|
||||
|
||||
// Find or create the binding for this username.
|
||||
|
||||
RelayServerBinding* binding;
|
||||
|
||||
auto biter = bindings_.find(username);
|
||||
if (biter != bindings_.end()) {
|
||||
binding = biter->second;
|
||||
} else {
|
||||
// NOTE: In the future, bindings will be created by the bot only. This
|
||||
// else-branch will then disappear.
|
||||
|
||||
// Compute the appropriate lifetime for this binding.
|
||||
int lifetime = MAX_LIFETIME;
|
||||
const StunUInt32Attribute* lifetime_attr =
|
||||
request.GetUInt32(STUN_ATTR_LIFETIME);
|
||||
if (lifetime_attr)
|
||||
lifetime =
|
||||
std::min(lifetime, static_cast<int>(lifetime_attr->value() * 1000));
|
||||
|
||||
binding = new RelayServerBinding(this, username, "0", lifetime);
|
||||
binding->SignalTimeout.connect(this, &RelayServer::OnTimeout);
|
||||
bindings_[username] = binding;
|
||||
|
||||
if (log_bindings_) {
|
||||
RTC_LOG(LS_INFO) << "Added new binding " << username << ", "
|
||||
<< bindings_.size() << " total";
|
||||
}
|
||||
}
|
||||
|
||||
// Add this connection to the binding. It starts out unlocked.
|
||||
RelayServerConnection* int_conn =
|
||||
new RelayServerConnection(binding, ap, socket);
|
||||
binding->AddInternalConnection(int_conn);
|
||||
AddConnection(int_conn);
|
||||
|
||||
// Now that we have a connection, this other method takes over.
|
||||
HandleStunAllocate(int_conn, request);
|
||||
}
|
||||
|
||||
void RelayServer::HandleStun(RelayServerConnection* int_conn,
|
||||
const char* bytes,
|
||||
size_t size) {
|
||||
// Make sure this is a valid STUN request.
|
||||
RelayMessage request;
|
||||
std::string username;
|
||||
if (!HandleStun(bytes, size, int_conn->addr_pair().source(),
|
||||
int_conn->socket(), &username, &request))
|
||||
return;
|
||||
|
||||
// Make sure the username is the one were were expecting.
|
||||
if (username != int_conn->binding()->username()) {
|
||||
int_conn->SendStunError(request, 430, "Stale Credentials");
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO(?): Check the HMAC.
|
||||
|
||||
// Send this request to the appropriate handler.
|
||||
if (request.type() == STUN_SEND_REQUEST)
|
||||
HandleStunSend(int_conn, request);
|
||||
else if (request.type() == STUN_ALLOCATE_REQUEST)
|
||||
HandleStunAllocate(int_conn, request);
|
||||
else
|
||||
int_conn->SendStunError(request, 600, "Operation Not Supported");
|
||||
}
|
||||
|
||||
void RelayServer::HandleStunAllocate(RelayServerConnection* int_conn,
|
||||
const StunMessage& request) {
|
||||
// Create a response message that includes an address with which external
|
||||
// clients can communicate.
|
||||
|
||||
RelayMessage response;
|
||||
response.SetType(STUN_ALLOCATE_RESPONSE);
|
||||
response.SetTransactionID(request.transaction_id());
|
||||
|
||||
auto magic_cookie_attr =
|
||||
StunAttribute::CreateByteString(cricket::STUN_ATTR_MAGIC_COOKIE);
|
||||
magic_cookie_attr->CopyBytes(int_conn->binding()->magic_cookie().c_str(),
|
||||
int_conn->binding()->magic_cookie().size());
|
||||
response.AddAttribute(std::move(magic_cookie_attr));
|
||||
|
||||
RTC_DCHECK_GT(external_sockets_.size(), 0);
|
||||
size_t index =
|
||||
random_.Rand(rtc::dchecked_cast<uint32_t>(external_sockets_.size() - 1));
|
||||
rtc::SocketAddress ext_addr = external_sockets_[index]->GetLocalAddress();
|
||||
|
||||
auto addr_attr = StunAttribute::CreateAddress(STUN_ATTR_MAPPED_ADDRESS);
|
||||
addr_attr->SetIP(ext_addr.ipaddr());
|
||||
addr_attr->SetPort(ext_addr.port());
|
||||
response.AddAttribute(std::move(addr_attr));
|
||||
|
||||
auto res_lifetime_attr = StunAttribute::CreateUInt32(STUN_ATTR_LIFETIME);
|
||||
res_lifetime_attr->SetValue(int_conn->binding()->lifetime() / 1000);
|
||||
response.AddAttribute(std::move(res_lifetime_attr));
|
||||
|
||||
// TODO(?): Support transport-prefs (preallocate RTCP port).
|
||||
// TODO(?): Support bandwidth restrictions.
|
||||
// TODO(?): Add message integrity check.
|
||||
|
||||
// Send a response to the caller.
|
||||
int_conn->SendStun(response);
|
||||
}
|
||||
|
||||
void RelayServer::HandleStunSend(RelayServerConnection* int_conn,
|
||||
const StunMessage& request) {
|
||||
const StunAddressAttribute* addr_attr =
|
||||
request.GetAddress(STUN_ATTR_DESTINATION_ADDRESS);
|
||||
if (!addr_attr) {
|
||||
int_conn->SendStunError(request, 400, "Bad Request");
|
||||
return;
|
||||
}
|
||||
|
||||
const StunByteStringAttribute* data_attr =
|
||||
request.GetByteString(STUN_ATTR_DATA);
|
||||
if (!data_attr) {
|
||||
int_conn->SendStunError(request, 400, "Bad Request");
|
||||
return;
|
||||
}
|
||||
|
||||
rtc::SocketAddress ext_addr(addr_attr->ipaddr(), addr_attr->port());
|
||||
RelayServerConnection* ext_conn =
|
||||
int_conn->binding()->GetExternalConnection(ext_addr);
|
||||
if (!ext_conn) {
|
||||
// Create a new connection to establish the relationship with this binding.
|
||||
RTC_DCHECK(external_sockets_.size() == 1);
|
||||
rtc::AsyncPacketSocket* socket = external_sockets_[0];
|
||||
rtc::SocketAddressPair ap(ext_addr, socket->GetLocalAddress());
|
||||
ext_conn = new RelayServerConnection(int_conn->binding(), ap, socket);
|
||||
ext_conn->binding()->AddExternalConnection(ext_conn);
|
||||
AddConnection(ext_conn);
|
||||
}
|
||||
|
||||
// If this connection has pinged us, then allow outgoing traffic.
|
||||
if (ext_conn->locked())
|
||||
ext_conn->Send(data_attr->bytes(), data_attr->length());
|
||||
|
||||
const StunUInt32Attribute* options_attr =
|
||||
request.GetUInt32(STUN_ATTR_OPTIONS);
|
||||
if (options_attr && (options_attr->value() & 0x01)) {
|
||||
int_conn->set_default_destination(ext_addr);
|
||||
int_conn->Lock();
|
||||
|
||||
RelayMessage response;
|
||||
response.SetType(STUN_SEND_RESPONSE);
|
||||
response.SetTransactionID(request.transaction_id());
|
||||
|
||||
auto magic_cookie_attr =
|
||||
StunAttribute::CreateByteString(cricket::STUN_ATTR_MAGIC_COOKIE);
|
||||
magic_cookie_attr->CopyBytes(int_conn->binding()->magic_cookie().c_str(),
|
||||
int_conn->binding()->magic_cookie().size());
|
||||
response.AddAttribute(std::move(magic_cookie_attr));
|
||||
|
||||
auto options2_attr =
|
||||
StunAttribute::CreateUInt32(cricket::STUN_ATTR_OPTIONS);
|
||||
options2_attr->SetValue(0x01);
|
||||
response.AddAttribute(std::move(options2_attr));
|
||||
|
||||
int_conn->SendStun(response);
|
||||
}
|
||||
}
|
||||
|
||||
void RelayServer::AddConnection(RelayServerConnection* conn) {
|
||||
RTC_DCHECK(connections_.find(conn->addr_pair()) == connections_.end());
|
||||
connections_[conn->addr_pair()] = conn;
|
||||
}
|
||||
|
||||
void RelayServer::RemoveConnection(RelayServerConnection* conn) {
|
||||
auto iter = connections_.find(conn->addr_pair());
|
||||
RTC_DCHECK(iter != connections_.end());
|
||||
connections_.erase(iter);
|
||||
}
|
||||
|
||||
void RelayServer::RemoveBinding(RelayServerBinding* binding) {
|
||||
auto iter = bindings_.find(binding->username());
|
||||
RTC_DCHECK(iter != bindings_.end());
|
||||
bindings_.erase(iter);
|
||||
|
||||
if (log_bindings_) {
|
||||
RTC_LOG(LS_INFO) << "Removed binding " << binding->username() << ", "
|
||||
<< bindings_.size() << " remaining";
|
||||
}
|
||||
}
|
||||
|
||||
void RelayServer::OnMessage(rtc::Message* pmsg) {
|
||||
static const uint32_t kMessageAcceptConnection = 1;
|
||||
RTC_DCHECK(pmsg->message_id == kMessageAcceptConnection);
|
||||
|
||||
rtc::MessageData* data = pmsg->pdata;
|
||||
rtc::AsyncSocket* socket =
|
||||
static_cast<rtc::TypedMessageData<rtc::AsyncSocket*>*>(data)->data();
|
||||
AcceptConnection(socket);
|
||||
delete data;
|
||||
}
|
||||
|
||||
void RelayServer::OnTimeout(RelayServerBinding* binding) {
|
||||
// This call will result in all of the necessary clean-up. We can't call
|
||||
// delete here, because you can't delete an object that is signaling you.
|
||||
thread_->Dispose(binding);
|
||||
}
|
||||
|
||||
void RelayServer::AcceptConnection(rtc::AsyncSocket* server_socket) {
|
||||
// Check if someone is trying to connect to us.
|
||||
rtc::SocketAddress accept_addr;
|
||||
rtc::AsyncSocket* accepted_socket = server_socket->Accept(&accept_addr);
|
||||
if (accepted_socket != NULL) {
|
||||
// We had someone trying to connect, now check which protocol to
|
||||
// use and create a packet socket.
|
||||
RTC_DCHECK(server_sockets_[server_socket] == cricket::PROTO_TCP ||
|
||||
server_sockets_[server_socket] == cricket::PROTO_SSLTCP);
|
||||
if (server_sockets_[server_socket] == cricket::PROTO_SSLTCP) {
|
||||
accepted_socket = new rtc::AsyncSSLServerSocket(accepted_socket);
|
||||
}
|
||||
rtc::AsyncTCPSocket* tcp_socket =
|
||||
new rtc::AsyncTCPSocket(accepted_socket, false);
|
||||
|
||||
// Finally add the socket so it can start communicating with the client.
|
||||
AddInternalSocket(tcp_socket);
|
||||
}
|
||||
}
|
||||
|
||||
RelayServerConnection::RelayServerConnection(
|
||||
RelayServerBinding* binding,
|
||||
const rtc::SocketAddressPair& addrs,
|
||||
rtc::AsyncPacketSocket* socket)
|
||||
: binding_(binding), addr_pair_(addrs), socket_(socket), locked_(false) {
|
||||
// The creation of a new connection constitutes a use of the binding.
|
||||
binding_->NoteUsed();
|
||||
}
|
||||
|
||||
RelayServerConnection::~RelayServerConnection() {
|
||||
// Remove this connection from the server's map (if it exists there).
|
||||
binding_->server()->RemoveConnection(this);
|
||||
}
|
||||
|
||||
void RelayServerConnection::Send(const char* data, size_t size) {
|
||||
// Note that the binding has been used again.
|
||||
binding_->NoteUsed();
|
||||
|
||||
cricket::Send(socket_, data, size, addr_pair_.source());
|
||||
}
|
||||
|
||||
void RelayServerConnection::Send(const char* data,
|
||||
size_t size,
|
||||
const rtc::SocketAddress& from_addr) {
|
||||
// If the from address is known to the client, we don't need to send it.
|
||||
if (locked() && (from_addr == default_dest_)) {
|
||||
Send(data, size);
|
||||
return;
|
||||
}
|
||||
|
||||
// Wrap the given data in a data-indication packet.
|
||||
|
||||
RelayMessage msg;
|
||||
msg.SetType(STUN_DATA_INDICATION);
|
||||
|
||||
auto magic_cookie_attr =
|
||||
StunAttribute::CreateByteString(cricket::STUN_ATTR_MAGIC_COOKIE);
|
||||
magic_cookie_attr->CopyBytes(binding_->magic_cookie().c_str(),
|
||||
binding_->magic_cookie().size());
|
||||
msg.AddAttribute(std::move(magic_cookie_attr));
|
||||
|
||||
auto addr_attr = StunAttribute::CreateAddress(STUN_ATTR_SOURCE_ADDRESS2);
|
||||
addr_attr->SetIP(from_addr.ipaddr());
|
||||
addr_attr->SetPort(from_addr.port());
|
||||
msg.AddAttribute(std::move(addr_attr));
|
||||
|
||||
auto data_attr = StunAttribute::CreateByteString(STUN_ATTR_DATA);
|
||||
RTC_DCHECK(size <= 65536);
|
||||
data_attr->CopyBytes(data, uint16_t(size));
|
||||
msg.AddAttribute(std::move(data_attr));
|
||||
|
||||
SendStun(msg);
|
||||
}
|
||||
|
||||
void RelayServerConnection::SendStun(const StunMessage& msg) {
|
||||
// Note that the binding has been used again.
|
||||
binding_->NoteUsed();
|
||||
|
||||
cricket::SendStun(msg, socket_, addr_pair_.source());
|
||||
}
|
||||
|
||||
void RelayServerConnection::SendStunError(const StunMessage& request,
|
||||
int error_code,
|
||||
const char* error_desc) {
|
||||
// An error does not indicate use. If no legitimate use off the binding
|
||||
// occurs, we want it to be cleaned up even if errors are still occuring.
|
||||
|
||||
cricket::SendStunError(request, socket_, addr_pair_.source(), error_code,
|
||||
error_desc, binding_->magic_cookie());
|
||||
}
|
||||
|
||||
void RelayServerConnection::Lock() {
|
||||
locked_ = true;
|
||||
}
|
||||
|
||||
void RelayServerConnection::Unlock() {
|
||||
locked_ = false;
|
||||
}
|
||||
|
||||
// IDs used for posted messages:
|
||||
const uint32_t MSG_LIFETIME_TIMER = 1;
|
||||
|
||||
RelayServerBinding::RelayServerBinding(RelayServer* server,
|
||||
const std::string& username,
|
||||
const std::string& password,
|
||||
int lifetime)
|
||||
: server_(server),
|
||||
username_(username),
|
||||
password_(password),
|
||||
lifetime_(lifetime) {
|
||||
// For now, every connection uses the standard magic cookie value.
|
||||
magic_cookie_.append(reinterpret_cast<const char*>(TURN_MAGIC_COOKIE_VALUE),
|
||||
sizeof(TURN_MAGIC_COOKIE_VALUE));
|
||||
|
||||
// Initialize the last-used time to now.
|
||||
NoteUsed();
|
||||
|
||||
// Set the first timeout check.
|
||||
server_->thread()->PostDelayed(RTC_FROM_HERE, lifetime_, this,
|
||||
MSG_LIFETIME_TIMER);
|
||||
}
|
||||
|
||||
RelayServerBinding::~RelayServerBinding() {
|
||||
// Clear the outstanding timeout check.
|
||||
server_->thread()->Clear(this);
|
||||
|
||||
// Clean up all of the connections.
|
||||
for (size_t i = 0; i < internal_connections_.size(); ++i)
|
||||
delete internal_connections_[i];
|
||||
for (size_t i = 0; i < external_connections_.size(); ++i)
|
||||
delete external_connections_[i];
|
||||
|
||||
// Remove this binding from the server's map.
|
||||
server_->RemoveBinding(this);
|
||||
}
|
||||
|
||||
void RelayServerBinding::AddInternalConnection(RelayServerConnection* conn) {
|
||||
internal_connections_.push_back(conn);
|
||||
}
|
||||
|
||||
void RelayServerBinding::AddExternalConnection(RelayServerConnection* conn) {
|
||||
external_connections_.push_back(conn);
|
||||
}
|
||||
|
||||
void RelayServerBinding::NoteUsed() {
|
||||
last_used_ = rtc::TimeMillis();
|
||||
}
|
||||
|
||||
bool RelayServerBinding::HasMagicCookie(const char* bytes, size_t size) const {
|
||||
if (size < 24 + magic_cookie_.size()) {
|
||||
return false;
|
||||
} else {
|
||||
return memcmp(bytes + 24, magic_cookie_.c_str(), magic_cookie_.size()) == 0;
|
||||
}
|
||||
}
|
||||
|
||||
RelayServerConnection* RelayServerBinding::GetInternalConnection(
|
||||
const rtc::SocketAddress& ext_addr) {
|
||||
// Look for an internal connection that is locked to this address.
|
||||
for (size_t i = 0; i < internal_connections_.size(); ++i) {
|
||||
if (internal_connections_[i]->locked() &&
|
||||
(ext_addr == internal_connections_[i]->default_destination()))
|
||||
return internal_connections_[i];
|
||||
}
|
||||
|
||||
// If one was not found, we send to the first connection.
|
||||
RTC_DCHECK(internal_connections_.size() > 0);
|
||||
return internal_connections_[0];
|
||||
}
|
||||
|
||||
RelayServerConnection* RelayServerBinding::GetExternalConnection(
|
||||
const rtc::SocketAddress& ext_addr) {
|
||||
for (size_t i = 0; i < external_connections_.size(); ++i) {
|
||||
if (ext_addr == external_connections_[i]->addr_pair().source())
|
||||
return external_connections_[i];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void RelayServerBinding::OnMessage(rtc::Message* pmsg) {
|
||||
if (pmsg->message_id == MSG_LIFETIME_TIMER) {
|
||||
RTC_DCHECK(!pmsg->pdata);
|
||||
|
||||
// If the lifetime timeout has been exceeded, then send a signal.
|
||||
// Otherwise, just keep waiting.
|
||||
if (rtc::TimeMillis() >= last_used_ + lifetime_) {
|
||||
RTC_LOG(LS_INFO) << "Expiring binding " << username_;
|
||||
SignalTimeout(this);
|
||||
} else {
|
||||
server_->thread()->PostDelayed(RTC_FROM_HERE, lifetime_, this,
|
||||
MSG_LIFETIME_TIMER);
|
||||
}
|
||||
|
||||
} else {
|
||||
RTC_NOTREACHED();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace cricket
|
||||
@ -1,235 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef P2P_BASE_RELAY_SERVER_H_
|
||||
#define P2P_BASE_RELAY_SERVER_H_
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "p2p/base/port.h"
|
||||
#include "p2p/base/stun.h"
|
||||
#include "rtc_base/async_udp_socket.h"
|
||||
#include "rtc_base/random.h"
|
||||
#include "rtc_base/socket_address_pair.h"
|
||||
#include "rtc_base/thread.h"
|
||||
#include "rtc_base/time_utils.h"
|
||||
|
||||
namespace cricket {
|
||||
|
||||
class RelayServerBinding;
|
||||
class RelayServerConnection;
|
||||
|
||||
// Relays traffic between connections to the server that are "bound" together.
|
||||
// All connections created with the same username/password are bound together.
|
||||
class RelayServer : public rtc::MessageHandler, public sigslot::has_slots<> {
|
||||
public:
|
||||
// Creates a server, which will use this thread to post messages to itself.
|
||||
explicit RelayServer(rtc::Thread* thread);
|
||||
~RelayServer() override;
|
||||
|
||||
rtc::Thread* thread() { return thread_; }
|
||||
|
||||
// Indicates whether we will print updates of the number of bindings.
|
||||
bool log_bindings() const { return log_bindings_; }
|
||||
void set_log_bindings(bool log_bindings) { log_bindings_ = log_bindings; }
|
||||
|
||||
// Updates the set of sockets that the server uses to talk to "internal"
|
||||
// clients. These are clients that do the "port allocations".
|
||||
void AddInternalSocket(rtc::AsyncPacketSocket* socket);
|
||||
void RemoveInternalSocket(rtc::AsyncPacketSocket* socket);
|
||||
|
||||
// Updates the set of sockets that the server uses to talk to "external"
|
||||
// clients. These are the clients that do not do allocations. They do not
|
||||
// know that these addresses represent a relay server.
|
||||
void AddExternalSocket(rtc::AsyncPacketSocket* socket);
|
||||
void RemoveExternalSocket(rtc::AsyncPacketSocket* socket);
|
||||
|
||||
// Starts listening for connections on this sockets. When someone
|
||||
// tries to connect, the connection will be accepted and a new
|
||||
// internal socket will be added.
|
||||
void AddInternalServerSocket(rtc::AsyncSocket* socket,
|
||||
cricket::ProtocolType proto);
|
||||
|
||||
// Removes this server socket from the list.
|
||||
void RemoveInternalServerSocket(rtc::AsyncSocket* socket);
|
||||
|
||||
// Methods for testing and debuging.
|
||||
int GetConnectionCount() const;
|
||||
rtc::SocketAddressPair GetConnection(int connection) const;
|
||||
bool HasConnection(const rtc::SocketAddress& address) const;
|
||||
|
||||
private:
|
||||
rtc::Thread* thread_;
|
||||
webrtc::Random random_;
|
||||
bool log_bindings_;
|
||||
std::vector<rtc::AsyncPacketSocket*> internal_sockets_;
|
||||
std::vector<rtc::AsyncPacketSocket*> external_sockets_;
|
||||
std::vector<rtc::AsyncPacketSocket*> removed_sockets_;
|
||||
std::map<rtc::AsyncSocket*, cricket::ProtocolType> server_sockets_;
|
||||
std::map<std::string, RelayServerBinding*> bindings_;
|
||||
std::map<rtc::SocketAddressPair, RelayServerConnection*> connections_;
|
||||
|
||||
// Called when a packet is received by the server on one of its sockets.
|
||||
void OnInternalPacket(rtc::AsyncPacketSocket* socket,
|
||||
const char* bytes,
|
||||
size_t size,
|
||||
const rtc::SocketAddress& remote_addr,
|
||||
const int64_t& packet_time_us);
|
||||
void OnExternalPacket(rtc::AsyncPacketSocket* socket,
|
||||
const char* bytes,
|
||||
size_t size,
|
||||
const rtc::SocketAddress& remote_addr,
|
||||
const int64_t& packet_time_us);
|
||||
|
||||
void OnReadEvent(rtc::AsyncSocket* socket);
|
||||
|
||||
// Processes the relevant STUN request types from the client.
|
||||
bool HandleStun(const char* bytes,
|
||||
size_t size,
|
||||
const rtc::SocketAddress& remote_addr,
|
||||
rtc::AsyncPacketSocket* socket,
|
||||
std::string* username,
|
||||
StunMessage* msg);
|
||||
void HandleStunAllocate(const char* bytes,
|
||||
size_t size,
|
||||
const rtc::SocketAddressPair& ap,
|
||||
rtc::AsyncPacketSocket* socket);
|
||||
void HandleStun(RelayServerConnection* int_conn,
|
||||
const char* bytes,
|
||||
size_t size);
|
||||
void HandleStunAllocate(RelayServerConnection* int_conn,
|
||||
const StunMessage& msg);
|
||||
void HandleStunSend(RelayServerConnection* int_conn, const StunMessage& msg);
|
||||
|
||||
// Adds/Removes the a connection or binding.
|
||||
void AddConnection(RelayServerConnection* conn);
|
||||
void RemoveConnection(RelayServerConnection* conn);
|
||||
void RemoveBinding(RelayServerBinding* binding);
|
||||
|
||||
// Handle messages in our thread.
|
||||
void OnMessage(rtc::Message* pmsg) override;
|
||||
|
||||
// Called when the timer for checking lifetime times out.
|
||||
void OnTimeout(RelayServerBinding* binding);
|
||||
|
||||
// Accept connections on this server socket.
|
||||
void AcceptConnection(rtc::AsyncSocket* server_socket);
|
||||
|
||||
friend class RelayServerConnection;
|
||||
friend class RelayServerBinding;
|
||||
};
|
||||
|
||||
// Maintains information about a connection to the server. Each connection is
|
||||
// part of one and only one binding.
|
||||
class RelayServerConnection {
|
||||
public:
|
||||
RelayServerConnection(RelayServerBinding* binding,
|
||||
const rtc::SocketAddressPair& addrs,
|
||||
rtc::AsyncPacketSocket* socket);
|
||||
~RelayServerConnection();
|
||||
|
||||
RelayServerBinding* binding() { return binding_; }
|
||||
rtc::AsyncPacketSocket* socket() { return socket_; }
|
||||
|
||||
// Returns a pair where the source is the remote address and the destination
|
||||
// is the local address.
|
||||
const rtc::SocketAddressPair& addr_pair() { return addr_pair_; }
|
||||
|
||||
// Sends a packet to the connected client. If an address is provided, then
|
||||
// we make sure the internal client receives it, wrapping if necessary.
|
||||
void Send(const char* data, size_t size);
|
||||
void Send(const char* data, size_t size, const rtc::SocketAddress& ext_addr);
|
||||
|
||||
// Sends a STUN message to the connected client with no wrapping.
|
||||
void SendStun(const StunMessage& msg);
|
||||
void SendStunError(const StunMessage& request, int code, const char* desc);
|
||||
|
||||
// A locked connection is one for which we know the intended destination of
|
||||
// any raw packet received.
|
||||
bool locked() const { return locked_; }
|
||||
void Lock();
|
||||
void Unlock();
|
||||
|
||||
// Records the address that raw packets should be forwarded to (for internal
|
||||
// packets only; for external, we already know where they go).
|
||||
const rtc::SocketAddress& default_destination() const {
|
||||
return default_dest_;
|
||||
}
|
||||
void set_default_destination(const rtc::SocketAddress& addr) {
|
||||
default_dest_ = addr;
|
||||
}
|
||||
|
||||
private:
|
||||
RelayServerBinding* binding_;
|
||||
rtc::SocketAddressPair addr_pair_;
|
||||
rtc::AsyncPacketSocket* socket_;
|
||||
bool locked_;
|
||||
rtc::SocketAddress default_dest_;
|
||||
};
|
||||
|
||||
// Records a set of internal and external connections that we relay between,
|
||||
// or in other words, that are "bound" together.
|
||||
class RelayServerBinding : public rtc::MessageHandler {
|
||||
public:
|
||||
RelayServerBinding(RelayServer* server,
|
||||
const std::string& username,
|
||||
const std::string& password,
|
||||
int lifetime);
|
||||
~RelayServerBinding() override;
|
||||
|
||||
RelayServer* server() { return server_; }
|
||||
int lifetime() { return lifetime_; }
|
||||
const std::string& username() { return username_; }
|
||||
const std::string& password() { return password_; }
|
||||
const std::string& magic_cookie() { return magic_cookie_; }
|
||||
|
||||
// Adds/Removes a connection into the binding.
|
||||
void AddInternalConnection(RelayServerConnection* conn);
|
||||
void AddExternalConnection(RelayServerConnection* conn);
|
||||
|
||||
// We keep track of the use of each binding. If we detect that it was not
|
||||
// used for longer than the lifetime, then we send a signal.
|
||||
void NoteUsed();
|
||||
sigslot::signal1<RelayServerBinding*> SignalTimeout;
|
||||
|
||||
// Determines whether the given packet has the magic cookie present (in the
|
||||
// right place).
|
||||
bool HasMagicCookie(const char* bytes, size_t size) const;
|
||||
|
||||
// Determines the connection to use to send packets to or from the given
|
||||
// external address.
|
||||
RelayServerConnection* GetInternalConnection(
|
||||
const rtc::SocketAddress& ext_addr);
|
||||
RelayServerConnection* GetExternalConnection(
|
||||
const rtc::SocketAddress& ext_addr);
|
||||
|
||||
// MessageHandler:
|
||||
void OnMessage(rtc::Message* pmsg) override;
|
||||
|
||||
private:
|
||||
RelayServer* server_;
|
||||
|
||||
std::string username_;
|
||||
std::string password_;
|
||||
std::string magic_cookie_;
|
||||
|
||||
std::vector<RelayServerConnection*> internal_connections_;
|
||||
std::vector<RelayServerConnection*> external_connections_;
|
||||
|
||||
int lifetime_;
|
||||
int64_t last_used_;
|
||||
// TODO(?): bandwidth
|
||||
};
|
||||
|
||||
} // namespace cricket
|
||||
|
||||
#endif // P2P_BASE_RELAY_SERVER_H_
|
||||
@ -1,511 +0,0 @@
|
||||
/*
|
||||
* 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/relay_server.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "absl/memory/memory.h"
|
||||
#include "rtc_base/async_udp_socket.h"
|
||||
#include "rtc_base/byte_buffer.h"
|
||||
#include "rtc_base/helpers.h"
|
||||
#include "rtc_base/socket_address.h"
|
||||
#include "rtc_base/test_client.h"
|
||||
#include "rtc_base/thread.h"
|
||||
#include "rtc_base/virtual_socket_server.h"
|
||||
#include "test/gtest.h"
|
||||
|
||||
using rtc::SocketAddress;
|
||||
|
||||
namespace cricket {
|
||||
namespace {
|
||||
|
||||
constexpr uint32_t LIFETIME = 4; // seconds
|
||||
const SocketAddress server_int_addr("127.0.0.1", 5000);
|
||||
const SocketAddress server_ext_addr("127.0.0.1", 5001);
|
||||
const SocketAddress client1_addr("127.0.0.1", 6111);
|
||||
const SocketAddress client2_addr("127.0.0.1", 7222);
|
||||
const char* bad =
|
||||
"this is a completely nonsensical message whose only "
|
||||
"purpose is to make the parser go 'ack'. it doesn't "
|
||||
"look anything like a normal stun message";
|
||||
const char* msg1 = "spamspamspamspamspamspamspambakedbeansspam";
|
||||
const char* msg2 = "Lobster Thermidor a Crevette with a mornay sauce...";
|
||||
|
||||
} // namespace
|
||||
|
||||
class RelayServerTest : public ::testing::Test {
|
||||
public:
|
||||
RelayServerTest()
|
||||
: ss_(new rtc::VirtualSocketServer()),
|
||||
thread_(ss_.get()),
|
||||
username_(rtc::CreateRandomString(12)),
|
||||
password_(rtc::CreateRandomString(12)) {}
|
||||
|
||||
protected:
|
||||
virtual void SetUp() {
|
||||
server_.reset(new RelayServer(rtc::Thread::Current()));
|
||||
|
||||
server_->AddInternalSocket(
|
||||
rtc::AsyncUDPSocket::Create(ss_.get(), server_int_addr));
|
||||
server_->AddExternalSocket(
|
||||
rtc::AsyncUDPSocket::Create(ss_.get(), server_ext_addr));
|
||||
|
||||
client1_.reset(new rtc::TestClient(absl::WrapUnique(
|
||||
rtc::AsyncUDPSocket::Create(ss_.get(), client1_addr))));
|
||||
client2_.reset(new rtc::TestClient(absl::WrapUnique(
|
||||
rtc::AsyncUDPSocket::Create(ss_.get(), client2_addr))));
|
||||
}
|
||||
|
||||
void Allocate() {
|
||||
std::unique_ptr<StunMessage> req(CreateStunMessage(STUN_ALLOCATE_REQUEST));
|
||||
AddUsernameAttr(req.get(), username_);
|
||||
AddLifetimeAttr(req.get(), LIFETIME);
|
||||
Send1(req.get());
|
||||
delete Receive1();
|
||||
}
|
||||
void Bind() {
|
||||
std::unique_ptr<StunMessage> req(CreateStunMessage(STUN_BINDING_REQUEST));
|
||||
AddUsernameAttr(req.get(), username_);
|
||||
Send2(req.get());
|
||||
delete Receive1();
|
||||
}
|
||||
|
||||
void Send1(const StunMessage* msg) {
|
||||
rtc::ByteBufferWriter buf;
|
||||
msg->Write(&buf);
|
||||
SendRaw1(buf.Data(), static_cast<int>(buf.Length()));
|
||||
}
|
||||
void Send2(const StunMessage* msg) {
|
||||
rtc::ByteBufferWriter buf;
|
||||
msg->Write(&buf);
|
||||
SendRaw2(buf.Data(), static_cast<int>(buf.Length()));
|
||||
}
|
||||
void SendRaw1(const char* data, int len) {
|
||||
return Send(client1_.get(), data, len, server_int_addr);
|
||||
}
|
||||
void SendRaw2(const char* data, int len) {
|
||||
return Send(client2_.get(), data, len, server_ext_addr);
|
||||
}
|
||||
void Send(rtc::TestClient* client,
|
||||
const char* data,
|
||||
int len,
|
||||
const SocketAddress& addr) {
|
||||
client->SendTo(data, len, addr);
|
||||
}
|
||||
|
||||
bool Receive1Fails() { return client1_.get()->CheckNoPacket(); }
|
||||
bool Receive2Fails() { return client2_.get()->CheckNoPacket(); }
|
||||
|
||||
StunMessage* Receive1() { return Receive(client1_.get()); }
|
||||
StunMessage* Receive2() { return Receive(client2_.get()); }
|
||||
std::string ReceiveRaw1() { return ReceiveRaw(client1_.get()); }
|
||||
std::string ReceiveRaw2() { return ReceiveRaw(client2_.get()); }
|
||||
StunMessage* Receive(rtc::TestClient* client) {
|
||||
StunMessage* msg = NULL;
|
||||
std::unique_ptr<rtc::TestClient::Packet> packet =
|
||||
client->NextPacket(rtc::TestClient::kTimeoutMs);
|
||||
if (packet) {
|
||||
rtc::ByteBufferWriter buf(packet->buf, packet->size);
|
||||
rtc::ByteBufferReader read_buf(buf);
|
||||
msg = new RelayMessage();
|
||||
msg->Read(&read_buf);
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
std::string ReceiveRaw(rtc::TestClient* client) {
|
||||
std::string raw;
|
||||
std::unique_ptr<rtc::TestClient::Packet> packet =
|
||||
client->NextPacket(rtc::TestClient::kTimeoutMs);
|
||||
if (packet) {
|
||||
raw = std::string(packet->buf, packet->size);
|
||||
}
|
||||
return raw;
|
||||
}
|
||||
|
||||
static StunMessage* CreateStunMessage(int type) {
|
||||
StunMessage* msg = new RelayMessage();
|
||||
msg->SetType(type);
|
||||
msg->SetTransactionID(rtc::CreateRandomString(kStunTransactionIdLength));
|
||||
return msg;
|
||||
}
|
||||
static void AddMagicCookieAttr(StunMessage* msg) {
|
||||
auto attr = StunAttribute::CreateByteString(STUN_ATTR_MAGIC_COOKIE);
|
||||
attr->CopyBytes(TURN_MAGIC_COOKIE_VALUE, sizeof(TURN_MAGIC_COOKIE_VALUE));
|
||||
msg->AddAttribute(std::move(attr));
|
||||
}
|
||||
static void AddUsernameAttr(StunMessage* msg, const std::string& val) {
|
||||
auto attr = StunAttribute::CreateByteString(STUN_ATTR_USERNAME);
|
||||
attr->CopyBytes(val.c_str(), val.size());
|
||||
msg->AddAttribute(std::move(attr));
|
||||
}
|
||||
static void AddLifetimeAttr(StunMessage* msg, int val) {
|
||||
auto attr = StunAttribute::CreateUInt32(STUN_ATTR_LIFETIME);
|
||||
attr->SetValue(val);
|
||||
msg->AddAttribute(std::move(attr));
|
||||
}
|
||||
static void AddDestinationAttr(StunMessage* msg, const SocketAddress& addr) {
|
||||
auto attr = StunAttribute::CreateAddress(STUN_ATTR_DESTINATION_ADDRESS);
|
||||
attr->SetIP(addr.ipaddr());
|
||||
attr->SetPort(addr.port());
|
||||
msg->AddAttribute(std::move(attr));
|
||||
}
|
||||
|
||||
std::unique_ptr<rtc::VirtualSocketServer> ss_;
|
||||
rtc::AutoSocketServerThread thread_;
|
||||
std::unique_ptr<RelayServer> server_;
|
||||
std::unique_ptr<rtc::TestClient> client1_;
|
||||
std::unique_ptr<rtc::TestClient> client2_;
|
||||
std::string username_;
|
||||
std::string password_;
|
||||
};
|
||||
|
||||
// Send a complete nonsense message and verify that it is eaten.
|
||||
TEST_F(RelayServerTest, TestBadRequest) {
|
||||
SendRaw1(bad, static_cast<int>(strlen(bad)));
|
||||
ASSERT_TRUE(Receive1Fails());
|
||||
}
|
||||
|
||||
// Send an allocate request without a username and verify it is rejected.
|
||||
TEST_F(RelayServerTest, TestAllocateNoUsername) {
|
||||
std::unique_ptr<StunMessage> req(CreateStunMessage(STUN_ALLOCATE_REQUEST)),
|
||||
res;
|
||||
|
||||
Send1(req.get());
|
||||
res.reset(Receive1());
|
||||
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(STUN_ALLOCATE_ERROR_RESPONSE, res->type());
|
||||
EXPECT_EQ(req->transaction_id(), res->transaction_id());
|
||||
|
||||
const StunErrorCodeAttribute* err = res->GetErrorCode();
|
||||
ASSERT_TRUE(err != NULL);
|
||||
EXPECT_EQ(4, err->eclass());
|
||||
EXPECT_EQ(32, err->number());
|
||||
EXPECT_EQ("Missing Username", err->reason());
|
||||
}
|
||||
|
||||
// Send a binding request and verify that it is rejected.
|
||||
TEST_F(RelayServerTest, TestBindingRequest) {
|
||||
std::unique_ptr<StunMessage> req(CreateStunMessage(STUN_BINDING_REQUEST)),
|
||||
res;
|
||||
AddUsernameAttr(req.get(), username_);
|
||||
|
||||
Send1(req.get());
|
||||
res.reset(Receive1());
|
||||
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(STUN_BINDING_ERROR_RESPONSE, res->type());
|
||||
EXPECT_EQ(req->transaction_id(), res->transaction_id());
|
||||
|
||||
const StunErrorCodeAttribute* err = res->GetErrorCode();
|
||||
ASSERT_TRUE(err != NULL);
|
||||
EXPECT_EQ(6, err->eclass());
|
||||
EXPECT_EQ(0, err->number());
|
||||
EXPECT_EQ("Operation Not Supported", err->reason());
|
||||
}
|
||||
|
||||
// Send an allocate request and verify that it is accepted.
|
||||
TEST_F(RelayServerTest, TestAllocate) {
|
||||
std::unique_ptr<StunMessage> req(CreateStunMessage(STUN_ALLOCATE_REQUEST)),
|
||||
res;
|
||||
AddUsernameAttr(req.get(), username_);
|
||||
AddLifetimeAttr(req.get(), LIFETIME);
|
||||
|
||||
Send1(req.get());
|
||||
res.reset(Receive1());
|
||||
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(STUN_ALLOCATE_RESPONSE, res->type());
|
||||
EXPECT_EQ(req->transaction_id(), res->transaction_id());
|
||||
|
||||
const StunAddressAttribute* mapped_addr =
|
||||
res->GetAddress(STUN_ATTR_MAPPED_ADDRESS);
|
||||
ASSERT_TRUE(mapped_addr != NULL);
|
||||
EXPECT_EQ(1, mapped_addr->family());
|
||||
EXPECT_EQ(server_ext_addr.port(), mapped_addr->port());
|
||||
EXPECT_EQ(server_ext_addr.ipaddr(), mapped_addr->ipaddr());
|
||||
|
||||
const StunUInt32Attribute* res_lifetime_attr =
|
||||
res->GetUInt32(STUN_ATTR_LIFETIME);
|
||||
ASSERT_TRUE(res_lifetime_attr != NULL);
|
||||
EXPECT_EQ(LIFETIME, res_lifetime_attr->value());
|
||||
}
|
||||
|
||||
// Send a second allocate request and verify that it is also accepted, though
|
||||
// the lifetime should be ignored.
|
||||
TEST_F(RelayServerTest, TestReallocate) {
|
||||
Allocate();
|
||||
|
||||
std::unique_ptr<StunMessage> req(CreateStunMessage(STUN_ALLOCATE_REQUEST)),
|
||||
res;
|
||||
AddMagicCookieAttr(req.get());
|
||||
AddUsernameAttr(req.get(), username_);
|
||||
|
||||
Send1(req.get());
|
||||
res.reset(Receive1());
|
||||
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(STUN_ALLOCATE_RESPONSE, res->type());
|
||||
EXPECT_EQ(req->transaction_id(), res->transaction_id());
|
||||
|
||||
const StunAddressAttribute* mapped_addr =
|
||||
res->GetAddress(STUN_ATTR_MAPPED_ADDRESS);
|
||||
ASSERT_TRUE(mapped_addr != NULL);
|
||||
EXPECT_EQ(1, mapped_addr->family());
|
||||
EXPECT_EQ(server_ext_addr.port(), mapped_addr->port());
|
||||
EXPECT_EQ(server_ext_addr.ipaddr(), mapped_addr->ipaddr());
|
||||
|
||||
const StunUInt32Attribute* lifetime_attr = res->GetUInt32(STUN_ATTR_LIFETIME);
|
||||
ASSERT_TRUE(lifetime_attr != NULL);
|
||||
EXPECT_EQ(LIFETIME, lifetime_attr->value());
|
||||
}
|
||||
|
||||
// Send a request from another client and see that it arrives at the first
|
||||
// client in the binding.
|
||||
TEST_F(RelayServerTest, TestRemoteBind) {
|
||||
Allocate();
|
||||
|
||||
std::unique_ptr<StunMessage> req(CreateStunMessage(STUN_BINDING_REQUEST)),
|
||||
res;
|
||||
AddUsernameAttr(req.get(), username_);
|
||||
|
||||
Send2(req.get());
|
||||
res.reset(Receive1());
|
||||
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(STUN_DATA_INDICATION, res->type());
|
||||
|
||||
const StunByteStringAttribute* recv_data = res->GetByteString(STUN_ATTR_DATA);
|
||||
ASSERT_TRUE(recv_data != NULL);
|
||||
|
||||
rtc::ByteBufferReader buf(recv_data->bytes(), recv_data->length());
|
||||
std::unique_ptr<StunMessage> res2(new StunMessage());
|
||||
EXPECT_TRUE(res2->Read(&buf));
|
||||
EXPECT_EQ(STUN_BINDING_REQUEST, res2->type());
|
||||
EXPECT_EQ(req->transaction_id(), res2->transaction_id());
|
||||
|
||||
const StunAddressAttribute* src_addr =
|
||||
res->GetAddress(STUN_ATTR_SOURCE_ADDRESS2);
|
||||
ASSERT_TRUE(src_addr != NULL);
|
||||
EXPECT_EQ(1, src_addr->family());
|
||||
EXPECT_EQ(client2_addr.ipaddr(), src_addr->ipaddr());
|
||||
EXPECT_EQ(client2_addr.port(), src_addr->port());
|
||||
|
||||
EXPECT_TRUE(Receive2Fails());
|
||||
}
|
||||
|
||||
// Send a complete nonsense message to the established connection and verify
|
||||
// that it is dropped by the server.
|
||||
TEST_F(RelayServerTest, TestRemoteBadRequest) {
|
||||
Allocate();
|
||||
Bind();
|
||||
|
||||
SendRaw1(bad, static_cast<int>(strlen(bad)));
|
||||
EXPECT_TRUE(Receive1Fails());
|
||||
EXPECT_TRUE(Receive2Fails());
|
||||
}
|
||||
|
||||
// Send a send request without a username and verify it is rejected.
|
||||
TEST_F(RelayServerTest, TestSendRequestMissingUsername) {
|
||||
Allocate();
|
||||
Bind();
|
||||
|
||||
std::unique_ptr<StunMessage> req(CreateStunMessage(STUN_SEND_REQUEST)), res;
|
||||
AddMagicCookieAttr(req.get());
|
||||
|
||||
Send1(req.get());
|
||||
res.reset(Receive1());
|
||||
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(STUN_SEND_ERROR_RESPONSE, res->type());
|
||||
EXPECT_EQ(req->transaction_id(), res->transaction_id());
|
||||
|
||||
const StunErrorCodeAttribute* err = res->GetErrorCode();
|
||||
ASSERT_TRUE(err != NULL);
|
||||
EXPECT_EQ(4, err->eclass());
|
||||
EXPECT_EQ(32, err->number());
|
||||
EXPECT_EQ("Missing Username", err->reason());
|
||||
}
|
||||
|
||||
// Send a send request with the wrong username and verify it is rejected.
|
||||
TEST_F(RelayServerTest, TestSendRequestBadUsername) {
|
||||
Allocate();
|
||||
Bind();
|
||||
|
||||
std::unique_ptr<StunMessage> req(CreateStunMessage(STUN_SEND_REQUEST)), res;
|
||||
AddMagicCookieAttr(req.get());
|
||||
AddUsernameAttr(req.get(), "foobarbizbaz");
|
||||
|
||||
Send1(req.get());
|
||||
res.reset(Receive1());
|
||||
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(STUN_SEND_ERROR_RESPONSE, res->type());
|
||||
EXPECT_EQ(req->transaction_id(), res->transaction_id());
|
||||
|
||||
const StunErrorCodeAttribute* err = res->GetErrorCode();
|
||||
ASSERT_TRUE(err != NULL);
|
||||
EXPECT_EQ(4, err->eclass());
|
||||
EXPECT_EQ(30, err->number());
|
||||
EXPECT_EQ("Stale Credentials", err->reason());
|
||||
}
|
||||
|
||||
// Send a send request without a destination address and verify that it is
|
||||
// rejected.
|
||||
TEST_F(RelayServerTest, TestSendRequestNoDestinationAddress) {
|
||||
Allocate();
|
||||
Bind();
|
||||
|
||||
std::unique_ptr<StunMessage> req(CreateStunMessage(STUN_SEND_REQUEST)), res;
|
||||
AddMagicCookieAttr(req.get());
|
||||
AddUsernameAttr(req.get(), username_);
|
||||
|
||||
Send1(req.get());
|
||||
res.reset(Receive1());
|
||||
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(STUN_SEND_ERROR_RESPONSE, res->type());
|
||||
EXPECT_EQ(req->transaction_id(), res->transaction_id());
|
||||
|
||||
const StunErrorCodeAttribute* err = res->GetErrorCode();
|
||||
ASSERT_TRUE(err != NULL);
|
||||
EXPECT_EQ(4, err->eclass());
|
||||
EXPECT_EQ(0, err->number());
|
||||
EXPECT_EQ("Bad Request", err->reason());
|
||||
}
|
||||
|
||||
// Send a send request without data and verify that it is rejected.
|
||||
TEST_F(RelayServerTest, TestSendRequestNoData) {
|
||||
Allocate();
|
||||
Bind();
|
||||
|
||||
std::unique_ptr<StunMessage> req(CreateStunMessage(STUN_SEND_REQUEST)), res;
|
||||
AddMagicCookieAttr(req.get());
|
||||
AddUsernameAttr(req.get(), username_);
|
||||
AddDestinationAttr(req.get(), client2_addr);
|
||||
|
||||
Send1(req.get());
|
||||
res.reset(Receive1());
|
||||
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(STUN_SEND_ERROR_RESPONSE, res->type());
|
||||
EXPECT_EQ(req->transaction_id(), res->transaction_id());
|
||||
|
||||
const StunErrorCodeAttribute* err = res->GetErrorCode();
|
||||
ASSERT_TRUE(err != NULL);
|
||||
EXPECT_EQ(4, err->eclass());
|
||||
EXPECT_EQ(00, err->number());
|
||||
EXPECT_EQ("Bad Request", err->reason());
|
||||
}
|
||||
|
||||
// Send a binding request after an allocate and verify that it is rejected.
|
||||
TEST_F(RelayServerTest, TestSendRequestWrongType) {
|
||||
Allocate();
|
||||
Bind();
|
||||
|
||||
std::unique_ptr<StunMessage> req(CreateStunMessage(STUN_BINDING_REQUEST)),
|
||||
res;
|
||||
AddMagicCookieAttr(req.get());
|
||||
AddUsernameAttr(req.get(), username_);
|
||||
|
||||
Send1(req.get());
|
||||
res.reset(Receive1());
|
||||
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(STUN_BINDING_ERROR_RESPONSE, res->type());
|
||||
EXPECT_EQ(req->transaction_id(), res->transaction_id());
|
||||
|
||||
const StunErrorCodeAttribute* err = res->GetErrorCode();
|
||||
ASSERT_TRUE(err != NULL);
|
||||
EXPECT_EQ(6, err->eclass());
|
||||
EXPECT_EQ(0, err->number());
|
||||
EXPECT_EQ("Operation Not Supported", err->reason());
|
||||
}
|
||||
|
||||
// Verify that we can send traffic back and forth between the clients after a
|
||||
// successful allocate and bind.
|
||||
TEST_F(RelayServerTest, TestSendRaw) {
|
||||
Allocate();
|
||||
Bind();
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
std::unique_ptr<StunMessage> req(CreateStunMessage(STUN_SEND_REQUEST)), res;
|
||||
AddMagicCookieAttr(req.get());
|
||||
AddUsernameAttr(req.get(), username_);
|
||||
AddDestinationAttr(req.get(), client2_addr);
|
||||
|
||||
auto send_data = StunAttribute::CreateByteString(STUN_ATTR_DATA);
|
||||
send_data->CopyBytes(msg1);
|
||||
req->AddAttribute(std::move(send_data));
|
||||
|
||||
Send1(req.get());
|
||||
EXPECT_EQ(msg1, ReceiveRaw2());
|
||||
SendRaw2(msg2, static_cast<int>(strlen(msg2)));
|
||||
res.reset(Receive1());
|
||||
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(STUN_DATA_INDICATION, res->type());
|
||||
|
||||
const StunAddressAttribute* src_addr =
|
||||
res->GetAddress(STUN_ATTR_SOURCE_ADDRESS2);
|
||||
ASSERT_TRUE(src_addr != NULL);
|
||||
EXPECT_EQ(1, src_addr->family());
|
||||
EXPECT_EQ(client2_addr.ipaddr(), src_addr->ipaddr());
|
||||
EXPECT_EQ(client2_addr.port(), src_addr->port());
|
||||
|
||||
const StunByteStringAttribute* recv_data =
|
||||
res->GetByteString(STUN_ATTR_DATA);
|
||||
ASSERT_TRUE(recv_data != NULL);
|
||||
EXPECT_EQ(strlen(msg2), recv_data->length());
|
||||
EXPECT_EQ(0, memcmp(msg2, recv_data->bytes(), recv_data->length()));
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that a binding expires properly, and rejects send requests.
|
||||
// Flaky, see https://code.google.com/p/webrtc/issues/detail?id=4134
|
||||
TEST_F(RelayServerTest, DISABLED_TestExpiration) {
|
||||
Allocate();
|
||||
Bind();
|
||||
|
||||
// Wait twice the lifetime to make sure the server has expired the binding.
|
||||
rtc::Thread::Current()->ProcessMessages((LIFETIME * 2) * 1000);
|
||||
|
||||
std::unique_ptr<StunMessage> req(CreateStunMessage(STUN_SEND_REQUEST)), res;
|
||||
AddMagicCookieAttr(req.get());
|
||||
AddUsernameAttr(req.get(), username_);
|
||||
AddDestinationAttr(req.get(), client2_addr);
|
||||
|
||||
auto data_attr = StunAttribute::CreateByteString(STUN_ATTR_DATA);
|
||||
data_attr->CopyBytes(msg1);
|
||||
req->AddAttribute(std::move(data_attr));
|
||||
|
||||
Send1(req.get());
|
||||
res.reset(Receive1());
|
||||
|
||||
ASSERT_TRUE(res.get() != NULL);
|
||||
EXPECT_EQ(STUN_SEND_ERROR_RESPONSE, res->type());
|
||||
|
||||
const StunErrorCodeAttribute* err = res->GetErrorCode();
|
||||
ASSERT_TRUE(err != NULL);
|
||||
EXPECT_EQ(6, err->eclass());
|
||||
EXPECT_EQ(0, err->number());
|
||||
EXPECT_EQ("Operation Not Supported", err->reason());
|
||||
|
||||
// Also verify that traffic from the external client is ignored.
|
||||
SendRaw2(msg2, static_cast<int>(strlen(msg2)));
|
||||
EXPECT_TRUE(ReceiveRaw1().empty());
|
||||
}
|
||||
|
||||
} // namespace cricket
|
||||
@ -1,101 +0,0 @@
|
||||
/*
|
||||
* Copyright 2008 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.
|
||||
*/
|
||||
|
||||
#ifndef P2P_BASE_TEST_RELAY_SERVER_H_
|
||||
#define P2P_BASE_TEST_RELAY_SERVER_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "p2p/base/relay_server.h"
|
||||
#include "rtc_base/async_tcp_socket.h"
|
||||
#include "rtc_base/server_socket_adapters.h"
|
||||
#include "rtc_base/third_party/sigslot/sigslot.h"
|
||||
#include "rtc_base/thread.h"
|
||||
|
||||
namespace cricket {
|
||||
|
||||
// A test relay server. Useful for unit tests.
|
||||
class TestRelayServer : public sigslot::has_slots<> {
|
||||
public:
|
||||
TestRelayServer(rtc::Thread* thread,
|
||||
const rtc::SocketAddress& udp_int_addr,
|
||||
const rtc::SocketAddress& udp_ext_addr,
|
||||
const rtc::SocketAddress& tcp_int_addr,
|
||||
const rtc::SocketAddress& tcp_ext_addr,
|
||||
const rtc::SocketAddress& ssl_int_addr,
|
||||
const rtc::SocketAddress& ssl_ext_addr)
|
||||
: server_(thread) {
|
||||
server_.AddInternalSocket(
|
||||
rtc::AsyncUDPSocket::Create(thread->socketserver(), udp_int_addr));
|
||||
server_.AddExternalSocket(
|
||||
rtc::AsyncUDPSocket::Create(thread->socketserver(), udp_ext_addr));
|
||||
|
||||
tcp_int_socket_.reset(CreateListenSocket(thread, tcp_int_addr));
|
||||
tcp_ext_socket_.reset(CreateListenSocket(thread, tcp_ext_addr));
|
||||
ssl_int_socket_.reset(CreateListenSocket(thread, ssl_int_addr));
|
||||
ssl_ext_socket_.reset(CreateListenSocket(thread, ssl_ext_addr));
|
||||
}
|
||||
int GetConnectionCount() const { return server_.GetConnectionCount(); }
|
||||
rtc::SocketAddressPair GetConnection(int connection) const {
|
||||
return server_.GetConnection(connection);
|
||||
}
|
||||
bool HasConnection(const rtc::SocketAddress& address) const {
|
||||
return server_.HasConnection(address);
|
||||
}
|
||||
|
||||
private:
|
||||
rtc::AsyncSocket* CreateListenSocket(rtc::Thread* thread,
|
||||
const rtc::SocketAddress& addr) {
|
||||
rtc::AsyncSocket* socket =
|
||||
thread->socketserver()->CreateAsyncSocket(addr.family(), SOCK_STREAM);
|
||||
socket->Bind(addr);
|
||||
socket->Listen(5);
|
||||
socket->SignalReadEvent.connect(this, &TestRelayServer::OnAccept);
|
||||
return socket;
|
||||
}
|
||||
void OnAccept(rtc::AsyncSocket* socket) {
|
||||
bool external =
|
||||
(socket == tcp_ext_socket_.get() || socket == ssl_ext_socket_.get());
|
||||
bool ssl =
|
||||
(socket == ssl_int_socket_.get() || socket == ssl_ext_socket_.get());
|
||||
rtc::AsyncSocket* raw_socket = socket->Accept(NULL);
|
||||
if (raw_socket) {
|
||||
rtc::AsyncTCPSocket* packet_socket = new rtc::AsyncTCPSocket(
|
||||
(!ssl) ? raw_socket : new rtc::AsyncSSLServerSocket(raw_socket),
|
||||
false);
|
||||
if (!external) {
|
||||
packet_socket->SignalClose.connect(this,
|
||||
&TestRelayServer::OnInternalClose);
|
||||
server_.AddInternalSocket(packet_socket);
|
||||
} else {
|
||||
packet_socket->SignalClose.connect(this,
|
||||
&TestRelayServer::OnExternalClose);
|
||||
server_.AddExternalSocket(packet_socket);
|
||||
}
|
||||
}
|
||||
}
|
||||
void OnInternalClose(rtc::AsyncPacketSocket* socket, int error) {
|
||||
server_.RemoveInternalSocket(socket);
|
||||
}
|
||||
void OnExternalClose(rtc::AsyncPacketSocket* socket, int error) {
|
||||
server_.RemoveExternalSocket(socket);
|
||||
}
|
||||
|
||||
private:
|
||||
cricket::RelayServer server_;
|
||||
std::unique_ptr<rtc::AsyncSocket> tcp_int_socket_;
|
||||
std::unique_ptr<rtc::AsyncSocket> tcp_ext_socket_;
|
||||
std::unique_ptr<rtc::AsyncSocket> ssl_int_socket_;
|
||||
std::unique_ptr<rtc::AsyncSocket> ssl_ext_socket_;
|
||||
};
|
||||
|
||||
} // namespace cricket
|
||||
|
||||
#endif // P2P_BASE_TEST_RELAY_SERVER_H_
|
||||
@ -20,7 +20,6 @@
|
||||
#include "absl/algorithm/container.h"
|
||||
#include "p2p/base/basic_packet_socket_factory.h"
|
||||
#include "p2p/base/port.h"
|
||||
#include "p2p/base/relay_port.h"
|
||||
#include "p2p/base/stun_port.h"
|
||||
#include "p2p/base/tcp_port.h"
|
||||
#include "p2p/base/turn_port.h"
|
||||
@ -1522,42 +1521,7 @@ void AllocationSequence::CreateRelayPorts() {
|
||||
}
|
||||
|
||||
for (RelayServerConfig& relay : config_->relays) {
|
||||
if (relay.type == RELAY_GTURN) {
|
||||
CreateGturnPort(relay);
|
||||
} else if (relay.type == RELAY_TURN) {
|
||||
CreateTurnPort(relay);
|
||||
} else {
|
||||
RTC_NOTREACHED();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AllocationSequence::CreateGturnPort(const RelayServerConfig& config) {
|
||||
// TODO(mallinath) - Rename RelayPort to GTurnPort.
|
||||
std::unique_ptr<RelayPort> port = RelayPort::Create(
|
||||
session_->network_thread(), session_->socket_factory(), network_,
|
||||
session_->allocator()->min_port(), session_->allocator()->max_port(),
|
||||
config_->username, config_->password);
|
||||
if (port) {
|
||||
RelayPort* port_ptr = port.release();
|
||||
// Since RelayPort is not created using shared socket, |port| will not be
|
||||
// added to the dequeue.
|
||||
// Note: We must add the allocated port before we add addresses because
|
||||
// the latter will create candidates that need name and preference
|
||||
// settings. However, we also can't prepare the address (normally
|
||||
// done by AddAllocatedPort) until we have these addresses. So we
|
||||
// wait to do that until below.
|
||||
session_->AddAllocatedPort(port_ptr, this, false);
|
||||
|
||||
// Add the addresses of this protocol.
|
||||
PortList::const_iterator relay_port;
|
||||
for (relay_port = config.ports.begin(); relay_port != config.ports.end();
|
||||
++relay_port) {
|
||||
port_ptr->AddServerAddress(*relay_port);
|
||||
port_ptr->AddExternalAddress(*relay_port);
|
||||
}
|
||||
// Start fetching an address for this port.
|
||||
port_ptr->PrepareAddress();
|
||||
CreateTurnPort(relay);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1720,7 +1684,7 @@ ServerAddresses PortConfiguration::StunServers() {
|
||||
// Every UDP TURN server should also be used as a STUN server if
|
||||
// use_turn_server_as_stun_server is not disabled or the stun servers are
|
||||
// empty.
|
||||
ServerAddresses turn_servers = GetRelayServerAddresses(RELAY_TURN, PROTO_UDP);
|
||||
ServerAddresses turn_servers = GetRelayServerAddresses(PROTO_UDP);
|
||||
for (const rtc::SocketAddress& turn_server : turn_servers) {
|
||||
if (stun_servers.find(turn_server) == stun_servers.end()) {
|
||||
stun_servers.insert(turn_server);
|
||||
@ -1744,21 +1708,19 @@ bool PortConfiguration::SupportsProtocol(const RelayServerConfig& relay,
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PortConfiguration::SupportsProtocol(RelayType turn_type,
|
||||
ProtocolType type) const {
|
||||
bool PortConfiguration::SupportsProtocol(ProtocolType type) const {
|
||||
for (size_t i = 0; i < relays.size(); ++i) {
|
||||
if (relays[i].type == turn_type && SupportsProtocol(relays[i], type))
|
||||
if (SupportsProtocol(relays[i], type))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
ServerAddresses PortConfiguration::GetRelayServerAddresses(
|
||||
RelayType turn_type,
|
||||
ProtocolType type) const {
|
||||
ServerAddresses servers;
|
||||
for (size_t i = 0; i < relays.size(); ++i) {
|
||||
if (relays[i].type == turn_type && SupportsProtocol(relays[i], type)) {
|
||||
if (SupportsProtocol(relays[i], type)) {
|
||||
servers.insert(relays[i].ports.front().address);
|
||||
}
|
||||
}
|
||||
|
||||
@ -316,11 +316,10 @@ struct RTC_EXPORT PortConfiguration : public rtc::MessageData {
|
||||
// Determines whether the given relay server supports the given protocol.
|
||||
bool SupportsProtocol(const RelayServerConfig& relay,
|
||||
ProtocolType type) const;
|
||||
bool SupportsProtocol(RelayType turn_type, ProtocolType type) const;
|
||||
bool SupportsProtocol(ProtocolType type) const;
|
||||
// Helper method returns the server addresses for the matching RelayType and
|
||||
// Protocol type.
|
||||
ServerAddresses GetRelayServerAddresses(RelayType turn_type,
|
||||
ProtocolType type) const;
|
||||
ServerAddresses GetRelayServerAddresses(ProtocolType type) const;
|
||||
};
|
||||
|
||||
class UDPPort;
|
||||
@ -388,7 +387,6 @@ class AllocationSequence : public rtc::MessageHandler,
|
||||
void CreateTCPPorts();
|
||||
void CreateStunPorts();
|
||||
void CreateRelayPorts();
|
||||
void CreateGturnPort(const RelayServerConfig& config);
|
||||
|
||||
void OnReadPacket(rtc::AsyncPacketSocket* socket,
|
||||
const char* data,
|
||||
|
||||
@ -19,7 +19,6 @@
|
||||
#include "p2p/base/stun_port.h"
|
||||
#include "p2p/base/stun_request.h"
|
||||
#include "p2p/base/stun_server.h"
|
||||
#include "p2p/base/test_relay_server.h"
|
||||
#include "p2p/base/test_stun_server.h"
|
||||
#include "p2p/base/test_turn_server.h"
|
||||
#include "rtc_base/fake_clock.h"
|
||||
@ -218,7 +217,7 @@ class BasicPortAllocatorTestBase : public ::testing::Test,
|
||||
|
||||
RelayServerConfig CreateTurnServers(const rtc::SocketAddress& udp_turn,
|
||||
const rtc::SocketAddress& tcp_turn) {
|
||||
RelayServerConfig turn_server(RELAY_TURN);
|
||||
RelayServerConfig turn_server;
|
||||
RelayCredentials credentials(kTurnUsername, kTurnPassword);
|
||||
turn_server.credentials = credentials;
|
||||
|
||||
@ -1778,7 +1777,7 @@ TEST_F(BasicPortAllocatorTestWithRealClock,
|
||||
AddInterface(kClientAddr);
|
||||
allocator_.reset(new BasicPortAllocator(&network_manager_));
|
||||
allocator_->Initialize();
|
||||
RelayServerConfig turn_server(RELAY_TURN);
|
||||
RelayServerConfig turn_server;
|
||||
RelayCredentials credentials(kTurnUsername, kTurnPassword);
|
||||
turn_server.credentials = credentials;
|
||||
turn_server.ports.push_back(
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user