Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions Development/nmos-cpp-node/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,21 @@
// domain [registry, node]: the domain on which to browse for services or an empty string to use the default domain (specify "local." to explictly select mDNS)
//"domain": "",

// dns_sd_browse_mode [node]: DNS-SD browse method per TR-10-9 Section 15
// both(0) (default) = unicast DNS first, mDNS fallback if unsuccessful
// unicast(1) = unicast DNS only
// mdns(2) = mDNS only
// Expected resolve behaviour for each (mode, domain) combination:
// mode | domain | behaviour
// --------+-------------+--------------------
// both | example.com | unicast -> mdns
// both | local. | mdns
// unicast | example.com | unicast
// unicast | local. | mdns
// mdns | example.com | mdns
// mdns | local. | mdns
//"dns_sd_browse_mode": 0,

// host_address/host_addresses [registry, node]: IP addresses used to construct response headers (e.g. 'Link' or 'Location'), and host and URL fields in the data model
//"host_address": "127.0.0.1",
//"host_addresses": array-of-ip-address-strings,
Expand Down
84 changes: 64 additions & 20 deletions Development/nmos/mdns.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,14 @@ namespace nmos
const service_type register_{ "_nmos-register._tcp" };
}

// DNS-SD browse method per TR-10-9 Section 15; selected via the dns_sd_browse_mode setting
enum dns_sd_browse_mode
{
dns_sd_browse_mode_both = 0, // unicast DNS first, mDNS fallback if unsuccessful
dns_sd_browse_mode_unicast = 1, // unicast DNS only
dns_sd_browse_mode_mdns = 2 // mDNS only
};

