• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2014 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.
4
5#include "components/wifi/wifi_service.h"
6
7#import <netinet/in.h>
8#import <CoreWLAN/CoreWLAN.h>
9#import <SystemConfiguration/SystemConfiguration.h>
10
11#include "base/bind.h"
12#include "base/mac/foundation_util.h"
13#include "base/mac/scoped_cftyperef.h"
14#include "base/mac/scoped_nsobject.h"
15#include "base/mac/sdk_forward_declarations.h"
16#include "base/message_loop/message_loop.h"
17#include "base/strings/sys_string_conversions.h"
18#include "components/onc/onc_constants.h"
19#include "components/wifi/network_properties.h"
20
21namespace wifi {
22
23// Implementation of WiFiService for Mac OS X.
24class WiFiServiceMac : public WiFiService {
25 public:
26  WiFiServiceMac();
27  virtual ~WiFiServiceMac();
28
29  // WiFiService interface implementation.
30  virtual void Initialize(
31      scoped_refptr<base::SequencedTaskRunner> task_runner) OVERRIDE;
32
33  virtual void UnInitialize() OVERRIDE;
34
35  virtual void GetProperties(const std::string& network_guid,
36                             base::DictionaryValue* properties,
37                             std::string* error) OVERRIDE;
38
39  virtual void GetManagedProperties(const std::string& network_guid,
40                                    base::DictionaryValue* managed_properties,
41                                    std::string* error) OVERRIDE;
42
43  virtual void GetState(const std::string& network_guid,
44                        base::DictionaryValue* properties,
45                        std::string* error) OVERRIDE;
46
47  virtual void SetProperties(const std::string& network_guid,
48                             scoped_ptr<base::DictionaryValue> properties,
49                             std::string* error) OVERRIDE;
50
51  virtual void CreateNetwork(bool shared,
52                             scoped_ptr<base::DictionaryValue> properties,
53                             std::string* network_guid,
54                             std::string* error) OVERRIDE;
55
56  virtual void GetVisibleNetworks(const std::string& network_type,
57                                  base::ListValue* network_list,
58                                  bool include_details) OVERRIDE;
59
60  virtual void RequestNetworkScan() OVERRIDE;
61
62  virtual void StartConnect(const std::string& network_guid,
63                            std::string* error) OVERRIDE;
64
65  virtual void StartDisconnect(const std::string& network_guid,
66                               std::string* error) OVERRIDE;
67
68  virtual void GetKeyFromSystem(const std::string& network_guid,
69                                std::string* key_data,
70                                std::string* error) OVERRIDE;
71
72  virtual void SetEventObservers(
73      scoped_refptr<base::MessageLoopProxy> message_loop_proxy,
74      const NetworkGuidListCallback& networks_changed_observer,
75      const NetworkGuidListCallback& network_list_changed_observer) OVERRIDE;
76
77  virtual void RequestConnectedNetworkUpdate() OVERRIDE;
78
79 private:
80  // Checks |ns_error| and if is not |nil|, then stores |error_name|
81  // into |error|.
82  bool CheckError(NSError* ns_error,
83                  const char* error_name,
84                  std::string* error) const;
85
86  // Gets |ssid| from unique |network_guid|.
87  NSString* SSIDFromGUID(const std::string& network_guid) const {
88    return base::SysUTF8ToNSString(network_guid);
89  }
90
91  // Gets unique |network_guid| string based on |ssid|.
92  std::string GUIDFromSSID(NSString* ssid) const {
93    return base::SysNSStringToUTF8(ssid);
94  }
95
96  // Populates |properties| from |network|.
97  void NetworkPropertiesFromCWNetwork(const CWNetwork* network,
98                                      NetworkProperties* properties) const;
99
100  // Converts |CWSecurityMode| into onc::wifi::k{WPA|WEP}* security constant.
101  std::string SecurityFromCWSecurityMode(CWSecurityMode security) const;
102
103  // Returns onc::wifi::k{WPA|WEP}* security constant supported by the
104  // |CWNetwork|.
105  std::string SecurityFromCWNetwork(const CWNetwork* network) const;
106
107  // Converts |CWChannelBand| into Frequency constant.
108  Frequency FrequencyFromCWChannelBand(CWChannelBand band) const;
109
110  // Gets current |onc::connection_state| for given |network_guid|.
111  std::string GetNetworkConnectionState(const std::string& network_guid) const;
112
113  // Updates |networks_| with the list of visible wireless networks.
114  void UpdateNetworks();
115
116  // Find network by |network_guid| and return iterator to its entry in
117  // |networks_|.
118  NetworkList::iterator FindNetwork(const std::string& network_guid);
119
120  // Handles notification from |wlan_observer_|.
121  void OnWlanObserverNotification();
122
123  // Notifies |network_list_changed_observer_| that list of visible networks has
124  // changed to |networks|.
125  void NotifyNetworkListChanged(const NetworkList& networks);
126
127  // Notifies |networks_changed_observer_| that network |network_guid|
128  // connection state has changed.
129  void NotifyNetworkChanged(const std::string& network_guid);
130
131  // Default interface.
132  base::scoped_nsobject<CWInterface> interface_;
133  // WLAN Notifications observer. |this| doesn't own this reference.
134  id wlan_observer_;
135
136  // Observer to get notified when network(s) have changed (e.g. connect).
137  NetworkGuidListCallback networks_changed_observer_;
138  // Observer to get notified when network list has changed.
139  NetworkGuidListCallback network_list_changed_observer_;
140  // MessageLoopProxy to which events should be posted.
141  scoped_refptr<base::MessageLoopProxy> message_loop_proxy_;
142  // Task runner for worker tasks.
143  scoped_refptr<base::SequencedTaskRunner> task_runner_;
144  // Cached list of visible networks. Updated by |UpdateNetworks|.
145  NetworkList networks_;
146  // Guid of last known connected network.
147  std::string connected_network_guid_;
148  // Temporary storage of network properties indexed by |network_guid|.
149  base::DictionaryValue network_properties_;
150
151  DISALLOW_COPY_AND_ASSIGN(WiFiServiceMac);
152};
153
154WiFiServiceMac::WiFiServiceMac() : wlan_observer_(nil) {
155}
156
157WiFiServiceMac::~WiFiServiceMac() {
158}
159
160void WiFiServiceMac::Initialize(
161  scoped_refptr<base::SequencedTaskRunner> task_runner) {
162  task_runner_.swap(task_runner);
163  interface_.reset([[CWInterface interface] retain]);
164  if (!interface_) {
165    DVLOG(1) << "Failed to initialize default interface.";
166    return;
167  }
168
169  if (![interface_
170          respondsToSelector:@selector(associateToNetwork:password:error:)]) {
171    DVLOG(1) << "CWInterface does not support associateToNetwork.";
172    interface_.reset();
173    return;
174  }
175}
176
177void WiFiServiceMac::UnInitialize() {
178  if (wlan_observer_)
179    [[NSNotificationCenter defaultCenter] removeObserver:wlan_observer_];
180  interface_.reset();
181}
182
183void WiFiServiceMac::GetProperties(const std::string& network_guid,
184                                   base::DictionaryValue* properties,
185                                   std::string* error) {
186  NetworkList::iterator it = FindNetwork(network_guid);
187  if (it == networks_.end()) {
188    DVLOG(1) << "Network not found:" << network_guid;
189    *error = kErrorNotFound;
190    return;
191  }
192
193  it->connection_state = GetNetworkConnectionState(network_guid);
194  scoped_ptr<base::DictionaryValue> network(it->ToValue(false));
195  properties->Swap(network.get());
196  DVLOG(1) << *properties;
197}
198
199void WiFiServiceMac::GetManagedProperties(
200    const std::string& network_guid,
201    base::DictionaryValue* managed_properties,
202    std::string* error) {
203  *error = kErrorNotImplemented;
204}
205
206void WiFiServiceMac::GetState(const std::string& network_guid,
207                              base::DictionaryValue* properties,
208                              std::string* error) {
209  *error = kErrorNotImplemented;
210}
211
212void WiFiServiceMac::SetProperties(
213    const std::string& network_guid,
214    scoped_ptr<base::DictionaryValue> properties,
215    std::string* error) {
216  base::DictionaryValue* existing_properties;
217  // If the network properties already exist, don't override previously set
218  // properties, unless they are set in |properties|.
219  if (network_properties_.GetDictionaryWithoutPathExpansion(
220          network_guid, &existing_properties)) {
221    existing_properties->MergeDictionary(properties.get());
222  } else {
223    network_properties_.SetWithoutPathExpansion(network_guid,
224                                                properties.release());
225  }
226}
227
228void WiFiServiceMac::CreateNetwork(
229    bool shared,
230    scoped_ptr<base::DictionaryValue> properties,
231    std::string* network_guid,
232    std::string* error) {
233  NetworkProperties network_properties;
234  if (!network_properties.UpdateFromValue(*properties)) {
235    *error = kErrorInvalidData;
236    return;
237  }
238
239  std::string guid = network_properties.ssid;
240  if (FindNetwork(guid) != networks_.end()) {
241    *error = kErrorInvalidData;
242    return;
243  }
244  network_properties_.SetWithoutPathExpansion(guid,
245                                              properties.release());
246  *network_guid = guid;
247}
248
249void WiFiServiceMac::GetVisibleNetworks(const std::string& network_type,
250                                        base::ListValue* network_list,
251                                        bool include_details) {
252  if (!network_type.empty() &&
253      network_type != onc::network_type::kAllTypes &&
254      network_type != onc::network_type::kWiFi) {
255    return;
256  }
257
258  if (networks_.empty())
259    UpdateNetworks();
260
261  for (NetworkList::const_iterator it = networks_.begin();
262       it != networks_.end();
263       ++it) {
264    scoped_ptr<base::DictionaryValue> network(it->ToValue(!include_details));
265    network_list->Append(network.release());
266  }
267}
268
269void WiFiServiceMac::RequestNetworkScan() {
270  DVLOG(1) << "*** RequestNetworkScan";
271  UpdateNetworks();
272}
273
274void WiFiServiceMac::StartConnect(const std::string& network_guid,
275                                  std::string* error) {
276  NSError* ns_error = nil;
277
278  DVLOG(1) << "*** StartConnect: " << network_guid;
279  // Remember previously connected network.
280  std::string connected_network_guid = GUIDFromSSID([interface_ ssid]);
281  // Check whether desired network is already connected.
282  if (network_guid == connected_network_guid)
283    return;
284
285  NSSet* networks = [interface_
286      scanForNetworksWithName:SSIDFromGUID(network_guid)
287                        error:&ns_error];
288
289  if (CheckError(ns_error, kErrorScanForNetworksWithName, error))
290    return;
291
292  CWNetwork* network = [networks anyObject];
293  if (network == nil) {
294    // System can't find the network, remove it from the |networks_| and notify
295    // observers.
296    NetworkList::iterator it = FindNetwork(connected_network_guid);
297    if (it != networks_.end()) {
298      networks_.erase(it);
299      // Notify observers that list has changed.
300      NotifyNetworkListChanged(networks_);
301    }
302
303    *error = kErrorNotFound;
304    return;
305  }
306
307  // Check whether WiFi Password is set in |network_properties_|.
308  base::DictionaryValue* properties;
309  base::DictionaryValue* wifi;
310  std::string passphrase;
311  NSString* ns_password = nil;
312  if (network_properties_.GetDictionaryWithoutPathExpansion(network_guid,
313                                                            &properties) &&
314      properties->GetDictionary(onc::network_type::kWiFi, &wifi) &&
315      wifi->GetString(onc::wifi::kPassphrase, &passphrase)) {
316    ns_password = base::SysUTF8ToNSString(passphrase);
317  }
318
319  // Number of attempts to associate to network.
320  static const int kMaxAssociationAttempts = 3;
321  // Try to associate to network several times if timeout or PMK error occurs.
322  for (int i = 0; i < kMaxAssociationAttempts; ++i) {
323    // Nil out the PMK to prevent stale data from causing invalid PMK error
324    // (CoreWLANTypes -3924).
325    [interface_ setPairwiseMasterKey:nil error:&ns_error];
326    if (![interface_ associateToNetwork:network
327                              password:ns_password
328                                 error:&ns_error]) {
329      NSInteger error_code = [ns_error code];
330      if (error_code != kCWTimeoutErr && error_code != kCWInvalidPMKErr) {
331        break;
332      }
333    }
334  }
335  CheckError(ns_error, kErrorAssociateToNetwork, error);
336}
337
338void WiFiServiceMac::StartDisconnect(const std::string& network_guid,
339                                     std::string* error) {
340  DVLOG(1) << "*** StartDisconnect: " << network_guid;
341
342  if (network_guid == GUIDFromSSID([interface_ ssid])) {
343    // Power-cycle the interface to disconnect from current network and connect
344    // to default network.
345    NSError* ns_error = nil;
346    [interface_ setPower:NO error:&ns_error];
347    CheckError(ns_error, kErrorAssociateToNetwork, error);
348    [interface_ setPower:YES error:&ns_error];
349    CheckError(ns_error, kErrorAssociateToNetwork, error);
350  } else {
351    *error = kErrorNotConnected;
352  }
353}
354
355void WiFiServiceMac::GetKeyFromSystem(const std::string& network_guid,
356                                      std::string* key_data,
357                                      std::string* error) {
358  static const char kAirPortServiceName[] = "AirPort";
359
360  UInt32 password_length = 0;
361  void *password_data = NULL;
362  OSStatus status = SecKeychainFindGenericPassword(NULL,
363                                                   strlen(kAirPortServiceName),
364                                                   kAirPortServiceName,
365                                                   network_guid.length(),
366                                                   network_guid.c_str(),
367                                                   &password_length,
368                                                   &password_data,
369                                                   NULL);
370  if (status != errSecSuccess) {
371    *error = kErrorNotFound;
372    return;
373  }
374
375  if (password_data) {
376    *key_data = std::string(reinterpret_cast<char*>(password_data),
377                            password_length);
378    SecKeychainItemFreeContent(NULL, password_data);
379  }
380}
381
382void WiFiServiceMac::SetEventObservers(
383    scoped_refptr<base::MessageLoopProxy> message_loop_proxy,
384    const NetworkGuidListCallback& networks_changed_observer,
385    const NetworkGuidListCallback& network_list_changed_observer) {
386  message_loop_proxy_.swap(message_loop_proxy);
387  networks_changed_observer_ = networks_changed_observer;
388  network_list_changed_observer_ = network_list_changed_observer;
389
390  // Remove previous OS notifications observer.
391  if (wlan_observer_) {
392    [[NSNotificationCenter defaultCenter] removeObserver:wlan_observer_];
393    wlan_observer_ = nil;
394  }
395
396  // Subscribe to OS notifications.
397  if (!networks_changed_observer_.is_null()) {
398    void (^ns_observer) (NSNotification* notification) =
399        ^(NSNotification* notification) {
400            DVLOG(1) << "Received CWSSIDDidChangeNotification";
401            task_runner_->PostTask(
402                FROM_HERE,
403                base::Bind(&WiFiServiceMac::OnWlanObserverNotification,
404                           base::Unretained(this)));
405    };
406
407    wlan_observer_ = [[NSNotificationCenter defaultCenter]
408        addObserverForName:kCWSSIDDidChangeNotification
409                    object:nil
410                     queue:nil
411                usingBlock:ns_observer];
412  }
413}
414
415void WiFiServiceMac::RequestConnectedNetworkUpdate() {
416  OnWlanObserverNotification();
417}
418
419std::string WiFiServiceMac::GetNetworkConnectionState(
420    const std::string& network_guid) const {
421  if (network_guid != GUIDFromSSID([interface_ ssid]))
422    return onc::connection_state::kNotConnected;
423
424  // Check whether WiFi network is reachable.
425  struct sockaddr_in local_wifi_address;
426  bzero(&local_wifi_address, sizeof(local_wifi_address));
427  local_wifi_address.sin_len = sizeof(local_wifi_address);
428  local_wifi_address.sin_family = AF_INET;
429  local_wifi_address.sin_addr.s_addr = htonl(IN_LINKLOCALNETNUM);
430  base::ScopedCFTypeRef<SCNetworkReachabilityRef> reachability(
431      SCNetworkReachabilityCreateWithAddress(
432          kCFAllocatorDefault,
433          reinterpret_cast<const struct sockaddr*>(&local_wifi_address)));
434  SCNetworkReachabilityFlags flags = 0u;
435  if (SCNetworkReachabilityGetFlags(reachability, &flags) &&
436      (flags & kSCNetworkReachabilityFlagsReachable) &&
437      (flags & kSCNetworkReachabilityFlagsIsDirect)) {
438    // Network is reachable, report is as |kConnected|.
439    return onc::connection_state::kConnected;
440  }
441  // Network is not reachable yet, so it must be |kConnecting|.
442  return onc::connection_state::kConnecting;
443}
444
445void WiFiServiceMac::UpdateNetworks() {
446  NSError* ns_error = nil;
447  NSSet* cw_networks = [interface_ scanForNetworksWithName:nil
448                                                     error:&ns_error];
449  if (ns_error != nil)
450    return;
451
452  std::string connected_bssid = base::SysNSStringToUTF8([interface_ bssid]);
453  std::map<std::string, NetworkProperties*> network_properties_map;
454  networks_.clear();
455
456  // There is one |cw_network| per BSS in |cw_networks|, so go through the set
457  // and combine them, paying attention to supported frequencies.
458  for (CWNetwork* cw_network in cw_networks) {
459    std::string network_guid = GUIDFromSSID([cw_network ssid]);
460    bool update_all_properties = false;
461
462    if (network_properties_map.find(network_guid) ==
463            network_properties_map.end()) {
464      networks_.push_back(NetworkProperties());
465      network_properties_map[network_guid] = &networks_.back();
466      update_all_properties = true;
467    }
468    // If current network is connected, use its properties for this network.
469    if (base::SysNSStringToUTF8([cw_network bssid]) == connected_bssid)
470      update_all_properties = true;
471
472    NetworkProperties* properties = network_properties_map.at(network_guid);
473    if (update_all_properties) {
474      NetworkPropertiesFromCWNetwork(cw_network, properties);
475    } else {
476      properties->frequency_set.insert(FrequencyFromCWChannelBand(
477          [[cw_network wlanChannel] channelBand]));
478    }
479  }
480  // Sort networks, so connected/connecting is up front.
481  networks_.sort(NetworkProperties::OrderByType);
482  // Notify observers that list has changed.
483  NotifyNetworkListChanged(networks_);
484}
485
486bool WiFiServiceMac::CheckError(NSError* ns_error,
487                                const char* error_name,
488                                std::string* error) const {
489  if (ns_error != nil) {
490    DLOG(ERROR) << "*** Error:" << error_name << ":" << [ns_error code];
491    *error = error_name;
492    return true;
493  }
494  return false;
495}
496
497void WiFiServiceMac::NetworkPropertiesFromCWNetwork(
498    const CWNetwork* network,
499    NetworkProperties* properties) const {
500  std::string network_guid = GUIDFromSSID([network ssid]);
501
502  properties->connection_state = GetNetworkConnectionState(network_guid);
503  properties->ssid = base::SysNSStringToUTF8([network ssid]);
504  properties->name = properties->ssid;
505  properties->guid = network_guid;
506  properties->type = onc::network_type::kWiFi;
507
508  properties->bssid = base::SysNSStringToUTF8([network bssid]);
509  properties->frequency = FrequencyFromCWChannelBand(
510      static_cast<CWChannelBand>([[network wlanChannel] channelBand]));
511  properties->frequency_set.insert(properties->frequency);
512
513  // -[CWNetwork supportsSecurity:] is available from 10.7 SDK while
514  // -[CWNetwork securityMode] is deprecated and hidden as private since
515  // 10.9 SDK. The latter is kept for now to support running on 10.6. It
516  // should be removed when 10.6 support is dropped.
517  if ([network respondsToSelector:@selector(supportsSecurity:)]) {
518    properties->security = SecurityFromCWNetwork(network);
519  } else {
520    properties->security = SecurityFromCWSecurityMode(
521        static_cast<CWSecurityMode>([[network securityMode] intValue]));
522  }
523
524  // rssiValue property of CWNetwork is available from 10.7 SDK while
525  // -[CWNetwork rssi] is deprecated and hidden as private since 10.9 SDK.
526  // The latter is kept for now to support running on 10.6. It should be
527  // removed when 10.6 support is dropped.
528  if ([network respondsToSelector:@selector(rssiValue)])
529    properties->signal_strength = [network rssiValue];
530  else
531    properties->signal_strength = [[network rssi] intValue];
532}
533
534std::string WiFiServiceMac::SecurityFromCWSecurityMode(
535    CWSecurityMode security) const {
536  switch (security) {
537    case kCWSecurityModeWPA_Enterprise:
538    case kCWSecurityModeWPA2_Enterprise:
539      return onc::wifi::kWPA_EAP;
540    case kCWSecurityModeWPA_PSK:
541    case kCWSecurityModeWPA2_PSK:
542      return onc::wifi::kWPA_PSK;
543    case kCWSecurityModeWEP:
544      return onc::wifi::kWEP_PSK;
545    case kCWSecurityModeOpen:
546      return onc::wifi::kSecurityNone;
547    // TODO(mef): Figure out correct mapping.
548    case kCWSecurityModeWPS:
549    case kCWSecurityModeDynamicWEP:
550      return onc::wifi::kWPA_EAP;
551  }
552  return onc::wifi::kWPA_EAP;
553}
554
555std::string WiFiServiceMac::SecurityFromCWNetwork(
556    const CWNetwork* network) const {
557  if ([network supportsSecurity:kCWSecurityWPAEnterprise] ||
558      [network supportsSecurity:kCWSecurityWPA2Enterprise]) {
559    return onc::wifi::kWPA_EAP;
560  }
561
562  if ([network supportsSecurity:kCWSecurityWPAPersonal] ||
563      [network supportsSecurity:kCWSecurityWPA2Personal]) {
564    return onc::wifi::kWPA_PSK;
565  }
566
567  if ([network supportsSecurity:kCWSecurityWEP])
568    return onc::wifi::kWEP_PSK;
569
570  if ([network supportsSecurity:kCWSecurityNone])
571    return onc::wifi::kSecurityNone;
572
573  // TODO(mef): Figure out correct mapping.
574  if ([network supportsSecurity:kCWSecurityDynamicWEP])
575    return onc::wifi::kWPA_EAP;
576
577  return onc::wifi::kWPA_EAP;
578}
579
580Frequency WiFiServiceMac::FrequencyFromCWChannelBand(CWChannelBand band) const {
581  return band == kCWChannelBand2GHz ? kFrequency2400 : kFrequency5000;
582}
583
584NetworkList::iterator WiFiServiceMac::FindNetwork(
585    const std::string& network_guid) {
586  for (NetworkList::iterator it = networks_.begin();
587       it != networks_.end();
588       ++it) {
589    if (it->guid == network_guid)
590      return it;
591  }
592  return networks_.end();
593}
594
595void WiFiServiceMac::OnWlanObserverNotification() {
596  std::string connected_network_guid = GUIDFromSSID([interface_ ssid]);
597  DVLOG(1) << " *** Got Notification: " << connected_network_guid;
598  // Connected network has changed, mark previous one disconnected.
599  if (connected_network_guid != connected_network_guid_) {
600    // Update connection_state of newly connected network.
601    NetworkList::iterator it = FindNetwork(connected_network_guid_);
602    if (it != networks_.end()) {
603      it->connection_state = onc::connection_state::kNotConnected;
604      NotifyNetworkChanged(connected_network_guid_);
605    }
606    connected_network_guid_ = connected_network_guid;
607  }
608
609  if (!connected_network_guid.empty()) {
610    // Update connection_state of newly connected network.
611    NetworkList::iterator it = FindNetwork(connected_network_guid);
612    if (it != networks_.end()) {
613      it->connection_state = GetNetworkConnectionState(connected_network_guid);
614    } else {
615      // Can't find |connected_network_guid| in |networks_|, try to update it.
616      UpdateNetworks();
617    }
618    // Notify that network is connecting.
619    NotifyNetworkChanged(connected_network_guid);
620    // Further network change notification will be sent by detector.
621  }
622}
623
624void WiFiServiceMac::NotifyNetworkListChanged(const NetworkList& networks) {
625  if (network_list_changed_observer_.is_null())
626    return;
627
628  NetworkGuidList current_networks;
629  for (NetworkList::const_iterator it = networks.begin();
630       it != networks.end();
631       ++it) {
632    current_networks.push_back(it->guid);
633  }
634
635  message_loop_proxy_->PostTask(
636      FROM_HERE,
637      base::Bind(network_list_changed_observer_, current_networks));
638}
639
640void WiFiServiceMac::NotifyNetworkChanged(const std::string& network_guid) {
641  if (networks_changed_observer_.is_null())
642    return;
643
644  DVLOG(1) << "NotifyNetworkChanged: " << network_guid;
645  NetworkGuidList changed_networks(1, network_guid);
646  message_loop_proxy_->PostTask(
647      FROM_HERE,
648      base::Bind(networks_changed_observer_, changed_networks));
649}
650
651// static
652WiFiService* WiFiService::Create() { return new WiFiServiceMac(); }
653
654}  // namespace wifi
655