| // 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 <fuchsia/net/cpp/fidl.h> |
| #include <fuchsia/net/interfaces/cpp/fidl.h> |
| #include <fuchsia/net/stack/cpp/fidl.h> |
| #include <fuchsia/netstack/cpp/fidl.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; |
| |
| // 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; |
| |
| // 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"; |
| } |
| } |
| |
| // 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) { |
| auto svc = nl::Weave::DeviceLayer::PlatformMgrImpl().GetComponentContextForProcess()->svc(); |
| |
| // Determine interface name to add/remove from. |
| std::optional<std::string> interface_name = GetInterfaceName(interface_type); |
| if (!interface_name) { |
| return kPlatformResultFailure; |
| } |
| |
| fuchsia::net::stack::StackSyncPtr net_stack_sync_ptr; |
| zx_status_t status = svc->Connect(net_stack_sync_ptr.NewRequest()); |
| if (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 the IP address for the interface. |
| fuchsia::net::IpAddress ip_addr; |
| fuchsia::net::Ipv6Address ipv6_addr; |
| |
| std::memcpy(ipv6_addr.addr.data(), reinterpret_cast<const uint8_t *>(address.Addr), |
| ipv6_addr.addr.size()); |
| ip_addr.set_ipv6(ipv6_addr); |
| |
| fuchsia::net::Subnet subnet{ |
| .addr = std::move(ip_addr), |
| .prefix_len = prefix_length, |
| }; |
| |
| // Add or remove the address from the interface. |
| // TODO(https://fxbug.dev/92768): Migrate to fuchsia.net.interfaces.admin/Control. |
| std::optional<fuchsia::net::stack::Error> error; |
| if (add) { |
| fuchsia::net::stack::Stack_AddInterfaceAddressDeprecated_Result result; |
| status = net_stack_sync_ptr->AddInterfaceAddressDeprecated(interface_id.value(), |
| std::move(subnet), &result); |
| if (status == ZX_OK && result.is_err()) { |
| error = result.err(); |
| } |
| } else { |
| fuchsia::net::stack::Stack_DelInterfaceAddressDeprecated_Result result; |
| status = net_stack_sync_ptr->DelInterfaceAddressDeprecated(interface_id.value(), |
| std::move(subnet), &result); |
| if (status == ZX_OK && result.is_err()) { |
| error = result.err(); |
| } |
| } |
| if (status != ZX_OK) { |
| FX_LOGS(ERROR) << "Failed to configure interface address to interface id " |
| << interface_id.value() << ": " << zx_status_get_string(status); |
| return kPlatformResultFailure; |
| } |
| |
| // Verify that the result from netstack confirms that the address was actually |
| // added or removed. |
| if (error.has_value()) { |
| if (add) { |
| constexpr std::string_view msg = "Failed to add address on interface id "; |
| if (error.value() == fuchsia::net::stack::Error::ALREADY_EXISTS) { |
| FX_LOGS(WARNING) << msg << interface_id.value() << ": " |
| << StackErrorToString(error.value()); |
| return kPlatformResultSuccess; |
| } |
| FX_LOGS(ERROR) << msg << interface_id.value() << ": " << StackErrorToString(error.value()); |
| } else { |
| constexpr std::string_view msg = "Failed to remove address on interface id "; |
| if (error.value() == fuchsia::net::stack::Error::NOT_FOUND) { |
| FX_LOGS(WARNING) << msg << interface_id.value() << ": " |
| << StackErrorToString(error.value()); |
| return kPlatformResultSuccess; |
| } |
| FX_LOGS(ERROR) << msg << interface_id.value() << ": " << StackErrorToString(error.value()); |
| } |
| return kPlatformResultFailure; |
| } |
| |
| FX_LOGS(INFO) << (add ? "Added" : "Removed") << " address from interface id " |
| << interface_id.value(); |
| return kPlatformResultSuccess; |
| } |
| |
| // 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/78254): 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/94540): 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 |