namespace experimental
{
namespace details
Expand Down Expand Up @@ -503,6 +511,16 @@ namespace nmos
{
return discovery.browse([=, &discovery](const mdns::browse_result& resolving)
{
// Skip results from a different discovery domain class
// (prevents mDNS results leaking into unicast DNS queries and vice versa)
if (!browse_domain.empty())
{
if (is_local_domain(browse_domain) != is_local_domain(resolving.domain))
{
return true; // skip this result, keep browsing
}
}

const bool cancel = pplx::canceled == discovery.resolve([=](const mdns::resolve_result& resolved)
{
// "The Node [filters] out any APIs which do not support its required API version, protocol and authorization mode (TXT api_ver, api_proto and api_auth)."
Expand Down Expand Up @@ -590,8 +608,9 @@ namespace nmos
}
}

// helper function for resolving instances of the specified service (API)
// with the highest version, highest priority instances at the front, and optionally services with the same priority ordered randomly
// Helper function for resolving instances of the specified service (API), returning ((api_version, priority), uri)
// tuples so callers can inspect the matched version and priority. Highest version and highest priority first,
// and optionally services with the same priority ordered randomly.
pplx::task<std::list<resolved_service>> resolve_service_(mdns::service_discovery& discovery, const nmos::service_type& service, const std::string& browse_domain, const std::set<nmos::api_version>& api_ver, const std::pair<nmos::service_priority, nmos::service_priority>& priorities, const std::set<nmos::service_protocol>& api_proto, const std::set<bool>& api_auth, bool randomize, const std::chrono::steady_clock::duration& timeout, const pplx::cancellation_token& token)
{
const auto absolute_timeout = std::chrono::steady_clock::now() + timeout;
Expand Down Expand Up @@ -696,8 +715,10 @@ namespace nmos
});
}

// helper function for resolving instances of the specified service (API)
// with the highest version, highest priority instances at the front, and optionally services with the same priority ordered randomly
// DEPRECATED: this overload is unused; prefer resolve_service_ which also returns api_ver/priority info.
// Helper function for resolving instances of the specified service (API), returning a list of base URIs
// (with the API version path appended), highest version and highest priority first, and optionally
// services with the same priority ordered randomly.
pplx::task<std::list<web::uri>> resolve_service(mdns::service_discovery& discovery, const nmos::service_type& service, const std::string& browse_domain, const std::set<nmos::api_version>& api_ver, const std::pair<nmos::service_priority, nmos::service_priority>& priorities, const std::set<nmos::service_protocol>& api_proto, const std::set<bool>& api_auth, bool randomize, const std::chrono::steady_clock::duration& timeout, const pplx::cancellation_token& token)
{
return resolve_service_(discovery, service, browse_domain, api_ver, priorities, api_proto, api_auth, randomize, timeout, token).then([](std::list<resolved_service> resolved_services)
Expand All @@ -710,28 +731,33 @@ namespace nmos
});
}

// helper function for resolving instances of the specified service (API) based on the specified settings
// with the highest version, highest priority instances at the front, and services with the same priority ordered randomly
// Helper function for resolving instances of the specified service (API) based on the specified settings,
// returning a list of base URIs (with the API version path appended), highest version and highest priority
// first, services with the same priority ordered randomly.
// The browse method is selected by the dns_sd_browse_mode setting per TR-10-9 Section 15
// (delegates to resolve_service_, which carries the dual-discovery logic).
pplx::task<std::list<web::uri>> resolve_service(mdns::service_discovery& discovery, const nmos::service_type& service, const nmos::settings& settings, const pplx::cancellation_token& token)
{
const auto browse_domain = utility::us2s(nmos::get_domain(settings));
const auto versions = details::service_versions(service, settings);
const auto priorities = details::service_priorities(service, settings);
const auto protocols = std::set<nmos::service_protocol>{ nmos::get_service_protocol(service, settings) };
const auto authorization = std::set<bool>{ nmos::get_service_authorization(service, settings) };

// use a short timeout that's long enough to ensure the daemon's cache is exhausted
// when no cancellation token is specified
const auto timeout = token.is_cancelable() ? nmos::fields::discovery_backoff_max(settings) : 1;

return resolve_service(discovery, service, browse_domain, versions, priorities, protocols, authorization, true, std::chrono::duration_cast<std::chrono::steady_clock::duration>(std::chrono::seconds(timeout)), token);
return resolve_service_(discovery, service, settings, token).then([](std::list<resolved_service> resolved_services)
{
return boost::copy_range<std::list<web::uri>>(resolved_services | boost::adaptors::transformed([](const resolved_service& s)
{
return web::uri_builder(s.second).append_path(U("/") + make_api_version(s.first.first)).to_uri();
}));
});
}

// helper function for resolving instances of the specified service (API) based on the specified settings
// with the highest version, highest priority instances at the front, and services with the same priority ordered randomly
// Helper function for resolving instances of the specified service (API) based on the specified settings,
// returning ((api_version, priority), uri) tuples. Highest version and highest priority first, services
// with the same priority ordered randomly.
// The browse method is selected by the dns_sd_browse_mode setting per TR-10-9 Section 15:
// - both (default): unicast DNS first, mDNS fallback if unsuccessful
// - unicast : unicast DNS only
// - mdns : mDNS only
pplx::task<std::list<resolved_service>> resolve_service_(mdns::service_discovery& discovery, const nmos::service_type& service, const nmos::settings& settings, const pplx::cancellation_token& token)
{
const auto browse_domain = utility::us2s(nmos::get_domain(settings));
const auto browse_mode = dns_sd_browse_mode(nmos::fields::dns_sd_browse_mode(settings));
const auto versions = details::service_versions(service, settings);
const auto priorities = details::service_priorities(service, settings);
const auto protocols = std::set<nmos::service_protocol>{ nmos::get_service_protocol(service, settings) };
Expand All @@ -740,8 +766,26 @@ namespace nmos
// use a short timeout that's long enough to ensure the daemon's cache is exhausted
// when no cancellation token is specified
const auto timeout = token.is_cancelable() ? nmos::fields::discovery_backoff_max(settings) : 1;
const auto timeout_dur = std::chrono::duration_cast<std::chrono::steady_clock::duration>(std::chrono::seconds(timeout));

// determine primary browse domain based on mode
const auto primary_domain = (browse_mode == dns_sd_browse_mode_mdns) ? std::string("local.") : browse_domain;
const bool has_fallback = (browse_mode == dns_sd_browse_mode_both) && !is_local_domain(browse_domain);

auto primary_task = resolve_service_(discovery, service, primary_domain, versions, priorities, protocols, authorization, true, timeout_dur, token);

if (has_fallback)
{
return primary_task.then([&discovery, service, versions, priorities, protocols, authorization, timeout_dur, token](std::list<resolved_service> results)
{
if (!results.empty()) return pplx::task_from_result(std::move(results));

// TR-10-9: unicast DNS unsuccessful, fall back to mDNS (full timeout per service)
return resolve_service_(discovery, service, std::string("local."), versions, priorities, protocols, authorization, true, timeout_dur, token);
});
}

return resolve_service_(discovery, service, browse_domain, versions, priorities, protocols, authorization, true, std::chrono::duration_cast<std::chrono::steady_clock::duration>(std::chrono::seconds(timeout)), token);
return primary_task;
}
}
}
35 changes: 23 additions & 12 deletions Development/nmos/mdns.h
Original file line number Diff line number Diff line change
Expand Up @@ -157,39 +157,50 @@ namespace nmos
// helper function for updating the specified service (API) TXT records
void update_service(mdns::service_advertiser& advertiser, const nmos::service_type& service, const nmos::settings& settings, mdns::structured_txt_records add_records = {});

