diff options
author | Bill Yi <byi@google.com> | 2015-08-28 10:34:50 -0700 |
---|---|---|
committer | Bill Yi <byi@google.com> | 2015-08-28 10:35:08 -0700 |
commit | 083525f2c010c92dac08630ebcdd4a7998c8deaa (patch) | |
tree | d000f5a595ae279cec742ee2e4de9f83d820c9bf | |
parent | 84c70e1e92a1c060a460add51a51ae089755b259 (diff) | |
parent | 063c28415272d8026bfa87416811afe63e8023d3 (diff) | |
download | apmanager-083525f2c010c92dac08630ebcdd4a7998c8deaa.tar.gz |
Merge branch 'rewrite-apmanager' into merge-apmanager
BUG:23620021
70 files changed, 6199 insertions, 0 deletions
@@ -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}" @@ -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(); +} |