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.
5 #include "components/wifi/wifi_service.h"
8 #import <CoreWLAN/CoreWLAN.h>
9 #import <SystemConfiguration/SystemConfiguration.h>
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"
23 // Implementation of WiFiService for Mac OS X.
24 class WiFiServiceMac : public WiFiService {
27 ~WiFiServiceMac() override;
29 // WiFiService interface implementation.
31 scoped_refptr<base::SequencedTaskRunner> task_runner) override;
33 void UnInitialize() override;
35 void GetProperties(const std::string& network_guid,
36 base::DictionaryValue* properties,
37 std::string* error) override;
39 void GetManagedProperties(const std::string& network_guid,
40 base::DictionaryValue* managed_properties,
41 std::string* error) override;
43 void GetState(const std::string& network_guid,
44 base::DictionaryValue* properties,
45 std::string* error) override;
47 void SetProperties(const std::string& network_guid,
48 scoped_ptr<base::DictionaryValue> properties,
49 std::string* error) override;
51 void CreateNetwork(bool shared,
52 scoped_ptr<base::DictionaryValue> properties,
53 std::string* network_guid,
54 std::string* error) override;
56 void GetVisibleNetworks(const std::string& network_type,
57 base::ListValue* network_list,
58 bool include_details) override;
60 void RequestNetworkScan() override;
62 void StartConnect(const std::string& network_guid,
63 std::string* error) override;
65 void StartDisconnect(const std::string& network_guid,
66 std::string* error) override;
68 void GetKeyFromSystem(const std::string& network_guid,
69 std::string* key_data,
70 std::string* error) override;
72 void SetEventObservers(
73 scoped_refptr<base::MessageLoopProxy> message_loop_proxy,
74 const NetworkGuidListCallback& networks_changed_observer,
75 const NetworkGuidListCallback& network_list_changed_observer) override;
77 void RequestConnectedNetworkUpdate() override;
80 // Checks |ns_error| and if is not |nil|, then stores |error_name|
82 bool CheckError(NSError* ns_error,
83 const char* error_name,
84 std::string* error) const;
86 // Gets |ssid| from unique |network_guid|.
87 NSString* SSIDFromGUID(const std::string& network_guid) const {
88 return base::SysUTF8ToNSString(network_guid);
91 // Gets unique |network_guid| string based on |ssid|.
92 std::string GUIDFromSSID(NSString* ssid) const {
93 return base::SysNSStringToUTF8(ssid);
96 // Populates |properties| from |network|.
97 void NetworkPropertiesFromCWNetwork(const CWNetwork* network,
98 NetworkProperties* properties) const;
100 // Converts |CWSecurityMode| into onc::wifi::k{WPA|WEP}* security constant.
101 std::string SecurityFromCWSecurityMode(CWSecurityMode security) const;
103 // Returns onc::wifi::k{WPA|WEP}* security constant supported by the
105 std::string SecurityFromCWNetwork(const CWNetwork* network) const;
107 // Converts |CWChannelBand| into Frequency constant.
108 Frequency FrequencyFromCWChannelBand(CWChannelBand band) const;
110 // Gets current |onc::connection_state| for given |network_guid|.
111 std::string GetNetworkConnectionState(const std::string& network_guid) const;
113 // Updates |networks_| with the list of visible wireless networks.
114 void UpdateNetworks();
116 // Find network by |network_guid| and return iterator to its entry in
118 NetworkList::iterator FindNetwork(const std::string& network_guid);
120 // Handles notification from |wlan_observer_|.
121 void OnWlanObserverNotification();
123 // Notifies |network_list_changed_observer_| that list of visible networks has
124 // changed to |networks|.
125 void NotifyNetworkListChanged(const NetworkList& networks);
127 // Notifies |networks_changed_observer_| that network |network_guid|
128 // connection state has changed.
129 void NotifyNetworkChanged(const std::string& network_guid);
131 // Default interface.
132 base::scoped_nsobject<CWInterface> interface_;
133 // WLAN Notifications observer. |this| doesn't own this reference.
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_;
151 DISALLOW_COPY_AND_ASSIGN(WiFiServiceMac);
154 WiFiServiceMac::WiFiServiceMac() : wlan_observer_(nil) {
157 WiFiServiceMac::~WiFiServiceMac() {
160 void WiFiServiceMac::Initialize(
161 scoped_refptr<base::SequencedTaskRunner> task_runner) {
162 task_runner_.swap(task_runner);
163 interface_.reset([[CWInterface interface] retain]);
165 DVLOG(1) << "Failed to initialize default interface.";
170 respondsToSelector:@selector(associateToNetwork:password:error:)]) {
171 DVLOG(1) << "CWInterface does not support associateToNetwork.";
177 void WiFiServiceMac::UnInitialize() {
179 [[NSNotificationCenter defaultCenter] removeObserver:wlan_observer_];
183 void 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;
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;
199 void WiFiServiceMac::GetManagedProperties(
200 const std::string& network_guid,
201 base::DictionaryValue* managed_properties,
202 std::string* error) {
203 *error = kErrorNotImplemented;
206 void WiFiServiceMac::GetState(const std::string& network_guid,
207 base::DictionaryValue* properties,
208 std::string* error) {
209 *error = kErrorNotImplemented;
212 void 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());
223 network_properties_.SetWithoutPathExpansion(network_guid,
224 properties.release());
228 void WiFiServiceMac::CreateNetwork(
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;
239 std::string guid = network_properties.ssid;
240 if (FindNetwork(guid) != networks_.end()) {
241 *error = kErrorInvalidData;
244 network_properties_.SetWithoutPathExpansion(guid,
245 properties.release());
246 *network_guid = guid;
249 void 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) {
258 if (networks_.empty())
261 for (NetworkList::const_iterator it = networks_.begin();
262 it != networks_.end();
264 scoped_ptr<base::DictionaryValue> network(it->ToValue(!include_details));
265 network_list->Append(network.release());
269 void WiFiServiceMac::RequestNetworkScan() {
270 DVLOG(1) << "*** RequestNetworkScan";
274 void WiFiServiceMac::StartConnect(const std::string& network_guid,
275 std::string* error) {
276 NSError* ns_error = nil;
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)
285 NSSet* networks = [interface_
286 scanForNetworksWithName:SSIDFromGUID(network_guid)
289 if (CheckError(ns_error, kErrorScanForNetworksWithName, error))
292 CWNetwork* network = [networks anyObject];
293 if (network == nil) {
294 // System can't find the network, remove it from the |networks_| and notify
296 NetworkList::iterator it = FindNetwork(connected_network_guid);
297 if (it != networks_.end()) {
299 // Notify observers that list has changed.
300 NotifyNetworkListChanged(networks_);
303 *error = kErrorNotFound;
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,
314 properties->GetDictionary(onc::network_type::kWiFi, &wifi) &&
315 wifi->GetString(onc::wifi::kPassphrase, &passphrase)) {
316 ns_password = base::SysUTF8ToNSString(passphrase);
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
329 NSInteger error_code = [ns_error code];
330 if (error_code != kCWTimeoutErr && error_code != kCWInvalidPMKErr) {
335 CheckError(ns_error, kErrorAssociateToNetwork, error);
338 void WiFiServiceMac::StartDisconnect(const std::string& network_guid,
339 std::string* error) {
340 DVLOG(1) << "*** StartDisconnect: " << network_guid;
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);
351 *error = kErrorNotConnected;
355 void WiFiServiceMac::GetKeyFromSystem(const std::string& network_guid,
356 std::string* key_data,
357 std::string* error) {
358 static const char kAirPortServiceName[] = "AirPort";
360 UInt32 password_length = 0;
361 void *password_data = NULL;
362 OSStatus status = SecKeychainFindGenericPassword(NULL,
363 strlen(kAirPortServiceName),
365 network_guid.length(),
366 network_guid.c_str(),
370 if (status != errSecSuccess) {
371 *error = kErrorNotFound;
376 *key_data = std::string(reinterpret_cast<char*>(password_data),
378 SecKeychainItemFreeContent(NULL, password_data);
382 void 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;
390 // Remove previous OS notifications observer.
391 if (wlan_observer_) {
392 [[NSNotificationCenter defaultCenter] removeObserver:wlan_observer_];
393 wlan_observer_ = nil;
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(
403 base::Bind(&WiFiServiceMac::OnWlanObserverNotification,
404 base::Unretained(this)));
407 wlan_observer_ = [[NSNotificationCenter defaultCenter]
408 addObserverForName:kCWSSIDDidChangeNotification
411 usingBlock:ns_observer];
415 void WiFiServiceMac::RequestConnectedNetworkUpdate() {
416 OnWlanObserverNotification();
419 std::string WiFiServiceMac::GetNetworkConnectionState(
420 const std::string& network_guid) const {
421 if (network_guid != GUIDFromSSID([interface_ ssid]))
422 return onc::connection_state::kNotConnected;
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(
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;
441 // Network is not reachable yet, so it must be |kConnecting|.
442 return onc::connection_state::kConnecting;
445 void WiFiServiceMac::UpdateNetworks() {
446 NSError* ns_error = nil;
447 NSSet* cw_networks = [interface_ scanForNetworksWithName:nil
452 std::string connected_bssid = base::SysNSStringToUTF8([interface_ bssid]);
453 std::map<std::string, NetworkProperties*> network_properties_map;
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;
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;
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;
472 NetworkProperties* properties = network_properties_map.at(network_guid);
473 if (update_all_properties) {
474 NetworkPropertiesFromCWNetwork(cw_network, properties);
476 properties->frequency_set.insert(FrequencyFromCWChannelBand(
477 [[cw_network wlanChannel] channelBand]));
480 // Sort networks, so connected/connecting is up front.
481 networks_.sort(NetworkProperties::OrderByType);
482 // Notify observers that list has changed.
483 NotifyNetworkListChanged(networks_);
486 bool 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];
497 void WiFiServiceMac::NetworkPropertiesFromCWNetwork(
498 const CWNetwork* network,
499 NetworkProperties* properties) const {
500 std::string network_guid = GUIDFromSSID([network ssid]);
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;
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);
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);
520 properties->security = SecurityFromCWSecurityMode(
521 static_cast<CWSecurityMode>([[network securityMode] intValue]));
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];
531 properties->signal_strength = [[network rssi] intValue];
534 std::string WiFiServiceMac::SecurityFromCWSecurityMode(
535 CWSecurityMode security) const {
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;
552 return onc::wifi::kWPA_EAP;
555 std::string WiFiServiceMac::SecurityFromCWNetwork(
556 const CWNetwork* network) const {
557 if ([network supportsSecurity:kCWSecurityWPAEnterprise] ||
558 [network supportsSecurity:kCWSecurityWPA2Enterprise]) {
559 return onc::wifi::kWPA_EAP;
562 if ([network supportsSecurity:kCWSecurityWPAPersonal] ||
563 [network supportsSecurity:kCWSecurityWPA2Personal]) {
564 return onc::wifi::kWPA_PSK;
567 if ([network supportsSecurity:kCWSecurityWEP])
568 return onc::wifi::kWEP_PSK;
570 if ([network supportsSecurity:kCWSecurityNone])
571 return onc::wifi::kSecurityNone;
573 // TODO(mef): Figure out correct mapping.
574 if ([network supportsSecurity:kCWSecurityDynamicWEP])
575 return onc::wifi::kWPA_EAP;
577 return onc::wifi::kWPA_EAP;
580 Frequency WiFiServiceMac::FrequencyFromCWChannelBand(CWChannelBand band) const {
581 return band == kCWChannelBand2GHz ? kFrequency2400 : kFrequency5000;
584 NetworkList::iterator WiFiServiceMac::FindNetwork(
585 const std::string& network_guid) {
586 for (NetworkList::iterator it = networks_.begin();
587 it != networks_.end();
589 if (it->guid == network_guid)
592 return networks_.end();
595 void 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_);
606 connected_network_guid_ = connected_network_guid;
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);
615 // Can't find |connected_network_guid| in |networks_|, try to update it.
618 // Notify that network is connecting.
619 NotifyNetworkChanged(connected_network_guid);
620 // Further network change notification will be sent by detector.
624 void WiFiServiceMac::NotifyNetworkListChanged(const NetworkList& networks) {
625 if (network_list_changed_observer_.is_null())
628 NetworkGuidList current_networks;
629 for (NetworkList::const_iterator it = networks.begin();
630 it != networks.end();
632 current_networks.push_back(it->guid);
635 message_loop_proxy_->PostTask(
637 base::Bind(network_list_changed_observer_, current_networks));
640 void WiFiServiceMac::NotifyNetworkChanged(const std::string& network_guid) {
641 if (networks_changed_observer_.is_null())
644 DVLOG(1) << "NotifyNetworkChanged: " << network_guid;
645 NetworkGuidList changed_networks(1, network_guid);
646 message_loop_proxy_->PostTask(
648 base::Bind(networks_changed_observer_, changed_networks));
652 WiFiService* WiFiService::Create() { return new WiFiServiceMac(); }