Remove stun_prober

The STUN prober shows the old RFC 3489 way of determining the NAT type
by pinging two different servers. This is known to be faulty as pointed
out by
  https://datatracker.ietf.org/doc/html/rfc5389#section-2

Chromium dependency removed in
  https://chromium-review.googlesource.com/c/chromium/src/+/6036622

BUG=None

Change-Id: I2b61dfe2ff899ce71ec9d2253dc836c5908cf8c6
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/368182
Commit-Queue: Philipp Hancke <phancke@meta.com>
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Reviewed-by: Mirko Bonadei <mbonadei@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#43503}
This commit is contained in:
Philipp Hancke 2024-11-20 08:44:25 -08:00 committed by WebRTC LUCI CQ
parent cab60a0842
commit 03f56d75d5
7 changed files with 1 additions and 1271 deletions

View File

@ -650,7 +650,6 @@ if (rtc_include_tests && !build_with_chromium) {
"api/video_codecs/test:video_codecs_api_unittests", "api/video_codecs/test:video_codecs_api_unittests",
"api/voip:compile_all_headers", "api/voip:compile_all_headers",
"call:fake_network_pipe_unittests", "call:fake_network_pipe_unittests",
"p2p:libstunprober_unittests",
"p2p:rtc_p2p_unittests", "p2p:rtc_p2p_unittests",
"rtc_base:async_dns_resolver_unittests", "rtc_base:async_dns_resolver_unittests",
"rtc_base:async_packet_socket_unittest", "rtc_base:async_packet_socket_unittest",

View File

@ -42,10 +42,6 @@ group("examples") {
} }
} }
if (!build_with_chromium) {
deps += [ ":stun_prober" ]
}
if (is_ios || (is_mac && target_cpu != "x86")) { if (is_ios || (is_mac && target_cpu != "x86")) {
deps += [ ":AppRTCMobile" ] deps += [ ":AppRTCMobile" ]
} }
@ -869,32 +865,3 @@ if (is_android) {
] ] ] ]
} }
} }
if (!build_with_chromium) {
# Doesn't build within Chrome on Win.
rtc_executable("stun_prober") {
testonly = true
sources = [ "stunprober/main.cc" ]
deps = [
"../p2p:basic_packet_socket_factory",
"../p2p:libstunprober",
"../p2p:rtc_p2p",
"../rtc_base:checks",
"../rtc_base:crypto_random",
"../rtc_base:logging",
"../rtc_base:network",
"../rtc_base:socket_address",
"../rtc_base:ssl_adapter",
"../rtc_base:threading",
"../rtc_base:timeutils",
"../test:scoped_key_value_config",
"//third_party/abseil-cpp/absl/flags:flag",
"//third_party/abseil-cpp/absl/flags:parse",
]
if (is_win) {
deps += [
"../rtc_base:win32_socket_init",
]
}
}
}

View File