// helper function for resolving instances of the specified service (API)
// with the highest version, highest priority instances at the front, and (by default) services with the same priority ordered randomly
// DEPRECATED: this overload is unused; prefer resolve_service_ which also returns api_ver/priority info.
// Helper function for resolving instances of the specified service (API), returning a list of base URIs
// (with the API version path appended), highest version and highest priority first, and (by default)
// services with the same priority ordered randomly.
pplx::task<std::list<web::uri>> resolve_service(mdns::service_discovery& discovery, const nmos::service_type& service, const std::string& browse_domain, const std::set<nmos::api_version>& api_ver, const std::pair<nmos::service_priority, nmos::service_priority>& priorities, const std::set<nmos::service_protocol>& api_proto, const std::set<bool>& api_auth, bool randomize, const std::chrono::steady_clock::duration& timeout, const pplx::cancellation_token& token = pplx::cancellation_token::none());

// helper function for resolving instances of the specified service (API) based on the specified options or defaults
// with the highest version, highest priority instances at the front, and (by default) services with the same priority ordered randomly
// DEPRECATED: convenience wrapper around the deprecated explicit-args resolve_service overload above.
// Same behaviour, with default values for unspecified arguments.
template <typename Rep = std::chrono::seconds::rep, typename Period = std::chrono::seconds::period>
inline pplx::task<std::list<web::uri>> resolve_service(mdns::service_discovery& discovery, const nmos::service_type& service, const std::string& browse_domain = {}, const std::set<nmos::api_version>& api_ver = nmos::is04_versions::all, const std::pair<nmos::service_priority, nmos::service_priority>& priorities = { service_priorities::highest_active_priority, service_priorities::no_priority }, const std::set<nmos::service_protocol>& api_proto = nmos::service_protocols::all, const std::set<bool>& api_auth = { false, true }, bool randomize = true, const std::chrono::duration<Rep, Period>& timeout = std::chrono::seconds(mdns::default_timeout_seconds), const pplx::cancellation_token& token = pplx::cancellation_token::none())
{
return resolve_service(discovery, service, browse_domain, api_ver, api_proto, api_auth, randomize, std::chrono::duration_cast<std::chrono::steady_clock::duration>(timeout), token);
}

// helper function for resolving instances of the specified service (API) based on the specified settings
// with the highest version, highest priority instances at the front, and services with the same priority ordered randomly
// Helper function for resolving instances of the specified service (API) based on the specified settings,
// returning a list of base URIs (with the API version path appended), highest version and highest priority
// first, services with the same priority ordered randomly.
// The browse method is selected by the dns_sd_browse_mode setting per TR-10-9 Section 15
// (delegates to resolve_service_, which carries the dual-discovery logic).
pplx::task<std::list<web::uri>> resolve_service(mdns::service_discovery& discovery, const nmos::service_type& service, const nmos::settings& settings, const pplx::cancellation_token& token = pplx::cancellation_token::none());

typedef std::pair<api_version, service_priority> api_ver_pri;
typedef std::pair<api_ver_pri, web::uri> resolved_service;

