blob: 6cd30ffca1ca84440f94962182acfcd76c187372 [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_client.h"
#include "chromeos/network/firewall_hole.h"
#include "net/base/ip_endpoint.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "services/network/public/mojom/network_context.mojom.h"
namespace chromeos {
namespace smb_client {
namespace {
constexpr int kNetBiosPort = 137;
constexpr net::NetworkTrafficAnnotationTag GetNetworkTrafficAnnotationTag() {
return net::DefineNetworkTrafficAnnotation("smb_netbios_name_query", R"(
semantics {
sender: "Native SMB for ChromeOS"
description:
"Performs a NETBIOS Name Query Request on the network to find "
"discoverable file shares."
trigger: " Starting the File Share mount process."
data:
"A NETBIOS Name Query Request packet as defined by "
"RFC 1002 Section 4.2.12."
destination: OTHER
destination_other:
"Data is sent to the broadcast_address of the local network."
}
policy {
cookies_allowed: NO
setting:
"No settings control. This request will not be sent if the user "
"does not attempt to mount a Network File Share."
chrome_policy: {
NetBiosShareDiscoveryEnabled {
NetBiosShareDiscoveryEnabled: false
}
}
})");
}
} // namespace
NetBiosClient::NetBiosClient(network::mojom::NetworkContext* network_context)
: bind_address_(net::IPAddress::IPv4AllZeros(), 0 /* port */),
receiver_binding_(this) {
DCHECK(network_context);
network::mojom::UDPSocketReceiverPtr rec_int_ptr;
receiver_binding_.Bind(mojo::MakeRequest(&rec_int_ptr));
network_context->CreateUDPSocket(mojo::MakeRequest(&server_socket_),
std::move(rec_int_ptr));
}
NetBiosClient::~NetBiosClient() = default;
void NetBiosClient::ExecuteNameRequest(const net::IPAddress& broadcast_address,
uint16_t transaction_id,
NetBiosResponseCallback callback) {
DCHECK(!executed_);
DCHECK(callback);
broadcast_address_ = net::IPEndPoint(broadcast_address, kNetBiosPort);
transaction_id_ = transaction_id;
callback_ = callback;
executed_ = true;
BindSocket();
}
void NetBiosClient::BindSocket() {
server_socket_->Bind(
bind_address_, nullptr /* socket_options */,
base::BindOnce(&NetBiosClient::OnBindComplete, AsWeakPtr()));
}
void NetBiosClient::OpenPort(uint16_t port) {
FirewallHole::Open(
FirewallHole::PortType::UDP, port, "" /* all interfaces */,
base::BindRepeating(&NetBiosClient::OnOpenPortComplete, AsWeakPtr()));
}
void NetBiosClient::SetBroadcast() {
server_socket_->SetBroadcast(
true /* broadcast */,
base::BindOnce(&NetBiosClient::OnSetBroadcastCompleted, AsWeakPtr()));
}
void NetBiosClient::SendPacket() {
server_socket_->SendTo(
broadcast_address_, GenerateBroadcastPacket(),
net::MutableNetworkTrafficAnnotationTag(GetNetworkTrafficAnnotationTag()),
base::BindOnce(&NetBiosClient::OnSendCompleted, AsWeakPtr()));
}
void NetBiosClient::OnBindComplete(
int32_t result,
const base::Optional<net::IPEndPoint>& local_ip) {
if (result != net::OK) {
LOG(ERROR) << "NetBiosClient: Binding socket failed: " << result;
return;
}
OpenPort(local_ip.value().port());
}
void NetBiosClient::OnOpenPortComplete(
std::unique_ptr<FirewallHole> firewall_hole) {
if (!firewall_hole) {
LOG(ERROR) << "NetBiosClient: Opening port failed.";
return;
}
firewall_hole_ = std::move(firewall_hole);
SetBroadcast();
}
void NetBiosClient::OnSetBroadcastCompleted(int32_t result) {
if (result != net::OK) {
LOG(ERROR) << "NetBiosClient: SetBroadcast failed: " << result;
return;
}
SendPacket();
}
void NetBiosClient::OnSendCompleted(int32_t result) {
if (result != net::OK) {
LOG(ERROR) << "NetBiosClient: Send failed: " << result;
return;
}
server_socket_->ReceiveMore(1);
}
void NetBiosClient::OnReceived(int32_t result,
const base::Optional<net::IPEndPoint>& src_ip,
base::Optional<base::span<const uint8_t>> data) {
if (result != net::OK) {
LOG(ERROR) << "NetBiosClient: Receive failed: " << result;
return;
}
DCHECK(data);
std::vector<uint8_t> response_packet(data->begin(), data->end());
callback_.Run(response_packet, transaction_id_, src_ip.value());
server_socket_->ReceiveMore(1);
}
std::vector<uint8_t> NetBiosClient::GenerateBroadcastPacket() {
// https://tools.ietf.org/html/rfc1002
// Section 4.2.12
// [0-1] - Transaction Id.
// [2-3] - Broadcast Flag.
// [4-5] - Question Count.
// [6-7] - Answer Resource Count.
// [8-9] - Authority Resource Count.
// [10-11] - Additional Resource Count.
// [12] - Length of name. 16 bytes of name encoded to 32 bytes.
// [13-14] - '*' character encodes to 2 bytes.
// [15-44] - Reaming 15 nulls which encode as 30 * 0x41.
// [45] - Length of next segment.
// [46-47] - Question type: Node status.
// [48-49] - Question clasS: Internet.
std::vector<uint8_t> packet = {
0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x20, 0x43, 0x4b, 0x41, 0x41, 0x41, 0x41, 0x41,
0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
0x41, 0x41, 0x41, 0x41, 0x41, 0x00, 0x00, 0x21, 0x00, 0x01};
// Set Transaction ID in Big Endian representation.
uint8_t first_byte_tx_id = transaction_id_ >> 8;
uint8_t second_byte_tx_id = transaction_id_ & 0xFF;
packet[0] = first_byte_tx_id;
packet[1] = second_byte_tx_id;
return packet;
}
} // namespace smb_client
} // namespace chromeos