
Chrome will only see stunprober.h and stunprobercontext.h and link with libstunprober.a. It has support for shared and non-shared mode. In shared mode, a socket will be used to ping all resolved IPs once. In non-shared mode, each ping will get a new socket. The thread scheduling will try to run MaybeScheduleStunRequest every 1 ms. When the time is up for next ping, it'll send it out. BUG=4576 R=pthatcher@webrtc.org Review URL: https://webrtc-codereview.appspot.com/51729004 Cr-Commit-Position: refs/heads/master@{#9194}
410 lines
11 KiB
C++
410 lines
11 KiB
C++
/*
|
|
* 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 <iostream>
|
|
#include <set>
|
|
#include <string>
|
|
|
|
#include "webrtc/base/bind.h"
|
|
#include "webrtc/base/checks.h"
|
|
#include "webrtc/base/helpers.h"
|
|
#include "webrtc/base/timeutils.h"
|
|
#include "webrtc/p2p/base/stun.h"
|
|
#include "webrtc/p2p/stunprober/stunprober.h"
|
|
|
|
namespace stunprober {
|
|
|
|
StunProber::Requester::Requester(StunProber* prober,
|
|
ServerSocketInterface* socket,
|
|
const std::vector<rtc::IPAddress> server_ips,
|
|
uint16 port)
|
|
: prober_(prober),
|
|
socket_(socket),
|
|
response_packet_(new rtc::ByteBuffer(nullptr, kMaxUdpBufferSize)),
|
|
server_ips_(server_ips),
|
|
port_(port),
|
|
thread_checker_(prober->thread_checker_) {
|
|
}
|
|
|
|
StunProber::Requester::~Requester() {
|
|
if (socket_) {
|
|
socket_->Close();
|
|
}
|
|
for (auto req : requests_) {
|
|
if (req) {
|
|
delete req;
|
|
}
|
|
}
|
|
}
|
|
|
|
void StunProber::Requester::SendStunRequest() {
|
|
DCHECK(thread_checker_.CalledOnValidThread());
|
|
requests_.push_back(new Request());
|
|
Request& request = *(requests_.back());
|
|
cricket::StunMessage message;
|
|
|
|
// Random transaction ID, STUN_BINDING_REQUEST
|
|
message.SetTransactionID(
|
|
rtc::CreateRandomString(cricket::kStunTransactionIdLength));
|
|
message.SetType(cricket::STUN_BINDING_REQUEST);
|
|
|
|
rtc::scoped_ptr<rtc::ByteBuffer> request_packet(
|
|
new rtc::ByteBuffer(nullptr, kMaxUdpBufferSize));
|
|
if (!message.Write(request_packet.get())) {
|
|
prober_->End(WRITE_FAILED, 0);
|
|
return;
|
|
}
|
|
|
|
auto addr = rtc::SocketAddress(server_ips_[num_request_sent_], port_);
|
|
request.server_addr = addr.ipaddr();
|
|
|
|
int rv = 0;
|
|
|
|
// Only bind to the interface at the first request.
|
|
if (num_request_sent_ == 0) {
|
|
rtc::IPAddress local_addr;
|
|
rv = prober_->GetLocalAddress(&local_addr);
|
|
if (rv != 0) {
|
|
prober_->End(GENERIC_FAILURE, rv);
|
|
return;
|
|
}
|
|
rv = socket_->Bind(rtc::SocketAddress(local_addr, 0));
|
|
if (rv < 0) {
|
|
prober_->End(GENERIC_FAILURE, rv);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// The write must succeed immediately. Otherwise, the calculating of the STUN
|
|
// request timing could become too complicated. Callback is ignored by passing
|
|
// empty AsyncCallback.
|
|
rv = socket_->SendTo(addr, const_cast<char*>(request_packet->Data()),
|
|
request_packet->Length(), AsyncCallback());
|
|
if (rv < 0) {
|
|
prober_->End(WRITE_FAILED, rv);
|
|
return;
|
|
}
|
|
|
|
request.sent_time_ns = rtc::Time();
|
|
|
|
// Post a read waiting for response. For share mode, the subsequent read will
|
|
// be posted inside OnStunResponseReceived.
|
|
if (num_request_sent_ == 0) {
|
|
ReadStunResponse();
|
|
}
|
|
|
|
num_request_sent_++;
|
|
DCHECK(static_cast<size_t>(num_request_sent_) <= server_ips_.size());
|
|
}
|
|
|
|
void StunProber::Requester::ReadStunResponse() {
|
|
DCHECK(thread_checker_.CalledOnValidThread());
|
|
if (!socket_) {
|
|
return;
|
|
}
|
|
|
|
int rv = socket_->RecvFrom(
|
|
response_packet_->ReserveWriteBuffer(kMaxUdpBufferSize),
|
|
kMaxUdpBufferSize, &addr_,
|
|
[this](int result) { this->OnStunResponseReceived(result); });
|
|
if (rv != SocketInterface::IO_PENDING) {
|
|
OnStunResponseReceived(rv);
|
|
}
|
|
}
|
|
|
|
void StunProber::Requester::Request::ProcessResponse(
|
|
rtc::ByteBuffer* message,
|
|
int buf_len,
|
|
const rtc::IPAddress& local_addr) {
|
|
int64 now = rtc::Time();
|
|
|
|
cricket::StunMessage stun_response;
|
|
if (!stun_response.Read(message)) {
|
|
// Invalid or incomplete STUN packet.
|
|
received_time_ns = 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_ns = now;
|
|
|
|
srflx_ip = addr_attr->ipaddr().ToString();
|
|
|
|
// Calculate behind_nat.
|
|
behind_nat = (srflx_ip.compare(local_addr.ToString()) != 0);
|
|
}
|
|
|
|
void StunProber::Requester::OnStunResponseReceived(int result) {
|
|
DCHECK(thread_checker_.CalledOnValidThread());
|
|
DCHECK(socket_);
|
|
|
|
if (result < 0) {
|
|
// Something is wrong, finish the test.
|
|
prober_->End(READ_FAILED, result);
|
|
return;
|
|
}
|
|
|
|
Request* request = GetRequestByAddress(addr_.ipaddr());
|
|
if (!request) {
|
|
// Something is wrong, finish the test.
|
|
prober_->End(GENERIC_FAILURE, result);
|
|
return;
|
|
}
|
|
|
|
num_response_received_++;
|
|
|
|
// Resize will set the end_ to indicate that there are data available in this
|
|
// ByteBuffer.
|
|
response_packet_->Resize(result);
|
|
request->ProcessResponse(response_packet_.get(), result,
|
|
prober_->local_addr_);
|
|
|
|
if (static_cast<size_t>(num_response_received_) < server_ips_.size()) {
|
|
// Post another read response.
|
|
ReadStunResponse();
|
|
}
|
|
}
|
|
|
|
StunProber::Requester::Request* StunProber::Requester::GetRequestByAddress(
|
|
const rtc::IPAddress& ipaddr) {
|
|
DCHECK(thread_checker_.CalledOnValidThread());
|
|
for (auto request : requests_) {
|
|
if (request->server_addr == ipaddr) {
|
|
return request;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
StunProber::StunProber(HostNameResolverInterface* host_name_resolver,
|
|
SocketFactoryInterface* socket_factory,
|
|
TaskRunnerInterface* task_runner)
|
|
: interval_ms_(0),
|
|
socket_factory_(socket_factory),
|
|
resolver_(host_name_resolver),
|
|
task_runner_(task_runner) {
|
|
}
|
|
|
|
StunProber::~StunProber() {
|
|
for (auto req : requesters_) {
|
|
if (req) {
|
|
delete req;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool StunProber::Start(const std::string& server,
|
|
uint16 port,
|
|
bool shared_socket_mode,
|
|
int interval_ms,
|
|
int num_request_per_ip,
|
|
int timeout_ms,
|
|
const AsyncCallback callback) {
|
|
DCHECK(thread_checker_.CalledOnValidThread());
|
|
interval_ms_ = interval_ms;
|
|
shared_socket_mode_ = shared_socket_mode;
|
|
|
|
requests_per_ip_ = num_request_per_ip;
|
|
if (requests_per_ip_ == 0) {
|
|
return false;
|
|
}
|
|
|
|
timeout_ms_ = timeout_ms;
|
|
server_ = rtc::SocketAddress(server, port);
|
|
finished_callback_ = callback;
|
|
resolver_->Resolve(server_, &server_ips_,
|
|
[this](int result) { this->OnServerResolved(result); });
|
|
return true;
|
|
}
|
|
|
|
void StunProber::OnServerResolved(int result) {
|
|
DCHECK(thread_checker_.CalledOnValidThread());
|
|
if (result != 0 || server_ips_.size() == 0) {
|
|
End(RESOLVE_FAILED, result);
|
|
return;
|
|
}
|
|
|
|
// Dedupe.
|
|
std::set<rtc::IPAddress> addrs(server_ips_.begin(), server_ips_.end());
|
|
server_ips_.assign(addrs.begin(), addrs.end());
|
|
|
|
rtc::IPAddress addr;
|
|
if (GetLocalAddress(&addr) != 0) {
|
|
End(GENERIC_FAILURE, result);
|
|
return;
|
|
}
|
|
|
|
MaybeScheduleStunRequests();
|
|
}
|
|
|
|
int StunProber::GetLocalAddress(rtc::IPAddress* addr) {
|
|
DCHECK(thread_checker_.CalledOnValidThread());
|
|
if (local_addr_.family() == AF_UNSPEC) {
|
|
rtc::SocketAddress sock_addr;
|
|
rtc::scoped_ptr<ClientSocketInterface> socket(
|
|
socket_factory_->CreateClientSocket());
|
|
int rv =
|
|
socket->Connect(rtc::SocketAddress(server_ips_[0], server_.port()));
|
|
if (rv != SUCCESS) {
|
|
End(GENERIC_FAILURE, rv);
|
|
return rv;
|
|
}
|
|
rv = socket->GetLocalAddress(&sock_addr);
|
|
if (rv != SUCCESS) {
|
|
End(GENERIC_FAILURE, rv);
|
|
return rv;
|
|
}
|
|
local_addr_ = sock_addr.ipaddr();
|
|
socket->Close();
|
|
}
|
|
*addr = local_addr_;
|
|
return 0;
|
|
}
|
|
|
|
StunProber::Requester* StunProber::CreateRequester() {
|
|
DCHECK(thread_checker_.CalledOnValidThread());
|
|
rtc::scoped_ptr<ServerSocketInterface> socket(
|
|
socket_factory_->CreateServerSocket(kMaxUdpBufferSize,
|
|
kMaxUdpBufferSize));
|
|
if (!socket) {
|
|
return nullptr;
|
|
}
|
|
if (shared_socket_mode_) {
|
|
return new Requester(this, socket.release(), server_ips_, server_.port());
|
|
} else {
|
|
std::vector<rtc::IPAddress> server_ip;
|
|
server_ip.push_back(server_ips_[(num_request_sent_ % server_ips_.size())]);
|
|
return new Requester(this, socket.release(), server_ip, server_.port());
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
void StunProber::MaybeScheduleStunRequests() {
|
|
DCHECK(thread_checker_.CalledOnValidThread());
|
|
uint32 now = rtc::Time();
|
|
|
|
if (Done()) {
|
|
task_runner_->PostTask(rtc::Bind(&StunProber::End, this, SUCCESS, 0),
|
|
timeout_ms_);
|
|
return;
|
|
}
|
|
if (now >= next_request_time_ms_) {
|
|
if (!SendNextRequest()) {
|
|
End(GENERIC_FAILURE, 0);
|
|
return;
|
|
}
|
|
next_request_time_ms_ = now + interval_ms_;
|
|
}
|
|
task_runner_->PostTask(
|
|
rtc::Bind(&StunProber::MaybeScheduleStunRequests, this), 1 /* ms */);
|
|
}
|
|
|
|
bool StunProber::GetStats(StunProber::Stats* prob_stats) {
|
|
// No need to be on the same thread.
|
|
if (!prob_stats) {
|
|
return false;
|
|
}
|
|
|
|
StunProber::Stats stats;
|
|
|
|
int num_sent = 0, num_received = 0;
|
|
int rtt_sum = 0;
|
|
bool behind_nat_set = false;
|
|
int64 first_sent_time = 0;
|
|
int64 last_sent_time = 0;
|
|
|
|
for (auto* requester : requesters_) {
|
|
for (auto request : requester->requests()) {
|
|
if (request->sent_time_ns <= 0) {
|
|
continue;
|
|
}
|
|
num_sent++;
|
|
|
|
if (!first_sent_time) {
|
|
first_sent_time = request->sent_time_ns;
|
|
}
|
|
last_sent_time = request->sent_time_ns;
|
|
|
|
if (request->received_time_ns < request->sent_time_ns) {
|
|
continue;
|
|
}
|
|
num_received++;
|
|
rtt_sum += request->rtt();
|
|
if (!behind_nat_set) {
|
|
stats.behind_nat = request->behind_nat;
|
|
behind_nat_set = true;
|
|
} else if (stats.behind_nat != request->behind_nat) {
|
|
// Detect the inconsistency in NAT presence.
|
|
return false;
|
|
}
|
|
stats.srflx_ips.insert(request->srflx_ip);
|
|
}
|
|
}
|
|
|
|
stats.host_ip = local_addr_.ToString();
|
|
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 (num_sent > 1) {
|
|
stats.actual_request_interval_ns =
|
|
(100 * (last_sent_time - first_sent_time)) / (num_sent - 1);
|
|
}
|
|
|
|
if (num_received) {
|
|
stats.average_rtt_ms = static_cast<int>((rtt_sum / num_received));
|
|
}
|
|
|
|
*prob_stats = stats;
|
|
return true;
|
|
}
|
|
|
|
void StunProber::End(StunProber::Status status, int result) {
|
|
DCHECK(thread_checker_.CalledOnValidThread());
|
|
if (!finished_callback_.empty()) {
|
|
AsyncCallback callback = finished_callback_;
|
|
finished_callback_ = AsyncCallback();
|
|
|
|
// Callback at the last since the prober might be deleted in the callback.
|
|
callback(status);
|
|
}
|
|
}
|
|
|
|
} // namespace stunprober
|