diff --git a/Development/nmos-cpp-node/config.json b/Development/nmos-cpp-node/config.json index c529de695..43347c669 100644 --- a/Development/nmos-cpp-node/config.json +++ b/Development/nmos-cpp-node/config.json @@ -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, diff --git a/Development/nmos/mdns.cpp b/Development/nmos/mdns.cpp index 2549e5f43..057a799e2 100644 --- a/Development/nmos/mdns.cpp +++ b/Development/nmos/mdns.cpp @@ -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 @@ -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)." @@ -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> resolve_service_(mdns::service_discovery& discovery, const nmos::service_type& service, const std::string& browse_domain, const std::set& api_ver, const std::pair& priorities, const std::set& api_proto, const std::set& 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; @@ -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> resolve_service(mdns::service_discovery& discovery, const nmos::service_type& service, const std::string& browse_domain, const std::set& api_ver, const std::pair& priorities, const std::set& api_proto, const std::set& 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_services) @@ -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> 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::get_service_protocol(service, settings) }; - const auto authorization = std::set{ 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::seconds(timeout)), token); + return resolve_service_(discovery, service, settings, token).then([](std::list resolved_services) + { + return boost::copy_range>(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> 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::get_service_protocol(service, settings) }; @@ -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::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 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::seconds(timeout)), token); + return primary_task; } } } diff --git a/Development/nmos/mdns.h b/Development/nmos/mdns.h index 8267ab850..9d973b3c7 100644 --- a/Development/nmos/mdns.h +++ b/Development/nmos/mdns.h @@ -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> resolve_service(mdns::service_discovery& discovery, const nmos::service_type& service, const std::string& browse_domain, const std::set& api_ver, const std::pair& priorities, const std::set& api_proto, const std::set& 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 inline pplx::task> resolve_service(mdns::service_discovery& discovery, const nmos::service_type& service, const std::string& browse_domain = {}, const std::set& api_ver = nmos::is04_versions::all, const std::pair& priorities = { service_priorities::highest_active_priority, service_priorities::no_priority }, const std::set& api_proto = nmos::service_protocols::all, const std::set& api_auth = { false, true }, bool randomize = true, const std::chrono::duration& 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(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> 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_ver_pri; typedef std::pair 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> resolve_service_(mdns::service_discovery& discovery, const nmos::service_type& service, const std::string& browse_domain, const std::set& api_ver, const std::pair& priorities, const std::set& api_proto, const std::set& 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 inline pplx::task> resolve_service_(mdns::service_discovery& discovery, const nmos::service_type& service, const std::string& browse_domain = {}, const std::set& api_ver = nmos::is04_versions::all, const std::pair& priorities = { service_priorities::highest_active_priority, service_priorities::no_priority }, const std::set& api_proto = nmos::service_protocols::all, const std::set& api_auth = { false, true }, bool randomize = true, const std::chrono::duration& 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(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> resolve_service_(mdns::service_discovery& discovery, const nmos::service_type& service, const nmos::settings& settings, const pplx::cancellation_token& token = pplx::cancellation_token::none()); } } diff --git a/Development/nmos/settings.h b/Development/nmos/settings.h index 4f1a09328..02549d927 100644 --- a/Development/nmos/settings.h +++ b/Development/nmos/settings.h @@ -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") };