blob: a7624d0f5169e0520093074f91116edfc8d4c181 [file] [log] [blame]
// Copyright 2023 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "routing-simulator/routing_policy_entry.h"
#include <map>
#include <string_view>
#include <utility>
#include <vector>
#include <base/containers/span.h>
#include <base/logging.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_split.h>
#include <chromeos/net-base/ip_address.h>
namespace routing_simulator {
namespace {
// Parse an input string to Fwmark and returns it if the parsing is successful.
// Otherwise, returns std::nullopt.
std::optional<RoutingPolicyEntry::Fwmark> ParseFmwarkWithMask(
std::string_view fwmark_str) {
RoutingPolicyEntry::Fwmark fwmark;
const auto fwmark_tokens = base::SplitStringPiece(
fwmark_str, "/", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
if (fwmark_tokens.size() != 2) {
return std::nullopt;
}
if ((!base::HexStringToUInt(fwmark_tokens[0], &fwmark.mark)) ||
(!base::HexStringToUInt(fwmark_tokens[1], &fwmark.mask))) {
return std::nullopt;
}
return fwmark;
}
} // namespace
bool RoutingPolicyEntry::Fwmark::operator==(const Fwmark& rhs) const {
return ((mark == rhs.mark) && (mask == rhs.mask));
}
// static
std::optional<RoutingPolicyEntry> RoutingPolicyEntry::CreateFromPolicyString(
std::string_view policy_string, net_base::IPFamily ip_family) {
RoutingPolicyEntry policy(ip_family);
const auto policy_tokens = base::SplitStringPiece(
policy_string, " \t", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
base::span<const std::string_view> policy_tokens_span = policy_tokens;
// Check if the first component of |policy_tokens| is a priority.
if (policy_tokens.empty()) {
LOG(ERROR) << "Input is empty";
return std::nullopt;
}
if (!policy.SetPriority(policy_tokens_span[0])) {
LOG(ERROR) << "Parse with priority fails in: " << policy_string;
return std::nullopt;
}
policy_tokens_span = policy_tokens_span.subspan(1);
if (policy_tokens_span.empty()) {
LOG(ERROR) << "There is no string after priority in: " << policy_string;
return std::nullopt;
}
// Check if the second component of |policy_tokens| is a source prefix
// identifier.
if (!policy.SetSourcePrefix(&policy_tokens_span, ip_family)) {
LOG(ERROR) << "Parse with source prefix fails in: " << policy_string;
return std::nullopt;
}
// Parse other components in |policy_tokens_span| and set the value to the
// corresponding member field of a RoutingPolicyEntry object.
if (!policy.SetItems(policy_tokens_span)) {
LOG(ERROR) << "Input strings is not valid: " << policy_string;
return std::nullopt;
}
if (policy.table_id_ == "") {
LOG(ERROR) << "There is no table id in: " << policy_string;
return std::nullopt;
}
policy.policy_str_ = policy_string;
return policy;
}
RoutingPolicyEntry::RoutingPolicyEntry(const RoutingPolicyEntry& other) =
default;
RoutingPolicyEntry& RoutingPolicyEntry::operator=(
const RoutingPolicyEntry& other) = default;
bool RoutingPolicyEntry::operator==(const RoutingPolicyEntry& rhs) const =
default;
// TODO(b/307460180): Change the interface (output/input parameter or returned
// value type) to save the matched policy to the result.
bool RoutingPolicyEntry::Matches(const Packet& packet) const {
if (!source_prefix().InSameSubnetWith(packet.source_ip())) {
return false;
}
if (!output_interface().empty()) {
if (packet.output_interface() != output_interface()) {
return false;
}
}
if (!input_interface().empty()) {
if (packet.input_interface() != input_interface()) {
return false;
}
}
if ((packet.fwmark() & fwmark().mask) != (fwmark().mark & fwmark().mask)) {
return false;
}
return true;
}
RoutingPolicyEntry::RoutingPolicyEntry(net_base::IPFamily ip_family)
: source_prefix_(net_base::IPCIDR(ip_family)) {}
bool RoutingPolicyEntry::SetPriority(std::string_view priority_string) {
const auto priority_token = base::SplitStringPiece(
priority_string, ":", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
// IF a priority token is "1001:", |priority_token| should be like {1001} and
// thus the size of it should be 1.
if (priority_token.size() != 1) {
return false;
}
int priority;
if (!base::StringToInt(priority_token[0], &priority)) {
LOG(ERROR) << "Formats of priority is invalid in: " << priority_token[0];
return false;
}
// |priority| should be the number from 0 to 32767.
if (priority < 0 || priority > 32767) {
LOG(ERROR) << "Priority is out of range (0<=priority<=32767)";
return false;
}
priority_ = priority;
return true;
}
bool RoutingPolicyEntry::SetSourcePrefix(
base::span<const std::string_view>* policy_tokens_span_ptr,
net_base::IPFamily ip_family) {
static constexpr std::string_view kSourcePrefixIdentifier = "from";
if (policy_tokens_span_ptr->front() != kSourcePrefixIdentifier) {
LOG(ERROR) << "There is no source prefix identifier";
return false;
}
*policy_tokens_span_ptr = policy_tokens_span_ptr->subspan(1);
if (policy_tokens_span_ptr->empty()) {
LOG(ERROR) << "No source prefix found after the identifier (from)";
return false;
}
if (policy_tokens_span_ptr->front() == "all") {
source_prefix_ = net_base::IPCIDR(ip_family);
*policy_tokens_span_ptr = policy_tokens_span_ptr->subspan(1);
return true;
}
const auto source_prefix =
net_base::IPCIDR::CreateFromCIDRString(policy_tokens_span_ptr->front());
if (!source_prefix) {
LOG(ERROR) << "Formats of source prefix is invalid in: "
<< policy_tokens_span_ptr->front();
return false;
}
source_prefix_ = *source_prefix;
*policy_tokens_span_ptr = policy_tokens_span_ptr->subspan(1);
return true;
}
bool RoutingPolicyEntry::SetItems(
base::span<const std::string_view> policy_tokens_span) {
static constexpr std::string_view kOutputInterfaceIdentifier = "oif";
static constexpr std::string_view kInputInterfaceIdentifier = "iif";
static constexpr std::string_view kFwmarkIdentifier = "fwmark";
static constexpr std::string_view kTableIdIdentifier = "lookup";
std::string fwmark_str;
// map: {[key]:identifier, [value]: {member name, a pointer to the member}}
// We need a "member name" only for logging.
const std::map<std::string_view, std::pair<std::string_view, std::string*>>
identifier_map = {
{kOutputInterfaceIdentifier,
{"output interface", &output_interface_}},
{kInputInterfaceIdentifier, {"input interface", &input_interface_}},
{kFwmarkIdentifier, {"fwmark", &fwmark_str}},
{kTableIdIdentifier, {"table id", &table_id_}}};
while (!policy_tokens_span.empty()) {
if (auto it = identifier_map.find(policy_tokens_span.front());
it != identifier_map.end()) {
const auto& identifer = it->first;
const auto& name = it->second.first;
auto* member_to_set_ptr = it->second.second;
policy_tokens_span = policy_tokens_span.subspan(1);
if (policy_tokens_span.empty()) {
LOG(ERROR) << "No " << name << " found after the identifier ["
<< identifer << " ]";
return false;
}
if (identifier_map.contains(policy_tokens_span.front())) {
LOG(ERROR) << "Identifiers are next to each other";
return false;
}
*member_to_set_ptr = policy_tokens_span.front();
if (name == kFwmarkIdentifier) {
const auto fwmark = ParseFmwarkWithMask(fwmark_str);
if (!fwmark) {
LOG(ERROR) << "Formats of fwmark is invalid in: " << fwmark_str;
return false;
}
fwmark_ = *fwmark;
}
policy_tokens_span = policy_tokens_span.subspan(1);
} else {
// There is a token without the corresponding identifier before it.
LOG(ERROR) << "Unknown token: " << policy_tokens_span.front();
return false;
}
}
return true;
}
} // namespace routing_simulator