summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBill Yi <byi@google.com>2015-08-28 10:34:50 -0700
committerBill Yi <byi@google.com>2015-08-28 10:35:08 -0700
commit083525f2c010c92dac08630ebcdd4a7998c8deaa (patch)
treed000f5a595ae279cec742ee2e4de9f83d820c9bf
parent84c70e1e92a1c060a460add51a51ae089755b259 (diff)
parent063c28415272d8026bfa87416811afe63e8023d3 (diff)
downloadapmanager-083525f2c010c92dac08630ebcdd4a7998c8deaa.tar.gz
Merge branch 'rewrite-apmanager' into merge-apmanager
BUG:23620021
-rw-r--r--OWNERS2
-rw-r--r--apmanager.gyp136
-rw-r--r--config.cc420
-rw-r--r--config.h151
-rw-r--r--config_unittest.cc404
-rw-r--r--daemon.cc55
-rw-r--r--daemon.h41
-rw-r--r--dbus_bindings/dbus-service-config.json6
-rw-r--r--dbus_bindings/org.chromium.apmanager.Config.xml17
-rw-r--r--dbus_bindings/org.chromium.apmanager.Device.xml9
-rw-r--r--dbus_bindings/org.chromium.apmanager.Manager.xml32
-rw-r--r--dbus_bindings/org.chromium.apmanager.Service.xml18
-rw-r--r--dbus_permissions/org.chromium.apmanager.conf16
-rw-r--r--device.cc374
-rw-r--r--device.h131
-rw-r--r--device_info.cc322
-rw-r--r--device_info.h106
-rw-r--r--device_info_unittest.cc373
-rw-r--r--device_unittest.cc343
-rw-r--r--dhcp_server.cc122
-rw-r--r--dhcp_server.h55
-rw-r--r--dhcp_server_factory.cc28
-rw-r--r--dhcp_server_factory.h37
-rw-r--r--dhcp_server_unittest.cc118
-rw-r--r--event_dispatcher.cc37
-rw-r--r--event_dispatcher.h38
-rw-r--r--file_writer.cc35
-rw-r--r--file_writer.h36
-rw-r--r--firewall_manager.cc177
-rw-r--r--firewall_manager.h66
-rw-r--r--hostapd_monitor.cc212
-rw-r--r--hostapd_monitor.h98
-rw-r--r--hostapd_monitor_unittest.cc104
-rw-r--r--init/apmanager-seccomp-amd64.policy89
-rw-r--r--init/apmanager-seccomp-arm.policy84
-rw-r--r--init/apmanager-seccomp-mips.policy69
-rw-r--r--init/apmanager-seccomp-x86.policy71
-rw-r--r--init/apmanager.conf31
-rw-r--r--main.cc129
-rw-r--r--manager.cc215
-rw-r--r--manager.h111
-rw-r--r--manager_unittest.cc87
-rw-r--r--mock_config.cc13
-rw-r--r--mock_config.h34
-rw-r--r--mock_device.cc13
-rw-r--r--mock_device.h39
-rw-r--r--mock_dhcp_server.cc13
-rw-r--r--mock_dhcp_server.h28
-rw-r--r--mock_dhcp_server_factory.cc21
-rw-r--r--mock_dhcp_server_factory.h39
-rw-r--r--mock_event_dispatcher.cc21
-rw-r--r--mock_event_dispatcher.h37
-rw-r--r--mock_file_writer.cc21
-rw-r--r--mock_file_writer.h38
-rw-r--r--mock_hostapd_monitor.cc13
-rw-r--r--mock_hostapd_monitor.h27
-rw-r--r--mock_manager.cc13
-rw-r--r--mock_manager.h39
-rw-r--r--mock_process_factory.cc21
-rw-r--r--mock_process_factory.h35
-rw-r--r--mock_service.cc13
-rw-r--r--mock_service.h29
-rw-r--r--process_factory.cc27
-rw-r--r--process_factory.h36
-rw-r--r--service.cc224
-rw-r--r--service.h91
-rw-r--r--service_unittest.cc147
-rw-r--r--shill_proxy.cc98
-rw-r--r--shill_proxy.h48
-rw-r--r--testrunner.cc16
70 files changed, 6199 insertions, 0 deletions
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..ea7e34d
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1,2 @@
+set noparent
+zqiu@chromium.org
diff --git a/apmanager.gyp b/apmanager.gyp
new file mode 100644
index 0000000..4afea9a
--- /dev/null
+++ b/apmanager.gyp
@@ -0,0 +1,136 @@
+{
+ 'target_defaults': {
+ 'variables': {
+ 'deps': [
+ 'libchrome-<(libbase_ver)',
+ 'libchromeos-<(libbase_ver)',
+ ],
+ },
+ 'cflags': [
+ '-Wextra',
+ '-Wno-unused-parameter', # base/lazy_instance.h, etc.
+ ],
+ 'cflags_cc': [
+ '-Wno-missing-field-initializers', # for LAZY_INSTANCE_INITIALIZER
+ ],
+ },
+ 'targets': [
+ {
+ 'target_name': 'apmanager-adaptors',
+ 'type': 'none',
+ 'variables': {
+ 'dbus_adaptors_out_dir': 'include/apmanager/dbus_adaptors',
+ },
+ 'sources': [
+ 'dbus_bindings/org.chromium.apmanager.Config.xml',
+ 'dbus_bindings/org.chromium.apmanager.Device.xml',
+ 'dbus_bindings/org.chromium.apmanager.Manager.xml',
+ 'dbus_bindings/org.chromium.apmanager.Service.xml',
+ ],
+ 'includes': ['../common-mk/generate-dbus-adaptors.gypi'],
+ },
+ {
+ 'target_name': 'libapmanager',
+ 'type': 'static_library',
+ 'dependencies': [
+ 'apmanager-adaptors',
+ ],
+ 'variables': {
+ 'exported_deps': [
+ 'libshill-net-<(libbase_ver)',
+ ],
+ 'deps': ['<@(exported_deps)'],
+ },
+ 'all_dependent_settings': {
+ 'variables': {
+ 'deps': [
+ '<@(exported_deps)',
+ ],
+ },
+ },
+ 'sources': [
+ 'config.cc',
+ 'daemon.cc',
+ 'device.cc',
+ 'device_info.cc',
+ 'dhcp_server.cc',
+ 'dhcp_server_factory.cc',
+ 'event_dispatcher.cc',
+ 'file_writer.cc',
+ 'firewall_manager.cc',
+ 'hostapd_monitor.cc',
+ 'manager.cc',
+ 'process_factory.cc',
+ 'service.cc',
+ 'shill_proxy.cc',
+ ],
+ 'actions': [
+ {
+ 'action_name': 'generate-shill-proxies',
+ 'variables': {
+ 'proxy_output_file': 'include/shill/dbus-proxies.h'
+ },
+ 'sources': [
+ '../shill/dbus_bindings/org.chromium.flimflam.Manager.xml',
+ ],
+ 'includes': ['../common-mk/generate-dbus-proxies.gypi'],
+ },
+ {
+ 'action_name': 'generate-permission_broker-proxies',
+ 'variables': {
+ 'proxy_output_file': 'include/permission_broker/dbus-proxies.h'
+ },
+ 'sources': [
+ '../permission_broker/dbus_bindings/org.chromium.PermissionBroker.xml',
+ ],
+ 'includes': ['../common-mk/generate-dbus-proxies.gypi'],
+ },
+ ],
+ },
+ {
+ 'target_name': 'apmanager',
+ 'type': 'executable',
+ 'dependencies': ['libapmanager'],
+ 'variables': {
+ 'deps': [
+ 'libminijail',
+ ],
+ },
+ 'sources': [
+ 'main.cc',
+ ],
+ },
+ ],
+ 'conditions': [
+ ['USE_test == 1', {
+ 'targets': [
+ {
+ 'target_name': 'apmanager_testrunner',
+ 'type': 'executable',
+ 'dependencies': ['libapmanager'],
+ 'includes': ['../common-mk/common_test.gypi'],
+ 'sources': [
+ 'config_unittest.cc',
+ 'device_info_unittest.cc',
+ 'device_unittest.cc',
+ 'dhcp_server_unittest.cc',
+ 'hostapd_monitor_unittest.cc',
+ 'manager_unittest.cc',
+ 'mock_config.cc',
+ 'mock_device.cc',
+ 'mock_dhcp_server.cc',
+ 'mock_dhcp_server_factory.cc',
+ 'mock_event_dispatcher.cc',
+ 'mock_file_writer.cc',
+ 'mock_hostapd_monitor.cc',
+ 'mock_manager.cc',
+ 'mock_process_factory.cc',
+ 'mock_service.cc',
+ 'service_unittest.cc',
+ 'testrunner.cc',
+ ],
+ },
+ ],
+ }],
+ ],
+}
diff --git a/config.cc b/config.cc
new file mode 100644
index 0000000..8ad2d87
--- /dev/null
+++ b/config.cc
@@ -0,0 +1,420 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "apmanager/config.h"
+
+#include <base/strings/stringprintf.h>
+#include <chromeos/dbus/service_constants.h>
+
+#include "apmanager/daemon.h"
+#include "apmanager/device.h"
+#include "apmanager/manager.h"
+
+using chromeos::dbus_utils::AsyncEventSequencer;
+using chromeos::dbus_utils::ExportedObjectManager;
+using chromeos::ErrorPtr;
+using std::string;
+
+namespace apmanager {
+
+// static
+const char Config::kHostapdConfigKeyBridgeInterface[] = "bridge";
+const char Config::kHostapdConfigKeyChannel[] = "channel";
+const char Config::kHostapdConfigKeyControlInterface[] = "ctrl_interface";
+const char Config::kHostapdConfigKeyControlInterfaceGroup[] =
+ "ctrl_interface_group";
+const char Config::kHostapdConfigKeyDriver[] = "driver";
+const char Config::kHostapdConfigKeyFragmThreshold[] = "fragm_threshold";
+const char Config::kHostapdConfigKeyHTCapability[] = "ht_capab";
+const char Config::kHostapdConfigKeyHwMode[] = "hw_mode";
+const char Config::kHostapdConfigKeyIeee80211ac[] = "ieee80211ac";
+const char Config::kHostapdConfigKeyIeee80211n[] = "ieee80211n";
+const char Config::kHostapdConfigKeyIgnoreBroadcastSsid[] =
+ "ignore_broadcast_ssid";
+const char Config::kHostapdConfigKeyInterface[] = "interface";
+const char Config::kHostapdConfigKeyRsnPairwise[] = "rsn_pairwise";
+const char Config::kHostapdConfigKeyRtsThreshold[] = "rts_threshold";
+const char Config::kHostapdConfigKeySsid[] = "ssid";
+const char Config::kHostapdConfigKeyWepDefaultKey[] = "wep_default_key";
+const char Config::kHostapdConfigKeyWepKey0[] = "wep_key0";
+const char Config::kHostapdConfigKeyWpa[] = "wpa";
+const char Config::kHostapdConfigKeyWpaKeyMgmt[] = "wpa_key_mgmt";
+const char Config::kHostapdConfigKeyWpaPassphrase[] = "wpa_passphrase";
+
+const char Config::kHostapdHwMode80211a[] = "a";
+const char Config::kHostapdHwMode80211b[] = "b";
+const char Config::kHostapdHwMode80211g[] = "g";
+
+// static
+const uint16_t Config::kPropertyDefaultChannel = 6;
+const uint16_t Config::kPropertyDefaultServerAddressIndex = 0;
+const bool Config::kPropertyDefaultHiddenNetwork = false;
+
+// static
+const char Config::kHostapdDefaultDriver[] = "nl80211";
+const char Config::kHostapdDefaultRsnPairwise[] = "CCMP";
+const char Config::kHostapdDefaultWpaKeyMgmt[] = "WPA-PSK";
+// Fragmentation threshold: disabled.
+const int Config::kHostapdDefaultFragmThreshold = 2346;
+// RTS threshold: disabled.
+const int Config::kHostapdDefaultRtsThreshold = 2347;
+
+// static
+const uint16_t Config::kBand24GHzChannelLow = 1;
+const uint16_t Config::kBand24GHzChannelHigh = 13;
+const uint32_t Config::kBand24GHzBaseFrequency = 2412;
+const uint16_t Config::kBand5GHzChannelLow = 34;
+const uint16_t Config::kBand5GHzChannelHigh = 165;
+const uint16_t Config::kBand5GHzBaseFrequency = 5170;
+
+// static
+const int Config::kSsidMinLength = 1;
+const int Config::kSsidMaxLength = 32;
+const int Config::kPassphraseMinLength = 8;
+const int Config::kPassphraseMaxLength = 63;
+
+Config::Config(Manager* manager, const string& service_path)
+ : org::chromium::apmanager::ConfigAdaptor(this),
+ manager_(manager),
+ dbus_path_(dbus::ObjectPath(
+ base::StringPrintf("%s/config", service_path.c_str()))) {
+ // Initialize default configuration values.
+ SetSecurityMode(kSecurityModeNone);
+ SetHwMode(kHwMode80211g);
+ SetOperationMode(kOperationModeServer);
+ SetServerAddressIndex(kPropertyDefaultServerAddressIndex);
+ SetChannel(kPropertyDefaultChannel);
+ SetHiddenNetwork(kPropertyDefaultHiddenNetwork);
+ SetFullDeviceControl(true);
+}
+
+Config::~Config() {}
+
+// static.
+bool Config::GetFrequencyFromChannel(uint16_t channel, uint32_t* freq) {
+ bool ret_value = true;
+ if (channel >= kBand24GHzChannelLow && channel <= kBand24GHzChannelHigh) {
+ *freq = kBand24GHzBaseFrequency + (channel - kBand24GHzChannelLow) * 5;
+ } else if (channel >= kBand5GHzChannelLow &&
+ channel <= kBand5GHzChannelHigh) {
+ *freq = kBand5GHzBaseFrequency + (channel - kBand5GHzChannelLow) * 5;
+ } else {
+ ret_value = false;
+ }
+ return ret_value;
+}
+
+bool Config::ValidateSsid(ErrorPtr* error, const string& value) {
+ if (value.length() < kSsidMinLength || value.length() > kSsidMaxLength) {
+ chromeos::Error::AddToPrintf(
+ error, FROM_HERE, chromeos::errors::dbus::kDomain, kConfigError,
+ "SSID must contain between %d and %d characters",
+ kSsidMinLength, kSsidMaxLength);
+ return false;
+ }
+ return true;
+}
+
+bool Config::ValidateSecurityMode(ErrorPtr* error, const string& value) {
+ if (value != kSecurityModeNone && value != kSecurityModeRSN) {
+ chromeos::Error::AddToPrintf(
+ error, FROM_HERE, chromeos::errors::dbus::kDomain, kConfigError,
+ "Invalid/unsupported security mode [%s]", value.c_str());
+ return false;
+ }
+ return true;
+}
+
+bool Config::ValidatePassphrase(ErrorPtr* error, const string& value) {
+ if (value.length() < kPassphraseMinLength ||
+ value.length() > kPassphraseMaxLength) {
+ chromeos::Error::AddToPrintf(
+ error, FROM_HERE, chromeos::errors::dbus::kDomain, kConfigError,
+ "Passphrase must contain between %d and %d characters",
+ kPassphraseMinLength, kPassphraseMaxLength);
+ return false;
+ }
+ return true;
+}
+
+bool Config::ValidateHwMode(ErrorPtr* error, const string& value) {
+ if (value != kHwMode80211a && value != kHwMode80211b &&
+ value != kHwMode80211g && value != kHwMode80211n &&
+ value != kHwMode80211ac) {
+ chromeos::Error::AddToPrintf(
+ error, FROM_HERE, chromeos::errors::dbus::kDomain, kConfigError,
+ "Invalid HW mode [%s]", value.c_str());
+ return false;
+ }
+ return true;
+}
+
+bool Config::ValidateOperationMode(ErrorPtr* error, const string& value) {
+ if (value != kOperationModeServer && value != kOperationModeBridge) {
+ chromeos::Error::AddToPrintf(
+ error, FROM_HERE, chromeos::errors::dbus::kDomain, kConfigError,
+ "Invalid operation mode [%s]", value.c_str());
+ return false;
+ }
+ return true;
+}
+
+bool Config::ValidateChannel(ErrorPtr* error, const uint16_t& value) {
+ if ((value >= kBand24GHzChannelLow && value <= kBand24GHzChannelHigh) ||
+ (value >= kBand5GHzChannelLow && value <= kBand5GHzChannelHigh)) {
+ return true;
+ }
+ chromeos::Error::AddToPrintf(
+ error, FROM_HERE, chromeos::errors::dbus::kDomain, kConfigError,
+ "Invalid channel [%d]", value);
+ return false;
+}
+
+void Config::RegisterAsync(ExportedObjectManager* object_manager,
+ const scoped_refptr<dbus::Bus>& bus,
+ AsyncEventSequencer* sequencer) {
+ CHECK(!dbus_object_) << "Already registered";
+ dbus_object_.reset(
+ new chromeos::dbus_utils::DBusObject(
+ object_manager,
+ bus,
+ dbus_path_));
+ RegisterWithDBusObject(dbus_object_.get());
+ dbus_object_->RegisterAsync(
+ sequencer->GetHandler("Config.RegisterAsync() failed.", true));
+}
+
+bool Config::GenerateConfigFile(ErrorPtr* error, string* config_str) {
+ // SSID.
+ string ssid = GetSsid();
+ if (ssid.empty()) {
+ chromeos::Error::AddTo(
+ error, FROM_HERE, chromeos::errors::dbus::kDomain, kConfigError,
+ "SSID not specified");
+ return false;
+ }
+ base::StringAppendF(
+ config_str, "%s=%s\n", kHostapdConfigKeySsid, ssid.c_str());
+
+ // Bridge interface is required for bridge mode operation.
+ if (GetOperationMode() == kOperationModeBridge) {
+ if (GetBridgeInterface().empty()) {
+ chromeos::Error::AddTo(
+ error, FROM_HERE, chromeos::errors::dbus::kDomain, kConfigError,
+ "Bridge interface not specified, required for bridge mode");
+ return false;
+ }
+ base::StringAppendF(config_str,
+ "%s=%s\n",
+ kHostapdConfigKeyBridgeInterface,
+ GetBridgeInterface().c_str());
+ }
+
+ // Channel.
+ base::StringAppendF(
+ config_str, "%s=%d\n", kHostapdConfigKeyChannel, GetChannel());
+
+ // Interface.
+ if (!AppendInterface(error, config_str)) {
+ return false;
+ }
+
+ // Hardware mode.
+ if (!AppendHwMode(error, config_str)) {
+ return false;
+ }
+
+ // Security mode configurations.
+ if (!AppendSecurityMode(error, config_str)) {
+ return false;
+ }
+
+ // Control interface.
+ if (!control_interface_.empty()) {
+ base::StringAppendF(config_str,
+ "%s=%s\n",
+ kHostapdConfigKeyControlInterface,
+ control_interface_.c_str());
+ base::StringAppendF(config_str,
+ "%s=%s\n",
+ kHostapdConfigKeyControlInterfaceGroup,
+ Daemon::kAPManagerGroupName);
+ }
+
+ // Hostapd default configurations.
+ if (!AppendHostapdDefaults(error, config_str)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool Config::ClaimDevice() {
+ if (!device_) {
+ LOG(ERROR) << "Failed to claim device: device doesn't exist.";
+ return false;
+ }
+ return device_->ClaimDevice(GetFullDeviceControl());
+}
+
+bool Config::ReleaseDevice() {
+ if (!device_) {
+ LOG(ERROR) << "Failed to release device: device doesn't exist.";
+ return false;
+ }
+ return device_->ReleaseDevice();
+}
+
+bool Config::AppendHwMode(ErrorPtr* error, std::string* config_str) {
+ string hw_mode = GetHwMode();
+ string hostapd_hw_mode;
+ if (hw_mode == kHwMode80211a) {
+ hostapd_hw_mode = kHostapdHwMode80211a;
+ } else if (hw_mode == kHwMode80211b) {
+ hostapd_hw_mode = kHostapdHwMode80211b;
+ } else if (hw_mode == kHwMode80211g) {
+ hostapd_hw_mode = kHostapdHwMode80211g;
+ } else if (hw_mode == kHwMode80211n) {
+ // Use 802.11a for 5GHz channel and 802.11g for 2.4GHz channel
+ if (GetChannel() >= 34) {
+ hostapd_hw_mode = kHostapdHwMode80211a;
+ } else {
+ hostapd_hw_mode = kHostapdHwMode80211g;
+ }
+ base::StringAppendF(config_str, "%s=1\n", kHostapdConfigKeyIeee80211n);
+
+ // Get HT Capability.
+ string ht_cap;
+ if (!device_->GetHTCapability(GetChannel(), &ht_cap)) {
+ chromeos::Error::AddTo(
+ error, FROM_HERE, chromeos::errors::dbus::kDomain, kConfigError,
+ "Failed to get HT Capability");
+ return false;
+ }
+ base::StringAppendF(config_str, "%s=%s\n",
+ kHostapdConfigKeyHTCapability,
+ ht_cap.c_str());
+ } else if (hw_mode == kHwMode80211ac) {
+ if (GetChannel() >= 34) {
+ hostapd_hw_mode = kHostapdHwMode80211a;
+ } else {
+ hostapd_hw_mode = kHostapdHwMode80211g;
+ }
+ base::StringAppendF(config_str, "%s=1\n", kHostapdConfigKeyIeee80211ac);
+
+ // TODO(zqiu): Determine VHT Capabilities based on the interface PHY's
+ // capababilites.
+ } else {
+ chromeos::Error::AddToPrintf(
+ error, FROM_HERE, chromeos::errors::dbus::kDomain, kConfigError,
+ "Invalid hardware mode: %s", hw_mode.c_str());
+ return false;
+ }
+
+ base::StringAppendF(
+ config_str, "%s=%s\n", kHostapdConfigKeyHwMode, hostapd_hw_mode.c_str());
+ return true;
+}
+
+bool Config::AppendHostapdDefaults(ErrorPtr* error,
+ std::string* config_str) {
+ // Driver: NL80211.
+ base::StringAppendF(
+ config_str, "%s=%s\n", kHostapdConfigKeyDriver, kHostapdDefaultDriver);
+
+ // Fragmentation threshold: disabled.
+ base::StringAppendF(config_str,
+ "%s=%d\n",
+ kHostapdConfigKeyFragmThreshold,
+ kHostapdDefaultFragmThreshold);
+
+ // RTS threshold: disabled.
+ base::StringAppendF(config_str,
+ "%s=%d\n",
+ kHostapdConfigKeyRtsThreshold,
+ kHostapdDefaultRtsThreshold);
+
+ return true;
+}
+
+bool Config::AppendInterface(ErrorPtr* error,
+ std::string* config_str) {
+ string interface = GetInterfaceName();
+ if (interface.empty()) {
+ // Ask manager for unused ap capable device.
+ device_ = manager_->GetAvailableDevice();
+ if (!device_) {
+ chromeos::Error::AddTo(
+ error, FROM_HERE, chromeos::errors::dbus::kDomain, kConfigError,
+ "No device available");
+ return false;
+ }
+ } else {
+ device_ = manager_->GetDeviceFromInterfaceName(interface);
+ if (!device_) {
+ chromeos::Error::AddToPrintf(
+ error, FROM_HERE, chromeos::errors::dbus::kDomain, kConfigError,
+ "Unable to find device for the specified interface [%s]",
+ interface.c_str());
+ return false;
+ }
+ if (device_->GetInUsed()) {
+ chromeos::Error::AddToPrintf(
+ error, FROM_HERE, chromeos::errors::dbus::kDomain, kConfigError,
+ "Device [%s] for interface [%s] already in use",
+ device_->GetDeviceName().c_str(),
+ interface.c_str());
+ return false;
+ }
+ }
+
+ // Use the preferred AP interface from the device.
+ selected_interface_ = device_->GetPreferredApInterface();
+ base::StringAppendF(config_str,
+ "%s=%s\n",
+ kHostapdConfigKeyInterface,
+ selected_interface_.c_str());
+ return true;
+}
+
+bool Config::AppendSecurityMode(ErrorPtr* error,
+ std::string* config_str) {
+ string security_mode = GetSecurityMode();
+ if (security_mode == kSecurityModeNone) {
+ // Nothing need to be done for open network.
+ return true;
+ }
+
+ if (security_mode == kSecurityModeRSN) {
+ string passphrase = GetPassphrase();
+ if (passphrase.empty()) {
+ chromeos::Error::AddToPrintf(
+ error, FROM_HERE, chromeos::errors::dbus::kDomain, kConfigError,
+ "Passphrase not set for security mode: %s", security_mode.c_str());
+ return false;
+ }
+
+ base::StringAppendF(config_str, "%s=2\n", kHostapdConfigKeyWpa);
+ base::StringAppendF(config_str,
+ "%s=%s\n",
+ kHostapdConfigKeyRsnPairwise,
+ kHostapdDefaultRsnPairwise);
+ base::StringAppendF(config_str,
+ "%s=%s\n",
+ kHostapdConfigKeyWpaKeyMgmt,
+ kHostapdDefaultWpaKeyMgmt);
+ base::StringAppendF(config_str,
+ "%s=%s\n",
+ kHostapdConfigKeyWpaPassphrase,
+ passphrase.c_str());
+ return true;
+ }
+
+ chromeos::Error::AddToPrintf(
+ error, FROM_HERE, chromeos::errors::dbus::kDomain, kConfigError,
+ "Invalid security mode: %s", security_mode.c_str());
+ return false;
+}
+
+} // namespace apmanager
diff --git a/config.h b/config.h
new file mode 100644
index 0000000..653b4d0
--- /dev/null
+++ b/config.h
@@ -0,0 +1,151 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef APMANAGER_CONFIG_H_
+#define APMANAGER_CONFIG_H_
+
+#include <memory>
+#include <string>
+
+#include <base/macros.h>
+#include <chromeos/errors/error.h>
+
+#include "apmanager/dbus_adaptors/org.chromium.apmanager.Config.h"
+
+namespace apmanager {
+
+class Device;
+class Manager;
+
+class Config
+ : public org::chromium::apmanager::ConfigAdaptor,
+ public org::chromium::apmanager::ConfigInterface {
+ public:
+ Config(Manager* manager, const std::string& service_path);
+ virtual ~Config();
+
+ // Override ConfigAdaptor Validate functions.
+ bool ValidateSsid(chromeos::ErrorPtr* error,
+ const std::string& value) override;
+ bool ValidateSecurityMode(chromeos::ErrorPtr* error,
+ const std::string& value) override;
+ bool ValidatePassphrase(chromeos::ErrorPtr* error,
+ const std::string& value) override;
+ bool ValidateHwMode(chromeos::ErrorPtr* error,
+ const std::string& value) override;
+ bool ValidateOperationMode(chromeos::ErrorPtr* error,
+ const std::string& value) override;
+ bool ValidateChannel(chromeos::ErrorPtr* error,
+ const uint16_t& value) override;
+
+ // Calculate the frequency based on the given |channel|. Return true and set
+ // the output |frequency| if is valid channel, false otherwise.
+ static bool GetFrequencyFromChannel(uint16_t channel, uint32_t* freq);
+
+ // Register Config DBus object.
+ void RegisterAsync(
+ chromeos::dbus_utils::ExportedObjectManager* object_manager,
+ const scoped_refptr<dbus::Bus>& bus,
+ chromeos::dbus_utils::AsyncEventSequencer* sequencer);
+
+ // Generate a config file string for a hostapd instance. Raise appropriate
+ // error when encounter invalid configuration. Return true if success,
+ // false otherwise.
+ virtual bool GenerateConfigFile(chromeos::ErrorPtr* error,
+ std::string* config_str);
+
+ // Claim and release the device needed for this configuration.
+ virtual bool ClaimDevice();
+ virtual bool ReleaseDevice();
+
+ const std::string& control_interface() const { return control_interface_; }
+ void set_control_interface(const std::string& control_interface) {
+ control_interface_ = control_interface;
+ }
+
+ const std::string& selected_interface() const { return selected_interface_; }
+
+ const dbus::ObjectPath& dbus_path() const { return dbus_path_; }
+
+ private:
+ // Keys used in hostapd config file.
+ static const char kHostapdConfigKeyBridgeInterface[];
+ static const char kHostapdConfigKeyChannel[];
+ static const char kHostapdConfigKeyControlInterface[];
+ static const char kHostapdConfigKeyControlInterfaceGroup[];
+ static const char kHostapdConfigKeyDriver[];
+ static const char kHostapdConfigKeyFragmThreshold[];
+ static const char kHostapdConfigKeyHTCapability[];
+ static const char kHostapdConfigKeyHwMode[];
+ static const char kHostapdConfigKeyIeee80211ac[];
+ static const char kHostapdConfigKeyIeee80211n[];
+ static const char kHostapdConfigKeyIgnoreBroadcastSsid[];
+ static const char kHostapdConfigKeyInterface[];
+ static const char kHostapdConfigKeyRsnPairwise[];
+ static const char kHostapdConfigKeyRtsThreshold[];
+ static const char kHostapdConfigKeySsid[];
+ static const char kHostapdConfigKeyWepDefaultKey[];
+ static const char kHostapdConfigKeyWepKey0[];
+ static const char kHostapdConfigKeyWpa[];
+ static const char kHostapdConfigKeyWpaKeyMgmt[];
+ static const char kHostapdConfigKeyWpaPassphrase[];
+
+ // Hardware mode value for hostapd config file.
+ static const char kHostapdHwMode80211a[];
+ static const char kHostapdHwMode80211b[];
+ static const char kHostapdHwMode80211g[];
+
+ // Default hostapd configuration values. User will not be able to configure
+ // these.
+ static const char kHostapdDefaultDriver[];
+ static const char kHostapdDefaultRsnPairwise[];
+ static const char kHostapdDefaultWpaKeyMgmt[];
+ static const int kHostapdDefaultFragmThreshold;
+ static const int kHostapdDefaultRtsThreshold;
+
+ // Default config property values.
+ static const uint16_t kPropertyDefaultChannel;;
+ static const bool kPropertyDefaultHiddenNetwork;
+ static const uint16_t kPropertyDefaultServerAddressIndex;
+
+ // Constants use for converting channel to frequency.
+ static const uint16_t kBand24GHzChannelLow;
+ static const uint16_t kBand24GHzChannelHigh;
+ static const uint32_t kBand24GHzBaseFrequency;
+ static const uint16_t kBand5GHzChannelLow;
+ static const uint16_t kBand5GHzChannelHigh;
+ static const uint16_t kBand5GHzBaseFrequency;
+
+ static const int kSsidMinLength;
+ static const int kSsidMaxLength;
+ static const int kPassphraseMinLength;
+ static const int kPassphraseMaxLength;
+
+ // Append default hostapd configurations to the config file.
+ bool AppendHostapdDefaults(chromeos::ErrorPtr* error,
+ std::string* config_str);
+
+ // Append hardware mode related configurations to the config file.
+ bool AppendHwMode(chromeos::ErrorPtr* error, std::string* config_str);
+
+ // Determine/append interface configuration to the config file.
+ bool AppendInterface(chromeos::ErrorPtr* error, std::string* config_str);
+
+ // Append security related configurations to the config file.
+ bool AppendSecurityMode(chromeos::ErrorPtr* error, std::string* config_str);
+
+ Manager* manager_;
+ dbus::ObjectPath dbus_path_;
+ std::string control_interface_;
+ // Interface selected for hostapd.
+ std::string selected_interface_;
+ std::unique_ptr<chromeos::dbus_utils::DBusObject> dbus_object_;
+ scoped_refptr<Device> device_;
+
+ DISALLOW_COPY_AND_ASSIGN(Config);
+};
+
+} // namespace apmanager
+
+#endif // APMANAGER_CONFIG_H_
diff --git a/config_unittest.cc b/config_unittest.cc
new file mode 100644
index 0000000..aa11c76
--- /dev/null
+++ b/config_unittest.cc
@@ -0,0 +1,404 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "apmanager/config.h"
+
+#include <string>
+
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <chromeos/dbus/service_constants.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "apmanager/mock_device.h"
+#include "apmanager/mock_manager.h"
+
+using ::testing::_;
+using ::testing::Mock;
+using ::testing::Return;
+using ::testing::SetArgumentPointee;
+namespace apmanager {
+
+namespace {
+
+const char kServicePath[] = "/manager/services/0";
+const char kSsid[] = "TestSsid";
+const char kInterface[] = "uap0";
+const char kBridgeInterface[] = "br0";
+const char kControlInterfacePath[] = "/var/run/apmanager/hostapd/ctrl_iface";
+const char kPassphrase[] = "Passphrase";
+const char k24GHzHTCapab[] = "[LDPC SMPS-STATIC GF SHORT-GI-20]";
+const char k5GHzHTCapab[] =
+ "[LDPC HT40+ SMPS-STATIC GF SHORT-GI-20 SHORT-GI-40]";
+
+const uint16_t k24GHzChannel = 6;
+const uint16_t k5GHzChannel = 36;
+
+const char kExpected80211gConfigContent[] = "ssid=TestSsid\n"
+ "channel=6\n"
+ "interface=uap0\n"
+ "hw_mode=g\n"
+ "driver=nl80211\n"
+ "fragm_threshold=2346\n"
+ "rts_threshold=2347\n";
+
+const char kExpected80211gBridgeConfigContent[] = "ssid=TestSsid\n"
+ "bridge=br0\n"
+ "channel=6\n"
+ "interface=uap0\n"
+ "hw_mode=g\n"
+ "driver=nl80211\n"
+ "fragm_threshold=2346\n"
+ "rts_threshold=2347\n";
+
+const char kExpected80211gCtrlIfaceConfigContent[] =
+ "ssid=TestSsid\n"
+ "channel=6\n"
+ "interface=uap0\n"
+ "hw_mode=g\n"
+ "ctrl_interface=/var/run/apmanager/hostapd/ctrl_iface\n"
+ "ctrl_interface_group=apmanager\n"
+ "driver=nl80211\n"
+ "fragm_threshold=2346\n"
+ "rts_threshold=2347\n";
+
+const char kExpected80211n5GHzConfigContent[] =
+ "ssid=TestSsid\n"
+ "channel=36\n"
+ "interface=uap0\n"
+ "ieee80211n=1\n"
+ "ht_capab=[LDPC HT40+ SMPS-STATIC GF SHORT-GI-20 SHORT-GI-40]\n"
+ "hw_mode=a\n"
+ "driver=nl80211\n"
+ "fragm_threshold=2346\n"
+ "rts_threshold=2347\n";
+
+const char kExpected80211n24GHzConfigContent[] =
+ "ssid=TestSsid\n"
+ "channel=6\n"
+ "interface=uap0\n"
+ "ieee80211n=1\n"
+ "ht_capab=[LDPC SMPS-STATIC GF SHORT-GI-20]\n"
+ "hw_mode=g\n"
+ "driver=nl80211\n"
+ "fragm_threshold=2346\n"
+ "rts_threshold=2347\n";
+
+const char kExpectedRsnConfigContent[] = "ssid=TestSsid\n"
+ "channel=6\n"
+ "interface=uap0\n"
+ "hw_mode=g\n"
+ "wpa=2\n"
+ "rsn_pairwise=CCMP\n"
+ "wpa_key_mgmt=WPA-PSK\n"
+ "wpa_passphrase=Passphrase\n"
+ "driver=nl80211\n"
+ "fragm_threshold=2346\n"
+ "rts_threshold=2347\n";
+
+} // namespace
+
+class ConfigTest : public testing::Test {
+ public:
+ ConfigTest() : config_(&manager_, kServicePath) {}
+
+ void SetupDevice(const std::string& interface) {
+ // Setup mock device.
+ device_ = new MockDevice();
+ device_->SetPreferredApInterface(interface);
+ EXPECT_CALL(manager_, GetDeviceFromInterfaceName(interface))
+ .WillRepeatedly(Return(device_));
+ }
+
+ protected:
+ Config config_;
+ MockManager manager_;
+ scoped_refptr<MockDevice> device_;
+};
+
+MATCHER_P(IsConfigErrorStartingWith, message, "") {
+ return arg != nullptr &&
+ arg->GetDomain() == chromeos::errors::dbus::kDomain &&
+ arg->GetCode() == kConfigError &&
+ base::StartsWithASCII(arg->GetMessage(), message, false);
+}
+
+TEST_F(ConfigTest, GetFrequencyFromChannel) {
+ uint32_t frequency;
+ // Invalid channel.
+ EXPECT_FALSE(Config::GetFrequencyFromChannel(0, &frequency));
+ EXPECT_FALSE(Config::GetFrequencyFromChannel(166, &frequency));
+ EXPECT_FALSE(Config::GetFrequencyFromChannel(14, &frequency));
+ EXPECT_FALSE(Config::GetFrequencyFromChannel(33, &frequency));
+
+ // Valid channel.
+ const uint32_t kChannel1Frequency = 2412;
+ const uint32_t kChannel13Frequency = 2472;
+ const uint32_t kChannel34Frequency = 5170;
+ const uint32_t kChannel165Frequency = 5825;
+ EXPECT_TRUE(Config::GetFrequencyFromChannel(1, &frequency));
+ EXPECT_EQ(kChannel1Frequency, frequency);
+ EXPECT_TRUE(Config::GetFrequencyFromChannel(13, &frequency));
+ EXPECT_EQ(kChannel13Frequency, frequency);
+ EXPECT_TRUE(Config::GetFrequencyFromChannel(34, &frequency));
+ EXPECT_EQ(kChannel34Frequency, frequency);
+ EXPECT_TRUE(Config::GetFrequencyFromChannel(165, &frequency));
+ EXPECT_EQ(kChannel165Frequency, frequency);
+}
+
+TEST_F(ConfigTest, ValidateSsid) {
+ chromeos::ErrorPtr error;
+ // SSID must contain between 1 and 32 characters.
+ EXPECT_TRUE(config_.ValidateSsid(&error, "s"));
+ EXPECT_TRUE(config_.ValidateSsid(&error, std::string(32, 'c')));
+ EXPECT_FALSE(config_.ValidateSsid(&error, ""));
+ EXPECT_FALSE(config_.ValidateSsid(&error, std::string(33, 'c')));
+}
+
+TEST_F(ConfigTest, ValidateSecurityMode) {
+ chromeos::ErrorPtr error;
+ EXPECT_TRUE(config_.ValidateSecurityMode(&error, kSecurityModeNone));
+ EXPECT_TRUE(config_.ValidateSecurityMode(&error, kSecurityModeRSN));
+ EXPECT_FALSE(config_.ValidateSecurityMode(&error, "InvalidSecurityMode"));
+}
+
+TEST_F(ConfigTest, ValidatePassphrase) {
+ chromeos::ErrorPtr error;
+ // Passpharse must contain between 8 and 63 characters.
+ EXPECT_TRUE(config_.ValidatePassphrase(&error, std::string(8, 'c')));
+ EXPECT_TRUE(config_.ValidatePassphrase(&error, std::string(63, 'c')));
+ EXPECT_FALSE(config_.ValidatePassphrase(&error, std::string(7, 'c')));
+ EXPECT_FALSE(config_.ValidatePassphrase(&error, std::string(64, 'c')));
+}
+
+TEST_F(ConfigTest, ValidateHwMode) {
+ chromeos::ErrorPtr error;
+ EXPECT_TRUE(config_.ValidateHwMode(&error, kHwMode80211a));
+ EXPECT_TRUE(config_.ValidateHwMode(&error, kHwMode80211b));
+ EXPECT_TRUE(config_.ValidateHwMode(&error, kHwMode80211g));
+ EXPECT_TRUE(config_.ValidateHwMode(&error, kHwMode80211n));
+ EXPECT_TRUE(config_.ValidateHwMode(&error, kHwMode80211ac));
+ EXPECT_FALSE(config_.ValidateSecurityMode(&error, "InvalidHwMode"));
+}
+
+TEST_F(ConfigTest, ValidateOperationMode) {
+ chromeos::ErrorPtr error;
+ EXPECT_TRUE(config_.ValidateOperationMode(&error, kOperationModeServer));
+ EXPECT_TRUE(config_.ValidateOperationMode(&error, kOperationModeBridge));
+ EXPECT_FALSE(config_.ValidateOperationMode(&error, "InvalidMode"));
+}
+
+TEST_F(ConfigTest, ValidateChannel) {
+ chromeos::ErrorPtr error;
+ EXPECT_TRUE(config_.ValidateChannel(&error, 1));
+ EXPECT_TRUE(config_.ValidateChannel(&error, 13));
+ EXPECT_TRUE(config_.ValidateChannel(&error, 34));
+ EXPECT_TRUE(config_.ValidateChannel(&error, 165));
+ EXPECT_FALSE(config_.ValidateChannel(&error, 0));
+ EXPECT_FALSE(config_.ValidateChannel(&error, 14));
+ EXPECT_FALSE(config_.ValidateChannel(&error, 33));
+ EXPECT_FALSE(config_.ValidateChannel(&error, 166));
+}
+
+TEST_F(ConfigTest, NoSsid) {
+ config_.SetChannel(k24GHzChannel);
+ config_.SetHwMode(kHwMode80211g);
+ config_.SetInterfaceName(kInterface);
+
+ std::string config_content;
+ chromeos::ErrorPtr error;
+ EXPECT_FALSE(config_.GenerateConfigFile(&error, &config_content));
+ EXPECT_THAT(error, IsConfigErrorStartingWith("SSID not specified"));
+}
+
+TEST_F(ConfigTest, NoInterface) {
+ // Basic 80211.g configuration.
+ config_.SetSsid(kSsid);
+ config_.SetChannel(k24GHzChannel);
+ config_.SetHwMode(kHwMode80211g);
+
+ // No device available, fail to generate config file.
+ chromeos::ErrorPtr error;
+ std::string config_content;
+ EXPECT_CALL(manager_, GetAvailableDevice()).WillOnce(Return(nullptr));
+ EXPECT_FALSE(config_.GenerateConfigFile(&error, &config_content));
+ EXPECT_THAT(error, IsConfigErrorStartingWith("No device available"));
+ Mock::VerifyAndClearExpectations(&manager_);
+
+ // Device available, config file should be generated without any problem.
+ scoped_refptr<MockDevice> device = new MockDevice();
+ device->SetPreferredApInterface(kInterface);
+ chromeos::ErrorPtr error1;
+ EXPECT_CALL(manager_, GetAvailableDevice()).WillOnce(Return(device));
+ EXPECT_TRUE(config_.GenerateConfigFile(&error1, &config_content));
+ EXPECT_NE(std::string::npos, config_content.find(
+ kExpected80211gConfigContent))
+ << "Expected to find the following config...\n"
+ << kExpected80211gConfigContent << "..within content...\n"
+ << config_content;
+ EXPECT_EQ(nullptr, error1.get());
+ Mock::VerifyAndClearExpectations(&manager_);
+}
+
+TEST_F(ConfigTest, InvalidInterface) {
+ // Basic 80211.g configuration.
+ config_.SetSsid(kSsid);
+ config_.SetChannel(k24GHzChannel);
+ config_.SetHwMode(kHwMode80211g);
+ config_.SetInterfaceName(kInterface);
+
+ // No device available, fail to generate config file.
+ chromeos::ErrorPtr error;
+ std::string config_content;
+ EXPECT_CALL(manager_, GetDeviceFromInterfaceName(kInterface))
+ .WillOnce(Return(nullptr));
+ EXPECT_FALSE(config_.GenerateConfigFile(&error, &config_content));
+ EXPECT_THAT(error,
+ IsConfigErrorStartingWith(
+ "Unable to find device for the specified interface"));
+ Mock::VerifyAndClearExpectations(&manager_);
+}
+
+TEST_F(ConfigTest, BridgeMode) {
+ config_.SetSsid(kSsid);
+ config_.SetChannel(k24GHzChannel);
+ config_.SetHwMode(kHwMode80211g);
+ config_.SetInterfaceName(kInterface);
+ config_.SetOperationMode(kOperationModeBridge);
+
+ // Bridge interface required for bridge mode.
+ chromeos::ErrorPtr error;
+ std::string config_content;
+ EXPECT_FALSE(config_.GenerateConfigFile(&error, &config_content));
+ EXPECT_THAT(error,
+ IsConfigErrorStartingWith("Bridge interface not specified"));
+
+ // Set bridge interface, config file should be generated without error.
+ config_.SetBridgeInterface(kBridgeInterface);
+ // Setup mock device.
+ SetupDevice(kInterface);
+ chromeos::ErrorPtr error1;
+ std::string config_content1;
+ EXPECT_TRUE(config_.GenerateConfigFile(&error1, &config_content1));
+ EXPECT_NE(std::string::npos, config_content1.find(
+ kExpected80211gBridgeConfigContent))
+ << "Expected to find the following config...\n"
+ << kExpected80211gBridgeConfigContent << "..within content...\n"
+ << config_content1;
+ EXPECT_EQ(nullptr, error1.get());
+}
+
+TEST_F(ConfigTest, 80211gConfig) {
+ config_.SetSsid(kSsid);
+ config_.SetChannel(k24GHzChannel);
+ config_.SetHwMode(kHwMode80211g);
+ config_.SetInterfaceName(kInterface);
+
+ // Setup mock device.
+ SetupDevice(kInterface);
+
+ std::string config_content;
+ chromeos::ErrorPtr error;
+ EXPECT_TRUE(config_.GenerateConfigFile(&error, &config_content));
+ EXPECT_NE(std::string::npos, config_content.find(
+ kExpected80211gConfigContent))
+ << "Expected to find the following config...\n"
+ << kExpected80211gConfigContent << "..within content...\n"
+ << config_content;
+ EXPECT_EQ(nullptr, error.get());
+}
+
+TEST_F(ConfigTest, 80211gConfigWithControlInterface) {
+ config_.SetSsid(kSsid);
+ config_.SetChannel(k24GHzChannel);
+ config_.SetHwMode(kHwMode80211g);
+ config_.SetInterfaceName(kInterface);
+ config_.set_control_interface(kControlInterfacePath);
+
+ // Setup mock device.
+ SetupDevice(kInterface);
+
+ std::string config_content;
+ chromeos::ErrorPtr error;
+ EXPECT_TRUE(config_.GenerateConfigFile(&error, &config_content));
+ EXPECT_NE(std::string::npos, config_content.find(
+ kExpected80211gCtrlIfaceConfigContent))
+ << "Expected to find the following config...\n"
+ << kExpected80211gCtrlIfaceConfigContent << "..within content...\n"
+ << config_content;
+ EXPECT_EQ(nullptr, error.get());
+}
+
+TEST_F(ConfigTest, 80211nConfig) {
+ config_.SetSsid(kSsid);
+ config_.SetHwMode(kHwMode80211n);
+ config_.SetInterfaceName(kInterface);
+
+ // Setup mock device.
+ SetupDevice(kInterface);
+
+ // 5GHz channel.
+ config_.SetChannel(k5GHzChannel);
+ std::string ghz5_config_content;
+ chromeos::ErrorPtr error;
+ std::string ht_capab_5ghz(k5GHzHTCapab);
+ EXPECT_CALL(*device_.get(), GetHTCapability(k5GHzChannel, _))
+ .WillOnce(DoAll(SetArgumentPointee<1>(ht_capab_5ghz), Return(true)));
+ EXPECT_TRUE(config_.GenerateConfigFile(&error, &ghz5_config_content));
+ EXPECT_NE(std::string::npos, ghz5_config_content.find(
+ kExpected80211n5GHzConfigContent))
+ << "Expected to find the following config...\n"
+ << kExpected80211n5GHzConfigContent << "..within content...\n"
+ << ghz5_config_content;
+ EXPECT_EQ(nullptr, error.get());
+ Mock::VerifyAndClearExpectations(device_.get());
+
+ // 2.4GHz channel.
+ config_.SetChannel(k24GHzChannel);
+ std::string ghz24_config_content;
+ chromeos::ErrorPtr error1;
+ std::string ht_capab_24ghz(k24GHzHTCapab);
+ EXPECT_CALL(*device_.get(), GetHTCapability(k24GHzChannel, _))
+ .WillOnce(DoAll(SetArgumentPointee<1>(ht_capab_24ghz), Return(true)));
+ EXPECT_TRUE(config_.GenerateConfigFile(&error1, &ghz24_config_content));
+ EXPECT_NE(std::string::npos, ghz24_config_content.find(
+ kExpected80211n24GHzConfigContent))
+ << "Expected to find the following config...\n"
+ << kExpected80211n24GHzConfigContent << "..within content...\n"
+ << ghz24_config_content;
+ EXPECT_EQ(nullptr, error.get());
+ Mock::VerifyAndClearExpectations(device_.get());
+}
+
+TEST_F(ConfigTest, RsnConfig) {
+ config_.SetSsid(kSsid);
+ config_.SetChannel(k24GHzChannel);
+ config_.SetHwMode(kHwMode80211g);
+ config_.SetInterfaceName(kInterface);
+ config_.SetSecurityMode(kSecurityModeRSN);
+
+ // Setup mock device.
+ SetupDevice(kInterface);
+
+ // Failed due to no passphrase specified.
+ std::string config_content;
+ chromeos::ErrorPtr error;
+ EXPECT_FALSE(config_.GenerateConfigFile(&error, &config_content));
+ EXPECT_THAT(error, IsConfigErrorStartingWith(
+ base::StringPrintf("Passphrase not set for security mode: %s",
+ kSecurityModeRSN)));
+
+ chromeos::ErrorPtr error1;
+ config_.SetPassphrase(kPassphrase);
+ EXPECT_TRUE(config_.GenerateConfigFile(&error1, &config_content));
+ EXPECT_NE(std::string::npos, config_content.find(
+ kExpectedRsnConfigContent))
+ << "Expected to find the following config...\n"
+ << kExpectedRsnConfigContent << "..within content...\n"
+ << config_content;
+ EXPECT_EQ(nullptr, error1.get());
+}
+
+} // namespace apmanager
diff --git a/daemon.cc b/daemon.cc
new file mode 100644
index 0000000..15d457c
--- /dev/null
+++ b/daemon.cc
@@ -0,0 +1,55 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "apmanager/daemon.h"
+
+#include <sysexits.h>
+
+#include <base/logging.h>
+#include <base/message_loop/message_loop_proxy.h>
+#include <base/run_loop.h>
+
+namespace apmanager {
+
+namespace {
+const char kAPManagerServiceName[] = "org.chromium.apmanager";
+const char kAPMRootServicePath[] = "/org/chromium/apmanager";
+} // namespace
+
+// static
+const char Daemon::kAPManagerGroupName[] = "apmanager";
+const char Daemon::kAPManagerUserName[] = "apmanager";
+
+Daemon::Daemon(const base::Closure& startup_callback)
+ : DBusServiceDaemon(kAPManagerServiceName, kAPMRootServicePath),
+ startup_callback_(startup_callback) {
+}
+
+int Daemon::OnInit() {
+ int return_code = chromeos::DBusServiceDaemon::OnInit();
+ if (return_code != EX_OK) {
+ return return_code;
+ }
+
+ // Signal that we've acquired all resources.
+ startup_callback_.Run();
+
+ // Start manager.
+ manager_->Start();
+
+ return EX_OK;
+}
+
+void Daemon::OnShutdown(int* return_code) {
+ manager_.reset();
+ chromeos::DBusServiceDaemon::OnShutdown(return_code);
+}
+
+void Daemon::RegisterDBusObjectsAsync(
+ chromeos::dbus_utils::AsyncEventSequencer* sequencer) {
+ manager_.reset(new apmanager::Manager());
+ manager_->RegisterAsync(object_manager_.get(), bus_, sequencer);
+}
+
+} // namespace apmanager
diff --git a/daemon.h b/daemon.h
new file mode 100644
index 0000000..cd6af1c
--- /dev/null
+++ b/daemon.h
@@ -0,0 +1,41 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef APMANAGER_DAEMON_H_
+#define APMANAGER_DAEMON_H_
+
+#include <base/callback_forward.h>
+#include <chromeos/daemons/dbus_daemon.h>
+
+#include "apmanager/manager.h"
+
+namespace apmanager {
+
+class Daemon : public chromeos::DBusServiceDaemon {
+ public:
+ // User and group to run the apmanager process.
+ static const char kAPManagerGroupName[];
+ static const char kAPManagerUserName[];
+
+ explicit Daemon(const base::Closure& startup_callback);
+ ~Daemon() = default;
+
+ protected:
+ int OnInit() override;
+ void OnShutdown(int* return_code) override;
+ void RegisterDBusObjectsAsync(
+ chromeos::dbus_utils::AsyncEventSequencer* sequencer) override;
+
+ private:
+ friend class DaemonTest;
+
+ std::unique_ptr<Manager> manager_;
+ base::Closure startup_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(Daemon);
+};
+
+} // namespace apmanager
+
+#endif // APMANAGER_DAEMON_H_
diff --git a/dbus_bindings/dbus-service-config.json b/dbus_bindings/dbus-service-config.json
new file mode 100644
index 0000000..8410ed4
--- /dev/null
+++ b/dbus_bindings/dbus-service-config.json
@@ -0,0 +1,6 @@
+{
+ "service_name": "org.chromium.apmanager",
+ "object_manager": {
+ "object_path": "/org/chromium/apmanager"
+ }
+} \ No newline at end of file
diff --git a/dbus_bindings/org.chromium.apmanager.Config.xml b/dbus_bindings/org.chromium.apmanager.Config.xml
new file mode 100644
index 0000000..815a617
--- /dev/null
+++ b/dbus_bindings/org.chromium.apmanager.Config.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<node xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <interface name="org.chromium.apmanager.Config">
+ <property name="Ssid" type="s" access="readwrite"/>
+ <property name="InterfaceName" type="s" access="readwrite"/>
+ <property name="SecurityMode" type="s" access="readwrite"/>
+ <property name="Passphrase" type="s" access="write"/>
+ <property name="HwMode" type="s" access="readwrite"/>
+ <property name="OperationMode" type="s" access="readwrite"/>
+ <property name="Channel" type="q" access="readwrite"/>
+ <property name="HiddenNetwork" type="b" access="readwrite"/>
+ <property name="BridgeInterface" type="s" access="readwrite"/>
+ <property name="ServerAddressIndex" type="q" access="readwrite"/>
+ <property name="FullDeviceControl" type="b" access="readwrite"/>
+ </interface>
+</node>
diff --git a/dbus_bindings/org.chromium.apmanager.Device.xml b/dbus_bindings/org.chromium.apmanager.Device.xml
new file mode 100644
index 0000000..a885703
--- /dev/null
+++ b/dbus_bindings/org.chromium.apmanager.Device.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<node xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <interface name="org.chromium.apmanager.Device">
+ <property name="DeviceName" type="s" access="read"/>
+ <property name="InUsed" type="b" access="read"/>
+ <property name="PreferredApInterface" type="s" access="read"/>
+ </interface>
+</node>
diff --git a/dbus_bindings/org.chromium.apmanager.Manager.xml b/dbus_bindings/org.chromium.apmanager.Manager.xml
new file mode 100644
index 0000000..591204f
--- /dev/null
+++ b/dbus_bindings/org.chromium.apmanager.Manager.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<node name="/org/chromium/apmanager/Manager"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <interface name="org.chromium.apmanager.Manager">
+ <method name="CreateService">
+ <tp:docstring>
+ Create an Access Point service instance.
+ </tp:docstring>
+ <arg name="service" type="o" direction="out">
+ <tp:docstring>
+ Service for managing an access point.
+ </tp:docstring>
+ </arg>
+ <annotation name="org.chromium.DBus.Method.Kind" value="async"/>
+ <annotation name="org.chromium.DBus.Method.IncludeDBusMessage"
+ value="true"/>
+ </method>
+ <method name="RemoveService">
+ <tp:docstring>
+ Remove the given access point service.
+ </tp:docstring>
+ <arg name="service" type="o" direction="in">
+ <tp:docstring>
+ Service for managing an access point.
+ </tp:docstring>
+ </arg>
+ <annotation name="org.chromium.DBus.Method.IncludeDBusMessage"
+ value="true"/>
+ </method>
+ </interface>
+</node>
diff --git a/dbus_bindings/org.chromium.apmanager.Service.xml b/dbus_bindings/org.chromium.apmanager.Service.xml
new file mode 100644
index 0000000..c17a2f9
--- /dev/null
+++ b/dbus_bindings/org.chromium.apmanager.Service.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<node xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <interface name="org.chromium.apmanager.Service">
+ <method name="Start">
+ <tp:docstring>
+ Start the service.
+ </tp:docstring>
+ </method>
+ <method name="Stop">
+ <tp:docstring>
+ Stop the service.
+ </tp:docstring>
+ </method>
+ <property name="Config" type="o" access="read"/>
+ <property name="State" type="s" access="read"/>
+ </interface>
+</node>
diff --git a/dbus_permissions/org.chromium.apmanager.conf b/dbus_permissions/org.chromium.apmanager.conf
new file mode 100644
index 0000000..85a1967
--- /dev/null
+++ b/dbus_permissions/org.chromium.apmanager.conf
@@ -0,0 +1,16 @@
+<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
+ "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+<busconfig>
+ <policy user="root">
+ <allow own="org.chromium.apmanager"/>
+ <allow send_destination="org.chromium.apmanager"/>
+ </policy>
+
+ <policy user="apmanager">
+ <allow own="org.chromium.apmanager"/>
+ </policy>
+
+ <policy group="apmanager">
+ <allow send_destination="org.chromium.apmanager" />
+ </policy>
+</busconfig>
diff --git a/device.cc b/device.cc
new file mode 100644
index 0000000..fe8c9d1
--- /dev/null
+++ b/device.cc
@@ -0,0 +1,374 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "apmanager/device.h"
+
+#include <base/strings/stringprintf.h>
+#include <chromeos/strings/string_utils.h>
+#include <shill/net/attribute_list.h>
+#include <shill/net/ieee80211.h>
+
+#include "apmanager/config.h"
+#include "apmanager/manager.h"
+
+using chromeos::dbus_utils::AsyncEventSequencer;
+using chromeos::dbus_utils::ExportedObjectManager;
+using org::chromium::apmanager::ManagerAdaptor;
+using shill::ByteString;
+using std::string;
+
+namespace apmanager {
+
+Device::Device(Manager* manager, const string& device_name)
+ : org::chromium::apmanager::DeviceAdaptor(this),
+ manager_(manager),
+ supports_ap_mode_(false) {
+ SetDeviceName(device_name);
+ SetInUsed(false);
+}
+
+Device::~Device() {}
+
+void Device::RegisterAsync(ExportedObjectManager* object_manager,
+ const scoped_refptr<dbus::Bus>& bus,
+ AsyncEventSequencer* sequencer,
+ int device_identifier) {
+ CHECK(!dbus_object_) << "Already registered";
+ dbus_path_ = dbus::ObjectPath(
+ base::StringPrintf("%s/devices/%d",
+ ManagerAdaptor::GetObjectPath().value().c_str(),
+ device_identifier));
+ dbus_object_.reset(
+ new chromeos::dbus_utils::DBusObject(
+ object_manager,
+ bus,
+ dbus_path_));
+ RegisterWithDBusObject(dbus_object_.get());
+ dbus_object_->RegisterAsync(
+ sequencer->GetHandler("Config.RegisterAsync() failed.", true));
+}
+
+void Device::RegisterInterface(const WiFiInterface& new_interface) {
+ LOG(INFO) << "RegisteringInterface " << new_interface.iface_name
+ << " on device " << GetDeviceName();
+ for (const auto& interface : interface_list_) {
+ // Done if interface already in the list.
+ if (interface.iface_index == new_interface.iface_index) {
+ LOG(INFO) << "Interface " << new_interface.iface_name
+ << " already registered.";
+ return;
+ }
+ }
+ interface_list_.push_back(new_interface);
+ UpdatePreferredAPInterface();
+}
+
+void Device::DeregisterInterface(const WiFiInterface& interface) {
+ LOG(INFO) << "DeregisteringInterface " << interface.iface_name
+ << " on device " << GetDeviceName();
+ for (auto it = interface_list_.begin(); it != interface_list_.end(); ++it) {
+ if (it->iface_index == interface.iface_index) {
+ interface_list_.erase(it);
+ UpdatePreferredAPInterface();
+ return;
+ }
+ }
+}
+
+void Device::ParseWiphyCapability(const shill::Nl80211Message& msg) {
+ // Parse NL80211_ATTR_SUPPORTED_IFTYPES for AP mode interface support.
+ shill::AttributeListConstRefPtr supported_iftypes;
+ if (!msg.const_attributes()->ConstGetNestedAttributeList(
+ NL80211_ATTR_SUPPORTED_IFTYPES, &supported_iftypes)) {
+ LOG(ERROR) << "NL80211_CMD_NEW_WIPHY had no NL80211_ATTR_SUPPORTED_IFTYPES";
+ return;
+ }
+ supported_iftypes->GetFlagAttributeValue(NL80211_IFTYPE_AP,
+ &supports_ap_mode_);
+
+ // Parse WiFi band capabilities.
+ shill::AttributeListConstRefPtr wiphy_bands;
+ if (!msg.const_attributes()->ConstGetNestedAttributeList(
+ NL80211_ATTR_WIPHY_BANDS, &wiphy_bands)) {
+ LOG(ERROR) << "NL80211_CMD_NEW_WIPHY had no NL80211_ATTR_WIPHY_BANDS";
+ return;
+ }
+
+ shill::AttributeIdIterator band_iter(*wiphy_bands);
+ for (; !band_iter.AtEnd(); band_iter.Advance()) {
+ BandCapability band_cap;
+
+ shill::AttributeListConstRefPtr wiphy_band;
+ if (!wiphy_bands->ConstGetNestedAttributeList(band_iter.GetId(),
+ &wiphy_band)) {
+ LOG(WARNING) << "WiFi band " << band_iter.GetId() << " not found";
+ continue;
+ }
+
+ // ...Each band has a FREQS attribute...
+ shill::AttributeListConstRefPtr frequencies;
+ if (!wiphy_band->ConstGetNestedAttributeList(NL80211_BAND_ATTR_FREQS,
+ &frequencies)) {
+ LOG(ERROR) << "BAND " << band_iter.GetId()
+ << " had no 'frequencies' attribute";
+ continue;
+ }
+
+ // ...And each FREQS attribute contains an array of information about the
+ // frequency...
+ shill::AttributeIdIterator freq_iter(*frequencies);
+ for (; !freq_iter.AtEnd(); freq_iter.Advance()) {
+ shill::AttributeListConstRefPtr frequency;
+ if (frequencies->ConstGetNestedAttributeList(freq_iter.GetId(),
+ &frequency)) {
+ // ...Including the frequency, itself (the part we want).
+ uint32_t frequency_value = 0;
+ if (frequency->GetU32AttributeValue(NL80211_FREQUENCY_ATTR_FREQ,
+ &frequency_value)) {
+ band_cap.frequencies.push_back(frequency_value);
+ }
+ }
+ }
+
+ wiphy_band->GetU16AttributeValue(NL80211_BAND_ATTR_HT_CAPA,
+ &band_cap.ht_capability_mask);
+ wiphy_band->GetU16AttributeValue(NL80211_BAND_ATTR_VHT_CAPA,
+ &band_cap.vht_capability_mask);
+ band_capability_.push_back(band_cap);
+ }
+}
+
+bool Device::ClaimDevice(bool full_control) {
+ if (GetInUsed()) {
+ LOG(ERROR) << "Failed to claim device [" << GetDeviceName()
+ << "]: already in used.";
+ return false;
+ }
+
+ if (full_control) {
+ for (const auto& interface : interface_list_) {
+ manager_->ClaimInterface(interface.iface_name);
+ claimed_interfaces_.insert(interface.iface_name);
+ }
+ } else {
+ manager_->ClaimInterface(GetPreferredApInterface());
+ claimed_interfaces_.insert(GetPreferredApInterface());
+ }
+ SetInUsed(true);
+ return true;
+}
+
+bool Device::ReleaseDevice() {
+ if (!GetInUsed()) {
+ LOG(ERROR) << "Failed to release device [" << GetDeviceName()
+ << "]: not currently in-used.";
+ return false;
+ }
+
+ for (const auto& interface : claimed_interfaces_) {
+ manager_->ReleaseInterface(interface);
+ }
+ claimed_interfaces_.clear();
+ SetInUsed(false);
+ return true;
+}
+
+bool Device::InterfaceExists(const string& interface_name) {
+ for (const auto& interface : interface_list_) {
+ if (interface.iface_name == interface_name) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool Device::GetHTCapability(uint16_t channel, string* ht_cap) {
+ // Get the band capability based on the channel.
+ BandCapability band_cap;
+ if (!GetBandCapability(channel, &band_cap)) {
+ LOG(ERROR) << "No band capability found for channel " << channel;
+ return false;
+ }
+
+ std::vector<string> ht_capability;
+ // LDPC coding capability.
+ if (band_cap.ht_capability_mask & shill::IEEE_80211::kHTCapMaskLdpcCoding) {
+ ht_capability.push_back("LDPC");
+ }
+
+ // Supported channel width set.
+ if (band_cap.ht_capability_mask &
+ shill::IEEE_80211::kHTCapMaskSupWidth2040) {
+ // Determine secondary channel is below or above the primary.
+ bool above = false;
+ if (!GetHTSecondaryChannelLocation(channel, &above)) {
+ LOG(ERROR) << "Unable to determine secondary channel location for "
+ << "channel " << channel;
+ return false;
+ }
+ if (above) {
+ ht_capability.push_back("HT40+");
+ } else {
+ ht_capability.push_back("HT40-");
+ }
+ }
+
+ // Spatial Multiplexing (SM) Power Save.
+ uint16_t power_save_mask =
+ (band_cap.ht_capability_mask >>
+ shill::IEEE_80211::kHTCapMaskSmPsShift) & 0x3;
+ if (power_save_mask == 0) {
+ ht_capability.push_back("SMPS-STATIC");
+ } else if (power_save_mask == 1) {
+ ht_capability.push_back("SMPS-DYNAMIC");
+ }
+
+ // HT-greenfield.
+ if (band_cap.ht_capability_mask & shill::IEEE_80211::kHTCapMaskGrnFld) {
+ ht_capability.push_back("GF");
+ }
+
+ // Short GI for 20 MHz.
+ if (band_cap.ht_capability_mask & shill::IEEE_80211::kHTCapMaskSgi20) {
+ ht_capability.push_back("SHORT-GI-20");
+ }
+
+ // Short GI for 40 MHz.
+ if (band_cap.ht_capability_mask & shill::IEEE_80211::kHTCapMaskSgi40) {
+ ht_capability.push_back("SHORT-GI-40");
+ }
+
+ // Tx STBC.
+ if (band_cap.ht_capability_mask & shill::IEEE_80211::kHTCapMaskTxStbc) {
+ ht_capability.push_back("TX-STBC");
+ }
+
+ // Rx STBC.
+ uint16_t rx_stbc =
+ (band_cap.ht_capability_mask >>
+ shill::IEEE_80211::kHTCapMaskRxStbcShift) & 0x3;
+ if (rx_stbc == 1) {
+ ht_capability.push_back("RX-STBC1");
+ } else if (rx_stbc == 2) {
+ ht_capability.push_back("RX-STBC12");
+ } else if (rx_stbc == 3) {
+ ht_capability.push_back("RX-STBC123");
+ }
+
+ // HT-delayed Block Ack.
+ if (band_cap.ht_capability_mask & shill::IEEE_80211::kHTCapMaskDelayBA) {
+ ht_capability.push_back("DELAYED-BA");
+ }
+
+ // Maximum A-MSDU length.
+ if (band_cap.ht_capability_mask & shill::IEEE_80211::kHTCapMaskMaxAmsdu) {
+ ht_capability.push_back("MAX-AMSDU-7935");
+ }
+
+ // DSSS/CCK Mode in 40 MHz.
+ if (band_cap.ht_capability_mask & shill::IEEE_80211::kHTCapMaskDsssCck40) {
+ ht_capability.push_back("DSSS_CCK-40");
+ }
+
+ // 40 MHz intolerant.
+ if (band_cap.ht_capability_mask &
+ shill::IEEE_80211::kHTCapMask40MHzIntolerant) {
+ ht_capability.push_back("40-INTOLERANT");
+ }
+
+ *ht_cap = base::StringPrintf("[%s]",
+ chromeos::string_utils::Join(" ", ht_capability).c_str());
+ return true;
+}
+
+bool Device::GetVHTCapability(uint16_t channel, string* vht_cap) {
+ // TODO(zqiu): to be implemented.
+ return false;
+}
+
+// static
+bool Device::GetHTSecondaryChannelLocation(uint16_t channel, bool* above) {
+ bool ret_val = true;
+
+ // Determine secondary channel location base on the channel. Refer to
+ // ht_cap section in hostapd.conf documentation.
+ switch (channel) {
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 40:
+ case 48:
+ case 56:
+ case 64:
+ *above = false;
+ break;
+
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 36:
+ case 44:
+ case 52:
+ case 60:
+ *above = true;
+ break;
+
+ default:
+ ret_val = false;
+ break;
+ }
+
+ return ret_val;
+}
+
+bool Device::GetBandCapability(uint16_t channel, BandCapability* capability) {
+ uint32_t frequency;
+ if (!Config::GetFrequencyFromChannel(channel, &frequency)) {
+ LOG(ERROR) << "Invalid channel " << channel;
+ return false;
+ }
+
+ for (const auto& band : band_capability_) {
+ if (std::find(band.frequencies.begin(),
+ band.frequencies.end(),
+ frequency) != band.frequencies.end()) {
+ *capability = band;
+ return true;
+ }
+ }
+ return false;
+}
+
+void Device::UpdatePreferredAPInterface() {
+ // Return if device doesn't support AP interface mode.
+ if (!supports_ap_mode_) {
+ return;
+ }
+
+ // Use the first registered AP mode interface if there is one, otherwise use
+ // the first registered managed mode interface. If none are available, then
+ // no interface can be used for AP operation on this device.
+ WiFiInterface preferred_interface;
+ for (const auto& interface : interface_list_) {
+ if (interface.iface_type == NL80211_IFTYPE_AP) {
+ preferred_interface = interface;
+ break;
+ } else if (interface.iface_type == NL80211_IFTYPE_STATION &&
+ preferred_interface.iface_name.empty()) {
+ preferred_interface = interface;
+ }
+ // Ignore all other interface types.
+ }
+ // Update preferred AP interface property.
+ SetPreferredApInterface(preferred_interface.iface_name);
+}
+
+} // namespace apmanager
diff --git a/device.h b/device.h
new file mode 100644
index 0000000..36be348
--- /dev/null
+++ b/device.h
@@ -0,0 +1,131 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef APMANAGER_DEVICE_H_
+#define APMANAGER_DEVICE_H_
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+#include <base/memory/ref_counted.h>
+#include <shill/net/byte_string.h>
+#include <shill/net/nl80211_message.h>
+
+#include "apmanager/dbus_adaptors/org.chromium.apmanager.Device.h"
+
+namespace apmanager {
+
+class Manager;
+
+// Abstraction for WiFi Device (PHY). Each device can have one or more
+// interfaces defined on it.
+class Device : public base::RefCounted<Device>,
+ public org::chromium::apmanager::DeviceAdaptor,
+ public org::chromium::apmanager::DeviceInterface {
+ public:
+ struct WiFiInterface {
+ WiFiInterface() : iface_index(0), iface_type(0) {}
+ WiFiInterface(const std::string& in_iface_name,
+ const std::string& in_device_name,
+ uint32_t in_iface_index,
+ uint32_t in_iface_type)
+ : iface_name(in_iface_name),
+ device_name(in_device_name),
+ iface_index(in_iface_index),
+ iface_type(in_iface_type) {}
+ std::string iface_name;
+ std::string device_name;
+ uint32_t iface_index;
+ uint32_t iface_type;
+ bool Equals(const WiFiInterface& other) const {
+ return this->iface_name == other.iface_name &&
+ this->device_name == other.device_name &&
+ this->iface_index == other.iface_index &&
+ this->iface_type == other.iface_type;
+ }
+ };
+
+ struct BandCapability {
+ std::vector<uint32_t> frequencies;
+ uint16_t ht_capability_mask;
+ uint16_t vht_capability_mask;
+ };
+
+ Device(Manager* manager, const std::string& device_name);
+ virtual ~Device();
+
+ // Register Device DBus object.
+ void RegisterAsync(
+ chromeos::dbus_utils::ExportedObjectManager* object_manager,
+ const scoped_refptr<dbus::Bus>& bus,
+ chromeos::dbus_utils::AsyncEventSequencer* sequencer,
+ int device_identifier);
+
+ // Register/deregister WiFi interface on this device.
+ virtual void RegisterInterface(const WiFiInterface& interface);
+ virtual void DeregisterInterface(const WiFiInterface& interface);
+
+ // Parse device capability from NL80211 message.
+ void ParseWiphyCapability(const shill::Nl80211Message& msg);
+
+ // Claim ownership of this device for AP operation. When |full_control| is
+ // set to true, this will claim all interfaces reside on this device.
+ // When it is set to false, this will only claim the interface used for AP
+ // operation.
+ virtual bool ClaimDevice(bool full_control);
+ // Release any claimed interfaces.
+ virtual bool ReleaseDevice();
+
+ // Return true if interface with |interface_name| resides on this device,
+ // false otherwise.
+ virtual bool InterfaceExists(const std::string& interface_name);
+
+ // Get HT and VHT capability string based on the operating channel.
+ // Return true and set the output capability string if such capability
+ // exist for the band the given |channel| is in, false otherwise.
+ virtual bool GetHTCapability(uint16_t channel, std::string* ht_cap);
+ virtual bool GetVHTCapability(uint16_t channel, std::string* vht_cap);
+
+ private:
+ friend class DeviceTest;
+
+ // Get the HT secondary channel location base on the primary channel.
+ // Return true and set the output |above| flag if channel is valid,
+ // otherwise return false.
+ static bool GetHTSecondaryChannelLocation(uint16_t channel, bool* above);
+
+ // Determine preferred interface to used for AP operation based on the list
+ // of interfaces reside on this device
+ void UpdatePreferredAPInterface();
+
+ // Get the capability for the band the given |channel| is in. Return true
+ // and set the output |capability| pointer if such capability exist for the
+ // band the given |channel| is in, false otherwise.
+ bool GetBandCapability(uint16_t channel, BandCapability* capability);
+
+ Manager* manager_;
+
+ // List of WiFi interfaces live on this device (PHY).
+ std::vector<WiFiInterface> interface_list_;
+
+ dbus::ObjectPath dbus_path_;
+ std::unique_ptr<chromeos::dbus_utils::DBusObject> dbus_object_;
+
+ // Flag indicating if this device supports AP mode interface or not.
+ bool supports_ap_mode_;
+
+ // Wiphy band capabilities.
+ std::vector<BandCapability> band_capability_;
+
+ // List of claimed interfaces.
+ std::set<std::string> claimed_interfaces_;
+
+ DISALLOW_COPY_AND_ASSIGN(Device);
+};
+
+} // namespace apmanager
+
+#endif // APMANAGER_DEVICE_H_
diff --git a/device_info.cc b/device_info.cc
new file mode 100644
index 0000000..6df9cd3
--- /dev/null
+++ b/device_info.cc
@@ -0,0 +1,322 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "apmanager/device_info.h"
+
+#include <linux/rtnetlink.h>
+
+#include <string>
+
+#include <base/bind.h>
+#include <base/files/file_util.h>
+#include <base/logging.h>
+#include <shill/net/ndisc.h>
+#include <shill/net/netlink_attribute.h>
+#include <shill/net/netlink_manager.h>
+#include <shill/net/nl80211_message.h>
+#include <shill/net/rtnl_handler.h>
+#include <shill/net/rtnl_listener.h>
+#include <shill/net/rtnl_message.h>
+
+#include "apmanager/manager.h"
+
+using base::Bind;
+using shill::ByteString;
+using shill::NetlinkManager;
+using shill::NetlinkMessage;
+using shill::Nl80211Message;
+using shill::RTNLHandler;
+using shill::RTNLMessage;
+using shill::RTNLListener;
+using std::map;
+using std::string;
+
+namespace apmanager {
+
+const char DeviceInfo::kDeviceInfoRoot[] = "/sys/class/net";
+const char DeviceInfo::kInterfaceUevent[] = "uevent";
+const char DeviceInfo::kInterfaceUeventWifiSignature[] = "DEVTYPE=wlan\n";
+
+DeviceInfo::DeviceInfo(Manager* manager)
+ : link_callback_(Bind(&DeviceInfo::LinkMsgHandler, Unretained(this))),
+ device_info_root_(kDeviceInfoRoot),
+ manager_(manager),
+ netlink_manager_(NetlinkManager::GetInstance()),
+ rtnl_handler_(RTNLHandler::GetInstance()) {
+}
+
+DeviceInfo::~DeviceInfo() {}
+
+void DeviceInfo::Start() {
+ // Start netlink manager.
+ netlink_manager_->Init();
+ uint16_t nl80211_family_id = netlink_manager_->GetFamily(
+ Nl80211Message::kMessageTypeString,
+ Bind(&Nl80211Message::CreateMessage));
+ if (nl80211_family_id == NetlinkMessage::kIllegalMessageType) {
+ LOG(FATAL) << "Didn't get a legal message type for 'nl80211' messages.";
+ }
+ Nl80211Message::SetMessageType(nl80211_family_id);
+ netlink_manager_->Start();
+
+ // Start enumerating WiFi devices (PHYs).
+ EnumerateDevices();
+
+ // Start RTNL for monitoring network interfaces.
+ rtnl_handler_->Start(RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV4_ROUTE |
+ RTMGRP_IPV6_IFADDR | RTMGRP_IPV6_ROUTE |
+ RTMGRP_ND_USEROPT);
+ link_listener_.reset(
+ new RTNLListener(RTNLHandler::kRequestLink, link_callback_));
+ // Request link infos.
+ rtnl_handler_->RequestDump(RTNLHandler::kRequestLink);
+}
+
+void DeviceInfo::Stop() {
+ link_listener_.reset();
+}
+
+void DeviceInfo::EnumerateDevices() {
+ shill::GetWiphyMessage get_wiphy;
+ get_wiphy.attributes()->SetFlagAttributeValue(NL80211_ATTR_SPLIT_WIPHY_DUMP,
+ true);
+ get_wiphy.AddFlag(NLM_F_DUMP);
+ netlink_manager_->SendNl80211Message(
+ &get_wiphy,
+ Bind(&DeviceInfo::OnWiFiPhyInfoReceived, AsWeakPtr()),
+ Bind(&NetlinkManager::OnAckDoNothing),
+ Bind(&NetlinkManager::OnNetlinkMessageError));
+}
+
+void DeviceInfo::OnWiFiPhyInfoReceived(const shill::Nl80211Message& msg) {
+ // Verify NL80211_CMD_NEW_WIPHY.
+ if (msg.command() != shill::NewWiphyMessage::kCommand) {
+ LOG(ERROR) << "Received unexpected command:"
+ << msg.command();
+ return;
+ }
+
+ string device_name;
+ if (!msg.const_attributes()->GetStringAttributeValue(NL80211_ATTR_WIPHY_NAME,
+ &device_name)) {
+ LOG(ERROR) << "NL80211_CMD_NEW_WIPHY had no NL80211_ATTR_WIPHY_NAME";
+ return;
+ }
+
+ if (GetDevice(device_name)) {
+ LOG(INFO) << "Device " << device_name << " already enumerated.";
+ return;
+ }
+
+ scoped_refptr<Device> device = new Device(manager_, device_name);
+ device->ParseWiphyCapability(msg);
+
+ // Register device
+ RegisterDevice(device);
+}
+
+void DeviceInfo::LinkMsgHandler(const RTNLMessage& msg) {
+ DCHECK(msg.type() == RTNLMessage::kTypeLink);
+
+ // Get interface name.
+ if (!msg.HasAttribute(IFLA_IFNAME)) {
+ LOG(ERROR) << "Link event message does not have IFLA_IFNAME!";
+ return;
+ }
+ ByteString b(msg.GetAttribute(IFLA_IFNAME));
+ string iface_name(reinterpret_cast<const char*>(b.GetConstData()));
+
+ int dev_index = msg.interface_index();
+ if (msg.mode() == RTNLMessage::kModeAdd) {
+ AddLinkMsgHandler(iface_name, dev_index);
+ } else if (msg.mode() == RTNLMessage::kModeDelete) {
+ DelLinkMsgHandler(iface_name, dev_index);
+ } else {
+ NOTREACHED();
+ }
+}
+
+void DeviceInfo::AddLinkMsgHandler(const string& iface_name, int iface_index) {
+ // Ignore non-wifi interfaces.
+ if (!IsWifiInterface(iface_name)) {
+ LOG(INFO) << "Ignore link event for non-wifi interface: " << iface_name;
+ return;
+ }
+
+ // Return if interface already existed. Could receive multiple add link event
+ // for a single interface.
+ if (interface_infos_.find(iface_index) != interface_infos_.end()) {
+ LOG(INFO) << "AddLinkMsgHandler: interface " << iface_name
+ << " is already added";
+ return;
+ }
+
+ // Add interface.
+ Device::WiFiInterface wifi_interface;
+ wifi_interface.iface_name = iface_name;
+ wifi_interface.iface_index = iface_index;
+ interface_infos_[iface_index] = wifi_interface;
+
+ // Get interface info.
+ GetWiFiInterfaceInfo(iface_index);
+}
+
+void DeviceInfo::DelLinkMsgHandler(const string& iface_name, int iface_index) {
+ LOG(INFO) << "DelLinkMsgHandler iface_name: " << iface_name
+ << "iface_index: " << iface_index;
+ map<uint32_t, Device::WiFiInterface>::iterator iter =
+ interface_infos_.find(iface_index);
+ if (iter != interface_infos_.end()) {
+ // Deregister interface from the Device.
+ scoped_refptr<Device> device = GetDevice(iter->second.device_name);
+ if (device) {
+ device->DeregisterInterface(iter->second);
+ }
+ interface_infos_.erase(iter);
+ }
+}
+
+bool DeviceInfo::IsWifiInterface(const string& iface_name) {
+ string contents;
+ if (!GetDeviceInfoContents(iface_name, kInterfaceUevent, &contents)) {
+ LOG(INFO) << "Interface " << iface_name << " has no uevent file";
+ return false;
+ }
+
+ if (contents.find(kInterfaceUeventWifiSignature) == string::npos) {
+ LOG(INFO) << "Interface " << iface_name << " is not a WiFi interface";
+ return false;
+ }
+
+ return true;
+}
+
+bool DeviceInfo::GetDeviceInfoContents(const string& iface_name,
+ const string& path_name,
+ string* contents_out) {
+ return base::ReadFileToString(
+ device_info_root_.Append(iface_name).Append(path_name),
+ contents_out);
+}
+
+void DeviceInfo::GetWiFiInterfaceInfo(int interface_index) {
+ shill::GetInterfaceMessage msg;
+ if (!msg.attributes()->SetU32AttributeValue(NL80211_ATTR_IFINDEX,
+ interface_index)) {
+ LOG(ERROR) << "Unable to set interface index attribute for "
+ "GetInterface message. Interface type cannot be "
+ "determined!";
+ return;
+ }
+
+ netlink_manager_->SendNl80211Message(
+ &msg,
+ Bind(&DeviceInfo::OnWiFiInterfaceInfoReceived, AsWeakPtr()),
+ Bind(&NetlinkManager::OnAckDoNothing),
+ Bind(&NetlinkManager::OnNetlinkMessageError));
+}
+
+void DeviceInfo::OnWiFiInterfaceInfoReceived(const shill::Nl80211Message& msg) {
+ if (msg.command() != NL80211_CMD_NEW_INTERFACE) {
+ LOG(ERROR) << "Message is not a new interface response";
+ return;
+ }
+
+ uint32_t interface_index;
+ if (!msg.const_attributes()->GetU32AttributeValue(NL80211_ATTR_IFINDEX,
+ &interface_index)) {
+ LOG(ERROR) << "Message contains no interface index";
+ return;
+ }
+ uint32_t interface_type;
+ if (!msg.const_attributes()->GetU32AttributeValue(NL80211_ATTR_IFTYPE,
+ &interface_type)) {
+ LOG(ERROR) << "Message contains no interface type";
+ return;
+ }
+
+ map<uint32_t, Device::WiFiInterface>::iterator iter =
+ interface_infos_.find(interface_index);
+ if (iter == interface_infos_.end()) {
+ LOG(ERROR) << "Receive WiFi interface info for non-exist interface: "
+ << interface_index;
+ return;
+ }
+ iter->second.iface_type = interface_type;
+
+ // Request PHY info, to know which Device to register this interface to.
+ GetWiFiInterfacePhyInfo(interface_index);
+}
+
+void DeviceInfo::GetWiFiInterfacePhyInfo(uint32_t iface_index) {
+ shill::GetWiphyMessage get_wiphy;
+ get_wiphy.attributes()->SetU32AttributeValue(NL80211_ATTR_IFINDEX,
+ iface_index);
+ netlink_manager_->SendNl80211Message(
+ &get_wiphy,
+ Bind(&DeviceInfo::OnWiFiInterfacePhyInfoReceived,
+ AsWeakPtr(),
+ iface_index),
+ Bind(&NetlinkManager::OnAckDoNothing),
+ Bind(&NetlinkManager::OnNetlinkMessageError));
+}
+
+void DeviceInfo::OnWiFiInterfacePhyInfoReceived(
+ uint32_t iface_index, const shill::Nl80211Message& msg) {
+ // Verify NL80211_CMD_NEW_WIPHY.
+ if (msg.command() != shill::NewWiphyMessage::kCommand) {
+ LOG(ERROR) << "Received unexpected command:"
+ << msg.command();
+ return;
+ }
+
+ map<uint32_t, Device::WiFiInterface>::iterator iter =
+ interface_infos_.find(iface_index);
+ if (iter == interface_infos_.end()) {
+ // Interface is gone by the time we received its PHY info.
+ LOG(ERROR) << "Interface [" << iface_index
+ << "] is deleted when PHY info is received";
+ return;
+ }
+
+ string device_name;
+ if (!msg.const_attributes()->GetStringAttributeValue(NL80211_ATTR_WIPHY_NAME,
+ &device_name)) {
+ LOG(ERROR) << "NL80211_CMD_NEW_WIPHY had no NL80211_ATTR_WIPHY_NAME";
+ return;
+ }
+
+ scoped_refptr<Device> device = GetDevice(device_name);
+ // Create device if it is not enumerated yet.
+ if (!device) {
+ device = new Device(manager_, device_name);
+ device->ParseWiphyCapability(msg);
+
+ // Register device
+ RegisterDevice(device);
+ }
+ iter->second.device_name = device_name;
+
+ device->RegisterInterface(iter->second);
+}
+
+void DeviceInfo::RegisterDevice(scoped_refptr<Device> device) {
+ if (!device) {
+ return;
+ }
+ devices_[device->GetDeviceName()] = device;
+ // Register device with manager.
+ manager_->RegisterDevice(device);
+}
+
+scoped_refptr<Device> DeviceInfo::GetDevice(const string& device_name) {
+ map<string, scoped_refptr<Device>>::iterator iter =
+ devices_.find(device_name);
+ if (iter == devices_.end()) {
+ return nullptr;
+ }
+ return iter->second;
+}
+
+} // namespace apmanager
diff --git a/device_info.h b/device_info.h
new file mode 100644
index 0000000..afd6521
--- /dev/null
+++ b/device_info.h
@@ -0,0 +1,106 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef APMANAGER_DEVICE_INFO_H_
+#define APMANAGER_DEVICE_INFO_H_
+
+#include <map>
+#include <string>
+
+#include <base/callback.h>
+#include <base/files/file_path.h>
+#include <base/memory/ref_counted.h>
+#include <base/memory/weak_ptr.h>
+#include <gtest/gtest_prod.h> // for FRIEND_TEST
+
+#include "apmanager/device.h"
+
+namespace shill {
+
+class NetlinkManager;
+class Nl80211Message;
+class RTNLHandler;
+class RTNLMessage;
+class RTNLListener;
+
+} // namespace shill
+
+namespace apmanager {
+
+class Manager;
+
+// DeviceInfo will enumerate WiFi devices (PHYs) during startup and on-demand
+// (when new interface is detected but the corresponding device is not
+// enumerated). And use RTNL to monitor creation/deletion of WiFi interfaces.
+class DeviceInfo : public base::SupportsWeakPtr<DeviceInfo> {
+ public:
+ explicit DeviceInfo(Manager* manager);
+ virtual ~DeviceInfo();
+
+ // Start and stop device detection monitoring.
+ void Start();
+ void Stop();
+
+ private:
+ friend class DeviceInfoTest;
+
+ static const char kDeviceInfoRoot[];
+ static const char kInterfaceUevent[];
+ static const char kInterfaceUeventWifiSignature[];
+
+ // Use nl80211 to enumerate available WiFi PHYs.
+ void EnumerateDevices();
+ void OnWiFiPhyInfoReceived(const shill::Nl80211Message& msg);
+
+ // Handler for RTNL link event.
+ void LinkMsgHandler(const shill::RTNLMessage& msg);
+ void AddLinkMsgHandler(const std::string& iface_name, int iface_index);
+ void DelLinkMsgHandler(const std::string& iface_name, int iface_index);
+
+ // Return true if the specify |iface_name| is a wifi interface, false
+ // otherwise.
+ bool IsWifiInterface(const std::string& iface_name);
+
+ // Return the contents of the device info file |path_name| for interface
+ // |iface_name| in output parameter |contents_out|. Return true if file
+ // read succeed, fales otherwise.
+ bool GetDeviceInfoContents(const std::string& iface_name,
+ const std::string& path_name,
+ std::string* contents_out);
+
+ // Use nl80211 to get WiFi interface information for interface on
+ // |iface_index|.
+ void GetWiFiInterfaceInfo(int iface_index);
+ void OnWiFiInterfaceInfoReceived(const shill::Nl80211Message& msg);
+
+ // Use nl80211 to get PHY info for interface on |iface_index|.
+ void GetWiFiInterfacePhyInfo(uint32_t iface_index);
+ void OnWiFiInterfacePhyInfoReceived(
+ uint32_t iface_index, const shill::Nl80211Message& msg);
+
+ scoped_refptr<Device> GetDevice(const std::string& phy_name);
+ void RegisterDevice(scoped_refptr<Device> device);
+
+ // Maps interface index to interface info
+ std::map<uint32_t, Device::WiFiInterface> interface_infos_;
+ // Maps device name to device object. Each device object represents a PHY.
+ std::map<std::string, scoped_refptr<Device>> devices_;
+
+ // RTNL link event callback and listener.
+ base::Callback<void(const shill::RTNLMessage&)> link_callback_;
+ std::unique_ptr<shill::RTNLListener> link_listener_;
+
+ base::FilePath device_info_root_;
+ Manager *manager_;
+
+ // Cache copy of singleton pointers.
+ shill::NetlinkManager* netlink_manager_;
+ shill::RTNLHandler* rtnl_handler_;
+
+ DISALLOW_COPY_AND_ASSIGN(DeviceInfo);
+};
+
+} // namespace apmanager
+
+#endif // APMANAGER_DEVICE_INFO_H_
diff --git a/device_info_unittest.cc b/device_info_unittest.cc
new file mode 100644
index 0000000..afe59ec
--- /dev/null
+++ b/device_info_unittest.cc
@@ -0,0 +1,373 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "apmanager/device_info.h"
+
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <base/files/file_util.h>
+#include <base/files/scoped_temp_dir.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <shill/net/byte_string.h>
+#include <shill/net/mock_netlink_manager.h>
+#include "shill/net/netlink_message_matchers.h"
+#include "shill/net/nl80211_attribute.h"
+#include "shill/net/nl80211_message.h"
+#include <shill/net/rtnl_message.h>
+
+#include "apmanager/mock_device.h"
+#include "apmanager/mock_manager.h"
+
+using shill::ByteString;
+using shill::Nl80211Message;
+using shill::RTNLMessage;
+using std::map;
+using std::string;
+using std::vector;
+using ::testing::_;
+using ::testing::Mock;
+
+namespace apmanager {
+
+namespace {
+
+const char kTestDeviceName[] = "test-phy";
+const char kTestInterface0Name[] = "test-interface0";
+const char kTestInterface1Name[] = "test-interface1";
+const uint32_t kTestInterface0Index = 1000;
+const uint32_t kTestInterface1Index = 1001;
+
+} // namespace
+
+class DeviceInfoTest : public testing::Test {
+ public:
+ DeviceInfoTest() : device_info_(&manager_) {}
+ virtual ~DeviceInfoTest() {}
+
+ virtual void SetUp() {
+ // Setup temporary directory for device info files.
+ CHECK(temp_dir_.CreateUniqueTempDir());
+ device_info_root_ = temp_dir_.path().Append("sys/class/net");
+ device_info_.device_info_root_ = device_info_root_;
+
+ // Setup mock pointers;
+ device_info_.netlink_manager_ = &netlink_manager_;
+ }
+
+ bool IsWifiInterface(const string& interface_name) {
+ return device_info_.IsWifiInterface(interface_name);
+ }
+
+ void CreateDeviceInfoFile(const string& interface_name,
+ const string& file_name,
+ const string& contents) {
+ base::FilePath info_path =
+ device_info_root_.Append(interface_name).Append(file_name);
+ EXPECT_TRUE(base::CreateDirectory(info_path.DirName()));
+ EXPECT_TRUE(base::WriteFile(info_path, contents.c_str(), contents.size()));
+ }
+
+ void SendLinkMsg(RTNLMessage::Mode mode,
+ uint32_t interface_index,
+ const string& interface_name) {
+ RTNLMessage message(RTNLMessage::kTypeLink,
+ mode,
+ 0,
+ 0,
+ 0,
+ interface_index,
+ shill::IPAddress::kFamilyIPv4);
+ message.SetAttribute(static_cast<uint16_t>(IFLA_IFNAME),
+ ByteString(interface_name, true));
+ device_info_.LinkMsgHandler(message);
+ }
+
+ void VerifyInterfaceList(const vector<Device::WiFiInterface>& interfaces) {
+ // Verify number of elements in the interface infos map and interface index
+ // of the elements in the map.
+ EXPECT_EQ(interfaces.size(), device_info_.interface_infos_.size());
+ for (const auto& interface : interfaces) {
+ map<uint32_t, Device::WiFiInterface>::iterator it =
+ device_info_.interface_infos_.find(interface.iface_index);
+ EXPECT_NE(device_info_.interface_infos_.end(), it);
+ EXPECT_TRUE(interface.Equals(it->second));
+ }
+ }
+
+ void VerifyDeviceList(const vector<scoped_refptr<Device>>& devices) {
+ // Verify number of elements in the device map and the elements in the map.
+ EXPECT_EQ(devices.size(), device_info_.devices_.size());
+ for (const auto& device : devices) {
+ map<string, scoped_refptr<Device>>::iterator it =
+ device_info_.devices_.find(device->GetDeviceName());
+ EXPECT_NE(device_info_.devices_.end(), it);
+ EXPECT_EQ(device, it->second);
+ }
+ }
+ void AddInterface(const Device::WiFiInterface& interface) {
+ device_info_.interface_infos_[interface.iface_index] = interface;
+ }
+
+ void OnWiFiPhyInfoReceived(const Nl80211Message& message) {
+ device_info_.OnWiFiPhyInfoReceived(message);
+ }
+
+ void OnWiFiInterfaceInfoReceived(const Nl80211Message& message) {
+ device_info_.OnWiFiInterfaceInfoReceived(message);
+ }
+
+ void OnWiFiInterfacePhyInfoReceived(uint32_t interface_index,
+ const Nl80211Message& message) {
+ device_info_.OnWiFiInterfacePhyInfoReceived(interface_index, message);
+ }
+
+ void RegisterDevice(scoped_refptr<Device> device) {
+ device_info_.RegisterDevice(device);
+ }
+
+ protected:
+ DeviceInfo device_info_;
+ MockManager manager_;
+ shill::MockNetlinkManager netlink_manager_;
+ base::ScopedTempDir temp_dir_;
+ base::FilePath device_info_root_;
+};
+
+MATCHER_P2(IsGetInfoMessage, command, index, "") {
+ if (arg->message_type() != Nl80211Message::GetMessageType()) {
+ return false;
+ }
+ const Nl80211Message *msg = reinterpret_cast<const Nl80211Message *>(arg);
+ if (msg->command() != command) {
+ return false;
+ }
+ uint32_t interface_index;
+ if (!msg->const_attributes()->GetU32AttributeValue(NL80211_ATTR_IFINDEX,
+ &interface_index)) {
+ return false;
+ }
+ // kInterfaceIndex is signed, but the attribute as handed from the kernel
+ // is unsigned. We're silently casting it away with this assignment.
+ uint32_t test_interface_index = index;
+ return interface_index == test_interface_index;
+}
+
+MATCHER_P(IsInterface, interface, "") {
+ return arg.Equals(interface);
+}
+
+MATCHER_P(IsDevice, device_name, "") {
+ return arg->GetDeviceName() == device_name;
+}
+
+TEST_F(DeviceInfoTest, EnumerateDevices) {
+ shill::NewWiphyMessage message;
+
+ // No device name in the message, failed to create device.
+ EXPECT_CALL(manager_, RegisterDevice(_)).Times(0);
+ OnWiFiPhyInfoReceived(message);
+
+ // Device name in the message, device should be created/register to manager.
+ message.attributes()->CreateNl80211Attribute(
+ NL80211_ATTR_WIPHY_NAME, shill::NetlinkMessage::MessageContext());
+ message.attributes()->SetStringAttributeValue(NL80211_ATTR_WIPHY_NAME,
+ kTestDeviceName);
+ EXPECT_CALL(manager_, RegisterDevice(IsDevice(kTestDeviceName))).Times(1);
+ OnWiFiPhyInfoReceived(message);
+ Mock::VerifyAndClearExpectations(&manager_);
+
+ // Receive a message for a device already created, should not create/register
+ // device again.
+ EXPECT_CALL(manager_, RegisterDevice(_)).Times(0);
+ OnWiFiPhyInfoReceived(message);
+}
+
+TEST_F(DeviceInfoTest, IsWiFiInterface) {
+ // No device info file exist, not a wifi interface.
+ EXPECT_FALSE(IsWifiInterface(kTestInterface0Name));
+
+ // Device info for an ethernet device, not a wifi interface
+ CreateDeviceInfoFile(kTestInterface0Name, "uevent", "INTERFACE=eth0\n");
+ EXPECT_FALSE(IsWifiInterface(kTestInterface0Name));
+
+ // Device info for a wifi interface.
+ CreateDeviceInfoFile(kTestInterface1Name, "uevent", "DEVTYPE=wlan\n");
+ EXPECT_TRUE(IsWifiInterface(kTestInterface1Name));
+}
+
+TEST_F(DeviceInfoTest, InterfaceDetection) {
+ vector<Device::WiFiInterface> interface_list;
+ // Ignore non-wifi interface.
+ SendLinkMsg(RTNLMessage::kModeAdd,
+ kTestInterface0Index,
+ kTestInterface0Name);
+ VerifyInterfaceList(interface_list);
+
+ // AddLink event for wifi interface.
+ CreateDeviceInfoFile(kTestInterface0Name, "uevent", "DEVTYPE=wlan\n");
+ EXPECT_CALL(netlink_manager_, SendNl80211Message(
+ IsGetInfoMessage(NL80211_CMD_GET_INTERFACE, kTestInterface0Index),
+ _, _, _)).Times(1);
+ SendLinkMsg(RTNLMessage::kModeAdd,
+ kTestInterface0Index,
+ kTestInterface0Name);
+ interface_list.push_back(Device::WiFiInterface(
+ kTestInterface0Name, "", kTestInterface0Index, 0));
+ VerifyInterfaceList(interface_list);
+ Mock::VerifyAndClearExpectations(&netlink_manager_);
+
+ // AddLink event for another wifi interface.
+ CreateDeviceInfoFile(kTestInterface1Name, "uevent", "DEVTYPE=wlan\n");
+ EXPECT_CALL(netlink_manager_, SendNl80211Message(
+ IsGetInfoMessage(NL80211_CMD_GET_INTERFACE, kTestInterface1Index),
+ _, _, _)).Times(1);
+ SendLinkMsg(RTNLMessage::kModeAdd,
+ kTestInterface1Index,
+ kTestInterface1Name);
+ interface_list.push_back(Device::WiFiInterface(
+ kTestInterface1Name, "", kTestInterface1Index, 0));
+ VerifyInterfaceList(interface_list);
+ Mock::VerifyAndClearExpectations(&netlink_manager_);
+
+ // AddLink event for an interface that's already added, no change to interface
+ // list.
+ EXPECT_CALL(netlink_manager_, SendNl80211Message(_, _, _, _)).Times(0);
+ SendLinkMsg(RTNLMessage::kModeAdd,
+ kTestInterface0Index,
+ kTestInterface0Name);
+ VerifyInterfaceList(interface_list);
+ Mock::VerifyAndClearExpectations(&netlink_manager_);
+
+ // Remove the first wifi interface.
+ SendLinkMsg(RTNLMessage::kModeDelete,
+ kTestInterface0Index,
+ kTestInterface0Name);
+ interface_list.clear();
+ interface_list.push_back(Device::WiFiInterface(
+ kTestInterface1Name, "", kTestInterface1Index, 0));
+ VerifyInterfaceList(interface_list);
+
+ // Remove the non-exist interface, no change to the list.
+ SendLinkMsg(RTNLMessage::kModeDelete,
+ kTestInterface0Index,
+ kTestInterface0Name);
+ VerifyInterfaceList(interface_list);
+
+ // Remove the last interface, list should be empty now.
+ SendLinkMsg(RTNLMessage::kModeDelete,
+ kTestInterface1Index,
+ kTestInterface1Name);
+ interface_list.clear();
+ VerifyInterfaceList(interface_list);
+}
+
+TEST_F(DeviceInfoTest, ParseWifiInterfaceInfo) {
+ // Add an interface without interface type info.
+ Device::WiFiInterface interface(
+ kTestInterface0Name, "", kTestInterface0Index, 0);
+ AddInterface(interface);
+ vector<Device::WiFiInterface> interface_list;
+ interface_list.push_back(interface);
+
+ // Message contain no interface index, no change to the interface info.
+ shill::NewInterfaceMessage message;
+ OnWiFiInterfaceInfoReceived(message);
+ VerifyInterfaceList(interface_list);
+
+ // Message contain no interface type, no change to the interface info.
+ message.attributes()->CreateNl80211Attribute(
+ NL80211_ATTR_IFINDEX, shill::NetlinkMessage::MessageContext());
+ message.attributes()->SetU32AttributeValue(NL80211_ATTR_IFINDEX,
+ kTestInterface0Index);
+ OnWiFiInterfaceInfoReceived(message);
+
+ // Message contain interface type, interface info should be updated with
+ // the interface type, and a new Nl80211 message should be send to query for
+ // the PHY info.
+ EXPECT_CALL(netlink_manager_, SendNl80211Message(
+ IsGetInfoMessage(NL80211_CMD_GET_WIPHY, kTestInterface0Index),
+ _, _, _)).Times(1);
+ message.attributes()->CreateNl80211Attribute(
+ NL80211_ATTR_IFTYPE, shill::NetlinkMessage::MessageContext());
+ message.attributes()->SetU32AttributeValue(NL80211_ATTR_IFTYPE,
+ NL80211_IFTYPE_AP);
+ OnWiFiInterfaceInfoReceived(message);
+ interface_list[0].iface_type = NL80211_IFTYPE_AP;
+ VerifyInterfaceList(interface_list);
+}
+
+TEST_F(DeviceInfoTest, ParsePhyInfoForWifiInterface) {
+ // Register a mock device.
+ scoped_refptr<MockDevice> device = new MockDevice();
+ device->SetDeviceName(kTestDeviceName);
+ EXPECT_CALL(manager_, RegisterDevice(_)).Times(1);
+ RegisterDevice(device);
+
+ // PHY info message.
+ shill::NewWiphyMessage message;
+ message.attributes()->CreateNl80211Attribute(
+ NL80211_ATTR_WIPHY_NAME, shill::NetlinkMessage::MessageContext());
+ message.attributes()->SetStringAttributeValue(NL80211_ATTR_WIPHY_NAME,
+ kTestDeviceName);
+
+ // Receive PHY info message for an interface that have not been detected yet.
+ EXPECT_CALL(*device.get(), RegisterInterface(_)).Times(0);
+ OnWiFiInterfacePhyInfoReceived(kTestInterface0Index, message);
+
+ // Pretend interface is detected through AddLink with interface info already
+ // received (interface type), and still missing PHY info for that interface.
+ Device::WiFiInterface interface(
+ kTestInterface0Name, "", kTestInterface0Index, NL80211_IFTYPE_AP);
+ AddInterface(interface);
+
+ // PHY info is received for a detected interface, should register that
+ // interface to the corresponding Device.
+ interface.device_name = kTestDeviceName;
+ EXPECT_CALL(*device.get(),
+ RegisterInterface(IsInterface(interface))).Times(1);
+ OnWiFiInterfacePhyInfoReceived(kTestInterface0Index, message);
+}
+
+TEST_F(DeviceInfoTest, ReceivePhyInfoBeforePhyIsEnumerated) {
+ // New interface is detected.
+ Device::WiFiInterface interface(
+ kTestInterface0Name, "", kTestInterface0Index, NL80211_IFTYPE_AP);
+ AddInterface(interface);
+ vector<Device::WiFiInterface> interface_list;
+ interface_list.push_back(interface);
+
+ // Received PHY info for the interface when the corresponding PHY is not
+ // enumerated yet, new device should be created and register to manager.
+ shill::NewWiphyMessage message;
+ message.attributes()->CreateNl80211Attribute(
+ NL80211_ATTR_WIPHY_NAME, shill::NetlinkMessage::MessageContext());
+ message.attributes()->SetStringAttributeValue(NL80211_ATTR_WIPHY_NAME,
+ kTestDeviceName);
+ EXPECT_CALL(manager_, RegisterDevice(IsDevice(kTestDeviceName))).Times(1);
+ OnWiFiInterfacePhyInfoReceived(kTestInterface0Index, message);
+ interface_list[0].device_name = kTestDeviceName;
+ VerifyInterfaceList(interface_list);
+}
+
+TEST_F(DeviceInfoTest, RegisterDevice) {
+ vector<scoped_refptr<Device>> device_list;
+
+ // Register a nullptr.
+ RegisterDevice(nullptr);
+ VerifyDeviceList(device_list);
+
+ // Register a device.
+ device_list.push_back(new Device(&manager_, kTestDeviceName));
+ EXPECT_CALL(manager_, RegisterDevice(device_list[0]));
+ RegisterDevice(device_list[0]);
+ VerifyDeviceList(device_list);
+}
+
+} // namespace apmanager
diff --git a/device_unittest.cc b/device_unittest.cc
new file mode 100644
index 0000000..516ffb3
--- /dev/null
+++ b/device_unittest.cc
@@ -0,0 +1,343 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "apmanager/device.h"
+
+#include <string>
+#include <vector>
+
+#include <base/strings/stringprintf.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <shill/net/ieee80211.h>
+#include <shill/net/nl80211_attribute.h>
+#include <shill/net/nl80211_message.h>
+
+#include "apmanager/mock_manager.h"
+
+using ::testing::_;
+using ::testing::Mock;
+using std::vector;
+
+namespace apmanager {
+
+namespace {
+
+const char kDeviceName[] = "phy0";
+const Device::WiFiInterface kApModeInterface0 = {
+ "uap0", kDeviceName, 1, NL80211_IFTYPE_AP
+};
+const Device::WiFiInterface kApModeInterface1 = {
+ "uap1", kDeviceName, 2, NL80211_IFTYPE_AP
+};
+const Device::WiFiInterface kManagedModeInterface0 = {
+ "wlan0", kDeviceName, 3, NL80211_IFTYPE_STATION
+};
+const Device::WiFiInterface kManagedModeInterface1 = {
+ "wlan1", kDeviceName, 4, NL80211_IFTYPE_STATION
+};
+const Device::WiFiInterface kMonitorModeInterface = {
+ "monitor0", kDeviceName, 5, NL80211_IFTYPE_MONITOR
+};
+
+} // namespace
+
+class DeviceTest : public testing::Test {
+ public:
+ DeviceTest() : device_(new Device(&manager_, kDeviceName)) {}
+
+ void VerifyInterfaceList(
+ const vector<Device::WiFiInterface>& interface_list) {
+ EXPECT_EQ(interface_list.size(), device_->interface_list_.size());
+ for (size_t i = 0; i < interface_list.size(); i++) {
+ EXPECT_TRUE(interface_list[i].Equals(device_->interface_list_[i]));
+ }
+ }
+
+ void VerifyPreferredApInterface(const std::string& interface_name) {
+ EXPECT_EQ(interface_name, device_->GetPreferredApInterface());
+ }
+
+ void AddWiphyBandAttribute(shill::AttributeListRefPtr wiphy_bands,
+ const std::string& band_name,
+ int band_id,
+ std::vector<uint32_t> frequency_list,
+ uint16_t ht_cap_mask) {
+ // Band attribute.
+ shill::AttributeListRefPtr wiphy_band;
+ wiphy_bands->CreateNestedAttribute(band_id, band_name.c_str());
+ wiphy_bands->GetNestedAttributeList(band_id, &wiphy_band);
+ // Frequencies attribute.
+ shill::AttributeListRefPtr frequencies;
+ wiphy_band->CreateNestedAttribute(NL80211_BAND_ATTR_FREQS,
+ "NL80211_BAND_ATTR_FREQS");
+ wiphy_band->GetNestedAttributeList(NL80211_BAND_ATTR_FREQS,
+ &frequencies);
+ // Frequency attribute.
+ for (size_t i = 0; i < frequency_list.size(); i++) {
+ shill::AttributeListRefPtr frequency;
+ frequencies->CreateNestedAttribute(
+ i, base::StringPrintf("Frequency %d", frequency_list[i]).c_str());
+ frequencies->GetNestedAttributeList(i, &frequency);
+ frequency->CreateU32Attribute(NL80211_FREQUENCY_ATTR_FREQ,
+ "NL80211_FREQUENCY_ATTR_FREQ");
+ frequency->SetU32AttributeValue(NL80211_FREQUENCY_ATTR_FREQ,
+ frequency_list[i]);
+ frequencies->SetNestedAttributeHasAValue(i);
+ }
+ wiphy_band->SetNestedAttributeHasAValue(NL80211_BAND_ATTR_FREQS);
+
+ // HT Capability attribute.
+ wiphy_band->CreateU16Attribute(NL80211_BAND_ATTR_HT_CAPA,
+ "NL80211_BAND_ATTR_HT_CAPA");
+ wiphy_band->SetU16AttributeValue(NL80211_BAND_ATTR_HT_CAPA, ht_cap_mask);
+
+ wiphy_bands->SetNestedAttributeHasAValue(band_id);
+ }
+
+ void EnableApModeSupport() {
+ device_->supports_ap_mode_ = true;
+ }
+
+ void VerifyApModeSupport(bool supports_ap_mode) {
+ EXPECT_EQ(supports_ap_mode, device_->supports_ap_mode_);
+ }
+
+ void VerifyFrequencyList(int band_id, std::vector<uint32_t> frequency_list) {
+ EXPECT_EQ(frequency_list, device_->band_capability_[band_id].frequencies);
+ }
+
+ protected:
+ MockManager manager_;
+ scoped_refptr<Device> device_;
+};
+
+TEST_F(DeviceTest, RegisterInterface) {
+ vector<Device::WiFiInterface> interface_list;
+ interface_list.push_back(kApModeInterface0);
+ interface_list.push_back(kManagedModeInterface0);
+ interface_list.push_back(kMonitorModeInterface);
+
+ device_->RegisterInterface(kApModeInterface0);
+ device_->RegisterInterface(kManagedModeInterface0);
+ device_->RegisterInterface(kMonitorModeInterface);
+
+ // Verify result interface list.
+ VerifyInterfaceList(interface_list);
+}
+
+TEST_F(DeviceTest, DeregisterInterface) {
+ vector<Device::WiFiInterface> interface_list;
+ interface_list.push_back(kApModeInterface0);
+ interface_list.push_back(kManagedModeInterface0);
+
+ // Register all interfaces, then deregister monitor0 and wlan1 interfaces.
+ device_->RegisterInterface(kApModeInterface0);
+ device_->RegisterInterface(kMonitorModeInterface);
+ device_->RegisterInterface(kManagedModeInterface0);
+ device_->RegisterInterface(kManagedModeInterface1);
+ device_->DeregisterInterface(kMonitorModeInterface);
+ device_->DeregisterInterface(kManagedModeInterface1);
+
+ // Verify result interface list.
+ VerifyInterfaceList(interface_list);
+}
+
+TEST_F(DeviceTest, PreferredAPInterface) {
+ EnableApModeSupport();
+
+ // Register a monitor mode interface, no preferred AP mode interface.
+ device_->RegisterInterface(kMonitorModeInterface);
+ VerifyPreferredApInterface("");
+
+ // Register a managed mode interface, should be set to preferred AP interface.
+ device_->RegisterInterface(kManagedModeInterface0);
+ VerifyPreferredApInterface(kManagedModeInterface0.iface_name);
+
+ // Register a ap mode interface, should be set to preferred AP interface.
+ device_->RegisterInterface(kApModeInterface0);
+ VerifyPreferredApInterface(kApModeInterface0.iface_name);
+
+ // Register another ap mode interface "uap1" and managed mode interface
+ // "wlan1", preferred AP interface should still be set to the first detected
+ // ap mode interface "uap0".
+ device_->RegisterInterface(kApModeInterface1);
+ device_->RegisterInterface(kManagedModeInterface1);
+ VerifyPreferredApInterface(kApModeInterface0.iface_name);
+
+ // Deregister the first ap mode interface, preferred AP interface should be
+ // set to the second ap mode interface.
+ device_->DeregisterInterface(kApModeInterface0);
+ VerifyPreferredApInterface(kApModeInterface1.iface_name);
+
+ // Deregister the second ap mode interface, preferred AP interface should be
+ // set the first managed mode interface.
+ device_->DeregisterInterface(kApModeInterface1);
+ VerifyPreferredApInterface(kManagedModeInterface0.iface_name);
+
+ // Deregister the first managed mode interface, preferred AP interface
+ // should be set to the second managed mode interface.
+ device_->DeregisterInterface(kManagedModeInterface0);
+ VerifyPreferredApInterface(kManagedModeInterface1.iface_name);
+
+ // Deregister the second managed mode interface, preferred AP interface
+ // should be set to empty string.
+ device_->DeregisterInterface(kManagedModeInterface1);
+ VerifyPreferredApInterface("");
+}
+
+TEST_F(DeviceTest, DeviceWithoutAPModeSupport) {
+ // AP mode support is not enabled for the device, so no preferred AP
+ // mode interface.
+ device_->RegisterInterface(kApModeInterface0);
+ VerifyPreferredApInterface("");
+}
+
+TEST_F(DeviceTest, ParseWiphyCapability) {
+ shill::NewWiphyMessage message;
+
+ // Supported interface types attribute.
+ message.attributes()->CreateNestedAttribute(
+ NL80211_ATTR_SUPPORTED_IFTYPES, "NL80211_ATTR_SUPPORTED_IFTYPES");
+ shill::AttributeListRefPtr supported_iftypes;
+ message.attributes()->GetNestedAttributeList(
+ NL80211_ATTR_SUPPORTED_IFTYPES, &supported_iftypes);
+ // Add support for AP mode interface.
+ supported_iftypes->CreateFlagAttribute(
+ NL80211_IFTYPE_AP, "NL80211_IFTYPE_AP");
+ supported_iftypes->SetFlagAttributeValue(NL80211_IFTYPE_AP, true);
+ message.attributes()->SetNestedAttributeHasAValue(
+ NL80211_ATTR_SUPPORTED_IFTYPES);
+
+ // Wiphy bands attribute.
+ message.attributes()->CreateNestedAttribute(
+ NL80211_ATTR_WIPHY_BANDS, "NL80211_ATTR_WIPHY_BANDS");
+ shill::AttributeListRefPtr wiphy_bands;
+ message.attributes()->GetNestedAttributeList(
+ NL80211_ATTR_WIPHY_BANDS, &wiphy_bands);
+
+ // 2.4GHz band capability.
+ const uint32_t kBand24GHzFrequencies[] = {
+ 2412, 2417, 2422, 2427, 2432, 2437, 2442, 2447, 2452, 2457, 2462, 2467};
+ const uint16_t kBand24GHzHTCapMask = shill::IEEE_80211::kHTCapMaskLdpcCoding |
+ shill::IEEE_80211::kHTCapMaskGrnFld |
+ shill::IEEE_80211::kHTCapMaskSgi20;
+ std::vector<uint32_t> band_24ghz_freq_list(
+ kBand24GHzFrequencies,
+ kBand24GHzFrequencies + sizeof(kBand24GHzFrequencies) /
+ sizeof(kBand24GHzFrequencies[0]));
+ AddWiphyBandAttribute(
+ wiphy_bands, "2.4GHz band", 0, band_24ghz_freq_list,
+ kBand24GHzHTCapMask);
+
+ // 5GHz band capability.
+ const uint32_t kBand5GHzFrequencies[] = {
+ 5180, 5190, 5200, 5210, 5220, 5230, 5240, 5260, 5280, 5300, 5320};
+ const uint16_t kBand5GHzHTCapMask =
+ shill::IEEE_80211::kHTCapMaskLdpcCoding |
+ shill::IEEE_80211::kHTCapMaskSupWidth2040 |
+ shill::IEEE_80211::kHTCapMaskGrnFld |
+ shill::IEEE_80211::kHTCapMaskSgi20 |
+ shill::IEEE_80211::kHTCapMaskSgi40;
+ std::vector<uint32_t> band_5ghz_freq_list(
+ kBand5GHzFrequencies,
+ kBand5GHzFrequencies + sizeof(kBand5GHzFrequencies) /
+ sizeof(kBand5GHzFrequencies[0]));
+ AddWiphyBandAttribute(
+ wiphy_bands, "5GHz band", 1, band_5ghz_freq_list, kBand5GHzHTCapMask);
+
+ message.attributes()->SetNestedAttributeHasAValue(NL80211_ATTR_WIPHY_BANDS);
+
+ device_->ParseWiphyCapability(message);
+
+ // Verify AP mode support.
+ VerifyApModeSupport(true);
+
+ // Verify frequency list for both bands.
+ VerifyFrequencyList(0, band_24ghz_freq_list);
+ VerifyFrequencyList(1, band_5ghz_freq_list);
+
+ // Verify HT Capablity for 2.4GHz band.
+ const char kBand24GHzHTCapability[] = "[LDPC SMPS-STATIC GF SHORT-GI-20]";
+ std::string band_24ghz_cap;
+ EXPECT_TRUE(device_->GetHTCapability(6, &band_24ghz_cap));
+ EXPECT_EQ(kBand24GHzHTCapability, band_24ghz_cap);
+
+ // Verify HT Capablity for 5GHz band.
+ const char kBand5GHzHTCapability[] =
+ "[LDPC HT40+ SMPS-STATIC GF SHORT-GI-20 SHORT-GI-40]";
+ std::string band_5ghz_cap;
+ EXPECT_TRUE(device_->GetHTCapability(36, &band_5ghz_cap));
+ EXPECT_EQ(kBand5GHzHTCapability, band_5ghz_cap);
+}
+
+TEST_F(DeviceTest, ClaimAndReleaseDeviceWithFullControl) {
+ EnableApModeSupport();
+
+ // Register multiple interfaces.
+ device_->RegisterInterface(kApModeInterface1);
+ device_->RegisterInterface(kManagedModeInterface1);
+
+ // Claim the device should claim all interfaces registered on this device..
+ EXPECT_CALL(manager_, ClaimInterface(kApModeInterface1.iface_name)).Times(1);
+ EXPECT_CALL(manager_,
+ ClaimInterface(kManagedModeInterface1.iface_name)).Times(1);
+ EXPECT_TRUE(device_->ClaimDevice(true));
+ Mock::VerifyAndClearExpectations(&manager_);
+
+ // Claim the device when it is already claimed.
+ EXPECT_CALL(manager_, ClaimInterface(_)).Times(0);
+ EXPECT_FALSE(device_->ClaimDevice(true));
+ Mock::VerifyAndClearExpectations(&manager_);
+
+ // Release the device should release all interfaces registered on this device.
+ EXPECT_CALL(manager_,
+ ReleaseInterface(kApModeInterface1.iface_name)).Times(1);
+ EXPECT_CALL(manager_,
+ ReleaseInterface(kManagedModeInterface1.iface_name)).Times(1);
+ EXPECT_TRUE(device_->ReleaseDevice());
+ Mock::VerifyAndClearExpectations(&manager_);
+
+ // Release the device when it is not claimed.
+ EXPECT_CALL(manager_, ReleaseInterface(_)).Times(0);
+ EXPECT_FALSE(device_->ReleaseDevice());
+ Mock::VerifyAndClearExpectations(&manager_);
+}
+
+TEST_F(DeviceTest, ClaimAndReleaseDeviceWithoutFullControl) {
+ EnableApModeSupport();
+
+ // Register multiple interfaces.
+ device_->RegisterInterface(kApModeInterface1);
+ device_->RegisterInterface(kManagedModeInterface1);
+
+ // Claim the device should only claim the preferred AP interface registered
+ // on this device.
+ EXPECT_CALL(manager_, ClaimInterface(kApModeInterface1.iface_name)).Times(1);
+ EXPECT_CALL(manager_,
+ ClaimInterface(kManagedModeInterface1.iface_name)).Times(0);
+ EXPECT_TRUE(device_->ClaimDevice(false));
+ Mock::VerifyAndClearExpectations(&manager_);
+
+ // Claim the device when it is already claimed.
+ EXPECT_CALL(manager_, ClaimInterface(_)).Times(0);
+ EXPECT_FALSE(device_->ClaimDevice(false));
+ Mock::VerifyAndClearExpectations(&manager_);
+
+ // Release the device should release the preferred AP interface registered
+ // on this device.
+ EXPECT_CALL(manager_,
+ ReleaseInterface(kApModeInterface1.iface_name)).Times(1);
+ EXPECT_CALL(manager_,
+ ReleaseInterface(kManagedModeInterface1.iface_name)).Times(0);
+ EXPECT_TRUE(device_->ReleaseDevice());
+ Mock::VerifyAndClearExpectations(&manager_);
+
+ // Release the device when it is not claimed.
+ EXPECT_CALL(manager_, ReleaseInterface(_)).Times(0);
+ EXPECT_FALSE(device_->ReleaseDevice());
+ Mock::VerifyAndClearExpectations(&manager_);
+}
+
+} // namespace apmanager
diff --git a/dhcp_server.cc b/dhcp_server.cc
new file mode 100644
index 0000000..1b3bcf2
--- /dev/null
+++ b/dhcp_server.cc
@@ -0,0 +1,122 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "apmanager/dhcp_server.h"
+
+#include <net/if.h>
+#include <signal.h>
+
+#include <base/strings/stringprintf.h>
+
+#include "apmanager/daemon.h"
+
+using std::string;
+
+namespace apmanager {
+
+// static.
+const char DHCPServer::kDnsmasqPath[] = "/usr/sbin/dnsmasq";
+const char DHCPServer::kDnsmasqConfigFilePathFormat[] =
+ "/var/run/apmanager/dnsmasq/dhcpd-%d.conf";
+const char DHCPServer::kDHCPLeasesFilePathFormat[] =
+ "/var/run/apmanager/dnsmasq/dhcpd-%d.leases";
+const char DHCPServer::kServerAddressFormat[] = "192.168.%d.254";
+const char DHCPServer::kAddressRangeLowFormat[] = "192.168.%d.1";
+const char DHCPServer::kAddressRangeHighFormat[] = "192.168.%d.128";
+const int DHCPServer::kServerAddressPrefix = 24;
+const int DHCPServer::kTerminationTimeoutSeconds = 2;
+
+DHCPServer::DHCPServer(uint16_t server_address_index,
+ const string& interface_name)
+ : server_address_index_(server_address_index),
+ interface_name_(interface_name),
+ server_address_(shill::IPAddress::kFamilyIPv4),
+ rtnl_handler_(shill::RTNLHandler::GetInstance()),
+ file_writer_(FileWriter::GetInstance()),
+ process_factory_(ProcessFactory::GetInstance()) {}
+
+DHCPServer::~DHCPServer() {
+ if (dnsmasq_process_) {
+ // The destructor of the Process will send a SIGKILL signal if it is not
+ // already terminated.
+ dnsmasq_process_->Kill(SIGTERM, kTerminationTimeoutSeconds);
+ dnsmasq_process_.reset();
+ rtnl_handler_->RemoveInterfaceAddress(
+ rtnl_handler_->GetInterfaceIndex(interface_name_), server_address_);
+ }
+}
+
+bool DHCPServer::Start() {
+ if (dnsmasq_process_) {
+ LOG(ERROR) << "DHCP Server already running";
+ return false;
+ }
+
+ // Generate dnsmasq config file.
+ string config_str = GenerateConfigFile();
+ string file_name = base::StringPrintf(kDnsmasqConfigFilePathFormat,
+ server_address_index_);
+ if (!file_writer_->Write(file_name, config_str)) {
+ LOG(ERROR) << "Failed to write configuration to a file";
+ return false;
+ }
+
+ // Setup local server address and bring up the interface in case it is down.
+ server_address_.SetAddressFromString(
+ base::StringPrintf(kServerAddressFormat, server_address_index_));
+ server_address_.set_prefix(kServerAddressPrefix);
+ int interface_index = rtnl_handler_->GetInterfaceIndex(interface_name_);
+ rtnl_handler_->AddInterfaceAddress(
+ interface_index,
+ server_address_,
+ server_address_.GetDefaultBroadcast(),
+ shill::IPAddress(shill::IPAddress::kFamilyIPv4));
+ rtnl_handler_->SetInterfaceFlags(interface_index, IFF_UP, IFF_UP);
+
+ // Start a dnsmasq process.
+ dnsmasq_process_.reset(process_factory_->CreateProcess());
+ dnsmasq_process_->AddArg(kDnsmasqPath);
+ dnsmasq_process_->AddArg(base::StringPrintf("--conf-file=%s",
+ file_name.c_str()));
+ if (!dnsmasq_process_->Start()) {
+ rtnl_handler_->RemoveInterfaceAddress(interface_index, server_address_);
+ dnsmasq_process_.reset();
+ LOG(ERROR) << "Failed to start dnsmasq process";
+ return false;
+ }
+
+ return true;
+}
+
+string DHCPServer::GenerateConfigFile() {
+ string server_address = base::StringPrintf(kServerAddressFormat,
+ server_address_index_);
+ string address_low = base::StringPrintf(kAddressRangeLowFormat,
+ server_address_index_);
+ string address_high = base::StringPrintf(kAddressRangeHighFormat,
+ server_address_index_);
+ string lease_file_path = base::StringPrintf(kDHCPLeasesFilePathFormat,
+ server_address_index_);
+ string config;
+ config += "port=0\n";
+ config += "bind-interfaces\n";
+ config += "log-dhcp\n";
+ // By default, dnsmasq process will spawn off another process to run the
+ // dnsmasq task in the "background" and exit the current process immediately.
+ // This means the daemon would not have any knowledge of the background
+ // dnsmasq process, and it will continue to run even after the AP service is
+ // terminated. Configure dnsmasq to run in "foreground" so no extra process
+ // will be spawned.
+ config += "keep-in-foreground\n";
+ // Explicitly set the user to apmanager. If not set, dnsmasq will default to
+ // run as "nobody".
+ base::StringAppendF(&config, "user=%s\n", Daemon::kAPManagerUserName);
+ base::StringAppendF(
+ &config, "dhcp-range=%s,%s\n", address_low.c_str(), address_high.c_str());
+ base::StringAppendF(&config, "interface=%s\n", interface_name_.c_str());
+ base::StringAppendF(&config, "dhcp-leasefile=%s\n", lease_file_path.c_str());
+ return config;
+}
+
+} // namespace apmanager
diff --git a/dhcp_server.h b/dhcp_server.h
new file mode 100644
index 0000000..f50567a
--- /dev/null
+++ b/dhcp_server.h
@@ -0,0 +1,55 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef APMANAGER_DHCP_SERVER_H_
+#define APMANAGER_DHCP_SERVER_H_
+
+#include <string>
+
+#include <base/macros.h>
+#include <shill/net/ip_address.h>
+#include <shill/net/rtnl_handler.h>
+
+#include "apmanager/file_writer.h"
+#include "apmanager/process_factory.h"
+
+namespace apmanager {
+
+class DHCPServer {
+ public:
+ DHCPServer(uint16_t server_address_index,
+ const std::string& interface_name);
+ virtual ~DHCPServer();
+
+ // Start the DHCP server
+ virtual bool Start();
+
+ private:
+ friend class DHCPServerTest;
+
+ std::string GenerateConfigFile();
+
+ static const char kDnsmasqPath[];
+ static const char kDnsmasqConfigFilePathFormat[];
+ static const char kDHCPLeasesFilePathFormat[];
+ static const char kServerAddressFormat[];
+ static const char kAddressRangeLowFormat[];
+ static const char kAddressRangeHighFormat[];
+ static const int kServerAddressPrefix;
+ static const int kTerminationTimeoutSeconds;
+
+ uint16_t server_address_index_;
+ std::string interface_name_;
+ shill::IPAddress server_address_;
+ std::unique_ptr<chromeos::Process> dnsmasq_process_;
+ shill::RTNLHandler* rtnl_handler_;
+ FileWriter* file_writer_;
+ ProcessFactory* process_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(DHCPServer);
+};
+
+} // namespace apmanager
+
+#endif // APMANAGER_DHCP_SERVER_H_
diff --git a/dhcp_server_factory.cc b/dhcp_server_factory.cc
new file mode 100644
index 0000000..602087b
--- /dev/null
+++ b/dhcp_server_factory.cc
@@ -0,0 +1,28 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "apmanager/dhcp_server_factory.h"
+
+namespace apmanager {
+
+namespace {
+
+base::LazyInstance<DHCPServerFactory> g_dhcp_server_factory
+ = LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+DHCPServerFactory::DHCPServerFactory() {}
+DHCPServerFactory::~DHCPServerFactory() {}
+
+DHCPServerFactory* DHCPServerFactory::GetInstance() {
+ return g_dhcp_server_factory.Pointer();
+}
+
+DHCPServer* DHCPServerFactory::CreateDHCPServer(
+ uint16_t server_addr_index, const std::string& interface_name) {
+ return new DHCPServer(server_addr_index, interface_name);
+}
+
+} // namespace apmanager
diff --git a/dhcp_server_factory.h b/dhcp_server_factory.h
new file mode 100644
index 0000000..d95e7ca
--- /dev/null
+++ b/dhcp_server_factory.h
@@ -0,0 +1,37 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef APMANAGER_DHCP_SERVER_FACTORY_H_
+#define APMANAGER_DHCP_SERVER_FACTORY_H_
+
+#include <string>
+
+#include <base/lazy_instance.h>
+
+#include "apmanager/dhcp_server.h"
+
+namespace apmanager {
+
+class DHCPServerFactory {
+ public:
+ virtual ~DHCPServerFactory();
+
+ // This is a singleton. Use DHCPServerFactory::GetInstance()->Foo().
+ static DHCPServerFactory* GetInstance();
+
+ virtual DHCPServer* CreateDHCPServer(uint16_t server_address_index,
+ const std::string& interface_name);
+
+ protected:
+ DHCPServerFactory();
+
+ private:
+ friend struct base::DefaultLazyInstanceTraits<DHCPServerFactory>;
+
+ DISALLOW_COPY_AND_ASSIGN(DHCPServerFactory);
+};
+
+} // namespace apmanager
+
+#endif // APMANAGER_DHCP_SERVER_FACTORY_H_
diff --git a/dhcp_server_unittest.cc b/dhcp_server_unittest.cc
new file mode 100644
index 0000000..dd90546
--- /dev/null
+++ b/dhcp_server_unittest.cc
@@ -0,0 +1,118 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "apmanager/dhcp_server.h"
+
+#include <string>
+
+#include <net/if.h>
+
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <chromeos/process_mock.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <shill/net/mock_rtnl_handler.h>
+
+#include "apmanager/mock_file_writer.h"
+#include "apmanager/mock_process_factory.h"
+
+using chromeos::ProcessMock;
+using ::testing::_;
+using ::testing::Mock;
+using ::testing::Return;
+using std::string;
+
+namespace {
+ const uint16_t kServerAddressIndex = 1;
+ const char kTestInterfaceName[] = "test_interface";
+ const char kBinSleep[] = "/bin/sleep";
+ const char kExpectedDnsmasqConfigFile[] =
+ "port=0\n"
+ "bind-interfaces\n"
+ "log-dhcp\n"
+ "keep-in-foreground\n"
+ "user=apmanager\n"
+ "dhcp-range=192.168.1.1,192.168.1.128\n"
+ "interface=test_interface\n"
+ "dhcp-leasefile=/var/run/apmanager/dnsmasq/dhcpd-1.leases\n";
+ const char kDnsmasqConfigFilePath[] =
+ "/var/run/apmanager/dnsmasq/dhcpd-1.conf";
+} // namespace
+
+namespace apmanager {
+
+class DHCPServerTest : public testing::Test {
+ public:
+ DHCPServerTest()
+ : dhcp_server_(new DHCPServer(kServerAddressIndex, kTestInterfaceName)),
+ rtnl_handler_(new shill::MockRTNLHandler()),
+ file_writer_(MockFileWriter::GetInstance()),
+ process_factory_(MockProcessFactory::GetInstance()) {}
+ virtual ~DHCPServerTest() {}
+
+ virtual void SetUp() {
+ dhcp_server_->rtnl_handler_ = rtnl_handler_.get();
+ dhcp_server_->file_writer_ = file_writer_;
+ dhcp_server_->process_factory_ = process_factory_;
+ }
+
+ virtual void TearDown() {
+ // Reset DHCP server now while RTNLHandler is still valid.
+ dhcp_server_.reset();
+ }
+
+ void StartDummyProcess() {
+ dhcp_server_->dnsmasq_process_.reset(new chromeos::ProcessImpl);
+ dhcp_server_->dnsmasq_process_->AddArg(kBinSleep);
+ dhcp_server_->dnsmasq_process_->AddArg("12345");
+ CHECK(dhcp_server_->dnsmasq_process_->Start());
+ }
+
+ string GenerateConfigFile() {
+ return dhcp_server_->GenerateConfigFile();
+ }
+
+ protected:
+ std::unique_ptr<DHCPServer> dhcp_server_;
+ std::unique_ptr<shill::MockRTNLHandler> rtnl_handler_;
+ MockFileWriter* file_writer_;
+ MockProcessFactory* process_factory_;
+};
+
+
+TEST_F(DHCPServerTest, GenerateConfigFile) {
+ string config_content = GenerateConfigFile();
+ EXPECT_STREQ(kExpectedDnsmasqConfigFile, config_content.c_str())
+ << "Expected to find the following config...\n"
+ << kExpectedDnsmasqConfigFile << ".....\n"
+ << config_content;
+}
+
+TEST_F(DHCPServerTest, StartWhenServerAlreadyStarted) {
+ StartDummyProcess();
+
+ EXPECT_FALSE(dhcp_server_->Start());
+}
+
+TEST_F(DHCPServerTest, StartSuccess) {
+ ProcessMock* process = new ProcessMock();
+
+ const int kInterfaceIndex = 1;
+ EXPECT_CALL(*file_writer_,
+ Write(kDnsmasqConfigFilePath, kExpectedDnsmasqConfigFile))
+ .WillOnce(Return(true));
+ EXPECT_CALL(*rtnl_handler_.get(), GetInterfaceIndex(kTestInterfaceName))
+ .WillOnce(Return(kInterfaceIndex));
+ EXPECT_CALL(*rtnl_handler_.get(),
+ AddInterfaceAddress(kInterfaceIndex, _, _, _)).Times(1);
+ EXPECT_CALL(*rtnl_handler_.get(),
+ SetInterfaceFlags(kInterfaceIndex, IFF_UP, IFF_UP)).Times(1);
+ EXPECT_CALL(*process_factory_, CreateProcess()).WillOnce(Return(process));
+ EXPECT_CALL(*process, Start()).WillOnce(Return(true));
+ EXPECT_TRUE(dhcp_server_->Start());
+ Mock::VerifyAndClearExpectations(rtnl_handler_.get());
+}
+
+} // namespace apmanager
diff --git a/event_dispatcher.cc b/event_dispatcher.cc
new file mode 100644
index 0000000..c95e4b4
--- /dev/null
+++ b/event_dispatcher.cc
@@ -0,0 +1,37 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "apmanager/event_dispatcher.h"
+
+#include <base/location.h>
+#include <base/message_loop/message_loop_proxy.h>
+#include <base/time/time.h>
+
+namespace apmanager {
+
+namespace {
+
+base::LazyInstance<EventDispatcher> g_event_dispatcher
+ = LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+EventDispatcher::EventDispatcher() {}
+EventDispatcher::~EventDispatcher() {}
+
+EventDispatcher* EventDispatcher::GetInstance() {
+ return g_event_dispatcher.Pointer();
+}
+
+bool EventDispatcher::PostTask(const base::Closure& task) {
+ return base::MessageLoopProxy::current()->PostTask(FROM_HERE, task);
+}
+
+bool EventDispatcher::PostDelayedTask(const base::Closure& task,
+ int64_t delay_ms) {
+ return base::MessageLoopProxy::current()->PostDelayedTask(
+ FROM_HERE, task, base::TimeDelta::FromMilliseconds(delay_ms));
+}
+
+} // namespace apmanager
diff --git a/event_dispatcher.h b/event_dispatcher.h
new file mode 100644
index 0000000..ac4d121
--- /dev/null
+++ b/event_dispatcher.h
@@ -0,0 +1,38 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef APMANAGER_EVENT_DISPATCHER_H_
+#define APMANAGER_EVENT_DISPATCHER_H_
+
+#include <base/callback.h>
+#include <base/lazy_instance.h>
+
+namespace apmanager {
+
+// Singleton class for dispatching tasks to current message loop.
+class EventDispatcher {
+ public:
+ virtual ~EventDispatcher();
+
+ // This is a singleton. Use EventDispatcher::GetInstance()->Foo().
+ static EventDispatcher* GetInstance();
+
+ // These are thin wrappers around calls of the same name in
+ // <base/message_loop_proxy.h>
+ virtual bool PostTask(const base::Closure& task);
+ virtual bool PostDelayedTask(const base::Closure& task,
+ int64_t delay_ms);
+
+ protected:
+ EventDispatcher();
+
+ private:
+ friend struct base::DefaultLazyInstanceTraits<EventDispatcher>;
+
+ DISALLOW_COPY_AND_ASSIGN(EventDispatcher);
+};
+
+} // namespace apmanager
+
+#endif // APMANAGER_EVENT_DISPATCHER_H_
diff --git a/file_writer.cc b/file_writer.cc
new file mode 100644
index 0000000..b81848b
--- /dev/null
+++ b/file_writer.cc
@@ -0,0 +1,35 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "apmanager/file_writer.h"
+
+#include <base/files/file_util.h>
+
+namespace apmanager {
+
+namespace {
+
+base::LazyInstance<FileWriter> g_file_writer
+ = LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+FileWriter::FileWriter() {}
+FileWriter::~FileWriter() {}
+
+FileWriter* FileWriter::GetInstance() {
+ return g_file_writer.Pointer();
+}
+
+bool FileWriter::Write(const std::string& file_name,
+ const std::string& content) {
+ if (base::WriteFile(base::FilePath(file_name),
+ content.c_str(),
+ content.size()) == -1) {
+ return false;
+ }
+ return true;
+}
+
+} // namespace apmanager
diff --git a/file_writer.h b/file_writer.h
new file mode 100644
index 0000000..320d623
--- /dev/null
+++ b/file_writer.h
@@ -0,0 +1,36 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef APMANAGER_FILE_WRITER_H_
+#define APMANAGER_FILE_WRITER_H_
+
+#include <string>
+
+#include <base/lazy_instance.h>
+
+namespace apmanager {
+
+// Singleton class for handling file writes.
+class FileWriter {
+ public:
+ virtual ~FileWriter();
+
+ // This is a singleton. Use FileWriter::GetInstance()->Foo().
+ static FileWriter* GetInstance();
+
+ virtual bool Write(const std::string& file_name,
+ const std::string& content);
+
+ protected:
+ FileWriter();
+
+ private:
+ friend struct base::DefaultLazyInstanceTraits<FileWriter>;
+
+ DISALLOW_COPY_AND_ASSIGN(FileWriter);
+};
+
+} // namespace apmanager
+
+#endif // APMANAGER_FILE_WRITER_H_
diff --git a/firewall_manager.cc b/firewall_manager.cc
new file mode 100644
index 0000000..c26a95e
--- /dev/null
+++ b/firewall_manager.cc
@@ -0,0 +1,177 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "apmanager/firewall_manager.h"
+
+#include <base/bind.h>
+#include <chromeos/dbus/service_constants.h>
+#include <chromeos/errors/error.h>
+
+using std::string;
+
+namespace apmanager {
+
+namespace {
+
+const uint16_t kDhcpServerPort = 67;
+const int kInvalidFd = -1;
+
+} // namespace
+
+FirewallManager::FirewallManager()
+ : lifeline_read_fd_(kInvalidFd),
+ lifeline_write_fd_(kInvalidFd) {}
+
+FirewallManager::~FirewallManager() {
+ if (lifeline_read_fd_ != kInvalidFd) {
+ close(lifeline_read_fd_);
+ close(lifeline_write_fd_);
+ }
+}
+
+void FirewallManager::Init(const scoped_refptr<dbus::Bus>& bus) {
+ CHECK(!permission_broker_proxy_) << "Already started";
+
+ if (!SetupLifelinePipe()) {
+ return;
+ }
+
+ permission_broker_proxy_.reset(
+ new org::chromium::PermissionBrokerProxy(
+ bus,
+ permission_broker::kPermissionBrokerServiceName));
+
+ // This will connect the name owner changed signal in DBus object proxy,
+ // The callback will be invoked as soon as service is avalilable. and will
+ // be cleared after it is invoked. So this will be an one time callback.
+ permission_broker_proxy_->GetObjectProxy()->WaitForServiceToBeAvailable(
+ base::Bind(&FirewallManager::OnServiceAvailable, base::Unretained(this)));
+
+ // This will continuously monitor the name owner of the service. However,
+ // it does not connect the name owner changed signal in DBus object proxy
+ // for some reason. In order to connect the name owner changed signal,
+ // either WaitForServiceToBeAvaiable or ConnectToSignal need to be invoked.
+ // Since we're not interested in any signals from the proxy,
+ // WaitForServiceToBeAvailable is used.
+ permission_broker_proxy_->GetObjectProxy()->SetNameOwnerChangedCallback(
+ base::Bind(&FirewallManager::OnServiceNameChanged,
+ base::Unretained(this)));
+}
+
+void FirewallManager::RequestDHCPPortAccess(const std::string& interface) {
+ CHECK(permission_broker_proxy_) << "Proxy not initialized yet";
+ if (dhcp_access_interfaces_.find(interface) !=
+ dhcp_access_interfaces_.end()) {
+ LOG(ERROR) << "DHCP access already requested for interface: " << interface;
+ return;
+ }
+ RequestUdpPortAccess(interface, kDhcpServerPort);
+ dhcp_access_interfaces_.insert(interface);
+}
+
+void FirewallManager::ReleaseDHCPPortAccess(const std::string& interface) {
+ CHECK(permission_broker_proxy_) << "Proxy not initialized yet";
+ if (dhcp_access_interfaces_.find(interface) ==
+ dhcp_access_interfaces_.end()) {
+ LOG(ERROR) << "DHCP access has not been requested for interface: "
+ << interface;
+ return;
+ }
+ ReleaseUdpPortAccess(interface, kDhcpServerPort);
+ dhcp_access_interfaces_.erase(interface);
+}
+
+bool FirewallManager::SetupLifelinePipe() {
+ if (lifeline_read_fd_ != kInvalidFd) {
+ LOG(ERROR) << "Lifeline pipe already created";
+ return false;
+ }
+
+ // Setup lifeline pipe.
+ int fds[2];
+ if (pipe(fds) != 0) {
+ PLOG(ERROR) << "Failed to create lifeline pipe";
+ return false;
+ }
+ lifeline_read_fd_ = fds[0];
+ lifeline_write_fd_ = fds[1];
+
+ return true;
+}
+
+void FirewallManager::OnServiceAvailable(bool service_available) {
+ LOG(INFO) << "FirewallManager::OnServiceAvailabe " << service_available;
+ // Nothing to be done if proxy service is not available.
+ if (!service_available) {
+ return;
+ }
+ RequestAllPortsAccess();
+}
+
+void FirewallManager::OnServiceNameChanged(const string& old_owner,
+ const string& new_owner) {
+ LOG(INFO) << "FirewallManager::OnServiceNameChanged old " << old_owner
+ << " new " << new_owner;
+ // Nothing to be done if no owner is attached to the proxy service.
+ if (new_owner.empty()) {
+ return;
+ }
+ RequestAllPortsAccess();
+}
+
+void FirewallManager::RequestAllPortsAccess() {
+ // Request access to DHCP port for all specified interfaces.
+ for (const auto& dhcp_interface : dhcp_access_interfaces_) {
+ RequestUdpPortAccess(dhcp_interface, kDhcpServerPort);
+ }
+}
+
+void FirewallManager::RequestUdpPortAccess(const string& interface,
+ uint16_t port) {
+ bool allowed = false;
+ // Pass the read end of the pipe to permission_broker, for it to monitor this
+ // process.
+ dbus::FileDescriptor fd(lifeline_read_fd_);
+ fd.CheckValidity();
+ chromeos::ErrorPtr error;
+ if (!permission_broker_proxy_->RequestUdpPortAccess(port,
+ interface,
+ fd,
+ &allowed,
+ &error)) {
+ LOG(ERROR) << "Failed to request UDP port access: "
+ << error->GetCode() << " " << error->GetMessage();
+ return;
+ }
+ if (!allowed) {
+ LOG(ERROR) << "Access request for UDP port " << port
+ << " on interface " << interface << " is denied";
+ return;
+ }
+ LOG(INFO) << "Access granted for UDP port " << port
+ << " on interface " << interface;;
+}
+
+void FirewallManager::ReleaseUdpPortAccess(const string& interface,
+ uint16_t port) {
+ chromeos::ErrorPtr error;
+ bool success;
+ if (!permission_broker_proxy_->ReleaseUdpPort(port,
+ interface,
+ &success,
+ &error)) {
+ LOG(ERROR) << "Failed to release UDP port access: "
+ << error->GetCode() << " " << error->GetMessage();
+ return;
+ }
+ if (!success) {
+ LOG(ERROR) << "Release request for UDP port " << port
+ << " on interface " << interface << " is denied";
+ return;
+ }
+ LOG(INFO) << "Access released for UDP port " << port
+ << " on interface " << interface;
+}
+
+} // namespace apmanager
diff --git a/firewall_manager.h b/firewall_manager.h
new file mode 100644
index 0000000..0f81332
--- /dev/null
+++ b/firewall_manager.h
@@ -0,0 +1,66 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef APMANAGER_FIREWALL_MANAGER_H_
+#define APMANAGER_FIREWALL_MANAGER_H_
+
+#include <set>
+#include <string>
+
+#include <base/macros.h>
+#include <base/memory/scoped_ptr.h>
+
+#include "permission_broker/dbus-proxies.h"
+
+// Class for managing required firewall rules for apmanager.
+namespace apmanager {
+
+class FirewallManager final {
+ public:
+ FirewallManager();
+ ~FirewallManager();
+
+ void Init(const scoped_refptr<dbus::Bus>& bus);
+
+ // Request/release DHCP port access for the specified interface.
+ void RequestDHCPPortAccess(const std::string& interface);
+ void ReleaseDHCPPortAccess(const std::string& interface);
+
+ private:
+ // Setup lifeline pipe to allow the remote firewall server
+ // (permission_broker) to monitor this process, so it can remove the firewall
+ // rules in case this process crashes.
+ bool SetupLifelinePipe();
+
+ void OnServiceAvailable(bool service_available);
+ void OnServiceNameChanged(const std::string& old_owner,
+ const std::string& new_owner);
+
+ // This is called when a new instance of permission_broker is detected. Since
+ // the new instance doesn't have any knowledge of previously port access
+ // requests, re-issue those requests to permission_broker to get in sync.
+ void RequestAllPortsAccess();
+
+ // Request/release UDP port access for the specified interface and port.
+ void RequestUdpPortAccess(const std::string& interface, uint16_t port);
+ void ReleaseUdpPortAccess(const std::string& interface, uint16_t port);
+
+ // DBus proxy for permission_broker.
+ std::unique_ptr<org::chromium::PermissionBrokerProxy>
+ permission_broker_proxy_;
+ // File descriptors for the two end of the pipe use for communicating with
+ // remote firewall server (permission_broker), where the remote firewall
+ // server will use the read end of the pipe to detect when this process exits.
+ int lifeline_read_fd_;
+ int lifeline_write_fd_;
+
+ // List of interfaces with DHCP port access.
+ std::set<std::string> dhcp_access_interfaces_;
+
+ DISALLOW_COPY_AND_ASSIGN(FirewallManager);
+};
+
+} // namespace apmanager
+
+#endif // APMANAGER_FIREWALL_MANAGER_H_
diff --git a/hostapd_monitor.cc b/hostapd_monitor.cc
new file mode 100644
index 0000000..e78febc
--- /dev/null
+++ b/hostapd_monitor.cc
@@ -0,0 +1,212 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "apmanager/hostapd_monitor.h"
+
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/un.h>
+
+#include <base/bind.h>
+#include <base/logging.h>
+#include <base/strings/stringprintf.h>
+#include <shill/net/io_handler_factory_container.h>
+#include <shill/net/sockets.h>
+
+using base::Bind;
+using base::Unretained;
+using shill::IOHandlerFactoryContainer;
+using std::string;
+
+namespace apmanager {
+
+// static.
+const char HostapdMonitor::kLocalPathFormat[] =
+ "/var/run/apmanager/hostapd/hostapd_ctrl_%s";
+const char HostapdMonitor::kHostapdCmdAttach[] = "ATTACH";
+const char HostapdMonitor::kHostapdRespOk[] = "OK\n";
+const char HostapdMonitor::kHostapdEventStationConnected[] = "AP-STA-CONNECTED";
+const char HostapdMonitor::kHostapdEventStationDisconnected[] =
+ "AP-STA-DISCONNECTED";
+const int HostapdMonitor::kHostapdCtrlIfaceCheckIntervalMs = 500;
+const int HostapdMonitor::kHostapdCtrlIfaceCheckMaxAttempts = 5;
+const int HostapdMonitor::kHostapdAttachTimeoutMs = 1000;
+const int HostapdMonitor::kInvalidSocket = -1;
+
+HostapdMonitor::HostapdMonitor(const EventCallback& callback,
+ const string& control_interface_path,
+ const string& network_interface_name)
+ : sockets_(new shill::Sockets()),
+ event_callback_(callback),
+ dest_path_(base::StringPrintf("%s/%s",
+ control_interface_path.c_str(),
+ network_interface_name.c_str())),
+ local_path_(base::StringPrintf(kLocalPathFormat,
+ network_interface_name.c_str())),
+ hostapd_socket_(kInvalidSocket),
+ io_handler_factory_(
+ IOHandlerFactoryContainer::GetInstance()->GetIOHandlerFactory()),
+ event_dispatcher_(EventDispatcher::GetInstance()),
+ weak_ptr_factory_(this),
+ started_(false) {}
+
+HostapdMonitor::~HostapdMonitor() {
+ if (hostapd_socket_ != kInvalidSocket) {
+ unlink(local_path_.c_str());
+ sockets_->Close(hostapd_socket_);
+ }
+}
+
+void HostapdMonitor::Start() {
+ if (started_) {
+ LOG(ERROR) << "HostapdMonitor already started";
+ return;
+ }
+
+ hostapd_ctrl_iface_check_count_ = 0;
+ // Start off by checking the control interface file for the hostapd process.
+ event_dispatcher_->PostTask(
+ Bind(&HostapdMonitor::HostapdCtrlIfaceCheckTask,
+ weak_ptr_factory_.GetWeakPtr()));
+ started_ = true;
+}
+
+void HostapdMonitor::HostapdCtrlIfaceCheckTask() {
+ struct stat buf;
+ if (stat(dest_path_.c_str(), &buf) != 0) {
+ if (hostapd_ctrl_iface_check_count_ >= kHostapdCtrlIfaceCheckMaxAttempts) {
+ // This indicates the hostapd failed to start. Invoke callback indicating
+ // hostapd start failed.
+ LOG(ERROR) << "Timeout waiting for hostapd control interface";
+ event_callback_.Run(kHostapdFailed, "");
+ } else {
+ hostapd_ctrl_iface_check_count_++;
+ event_dispatcher_->PostDelayedTask(
+ base::Bind(&HostapdMonitor::HostapdCtrlIfaceCheckTask,
+ weak_ptr_factory_.GetWeakPtr()),
+ kHostapdCtrlIfaceCheckIntervalMs);
+ }
+ return;
+ }
+
+ // Control interface is up, meaning hostapd started successfully.
+ event_callback_.Run(kHostapdStarted, "");
+
+ // Attach to the control interface to receive unsolicited event notifications.
+ AttachToHostapd();
+}
+
+void HostapdMonitor::AttachToHostapd() {
+ if (hostapd_socket_ != kInvalidSocket) {
+ LOG(ERROR) << "Socket already initialized";
+ return;
+ }
+
+ // Setup socket address for local file and remote file.
+ struct sockaddr_un local;
+ local.sun_family = AF_UNIX;
+ snprintf(local.sun_path, sizeof(local.sun_path), "%s", local_path_.c_str());
+ struct sockaddr_un dest;
+ dest.sun_family = AF_UNIX;
+ snprintf(dest.sun_path, sizeof(dest.sun_path), "%s", dest_path_.c_str());
+
+ // Setup socket for interprocess communication.
+ hostapd_socket_ = sockets_->Socket(PF_UNIX, SOCK_DGRAM, 0);
+ if (hostapd_socket_ < 0) {
+ LOG(ERROR) << "Failed to open hostapd socket";
+ return;
+ }
+ if (sockets_->Bind(hostapd_socket_,
+ reinterpret_cast<struct sockaddr*>(&local),
+ sizeof(local)) < 0) {
+ PLOG(ERROR) << "Failed to bind to local socket";
+ return;
+ }
+ if (sockets_->Connect(hostapd_socket_,
+ reinterpret_cast<struct sockaddr*>(&dest),
+ sizeof(dest)) < 0) {
+ PLOG(ERROR) << "Failed to connect";
+ return;
+ }
+
+ // Setup IO Input handler.
+ hostapd_input_handler_.reset(io_handler_factory_->CreateIOInputHandler(
+ hostapd_socket_,
+ Bind(&HostapdMonitor::ParseMessage, Unretained(this)),
+ Bind(&HostapdMonitor::OnReadError, Unretained(this))));
+
+ if (!SendMessage(kHostapdCmdAttach, strlen(kHostapdCmdAttach))) {
+ LOG(ERROR) << "Failed to attach to hostapd";
+ return;
+ }
+
+ // Start a timer for ATTACH response.
+ attach_timeout_callback_.Reset(
+ Bind(&HostapdMonitor::AttachTimeoutHandler,
+ weak_ptr_factory_.GetWeakPtr()));
+ event_dispatcher_->PostDelayedTask(attach_timeout_callback_.callback(),
+ kHostapdAttachTimeoutMs);
+ return;
+}
+
+void HostapdMonitor::AttachTimeoutHandler() {
+ LOG(ERROR) << "Timeout waiting for attach response";
+}
+
+// Method for sending message to hostapd control interface.
+bool HostapdMonitor::SendMessage(const char* message, size_t length) {
+ if (sockets_->Send(hostapd_socket_, message, length, 0) < 0) {
+ PLOG(ERROR) << "Send to hostapd failed";
+ return false;
+ }
+
+ return true;
+}
+
+void HostapdMonitor::ParseMessage(shill::InputData* data) {
+ string str(reinterpret_cast<const char*>(data->buf), data->len);
+ // "OK" response for the "ATTACH" command.
+ if (str == kHostapdRespOk) {
+ attach_timeout_callback_.Cancel();
+ return;
+ }
+
+ // Event messages are in format of <[Level]>[Event] [Detail message].
+ // For example: <2>AP-STA-CONNECTED 00:11:22:33:44:55
+ // Refer to wpa_ctrl.h for complete list of possible events.
+ if (str.find_first_of('<', 0) == 0 && str.find_first_of('>', 0) == 2) {
+ // Remove the log level.
+ string msg = str.substr(3);
+ string event;
+ string data;
+ size_t pos = msg.find_first_of(' ', 0);
+ if (pos == string::npos) {
+ event = msg;
+ } else {
+ event = msg.substr(0, pos);
+ data = msg.substr(pos + 1);
+ }
+
+ Event event_code;
+ if (event == kHostapdEventStationConnected) {
+ event_code = kStationConnected;
+ } else if (event == kHostapdEventStationDisconnected) {
+ event_code = kStationDisconnected;
+ } else {
+ LOG(INFO) << "Received unknown event: " << event;
+ return;
+ }
+ event_callback_.Run(event_code, data);
+ return;
+ }
+
+ LOG(INFO) << "Received unknown message: " << str;
+}
+
+void HostapdMonitor::OnReadError(const string& error_msg) {
+ LOG(FATAL) << "Hostapd Socket read returns error: "
+ << error_msg;
+}
+
+} // namespace apmanager
diff --git a/hostapd_monitor.h b/hostapd_monitor.h
new file mode 100644
index 0000000..05ab08b
--- /dev/null
+++ b/hostapd_monitor.h
@@ -0,0 +1,98 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef APMANAGER_HOSTAPD_MONITOR_H_
+#define APMANAGER_HOSTAPD_MONITOR_H_
+
+#include <string>
+
+#include <base/cancelable_callback.h>
+#include <base/macros.h>
+#include <base/memory/weak_ptr.h>
+
+#include "apmanager/event_dispatcher.h"
+
+namespace shill {
+
+struct InputData;
+class IOHandler;
+class IOHandlerFactory;
+class Sockets;
+
+} // namespace shill
+
+namespace apmanager {
+
+// Class for monitoring events from hostapd control interface.
+class HostapdMonitor {
+ public:
+ enum Event {
+ kHostapdFailed,
+ kHostapdStarted,
+ kStationConnected,
+ kStationDisconnected,
+ };
+
+ typedef base::Callback<void(Event event, const std::string& data)>
+ EventCallback;
+
+ HostapdMonitor(const EventCallback& callback_,
+ const std::string& control_interface_path,
+ const std::string& network_interface_name);
+ virtual ~HostapdMonitor();
+
+ virtual void Start();
+
+ private:
+ friend class HostapdMonitorTest;
+
+ static const char kLocalPathFormat[];
+ static const char kHostapdCmdAttach[];
+ static const char kHostapdRespOk[];
+ static const char kHostapdEventStationConnected[];
+ static const char kHostapdEventStationDisconnected[];
+ static const int kHostapdCtrlIfaceCheckIntervalMs;
+ static const int kHostapdCtrlIfaceCheckMaxAttempts;
+ static const int kHostapdAttachTimeoutMs;
+ static const int kInvalidSocket;
+
+ // Task for checking if hostapd control interface is up or not.
+ void HostapdCtrlIfaceCheckTask();
+
+ // Attach to hostapd control interface to receive unsolicited event
+ // notifications.
+ void AttachToHostapd();
+ void AttachTimeoutHandler();
+
+ bool SendMessage(const char* message, size_t length);
+ void ParseMessage(shill::InputData* data);
+ void OnReadError(const std::string& error_msg);
+
+ std::unique_ptr<shill::Sockets> sockets_;
+ EventCallback event_callback_;
+
+ // File path for interprocess communication with hostapd.
+ std::string dest_path_;
+ std::string local_path_;
+
+ // Socket descriptor for communication with hostapd.
+ int hostapd_socket_;
+
+ base::Callback<void(shill::InputData *)> hostapd_callback_;
+ std::unique_ptr<shill::IOHandler> hostapd_input_handler_;
+ shill::IOHandlerFactory *io_handler_factory_;
+ EventDispatcher* event_dispatcher_;
+ base::WeakPtrFactory<HostapdMonitor> weak_ptr_factory_;
+
+ int hostapd_ctrl_iface_check_count_;
+ base::CancelableClosure attach_timeout_callback_;
+
+ bool started_;
+
+ DISALLOW_COPY_AND_ASSIGN(HostapdMonitor);
+};
+
+} // namespace apmanager
+
+#endif // APMANAGER_HOSTAPD_MONITOR_H_
diff --git a/hostapd_monitor_unittest.cc b/hostapd_monitor_unittest.cc
new file mode 100644
index 0000000..44f0304
--- /dev/null
+++ b/hostapd_monitor_unittest.cc
@@ -0,0 +1,104 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "apmanager/hostapd_monitor.h"
+
+#include <base/bind.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <shill/net/io_handler.h>
+
+#include "apmanager/mock_event_dispatcher.h"
+
+using base::Bind;
+using base::Unretained;
+using ::testing::_;
+
+namespace {
+ const char kStationMac[] = "00:11:22:33:44:55";
+ const char kHostapdEventStationConnected[] =
+ "<2>AP-STA-CONNECTED 00:11:22:33:44:55";
+ const char kHostapdEventStationDisconnected[] =
+ "<2>AP-STA-DISCONNECTED 00:11:22:33:44:55";
+} // namespace
+
+namespace apmanager {
+
+class HostapdEventCallbackObserver {
+ public:
+ HostapdEventCallbackObserver()
+ : event_callback_(
+ Bind(&HostapdEventCallbackObserver::OnEventCallback,
+ Unretained(this))) {}
+ virtual ~HostapdEventCallbackObserver() {}
+
+ MOCK_METHOD2(OnEventCallback,
+ void(HostapdMonitor::Event event, const std::string& data));
+
+ const HostapdMonitor::EventCallback event_callback() {
+ return event_callback_;
+ }
+
+ private:
+ HostapdMonitor::EventCallback event_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(HostapdEventCallbackObserver);
+};
+
+class HostapdMonitorTest : public testing::Test {
+ public:
+ HostapdMonitorTest()
+ : hostapd_monitor_(observer_.event_callback(), "", ""),
+ event_dispatcher_(MockEventDispatcher::GetInstance()) {}
+
+ virtual void SetUp() {
+ hostapd_monitor_.event_dispatcher_ = event_dispatcher_;
+ }
+
+ void Start() {
+ hostapd_monitor_.Start();
+ }
+
+ void ParseMessage(shill::InputData* data) {
+ hostapd_monitor_.ParseMessage(data);
+ }
+
+ protected:
+ HostapdEventCallbackObserver observer_;
+ HostapdMonitor hostapd_monitor_;
+ MockEventDispatcher* event_dispatcher_;
+};
+
+TEST_F(HostapdMonitorTest, Start) {
+ EXPECT_CALL(*event_dispatcher_, PostTask(_)).Times(1);
+ Start();
+
+ // Monitor already started, nothing to be done.
+ EXPECT_CALL(*event_dispatcher_, PostTask(_)).Times(0);
+ Start();
+}
+
+TEST_F(HostapdMonitorTest, StationConnected) {
+ shill::InputData data;
+ data.buf = reinterpret_cast<unsigned char*>(
+ const_cast<char*>(kHostapdEventStationConnected));
+ data.len = strlen(kHostapdEventStationConnected);
+ EXPECT_CALL(observer_,
+ OnEventCallback(HostapdMonitor::kStationConnected,
+ kStationMac)).Times(1);
+ ParseMessage(&data);
+}
+
+TEST_F(HostapdMonitorTest, StationDisconnected) {
+ shill::InputData data;
+ data.buf = reinterpret_cast<unsigned char*>(
+ const_cast<char*>(kHostapdEventStationDisconnected));
+ data.len = strlen(kHostapdEventStationDisconnected);
+ EXPECT_CALL(observer_,
+ OnEventCallback(HostapdMonitor::kStationDisconnected,
+ kStationMac)).Times(1);
+ ParseMessage(&data);
+}
+
+} // namespace apmanager
diff --git a/init/apmanager-seccomp-amd64.policy b/init/apmanager-seccomp-amd64.policy
new file mode 100644
index 0000000..b59bb06
--- /dev/null
+++ b/init/apmanager-seccomp-amd64.policy
@@ -0,0 +1,89 @@
+# Tested on stumpy board
+getegid: 1
+geteuid: 1
+getgid: 1
+getpid: 1
+getresgid: 1
+getresuid: 1
+gettid: 1
+getuid: 1
+setgroups: 1
+setresgid: 1
+setresuid: 1
+
+clock_getres: 1
+clock_gettime: 1
+nanosleep: 1
+alarm: 1
+
+connect: 1
+bind: 1
+getsockname: 1
+pipe: 1
+recvfrom: 1
+recvmsg: 1
+sendmsg: 1
+select: 1
+sendto: 1
+setsockopt: 1
+socket: 1
+socketpair: 1
+
+close: 1
+creat: 1
+ioctl: 1
+open: 1
+prctl: 1
+read: 1
+write: 1
+arch_prctl: 1
+capget: 1
+
+brk: 1
+dup2: 1
+clone: 1
+fork: 1
+mmap: 1
+munmap: 1
+
+fcntl: 1
+fstat: 1
+fsync: 1
+ftruncate: 1
+lseek: 1
+stat: 1
+
+futex: 1
+
+exit: 1
+exit_group: 1
+kill: 1
+rt_sigaction: 1
+rt_sigprocmask: 1
+rt_sigreturn: 1
+signalfd4: 1
+tkill: 1
+
+epoll_create: 1
+epoll_ctl: 1
+epoll_wait: 1
+poll: 1
+wait4: 1
+
+chdir: 1
+readlink: 1
+umask: 1
+
+set_robust_list: 1
+set_tid_address: 1
+
+execve: 1
+mprotect: 1
+access: 1
+getrlimit: 1
+unlink: 1
+mkdir: 1
+rmdir: 1
+chown: 1
+chmod: 1
+writev: 1 \ No newline at end of file
diff --git a/init/apmanager-seccomp-arm.policy b/init/apmanager-seccomp-arm.policy
new file mode 100644
index 0000000..7a7f6ce
--- /dev/null
+++ b/init/apmanager-seccomp-arm.policy
@@ -0,0 +1,84 @@
+# Tested on peach_pit board
+socket: 1
+setsockopt: 1
+bind: 1
+clock_gettime: 1
+_newselect: 1
+recvfrom: 1
+epoll_ctl: 1
+gettid: 1
+write: 1
+epoll_wait: 1
+read: 1
+open: 1
+futex: 1
+brk: 1
+fstat64: 1
+mmap2: 1
+close: 1
+munmap: 1
+sendmsg: 1
+poll: 1
+recvmsg: 1
+fork: 1
+clone: 1
+ioctl: 1
+rt_sigprocmask: 1
+rt_sigaction: 1
+rt_sigreturn: 1
+sigreturn: 1
+connect: 1
+sendto: 1
+creat: 1
+access: 1
+set_robust_list: 1
+set_tid_address: 1
+wait4: 1
+exit: 1
+exit_group: 1
+epoll_create: 1
+
+fcntl64: 1
+prctl: 1
+capget: 1
+capset: 1
+dup2: 1
+
+getpid: 1
+getuid32: 1
+setgroups32: 1
+setresgid32: 1
+setresuid32: 1
+setresgid32: 1
+setresuid32: 1
+setitimer: 1
+mprotect: 1
+stat64: 1
+send: 1
+_llseek: 1
+signalfd4: 1
+execve: 1
+
+getsockname: 1
+readlink: 1
+gettimeofday: 1
+
+restart_syscall: 1
+uname: 1
+ARM_set_tls: 1
+ugetrlimit: 1
+kill: 1
+nanosleep: 1
+
+umask: 1
+pipe: 1
+chdir: 1
+ftruncate64: 1
+fsync: 1
+unlink: 1
+mkdir: 1
+rmdir: 1
+chmod: 1
+chown32: 1
+dup: 1
+writev: 1 \ No newline at end of file
diff --git a/init/apmanager-seccomp-mips.policy b/init/apmanager-seccomp-mips.policy
new file mode 100644
index 0000000..1f3bda9
--- /dev/null
+++ b/init/apmanager-seccomp-mips.policy
@@ -0,0 +1,69 @@
+socket: 1
+connect: 1
+setsockopt: 1
+bind: 1
+clock_gettime: 1
+_newselect: 1
+recvfrom: 1
+epoll_ctl: 1
+send: 1
+gettid: 1
+write: 1
+gettimeofday: 1
+epoll_wait: 1
+read: 1
+time: 1
+open: 1
+brk: 1
+fstat64: 1
+mmap: 1
+close: 1
+munmap: 1
+sendmsg: 1
+poll: 1
+recvmsg: 1
+clone: 1
+futex: 1
+ioctl: 1
+fcntl64: 1
+stat64: 1
+set_robust_list: 1
+rt_sigprocmask: 1
+execve: 1
+access: 1
+uname: 1
+lseek: 1
+set_thread_area: 1
+mprotect: 1
+set_tid_address: 1
+getrlimit: 1
+rt_sigaction: 1
+getsockname: 1
+umask: 1
+_llseek: 1
+nanosleep: 1
+restart_syscall: 1
+readlink: 1
+sendto: 1
+mkdir: 1
+capget: 1
+chown: 1
+pipe: 1
+chdir: 1
+chmod: 1
+getuid: 1
+unlink: 1
+dup2: 1
+getpid: 1
+munmap: 1
+rmdir: 1
+exit_group: 1
+ftruncate64: 1
+fsync: 1
+alarm: 1
+signalfd4: 1
+sigreturn: 1
+kill: 1
+rt_sigaction: 1
+waitpid: 1
+writev: 1 \ No newline at end of file
diff --git a/init/apmanager-seccomp-x86.policy b/init/apmanager-seccomp-x86.policy
new file mode 100644
index 0000000..aaf206a
--- /dev/null
+++ b/init/apmanager-seccomp-x86.policy
@@ -0,0 +1,71 @@
+# Tested on x86-alex board
+clock_gettime: 1
+_newselect: 1
+epoll_ctl: 1
+gettid: 1
+write: 1
+gettimeofday: 1
+epoll_wait: 1
+read: 1
+open: 1
+brk: 1
+fstat64: 1
+mmap2: 1
+close: 1
+munmap: 1
+poll: 1
+rt_sigprocmask: 1
+clone: 1
+signalfd4: 1
+ioctl: 1
+set_robust_list: 1
+fork: 1
+stat64: 1
+execve: 1
+kill: 1
+fcntl64: 1
+access: 1
+mprotect: 1
+waitpid: 1
+set_thread_area: 1
+set_tid_address: 1
+futex: 1
+rt_sigaction: 1
+ugetrlimit: 1
+uname: 1
+readlink: 1
+nanosleep: 1
+restart_syscall: 1
+exit_group: 1
+alarm: 1
+sigreturn: 1
+umask: 1
+_llseek: 1
+capget: 1
+pipe: 1
+chdir: 1
+getuid32: 1
+dup2: 1
+getpid: 1
+stat64: 1
+ftruncate64: 1
+fsync: 1
+prctl: 1
+capset: 1
+getresgid32: 1
+getresuid32: 1
+geteuid32: 1
+getgid32: 1
+getegid32: 1
+setresgid32: 1
+setresuid32: 1
+tgkill: 1
+time: 1
+epoll_create: 1
+socketcall: 1
+mkdir: 1
+rmdir: 1
+chown32: 1
+chmod: 1
+unlink: 1
+writev: 1 \ No newline at end of file
diff --git a/init/apmanager.conf b/init/apmanager.conf
new file mode 100644
index 0000000..3d6f886
--- /dev/null
+++ b/init/apmanager.conf
@@ -0,0 +1,31 @@
+# Copyright 2014 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+description "Run the access point manager daemon"
+author "chromium-os-dev@chromium.org"
+
+start on stopped iptables and stopped ip6tables and started shill
+stop on stopping system-services
+expect fork
+
+env APMANAGER_LOG_LEVEL=0
+
+pre-start script
+ # Load the module that provides the WiFi configuration API, since
+ # apmanager will abort if that API is not available. In most cases,
+ # cfg80211 will be loaded implicitly when the device driver is
+ # loaded (in preload-network). However, this deals with the
+ # first-boot case, in case apmanager starts before the device driver is
+ # loaded.
+ modprobe cfg80211 ||
+ logger -p err -t "$UPSTART_JOB" "Failed to load cfg80211"
+
+ # Create directory for storing config files.
+ mkdir -m 0755 -p /var/run/apmanager/hostapd
+ mkdir -m 0755 -p /var/run/apmanager/dnsmasq
+ chown -R apmanager:apmanager /var/run/apmanager/hostapd
+ chown -R apmanager:apmanager /var/run/apmanager/dnsmasq
+end script
+
+exec /usr/bin/apmanager --v="${APMANAGER_LOG_LEVEL}"
diff --git a/main.cc b/main.cc
new file mode 100644
index 0000000..baac20b
--- /dev/null
+++ b/main.cc
@@ -0,0 +1,129 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <vector>
+
+#include <base/bind.h>
+#include <base/command_line.h>
+#include <base/logging.h>
+#include <chromeos/minijail/minijail.h>
+#include <chromeos/syslog_logging.h>
+
+#include "apmanager/daemon.h"
+
+using std::vector;
+
+namespace {
+
+namespace switches {
+
+// Don't daemon()ize; run in foreground.
+const char kForeground[] = "foreground";
+// Flag that causes apmanager to show the help message and exit.
+const char kHelp[] = "help";
+
+// The help message shown if help flag is passed to the program.
+const char kHelpMessage[] = "\n"
+ "Available Switches: \n"
+ " --foreground\n"
+ " Don\'t daemon()ize; run in foreground.\n";
+} // namespace switches
+
+} // namespace
+
+namespace {
+
+const char kLoggerCommand[] = "/usr/bin/logger";
+const char kLoggerUser[] = "syslog";
+const char kSeccompFilePath[] = "/usr/share/policy/apmanager-seccomp.policy";
+
+} // namespace
+
+// Always logs to the syslog and logs to stderr if
+// we are running in the foreground.
+void SetupLogging(chromeos::Minijail* minijail,
+ bool foreground,
+ const char* daemon_name) {
+ int log_flags = 0;
+ log_flags |= chromeos::kLogToSyslog;
+ log_flags |= chromeos::kLogHeader;
+ if (foreground) {
+ log_flags |= chromeos::kLogToStderr;
+ }
+ chromeos::InitLog(log_flags);
+
+ if (!foreground) {
+ vector<char*> logger_command_line;
+ int logger_stdin_fd;
+ logger_command_line.push_back(const_cast<char*>(kLoggerCommand));
+ logger_command_line.push_back(const_cast<char*>("--priority"));
+ logger_command_line.push_back(const_cast<char*>("daemon.err"));
+ logger_command_line.push_back(const_cast<char*>("--tag"));
+ logger_command_line.push_back(const_cast<char*>(daemon_name));
+ logger_command_line.push_back(nullptr);
+
+ struct minijail* jail = minijail->New();
+ minijail->DropRoot(jail, kLoggerUser, kLoggerUser);
+
+ if (!minijail->RunPipeAndDestroy(jail, logger_command_line,
+ nullptr, &logger_stdin_fd)) {
+ LOG(ERROR) << "Unable to spawn logger. "
+ << "Writes to stderr will be discarded.";
+ return;
+ }
+
+ // Note that we don't set O_CLOEXEC here. This means that stderr
+ // from any child processes will, by default, be logged to syslog.
+ if (dup2(logger_stdin_fd, fileno(stderr)) != fileno(stderr)) {
+ LOG(ERROR) << "Failed to redirect stderr to syslog: "
+ << strerror(errno);
+ }
+ close(logger_stdin_fd);
+ }
+}
+
+void DropPrivileges(chromeos::Minijail* minijail) {
+ struct minijail* jail = minijail->New();
+ minijail->DropRoot(jail, apmanager::Daemon::kAPManagerUserName,
+ apmanager::Daemon::kAPManagerGroupName);
+ // Permissions needed for the daemon and its child processes for managing
+ // network interfaces and binding to network sockets.
+ minijail->UseCapabilities(jail, CAP_TO_MASK(CAP_NET_ADMIN) |
+ CAP_TO_MASK(CAP_NET_RAW) |
+ CAP_TO_MASK(CAP_NET_BIND_SERVICE));
+ minijail->UseSeccompFilter(jail, kSeccompFilePath);
+ minijail_enter(jail);
+ minijail->Destroy(jail);
+}
+
+void OnStartup(const char* daemon_name, base::CommandLine* cl) {
+ chromeos::Minijail* minijail = chromeos::Minijail::GetInstance();
+ SetupLogging(minijail, cl->HasSwitch(switches::kForeground), daemon_name);
+
+ LOG(INFO) << __func__ << ": Dropping privileges";
+
+ // Now that the daemon has all the resources it needs to run, we can drop
+ // privileges further.
+ DropPrivileges(minijail);
+}
+
+int main(int argc, char* argv[]) {
+ base::CommandLine::Init(argc, argv);
+ base::CommandLine* cl = base::CommandLine::ForCurrentProcess();
+
+ if (cl->HasSwitch(switches::kHelp)) {
+ LOG(INFO) << switches::kHelpMessage;
+ return 0;
+ }
+
+ const int nochdir = 0, noclose = 0;
+ if (!cl->HasSwitch(switches::kForeground))
+ PLOG_IF(FATAL, daemon(nochdir, noclose) == -1) << "Failed to daemonize";
+
+ apmanager::Daemon daemon(base::Bind(&OnStartup, argv[0], cl));
+
+ daemon.Run();
+
+ return 0;
+}
diff --git a/manager.cc b/manager.cc
new file mode 100644
index 0000000..959119d
--- /dev/null
+++ b/manager.cc
@@ -0,0 +1,215 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "apmanager/manager.h"
+
+#include <base/bind.h>
+#include <chromeos/dbus/service_constants.h>
+
+using chromeos::dbus_utils::AsyncEventSequencer;
+using chromeos::dbus_utils::ExportedObjectManager;
+using chromeos::dbus_utils::DBusMethodResponse;
+using std::string;
+
+namespace apmanager {
+
+Manager::Manager()
+ : org::chromium::apmanager::ManagerAdaptor(this),
+ service_identifier_(0),
+ device_identifier_(0),
+ device_info_(this) {}
+
+Manager::~Manager() {
+ // Terminate all services before cleanup other resources.
+ for (auto& service : services_) {
+ service.reset();
+ }
+}
+
+void Manager::RegisterAsync(ExportedObjectManager* object_manager,
+ const scoped_refptr<dbus::Bus>& bus,
+ AsyncEventSequencer* sequencer) {
+ CHECK(!dbus_object_) << "Already registered";
+ dbus_object_.reset(
+ new chromeos::dbus_utils::DBusObject(
+ object_manager,
+ bus,
+ org::chromium::apmanager::ManagerAdaptor::GetObjectPath()));
+ RegisterWithDBusObject(dbus_object_.get());
+ dbus_object_->RegisterAsync(
+ sequencer->GetHandler("Manager.RegisterAsync() failed.", true));
+ bus_ = bus;
+
+ shill_proxy_.Init(bus);
+ firewall_manager_.Init(bus);
+}
+
+void Manager::Start() {
+ device_info_.Start();
+}
+
+void Manager::Stop() {
+ device_info_.Stop();
+}
+
+void Manager::CreateService(
+ std::unique_ptr<DBusMethodResponse<dbus::ObjectPath>> response,
+ dbus::Message* message) {
+ LOG(INFO) << "Manager::CreateService";
+ scoped_refptr<AsyncEventSequencer> sequencer(new AsyncEventSequencer());
+ std::unique_ptr<Service> service(new Service(this, service_identifier_));
+
+ service->RegisterAsync(
+ dbus_object_->GetObjectManager().get(), bus_, sequencer.get());
+ sequencer->OnAllTasksCompletedCall({
+ base::Bind(&Manager::OnServiceRegistered,
+ base::Unretained(this),
+ base::Passed(&response),
+ base::Passed(&service))
+ });
+
+ base::Closure on_connection_vanish = base::Bind(
+ &Manager::OnAPServiceOwnerDisappeared,
+ base::Unretained(this),
+ service_identifier_);
+ service_watchers_[service_identifier_].reset(
+ new DBusServiceWatcher{bus_, message->GetSender(), on_connection_vanish});
+ service_identifier_++;
+}
+
+bool Manager::RemoveService(chromeos::ErrorPtr* error,
+ dbus::Message* message,
+ const dbus::ObjectPath& in_service) {
+ for (auto it = services_.begin(); it != services_.end(); ++it) {
+ if ((*it)->dbus_path() == in_service) {
+ // Verify the owner.
+ auto watcher = service_watchers_.find((*it)->identifier());
+ CHECK(watcher != service_watchers_.end())
+ << "DBus watcher not created for service: " << (*it)->identifier();
+ if (watcher->second->connection_name() != message->GetSender()) {
+ chromeos::Error::AddToPrintf(
+ error, FROM_HERE, chromeos::errors::dbus::kDomain, kManagerError,
+ "Service %d is owned by another local process.",
+ (*it)->identifier());
+ return false;
+ }
+ service_watchers_.erase(watcher);
+
+ services_.erase(it);
+ return true;
+ }
+ }
+
+ chromeos::Error::AddTo(
+ error, FROM_HERE, chromeos::errors::dbus::kDomain, kManagerError,
+ "Service does not exist");
+ return false;
+}
+
+scoped_refptr<Device> Manager::GetAvailableDevice() {
+ for (const auto& device : devices_) {
+ // Look for an unused device with AP interface mode support.
+ if (!device->GetInUsed() && !device->GetPreferredApInterface().empty()) {
+ return device;
+ }
+ }
+ return nullptr;
+}
+
+scoped_refptr<Device> Manager::GetDeviceFromInterfaceName(
+ const string& interface_name) {
+ for (const auto& device : devices_) {
+ if (device->InterfaceExists(interface_name)) {
+ return device;
+ }
+ }
+ return nullptr;
+}
+
+void Manager::RegisterDevice(scoped_refptr<Device> device) {
+ LOG(INFO) << "Manager::RegisterDevice: registering device "
+ << device->GetDeviceName();
+ // Register device DBbus interfaces.
+ scoped_refptr<AsyncEventSequencer> sequencer(new AsyncEventSequencer());
+ device->RegisterAsync(dbus_object_->GetObjectManager().get(),
+ bus_,
+ sequencer.get(),
+ device_identifier_++);
+ sequencer->OnAllTasksCompletedCall({
+ base::Bind(&Manager::OnDeviceRegistered,
+ base::Unretained(this),
+ device)
+ });
+}
+
+void Manager::ClaimInterface(const string& interface_name) {
+ shill_proxy_.ClaimInterface(interface_name);
+}
+
+void Manager::ReleaseInterface(const string& interface_name) {
+ shill_proxy_.ReleaseInterface(interface_name);
+}
+
+void Manager::RequestDHCPPortAccess(const string& interface) {
+ firewall_manager_.RequestDHCPPortAccess(interface);
+}
+
+void Manager::ReleaseDHCPPortAccess(const string& interface) {
+ firewall_manager_.ReleaseDHCPPortAccess(interface);
+}
+
+void Manager::OnServiceRegistered(
+ std::unique_ptr<DBusMethodResponse<dbus::ObjectPath>> response,
+ std::unique_ptr<Service> service,
+ bool success) {
+ LOG(INFO) << "ServiceRegistered";
+ // Success should always be true since we've said that failures are fatal.
+ CHECK(success) << "Init of one or more objects has failed.";
+
+ // Remove this service if the owner doesn't exist anymore. It is theoretically
+ // possible to have the owner disappear before the AP service complete its
+ // registration with DBus.
+ if (service_watchers_.find(service->identifier()) ==
+ service_watchers_.end()) {
+ LOG(INFO) << "Service " << service->identifier()
+ << ": owner doesn't exist anymore";
+ service.reset();
+ return;
+ }
+
+ // Add service to the service list and return the service dbus path for the
+ // CreateService call.
+ dbus::ObjectPath service_path = service->dbus_path();
+ services_.push_back(std::move(service));
+ response->Return(service_path);
+}
+
+void Manager::OnDeviceRegistered(scoped_refptr<Device> device, bool success) {
+ // Success should always be true since we've said that failures are fatal.
+ CHECK(success) << "Init of one or more objects has failed.";
+
+ devices_.push_back(device);
+ // TODO(zqiu): Property update for available devices.
+}
+
+void Manager::OnAPServiceOwnerDisappeared(int service_identifier) {
+ LOG(INFO) << "Owner for service " << service_identifier << " disappeared";
+ // Remove service watcher.
+ auto watcher = service_watchers_.find(service_identifier);
+ CHECK(watcher != service_watchers_.end())
+ << "Owner disappeared without watcher setup";
+ service_watchers_.erase(watcher);
+
+ // Remove the service.
+ for (auto it = services_.begin(); it != services_.end(); ++it) {
+ if ((*it)->identifier() == service_identifier) {
+ services_.erase(it);
+ return;
+ }
+ }
+ LOG(INFO) << "Owner for service " << service_identifier
+ << " disappeared before it is registered";
+}
+
+} // namespace apmanager
diff --git a/manager.h b/manager.h
new file mode 100644
index 0000000..9c5fb93
--- /dev/null
+++ b/manager.h
@@ -0,0 +1,111 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef APMANAGER_MANAGER_H_
+#define APMANAGER_MANAGER_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+#include <chromeos/dbus/dbus_service_watcher.h>
+
+#include "apmanager/dbus_adaptors/org.chromium.apmanager.Manager.h"
+
+#include "apmanager/device_info.h"
+#include "apmanager/firewall_manager.h"
+#include "apmanager/service.h"
+#include "apmanager/shill_proxy.h"
+
+namespace apmanager {
+
+class Manager : public org::chromium::apmanager::ManagerAdaptor,
+ public org::chromium::apmanager::ManagerInterface {
+ public:
+ template<typename T>
+ using DBusMethodResponse = chromeos::dbus_utils::DBusMethodResponse<T>;
+
+ Manager();
+ virtual ~Manager();
+
+ // Implementation of ManagerInterface.
+ // Handles calls to org.chromium.apmanager.Manager.CreateService().
+ // This is an asynchronous call, response is invoked when Service and Config
+ // dbus objects complete the DBus service registration.
+ virtual void CreateService(
+ std::unique_ptr<DBusMethodResponse<dbus::ObjectPath>> response,
+ dbus::Message* message);
+ // Handles calls to org.chromium.apmanager.Manager.RemoveService().
+ virtual bool RemoveService(chromeos::ErrorPtr* error,
+ dbus::Message* message,
+ const dbus::ObjectPath& in_service);
+
+ // Register DBus object.
+ void RegisterAsync(
+ chromeos::dbus_utils::ExportedObjectManager* object_manager,
+ const scoped_refptr<dbus::Bus>& bus,
+ chromeos::dbus_utils::AsyncEventSequencer* sequencer);
+
+ virtual void Start();
+ virtual void Stop();
+
+ virtual void RegisterDevice(scoped_refptr<Device> device);
+
+ // Return an unuse device with AP interface mode support.
+ virtual scoped_refptr<Device> GetAvailableDevice();
+
+ // Return the device that's associated with the given interface
+ // |interface_name|.
+ virtual scoped_refptr<Device> GetDeviceFromInterfaceName(
+ const std::string& interface_name);
+
+ // Claim the given interface |interface_name| from shill.
+ virtual void ClaimInterface(const std::string& interface_name);
+ // Release the given interface |interface_name| to shill.
+ virtual void ReleaseInterface(const std::string& interface_name);
+
+ // Request/release access to DHCP port for the specified interface.
+ virtual void RequestDHCPPortAccess(const std::string& interface);
+ virtual void ReleaseDHCPPortAccess(const std::string& interface);
+
+ private:
+ friend class ManagerTest;
+
+ // A callback that will be called when the Service/Config D-Bus
+ // objects/interfaces are exported successfully and ready to be used.
+ void OnServiceRegistered(
+ std::unique_ptr<DBusMethodResponse<dbus::ObjectPath>> response,
+ std::unique_ptr<Service> service,
+ bool success);
+
+ // A callback that will be called when a Device D-Bus object/interface is
+ // exported successfully and ready to be used.
+ void OnDeviceRegistered(scoped_refptr<Device> device, bool success);
+
+ // This is invoked when the owner of an AP service disappeared.
+ void OnAPServiceOwnerDisappeared(int service_identifier);
+
+ int service_identifier_;
+ int device_identifier_;
+ std::unique_ptr<chromeos::dbus_utils::DBusObject> dbus_object_;
+ scoped_refptr<dbus::Bus> bus_;
+ std::vector<std::unique_ptr<Service>> services_;
+ std::vector<scoped_refptr<Device>> devices_;
+ // DBus service watchers for the owner of AP services.
+ using DBusServiceWatcher = chromeos::dbus_utils::DBusServiceWatcher;
+ std::map<int, std::unique_ptr<DBusServiceWatcher>> service_watchers_;
+ DeviceInfo device_info_;
+
+ // Proxy to shill DBus services.
+ ShillProxy shill_proxy_;
+ // Proxy to DBus service for managing firewall rules.
+ FirewallManager firewall_manager_;
+
+ DISALLOW_COPY_AND_ASSIGN(Manager);
+};
+
+} // namespace apmanager
+
+#endif // APMANAGER_MANAGER_H_
diff --git a/manager_unittest.cc b/manager_unittest.cc
new file mode 100644
index 0000000..5d7e5a4
--- /dev/null
+++ b/manager_unittest.cc
@@ -0,0 +1,87 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "apmanager/manager.h"
+
+#include <gtest/gtest.h>
+
+#include "apmanager/mock_device.h"
+
+using ::testing::_;
+using ::testing::Return;
+
+namespace apmanager {
+
+class ManagerTest : public testing::Test {
+ public:
+ ManagerTest() : manager_() {}
+
+ void RegisterDevice(scoped_refptr<Device> device) {
+ manager_.devices_.push_back(device);
+ }
+
+ protected:
+ Manager manager_;
+};
+
+TEST_F(ManagerTest, GetAvailableDevice) {
+ // Register a device without AP support (no preferred AP interface).
+ scoped_refptr<MockDevice> device0 = new MockDevice();
+ RegisterDevice(device0);
+
+ // No available device for AP operation.
+ EXPECT_EQ(nullptr, manager_.GetAvailableDevice());
+
+ // Add AP support to the device.
+ const char kTestInterface0[] = "test-interface0";
+ device0->SetPreferredApInterface(kTestInterface0);
+ EXPECT_EQ(device0, manager_.GetAvailableDevice());
+
+ // Register another device with AP support.
+ const char kTestInterface1[] = "test-interface1";
+ scoped_refptr<MockDevice> device1 = new MockDevice();
+ device1->SetPreferredApInterface(kTestInterface1);
+ RegisterDevice(device1);
+
+ // Both devices are idle by default, should return the first added device.
+ EXPECT_EQ(device0, manager_.GetAvailableDevice());
+
+ // Set first one to be in used, should return the non-used device.
+ device0->SetInUsed(true);
+ EXPECT_EQ(device1, manager_.GetAvailableDevice());
+
+ // Both devices are in used, should return a nullptr.
+ device1->SetInUsed(true);
+ EXPECT_EQ(nullptr, manager_.GetAvailableDevice());
+}
+
+TEST_F(ManagerTest, GetDeviceFromInterfaceName) {
+ // Register two devices
+ scoped_refptr<MockDevice> device0 = new MockDevice();
+ scoped_refptr<MockDevice> device1 = new MockDevice();
+ RegisterDevice(device0);
+ RegisterDevice(device1);
+
+ const char kTestInterface0[] = "test-interface0";
+ const char kTestInterface1[] = "test-interface1";
+
+ // interface0 belongs to device0.
+ EXPECT_CALL(*device0.get(), InterfaceExists(kTestInterface0))
+ .WillOnce(Return(true));
+ EXPECT_EQ(device0, manager_.GetDeviceFromInterfaceName(kTestInterface0));
+
+ // interface1 belongs to device1.
+ EXPECT_CALL(*device0.get(), InterfaceExists(_))
+ .WillRepeatedly(Return(false));
+ EXPECT_CALL(*device1.get(), InterfaceExists(kTestInterface1))
+ .WillOnce(Return(true));
+ EXPECT_EQ(device1, manager_.GetDeviceFromInterfaceName(kTestInterface1));
+
+ // "random" interface is not found.
+ EXPECT_CALL(*device1.get(), InterfaceExists(_))
+ .WillRepeatedly(Return(false));
+ EXPECT_EQ(nullptr, manager_.GetDeviceFromInterfaceName("random"));
+}
+
+} // namespace apmanager
diff --git a/mock_config.cc b/mock_config.cc
new file mode 100644
index 0000000..d1f27ca
--- /dev/null
+++ b/mock_config.cc
@@ -0,0 +1,13 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "apmanager/mock_config.h"
+
+namespace apmanager {
+
+MockConfig::MockConfig() : Config(nullptr, std::string()) {}
+
+MockConfig::~MockConfig() {}
+
+} // namespace apmanager
diff --git a/mock_config.h b/mock_config.h
new file mode 100644
index 0000000..4f1abc9
--- /dev/null
+++ b/mock_config.h
@@ -0,0 +1,34 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef APMANAGER_MOCK_CONFIG_H_
+#define APMANAGER_MOCK_CONFIG_H_
+
+#include <string>
+
+#include <base/macros.h>
+#include <gmock/gmock.h>
+
+#include "apmanager/config.h"
+
+namespace apmanager {
+
+class MockConfig : public Config {
+ public:
+ MockConfig();
+ ~MockConfig() override;
+
+ MOCK_METHOD2(GenerateConfigFile,
+ bool(chromeos::ErrorPtr *error,
+ std::string* config_str));
+ MOCK_METHOD0(ClaimDevice, bool());
+ MOCK_METHOD0(ReleaseDevice, bool());
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockConfig);
+};
+
+} // namespace apmanager
+
+#endif // APMANAGER_MOCK_CONFIG_H_
diff --git a/mock_device.cc b/mock_device.cc
new file mode 100644
index 0000000..770c2bd
--- /dev/null
+++ b/mock_device.cc
@@ -0,0 +1,13 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "apmanager/mock_device.h"
+
+namespace apmanager {
+
+MockDevice::MockDevice() : Device(nullptr, "") {}
+
+MockDevice::~MockDevice() {}
+
+} // namespace apmanager
diff --git a/mock_device.h b/mock_device.h
new file mode 100644
index 0000000..31f2944
--- /dev/null
+++ b/mock_device.h
@@ -0,0 +1,39 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef APMANAGER_MOCK_DEVICE_H_
+#define APMANAGER_MOCK_DEVICE_H_
+
+#include <string>
+
+#include <base/macros.h>
+#include <gmock/gmock.h>
+
+#include "apmanager/device.h"
+
+namespace apmanager {
+
+class MockDevice : public Device {
+ public:
+ MockDevice();
+ ~MockDevice() override;
+
+ MOCK_METHOD1(RegisterInterface,
+ void(const WiFiInterface& interface));
+ MOCK_METHOD1(DeregisterInterface,
+ void(const WiFiInterface& interface));
+ MOCK_METHOD1(ParseWiphyCapability,
+ void(const shill::Nl80211Message& msg));
+ MOCK_METHOD1(ClaimDevice, bool(bool full_control));
+ MOCK_METHOD0(ReleaseDevice, bool());
+ MOCK_METHOD1(InterfaceExists, bool(const std::string& interface_name));
+ MOCK_METHOD2(GetHTCapability, bool(uint16_t channel, std::string* ht_capab));
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockDevice);
+};
+
+} // namespace apmanager
+
+#endif // APMANAGER_MOCK_DEVICE_H_
diff --git a/mock_dhcp_server.cc b/mock_dhcp_server.cc
new file mode 100644
index 0000000..de31eae
--- /dev/null
+++ b/mock_dhcp_server.cc
@@ -0,0 +1,13 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "apmanager/mock_dhcp_server.h"
+
+namespace apmanager {
+
+MockDHCPServer::MockDHCPServer() : DHCPServer(0, "") {}
+
+MockDHCPServer::~MockDHCPServer() {}
+
+} // namespace apmanager
diff --git a/mock_dhcp_server.h b/mock_dhcp_server.h
new file mode 100644
index 0000000..df68b81
--- /dev/null
+++ b/mock_dhcp_server.h
@@ -0,0 +1,28 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef APMANAGER_MOCK_DHCP_SERVER_H_
+#define APMANAGER_MOCK_DHCP_SERVER_H_
+
+#include <base/macros.h>
+#include <gmock/gmock.h>
+
+#include "apmanager/dhcp_server.h"
+
+namespace apmanager {
+
+class MockDHCPServer : public DHCPServer {
+ public:
+ MockDHCPServer();
+ ~MockDHCPServer() override;
+
+ MOCK_METHOD0(Start, bool());
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockDHCPServer);
+};
+
+} // namespace apmanager
+
+#endif // APMANAGER_MOCK_DHCP_SERVER_H_
diff --git a/mock_dhcp_server_factory.cc b/mock_dhcp_server_factory.cc
new file mode 100644
index 0000000..b5eb7a4
--- /dev/null
+++ b/mock_dhcp_server_factory.cc
@@ -0,0 +1,21 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "apmanager/mock_dhcp_server_factory.h"
+
+namespace apmanager {
+
+namespace {
+base::LazyInstance<MockDHCPServerFactory> g_mock_dhcp_server_factory
+ = LAZY_INSTANCE_INITIALIZER;
+} // namespace
+
+MockDHCPServerFactory::MockDHCPServerFactory() {}
+MockDHCPServerFactory::~MockDHCPServerFactory() {}
+
+MockDHCPServerFactory* MockDHCPServerFactory::GetInstance() {
+ return g_mock_dhcp_server_factory.Pointer();
+}
+
+} // namespace apmanager
diff --git a/mock_dhcp_server_factory.h b/mock_dhcp_server_factory.h
new file mode 100644
index 0000000..c20fd59
--- /dev/null
+++ b/mock_dhcp_server_factory.h
@@ -0,0 +1,39 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef APMANAGER_MOCK_DHCP_SERVER_FACTORY_H_
+#define APMANAGER_MOCK_DHCP_SERVER_FACTORY_H_
+
+#include <string>
+
+#include <base/lazy_instance.h>
+#include <gmock/gmock.h>
+
+#include "apmanager/dhcp_server_factory.h"
+
+namespace apmanager {
+
+class MockDHCPServerFactory : public DHCPServerFactory {
+ public:
+ ~MockDHCPServerFactory() override;
+
+ // This is a singleton. Use MockDHCPServerFactory::GetInstance()->Foo().
+ static MockDHCPServerFactory* GetInstance();
+
+ MOCK_METHOD2(CreateDHCPServer,
+ DHCPServer*(uint16_t server_address_index,
+ const std::string& interface_name));
+
+ protected:
+ MockDHCPServerFactory();
+
+ private:
+ friend struct base::DefaultLazyInstanceTraits<MockDHCPServerFactory>;
+
+ DISALLOW_COPY_AND_ASSIGN(MockDHCPServerFactory);
+};
+
+} // namespace apmanager
+
+#endif // APMANAGER_MOCK_DHCP_SERVER_FACTORY_H_
diff --git a/mock_event_dispatcher.cc b/mock_event_dispatcher.cc
new file mode 100644
index 0000000..92e739b
--- /dev/null
+++ b/mock_event_dispatcher.cc
@@ -0,0 +1,21 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "apmanager/mock_event_dispatcher.h"
+
+namespace apmanager {
+
+namespace {
+base::LazyInstance<MockEventDispatcher> g_mock_event_dispatcher
+ = LAZY_INSTANCE_INITIALIZER;
+} // namespace
+
+MockEventDispatcher::MockEventDispatcher() {}
+MockEventDispatcher::~MockEventDispatcher() {}
+
+MockEventDispatcher* MockEventDispatcher::GetInstance() {
+ return g_mock_event_dispatcher.Pointer();
+}
+
+} // namespace apmanager
diff --git a/mock_event_dispatcher.h b/mock_event_dispatcher.h
new file mode 100644
index 0000000..aa2ba32
--- /dev/null
+++ b/mock_event_dispatcher.h
@@ -0,0 +1,37 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef APMANAGER_MOCK_EVENT_DISPATCHER_H_
+#define APMANAGER_MOCK_EVENT_DISPATCHER_H_
+
+#include <base/lazy_instance.h>
+#include <gmock/gmock.h>
+
+#include "apmanager/event_dispatcher.h"
+
+namespace apmanager {
+
+class MockEventDispatcher : public EventDispatcher {
+ public:
+ ~MockEventDispatcher() override;
+
+ // This is a singleton. Use MockEventDispatcher::GetInstance()->Foo().
+ static MockEventDispatcher* GetInstance();
+
+ MOCK_METHOD1(PostTask, bool(const base::Closure& task));
+ MOCK_METHOD2(PostDelayedTask, bool(const base::Closure& task,
+ int64_t delay_ms));
+
+ protected:
+ MockEventDispatcher();
+
+ private:
+ friend struct base::DefaultLazyInstanceTraits<MockEventDispatcher>;
+
+ DISALLOW_COPY_AND_ASSIGN(MockEventDispatcher);
+};
+
+} // namespace apmanager
+
+#endif // APMANAGER_MOCK_EVENT_DISPATCHER_H_
diff --git a/mock_file_writer.cc b/mock_file_writer.cc
new file mode 100644
index 0000000..168cae3
--- /dev/null
+++ b/mock_file_writer.cc
@@ -0,0 +1,21 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "apmanager/mock_file_writer.h"
+
+namespace apmanager {
+
+namespace {
+base::LazyInstance<MockFileWriter> g_mock_file_writer
+ = LAZY_INSTANCE_INITIALIZER;
+} // namespace
+
+MockFileWriter::MockFileWriter() {}
+MockFileWriter::~MockFileWriter() {}
+
+MockFileWriter* MockFileWriter::GetInstance() {
+ return g_mock_file_writer.Pointer();
+}
+
+} // namespace apmanager
diff --git a/mock_file_writer.h b/mock_file_writer.h
new file mode 100644
index 0000000..f5a7f31
--- /dev/null
+++ b/mock_file_writer.h
@@ -0,0 +1,38 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef APMANAGER_MOCK_FILE_WRITER_H_
+#define APMANAGER_MOCK_FILE_WRITER_H_
+
+#include <string>
+
+#include <base/lazy_instance.h>
+#include <gmock/gmock.h>
+
+#include "apmanager/file_writer.h"
+
+namespace apmanager {
+
+class MockFileWriter : public FileWriter {
+ public:
+ ~MockFileWriter() override;
+
+ // This is a singleton. Use MockFileWriter::GetInstance()->Foo().
+ static MockFileWriter* GetInstance();
+
+ MOCK_METHOD2(Write, bool(const std::string& file_name,
+ const std::string& content));
+
+ protected:
+ MockFileWriter();
+
+ private:
+ friend struct base::DefaultLazyInstanceTraits<MockFileWriter>;
+
+ DISALLOW_COPY_AND_ASSIGN(MockFileWriter);
+};
+
+} // namespace apmanager
+
+#endif // APMANAGER_MOCK_FILE_WRITER_H_
diff --git a/mock_hostapd_monitor.cc b/mock_hostapd_monitor.cc
new file mode 100644
index 0000000..5b3446f
--- /dev/null
+++ b/mock_hostapd_monitor.cc
@@ -0,0 +1,13 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "apmanager/mock_hostapd_monitor.h"
+
+namespace apmanager {
+
+MockHostapdMonitor::MockHostapdMonitor()
+ : HostapdMonitor(EventCallback(), "", "") {}
+MockHostapdMonitor::~MockHostapdMonitor() {}
+
+} // namespace apmanager
diff --git a/mock_hostapd_monitor.h b/mock_hostapd_monitor.h
new file mode 100644
index 0000000..1f4859c
--- /dev/null
+++ b/mock_hostapd_monitor.h
@@ -0,0 +1,27 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef APMANAGER_MOCK_HOSTAPD_MONITOR_H_
+#define APMANAGER_MOCK_HOSTAPD_MONITOR_H_
+
+#include <gmock/gmock.h>
+
+#include "apmanager/hostapd_monitor.h"
+
+namespace apmanager {
+
+class MockHostapdMonitor : public HostapdMonitor {
+ public:
+ MockHostapdMonitor();
+ ~MockHostapdMonitor() override;
+
+ MOCK_METHOD0(Start, void());
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockHostapdMonitor);
+};
+
+} // namespace apmanager
+
+#endif // APMANAGER_MOCK_HOSTAPD_MONITOR_H_
diff --git a/mock_manager.cc b/mock_manager.cc
new file mode 100644
index 0000000..de61d73
--- /dev/null
+++ b/mock_manager.cc
@@ -0,0 +1,13 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "apmanager/mock_manager.h"
+
+namespace apmanager {
+
+MockManager::MockManager() : Manager() {}
+
+MockManager::~MockManager() {}
+
+} // namespace apmanager
diff --git a/mock_manager.h b/mock_manager.h
new file mode 100644
index 0000000..dbbc731
--- /dev/null
+++ b/mock_manager.h
@@ -0,0 +1,39 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef APMANAGER_MOCK_MANAGER_H_
+#define APMANAGER_MOCK_MANAGER_H_
+
+#include <string>
+
+#include <base/macros.h>
+#include <gmock/gmock.h>
+
+#include "apmanager/manager.h"
+
+namespace apmanager {
+
+class MockManager : public Manager {
+ public:
+ MockManager();
+ ~MockManager() override;
+
+ MOCK_METHOD0(Start, void());
+ MOCK_METHOD0(Stop, void());
+ MOCK_METHOD1(RegisterDevice, void(scoped_refptr<Device> device));
+ MOCK_METHOD0(GetAvailableDevice, scoped_refptr<Device>());
+ MOCK_METHOD1(GetDeviceFromInterfaceName,
+ scoped_refptr<Device>(const std::string& interface_name));
+ MOCK_METHOD1(ClaimInterface, void(const std::string& interface_name));
+ MOCK_METHOD1(ReleaseInterface, void(const std::string& interface_name));
+ MOCK_METHOD1(RequestDHCPPortAccess, void(const std::string& interface));
+ MOCK_METHOD1(ReleaseDHCPPortAccess, void(const std::string& interface));
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockManager);
+};
+
+} // namespace apmanager
+
+#endif // APMANAGER_MOCK_MANAGER_H_
diff --git a/mock_process_factory.cc b/mock_process_factory.cc
new file mode 100644
index 0000000..587c7cd
--- /dev/null
+++ b/mock_process_factory.cc
@@ -0,0 +1,21 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "apmanager/mock_process_factory.h"
+
+namespace apmanager {
+
+namespace {
+base::LazyInstance<MockProcessFactory> g_mock_process_factory
+ = LAZY_INSTANCE_INITIALIZER;
+} // namespace
+
+MockProcessFactory::MockProcessFactory() {}
+MockProcessFactory::~MockProcessFactory() {}
+
+MockProcessFactory* MockProcessFactory::GetInstance() {
+ return g_mock_process_factory.Pointer();
+}
+
+} // namespace apmanager
diff --git a/mock_process_factory.h b/mock_process_factory.h
new file mode 100644
index 0000000..4a598ce
--- /dev/null
+++ b/mock_process_factory.h
@@ -0,0 +1,35 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef APMANAGER_MOCK_PROCESS_FACTORY_H_
+#define APMANAGER_MOCK_PROCESS_FACTORY_H_
+
+#include <base/lazy_instance.h>
+#include <gmock/gmock.h>
+
+#include "apmanager/process_factory.h"
+
+namespace apmanager {
+
+class MockProcessFactory : public ProcessFactory {
+ public:
+ ~MockProcessFactory() override;
+
+ // This is a singleton. Use MockDHCPServerFactory::GetInstance()->Foo().
+ static MockProcessFactory* GetInstance();
+
+ MOCK_METHOD0(CreateProcess, chromeos::Process*());
+
+ protected:
+ MockProcessFactory();
+
+ private:
+ friend struct base::DefaultLazyInstanceTraits<MockProcessFactory>;
+
+ DISALLOW_COPY_AND_ASSIGN(MockProcessFactory);
+};
+
+} // namespace apmanager
+
+#endif // APMANAGER_MOCK_PROCESS_FACTORY_H_
diff --git a/mock_service.cc b/mock_service.cc
new file mode 100644
index 0000000..da17b13
--- /dev/null
+++ b/mock_service.cc
@@ -0,0 +1,13 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "apmanager/mock_service.h"
+
+namespace apmanager {
+
+MockService::MockService() : Service(nullptr, 0) {}
+
+MockService::~MockService() {}
+
+} // namespace apmanager
diff --git a/mock_service.h b/mock_service.h
new file mode 100644
index 0000000..13087d5
--- /dev/null
+++ b/mock_service.h
@@ -0,0 +1,29 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef APMANAGER_MOCK_SERVICE_H_
+#define APMANAGER_MOCK_SERVICE_H_
+
+#include <base/macros.h>
+#include <gmock/gmock.h>
+
+#include "apmanager/service.h"
+
+namespace apmanager {
+
+class MockService : public Service {
+ public:
+ MockService();
+ ~MockService() override;
+
+ MOCK_METHOD1(Start, bool(chromeos::ErrorPtr *error));
+ MOCK_METHOD1(Stop, bool(chromeos::ErrorPtr *error));
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockService);
+};
+
+} // namespace apmanager
+
+#endif // APMANAGER_MOCK_SERVICE_H_
diff --git a/process_factory.cc b/process_factory.cc
new file mode 100644
index 0000000..f7456a3
--- /dev/null
+++ b/process_factory.cc
@@ -0,0 +1,27 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "apmanager/process_factory.h"
+
+namespace apmanager {
+
+namespace {
+
+base::LazyInstance<ProcessFactory> g_process_factory
+ = LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+ProcessFactory::ProcessFactory() {}
+ProcessFactory::~ProcessFactory() {}
+
+ProcessFactory* ProcessFactory::GetInstance() {
+ return g_process_factory.Pointer();
+}
+
+chromeos::Process* ProcessFactory::CreateProcess() {
+ return new chromeos::ProcessImpl();
+}
+
+} // namespace apmanager
diff --git a/process_factory.h b/process_factory.h
new file mode 100644
index 0000000..967dc67
--- /dev/null
+++ b/process_factory.h
@@ -0,0 +1,36 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef APMANAGER_PROCESS_FACTORY_H_
+#define APMANAGER_PROCESS_FACTORY_H_
+
+#include <string>
+
+#include <base/lazy_instance.h>
+
+#include <chromeos/process.h>
+
+namespace apmanager {
+
+class ProcessFactory {
+ public:
+ virtual ~ProcessFactory();
+
+ // This is a singleton. Use ProcessFactory::GetInstance()->Foo().
+ static ProcessFactory* GetInstance();
+
+ virtual chromeos::Process* CreateProcess();
+
+ protected:
+ ProcessFactory();
+
+ private:
+ friend struct base::DefaultLazyInstanceTraits<ProcessFactory>;
+
+ DISALLOW_COPY_AND_ASSIGN(ProcessFactory);
+};
+
+} // namespace apmanager
+
+#endif // APMANAGER_PROCESS_FACTORY_H_
diff --git a/service.cc b/service.cc
new file mode 100644
index 0000000..b5a1f26
--- /dev/null
+++ b/service.cc
@@ -0,0 +1,224 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "apmanager/service.h"
+
+#include <signal.h>
+
+#include <base/strings/stringprintf.h>
+#include <chromeos/dbus/service_constants.h>
+#include <chromeos/errors/error.h>
+
+#include "apmanager/manager.h"
+
+using chromeos::dbus_utils::AsyncEventSequencer;
+using chromeos::dbus_utils::ExportedObjectManager;
+using org::chromium::apmanager::ManagerAdaptor;
+using std::string;
+
+namespace apmanager {
+
+// static.
+const char Service::kHostapdPath[] = "/usr/sbin/hostapd";
+const char Service::kHostapdConfigPathFormat[] =
+ "/var/run/apmanager/hostapd/hostapd-%d.conf";
+const char Service::kHostapdControlInterfacePath[] =
+ "/var/run/apmanager/hostapd/ctrl_iface";
+const int Service::kTerminationTimeoutSeconds = 2;
+
+// static. Service state definitions.
+const char Service::kStateIdle[] = "Idle";
+const char Service::kStateStarting[] = "Starting";
+const char Service::kStateStarted[] = "Started";
+const char Service::kStateFailed[] = "Failed";
+
+Service::Service(Manager* manager, int service_identifier)
+ : org::chromium::apmanager::ServiceAdaptor(this),
+ manager_(manager),
+ identifier_(service_identifier),
+ service_path_(
+ base::StringPrintf("%s/services/%d",
+ ManagerAdaptor::GetObjectPath().value().c_str(),
+ service_identifier)),
+ dbus_path_(dbus::ObjectPath(service_path_)),
+ config_(new Config(manager, service_path_)),
+ dhcp_server_factory_(DHCPServerFactory::GetInstance()),
+ file_writer_(FileWriter::GetInstance()),
+ process_factory_(ProcessFactory::GetInstance()) {
+ SetConfig(config_->dbus_path());
+ SetState(kStateIdle);
+ // TODO(zqiu): come up with better server address management. This is good
+ // enough for now.
+ config_->SetServerAddressIndex(identifier_ & 0xFF);
+}
+
+Service::~Service() {
+ // Stop hostapd process if still running.
+ if (IsHostapdRunning()) {
+ ReleaseResources();
+ }
+}
+
+void Service::RegisterAsync(ExportedObjectManager* object_manager,
+ const scoped_refptr<dbus::Bus>& bus,
+ AsyncEventSequencer* sequencer) {
+ CHECK(!dbus_object_) << "Already registered";
+ dbus_object_.reset(
+ new chromeos::dbus_utils::DBusObject(
+ object_manager,
+ bus,
+ dbus_path_));
+ RegisterWithDBusObject(dbus_object_.get());
+ dbus_object_->RegisterAsync(
+ sequencer->GetHandler("Service.RegisterAsync() failed.", true));
+
+ // Register Config DBus object.
+ config_->RegisterAsync(object_manager, bus, sequencer);
+}
+
+bool Service::Start(chromeos::ErrorPtr* error) {
+ if (IsHostapdRunning()) {
+ chromeos::Error::AddTo(
+ error, FROM_HERE, chromeos::errors::dbus::kDomain, kServiceError,
+ "Service already running");
+ return false;
+ }
+
+ // Setup hostapd control interface path.
+ config_->set_control_interface(kHostapdControlInterfacePath);
+
+ // Generate hostapd configuration content.
+ string config_str;
+ if (!config_->GenerateConfigFile(error, &config_str)) {
+ chromeos::Error::AddTo(
+ error, FROM_HERE, chromeos::errors::dbus::kDomain, kServiceError,
+ "Failed to generate config file");
+ return false;
+ }
+
+ // Write configuration to a file.
+ string config_file_name = base::StringPrintf(kHostapdConfigPathFormat,
+ identifier_);
+ if (!file_writer_->Write(config_file_name, config_str)) {
+ chromeos::Error::AddTo(
+ error, FROM_HERE, chromeos::errors::dbus::kDomain, kServiceError,
+ "Failed to write configuration to a file");
+ return false;
+ }
+
+ // Claim the device needed for this ap service.
+ if (!config_->ClaimDevice()) {
+ chromeos::Error::AddTo(
+ error, FROM_HERE, chromeos::errors::dbus::kDomain, kServiceError,
+ "Failed to claim the device for this service");
+ return false;
+ }
+
+ // Start hostapd process.
+ if (!StartHostapdProcess(config_file_name)) {
+ chromeos::Error::AddTo(
+ error, FROM_HERE, chromeos::errors::dbus::kDomain, kServiceError,
+ "Failed to start hostapd");
+ // Release the device claimed for this service.
+ config_->ReleaseDevice();
+ return false;
+ }
+
+ // Start DHCP server if in server mode.
+ if (config_->GetOperationMode() == kOperationModeServer) {
+ dhcp_server_.reset(
+ dhcp_server_factory_->CreateDHCPServer(config_->GetServerAddressIndex(),
+ config_->selected_interface()));
+ if (!dhcp_server_->Start()) {
+ chromeos::Error::AddTo(
+ error, FROM_HERE, chromeos::errors::dbus::kDomain, kServiceError,
+ "Failed to start DHCP server");
+ ReleaseResources();
+ return false;
+ }
+ manager_->RequestDHCPPortAccess(config_->selected_interface());
+ }
+
+ // Start monitoring hostapd.
+ if (!hostapd_monitor_) {
+ hostapd_monitor_.reset(
+ new HostapdMonitor(base::Bind(&Service::HostapdEventCallback,
+ base::Unretained(this)),
+ config_->control_interface(),
+ config_->selected_interface()));
+ }
+ hostapd_monitor_->Start();
+
+ // Update service state.
+ SetState(kStateStarting);
+
+ return true;
+}
+
+bool Service::Stop(chromeos::ErrorPtr* error) {
+ if (!IsHostapdRunning()) {
+ chromeos::Error::AddTo(
+ error, FROM_HERE, chromeos::errors::dbus::kDomain, kServiceError,
+ "Service is not currently running");
+ return false;
+ }
+
+ ReleaseResources();
+ SetState(kStateIdle);
+ return true;
+}
+
+bool Service::IsHostapdRunning() {
+ return hostapd_process_ && hostapd_process_->pid() != 0 &&
+ chromeos::Process::ProcessExists(hostapd_process_->pid());
+}
+
+bool Service::StartHostapdProcess(const string& config_file_path) {
+ hostapd_process_.reset(process_factory_->CreateProcess());
+ hostapd_process_->AddArg(kHostapdPath);
+ hostapd_process_->AddArg(config_file_path);
+ if (!hostapd_process_->Start()) {
+ hostapd_process_.reset();
+ return false;
+ }
+ return true;
+}
+
+void Service::StopHostapdProcess() {
+ if (!hostapd_process_->Kill(SIGTERM, kTerminationTimeoutSeconds)) {
+ hostapd_process_->Kill(SIGKILL, kTerminationTimeoutSeconds);
+ }
+ hostapd_process_.reset();
+}
+
+void Service::ReleaseResources() {
+ hostapd_monitor_.reset();
+ StopHostapdProcess();
+ dhcp_server_.reset();
+ config_->ReleaseDevice();
+ manager_->ReleaseDHCPPortAccess(config_->selected_interface());
+}
+
+void Service::HostapdEventCallback(HostapdMonitor::Event event,
+ const std::string& data) {
+ switch (event) {
+ case HostapdMonitor::kHostapdFailed:
+ SetState(kStateFailed);
+ break;
+ case HostapdMonitor::kHostapdStarted:
+ SetState(kStateStarted);
+ break;
+ case HostapdMonitor::kStationConnected:
+ LOG(INFO) << "Station connected: " << data;
+ break;
+ case HostapdMonitor::kStationDisconnected:
+ LOG(INFO) << "Station disconnected: " << data;
+ break;
+ default:
+ LOG(ERROR) << "Unknown event: " << event;
+ break;
+ }
+}
+
+} // namespace apmanager
diff --git a/service.h b/service.h
new file mode 100644
index 0000000..edd3b24
--- /dev/null
+++ b/service.h
@@ -0,0 +1,91 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef APMANAGER_SERVICE_H_
+#define APMANAGER_SERVICE_H_
+
+#include <string>
+
+#include <base/macros.h>
+#include <chromeos/process.h>
+
+#include "apmanager/config.h"
+#include "apmanager/dbus_adaptors/org.chromium.apmanager.Service.h"
+#include "apmanager/dhcp_server_factory.h"
+#include "apmanager/file_writer.h"
+#include "apmanager/hostapd_monitor.h"
+#include "apmanager/process_factory.h"
+
+namespace apmanager {
+
+class Manager;
+
+class Service : public org::chromium::apmanager::ServiceAdaptor,
+ public org::chromium::apmanager::ServiceInterface {
+ public:
+ Service(Manager* manager, int service_identifier);
+ virtual ~Service();
+
+ // Implementation of ServiceInterface.
+ virtual bool Start(chromeos::ErrorPtr* error);
+ virtual bool Stop(chromeos::ErrorPtr* error);
+
+ // Register Service DBus object.
+ void RegisterAsync(
+ chromeos::dbus_utils::ExportedObjectManager* object_manager,
+ const scoped_refptr<dbus::Bus>& bus,
+ chromeos::dbus_utils::AsyncEventSequencer* sequencer);
+
+ const dbus::ObjectPath& dbus_path() const { return dbus_path_; }
+
+ int identifier() const { return identifier_; }
+
+ private:
+ friend class ServiceTest;
+
+ static const char kHostapdPath[];
+ static const char kHostapdConfigPathFormat[];
+ static const char kHostapdControlInterfacePath[];
+ static const int kTerminationTimeoutSeconds;
+ static const char kStateIdle[];
+ static const char kStateStarting[];
+ static const char kStateStarted[];
+ static const char kStateFailed[];
+
+ // Return true if hostapd process is currently running.
+ bool IsHostapdRunning();
+
+ // Start hostapd process. Return true if process is created/started
+ // successfully, false otherwise.
+ bool StartHostapdProcess(const std::string& config_file_path);
+
+ // Stop the running hostapd process. Sending it a SIGTERM signal first, then
+ // a SIGKILL if failed to terminated with SIGTERM.
+ void StopHostapdProcess();
+
+ // Release resources allocated to this service.
+ void ReleaseResources();
+
+ void HostapdEventCallback(HostapdMonitor::Event event,
+ const std::string& data);
+
+ Manager* manager_;
+ int identifier_;
+ std::string service_path_;
+ dbus::ObjectPath dbus_path_;
+ std::unique_ptr<Config> config_;
+ std::unique_ptr<chromeos::dbus_utils::DBusObject> dbus_object_;
+ std::unique_ptr<chromeos::Process> hostapd_process_;
+ std::unique_ptr<DHCPServer> dhcp_server_;
+ DHCPServerFactory* dhcp_server_factory_;
+ FileWriter* file_writer_;
+ ProcessFactory* process_factory_;
+ std::unique_ptr<HostapdMonitor> hostapd_monitor_;
+
+ DISALLOW_COPY_AND_ASSIGN(Service);
+};
+
+} // namespace apmanager
+
+#endif // APMANAGER_SERVICE_H_
diff --git a/service_unittest.cc b/service_unittest.cc
new file mode 100644
index 0000000..5170824
--- /dev/null
+++ b/service_unittest.cc
@@ -0,0 +1,147 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "apmanager/service.h"
+
+#include <string>
+
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <chromeos/dbus/service_constants.h>
+#include <chromeos/process_mock.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "apmanager/mock_config.h"
+#include "apmanager/mock_dhcp_server.h"
+#include "apmanager/mock_dhcp_server_factory.h"
+#include "apmanager/mock_file_writer.h"
+#include "apmanager/mock_hostapd_monitor.h"
+#include "apmanager/mock_manager.h"
+#include "apmanager/mock_process_factory.h"
+
+using chromeos::ProcessMock;
+using ::testing::_;
+using ::testing::Mock;
+using ::testing::Return;
+using ::testing::SetArgPointee;
+
+namespace {
+ const int kServiceIdentifier = 1;
+ const char kHostapdConfig[] = "ssid=test\n";
+ const char kBinSleep[] = "/bin/sleep";
+ const char kHostapdConfigFilePath[] =
+ "/var/run/apmanager/hostapd/hostapd-1.conf";
+} // namespace
+
+namespace apmanager {
+
+class ServiceTest : public testing::Test {
+ public:
+ ServiceTest()
+ : dhcp_server_factory_(MockDHCPServerFactory::GetInstance()),
+ file_writer_(MockFileWriter::GetInstance()),
+ process_factory_(MockProcessFactory::GetInstance()),
+ hostapd_monitor_(new MockHostapdMonitor()),
+ service_(&manager_, kServiceIdentifier) {}
+
+ virtual void SetUp() {
+ service_.dhcp_server_factory_ = dhcp_server_factory_;
+ service_.file_writer_ = file_writer_;
+ service_.process_factory_ = process_factory_;
+ service_.hostapd_monitor_.reset(hostapd_monitor_);
+ }
+
+ void StartDummyProcess() {
+ service_.hostapd_process_.reset(new chromeos::ProcessImpl);
+ service_.hostapd_process_->AddArg(kBinSleep);
+ service_.hostapd_process_->AddArg("12345");
+ CHECK(service_.hostapd_process_->Start());
+ LOG(INFO) << "DummyProcess: " << service_.hostapd_process_->pid();
+ }
+
+ void SetConfig(Config* config) {
+ service_.config_.reset(config);
+ }
+
+ protected:
+ MockManager manager_;
+ MockDHCPServerFactory* dhcp_server_factory_;
+ MockFileWriter* file_writer_;
+ MockProcessFactory* process_factory_;
+ MockHostapdMonitor* hostapd_monitor_;
+ Service service_;
+};
+
+MATCHER_P(IsServiceErrorStartingWith, message, "") {
+ return arg != nullptr &&
+ arg->GetDomain() == chromeos::errors::dbus::kDomain &&
+ arg->GetCode() == kServiceError &&
+ base::EndsWith(arg->GetMessage(), message, false);
+}
+
+TEST_F(ServiceTest, StartWhenServiceAlreadyRunning) {
+ StartDummyProcess();
+
+ chromeos::ErrorPtr error;
+ EXPECT_FALSE(service_.Start(&error));
+ EXPECT_THAT(error, IsServiceErrorStartingWith("Service already running"));
+}
+
+TEST_F(ServiceTest, StartWhenConfigFileFailed) {
+ MockConfig* config = new MockConfig();
+ SetConfig(config);
+
+ chromeos::ErrorPtr error;
+ EXPECT_CALL(*config, GenerateConfigFile(_, _)).WillOnce(Return(false));
+ EXPECT_FALSE(service_.Start(&error));
+ EXPECT_THAT(error, IsServiceErrorStartingWith(
+ "Failed to generate config file"));
+}
+
+TEST_F(ServiceTest, StartSuccess) {
+ MockConfig* config = new MockConfig();
+ SetConfig(config);
+
+ // Setup mock DHCP server.
+ MockDHCPServer* dhcp_server = new MockDHCPServer();
+ // Setup mock process.
+ ProcessMock* process = new ProcessMock();
+
+ std::string config_str(kHostapdConfig);
+ chromeos::ErrorPtr error;
+ EXPECT_CALL(*config, GenerateConfigFile(_, _)).WillOnce(
+ DoAll(SetArgPointee<1>(config_str), Return(true)));
+ EXPECT_CALL(*file_writer_, Write(kHostapdConfigFilePath, kHostapdConfig))
+ .WillOnce(Return(true));
+ EXPECT_CALL(*config, ClaimDevice()).WillOnce(Return(true));
+ EXPECT_CALL(*process_factory_, CreateProcess()).WillOnce(Return(process));
+ EXPECT_CALL(*process, Start()).WillOnce(Return(true));
+ EXPECT_CALL(*dhcp_server_factory_, CreateDHCPServer(_, _))
+ .WillOnce(Return(dhcp_server));
+ EXPECT_CALL(*dhcp_server, Start()).WillOnce(Return(true));
+ EXPECT_CALL(manager_, RequestDHCPPortAccess(_));
+ EXPECT_CALL(*hostapd_monitor_, Start());
+ EXPECT_TRUE(service_.Start(&error));
+ EXPECT_EQ(nullptr, error);
+}
+
+TEST_F(ServiceTest, StopWhenServiceNotRunning) {
+ chromeos::ErrorPtr error;
+ EXPECT_FALSE(service_.Stop(&error));
+ EXPECT_THAT(error, IsServiceErrorStartingWith(
+ "Service is not currently running"));
+}
+
+TEST_F(ServiceTest, StopSuccess) {
+ StartDummyProcess();
+
+ MockConfig* config = new MockConfig();
+ SetConfig(config);
+ chromeos::ErrorPtr error;
+ EXPECT_CALL(*config, ReleaseDevice()).Times(1);
+ EXPECT_TRUE(service_.Stop(&error));
+}
+
+} // namespace apmanager
diff --git a/shill_proxy.cc b/shill_proxy.cc
new file mode 100644
index 0000000..3a6dd4d
--- /dev/null
+++ b/shill_proxy.cc
@@ -0,0 +1,98 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "apmanager/shill_proxy.h"
+
+#include <base/bind.h>
+#include <chromeos/dbus/service_constants.h>
+#include <chromeos/errors/error.h>
+
+using std::string;
+
+namespace apmanager {
+
+// static.
+const char ShillProxy::kManagerPath[] = "/";
+
+ShillProxy::ShillProxy() {}
+
+ShillProxy::~ShillProxy() {}
+
+void ShillProxy::Init(const scoped_refptr<dbus::Bus>& bus) {
+ CHECK(!manager_proxy_) << "Already init";
+ manager_proxy_.reset(
+ new org::chromium::flimflam::ManagerProxy(
+ bus, shill::kFlimflamServiceName, dbus::ObjectPath(kManagerPath)));
+ // This will connect the name owner changed signal in DBus object proxy,
+ // The callback will be invoked as soon as service is avalilable. and will
+ // be cleared after it is invoked. So this will be an one time callback.
+ manager_proxy_->GetObjectProxy()->WaitForServiceToBeAvailable(
+ base::Bind(&ShillProxy::OnServiceAvailable, base::Unretained(this)));
+ // This will continuously monitor the name owner of the service. However,
+ // it does not connect the name owner changed signal in DBus object proxy
+ // for some reason. In order to connect the name owner changed signal,
+ // either WaitForServiceToBeAvaiable or ConnectToSignal need to be invoked.
+ // Since we're not interested in any signals from Shill proxy,
+ // WaitForServiceToBeAvailable is used.
+ manager_proxy_->GetObjectProxy()->SetNameOwnerChangedCallback(
+ base::Bind(&ShillProxy::OnServiceNameChanged, base::Unretained(this)));
+}
+
+void ShillProxy::ClaimInterface(const string& interface_name) {
+ CHECK(manager_proxy_) << "Proxy not initialize yet";
+ chromeos::ErrorPtr error;
+ if (!manager_proxy_->ClaimInterface(kServiceName, interface_name, &error)) {
+ // Ignore unknown object error (when shill is not running). Only report
+ // internal error from shill.
+ if (error->GetCode() != DBUS_ERROR_UNKNOWN_OBJECT) {
+ LOG(ERROR) << "Failed to claim interface from shill: "
+ << error->GetCode() << " " << error->GetMessage();
+ }
+ }
+ claimed_interfaces_.insert(interface_name);
+}
+
+void ShillProxy::ReleaseInterface(const string& interface_name) {
+ CHECK(manager_proxy_) << "Proxy not initialize yet";
+ chromeos::ErrorPtr error;
+ if (!manager_proxy_->ReleaseInterface(kServiceName, interface_name, &error)) {
+ // Ignore unknown object error (when shill is not running). Only report
+ // internal error from shill.
+ if (error->GetCode() != DBUS_ERROR_UNKNOWN_OBJECT) {
+ LOG(ERROR) << "Failed to release interface from shill: "
+ << error->GetCode() << " " << error->GetMessage();
+ }
+ }
+ claimed_interfaces_.erase(interface_name);
+}
+
+void ShillProxy::OnServiceAvailable(bool service_available) {
+ LOG(INFO) << "OnServiceAvailabe " << service_available;
+ // Nothing to be done if proxy service not available.
+ if (!service_available) {
+ return;
+ }
+ // Claim all interfaces from shill DBus service in case this is a new
+ // instance.
+ for (const auto& interface : claimed_interfaces_) {
+ chromeos::ErrorPtr error;
+ if (!manager_proxy_->ClaimInterface(kServiceName, interface, &error)) {
+ LOG(ERROR) << "Failed to claim interface from shill: "
+ << error->GetCode() << " " << error->GetMessage();
+ }
+ }
+}
+
+void ShillProxy::OnServiceNameChanged(const string& old_owner,
+ const string& new_owner) {
+ LOG(INFO) << "OnServiceNameChanged old " << old_owner
+ << " new " << new_owner;
+ // Nothing to be done if no owner is attached to the shill service.
+ if (new_owner.empty()) {
+ return;
+ }
+ OnServiceAvailable(true);
+}
+
+} // namespace apmanager
diff --git a/shill_proxy.h b/shill_proxy.h
new file mode 100644
index 0000000..24a0508
--- /dev/null
+++ b/shill_proxy.h
@@ -0,0 +1,48 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef APMANAGER_SHILL_PROXY_H_
+#define APMANAGER_SHILL_PROXY_H_
+
+#include <set>
+#include <string>
+
+#include <base/macros.h>
+#include <base/memory/scoped_ptr.h>
+
+#include "shill/dbus-proxies.h"
+
+// Proxy for shill "org.chromium.flimflam" DBus service.
+namespace apmanager {
+
+class ShillProxy {
+ public:
+ ShillProxy();
+ virtual ~ShillProxy();
+
+ void Init(const scoped_refptr<dbus::Bus>& bus);
+
+ // Claim the given interface |interface_name| from shill.
+ virtual void ClaimInterface(const std::string& interface_name);
+ // Release the given interface |interface_name| to shill.
+ virtual void ReleaseInterface(const std::string& interface_name);
+
+ private:
+ void OnServiceAvailable(bool service_available);
+ void OnServiceNameChanged(const std::string& old_owner,
+ const std::string& new_owner);
+
+ static const char kManagerPath[];
+
+ // DBus proxy for shill manager.
+ std::unique_ptr<org::chromium::flimflam::ManagerProxy> manager_proxy_;
+ // List of interfaces apmanager have claimed.
+ std::set<std::string> claimed_interfaces_;
+
+ DISALLOW_COPY_AND_ASSIGN(ShillProxy);
+};
+
+} // namespace apmanager
+
+#endif // APMANAGER_SHILL_PROXY_H_
diff --git a/testrunner.cc b/testrunner.cc
new file mode 100644
index 0000000..51bace0
--- /dev/null
+++ b/testrunner.cc
@@ -0,0 +1,16 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <base/at_exit.h>
+#include <base/command_line.h>
+#include <chromeos/syslog_logging.h>
+#include <gtest/gtest.h>
+
+int main(int argc, char** argv) {
+ base::AtExitManager exit_manager;
+ base::CommandLine::Init(argc, argv);
+ chromeos::InitLog(chromeos::kLogToStderr);
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}