blob: 6dee784e18f81cd62f554f0cc8277996362864b9 [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/sharing/webrtc/ice_config_fetcher.h"
#include "base/bind.h"
#include "base/json/json_reader.h"
#include "base/optional.h"
#include "base/strings/strcat.h"
#include "chrome/services/sharing/public/cpp/sharing_webrtc_metrics.h"
#include "google_apis/google_api_keys.h"
#include "net/base/load_flags.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/simple_url_loader.h"
namespace {
const char kIceConfigApiUrl[] =
"https://networktraversal.googleapis.com/v1alpha/iceconfig?key=";
// Response with 2 ice server configs takes ~1KB. A loose upper bound of 16KB is
// chosen to avoid breaking the flow in case the response has longer URLs in ice
// configs.
constexpr int kMaxBodySize = 16 * 1024;
const net::NetworkTrafficAnnotationTag kTrafficAnnotation =
net::DefineNetworkTrafficAnnotation("ice_config_fetcher", R"(
semantics {
sender: "IceConfigFetcher"
description:
"Fetches ice server configurations for p2p webrtc connection as "
"described in "
"https://www.w3.org/TR/webrtc/#rtciceserver-dictionary."
trigger:
"User uses any Chrome cross-device sharing feature and selects one"
" of their devices to send the data to."
data: "No data is sent in the request."
destination: GOOGLE_OWNED_SERVICE
}
policy {
cookies_allowed: NO
setting:
"Users can disable this behavior by signing out of Chrome."
chrome_policy {
BrowserSignin {
policy_options {mode: MANDATORY}
BrowserSignin: 0
}
}
})");
bool IsLoaderSuccessful(const network::SimpleURLLoader* loader) {
if (!loader || loader->NetError() != net::OK)
return false;
if (!loader->ResponseInfo() || !loader->ResponseInfo()->headers)
return false;
// Success response codes are 2xx.
return (loader->ResponseInfo()->headers->response_code() / 100) == 2;
}
} // namespace
IceConfigFetcher::IceConfigFetcher(
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
: url_loader_factory_(std::move(url_loader_factory)) {}
IceConfigFetcher::~IceConfigFetcher() = default;
void IceConfigFetcher::GetIceServers(IceServerCallback callback) {
url_loader_.reset();
auto resource_request = std::make_unique<network::ResourceRequest>();
resource_request->url =
GURL(base::StrCat({kIceConfigApiUrl, google_apis::GetSharingAPIKey()}));
resource_request->load_flags =
net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE;
resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
resource_request->method = net::HttpRequestHeaders::kPostMethod;
resource_request->headers.SetHeader(net::HttpRequestHeaders::kContentType,
"application/json");
url_loader_ = network::SimpleURLLoader::Create(std::move(resource_request),
kTrafficAnnotation);
url_loader_->DownloadToString(
url_loader_factory_.get(),
base::BindOnce(&IceConfigFetcher::OnIceServersResponse,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)),
kMaxBodySize);
}
void IceConfigFetcher::OnIceServersResponse(
IceServerCallback callback,
std::unique_ptr<std::string> response_body) {
std::vector<sharing::mojom::IceServerPtr> ice_servers;
if (IsLoaderSuccessful(url_loader_.get()) && response_body)
ice_servers = ParseIceConfigJson(*response_body);
sharing::LogWebRtcIceConfigFetched(ice_servers.size());
if (ice_servers.empty())
ice_servers = GetDefaultIceServers();
std::move(callback).Run(std::move(ice_servers));
}
std::vector<sharing::mojom::IceServerPtr> IceConfigFetcher::ParseIceConfigJson(
std::string json) {
std::vector<sharing::mojom::IceServerPtr> ice_servers;
base::Optional<base::Value> response = base::JSONReader::Read(json);
if (!response)
return ice_servers;
base::Value* ice_servers_json = response->FindListKey("iceServers");
if (!ice_servers_json)
return ice_servers;
for (base::Value& server : ice_servers_json->GetList()) {
const base::Value* urls_json = server.FindListKey("urls");
if (!urls_json)
continue;
std::vector<GURL> urls;
for (const base::Value& url_json : urls_json->GetList()) {
std::string url;
if (!url_json.GetAsString(&url))
continue;
urls.emplace_back(url);
}
if (urls.empty())
continue;
sharing::mojom::IceServerPtr ice_server(sharing::mojom::IceServer::New());
ice_server->urls = std::move(urls);
std::string* retrieved_username = server.FindStringKey("username");
if (retrieved_username)
ice_server->username.emplace(std::move(*retrieved_username));
std::string* retrieved_credential = server.FindStringKey("credential");
if (retrieved_credential)
ice_server->credential.emplace(std::move(*retrieved_credential));
ice_servers.push_back(std::move(ice_server));
}
return ice_servers;
}
// static
std::vector<sharing::mojom::IceServerPtr>
IceConfigFetcher::GetDefaultIceServers() {
sharing::mojom::IceServerPtr ice_server(sharing::mojom::IceServer::New());
ice_server->urls.emplace_back("stun:stun.l.google.com:19302");
ice_server->urls.emplace_back("stun:stun1.l.google.com:19302");
ice_server->urls.emplace_back("stun:stun2.l.google.com:19302");
ice_server->urls.emplace_back("stun:stun3.l.google.com:19302");
ice_server->urls.emplace_back("stun:stun4.l.google.com:19302");
std::vector<sharing::mojom::IceServerPtr> default_servers;
default_servers.push_back(std::move(ice_server));
return default_servers;
}