@ -1,153 +0,0 @@
/*
* Copyright 2015 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 <memory>
#include <set>
#include <sstream>
#include <string>
#include <vector>
#include "absl/flags/flag.h"
#include "absl/flags/parse.h"
#include "p2p/base/basic_packet_socket_factory.h"
#include "p2p/stunprober/stun_prober.h"
#include "rtc_base/crypto_random.h"
#include "rtc_base/logging.h"
#include "rtc_base/network.h"
#include "rtc_base/physical_socket_server.h"
#include "rtc_base/socket_address.h"
#include "rtc_base/ssl_adapter.h"
#include "rtc_base/thread.h"
#include "rtc_base/time_utils.h"
#include "test/scoped_key_value_config.h"
#ifdef WEBRTC_WIN
#include "rtc_base/win32_socket_init.h"
#endif // WEBRTC_WIN
using stunprober::AsyncCallback;
using stunprober::StunProber;
ABSL_FLAG(int,
interval,
10,
"Interval of consecutive stun pings in milliseconds");
ABSL_FLAG(bool,
shared_socket,
false,
"Share socket mode for different remote IPs");
ABSL_FLAG(int,
pings_per_ip,
10,
"Number of consecutive stun pings to send for each IP");
ABSL_FLAG(int,
timeout,
1000,
"Milliseconds of wait after the last ping sent before exiting");
ABSL_FLAG(
std::string,
servers,
"stun.l.google.com:19302,stun1.l.google.com:19302,stun2.l.google.com:19302",
"Comma separated STUN server addresses with ports");
namespace {
const char* PrintNatType(stunprober::NatType type) {
switch (type) {
case stunprober::NATTYPE_NONE:
return "Not behind a NAT";
case stunprober::NATTYPE_UNKNOWN:
return "Unknown NAT type";
case stunprober::NATTYPE_SYMMETRIC:
return "Symmetric NAT";
case stunprober::NATTYPE_NON_SYMMETRIC:
return "Non-Symmetric NAT";
default:
return "Invalid";
}
}
void PrintStats(StunProber* prober) {
StunProber::Stats stats;
if (!prober->GetStats(&stats)) {
RTC_LOG(LS_WARNING) << "Results are inconclusive.";
return;
}
RTC_LOG(LS_INFO) << "Shared Socket Mode: " << stats.shared_socket_mode;
RTC_LOG(LS_INFO) << "Requests sent: " << stats.num_request_sent;
RTC_LOG(LS_INFO) << "Responses received: " << stats.num_response_received;
RTC_LOG(LS_INFO) << "Target interval (ns): "
<< stats.target_request_interval_ns;
RTC_LOG(LS_INFO) << "Actual interval (ns): "
<< stats.actual_request_interval_ns;
RTC_LOG(LS_INFO) << "NAT Type: " << PrintNatType(stats.nat_type);
RTC_LOG(LS_INFO) << "Host IP: " << stats.host_ip;
RTC_LOG(LS_INFO) << "Server-reflexive ips: ";
for (auto& ip : stats.srflx_addrs) {
RTC_LOG(LS_INFO) << "\t" << ip;
}
RTC_LOG(LS_INFO) << "Success Precent: " << stats.success_percent;
RTC_LOG(LS_INFO) << "Response Latency:" << stats.average_rtt_ms;
}
void StopTrial(rtc::Thread* thread, StunProber* prober, int result) {
thread->Quit();
if (prober) {
RTC_LOG(LS_INFO) << "Result: " << result;
if (result == StunProber::SUCCESS) {
PrintStats(prober);
}
}
}
} // namespace
int main(int argc, char* argv[]) {
#ifdef WEBRTC_WIN
rtc::WinsockInitializer winsock_init;
#endif // WEBRTC_WIN
absl::ParseCommandLine(argc, argv);
std::vector<rtc::SocketAddress> server_addresses;
std::istringstream servers(absl::GetFlag(FLAGS_servers));
std::string server;
while (getline(servers, server, ',')) {
rtc::SocketAddress addr;
if (!addr.FromString(server)) {
RTC_LOG(LS_ERROR) << "Parsing " << server << " failed.";
return -1;
}
server_addresses.push_back(addr);
}
rtc::InitializeSSL();
rtc::InitRandom(rtc::Time32());
webrtc::test::ScopedKeyValueConfig field_trials;
rtc::PhysicalSocketServer socket_server;
rtc::AutoSocketServerThread thread(&socket_server);
auto socket_factory =
std::make_unique<rtc::BasicPacketSocketFactory>(&socket_server);
std::unique_ptr<rtc::BasicNetworkManager> network_manager(
new rtc::BasicNetworkManager(&socket_server, &field_trials));
std::vector<const rtc::Network*> networks = network_manager->GetNetworks();
auto prober = std::make_unique<StunProber>(socket_factory.get(),
rtc::Thread::Current(), networks);
auto finish_callback = [&thread](StunProber* prober, int result) {
StopTrial(&thread, prober, result);
};
prober->Start(server_addresses, absl::GetFlag(FLAGS_shared_socket),
absl::GetFlag(FLAGS_interval),
absl::GetFlag(FLAGS_pings_per_ip), absl::GetFlag(FLAGS_timeout),
AsyncCallback(finish_callback));
thread.Run();
return 0;
}

View File

@ -9,10 +9,7 @@
import("../webrtc.gni") import("../webrtc.gni")
group("p2p") { group("p2p") {
deps = [ deps = [ ":rtc_p2p" ]
":libstunprober",
":rtc_p2p",
]
} }
rtc_library("rtc_p2p") { rtc_library("rtc_p2p") {
@ -1258,53 +1255,3 @@ rtc_library("p2p_server_utils") {
"//third_party/abseil-cpp/absl/strings:string_view", "//third_party/abseil-cpp/absl/strings:string_view",
] ]
} }
rtc_library("libstunprober") {
visibility = [ "*" ]
sources = [
"stunprober/stun_prober.cc",
"stunprober/stun_prober.h",
]
deps = [
"../api:array_view",
"../api:async_dns_resolver",
"../api:packet_socket_factory",
"../api:sequence_checker",
"../api/task_queue:pending_task_safety_flag",
"../api/transport:stun_types",
"../api/units:time_delta",
"../rtc_base:async_packet_socket",
"../rtc_base:byte_buffer",
"../rtc_base:checks",
"../rtc_base:ip_address",
"../rtc_base:logging",
"../rtc_base:network",
"../rtc_base:socket_address",
"../rtc_base:ssl",
"../rtc_base:threading",
"../rtc_base:timeutils",
"../rtc_base/network:received_packet",
"../rtc_base/system:rtc_export",
]
}
if (rtc_include_tests) {
rtc_library("libstunprober_unittests") {
testonly = true
sources = [ "stunprober/stun_prober_unittest.cc" ]
deps = [
":basic_packet_socket_factory",
":libstunprober",
":p2p_test_utils",
"../rtc_base:checks",
"../rtc_base:gunit_helpers",
"../rtc_base:ip_address",
"../rtc_base:rtc_base_tests_utils",
"../rtc_base:ssl_adapter",
"../test:test_support",
"//testing/gtest",
]
}
}

View File

@ -1,602 +0,0 @@
/*
* Copyright 2015 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/stunprober/stun_prober.h"
#include <cstdint>
#include <map>
#include <memory>
#include <set>
#include <string>
#include <utility>
#include "api/array_view.h"
#include "api/packet_socket_factory.h"
#include "api/task_queue/pending_task_safety_flag.h"
#include "api/transport/stun.h"
#include "api/units/time_delta.h"
#include "rtc_base/async_packet_socket.h"
#include "rtc_base/checks.h"
#include "rtc_base/network/received_packet.h"
#include "rtc_base/thread.h"
#include "rtc_base/time_utils.h"
namespace stunprober {
namespace {
using ::webrtc::SafeTask;
using ::webrtc::TimeDelta;
const int THREAD_WAKE_UP_INTERVAL_MS = 5;
template <typename T>
void IncrementCounterByAddress(std::map<T, int>* counter_per_ip, const T& ip) {
counter_per_ip->insert(std::make_pair(ip, 0)).first->second++;
}
} // namespace
// A requester tracks the requests and responses from a single socket to many
// STUN servers
class StunProber::Requester : public sigslot::has_slots<> {
public:
// Each Request maps to a request and response.
struct Request {
// Actual time the STUN bind request was sent.
int64_t sent_time_ms = 0;
// Time the response was received.
int64_t received_time_ms = 0;
// Server reflexive address from STUN response for this given request.
rtc::SocketAddress srflx_addr;
rtc::IPAddress server_addr;
int64_t rtt() { return received_time_ms - sent_time_ms; }
void ProcessResponse(rtc::ArrayView<const uint8_t> payload);
};
// StunProber provides `server_ips` for Requester to probe. For shared
// socket mode, it'll be all the resolved IP addresses. For non-shared mode,
// it'll just be a single address.
Requester(StunProber* prober,
rtc::AsyncPacketSocket* socket,
const std::vector<rtc::SocketAddress>& server_ips);
~Requester() override;
Requester(const Requester&) = delete;
Requester& operator=(const Requester&) = delete;
// There is no callback for SendStunRequest as the underneath socket send is
// expected to be completed immediately. Otherwise, it'll skip this request
// and move to the next one.
void SendStunRequest();
void OnStunResponseReceived(rtc::AsyncPacketSocket* socket,
const rtc::ReceivedPacket& packet);
const std::vector<Request*>& requests() { return requests_; }
// Whether this Requester has completed all requests.
bool Done() {
return static_cast<size_t>(num_request_sent_) == server_ips_.size();
}
private:
Request* GetRequestByAddress(const rtc::IPAddress& ip);
StunProber* prober_;
// The socket for this session.
std::unique_ptr<rtc::AsyncPacketSocket> socket_;
// Temporary SocketAddress and buffer for RecvFrom.
rtc::SocketAddress addr_;
std::unique_ptr<rtc::ByteBufferWriter> response_packet_;
std::vector<Request*> requests_;
std::vector<rtc::SocketAddress> server_ips_;
int16_t num_request_sent_ = 0;
int16_t num_response_received_ = 0;
webrtc::SequenceChecker& thread_checker_;
};
StunProber::Requester::Requester(
StunProber* prober,
rtc::AsyncPacketSocket* socket,
const std::vector<rtc::SocketAddress>& server_ips)
: prober_(prober),
socket_(socket),
response_packet_(new rtc::ByteBufferWriter(nullptr, kMaxUdpBufferSize)),
server_ips_(server_ips),
thread_checker_(prober->thread_checker_) {
socket_->RegisterReceivedPacketCallback(
[&](rtc::AsyncPacketSocket* socket, const rtc::ReceivedPacket& packet) {
OnStunResponseReceived(socket, packet);
});
}
StunProber::Requester::~Requester() {
if (socket_) {
socket_->Close();
}
for (auto* req : requests_) {
if (req) {
delete req;
}
}
}
void StunProber::Requester::SendStunRequest() {
RTC_DCHECK(thread_checker_.IsCurrent());
requests_.push_back(new Request());
Request& request = *(requests_.back());
// Random transaction ID, STUN_BINDING_REQUEST
cricket::StunMessage message(cricket::STUN_BINDING_REQUEST);
std::unique_ptr<rtc::ByteBufferWriter> request_packet(
new rtc::ByteBufferWriter(nullptr, kMaxUdpBufferSize));
if (!message.Write(request_packet.get())) {
prober_->ReportOnFinished(WRITE_FAILED);
return;
}
auto addr = server_ips_[num_request_sent_];
request.server_addr = addr.ipaddr();
// The write must succeed immediately. Otherwise, the calculating of the STUN
// request timing could become too complicated. Callback is ignored by passing
// empty AsyncCallback.
rtc::PacketOptions options;
int rv = socket_->SendTo(request_packet->Data(), request_packet->Length(),
addr, options);
if (rv < 0) {
prober_->ReportOnFinished(WRITE_FAILED);
return;
}
request.sent_time_ms = rtc::TimeMillis();
num_request_sent_++;
RTC_DCHECK(static_cast<size_t>(num_request_sent_) <= server_ips_.size());
}
void StunProber::Requester::Request::ProcessResponse(
rtc::ArrayView<const uint8_t> payload) {
int64_t now = rtc::TimeMillis();
rtc::ByteBufferReader message(payload);
cricket::StunMessage stun_response;
if (!stun_response.Read(&message)) {
// Invalid or incomplete STUN packet.
received_time_ms = 0;
return;
}
// Get external address of the socket.
const cricket::StunAddressAttribute* addr_attr =
stun_response.GetAddress(cricket::STUN_ATTR_MAPPED_ADDRESS);
if (addr_attr == nullptr) {
// Addresses not available to detect whether or not behind a NAT.
return;
}
if (addr_attr->family() != cricket::STUN_ADDRESS_IPV4 &&
addr_attr->family() != cricket::STUN_ADDRESS_IPV6) {
return;
}
received_time_ms = now;
srflx_addr = addr_attr->GetAddress();
}
void StunProber::Requester::OnStunResponseReceived(
rtc::AsyncPacketSocket* socket,
const rtc::ReceivedPacket& packet) {
RTC_DCHECK(thread_checker_.IsCurrent());
RTC_DCHECK(socket_);
Request* request = GetRequestByAddress(packet.source_address().ipaddr());
if (!request) {
// Something is wrong, finish the test.
prober_->ReportOnFinished(GENERIC_FAILURE);
return;
}
num_response_received_++;
request->ProcessResponse(packet.payload());
}
StunProber::Requester::Request* StunProber::Requester::GetRequestByAddress(
const rtc::IPAddress& ipaddr) {
RTC_DCHECK(thread_checker_.IsCurrent());
for (auto* request : requests_) {
if (request->server_addr == ipaddr) {
return request;
}
}
return nullptr;
}
StunProber::Stats::Stats() = default;
StunProber::Stats::~Stats() = default;
StunProber::ObserverAdapter::ObserverAdapter() = default;
StunProber::ObserverAdapter::~ObserverAdapter() = default;
void StunProber::ObserverAdapter::OnPrepared(StunProber* stunprober,
Status status) {
if (status == SUCCESS) {
stunprober->Start(this);
} else {
callback_(stunprober, status);
}
}
void StunProber::ObserverAdapter::OnFinished(StunProber* stunprober,
Status status) {
callback_(stunprober, status);
}
StunProber::StunProber(rtc::PacketSocketFactory* socket_factory,
rtc::Thread* thread,
std::vector<const rtc::Network*> networks)
: interval_ms_(0),
socket_factory_(socket_factory),
thread_(thread),
networks_(std::move(networks)) {}
StunProber::~StunProber() {
RTC_DCHECK(thread_checker_.IsCurrent());
for (auto* req : requesters_) {
if (req) {
delete req;
}
}
for (auto* s : sockets_) {
if (s) {
delete s;
}
}
}
bool StunProber::Start(const std::vector<rtc::SocketAddress>& servers,
bool shared_socket_mode,
int interval_ms,
int num_request_per_ip,
int timeout_ms,
const AsyncCallback callback) {
observer_adapter_.set_callback(callback);
return Prepare(servers, shared_socket_mode, interval_ms, num_request_per_ip,
timeout_ms, &observer_adapter_);
}
bool StunProber::Prepare(const std::vector<rtc::SocketAddress>& servers,
bool shared_socket_mode,
int interval_ms,
int num_request_per_ip,
int timeout_ms,
StunProber::Observer* observer) {
RTC_DCHECK(thread_checker_.IsCurrent());
interval_ms_ = interval_ms;
shared_socket_mode_ = shared_socket_mode;
requests_per_ip_ = num_request_per_ip;
if (requests_per_ip_ == 0 || servers.size() == 0) {
return false;
}
timeout_ms_ = timeout_ms;
servers_ = servers;
observer_ = observer;
// Remove addresses that are already resolved.
for (auto it = servers_.begin(); it != servers_.end();) {
if (it->ipaddr().family() != AF_UNSPEC) {
all_servers_addrs_.push_back(*it);
it = servers_.erase(it);
} else {
++it;
}
}
if (servers_.empty()) {
CreateSockets();
return true;
}
return ResolveServerName(servers_.back());
}
bool StunProber::Start(StunProber::Observer* observer) {
observer_ = observer;
if (total_ready_sockets_ != total_socket_required()) {
return false;
}
MaybeScheduleStunRequests();
return true;
}
bool StunProber::ResolveServerName(const rtc::SocketAddress& addr) {
RTC_DCHECK(!resolver_);
resolver_ = socket_factory_->CreateAsyncDnsResolver();
if (!resolver_) {
return false;
}
resolver_->Start(addr, [this] { OnServerResolved(resolver_->result()); });
return true;
}
void StunProber::OnSocketReady(rtc::AsyncPacketSocket* socket,
const rtc::SocketAddress& addr) {
total_ready_sockets_++;
if (total_ready_sockets_ == total_socket_required()) {
ReportOnPrepared(SUCCESS);
}
}
void StunProber::OnServerResolved(
const webrtc::AsyncDnsResolverResult& result) {
RTC_DCHECK(thread_checker_.IsCurrent());
rtc::SocketAddress received_address;
if (result.GetResolvedAddress(AF_INET, &received_address)) {
// Construct an address without the name in it.
rtc::SocketAddress addr(received_address.ipaddr(), received_address.port());
all_servers_addrs_.push_back(addr);
}
resolver_.reset();
servers_.pop_back();
if (servers_.size()) {
if (!ResolveServerName(servers_.back())) {
ReportOnPrepared(RESOLVE_FAILED);
}
return;
}
if (all_servers_addrs_.size() == 0) {
ReportOnPrepared(RESOLVE_FAILED);
return;
}
CreateSockets();
}
void StunProber::CreateSockets() {
// Dedupe.
std::set<rtc::SocketAddress> addrs(all_servers_addrs_.begin(),
all_servers_addrs_.end());
all_servers_addrs_.assign(addrs.begin(), addrs.end());
// Prepare all the sockets beforehand. All of them will bind to "any" address.
while (sockets_.size() < total_socket_required()) {
std::unique_ptr<rtc::AsyncPacketSocket> socket(
socket_factory_->CreateUdpSocket(rtc::SocketAddress(INADDR_ANY, 0), 0,
0));
if (!socket) {
ReportOnPrepared(GENERIC_FAILURE);
return;
}
// Chrome and WebRTC behave differently in terms of the state of a socket
// once returned from PacketSocketFactory::CreateUdpSocket.
if (socket->GetState() == rtc::AsyncPacketSocket::STATE_BINDING) {
socket->SignalAddressReady.connect(this, &StunProber::OnSocketReady);
} else {
OnSocketReady(socket.get(), rtc::SocketAddress(INADDR_ANY, 0));
}
sockets_.push_back(socket.release());
}
}
StunProber::Requester* StunProber::CreateRequester() {
RTC_DCHECK(thread_checker_.IsCurrent());
if (!sockets_.size()) {
return nullptr;
}
StunProber::Requester* requester;
if (shared_socket_mode_) {
requester = new Requester(this, sockets_.back(), all_servers_addrs_);
} else {
std::vector<rtc::SocketAddress> server_ip;
server_ip.push_back(
all_servers_addrs_[(num_request_sent_ % all_servers_addrs_.size())]);
requester = new Requester(this, sockets_.back(), server_ip);
}
sockets_.pop_back();
return requester;
}
bool StunProber::SendNextRequest() {
if (!current_requester_ || current_requester_->Done()) {
current_requester_ = CreateRequester();
requesters_.push_back(current_requester_);
}
if (!current_requester_) {
return false;
}
current_requester_->SendStunRequest();
num_request_sent_++;
return true;
}
bool StunProber::should_send_next_request(int64_t now) {
if (interval_ms_ < THREAD_WAKE_UP_INTERVAL_MS) {
return now >= next_request_time_ms_;
} else {
return (now + (THREAD_WAKE_UP_INTERVAL_MS / 2)) >= next_request_time_ms_;
}
}
int StunProber::get_wake_up_interval_ms() {
if (interval_ms_ < THREAD_WAKE_UP_INTERVAL_MS) {
return 1;
} else {
return THREAD_WAKE_UP_INTERVAL_MS;
}
}
void StunProber::MaybeScheduleStunRequests() {
RTC_DCHECK_RUN_ON(thread_);
int64_t now = rtc::TimeMillis();
if (Done()) {
thread_->PostDelayedTask(
SafeTask(task_safety_.flag(), [this] { ReportOnFinished(SUCCESS); }),
TimeDelta::Millis(timeout_ms_));
return;
}
if (should_send_next_request(now)) {
if (!SendNextRequest()) {
ReportOnFinished(GENERIC_FAILURE);
return;
}
next_request_time_ms_ = now + interval_ms_;
}
thread_->PostDelayedTask(
SafeTask(task_safety_.flag(), [this] { MaybeScheduleStunRequests(); }),
TimeDelta::Millis(get_wake_up_interval_ms()));
}
bool StunProber::GetStats(StunProber::Stats* prob_stats) const {
// No need to be on the same thread.
if (!prob_stats) {
return false;
}
StunProber::Stats stats;
int rtt_sum = 0;
int64_t first_sent_time = 0;
int64_t last_sent_time = 0;
NatType nat_type = NATTYPE_INVALID;
// Track of how many srflx IP that we have seen.
std::set<rtc::IPAddress> srflx_ips;
// If we're not receiving any response on a given IP, all requests sent to
// that IP should be ignored as this could just be an DNS error.
std::map<rtc::IPAddress, int> num_response_per_server;
std::map<rtc::IPAddress, int> num_request_per_server;
for (auto* requester : requesters_) {
std::map<rtc::SocketAddress, int> num_response_per_srflx_addr;
for (auto* request : requester->requests()) {
if (request->sent_time_ms <= 0) {
continue;
}
++stats.raw_num_request_sent;
IncrementCounterByAddress(&num_request_per_server, request->server_addr);
if (!first_sent_time) {
first_sent_time = request->sent_time_ms;
}
last_sent_time = request->sent_time_ms;
if (request->received_time_ms < request->sent_time_ms) {
continue;
}
IncrementCounterByAddress(&num_response_per_server, request->server_addr);
IncrementCounterByAddress(&num_response_per_srflx_addr,
request->srflx_addr);
rtt_sum += request->rtt();
stats.srflx_addrs.insert(request->srflx_addr.ToString());
srflx_ips.insert(request->srflx_addr.ipaddr());
}
// If we're using shared mode and seeing >1 srflx addresses for a single
// requester, it's symmetric NAT.
if (shared_socket_mode_ && num_response_per_srflx_addr.size() > 1) {
nat_type = NATTYPE_SYMMETRIC;
}
}
// We're probably not behind a regular NAT. We have more than 1 distinct
// server reflexive IPs.
if (srflx_ips.size() > 1) {
return false;
}
int num_sent = 0;
int num_received = 0;
int num_server_ip_with_response = 0;
for (const auto& kv : num_response_per_server) {
RTC_DCHECK_GT(kv.second, 0);
num_server_ip_with_response++;
num_received += kv.second;
num_sent += num_request_per_server[kv.first];
}
// Shared mode is only true if we use the shared socket and there are more
// than 1 responding servers.
stats.shared_socket_mode =
shared_socket_mode_ && (num_server_ip_with_response > 1);
if (stats.shared_socket_mode && nat_type == NATTYPE_INVALID) {
nat_type = NATTYPE_NON_SYMMETRIC;
}
// If we could find a local IP matching srflx, we're not behind a NAT.
rtc::SocketAddress srflx_addr;
if (stats.srflx_addrs.size() &&
!srflx_addr.FromString(*(stats.srflx_addrs.begin()))) {
return false;
}
for (const auto* net : networks_) {
if (srflx_addr.ipaddr() == net->GetBestIP()) {
nat_type = stunprober::NATTYPE_NONE;
stats.host_ip = net->GetBestIP().ToString();
break;
}
}
// Finally, we know we're behind a NAT but can't determine which type it is.
if (nat_type == NATTYPE_INVALID) {
nat_type = NATTYPE_UNKNOWN;
}
stats.nat_type = nat_type;
stats.num_request_sent = num_sent;
stats.num_response_received = num_received;
stats.target_request_interval_ns = interval_ms_ * 1000;
if (num_sent) {
stats.success_percent = static_cast<int>(100 * num_received / num_sent);
}
if (stats.raw_num_request_sent > 1) {
stats.actual_request_interval_ns =
(1000 * (last_sent_time - first_sent_time)) /
(stats.raw_num_request_sent - 1);
}
if (num_received) {
stats.average_rtt_ms = static_cast<int>((rtt_sum / num_received));
}
*prob_stats = stats;
return true;
}
void StunProber::ReportOnPrepared(StunProber::Status status) {
if (observer_) {
observer_->OnPrepared(this, status);
}
}
void StunProber::ReportOnFinished(StunProber::Status status) {
if (observer_) {
observer_->OnFinished(this, status);
}
}
} // namespace stunprober

View File

@ -1,251 +0,0 @@
/*
* Copyright 2015 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_STUNPROBER_STUN_PROBER_H_
#define P2P_STUNPROBER_STUN_PROBER_H_
#include <memory>
#include <set>
#include <string>
#include <vector>
#include "api/async_dns_resolver.h"
#include "api/sequence_checker.h"
#include "api/task_queue/pending_task_safety_flag.h"
#include "rtc_base/network.h"
#include "rtc_base/socket_address.h"
#include "rtc_base/system/rtc_export.h"
#include "rtc_base/thread.h"
namespace rtc {
class AsyncPacketSocket;
class PacketSocketFactory;
class Thread;
class NetworkManager;
class AsyncResolverInterface;
} // namespace rtc
namespace stunprober {
class StunProber;
static const int kMaxUdpBufferSize = 1200;
typedef std::function<void(StunProber*, int)> AsyncCallback;
enum NatType {
NATTYPE_INVALID,
NATTYPE_NONE, // Not behind a NAT.
NATTYPE_UNKNOWN, // Behind a NAT but type can't be determine.
NATTYPE_SYMMETRIC, // Behind a symmetric NAT.
NATTYPE_NON_SYMMETRIC // Behind a non-symmetric NAT.
};
class RTC_EXPORT StunProber : public sigslot::has_slots<> {
public:
enum Status { // Used in UMA_HISTOGRAM_ENUMERATION.
SUCCESS, // Successfully received bytes from the server.
GENERIC_FAILURE, // Generic failure.
RESOLVE_FAILED, // Host resolution failed.
WRITE_FAILED, // Sending a message to the server failed.
READ_FAILED, // Reading the reply from the server failed.
};
class Observer {
public:
virtual ~Observer() = default;
virtual void OnPrepared(StunProber* prober, StunProber::Status status) = 0;
virtual void OnFinished(StunProber* prober, StunProber::Status status) = 0;
};
struct RTC_EXPORT Stats {
Stats();
~Stats();
// `raw_num_request_sent` is the total number of requests
// sent. `num_request_sent` is the count of requests against a server where
// we see at least one response. `num_request_sent` is designed to protect
// against DNS resolution failure or the STUN server is not responsive
// which could skew the result.
int raw_num_request_sent = 0;
int num_request_sent = 0;
int num_response_received = 0;
NatType nat_type = NATTYPE_INVALID;
int average_rtt_ms = -1;
int success_percent = 0;
int target_request_interval_ns = 0;
int actual_request_interval_ns = 0;
// Also report whether this trial can't be considered truly as shared
// mode. Share mode only makes sense when we have multiple IP resolved and
// successfully probed.
bool shared_socket_mode = false;
std::string host_ip;
// If the srflx_addrs has more than 1 element, the NAT is symmetric.
std::set<std::string> srflx_addrs;
};
StunProber(rtc::PacketSocketFactory* socket_factory,
rtc::Thread* thread,
std::vector<const rtc::Network*> networks);
~StunProber() override;
StunProber(const StunProber&) = delete;
StunProber& operator=(const StunProber&) = delete;
// Begin performing the probe test against the `servers`. If
// `shared_socket_mode` is false, each request will be done with a new socket.
// Otherwise, a unique socket will be used for a single round of requests
// against all resolved IPs. No single socket will be used against a given IP
// more than once. The interval of requests will be as close to the requested
// inter-probe interval `stun_ta_interval_ms` as possible. After sending out
// the last scheduled request, the probe will wait `timeout_ms` for request
// responses and then call `finish_callback`. `requests_per_ip` indicates how
// many requests should be tried for each resolved IP address. In shared mode,
// (the number of sockets to be created) equals to `requests_per_ip`. In
// non-shared mode, (the number of sockets) equals to requests_per_ip * (the
// number of resolved IP addresses). TODO(guoweis): Remove this once
// everything moved to Prepare() and Run().
bool Start(const std::vector<rtc::SocketAddress>& servers,
bool shared_socket_mode,
int stun_ta_interval_ms,
int requests_per_ip,
int timeout_ms,
AsyncCallback finish_callback);
// TODO(guoweis): The combination of Prepare() and Run() are equivalent to the
// Start() above. Remove Start() once everything is migrated.
bool Prepare(const std::vector<rtc::SocketAddress>& servers,
bool shared_socket_mode,
int stun_ta_interval_ms,
int requests_per_ip,
int timeout_ms,
StunProber::Observer* observer);
// Start to send out the STUN probes.
bool Start(StunProber::Observer* observer);
// Method to retrieve the Stats once `finish_callback` is invoked. Returning
// false when the result is inconclusive, for example, whether it's behind a
// NAT or not.
bool GetStats(Stats* stats) const;
int estimated_execution_time() {
return static_cast<int>(requests_per_ip_ * all_servers_addrs_.size() *
interval_ms_);
}
private:
// A requester tracks the requests and responses from a single socket to many
// STUN servers.
class Requester;
// TODO(guoweis): Remove this once all dependencies move away from
// AsyncCallback.
class ObserverAdapter : public Observer {
public:
ObserverAdapter();
~ObserverAdapter() override;
void set_callback(AsyncCallback callback) { callback_ = callback; }
void OnPrepared(StunProber* stunprober, Status status) override;
void OnFinished(StunProber* stunprober, Status status) override;
private:
AsyncCallback callback_;
};
bool ResolveServerName(const rtc::SocketAddress& addr);
void OnServerResolved(const webrtc::AsyncDnsResolverResult& resolver);
void OnSocketReady(rtc::AsyncPacketSocket* socket,
const rtc::SocketAddress& addr);
void CreateSockets();
bool Done() {
return num_request_sent_ >= requests_per_ip_ * all_servers_addrs_.size();
}
size_t total_socket_required() {
return (shared_socket_mode_ ? 1 : all_servers_addrs_.size()) *
requests_per_ip_;
}
bool should_send_next_request(int64_t now);
int get_wake_up_interval_ms();
bool SendNextRequest();
// Will be invoked in 1ms intervals and schedule the next request from the
// `current_requester_` if the time has passed for another request.
void MaybeScheduleStunRequests();
void ReportOnPrepared(StunProber::Status status);
void ReportOnFinished(StunProber::Status status);
Requester* CreateRequester();
Requester* current_requester_ = nullptr;
// The time when the next request should go out.
int64_t next_request_time_ms_ = 0;
// Total requests sent so far.
uint32_t num_request_sent_ = 0;
bool shared_socket_mode_ = false;
// How many requests should be done against each resolved IP.
uint32_t requests_per_ip_ = 0;
// Milliseconds to pause between each STUN request.
int interval_ms_;
// Timeout period after the last request is sent.
int timeout_ms_;
// STUN server name to be resolved.
std::vector<rtc::SocketAddress> servers_;
// Weak references.
rtc::PacketSocketFactory* socket_factory_;
rtc::Thread* thread_;
// Accumulate all resolved addresses.
std::vector<rtc::SocketAddress> all_servers_addrs_;
// The set of STUN probe sockets and their state.
std::vector<Requester*> requesters_;
webrtc::SequenceChecker thread_checker_;
// Temporary storage for created sockets.
std::vector<rtc::AsyncPacketSocket*> sockets_;
// This tracks how many of the sockets are ready.
size_t total_ready_sockets_ = 0;
Observer* observer_ = nullptr;
// TODO(guoweis): Remove this once all dependencies move away from
// AsyncCallback.
ObserverAdapter observer_adapter_;
const std::vector<const rtc::Network*> networks_;
std::unique_ptr<webrtc::AsyncDnsResolverInterface> resolver_;
webrtc::ScopedTaskSafety task_safety_;
};
} // namespace stunprober
#endif // P2P_STUNPROBER_STUN_PROBER_H_

View File

@ -1,177 +0,0 @@
/*
* Copyright 2015 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/stunprober/stun_prober.h"
#include <stdint.h>
#include <memory>
#include <utility>
#include "p2p/base/basic_packet_socket_factory.h"
#include "p2p/base/test_stun_server.h"
#include "rtc_base/gunit.h"
#include "rtc_base/ip_address.h"
#include "rtc_base/ssl_adapter.h"
#include "rtc_base/virtual_socket_server.h"
#include "test/gtest.h"
using stunprober::AsyncCallback;
using stunprober::StunProber;
namespace stunprober {
namespace {
const rtc::SocketAddress kLocalAddr("192.168.0.1", 0);
const rtc::SocketAddress kStunAddr1("1.1.1.1", 3478);
const rtc::SocketAddress kStunAddr2("1.1.1.2", 3478);
const rtc::SocketAddress kFailedStunAddr("1.1.1.3", 3478);
const rtc::SocketAddress kStunMappedAddr("77.77.77.77", 0);
} // namespace
class StunProberTest : public ::testing::Test {
public:
StunProberTest()
: ss_(std::make_unique<rtc::VirtualSocketServer>()),
main_(ss_.get()),
result_(StunProber::SUCCESS),
stun_server_1_(
cricket::TestStunServer::Create(ss_.get(), kStunAddr1, main_)),
stun_server_2_(
cricket::TestStunServer::Create(ss_.get(), kStunAddr2, main_)) {
stun_server_1_->set_fake_stun_addr(kStunMappedAddr);
stun_server_2_->set_fake_stun_addr(kStunMappedAddr);
rtc::InitializeSSL();
}
static constexpr int pings_per_ip = 3;
void set_expected_result(int result) { result_ = result; }
void CreateProber(rtc::PacketSocketFactory* socket_factory,
std::vector<const rtc::Network*> networks) {
prober_ = std::make_unique<StunProber>(socket_factory, &main_,
std::move(networks));
}
void StartProbing(rtc::PacketSocketFactory* socket_factory,
const std::vector<rtc::SocketAddress>& addrs,
std::vector<const rtc::Network*> networks,
bool shared_socket,
uint16_t interval,
uint16_t pings_per_ip) {
CreateProber(socket_factory, networks);
prober_->Start(addrs, shared_socket, interval, pings_per_ip,
100 /* timeout_ms */,
[this](StunProber* prober, int result) {
StopCallback(prober, result);
});
}
void RunProber(bool shared_mode) {
std::vector<rtc::SocketAddress> addrs;
addrs.push_back(kStunAddr1);
addrs.push_back(kStunAddr2);
// Add a non-existing server. This shouldn't pollute the result.
addrs.push_back(kFailedStunAddr);
RunProber(shared_mode, addrs, /* check_results= */ true);
}
void RunProber(bool shared_mode,
const std::vector<rtc::SocketAddress>& addrs,
bool check_results) {
rtc::Network ipv4_network1("test_eth0", "Test Network Adapter 1",
rtc::IPAddress(0x12345600U), 24);
ipv4_network1.AddIP(rtc::IPAddress(0x12345678));
std::vector<const rtc::Network*> networks;
networks.push_back(&ipv4_network1);
auto socket_factory =
std::make_unique<rtc::BasicPacketSocketFactory>(ss_.get());
// Set up the expected results for verification.
std::set<std::string> srflx_addresses;
srflx_addresses.insert(kStunMappedAddr.ToString());
const uint32_t total_pings_tried =
static_cast<uint32_t>(pings_per_ip * addrs.size());
// The reported total_pings should not count for pings sent to the
// kFailedStunAddr.
const uint32_t total_pings_reported = total_pings_tried - pings_per_ip;
StartProbing(socket_factory.get(), addrs, std::move(networks), shared_mode,
3, pings_per_ip);
WAIT(stopped_, 1000);
EXPECT_TRUE(prober_->GetStats(&stats_));
if (check_results) {
EXPECT_EQ(stats_.success_percent, 100);
EXPECT_TRUE(stats_.nat_type > stunprober::NATTYPE_NONE);
EXPECT_EQ(stats_.srflx_addrs, srflx_addresses);
EXPECT_EQ(static_cast<uint32_t>(stats_.num_request_sent),
total_pings_reported);
EXPECT_EQ(static_cast<uint32_t>(stats_.num_response_received),
total_pings_reported);
}
}
StunProber* prober() { return prober_.get(); }
StunProber::Stats& stats() { return stats_; }
private:
void StopCallback(StunProber* prober, int result) {
EXPECT_EQ(result, result_);
stopped_ = true;
}
std::unique_ptr<rtc::VirtualSocketServer> ss_;
rtc::AutoSocketServerThread main_;
std::unique_ptr<StunProber> prober_;
int result_ = 0;
bool stopped_ = false;
cricket::TestStunServer::StunServerPtr stun_server_1_;
cricket::TestStunServer::StunServerPtr stun_server_2_;
StunProber::Stats stats_;
};
TEST_F(StunProberTest, NonSharedMode) {
RunProber(false);
}
TEST_F(StunProberTest, SharedMode) {
RunProber(true);
}
TEST_F(StunProberTest, ResolveNonexistentHostname) {
std::vector<rtc::SocketAddress> addrs;
addrs.push_back(kStunAddr1);
// Add a non-existing server by name. This should cause a failed lookup.
addrs.push_back(rtc::SocketAddress("nonexistent.test", 3478));
RunProber(false, addrs, false);
// One server is pinged
EXPECT_EQ(stats().raw_num_request_sent, pings_per_ip);
}
TEST_F(StunProberTest, ResolveExistingHostname) {
std::vector<rtc::SocketAddress> addrs;
addrs.push_back(kStunAddr1);
// Add a non-existing server by name. This should cause a failed lookup.
addrs.push_back(rtc::SocketAddress("localhost", 3478));
RunProber(false, addrs, false);
// Two servers are pinged, only one responds.
// TODO(bugs.webrtc.org/15559): Figure out why this doesn't always work
// EXPECT_EQ(stats().raw_num_request_sent, pings_per_ip * 2);
EXPECT_EQ(stats().num_request_sent, pings_per_ip);
}
} // namespace stunprober