// helper function for resolving instances of the specified service (API)
// with the highest version, highest priority instances at the front, and (by default) services with the same priority ordered randomly
// Helper function for resolving instances of the specified service (API), returning ((api_version, priority), uri)
// tuples so callers can inspect the matched version and priority. Highest version and highest priority first,
// and (by default) services with the same priority ordered randomly.
pplx::task<std::list<resolved_service>> resolve_service_(mdns::service_discovery& discovery, const nmos::service_type& service, const std::string& browse_domain, const std::set<nmos::api_version>& api_ver, const std::pair<nmos::service_priority, nmos::service_priority>& priorities, const std::set<nmos::service_protocol>& api_proto, const std::set<bool>& api_auth, bool randomize, const std::chrono::steady_clock::duration& timeout, const pplx::cancellation_token& token = pplx::cancellation_token::none());

// helper function for resolving instances of the specified service (API) based on the specified options or defaults
// with the highest version, highest priority instances at the front, and (by default) services with the same priority ordered randomly
// Convenience wrapper around the explicit-args resolve_service_ overload above, with default values
// for unspecified arguments.
template <typename Rep = std::chrono::seconds::rep, typename Period = std::chrono::seconds::period>
inline pplx::task<std::list<resolved_service>> resolve_service_(mdns::service_discovery& discovery, const nmos::service_type& service, const std::string& browse_domain = {}, const std::set<nmos::api_version>& api_ver = nmos::is04_versions::all, const std::pair<nmos::service_priority, nmos::service_priority>& priorities = { service_priorities::highest_active_priority, service_priorities::no_priority }, const std::set<nmos::service_protocol>& api_proto = nmos::service_protocols::all, const std::set<bool>& api_auth = { false, true }, bool randomize = true, const std::chrono::duration<Rep, Period>& timeout = std::chrono::seconds(mdns::default_timeout_seconds), const pplx::cancellation_token& token = pplx::cancellation_token::none())
{
return resolve_service_(discovery, service, browse_domain, api_ver, api_proto, api_auth, randomize, std::chrono::duration_cast<std::chrono::steady_clock::duration>(timeout), token);
}

// helper function for resolving instances of the specified service (API) based on the specified settings
// with the highest version, highest priority instances at the front, and services with the same priority ordered randomly
// Helper function for resolving instances of the specified service (API) based on the specified settings,
// returning ((api_version, priority), uri) tuples. Highest version and highest priority first, services
// with the same priority ordered randomly.
// The browse method is selected by the dns_sd_browse_mode setting per TR-10-9 Section 15:
// - both (default): unicast DNS first, mDNS fallback if unsuccessful
// - unicast : unicast DNS only
// - mdns : mDNS only
pplx::task<std::list<resolved_service>> resolve_service_(mdns::service_discovery& discovery, const nmos::service_type& service, const nmos::settings& settings, const pplx::cancellation_token& token = pplx::cancellation_token::none());
}
}
Expand Down
15 changes: 15 additions & 0 deletions Development/nmos/settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,21 @@ namespace nmos
// domain [registry, node]: the domain on which to browse for services or an empty string to use the default domain (specify "local." to explictly select mDNS)
const web::json::field_as_string_or domain{ U("domain"), U("") };

// dns_sd_browse_mode [node]: DNS-SD browse method per TR-10-9 Section 15
// both(0) (default) = unicast DNS first, mDNS fallback if unsuccessful
// unicast(1) = unicast DNS only
// mdns(2) = mDNS only
// Expected resolve behaviour for each (mode, domain) combination:
// mode | domain | behaviour
// --------+-------------+--------------------
// both | example.com | unicast -> mdns
// both | local. | mdns
// unicast | example.com | unicast
// unicast | local. | mdns
// mdns | example.com | mdns
// mdns | local. | mdns
const web::json::field_as_integer_or dns_sd_browse_mode{ U("dns_sd_browse_mode"), 0 };

// host_address/host_addresses [registry, node]: IP addresses used to construct response headers (e.g. 'Link' or 'Location'), and host and URL fields in the data model
const web::json::field_as_string_or host_address{ U("host_address"), U("127.0.0.1") };
const web::json::field_as_array host_addresses{ U("host_addresses") };
Expand Down
Loading