/* * 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 #include #include #include #include #include #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 void IncrementCounterByAddress(std::map* 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 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& 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& requests() { return requests_; } // Whether this Requester has completed all requests. bool Done() { return static_cast(num_request_sent_) == server_ips_.size(); } private: Request* GetRequestByAddress(const rtc::IPAddress& ip); StunProber* prober_; // The socket for this session. std::unique_ptr socket_; // Temporary SocketAddress and buffer for RecvFrom. rtc::SocketAddress addr_; std::unique_ptr response_packet_; std::vector requests_; std::vector 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& 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 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(num_request_sent_) <= server_ips_.size()); } void StunProber::Requester::Request::ProcessResponse( rtc::ArrayView 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 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& 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& 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 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 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 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 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 num_response_per_server; std::map num_request_per_server; for (auto* requester : requesters_) { std::map 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(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((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