Skip to content

Commit fb842e3

Browse files
Semyon Barenboymsimonbaren
authored andcommitted
Implement TR-10-9 Section 15 DNS-SD browse strategy
TR-10-9 Section 15 requires IPMX devices to: - Support both mDNS and unicast DNS for DNS-SD browse operations - Default to using both methods - Provide user mechanisms to limit browsing to unicast or mDNS only - When using both: try unicast DNS first, fall back to mDNS only if unicast is unsuccessful (no service discovered) - When multiple results are returned: select by best priority, failing over to next-best if unresponsive - Once a service is selected and responsive: perform no further browse operations for that service type - If the selected service becomes unresponsive: perform a new DNS-SD browse and restart selection Add dns_sd_browse_mode setting ("both"/"unicast"/"mdns") to implement the required dual-discovery strategy. In "both" mode (default), unicast DNS is tried first with half the timeout budget; if no records are found, mDNS fallback gets the remaining half. "unicast" and "mdns" modes restrict to a single method. Also filters browse results by domain class to prevent mDNS results leaking into unicast DNS queries and vice versa. Signed-off-by: Semyon Barenboym <semyon.barenboym@ienso.com>
1 parent 20cb0c5 commit fb842e3

File tree

2 files changed

+52
-12
lines changed

2 files changed

+52
-12
lines changed

Development/nmos/mdns.cpp

Lines changed: 46 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,16 @@ namespace nmos
503503
{
504504
return discovery.browse([=, &discovery](const mdns::browse_result& resolving)
505505
{
506+
// Skip results from a different discovery domain class
507+
// (prevents mDNS results leaking into unicast DNS queries and vice versa)
508+
if (!browse_domain.empty())
509+
{
510+
if (is_local_domain(browse_domain) != is_local_domain(resolving.domain))
511+
{
512+
return true; // skip this result, keep browsing
513+
}
514+
}
515+
506516
const bool cancel = pplx::canceled == discovery.resolve([=](const mdns::resolve_result& resolved)
507517
{
508518
// "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)."
@@ -712,26 +722,26 @@ namespace nmos
712722

713723
// helper function for resolving instances of the specified service (API) based on the specified settings
714724
// with the highest version, highest priority instances at the front, and services with the same priority ordered randomly
725+
// TR-10-9 Section 15: supports "both" (unicast DNS first, mDNS fallback), "unicast", and "mdns" browse modes
715726
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)
716727
{
717-
const auto browse_domain = utility::us2s(nmos::get_domain(settings));
718-
const auto versions = details::service_versions(service, settings);
719-
const auto priorities = details::service_priorities(service, settings);
720-
const auto protocols = std::set<nmos::service_protocol>{ nmos::get_service_protocol(service, settings) };
721-
const auto authorization = std::set<bool>{ nmos::get_service_authorization(service, settings) };
722-
723-
// use a short timeout that's long enough to ensure the daemon's cache is exhausted
724-
// when no cancellation token is specified
725-
const auto timeout = token.is_cancelable() ? nmos::fields::discovery_backoff_max(settings) : 1;
726-
727-
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);
728+
// delegate to resolve_service_ (which has the dual-discovery logic) and transform results
729+
return resolve_service_(discovery, service, settings, token).then([](std::list<resolved_service> resolved_services)
730+
{
731+
return boost::copy_range<std::list<web::uri>>(resolved_services | boost::adaptors::transformed([](const resolved_service& s)
732+
{
733+
return web::uri_builder(s.second).append_path(U("/") + make_api_version(s.first.first)).to_uri();
734+
}));
735+
});
728736
}
729737

730738
// helper function for resolving instances of the specified service (API) based on the specified settings
731739
// with the highest version, highest priority instances at the front, and services with the same priority ordered randomly
740+
// TR-10-9 Section 15: supports "both" (unicast DNS first, mDNS fallback), "unicast", and "mdns" browse modes
732741
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)
733742
{
734743
const auto browse_domain = utility::us2s(nmos::get_domain(settings));
744+
const auto browse_mode = utility::us2s(nmos::fields::dns_sd_browse_mode(settings));
735745
const auto versions = details::service_versions(service, settings);
736746
const auto priorities = details::service_priorities(service, settings);
737747
const auto protocols = std::set<nmos::service_protocol>{ nmos::get_service_protocol(service, settings) };
@@ -740,8 +750,32 @@ namespace nmos
740750
// use a short timeout that's long enough to ensure the daemon's cache is exhausted
741751
// when no cancellation token is specified
742752
const auto timeout = token.is_cancelable() ? nmos::fields::discovery_backoff_max(settings) : 1;
753+
const auto timeout_dur = std::chrono::duration_cast<std::chrono::steady_clock::duration>(std::chrono::seconds(timeout));
754+
755+
// determine primary browse domain based on mode
756+
const auto primary_domain = (browse_mode == "mdns") ? std::string("local.") : browse_domain;
757+
const bool has_fallback = (browse_mode == "both") && !is_local_domain(browse_domain);
758+
759+
// when there's a fallback, give the primary browse half the budget
760+
// so the mDNS fallback gets a meaningful allocation
761+
const auto primary_timeout = has_fallback ? timeout_dur / 2 : timeout_dur;
762+
763+
auto primary_task = resolve_service_(discovery, service, primary_domain, versions, priorities, protocols, authorization, true, primary_timeout, token);
764+
765+
if (has_fallback)
766+
{
767+
return primary_task.then([&discovery, service, versions, priorities, protocols, authorization, timeout_dur, primary_timeout, token](std::list<resolved_service> results)
768+
{
769+
if (!results.empty()) return pplx::task_from_result(std::move(results));
770+
771+
// TR-10-9: unicast DNS unsuccessful, fall back to mDNS
772+
// give the fallback at least as much time as the primary browse had
773+
const auto fallback_timeout = timeout_dur - primary_timeout;
774+
return resolve_service_(discovery, service, std::string("local."), versions, priorities, protocols, authorization, true, fallback_timeout, token);
775+
});
776+
}
743777

744-
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);
778+
return primary_task;
745779
}
746780
}
747781
}

Development/nmos/settings.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,12 @@ namespace nmos
8282
// 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)
8383
const web::json::field_as_string_or domain{ U("domain"), U("") };
8484

85+
// dns_sd_browse_mode [node]: controls DNS-SD browse method per TR-10-9 Section 15
86+
// "both" (default) = unicast DNS first, mDNS fallback if unsuccessful
87+
// "unicast" = unicast DNS only (requires domain to be set to a non-local value)
88+
// "mdns" = mDNS only
89+
const web::json::field_as_string_or dns_sd_browse_mode{ U("dns_sd_browse_mode"), U("both") };
90+
8591
// 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
8692
const web::json::field_as_string_or host_address{ U("host_address"), U("127.0.0.1") };
8793
const web::json::field_as_array host_addresses{ U("host_addresses") };

0 commit comments

Comments
 (0)