Skip to content

Commit

Permalink
Add a generic address resolver - convert multiple DNSSD replies into …
Browse files Browse the repository at this point in the history
…one single address (#15200)

* Add a generic address resolver:
  - default implementation uses the global DNSSD resolver and
    implements timeouts for lookups
  - custom implementation is possible by providing the actual
    implementation header somewhere else.

Added a testing tool and build rules such that address resolution
can be verified.

This does NOT yet integrate with the CASESessionManager. I plan
to do that as a separate PR as integration is expected to change
more callbacks and logic.

* Fix comparison order

* Fix platform mdns circular dependency in init

* Support platfordns build for the address resolve

* Fix typo

* Fix more typos, add some spellcheck works (ULA and GUA), re-sort the workdlist file

* Address some code review comments

* Add logic for ULA with shared prefix for ordering items

* more comments/hopefully clearer

* one more spell check fix

* Add platformmdns with ipv6 only build target. On my system, avahi returns an IPv4 and IPv6 only build DOES NOT work

* Fix unit tests for build_examples

* Remove unused variable

* make clang builds happy, add a build variant to validate said clang build

* Fix noglob unit tests

* Make spell check happy

* DNSSD now uses memory, change fake platform test to do chip memory init

* Removed some duplicates in wordlist. Also kick CI since that seemed not to start
  • Loading branch information
andy31415 authored and pull[bot] committed Feb 2, 2024
1 parent 1932e58 commit 2140acc
Show file tree
Hide file tree
Showing 15 changed files with 957 additions and 10 deletions.
2 changes: 2 additions & 0 deletions .github/.wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,7 @@ gradlew
GroupId
GroupKeyManagement
gtk
GUA
Gv
gz
HaloaceticAcidsConcentrationMeasurement
Expand Down Expand Up @@ -1214,6 +1215,7 @@ udpPort
ug
ui
uint
ULA
UNBLUR
uncommissioned
unfocus
Expand Down
1 change: 1 addition & 0 deletions BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ if (current_toolchain != "${dir_pw_toolchain}/default:default") {
"${chip_root}/examples/shell/standalone:chip-shell",
"${chip_root}/src/app/tests/integration:chip-im-initiator",
"${chip_root}/src/app/tests/integration:chip-im-responder",
"${chip_root}/src/lib/address_resolve:address-resolve-tool",
"${chip_root}/src/messaging/tests/echo:chip-echo-requester",
"${chip_root}/src/messaging/tests/echo:chip-echo-responder",
"${chip_root}/src/qrcodetool",
Expand Down
9 changes: 7 additions & 2 deletions scripts/build/build/targets.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,6 @@ def HostTargets():
targets[0].Extend('rpc-console', app=HostApp.RPC_CONSOLE))
app_targets.append(
targets[0].Extend('tv-app', app=HostApp.TV_APP))
app_targets.append(
targets[0].Extend('chip-cert', app=HostApp.CERT_TOOL))

for target in targets:
app_targets.append(target.Extend(
Expand Down Expand Up @@ -202,6 +200,13 @@ def HostTargets():

yield variant_target

# Without extra build variants
yield targets[0].Extend('chip-cert', app=HostApp.CERT_TOOL)
yield targets[0].Extend('address-resolve-tool', app=HostApp.ADDRESS_RESOLVE)
yield targets[0].Extend('address-resolve-tool-clang', app=HostApp.ADDRESS_RESOLVE, use_clang=True).GlobBlacklist("Reduce default build variants")
yield targets[0].Extend('address-resolve-tool-platform-mdns', app=HostApp.ADDRESS_RESOLVE, use_platform_mdns=True).GlobBlacklist("Reduce default build variants")
yield targets[0].Extend('address-resolve-tool-platform-mdns-ipv6only', app=HostApp.ADDRESS_RESOLVE, use_platform_mdns=True, enable_ipv4=False).GlobBlacklist("Reduce default build variants")

test_target = Target(HostBoard.NATIVE.PlatformName(), HostBuilder)
for board in [HostBoard.NATIVE, HostBoard.FAKE]:
yield test_target.Extend(board.BoardName() + '-tests', board=board, app=HostApp.TESTS)
Expand Down
16 changes: 15 additions & 1 deletion scripts/build/builders/host.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class HostApp(Enum):
THERMOSTAT = auto()
RPC_CONSOLE = auto()
MIN_MDNS = auto()
ADDRESS_RESOLVE = auto()
TV_APP = auto()
LOCK = auto()
TESTS = auto()
Expand All @@ -43,6 +44,8 @@ def ExamplePath(self):
return 'common/pigweed/rpc_console'
elif self == HostApp.MIN_MDNS:
return 'minimal-mdns'
elif self == HostApp.ADDRESS_RESOLVE:
return '../'
elif self == HostApp.TV_APP:
return 'tv-app/linux'
elif self == HostApp.LOCK:
Expand Down Expand Up @@ -75,6 +78,9 @@ def OutputNames(self):
yield 'minimal-mdns-client.map'
yield 'minimal-mdns-server'
yield 'minimal-mdns-server.map'
elif self == HostApp.ADDRESS_RESOLVE:
yield 'address-resolve-tool'
yield 'address-resolve-tool.map'
elif self == HostApp.TV_APP:
yield 'chip-tv-app'
yield 'chip-tv-app.map'
Expand Down Expand Up @@ -137,7 +143,9 @@ class HostBuilder(GnBuilder):

def __init__(self, root, runner, app: HostApp, board=HostBoard.NATIVE, enable_ipv4=True,
enable_ble=True, use_tsan=False, use_asan=False, separate_event_loop=True,
test_group=False, use_libfuzzer=False, use_clang=False):
test_group=False, use_libfuzzer=False, use_clang=False,
use_platform_mdns=False
):
super(HostBuilder, self).__init__(
root=os.path.join(root, 'examples', app.ExamplePath()),
runner=runner)
Expand Down Expand Up @@ -171,6 +179,9 @@ def __init__(self, root, runner, app: HostApp, board=HostBoard.NATIVE, enable_ip
if use_clang:
self.extra_gn_options.append('is_clang=true')

if use_platform_mdns:
self.extra_gn_options.append('chip_mdns="platform"')

if app == HostApp.TESTS:
self.extra_gn_options.append('chip_build_tests=true')
self.build_command = 'check'
Expand All @@ -184,6 +195,9 @@ def __init__(self, root, runner, app: HostApp, board=HostBoard.NATIVE, enable_ip
self.extra_gn_options.append('chip_crypto="openssl"')
self.build_command = 'src/tools/chip-cert'

if app == HostApp.ADDRESS_RESOLVE:
self.build_command = 'src/lib/address_resolve:address-resolve-tool'

def GnBuildArgs(self):
if self.board == HostBoard.NATIVE:
return self.extra_gn_options
Expand Down
12 changes: 6 additions & 6 deletions scripts/build/testdata/build_linux_on_x64.txt
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ PKG_CONFIG_PATH="SYSROOT_AARCH64/lib/aarch64-linux-gnu/pkgconfig" \
# Generating linux-fake-tests
gn gen --check --fail-on-unused-args --export-compile-commands --root={root} '--args=chip_build_tests=true custom_toolchain="//build/toolchain/fake:fake_x64_gcc" chip_link_tests=true chip_device_platform="fake"' {out}/linux-fake-tests

# Generating linux-x64-address-resolve-tool
gn gen --check --fail-on-unused-args --export-compile-commands --root={root} {out}/linux-x64-address-resolve-tool

# Generating linux-x64-all-clusters
gn gen --check --fail-on-unused-args --export-compile-commands --root={root}/examples/all-clusters-app/linux {out}/linux-x64-all-clusters

Expand All @@ -73,9 +76,6 @@ gn gen --check --fail-on-unused-args --export-compile-commands --root={root}/exa
# Generating linux-x64-chip-cert
gn gen --check --fail-on-unused-args --export-compile-commands --root={root} '--args=chip_crypto="openssl"' {out}/linux-x64-chip-cert

# Generating linux-x64-chip-cert-ipv6only
gn gen --check --fail-on-unused-args --export-compile-commands --root={root} '--args=chip_inet_config_enable_ipv4=false chip_crypto="openssl"' {out}/linux-x64-chip-cert-ipv6only

# Generating linux-x64-chip-tool
gn gen --check --fail-on-unused-args --export-compile-commands --root={root}/examples/chip-tool {out}/linux-x64-chip-tool

Expand Down Expand Up @@ -157,6 +157,9 @@ ninja -C {out}/linux-arm64-thermostat-ipv6only
# Building linux-fake-tests
ninja -C {out}/linux-fake-tests check

# Building linux-x64-address-resolve-tool
ninja -C {out}/linux-x64-address-resolve-tool src/lib/address_resolve:address-resolve-tool

# Building linux-x64-all-clusters
ninja -C {out}/linux-x64-all-clusters

Expand All @@ -166,9 +169,6 @@ ninja -C {out}/linux-x64-all-clusters-ipv6only
# Building linux-x64-chip-cert
ninja -C {out}/linux-x64-chip-cert src/tools/chip-cert

# Building linux-x64-chip-cert-ipv6only
ninja -C {out}/linux-x64-chip-cert-ipv6only src/tools/chip-cert

# Building linux-x64-chip-tool
ninja -C {out}/linux-x64-chip-tool

Expand Down
32 changes: 32 additions & 0 deletions src/lib/address_resolve/AddressResolve.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
*
* 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 <lib/address_resolve/AddressResolve.h>

namespace chip {
namespace AddressResolve {

// Provide as storage location to make clang happy.
constexpr uint32_t NodeLookupRequest::kMinLookupTimeMsDefault;
constexpr uint32_t NodeLookupRequest::kMaxLookupTimeMsDefault;

// Placed here so we guarantee that the address resolve static library
// has at least a single cpp file to compile.
Resolver::~Resolver() {}

} // namespace AddressResolve
} // namespace chip
199 changes: 199 additions & 0 deletions src/lib/address_resolve/AddressResolve.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
/*
*
* 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 <lib/core/PeerId.h>
#include <lib/support/IntrusiveList.h>
#include <system/SystemClock.h>
#include <system/SystemLayer.h>
#include <transport/raw/PeerAddress.h>

namespace chip {
namespace AddressResolve {

/// Represents an object intersted in callbacks for a resolve operation.
class NodeListener
{
public:
NodeListener() = default;
virtual ~NodeListener() = default;

/// Callback executed once only for a lookup, when the final address of a
/// node is considered to be the best choice for reachability.
///
/// The callback is expected to be executed within the chip event loop
/// thread.
virtual void OnNodeAddressResolved(const PeerId & peerId, const Transport::PeerAddress & address) = 0;

/// Node resolution failure - occurs only once for a lookup, when an address
/// could not be resolved - generally due to a timeout or due to DNSSD
/// infrastructure returning an error.
///
/// The callback is expected to be executed within the chip event loop
/// thread.
virtual void OnNodeAddressResolutionFailed(const PeerId & peerId, CHIP_ERROR reason) = 0;
};

/// Represents an active Address resolution lookup.
///
/// Implementations extend this class with implementation-specific data like
/// storing the 'last known good address' and 'scores' or any additional data
/// required to figure out when a resolve is ok.
class NodeLookupHandleBase : public IntrusiveListNodeBase
{
public:
NodeLookupHandleBase() {}
virtual ~NodeLookupHandleBase() {}

// While active, resolve handles are maintain in an internal list
// to be processed, so copying their values (i.e. pointers) is not
// allowed.
NodeLookupHandleBase(const NodeLookupHandleBase &) = delete;
NodeLookupHandleBase & operator=(const NodeLookupHandleBase &) = delete;

void SetListener(NodeListener * listener) { mListener = listener; }
NodeListener * GetListener() { return mListener; }

protected:
NodeListener * mListener = nullptr;
};

/// Represents a request to perform a single node lookup
/// Contains all the information that should be looked for as well as
/// extra timeout configurations.
class NodeLookupRequest
{
public:
NodeLookupRequest() {}
NodeLookupRequest(const PeerId & peerId) : mPeerId(peerId) {}

NodeLookupRequest(const NodeLookupRequest &) = default;
NodeLookupRequest & operator=(const NodeLookupRequest &) = default;

const PeerId & GetPeerId() const { return mPeerId; }
System::Clock::Milliseconds32 GetMinLookupTime() const { return mMinLookupTimeMs; }
System::Clock::Milliseconds32 GetMaxLookupTime() const { return mMaxLookupTimeMs; }

/// The minimum lookup time is how much to wait for additional DNSSD
/// queries even if a reply has already been received or to allow for
/// additional heuristics regarding node choice to succeed.
/// Example heuristics and considerations:
/// - ping/ping6 could be used as an indicator of reacability. NOTE that
/// not all devices may respond to ping, so this would only be an
/// additional signal to accept/increase suitability score of an address
/// and should NOT be used as a reject if no ping response
/// - At lookup time, if the source ip of a dns reply is contained in the
/// list of server ips, that is a great indication of routability and
/// this minlookuptime could be bypassed altogether.
///
/// Implementations for DNSSD may choose to return responses one by one
/// for addresses (e.g. Platform mdns does this at the time this was written)
/// or different interfaces will return separate 'done resolving' calls.
///
/// If the min lookup time is set to 0, implementations are expected to call
/// 'OnNodeAddressResolved' as soon as the first DNSSD response is received.
NodeLookupRequest & SetMinLookupTime(System::Clock::Milliseconds32 value)
{
mMinLookupTimeMs = value;
return *this;
}

/// The maximum lookup time is how much to wait until a TIMEOUT error is
/// declared.
///
/// If a DNSSD response is received before this max timeout, then
/// OnNodeAddressResolved will be called on listener objects (immediately)
/// if the first DNSSD reply arrives after MinLookupTimeMs has expired.
NodeLookupRequest & SetMaxLookupTime(System::Clock::Milliseconds32 value)
{
mMaxLookupTimeMs = value;
return *this;
}

private:
static constexpr uint32_t kMinLookupTimeMsDefault = 200;
static constexpr uint32_t kMaxLookupTimeMsDefault = 3000;

PeerId mPeerId;
System::Clock::Milliseconds32 mMinLookupTimeMs{ kMinLookupTimeMsDefault };
System::Clock::Milliseconds32 mMaxLookupTimeMs{ kMaxLookupTimeMsDefault };
};

/// These things are expected to be defined by the implementation header.
namespace Impl {

// The NodeLookup handle is a CONCRETE implementation that
// MUST derive from NodeLookupHandleBase
//
// The underlying reason is that this handle is used to hold memory for
// lookup metadata, so that resolvers do not need to maintain a likely unused
// pool of 'active lookup' metadata.
//
// The sideffect of this is that the ImplNodeLookupHandle is exposed to clients
// for sizeof() memory purposes.
//
// Clients MUST only use the interface in NodeLookupHandleBase and assume all
// other methods/content is implementation defined.
class NodeLookupHandle;

} // namespace Impl

class Resolver
{
public:
virtual ~Resolver();

/// Expected to be called exactly once before the resolver is ever
/// used.
virtual CHIP_ERROR Init(System::Layer * systemLayer) = 0;

/// Initiate a node lookup for a particular node and use the specified
/// Lookup handle to keep track of node resolution
///
/// If this returns CHIP_NO_ERROR, the following is expected:
/// - exactly one of the listener OnNodeAddressResolved
/// or OnNodeAddressResolutionFailed will be called at a later time
/// - handle must NOT be destroyed while the lookup is in progress (it
/// is part of an internal 'lookup list')
/// - handle must NOT be reused (the lookup is done on a per-node basis
/// and maintains lookup data internally while the operation is still
/// in progress)
virtual CHIP_ERROR LookupNode(const NodeLookupRequest & request, Impl::NodeLookupHandle & handle) = 0;

/// Expected to be provided by the implementation.
static Resolver & Instance();
};

} // namespace AddressResolve
} // namespace chip

// outside the open space, include the required platform headers for the
// actual implementation.
// Expectations of this include:
// - define the `Impl::NodeLookupHandle` deriving from NodeLookupHandle
// - corresponding CPP file should provide a valid Resolver::Instance()
// implementation
#include CHIP_ADDRESS_RESOLVE_IMPL_INCLUDE_HEADER

namespace chip {
namespace AddressResolve {

// Make the code easy to read: do not reach into Impl.
using NodeLookupHandle = Impl::NodeLookupHandle;

} // namespace AddressResolve
} // namespace chip
Loading

0 comments on commit 2140acc

Please sign in to comment.