blob: ac23deb46349909a55323ccb0a331b913201aedb [file] [log] [blame]
// Copyright 2018 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/chromeos/smb_client/discovery/netbios_host_locator.h"
#include <memory>
#include "base/strings/string_util.h"
#include "base/time/time.h"
#include "chrome/browser/chromeos/smb_client/smb_constants.h"
#include "net/base/ip_endpoint.h"
#include "net/base/network_change_notifier.h"
namespace chromeos {
namespace smb_client {
namespace {
bool IsMLan(const net::NetworkInterface& interface) {
return interface.type == net::NetworkChangeNotifier::CONNECTION_UNKNOWN &&
base::StartsWith(interface.name, "mlan",
base::CompareCase::INSENSITIVE_ASCII);
}
} // namespace
net::IPAddress CalculateBroadcastAddress(
const net::NetworkInterface& interface) {
net::IPAddress internet_address = interface.address;
const uint32_t inverted_net_mask = 0xffffffff >> interface.prefix_length;
net::IPAddressBytes bytes = internet_address.bytes();
uint8_t b0 = bytes[0] | (inverted_net_mask >> 24);
uint8_t b1 = bytes[1] | (inverted_net_mask >> 16);
uint8_t b2 = bytes[2] | (inverted_net_mask >> 8);
uint8_t b3 = bytes[3] | (inverted_net_mask);
net::IPAddress broadcast_address(b0, b1, b2, b3);
return broadcast_address;
}
// TODO(baileyberro): Some devices' wifi interface has CONNECTION_UNKNOWN as
// type rather than CONNECTION_WIFI. https://crbug.com/872665
bool ShouldUseInterface(const net::NetworkInterface& interface) {
return interface.address.IsIPv4() &&
(interface.type == net::NetworkChangeNotifier::CONNECTION_ETHERNET ||
interface.type == net::NetworkChangeNotifier::CONNECTION_WIFI ||
IsMLan(interface));
}
NetBiosHostLocator::NetBiosHostLocator(GetInterfacesFunction get_interfaces,
NetBiosClientFactory client_factory,
SmbProviderClient* smb_provider_client)
: NetBiosHostLocator(get_interfaces,
client_factory,
smb_provider_client,
std::make_unique<base::OneShotTimer>()) {}
NetBiosHostLocator::NetBiosHostLocator(
GetInterfacesFunction get_interfaces,
NetBiosClientFactory client_factory,
SmbProviderClient* smb_provider_client,
std::unique_ptr<base::OneShotTimer> timer)
: get_interfaces_(std::move(get_interfaces)),
client_factory_(std::move(client_factory)),
smb_provider_client_(std::move(smb_provider_client)),
timer_(std::move(timer)) {}
NetBiosHostLocator::~NetBiosHostLocator() = default;
void NetBiosHostLocator::FindHosts(FindHostsCallback callback) {
DCHECK(!running_);
DCHECK(callback);
callback_ = std::move(callback);
running_ = true;
net::NetworkInterfaceList network_interface_list = GetNetworkInterfaceList();
for (const auto& interface : network_interface_list) {
if (ShouldUseInterface(interface)) {
FindHostsOnInterface(interface);
}
}
if (netbios_clients_.empty()) {
// No NetBiosClients were created since there were either no interfaces or
// no valid interfaces.
running_ = false;
std::move(callback_).Run(false /* success */, results_);
return;
}
timer_->Start(FROM_HERE,
base::TimeDelta::FromSeconds(kNetBiosDiscoveryTimeoutSeconds),
this, &NetBiosHostLocator::StopDiscovery);
}
net::NetworkInterfaceList NetBiosHostLocator::GetNetworkInterfaceList() {
return get_interfaces_.Run();
}
void NetBiosHostLocator::FindHostsOnInterface(
const net::NetworkInterface& interface) {
net::IPAddress broadcast_address = CalculateBroadcastAddress(interface);
netbios_clients_.push_back(CreateClient());
ExecuteNameRequest(broadcast_address);
}
std::unique_ptr<NetBiosClientInterface> NetBiosHostLocator::CreateClient()
const {
return client_factory_.Run();
}
void NetBiosHostLocator::ExecuteNameRequest(
const net::IPAddress& broadcast_address) {
netbios_clients_.back()->ExecuteNameRequest(
broadcast_address, transaction_id_++,
base::BindRepeating(&NetBiosHostLocator::PacketReceived,
base::Unretained(this)));
}
void NetBiosHostLocator::PacketReceived(const std::vector<uint8_t>& packet,
uint16_t transaction_id,
const net::IPEndPoint& sender_ip) {
if (discovery_done_) {
// Avoids race condition where this callback is called after the timer has
// expired.
return;
}
++outstanding_parse_requests_;
smb_provider_client_->ParseNetBiosPacket(
packet, transaction_id,
base::BindOnce(&NetBiosHostLocator::OnPacketParsed, AsWeakPtr(),
sender_ip));
}
void NetBiosHostLocator::OnPacketParsed(
const net::IPEndPoint& sender_ip,
const std::vector<std::string>& hostnames) {
DCHECK_GE(outstanding_parse_requests_, 0);
--outstanding_parse_requests_;
for (const auto& hostname : hostnames) {
AddHostToResult(sender_ip, hostname);
}
if (discovery_done_ && outstanding_parse_requests_ == 0) {
FinishFindHosts();
}
}
void NetBiosHostLocator::StopDiscovery() {
DCHECK(!discovery_done_);
discovery_done_ = true;
netbios_clients_.clear();
if (outstanding_parse_requests_ == 0) {
FinishFindHosts();
}
}
void NetBiosHostLocator::FinishFindHosts() {
std::move(callback_).Run(true /* success */, results_);
ResetHostLocator();
}
void NetBiosHostLocator::ResetHostLocator() {
DCHECK_EQ(0, outstanding_parse_requests_);
DCHECK(netbios_clients_.empty());
results_.clear();
discovery_done_ = false;
running_ = false;
}
void NetBiosHostLocator::AddHostToResult(const net::IPEndPoint& sender_ip,
const std::string& hostname) {
if (WouldOverwriteResult(sender_ip, hostname)) {
LOG(ERROR) << hostname << ":" << results_[hostname]
<< " will be overwritten by " << hostname << ":"
<< sender_ip.ToStringWithoutPort();
}
results_[hostname] = sender_ip.ToStringWithoutPort();
}
bool NetBiosHostLocator::WouldOverwriteResult(
const net::IPEndPoint& sender_ip,
const std::string& hostname) const {
return results_.count(hostname) &&
results_.at(hostname) != sender_ip.ToStringWithoutPort();
}
} // namespace smb_client
} // namespace chromeos