blob: b54a1b84006db50531625c5a1938be255f718617 [file] [log] [blame]
// Copyright (c) 2012 The Chromium OS 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 "debugd/src/packet_capture_tool.h"
#include <memory>
#include <string>
#include <unistd.h>
#include <utility>
#include <sys/select.h>
#include <base/bind.h>
#include <base/callback.h>
#include <base/files/file_descriptor_watcher_posix.h>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/strings/string_util.h>
#include "debugd/src/error_utils.h"
#include "debugd/src/helper_utils.h"
#include "debugd/src/process_with_id.h"
#include "debugd/src/variant_utils.h"
#include "policy/device_policy.h"
#include "policy/libpolicy.h"
namespace {
const char kPacketCaptureToolErrorString[] =
"org.chromium.debugd.error.PacketCapture";
bool CreateStatusPipe(base::ScopedFD* read_fd, base::ScopedFD* write_fd) {
int pipe_fd[2];
int ret = pipe(pipe_fd);
if (ret != 0) {
return false;
}
read_fd->reset(pipe_fd[0]);
write_fd->reset(pipe_fd[1]);
return true;
}
// Reads the status from the given file descriptor with a timeout of 3 seconds.
// Returns true if "1" is successfully read from the pipe, returns false
// otherwise.
bool ReadStatusFromPipe(int read_fd) {
fd_set set;
struct timeval timeout;
int rv;
// The helper process (capture_packets.cc) will write "1" to the pipe on
// successful start.
char buff[1];
int len = 1;
FD_ZERO(&set);
FD_SET(read_fd, &set);
// The timeout will be three seconds for read.
timeout.tv_sec = 3;
timeout.tv_usec = 0;
rv = select(read_fd + 1, &set, NULL, NULL, &timeout);
if (rv == -1) {
PLOG(ERROR) << "packet_capture: failed to read from pipe";
return false;
} else if (rv == 0) {
// The read operation didn't complete on time.
return false;
} else {
// The character we read must be "1".
return base::ReadFromFD(read_fd, buff, len) && buff[0] == '1';
}
}
bool ValidateInterfaceName(const std::string& name) {
for (char c : name) {
// These are the only plausible interface name characters.
if (!base::IsAsciiAlpha(c) && !base::IsAsciiDigit(c) && c != '-' &&
c != '_')
return false;
}
return true;
}
bool AddValidatedStringOption(debugd::ProcessWithId* p,
const brillo::VariantDictionary& options,
const std::string& dbus_option,
const std::string& command_line_option,
brillo::ErrorPtr* error) {
std::string name;
switch (debugd::GetOption(options, dbus_option, &name, error)) {
case debugd::ParseResult::NOT_PRESENT:
return true;
case debugd::ParseResult::PARSE_ERROR:
return false;
case debugd::ParseResult::PARSED:
break;
}
if (!ValidateInterfaceName(name)) {
DEBUGD_ADD_ERROR_FMT(error, kPacketCaptureToolErrorString,
"\"%s\" is not a valid interface name", name.c_str());
return false;
}
p->AddStringOption(command_line_option, name);
return true;
}
// Returns true when packet capture is allowed in device. Packet capture is
// allowed in all devices (consumer-owned devices, enterprise-enrolled devices
// and OOBE) by default and can be disabled by the
// DeviceDebugPacketCaptureAllowed policy by the administrator for
// enterprise-enrolled devices.
bool IsDevicePacketCaptureAllowed(brillo::ErrorPtr* error) {
policy::PolicyProvider policy_provider;
// Return true without trying to check the policy if the device is not
// enrolled as unenrolled devices won't have policies and packet capture
// should be available by default. This means packet capture will be
// allowed in consumer-owned devices and in OOBE state.
if (!policy_provider.IsEnterpriseEnrolledDevice()) {
return true;
}
policy_provider.Reload();
// No available policies.
if (!policy_provider.device_policy_is_loaded()) {
DEBUGD_ADD_ERROR(error, kPacketCaptureToolErrorString,
"No device policy available on this device, can't check "
"for packet capture policy setting.");
return false;
}
const policy::DevicePolicy* policy = &policy_provider.GetDevicePolicy();
bool packet_capture_allowed = false;
// Check if packet captures are allowed by policy for the device.
if (!policy->GetDeviceDebugPacketCaptureAllowed(&packet_capture_allowed)) {
// This means policy was not set for the device. Return true since the
// default value of the policy is defined as true in the policy
// documentation.
return true;
}
return packet_capture_allowed;
}
bool CheckDeviceBasedCaptureMode(const brillo::VariantDictionary& options,
brillo::ErrorPtr* error) {
std::string device_value;
// Check if the "device" option exists in options dictionary. It must be
// present in device based capture mode.
if (debugd::GetOption(options, "device", &device_value, error) !=
debugd::ParseResult::PARSED) {
return false;
}
int freq_value;
// Check if the "frequency" option exists in options dictionary. It can't be
// present in device based capture mode.
if (debugd::GetOption(options, "frequency", &freq_value, error) ==
debugd::ParseResult::PARSED) {
return false;
}
std::string frequency_based_options[] = {"ht_location", "vht_width",
"monitor_connection_on"};
// If any of the frequency-based options is present in the arguments, it means
// the capture will be frequency based.
for (const std::string& option : frequency_based_options) {
std::string val;
debugd::ParseResult result =
debugd::GetOption(options, option, &val, error);
if (result == debugd::ParseResult::PARSED) {
return false;
}
}
// If device option is parsed and none of the frequency based option is
// present, it means the capture is on device based mode.
return true;
}
} // namespace
namespace debugd {
// Creates helper process for frequency-based (Layer-2) capture and return the
// process. Returns nullptr if process can't be created.
debugd::ProcessWithId*
PacketCaptureTool::CreateCaptureProcessForFrequencyBasedCapture(
const brillo::VariantDictionary& options,
int output_fd,
brillo::ErrorPtr* error) {
std::string exec_path;
if (!GetHelperPath("capture_utility.sh", &exec_path)) {
DEBUGD_ADD_ERROR(error, kPacketCaptureToolErrorString,
"Unable to get helper path for frequency-based capture.");
return nullptr;
}
debugd::ProcessWithId* p =
CreateProcess(false /* sandboxed */, false /* access_root_mount_ns */);
if (!p) {
DEBUGD_ADD_ERROR(error, kPacketCaptureToolErrorString,
"Failed to create process for device-based capture.");
return nullptr;
}
p->AddArg(exec_path);
if (!AddValidatedStringOption(p, options, "device", "--device", error))
return nullptr;
if (!AddIntOption(p, options, "max_size", "--max-size", error))
return nullptr;
if (!AddIntOption(p, options, "frequency", "--frequency", error))
return nullptr;
if (!AddValidatedStringOption(p, options, "ht_location", "--ht-location",
error))
return nullptr;
if (!AddValidatedStringOption(p, options, "vht_width", "--vht-width", error))
return nullptr;
if (!AddValidatedStringOption(p, options, "monitor_connection_on",
"--monitor-connection-on", error))
return nullptr;
// Pass the output fd of the pcap as a command line option to the child
// process.
p->AddIntOption("--output-file", output_fd);
return p;
}
// Creates helper process for device-based (Layer-3) capture and return the
// process. Returns nullptr if process can't be created.
debugd::ProcessWithId*
PacketCaptureTool::CreateCaptureProcessForDeviceBasedCapture(
const brillo::VariantDictionary& options,
int output_fd,
brillo::ErrorPtr* error) {
std::string exec_path;
if (!GetHelperPath("capture_packets", &exec_path)) {
DEBUGD_ADD_ERROR(error, kPacketCaptureToolErrorString,
"Unable to get helper path for device-based capture.");
return nullptr;
}
ProcessWithId* p =
CreateProcess(false /* sandboxed */, false /* access_root_mount_ns */);
if (!p) {
DEBUGD_ADD_ERROR(error, kPacketCaptureToolErrorString,
"Failed to create process for device-based capture.");
return nullptr;
}
p->AddArg(exec_path);
// capture_packets executable takes three arguments as <device> <output_file>
// <max_size>
std::string device;
// device option must be present and successfully parsed in order to create
// process.
if (debugd::GetOption(options, "device", &device, error) !=
debugd::ParseResult::PARSED) {
DEBUGD_ADD_ERROR(
error, kPacketCaptureToolErrorString,
"Failed to parse required --device option from arguments.");
return nullptr;
}
p->AddArg(device);
p->AddArg(std::to_string(output_fd));
int max_size = 0;
debugd::GetOption(options, "max_size", &max_size, error);
p->AddArg(std::to_string(max_size));
return p;
}
void PacketCaptureTool::OnPacketCaptureStopped(std::string helper_process) {
auto process_info_iter = helper_processes_.find(helper_process);
if (process_info_iter == helper_processes_.end()) {
// Helper process has already been cleaned up. Don't need to do anything.
return;
}
base::OnceClosure callback =
std::move(process_info_iter->second.on_stopped_callback);
helper_processes_.erase(process_info_iter);
std::move(callback).Run();
}
bool PacketCaptureTool::HasActivePacketCaptureProcess() {
return !helper_processes_.empty();
}
bool PacketCaptureTool::Start(bool is_dev_mode,
const base::ScopedFD& status_fd,
const base::ScopedFD& output_fd,
const brillo::VariantDictionary& options,
std::string* out_id,
base::OnceClosure on_stopped_callback,
brillo::ErrorPtr* error) {
if (!IsDevicePacketCaptureAllowed(error)) {
DEBUGD_ADD_ERROR(error, kPacketCaptureToolErrorString,
"Packet capture is not allowed on device. Please check "
"your policy settings to enable.");
return false;
}
ProcessWithId* p;
// The fd in the child that we bind output_fd to. Since all other fd's are
// cleared automatically, picking a hardcoded value should be safe.
int child_output_fd = STDERR_FILENO + 1;
// Check if the capture will be device-based or frequency-based and create
// helper process accordingly using different executables.
// TODO(b/188391723): Merge capture_utility.sh and capture_packets executables
// into one.
if (CheckDeviceBasedCaptureMode(options, error)) {
p = CreateCaptureProcessForDeviceBasedCapture(options, child_output_fd,
error);
} else if (is_dev_mode) {
p = CreateCaptureProcessForFrequencyBasedCapture(options, child_output_fd,
error);
} else {
DEBUGD_ADD_ERROR(
error, kPacketCaptureToolErrorString,
"The requested capture is frequency-based and it's only available in "
"developer mode. Please switch to developer mode to use this option.");
return false;
}
if (!p) {
DEBUGD_ADD_ERROR(error, kPacketCaptureToolErrorString,
"Failed to create helper process.");
return false;
}
// Create a pipe to check the child process state and send the write end of
// the pipe to the child process.
base::ScopedFD write_fd, read_fd;
if (!CreateStatusPipe(&read_fd, &write_fd)) {
DEBUGD_ADD_ERROR(error, kPacketCaptureToolErrorString,
"Cannot create a pipe");
return false;
}
p->AddArg(std::to_string(write_fd.get()));
p->BindFd(output_fd.get(), child_output_fd);
p->BindFd(status_fd.get(), STDOUT_FILENO);
p->BindFd(status_fd.get(), STDERR_FILENO);
p->BindFd(write_fd.get(), write_fd.get());
LOG(INFO) << "packet_capture: running process id: " << p->id();
p->Start();
// Read the helper process status from the pipe and check if it was
// successful.
if (!ReadStatusFromPipe(read_fd.get())) {
DEBUGD_ADD_ERROR(error, kPacketCaptureToolErrorString,
"Packet capture helper process failed to start.");
return false;
}
// Watch the read end of the pipe. Since we read from the pipe already, the
// pipe will be readable again when the helper process closes the pipe. It
// means the packet capture has stopped.
std::unique_ptr<base::FileDescriptorWatcher::Controller> fd_watcher =
base::FileDescriptorWatcher::WatchReadable(
read_fd.get(),
base::BindRepeating(&PacketCaptureTool::OnPacketCaptureStopped,
base::Unretained(this), p->id()));
helper_processes_.insert(std::make_pair(
p->id(), ChildProcessInfo(std::move(read_fd), std::move(fd_watcher),
std::move(on_stopped_callback))));
*out_id = p->id();
return true;
}
} // namespace debugd