diff --git a/examples/minimal-mdns/BUILD.gn b/examples/minimal-mdns/BUILD.gn index b6aef7386c9b3f..58ca4fa774a36c 100644 --- a/examples/minimal-mdns/BUILD.gn +++ b/examples/minimal-mdns/BUILD.gn @@ -52,7 +52,6 @@ executable("minimal-mdns-server") { ":minimal-mdns-example-common", "${chip_root}/src/lib", "${chip_root}/src/lib/dnssd/minimal_mdns", - "${chip_root}/src/lib/dnssd/minimal_mdns:default_policy", "${chip_root}/src/lib/dnssd/minimal_mdns/responders", ] diff --git a/examples/minimal-mdns/client.cpp b/examples/minimal-mdns/client.cpp index be4cae44759294..397049c50ce8d6 100644 --- a/examples/minimal-mdns/client.cpp +++ b/examples/minimal-mdns/client.cpp @@ -22,8 +22,8 @@ #include #include +#include #include -#include #include #include #include @@ -315,7 +315,10 @@ int main(int argc, char ** args) ReportDelegate reporter; CHIP_ERROR err; - mdns::Minimal::SetDefaultAddressPolicy(); + // This forces the global MDNS instance to be loaded in, effectively setting + // built in policies for addresses. + (void) chip::Dnssd::GlobalMinimalMdnsServer::Instance(); + gMdnsServer.SetDelegate(&reporter); { diff --git a/examples/minimal-mdns/server.cpp b/examples/minimal-mdns/server.cpp index 39168be3033d95..1bee06f8773b3b 100644 --- a/examples/minimal-mdns/server.cpp +++ b/examples/minimal-mdns/server.cpp @@ -22,9 +22,9 @@ #include #include +#include #include #include -#include #include #include #include @@ -199,7 +199,9 @@ int main(int argc, char ** args) return 1; } - mdns::Minimal::SetDefaultAddressPolicy(); + // This forces the global MDNS instance to be loaded in, effectively setting + // built in policies for addresses. + (void) chip::Dnssd::GlobalMinimalMdnsServer::Instance(); printf("Running on port %d using %s...\n", gOptions.listenPort, gOptions.enableIpV4 ? "IPv4 AND IPv6" : "IPv6 ONLY"); diff --git a/scripts/build/build/targets.py b/scripts/build/build/targets.py index 9e0a9c1cfe7808..258b3ba7105a58 100755 --- a/scripts/build/build/targets.py +++ b/scripts/build/build/targets.py @@ -282,6 +282,8 @@ def HostTargets(): # Possible build variants. Note that number of potential # builds is exponential here + builder.AppendVariant(name="libnl", validator=AcceptNameWithSubstrings( + ['-minmdns']), minmdns_address_policy="libnl"), builder.AppendVariant(name="same-event-loop", validator=AcceptNameWithSubstrings( ['-chip-tool', '-darwin-framework-tool']), separate_event_loop=False), builder.AppendVariant(name="no-interactive", validator=AcceptNameWithSubstrings( diff --git a/scripts/build/builders/host.py b/scripts/build/builders/host.py index a536d6650b5e8f..58f04151c3f5f8 100644 --- a/scripts/build/builders/host.py +++ b/scripts/build/builders/host.py @@ -215,6 +215,7 @@ def __init__(self, root, runner, app: HostApp, board=HostBoard.NATIVE, interactive_mode=True, extra_tests=False, use_platform_mdns=False, enable_rpcs=False, use_coverage=False, use_dmalloc=False, + minmdns_address_policy=None, crypto_library: HostCryptoLibrary = None): super(HostBuilder, self).__init__( root=os.path.join(root, 'examples', app.ExamplePath()), @@ -280,6 +281,11 @@ def __init__(self, root, runner, app: HostApp, board=HostBoard.NATIVE, # so setting clang is not correct raise Exception('Fake host board is always gcc (not clang)') + if minmdns_address_policy: + if use_platform_mdns: + raise Exception('Address policy applies to minmdns only') + self.extra_gn_options.append('chip_minmdns_default_policy="%s"' % minmdns_address_policy) + if use_platform_mdns: self.extra_gn_options.append('chip_mdns="platform"') diff --git a/src/lib/dnssd/MinimalMdnsServer.cpp b/src/lib/dnssd/MinimalMdnsServer.cpp index a73f8830809b3a..ec07e0e8632c5e 100644 --- a/src/lib/dnssd/MinimalMdnsServer.cpp +++ b/src/lib/dnssd/MinimalMdnsServer.cpp @@ -22,8 +22,16 @@ #define CHIP_MINMDNS_DEFAULT_POLICY 0 #endif +#ifndef CHIP_MINMDNS_LIBNL_POLICY +#define CHIP_MINMDNS_LIBNL_POLICY 0 +#endif + #if CHIP_MINMDNS_DEFAULT_POLICY -#include +#include // nogncheck +#endif + +#if CHIP_MINMDNS_LIBNL_POLICY +#include // nogncheck #endif namespace chip { @@ -39,6 +47,10 @@ GlobalMinimalMdnsServer::GlobalMinimalMdnsServer() #if CHIP_MINMDNS_DEFAULT_POLICY mdns::Minimal::SetDefaultAddressPolicy(); #endif + +#if CHIP_MINMDNS_LIBNL_POLICY + mdns::Minimal::LibNl::SetAddressPolicy(); +#endif } GlobalMinimalMdnsServer & GlobalMinimalMdnsServer::Instance() diff --git a/src/lib/dnssd/minimal_mdns/AddressPolicy_LibNlImpl.cpp b/src/lib/dnssd/minimal_mdns/AddressPolicy_LibNlImpl.cpp new file mode 100644 index 00000000000000..e73627f42321e3 --- /dev/null +++ b/src/lib/dnssd/minimal_mdns/AddressPolicy_LibNlImpl.cpp @@ -0,0 +1,405 @@ +/* + * + * 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_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 (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 + // FOR IPv4, report all interfaces as 'try IPv4 here as well' + 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 idx = rtnl_link_get_ifindex(CurrentLink()); + if (idx == 0) + { + // Invalid index, move to the next interface + continue; + } + + *id = InterfaceId(idx); + *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 diff --git a/src/lib/dnssd/minimal_mdns/AddressPolicy_LibNlImpl.h b/src/lib/dnssd/minimal_mdns/AddressPolicy_LibNlImpl.h new file mode 100644 index 00000000000000..5299510e73c7d0 --- /dev/null +++ b/src/lib/dnssd/minimal_mdns/AddressPolicy_LibNlImpl.h @@ -0,0 +1,38 @@ +/* + * + * 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. + */ +#pragma once + +#include + +namespace mdns { +namespace Minimal { +namespace LibNl { + +class LibNl_AddressPolicy : public mdns::Minimal::AddressPolicy +{ +public: + chip::Platform::UniquePtr GetListenEndpoints() override; + + chip::Platform::UniquePtr GetIpAddressesForEndpoint(chip::Inet::InterfaceId interfaceId, + chip::Inet::IPAddressType type) override; +}; + +void SetAddressPolicy(); + +} // namespace LibNl +} // namespace Minimal +} // namespace mdns diff --git a/src/lib/dnssd/minimal_mdns/BUILD.gn b/src/lib/dnssd/minimal_mdns/BUILD.gn index 853e2359d2fb46..927b881f2aabfc 100644 --- a/src/lib/dnssd/minimal_mdns/BUILD.gn +++ b/src/lib/dnssd/minimal_mdns/BUILD.gn @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import("//build_overrides/build.gni") import("//build_overrides/chip.gni") declare_args() { @@ -33,8 +34,13 @@ declare_args() { # time. chip_minmdns_high_verbosity = false - # Compile in the default minmdns policy for endpoint and ip address fetching - chip_minmdns_default_policy = true + # MinMdns address policy to be compiled in. + # Supported values: + # - "default" will compile in AddressPolicy_DefaultImpl.h/cpp + # - "libnl" will compile in AddressPolicy_LibNlImpl.h/cpp (linux only) + # - "none" will NOT compile any default policy, app is expected to call + # SetAddressPolicy apropriately + chip_minmdns_default_policy = "default" } config("config") { @@ -52,8 +58,14 @@ config("config") { defines += [ "CHIP_MINMDNS_HIGH_VERBOSITY=0" ] } - if (chip_minmdns_default_policy) { + if (chip_minmdns_default_policy == "default") { defines += [ "CHIP_MINMDNS_DEFAULT_POLICY=1" ] + } else if (chip_minmdns_default_policy == "libnl") { + assert(current_os == "linux", "LibNL only supported on linux") + defines += [ "CHIP_MINMDNS_LIBNL_POLICY=1" ] + } else { + assert(chip_minmdns_default_policy == "none", + "minmdns default policy should be a supported value") } } @@ -82,6 +94,31 @@ static_library("default_policy") { ] } +if (current_os == "linux" && chip_minmdns_default_policy == "libnl") { + import("${build_root}/config/linux/pkg_config.gni") + + pkg_config("libnl_config") { + packages = [ + "libnl-3.0", + "libnl-route-3.0", + ] + } + + static_library("libnl_policy") { + sources = [ + "AddressPolicy_LibNlImpl.cpp", + "AddressPolicy_LibNlImpl.h", + ] + + public_deps = [ + ":address_policy", + "${chip_root}/src/inet", + ] + + public_configs = [ ":libnl_config" ] + } +} + static_library("minimal_mdns") { sources = [ "Logging.h", @@ -112,8 +149,10 @@ static_library("minimal_mdns") { "${chip_root}/src/platform", ] - if (chip_minmdns_default_policy) { + if (chip_minmdns_default_policy == "default") { public_deps += [ ":default_policy" ] + } else if (chip_minmdns_default_policy == "libnl") { + public_deps += [ ":libnl_policy" ] } public_configs = [ ":config" ]