blob: 11f980839967285e5357812f21d409bd27217bee [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// clang-format off
#include <Weave/DeviceLayer/internal/WeaveDeviceLayerInternal.h>
#include <Weave/DeviceLayer/ConnectivityManager.h>
#include <Weave/DeviceLayer/ThreadStackManager.h>
#include <Warm/Warm.h>
// clang-format on
#include <fidl/fuchsia.net.interfaces.admin/cpp/fidl.h>
#include <fidl/fuchsia.net.root/cpp/fidl.h>
#include <fuchsia/net/cpp/fidl.h>
#include <fuchsia/net/interfaces/cpp/fidl.h>
#include <fuchsia/net/stack/cpp/fidl.h>
#include <lib/component/incoming/cpp/protocol.h>
#include <lib/fidl/cpp/wire/channel.h>
#include <lib/syslog/cpp/macros.h>
#include <netinet/ip6.h>
#include <optional>
// ==================== WARM Platform Functions ====================
namespace nl::Weave::Warm::Platform {
namespace {
using DeviceLayer::ConnectivityMgrImpl;
constexpr char kFuchsiaNetRootInterfacesProtocolName[] =
"fuchsia.net.root.Interfaces_OnlyForWeavestack";
// Fixed name for tunnel interface.
constexpr char kTunInterfaceName[] = "weav-tun0";
// Route metric values for primary and backup tunnels. Higher priority tunnels
// have lower metric values so that they are prioritized in the routing table.
constexpr uint32_t kRouteMetric_HighPriority = 0;
constexpr uint32_t kRouteMetric_MediumPriority = 99;
constexpr uint32_t kRouteMetric_LowPriority = 999;
std::optional<std::string> AddressToString(const Inet::IPAddress &address) {
const uint8_t IPV6ADDR_STRLEN_MAX = 46;
char buf[IPV6ADDR_STRLEN_MAX];
if (address.ToString(buf, IPV6ADDR_STRLEN_MAX)) {
return std::string(buf);
} else {
return std::nullopt;
}
}
std::string AddressToStringInfallible(const Inet::IPAddress &address) {
return AddressToString(address).value_or("<UNFORMATTABLE ADDRESS>");
}
class AddressStateProviderRepository {
public:
// Add the `AddressStateProvider` handle to the repository. If an entry
// already exists, it will be overwritten.
PlatformResult Add(
uint64_t interface_id, const Inet::IPAddress &address,
fidl::SyncClient<fuchsia_net_interfaces_admin::AddressStateProvider> asp_client) {
std::optional<std::string> key = GetKey(interface_id, address);
if (!key.has_value()) {
FX_LOGS(ERROR) << "Could not generate a key for address " << AddressToStringInfallible
<< " on interface " << interface_id;
return kPlatformResultFailure;
}
if (repo_.find(*key) != repo_.end()) {
FX_LOGS(WARNING) << "Overwritting existing AddressStateProvider client stored for address "
<< AddressToStringInfallible(address) << " on interface " << interface_id;
}
repo_[*key] =
std::make_unique<fidl::SyncClient<fuchsia_net_interfaces_admin::AddressStateProvider>>(
std::move(asp_client));
return kPlatformResultSuccess;
}
// Erase the `AddressStateProvider` handle from the repository.
// Returns true if the handle was present, false otherwise.
// Note that erase causes the handle to be dropped, which triggers removal
// of the address from the Netstack, if not detached.
bool Erase(uint64_t interface_id, const Inet::IPAddress &address) {
std::optional<std::string> key = GetKey(interface_id, address);
if (!key.has_value()) {
FX_LOGS(ERROR) << "Could not generate a key for address " << AddressToStringInfallible
<< " on interface " << interface_id;
return false;
}
return repo_.erase(*key);
}
private:
// Deterministically converts the given interface_id and address into a unique
// key for the local storage.
// Returns std::nullopt if the address could not be formatted into a key.
std::optional<std::string> GetKey(uint64_t interface_id, const Inet::IPAddress &address) {
std::optional<std::string> addr_str = AddressToString(address);
if (addr_str.has_value()) {
return std::to_string(interface_id) + ":" + *addr_str;
} else {
return std::nullopt;
}
}
// The underlying storage of `AddressStateProvider` handles. Note that the
// `fidl::SyncClient` is wrapped in a unique_ptr, because it cannot be
// assigned to.
std::map<std::string,
std::unique_ptr<fidl::SyncClient<fuchsia_net_interfaces_admin::AddressStateProvider>>>
repo_;
};
// A Global Store of `AddressStateProvider` client-side handles for all
// addresses installed by Weavestack. The `AddressStateProvider` protocol
// expresses strong ownership; storing them here prevents the handles from being
// dropped and consequently prevents the Netstack from removing the addresses.
AddressStateProviderRepository global_asp_repo;
// Returns the interface name associated with the given interface type.
// Unsupported interface types will not populate the optional.
std::optional<std::string> GetInterfaceName(InterfaceType interface_type) {
switch (interface_type) {
#if WARM_CONFIG_SUPPORT_THREAD
case kInterfaceTypeThread:
return ThreadStackMgrImpl().GetInterfaceName();
#endif // WARM_CONFIG_SUPPORT_THREAD
case kInterfaceTypeTunnel:
return kTunInterfaceName;
case kInterfaceTypeWiFi:
return ConnectivityMgrImpl().GetWiFiInterfaceName();
default:
FX_LOGS(ERROR) << "Unknown interface type: " << interface_type;
return std::nullopt;
}
}
// Returns whether IPv6 forwarding is allowed for the given interface type.
bool ShouldEnableV6Forwarding(InterfaceType interface_type) {
return ((DeviceLayer::ConfigurationMgrImpl().IsIPv6ForwardingEnabled()) &&
((interface_type == kInterfaceTypeThread) || (interface_type == kInterfaceTypeTunnel)));
}
// Returns the interface id associated with the given interface name. On
// failures to fetch the list, no value will be returned. When the interface
// does not exist, the interface ID '0' will be returned (it is guaranteed by
// the networking stack that all valid interface IDs are positive).
std::optional<uint64_t> GetInterfaceId(const std::string &interface_name) {
fuchsia::net::interfaces::StateSyncPtr state_sync_ptr;
fuchsia::net::interfaces::WatcherOptions options;
fuchsia::net::interfaces::WatcherSyncPtr watcher_sync_ptr;
auto svc = nl::Weave::DeviceLayer::PlatformMgrImpl().GetComponentContextForProcess()->svc();
zx_status_t status = svc->Connect(state_sync_ptr.NewRequest());
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to bind state protocol: " << zx_status_get_string(status);
return std::nullopt;
}
state_sync_ptr->GetWatcher(std::move(options), watcher_sync_ptr.NewRequest());
if (!watcher_sync_ptr.is_bound()) {
FX_LOGS(ERROR) << "Failed to bind watcher.";
return std::nullopt;
}
fuchsia::net::interfaces::Event event;
do {
status = watcher_sync_ptr->Watch(&event);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to watch for interface event: " << zx_status_get_string(status);
return std::nullopt;
}
// If the entry is not of type `existing`, it is not part of the initial
// list of interfaces that was present when the channel was initialized.
if (!event.is_existing()) {
continue;
}
// Check if the event matches the provided interface name and if so, return
// the interface ID.
if (event.existing().name() == interface_name) {
return event.existing().id();
}
} while (!event.is_idle());
return 0;
}
std::string_view StackErrorToString(fuchsia::net::stack::Error error) {
switch (error) {
case fuchsia::net::stack::Error::INTERNAL:
return "internal";
case fuchsia::net::stack::Error::NOT_SUPPORTED:
return "not supported";
case fuchsia::net::stack::Error::INVALID_ARGS:
return "invalid arguments";
case fuchsia::net::stack::Error::BAD_STATE:
return "bad state";
case fuchsia::net::stack::Error::TIME_OUT:
return "timeout";
case fuchsia::net::stack::Error::NOT_FOUND:
return "not found";
case fuchsia::net::stack::Error::ALREADY_EXISTS:
return "already exists";
case fuchsia::net::stack::Error::IO:
return "i/o";
default:
return "malformed stack error";
}
}
std::string_view AddressRemovalReasonToString(
fuchsia_net_interfaces_admin::AddressRemovalReason reason) {
switch (reason) {
case fuchsia_net_interfaces_admin::AddressRemovalReason::kInvalid:
return "invalid";
case fuchsia_net_interfaces_admin::AddressRemovalReason::kAlreadyAssigned:
return "already assigned";
case fuchsia_net_interfaces_admin::AddressRemovalReason::kDadFailed:
return "DAD failed";
case fuchsia_net_interfaces_admin::AddressRemovalReason::kInterfaceRemoved:
return "interface removed";
case fuchsia_net_interfaces_admin::AddressRemovalReason::kUserRemoved:
return "user removed";
default:
return "malformed address removal reason";
}
}
std::string_view AddressAssignmentStateToString(
fuchsia_net_interfaces::AddressAssignmentState state) {
switch (state) {
case fuchsia_net_interfaces::AddressAssignmentState::kTentative:
return "tentative";
case fuchsia_net_interfaces::AddressAssignmentState::kAssigned:
return "assigned";
case fuchsia_net_interfaces::AddressAssignmentState::kUnavailable:
return "unavailable";
default:
return "malformed address assignment state";
}
}
// Retrieve a handle to the `fuchsia.net.interfaces.admin/Control' API.
//
// Note that this uses `fuchsia.net.root/Interfaces.GetAdmin` to do so, which
// circumvents the strong ownership model of the `fuchsia.net.interfaces.api`.
// This pattern is discouraged, but approved for this use case in Weavestack,
// see (https://bugs.fuchsia.dev/p/fuchsia/issues/detail?id=92768#c6).
// TODO(https://fxbug.dev/42062982) Delete the usage of the root API once an
// alternative API is available.
std::optional<fidl::SyncClient<fuchsia_net_interfaces_admin::Control>> GetInterfaceControlViaRoot(
uint64_t interface_id) {
auto svc = nl::Weave::DeviceLayer::PlatformMgrImpl().GetComponentContextForProcess()->svc();
zx::result root_endpoints = fidl::CreateEndpoints<fuchsia_net_root::Interfaces>();
if (!root_endpoints.is_ok()) {
FX_LOGS(ERROR)
<< "Synchronous Error while connecting to the |fuchsia.net.root/Interfaces| protocol: "
<< root_endpoints.status_string();
return std::nullopt;
}
auto [root_client_end, root_server_end] = std::move(*root_endpoints);
if (zx_status_t status =
svc->Connect(kFuchsiaNetRootInterfacesProtocolName, root_server_end.TakeChannel());
status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to connect to root: " << zx_status_get_string(status);
return std::nullopt;
}
fidl::SyncClient root_client{std::move(root_client_end)};
zx::result control_endpoints = fidl::CreateEndpoints<fuchsia_net_interfaces_admin::Control>();
if (!control_endpoints.is_ok()) {
FX_LOGS(ERROR) << "Synchronous error when connecting to the "
"|fuchsia.net.interfaces.admin/Control| protocol: "
<< control_endpoints.status_string();
return std::nullopt;
}
auto [control_client_end, control_server_end] = std::move(*control_endpoints);
auto result = root_client->GetAdmin({interface_id, std::move(control_server_end)});
if (!result.is_ok()) {
FX_LOGS(ERROR) << "Failure while invoking |GetAdmin| FIDL method: " << result.error_value();
return std::nullopt;
}
return fidl::SyncClient(std::move(control_client_end));
}
// A client of the `fuchsia.net.interfaces.admin/Control` protocol that is
// somewhat agnostic to FIDL bindings (e.g. HLCPP vs LLCPP).
//
// When Constructed, it takes the `zx::channel` from the given `ControlSyncPtr`
// (HLCPP) and uses it to create a `fidl::SyncClient` (LLCPP).
//
// When Destructed, it takes the `zx::channel` from the `fidl::SyncClient`
// (LLCPP), and rebinds it to the original `ControlSyncPtr` (HLCPP).
class LlcppControlClientFromHlcpp {
public:
// Constructor takes the `zx::channel` from the hlcpp_client.
LlcppControlClientFromHlcpp(fuchsia::net::interfaces::admin::ControlSyncPtr *hlcpp_client) {
hlcpp_client_ = hlcpp_client;
llcpp_client_ = fidl::SyncClient<fuchsia_net_interfaces_admin::Control>(
fidl::ClientEnd<fuchsia_net_interfaces_admin::Control>(
hlcpp_client_->Unbind().TakeChannel()));
}
// Destructor returns the `zx::channel` to the hlcpp_client.
~LlcppControlClientFromHlcpp() {
hlcpp_client_->Bind(llcpp_client_.TakeClientEnd().TakeChannel());
}
const fidl::SyncClient<fuchsia_net_interfaces_admin::Control> &Get() { return llcpp_client_; }
private:
// The HLCPP client whose `zx::channel` is taken when this object is
// constructed. The `zx::channel` is rebound to the HLCPP client when this
// object is destructed.
fuchsia::net::interfaces::admin::ControlSyncPtr *hlcpp_client_;
// The LLCPP client that holds the `zx::channel` throughout the lifetime of
// this object.
fidl::SyncClient<fuchsia_net_interfaces_admin::Control> llcpp_client_;
};
// A Handler for `fuchsia.net.interfaces.admin/AddressStateProvider` events.
//
// The `fuchsia.net.interfaces.admin/AddressStateProvider.OnAddressRemoved`
// event is stored.
//
// The `fuchsia.net.interfaces.admin/AddressStateProvider.OnAddressAdded` event
// is ignored by this handler.
class AddressStateProviderEventHandler
: public fidl::SyncEventHandler<fuchsia_net_interfaces_admin::AddressStateProvider> {
public:
// Handles events until `OnAddressRemoved` is observed.
//
// If the returned `fidl::Status` is ok, `removal_reason` is guaranteed to be
// populated with a value.
fidl::Status WaitForAddressRemoval(
fidl::UnownedClientEnd<::fuchsia_net_interfaces_admin::AddressStateProvider> client_end) {
// `OnAddressAdded` and `OnAddressRemoved` are both one-time only events, so
// at most two events will be observed.
fidl::Status status = HandleOneEvent(client_end);
if (!status.ok()) {
return status;
}
if (removal_reason.has_value()) {
// We've already observed removal; no need to wait for a second event.
return status;
}
// The first event was `OnAddressAdded`; wait for a second event.
return HandleOneEvent(client_end);
}
void OnAddressAdded() override {}
void OnAddressRemoved(
fidl::Event<fuchsia_net_interfaces_admin::AddressStateProvider::OnAddressRemoved> &event)
override {
removal_reason = event.error();
}
std::optional<fuchsia_net_interfaces_admin::AddressRemovalReason> removal_reason;
};
// Adds the given address to the given interface.
//
// This function waits for the given address to be assigned, and then detaches
// the AddressStateProvider FIDL client from the address lifetime in the
// Netstack.
//
// If the address already exists, `kPlatformResultSuccess` will be returned.
PlatformResult AddAddressInternal(
uint64_t interface_id, const Inet::IPAddress &address, uint8_t prefix_length,
const fidl::SyncClient<fuchsia_net_interfaces_admin::Control> &control_client) {
FX_LOGS(INFO) << "Adding address " << AddressToStringInfallible(address)
<< " to interface with id " << interface_id;
// Construct the IP address for the interface.
std::array<uint8_t, 16> ipv6_addr_bytes;
std::memcpy(ipv6_addr_bytes.data(), reinterpret_cast<const uint8_t *>(address.Addr),
ipv6_addr_bytes.size());
fuchsia_net::Ipv6Address ipv6_addr = fuchsia_net::Ipv6Address(ipv6_addr_bytes);
fuchsia_net::IpAddress ip_addr = fuchsia_net::IpAddress::WithIpv6(ipv6_addr);
fuchsia_net::Subnet subnet = fuchsia_net::Subnet(std::move(ip_addr), prefix_length);
// Empty `AddressParameters`.
fuchsia_net_interfaces_admin::AddressParameters params;
// FIDL Handle for the `AddressStateProvider` protocol.
zx::result asp_endpoints =
fidl::CreateEndpoints<fuchsia_net_interfaces_admin::AddressStateProvider>();
if (!asp_endpoints.is_ok()) {
FX_LOGS(ERROR) << "Synchronous error when connecting to the AddressStateProvider protocol: "
<< asp_endpoints.status_string();
return kPlatformResultFailure;
}
auto [asp_client_end, asp_server_end] = std::move(*asp_endpoints);
auto result =
control_client->AddAddress({std::move(subnet), std::move(params), std::move(asp_server_end)});
if (!result.is_ok()) {
FX_LOGS(ERROR) << "Failed to add address " << AddressToStringInfallible(address)
<< " to interface with id " << interface_id << ": " << result.error_value();
return kPlatformResultFailure;
}
// Adding an address is asynchronous: After successfully calling "AddAddress"
// we must wait either for the AddressStateProvider protocol to be closed
// (indicating an error) or for the Address to become `ASSIGNED`.
FX_LOGS(INFO) << "Waiting for address to be assigned...";
fidl::SyncClient asp_client{std::move(asp_client_end)};
fuchsia_net_interfaces::AddressAssignmentState state;
do {
fidl::Result result = asp_client->WatchAddressAssignmentState();
if (!result.is_ok()) {
// Non Peer Closed errors are unexpected.
if (!result.error_value().is_peer_closed()) {
FX_LOGS(ERROR) << "Failure while invoking |WatchAddressAssignmentState|: "
<< result.error_value();
return kPlatformResultFailure;
}
// Peer Closed errors will be accompanied by an `OnAddressRemoved` event.
// That provides additional context as to why the error was observed.
AddressStateProviderEventHandler handler;
fidl::Status status = handler.WaitForAddressRemoval(asp_client.client_end());
if (!status.ok()) {
FX_LOGS(ERROR) << "Failure while handling |OnAddressRemoved|: " << status.status_string();
return kPlatformResultFailure;
}
if (handler.removal_reason.value() ==
fuchsia_net_interfaces_admin::AddressRemovalReason::kAlreadyAssigned) {
FX_LOGS(WARNING) << "Address " << AddressToStringInfallible(address)
<< " was already assigned to interface " << interface_id;
return kPlatformResultSuccess;
} else {
FX_LOGS(ERROR) << "Failed to add address " << AddressToStringInfallible(address)
<< " to Interface " << interface_id << " : "
<< AddressRemovalReasonToString(handler.removal_reason.value());
return kPlatformResultFailure;
}
}
state = result.value().assignment_state();
FX_LOGS(INFO) << "Observed state change for " << AddressToStringInfallible(address) << ": "
<< AddressAssignmentStateToString(state);
} while (state != fuchsia_net_interfaces::AddressAssignmentState::kAssigned);
PlatformResult store_handle_result =
global_asp_repo.Add(interface_id, address, std::move(asp_client));
if (store_handle_result == kPlatformResultFailure) {
FX_LOGS(ERROR) << "Failed to Store AddressStateProvider handle for address "
<< AddressToStringInfallible(address) << " on interface with id "
<< interface_id;
} else {
FX_LOGS(INFO) << "Successfully added address " << AddressToStringInfallible(address)
<< " to interface with id " << interface_id;
}
return store_handle_result;
}
// Removes the given address from the given interface.
//
// If the address was not found, `kPlatformResultSuccess` will be returned.
PlatformResult RemoveAddressInternal(uint64_t interface_id, const Inet::IPAddress &address) {
FX_LOGS(INFO) << "Removing address " << AddressToStringInfallible(address)
<< " from interface with id " << interface_id;
// Erase the ASP client-side handle from the global repository. Note that when
// the handle is dropped, the Netstack will remove the address.
if (!global_asp_repo.Erase(interface_id, address)) {
FX_LOGS(WARNING) << "Did not remove non-existant address " << AddressToStringInfallible(address)
<< " from interface with id " << interface_id;
} else {
FX_LOGS(INFO) << "Successfully removed address " << AddressToStringInfallible(address)
<< " from interface with id " << interface_id;
}
return kPlatformResultSuccess;
}
PlatformResult AddWiFiAddress(uint64_t interface_id, const Inet::IPAddress &address,
uint8_t prefix_length) {
std::optional<fidl::SyncClient<fuchsia_net_interfaces_admin::Control>> control_client =
GetInterfaceControlViaRoot(interface_id);
if (!control_client) {
FX_LOGS(ERROR)
<< "Failed to acquire |fuchsia.net.interfaces.admin/Control| handle for interface "
<< interface_id;
return kPlatformResultFailure;
}
return AddAddressInternal(interface_id, address, prefix_length, control_client.value());
}
PlatformResult AddTunnelAddress(uint64_t interface_id, const Inet::IPAddress &address,
uint8_t prefix_length) {
fuchsia::net::interfaces::admin::ControlSyncPtr *hlcpp_control_client =
ConnectivityMgrImpl().GetTunInterfaceControlSyncPtr();
if (hlcpp_control_client == nullptr || !hlcpp_control_client->is_bound()) {
FX_LOGS(ERROR) << "Tun Interface does not have an owned Control handle.";
return kPlatformResultFailure;
}
// When dropped, returns the `zx_channel` to `hlcpp_control_client`.
LlcppControlClientFromHlcpp llcpp_control_client =
LlcppControlClientFromHlcpp(hlcpp_control_client);
return AddAddressInternal(interface_id, address, prefix_length, llcpp_control_client.Get());
}
// Add or remove an address attached to the Thread or WLAN interfaces.
PlatformResult AddRemoveAddressInternal(InterfaceType interface_type,
const Inet::IPAddress &address, uint8_t prefix_length,
bool add) {
// Determine interface name to add/remove from.
std::optional<std::string> interface_name = GetInterfaceName(interface_type);
if (!interface_name) {
return kPlatformResultFailure;
}
// Determine the interface ID to add/remove from.
std::optional<uint64_t> interface_id = GetInterfaceId(interface_name.value());
if (!add && interface_id && interface_id.value() == 0) {
// When removing, don't report an error if the interface wasn't found. The
// interface may already have been removed at this point.
FX_LOGS(INFO) << "Interface " << interface_name.value() << " has already been removed.";
return kPlatformResultSuccess;
}
if (!interface_id) {
return kPlatformResultFailure;
}
if (add) {
switch (interface_type) {
case kInterfaceTypeTunnel:
return AddTunnelAddress(interface_id.value(), address, prefix_length);
case kInterfaceTypeWiFi:
return AddWiFiAddress(interface_id.value(), address, prefix_length);
default:
FX_LOGS(ERROR) << "Unsupported interface type: " << interface_type;
return kPlatformResultFailure;
}
} else {
return RemoveAddressInternal(interface_id.value(), address);
}
}
// Add or remove route to/from forwarding table.
PlatformResult AddRemoveRouteInternal(InterfaceType interface_type, const Inet::IPPrefix &prefix,
RoutePriority priority, bool add) {
auto svc = nl::Weave::DeviceLayer::PlatformMgrImpl().GetComponentContextForProcess()->svc();
// Determine interface name to add to/remove from.
std::optional<std::string> interface_name = GetInterfaceName(interface_type);
if (!interface_name) {
return kPlatformResultFailure;
}
fuchsia::net::stack::StackSyncPtr net_stack_sync_ptr;
if (zx_status_t status = svc->Connect(net_stack_sync_ptr.NewRequest()); status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to connect to netstack: " << zx_status_get_string(status);
return kPlatformResultFailure;
}
// Determine the interface ID to add/remove from.
std::optional<uint64_t> interface_id = GetInterfaceId(interface_name.value());
if (!add && interface_id && interface_id.value() == 0) {
// When removing, don't report an error if the interface wasn't found. The
// interface may already have been removed at this point.
FX_LOGS(INFO) << "Interface " << interface_name.value() << " has already been removed.";
return kPlatformResultSuccess;
}
if (!interface_id) {
return kPlatformResultFailure;
}
// Construct route table entry to add or remove.
fuchsia::net::stack::ForwardingEntry route_table_entry{
.subnet =
{
.addr = fuchsia::net::IpAddress::WithIpv6([&prefix]() {
fuchsia::net::Ipv6Address ipv6_addr;
std::memcpy(ipv6_addr.addr.data(),
reinterpret_cast<const uint8_t *>(prefix.IPAddr.Addr),
ipv6_addr.addr.size());
return ipv6_addr;
}()),
.prefix_len = prefix.Length,
},
.device_id = interface_id.value(),
};
switch (priority) {
case RoutePriority::kRoutePriorityHigh:
route_table_entry.metric = kRouteMetric_HighPriority;
break;
case RoutePriority::kRoutePriorityMedium:
route_table_entry.metric = kRouteMetric_MediumPriority;
break;
case RoutePriority::kRoutePriorityLow:
route_table_entry.metric = kRouteMetric_LowPriority;
break;
default:
FX_LOGS(WARNING) << "Unhandled route priority type, using lowest priority.";
route_table_entry.metric = kRouteMetric_LowPriority;
}
std::optional<fuchsia::net::stack::Error> error;
if (add) {
fuchsia::net::stack::Stack_AddForwardingEntry_Result result;
if (zx_status_t status =
net_stack_sync_ptr->AddForwardingEntry(std::move(route_table_entry), &result);
status != ZX_OK) {
FX_PLOGS(ERROR, status) << "Failed to add route";
return kPlatformResultFailure;
}
if (result.is_err()) {
error = result.err();
}
} else {
fuchsia::net::stack::Stack_DelForwardingEntry_Result result;
if (zx_status_t status =
net_stack_sync_ptr->DelForwardingEntry(std::move(route_table_entry), &result);
status != ZX_OK) {
FX_PLOGS(ERROR, status) << "Failed to delete route";
return kPlatformResultFailure;
}
if (result.is_err()) {
error = result.err();
}
}
if (error.has_value()) {
FX_LOGS(ERROR) << "Unable to modify route: " << StackErrorToString(error.value());
return kPlatformResultFailure;
}
FX_LOGS(INFO) << (add ? "Added" : "Removed") << " route to/from interface id "
<< interface_id.value();
#if WARM_CONFIG_SUPPORT_BORDER_ROUTING
// Set IPv6 forwarding on interface. Note that IPv6 forwarding is only ever
// enabled and never disabled. Once an interface is being managed, routes may
// be added and removed over time, so do not thrash the forwarding state
// during these transitions.
//
// TODO(https://fxbug.dev/42158360): Enabling V6 forwarding should ideally happen
// elsewhere, as this function's contract does not make any specific mention about
// forwarding. However, implementations must enable forwarding when border routing
// is enabled, signaled by ThreadThreadRouteAction and ThreadThreadPriorityAction
// in WARM. As we defer those route changes to this function, this is done here for
// now. Long term, this bug tracks proposing upstream changes that would create a
// targeted action to enable forwarding / border-routing to clarify this contract.
if (add && ShouldEnableV6Forwarding(interface_type)) {
// TODO(https://fxbug.dev/42176447): Migrate to
// fuchsia.net.interfaces.admin/Control.SetConfiguration.
fuchsia::net::stack::Stack_SetInterfaceIpForwardingDeprecated_Result forwarding_result;
if (zx_status_t status = net_stack_sync_ptr->SetInterfaceIpForwardingDeprecated(
interface_id.value(), fuchsia::net::IpVersion::V6, true /* enable */,
&forwarding_result);
status != ZX_OK) {
FX_PLOGS(ERROR, status) << "Failed to enable IPv6 forwarding on interface id "
<< interface_id.value();
return kPlatformResultFailure;
}
if (forwarding_result.is_err()) {
FX_LOGS(ERROR) << "Unable to enable IPv6 forwarding on interface id " << interface_id.value()
<< ": " << static_cast<uint32_t>(forwarding_result.err());
return kPlatformResultFailure;
}
}
#endif // WARM_CONFIG_SUPPORT_BORDER_ROUTING
return kPlatformResultSuccess;
}
} // namespace
WEAVE_ERROR Init(WarmFabricStateDelegate *inFabricStateDelegate) { return WEAVE_NO_ERROR; }
NL_DLL_EXPORT
void CriticalSectionEnter() {}
NL_DLL_EXPORT
void CriticalSectionExit() {}
// Add or remove a host address attached to the Thread or WLAN interfaces.
PlatformResult AddRemoveHostAddress(InterfaceType interface_type, const Inet::IPAddress &address,
uint8_t prefix_length, bool add) {
return AddRemoveAddressInternal(interface_type, address, prefix_length, add);
}
// Add or remove a host route attached to the Thread or WLAN interfaces.
PlatformResult AddRemoveHostRoute(InterfaceType interface_type, const Inet::IPPrefix &prefix,
RoutePriority priority, bool add) {
return AddRemoveRouteInternal(interface_type, prefix, priority, add);
}
NL_DLL_EXPORT
void RequestInvokeActions() { ::nl::Weave::Warm::InvokeActions(); }
#if WARM_CONFIG_SUPPORT_THREAD
PlatformResult AddRemoveThreadAddress(InterfaceType interface_type, const Inet::IPAddress &address,
bool add) {
// Prefix length for Thread addresses.
static constexpr uint8_t kThreadPrefixLength = 64;
return AddRemoveAddressInternal(interface_type, address, kThreadPrefixLength, add);
}
#endif // WARM_CONFIG_SUPPORT_THREAD
#if WARM_CONFIG_SUPPORT_THREAD_ROUTING
PlatformResult StartStopThreadAdvertisement(InterfaceType interface_type,
const Inet::IPPrefix &prefix, bool start) {
// This is handled by the LoWPAN service, nothing to do here.
return kPlatformResultSuccess;
}
#endif // WARM_CONFIG_SUPPORT_THREAD_ROUTING
#if WARM_CONFIG_SUPPORT_BORDER_ROUTING
PlatformResult AddRemoveThreadRoute(InterfaceType interface_type, const Inet::IPPrefix &prefix,
RoutePriority priority, bool add) {
return AddRemoveRouteInternal(interface_type, prefix, priority, add);
}
PlatformResult SetThreadRoutePriority(InterfaceType interface_type, const Inet::IPPrefix &prefix,
RoutePriority priority) {
// This will be handled during the AddRemoveThreadRoute from WARM.
return kPlatformResultSuccess;
}
#endif // WARM_CONFIG_SUPPORT_BORDER_ROUTING
} // namespace nl::Weave::Warm::Platform