/* * * Copyright (c) 2022 Project CHIP Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "AddressPolicy_LibNlImpl.h" #include #include #include #include namespace mdns { namespace Minimal { namespace LibNl { namespace { using namespace chip::Inet; using namespace chip::Platform; class AllListenIterator : public mdns::Minimal::ListenIterator { public: ~AllListenIterator() override; bool Next(InterfaceId * id, IPAddressType * type) override; private: /// Move mCurrentLink to the next valid element. /// Opens up nl_sockets and caches as needed. void Advance(); /// Validate if the current interface is usable (up, not loopback etc.) bool IsCurrentLinkUsable(); nl_sock * mNlSocket = nullptr; nl_cache * mNlCache = nullptr; nl_cache * mNlAddrCache = nullptr; nl_object * mCurrentLink = nullptr; IPAddressType mCurrentLinkType = IPAddressType::kUnknown; rtnl_link * CurrentLink() { return reinterpret_cast(mCurrentLink); } }; class ValidIpAddressIterator : public mdns::Minimal::IpAddressIterator { public: ValidIpAddressIterator(InterfaceId id, IPAddressType type) : mInterfaceIdFilter(id), mAddrType(type) {} ~ValidIpAddressIterator() override; bool Next(IPAddress & dest) override; private: /// Move mCurrentAddress to the next valid element. /// Opens up nl_sockets and caches as needed. void Advance(); /// Validate if the current address is usable: /// - valid interface id /// - not temporary/deprecated/... bool IsCurrentAddressUsable(); /// Attempt to decode the address pointed by mCurrentAddress into /// a CHIP-specific IP address. CHIP_ERROR DecodeCurrentAddress(IPAddress & dest); const InterfaceId mInterfaceIdFilter; const IPAddressType mAddrType; nl_sock * mNlSocket = nullptr; nl_cache * mNlCache = nullptr; nl_object * mCurrentAddress = nullptr; rtnl_addr * CurrentAddress() { return reinterpret_cast(mCurrentAddress); } }; AllListenIterator::~AllListenIterator() { if (mNlCache != nullptr) { nl_cache_free(mNlCache); mNlCache = nullptr; } if (mNlAddrCache != nullptr) { nl_cache_free(mNlAddrCache); mNlAddrCache = nullptr; } if (mNlSocket != nullptr) { nl_socket_free(mNlSocket); mNlSocket = nullptr; } } void AllListenIterator::Advance() { // If finding a new link, assume IPv6 is supported on that link // this assumption may be wrong, however current MinMdns code just asks // on what interfaces to try listening and actual IP address detection // (if needed) will be done later anyway. mCurrentLinkType = IPAddressType::kIPv6; if (mNlCache != nullptr) { if (mCurrentLink != nullptr) { mCurrentLink = nl_cache_get_next(mCurrentLink); } return; } if (mNlSocket == nullptr) { mNlSocket = nl_socket_alloc(); if (mNlSocket == nullptr) { ChipLogError(Inet, "Failed to allocate nl socket"); return; } int result = nl_connect(mNlSocket, NETLINK_ROUTE); if (result != 0) { ChipLogError(Inet, "Failed to connect NL socket: %s", nl_geterror(result)); return; } } int result = rtnl_link_alloc_cache(mNlSocket, AF_UNSPEC, &mNlCache); if (result != 0) { ChipLogError(Inet, "Failed to cache links"); return; } mCurrentLink = nl_cache_get_first(mNlCache); } bool AllListenIterator::Next(InterfaceId * id, IPAddressType * type) { while (true) { #if INET_CONFIG_ENABLE_IPV4 if (mCurrentLinkType == IPAddressType::kIPv6) { mCurrentLinkType = IPAddressType::kIPv4; } else { mCurrentLinkType = IPAddressType::kIPv6; Advance(); } #else Advance(); #endif if (mCurrentLink == nullptr) { return false; } if (!IsCurrentLinkUsable()) { // move to the next interface continue; } int ifindex = rtnl_link_get_ifindex(CurrentLink()); if (ifindex == 0) { // Invalid index, move to the next interface continue; } // For IPv4, report only interfaces which have an IPv4 address if (mCurrentLinkType == IPAddressType::kIPv4) { if (mNlAddrCache == nullptr) { int result = rtnl_addr_alloc_cache(mNlSocket, &mNlAddrCache); if (result != 0) { ChipLogError(Inet, "Failed to cache addresses"); return false; } } // Find IPv4 address for this interface struct rtnl_addr * filter = rtnl_addr_alloc(); rtnl_addr_set_family(filter, AF_INET); rtnl_addr_set_ifindex(filter, ifindex); struct nl_object * addr = nl_cache_find(mNlAddrCache, OBJ_CAST(filter)); nl_object_put(OBJ_CAST(filter)); // No IPv4 address, skip this interface for IPv4. if (addr == nullptr) continue; nl_object_put(addr); } *id = InterfaceId(ifindex); *type = mCurrentLinkType; // advancing should have set this return true; } } bool AllListenIterator::IsCurrentLinkUsable() { VerifyOrReturnError(mCurrentLink != nullptr, false); unsigned int flags = rtnl_link_get_flags(CurrentLink()); if ((flags & IFF_UP) == 0) { // only consider interfaces that are actually up return false; } if ((flags & IFF_LOOPBACK) != 0) { // skip local loopback return false; } if ((flags & (IFF_BROADCAST | IFF_MULTICAST)) == 0) { // minmdns requires broadcast/multicast return false; } return true; } ValidIpAddressIterator::~ValidIpAddressIterator() { if (mNlCache != nullptr) { nl_cache_free(mNlCache); mNlCache = nullptr; } if (mNlSocket != nullptr) { nl_socket_free(mNlSocket); mNlSocket = nullptr; } } void ValidIpAddressIterator::Advance() { if (mNlCache != nullptr) { if (mCurrentAddress != nullptr) { mCurrentAddress = nl_cache_get_next(mCurrentAddress); } return; } if (mNlSocket == nullptr) { mNlSocket = nl_socket_alloc(); if (mNlSocket == nullptr) { ChipLogError(Inet, "Failed to allocate nl socket"); return; } int result = nl_connect(mNlSocket, NETLINK_ROUTE); if (result != 0) { ChipLogError(Inet, "Failed to connect NL socket: %s", nl_geterror(result)); return; } } int result = rtnl_addr_alloc_cache(mNlSocket, &mNlCache); if (result != 0) { ChipLogError(Inet, "Failed to cache addresses"); return; } mCurrentAddress = nl_cache_get_first(mNlCache); } bool ValidIpAddressIterator::Next(IPAddress & dest) { while (true) { Advance(); if (mCurrentAddress == nullptr) { return false; } if (!IsCurrentAddressUsable()) { continue; } if (DecodeCurrentAddress(dest) == CHIP_NO_ERROR) { return true; } } } bool ValidIpAddressIterator::IsCurrentAddressUsable() { VerifyOrReturnError(mCurrentAddress != nullptr, false); if (mInterfaceIdFilter != InterfaceId(rtnl_addr_get_ifindex(CurrentAddress()))) { // Not for the correct interface id return false; } // Check if flags seem ok for this to be a good address to use/report int ifa_flags = rtnl_addr_get_flags(CurrentAddress()); return (ifa_flags & ( // Still going through duplicate address detection (DAD), should only // be used once DAD completed IFA_F_OPTIMISTIC | IFA_F_DADFAILED | IFA_F_TENTATIVE // Linux deprecated - should not be used anymore. We skip this // from the list of used/reported addresses | IFA_F_DEPRECATED)) == 0; } CHIP_ERROR ValidIpAddressIterator::DecodeCurrentAddress(IPAddress & dest) { VerifyOrReturnError(mCurrentAddress != nullptr, CHIP_ERROR_INCORRECT_STATE); nl_addr * local = rtnl_addr_get_local(CurrentAddress()); if (local == nullptr) { ChipLogError(Inet, "Failed to get local IP address"); return CHIP_ERROR_INTERNAL; } switch (nl_addr_get_family(local)) { case AF_INET6: { if ((mAddrType != IPAddressType::kAny) && (mAddrType != IPAddressType::kIPv6)) { return CHIP_ERROR_INVALID_ADDRESS; } struct sockaddr_in6 sa; memset(&sa, 0, sizeof(sa)); socklen_t len = sizeof(sa); int result = nl_addr_fill_sockaddr(local, reinterpret_cast(&sa), &len); if (result != 0) { ChipLogError(Inet, "Failed to process address: %s", nl_geterror(result)); return CHIP_ERROR_INVALID_ADDRESS; } dest = IPAddress::FromSockAddr(sa); return CHIP_NO_ERROR; } case AF_INET: { #if INET_CONFIG_ENABLE_IPV4 if ((mAddrType != IPAddressType::kAny) && (mAddrType != IPAddressType::kIPv4)) { return CHIP_ERROR_INVALID_ADDRESS; } struct sockaddr_in sa; memset(&sa, 0, sizeof(sa)); socklen_t len = sizeof(sa); int result = nl_addr_fill_sockaddr(local, reinterpret_cast(&sa), &len); if (result != 0) { ChipLogError(Inet, "Failed to process address: %s", nl_geterror(result)); return CHIP_ERROR_INVALID_ADDRESS; } dest = IPAddress::FromSockAddr(sa); return CHIP_NO_ERROR; #else return CHIP_ERROR_INVALID_ADDRESS; #endif } default: ChipLogError(Inet, "Unsupported/unknown local address: %d", nl_addr_get_family(local)); return CHIP_ERROR_INVALID_ADDRESS; } return CHIP_NO_ERROR; } LibNl_AddressPolicy gAddressPolicy; } // namespace UniquePtr LibNl_AddressPolicy::GetListenEndpoints() { return UniquePtr(chip::Platform::New()); } UniquePtr LibNl_AddressPolicy::GetIpAddressesForEndpoint(InterfaceId interfaceId, IPAddressType type) { return UniquePtr(chip::Platform::New(interfaceId, type)); } void SetAddressPolicy() { SetAddressPolicy(&gAddressPolicy); } } // namespace LibNl } // namespace Minimal } // namespace mdns