1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/extensions/api/mdns/mdns_api.h"
9 #include "base/lazy_instance.h"
10 #include "base/strings/stringprintf.h"
11 #include "chrome/browser/extensions/extension_service.h"
12 #include "content/public/browser/render_frame_host.h"
13 #include "content/public/browser/render_process_host.h"
14 #include "content/public/browser/web_contents.h"
15 #include "extensions/browser/extension_function.h"
16 #include "extensions/browser/extension_host.h"
17 #include "extensions/browser/extension_registry.h"
19 namespace extensions
{
21 namespace mdns
= api::mdns
;
25 // Whitelisted mDNS service types.
26 const char kCastServiceType
[] = "_googlecast._tcp.local";
27 const char kPrivetServiceType
[] = "_privet._tcp.local";
28 const char kTestServiceType
[] = "_testing._tcp.local";
30 bool IsServiceTypeWhitelisted(const std::string
& service_type
) {
31 return service_type
== kCastServiceType
||
32 service_type
== kPrivetServiceType
||
33 service_type
== kTestServiceType
;
38 MDnsAPI::MDnsAPI(content::BrowserContext
* context
) : browser_context_(context
) {
39 DCHECK(browser_context_
);
40 extensions::EventRouter
* event_router
= EventRouter::Get(context
);
42 event_router
->RegisterObserver(this, mdns::OnServiceList::kEventName
);
46 if (dns_sd_registry_
.get()) {
47 dns_sd_registry_
->RemoveObserver(this);
52 MDnsAPI
* MDnsAPI::Get(content::BrowserContext
* context
) {
53 return BrowserContextKeyedAPIFactory
<MDnsAPI
>::Get(context
);
56 static base::LazyInstance
<BrowserContextKeyedAPIFactory
<MDnsAPI
> > g_factory
=
57 LAZY_INSTANCE_INITIALIZER
;
60 BrowserContextKeyedAPIFactory
<MDnsAPI
>* MDnsAPI::GetFactoryInstance() {
61 return g_factory
.Pointer();
64 void MDnsAPI::SetDnsSdRegistryForTesting(
65 scoped_ptr
<DnsSdRegistry
> dns_sd_registry
) {
66 dns_sd_registry_
= dns_sd_registry
.Pass();
67 if (dns_sd_registry_
.get())
68 dns_sd_registry_
.get()->AddObserver(this);
71 void MDnsAPI::ForceDiscovery() {
72 DCHECK(thread_checker_
.CalledOnValidThread());
73 DnsSdRegistry
* registry
= dns_sd_registry();
74 return registry
->ForceDiscovery();
77 DnsSdRegistry
* MDnsAPI::dns_sd_registry() {
78 DCHECK(thread_checker_
.CalledOnValidThread());
79 if (!dns_sd_registry_
.get()) {
80 dns_sd_registry_
.reset(new extensions::DnsSdRegistry());
81 dns_sd_registry_
->AddObserver(this);
83 return dns_sd_registry_
.get();
86 void MDnsAPI::OnListenerAdded(const EventListenerInfo
& details
) {
87 DCHECK(thread_checker_
.CalledOnValidThread());
88 UpdateMDnsListeners();
91 void MDnsAPI::OnListenerRemoved(const EventListenerInfo
& details
) {
92 DCHECK(thread_checker_
.CalledOnValidThread());
93 UpdateMDnsListeners();
96 void MDnsAPI::UpdateMDnsListeners() {
97 std::set
<std::string
> new_service_types
;
98 ServiceTypeCounts current_service_counts
;
99 GetValidOnServiceListListeners(
100 "" /* service_type_filter - blank = all services */,
101 nullptr /* extension_ids */, ¤t_service_counts
);
103 DnsSdRegistry
* registry
= dns_sd_registry();
105 // Check if the counts of per-service-type event handlers has changed since
106 // the previous invocation, and take appropriate action if a change was
109 // mDNS registration is performed for difference(cur, previous).
110 // mDNS unregistration is performed for difference(previous, cur).
111 // The mDNS device list is refreshed if the listener count has grown for
112 // a service type in union(cur, previous).
113 ServiceTypeCounts::iterator i_cur
= current_service_counts
.begin();
114 ServiceTypeCounts::iterator i_prev
= prev_service_counts_
.begin();
115 while (i_cur
!= current_service_counts
.end() ||
116 i_prev
!= prev_service_counts_
.end()) {
117 if (i_prev
== prev_service_counts_
.end() ||
118 (i_cur
!= current_service_counts
.end() &&
119 i_cur
->first
< i_prev
->first
)) {
120 DVLOG(2) << "Registering listener for mDNS service " << i_cur
->first
;
121 registry
->RegisterDnsSdListener(i_cur
->first
);
123 } else if (i_cur
== current_service_counts
.end() ||
124 (i_prev
!= prev_service_counts_
.end() &&
125 i_prev
->first
< i_cur
->first
)) {
126 DVLOG(2) << "Unregistering listener for mDNS service " << i_prev
->first
;
127 registry
->UnregisterDnsSdListener(i_prev
->first
);
130 if (i_cur
->second
> i_prev
->second
) {
131 DVLOG(2) << "Additional listeners added for mDNS service "
133 registry
->Publish(i_cur
->first
);
139 prev_service_counts_
.swap(current_service_counts
);
142 void MDnsAPI::OnDnsSdEvent(const std::string
& service_type
,
143 const DnsSdRegistry::DnsSdServiceList
& services
) {
144 DCHECK(thread_checker_
.CalledOnValidThread());
146 std::vector
<linked_ptr
<mdns::MDnsService
> > args
;
147 for (DnsSdRegistry::DnsSdServiceList::const_iterator it
= services
.begin();
148 it
!= services
.end(); ++it
) {
149 if (static_cast<int>(args
.size()) ==
150 api::mdns::MAX_SERVICE_INSTANCES_PER_EVENT
) {
151 // TODO(reddaly): This is not the most meaningful way of notifying the
152 // application that something bad happened. It will go to the user's
153 // console (which most users don't look at)and the developer will be none
154 // the wiser. Instead, changing the event to pass the number of
155 // discovered instances would allow the caller to know when the list is
156 // truncated and tell the user something meaningful in the extension/app.
157 WriteToConsole(service_type
,
158 content::CONSOLE_MESSAGE_LEVEL_WARNING
,
160 "Truncating number of service instances in "
161 "onServiceList to maximum allowed: %d",
162 api::mdns::MAX_SERVICE_INSTANCES_PER_EVENT
));
165 linked_ptr
<mdns::MDnsService
> mdns_service
=
166 make_linked_ptr(new mdns::MDnsService
);
167 mdns_service
->service_name
= (*it
).service_name
;
168 mdns_service
->service_host_port
= (*it
).service_host_port
;
169 mdns_service
->ip_address
= (*it
).ip_address
;
170 mdns_service
->service_data
= (*it
).service_data
;
171 args
.push_back(mdns_service
);
174 scoped_ptr
<base::ListValue
> results
= mdns::OnServiceList::Create(args
);
175 scoped_ptr
<Event
> event(new Event(events::MDNS_ON_SERVICE_LIST
,
176 mdns::OnServiceList::kEventName
,
178 event
->restrict_to_browser_context
= browser_context_
;
179 event
->filter_info
.SetServiceType(service_type
);
181 // TODO(justinlin): To avoid having listeners without filters getting all
182 // events, modify API to have this event require filters.
183 // TODO(reddaly): If event isn't on whitelist, ensure it does not get
184 // broadcast to extensions.
185 extensions::EventRouter::Get(browser_context_
)->BroadcastEvent(event
.Pass());
188 const extensions::EventListenerMap::ListenerList
& MDnsAPI::GetEventListeners() {
189 return extensions::EventRouter::Get(browser_context_
)
191 .GetEventListenersByName(mdns::OnServiceList::kEventName
);
194 bool MDnsAPI::IsMDnsAllowed(const std::string
& extension_id
,
195 const std::string
& service_type
) const {
196 const extensions::Extension
* extension
=
197 ExtensionRegistry::Get(browser_context_
)
198 ->enabled_extensions()
199 .GetByID(extension_id
);
200 return (extension
&& (extension
->is_platform_app() ||
201 IsServiceTypeWhitelisted(service_type
)));
204 void MDnsAPI::GetValidOnServiceListListeners(
205 const std::string
& service_type_filter
,
206 std::set
<std::string
>* extension_ids
,
207 ServiceTypeCounts
* service_type_counts
) {
208 for (const auto& listener
: GetEventListeners()) {
209 base::DictionaryValue
* filter
= listener
->filter();
211 std::string service_type
;
212 filter
->GetStringASCII(kEventFilterServiceTypeKey
, &service_type
);
213 if (service_type
.empty())
216 // Match service type when filter isn't ""
217 if (!service_type_filter
.empty() && service_type_filter
!= service_type
)
220 // Don't listen for services associated only with disabled extensions
221 // or non-whitelisted, non-platform-app extensions.
222 if (!IsMDnsAllowed(listener
->extension_id(), service_type
))
226 extension_ids
->insert(listener
->extension_id());
227 if (service_type_counts
) {
228 (*service_type_counts
)[service_type
]++;
233 void MDnsAPI::WriteToConsole(const std::string
& service_type
,
234 content::ConsoleMessageLevel level
,
235 const std::string
& message
) {
236 // Get all the extensions with an onServiceList listener for a particular
238 std::set
<std::string
> extension_ids
;
239 ServiceTypeCounts counts
;
240 GetValidOnServiceListListeners(service_type
, &extension_ids
,
241 nullptr /* service_type_counts */);
243 std::string
logged_message(std::string("[chrome.mdns] ") + message
);
245 // Log to the consoles of the background pages for those extensions.
246 // TODO(devlin): It's a little weird to log to the background pages,
247 // especially when it might be dormant. We should probably just log to a place
248 // like the ErrorConsole instead.
249 for (const std::string
& extension_id
: extension_ids
) {
250 extensions::ExtensionHost
* host
=
251 extensions::ProcessManager::Get(browser_context_
)
252 ->GetBackgroundHostForExtension(extension_id
);
253 content::RenderFrameHost
* rfh
=
254 host
? host
->host_contents()->GetMainFrame() : nullptr;
256 rfh
->AddMessageToConsole(level
, logged_message
);
260 MdnsForceDiscoveryFunction::MdnsForceDiscoveryFunction() {
263 MdnsForceDiscoveryFunction::~MdnsForceDiscoveryFunction() {
266 AsyncApiFunction::ResponseAction
MdnsForceDiscoveryFunction::Run() {
267 MDnsAPI
* api
= MDnsAPI::Get(browser_context());
269 return RespondNow(Error("Unknown error."));
271 api
->ForceDiscovery();
272 return RespondNow(NoArguments());
275 } // namespace extensions