blob: 331b5bcbc10d32f0acbc35e3787a8eefe9023db5 [file] [log] [blame] [edit]
// 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/route.h"
#include <optional>
#include <string_view>
#include <vector>
#include <base/containers/fixed_flat_map.h>
#include <base/containers/span.h>
#include <base/logging.h>
#include <base/strings/string_split.h>
#include <chromeos/net-base/ip_address.h>
#include <chromeos/net-base/ip_address_utils.h>
namespace routing_simulator {
namespace {
// Sets the value to the |destination_prefix| and returns true if the format of
// the prefix in the string form of a route entry in ip route is valid. If the
// format is invalid, returns false. For example, if an input string is
// "192.25.25.0/24 dev eth0" as input, sets |destination_prefix| to
// "192.25.25.0/24". Another example is "default via 100.87.84.254 dev eth0
// table 1002 metric "65536", sets |destination_prefix| to "0.0.0.0/0".
bool GetPrefix(const std::string_view prefix_token,
net_base::IPFamily ip_family,
net_base::IPCIDR* destination_prefix) {
if (prefix_token == "default") {
*destination_prefix = net_base::IPCIDR(ip_family);
return true;
}
const auto prefix = net_base::IPCIDR::CreateFromCIDRString(prefix_token);
if (!prefix) {
return false;
}
*destination_prefix = *prefix;
return true;
}
// Sets the value to |table_id| and returns true if the format of the table id
// in the string form of a route entry in ip route is valid. For example, if an
// input string is "default via 100.86.211.254 dev wlan0 table 1003 metric
// 65536" sets |table_id| to "1003". If there is no "table" identifier, set the
// default table id "main". If the format is invalid, returns false.
bool GetTableId(const std::vector<std::string_view>& route_tokens,
std::string* table_id) {
static constexpr std::string_view kTableIdIdentifier = "table";
const auto it =
std::find(route_tokens.begin(), route_tokens.end(), kTableIdIdentifier);
if (it == route_tokens.end()) {
*table_id = "main";
return true;
}
if (it + 1 == route_tokens.end()) {
return false;
}
*table_id = std::string(*(it + 1));
return true;
}
// Sets the value to |next_hop| and returns true if the format of the next hop
// in the string form of a route entry in ip route is valid. For example, if an
// input string is "default via 100.86.211.254 dev wlan0 table 1003 metric
// 65536", sets |next_hop| to "100.86.211.254". If the format is invalid,
// returns false.
bool GetNextHop(const std::vector<std::string_view>& route_tokens,
std::optional<net_base::IPAddress>* next_hop) {
static constexpr std::string_view kNextHopIdentifier = "via";
const auto it =
std::find(route_tokens.begin(), route_tokens.end(), kNextHopIdentifier);
if (it == route_tokens.end()) {
*next_hop = std::nullopt;
return true;
}
if (it + 1 == route_tokens.end()) {
return false;
}
const auto prefix = net_base::IPAddress::CreateFromString(*(it + 1));
if (!prefix) {
return false;
}
*next_hop = prefix;
return true;
}
// Sets the value to |output_interface| and returns true if the format of the
// output interface in the string form of a route entry in ip route is valid .
// For example, if an input string is "192.25.25.0/24 dev eth0" sets
// |output_interface| to "eth0". If the format is invalid, returns false.
bool GetOutputInterface(const std::vector<std::string_view>& route_tokens,
std::string* output_interface) {
static constexpr std::string_view kOutputInterfaceIdentifier = "dev";
const auto it = std::find(route_tokens.begin(), route_tokens.end(),
kOutputInterfaceIdentifier);
if (it == route_tokens.end()) {
*output_interface = "";
return true;
}
if (it + 1 == route_tokens.end()) {
return false;
}
*output_interface = *(it + 1);
return true;
}
// Returns the route type in the string form of a route entry in ip route.
// Parses the route type string. Returns std::nullopt is the input string is not
// a valid route type.
std::optional<Route::Type> ParseRouteType(std::string_view type) {
static constexpr auto kStrToRouteType =
base::MakeFixedFlatMap<std::string_view, Route::Type>({
{"unicast", Route::Type::kUnicast},
{"broadcast", Route::Type::kBroadcast},
{"anycast", Route::Type::kAnycast},
{"local", Route::Type::kLocal},
{"blackhole", Route::Type::kBlackhole},
{"unreachable", Route::Type::kUnreachable},
{"prohibit", Route::Type::kProhibit},
{"throw", Route::Type::kThrow},
{"multicast", Route::Type::kMulticast},
});
const auto it = kStrToRouteType.find(type);
if (it == kStrToRouteType.end()) {
return std::nullopt;
}
return it->second;
}
} // namespace
// static
// TODO(b/307460180): Refactor the code using for loop for each token and
// combine helper functions for each identifier into one function. In addition
// to that, add a check if some identifiers exist adjacently.
// (e.g. "192.25.25.0/24 dev table main").
std::optional<Route> Route::CreateFromRouteString(
std::string_view route_string, net_base::IPFamily ip_family) {
const auto route_tokens = base::SplitStringPiece(
route_string, " \t", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
auto type = Type::kUnicast;
// Check if the first component of the route_tokens is the route type or
// not.
if (route_tokens.size() == 0) {
LOG(ERROR) << "No input";
return std::nullopt;
}
base::span<const std::string_view> route_tokens_span = route_tokens;
const auto route_type = ParseRouteType(route_tokens_span[0]);
if (route_type) {
route_tokens_span = route_tokens_span.subspan(1);
type = route_type.value();
}
Route route(type, ip_family);
if (route_tokens_span.size() == 0) {
LOG(ERROR) << "There is only route type in " << route_string;
return std::nullopt;
}
// Check if a route_tokens includes necessary components.
if (!GetPrefix(route_tokens_span[0], ip_family, &route.destination_prefix_)) {
LOG(ERROR) << "Failed to parse prefix in " << route_string;
return std::nullopt;
}
if (!GetTableId(route_tokens, &route.table_id_)) {
LOG(ERROR) << "Failed to parse table id in " << route_string;
return std::nullopt;
}
// Set the value to the corresponding field of a optional member.
if (!GetOutputInterface(route_tokens, &route.output_interface_)) {
LOG(ERROR) << "Failed to parse output interface in " << route_string;
return std::nullopt;
}
if (!GetNextHop(route_tokens, &route.next_hop_)) {
LOG(ERROR) << "Failed to parse next hop in " << route_string;
return std::nullopt;
}
route.route_str_ = route_string;
return route;
}
Route::Route(const Route& other) = default;
Route& Route::operator=(const Route& other) = default;
bool Route::operator==(const Route& rhs) const = default;
Route::Route(Type type, net_base::IPFamily ip_family)
: destination_prefix_(ip_family), type_(type) {}
} // namespace routing_simulator