diff options
author | Christopher Wiley <wiley@google.com> | 2016-07-14 16:40:58 -0700 |
---|---|---|
committer | Christopher Wiley <wiley@google.com> | 2016-07-18 13:58:21 -0700 |
commit | 944db7cbdafa91279b40dd8a7737307e49cf711e (patch) | |
tree | 3b6cb1aef535ccb223437a8b27a622d14de0a0d2 /libwifi_system | |
parent | 3efd47ff5e926cc1fa042e288e129df10fa4ec83 (diff) | |
download | frameworks_opt_net_wifi-944db7cbdafa91279b40dd8a7737307e49cf711e.tar.gz frameworks_opt_net_wifi-944db7cbdafa91279b40dd8a7737307e49cf711e.tar.bz2 frameworks_opt_net_wifi-944db7cbdafa91279b40dd8a7737307e49cf711e.zip |
Move hostapd management logic to libwifi-system
This is the logic that existed in netd, but moved into a mockable
C++ object and formulated to support unittesting the config file
generation.
Bug: 30040724
Test: unit tests pass, data for tests extracted from config files
generated by existing netd code.
Test: netd continues to be able to set up APs for tethering using
libwifi-system rather than previous code.
Test: No SELinux policy violations during/after setup.
Test: Can sign in to network backed by hostapd started by netd using
libwifi-system.
Change-Id: I1156c494fc889a3fdf14182b11c7697d93fdf586
Diffstat (limited to 'libwifi_system')
-rw-r--r-- | libwifi_system/Android.mk | 20 | ||||
-rw-r--r-- | libwifi_system/hostapd_manager.cpp | 202 | ||||
-rw-r--r-- | libwifi_system/include/wifi_system/hostapd_manager.h | 81 | ||||
-rw-r--r-- | libwifi_system/tests/hostapd_manager_unittest.cpp | 145 | ||||
-rw-r--r-- | libwifi_system/tests/main.cpp | 27 |
5 files changed, 475 insertions, 0 deletions
diff --git a/libwifi_system/Android.mk b/libwifi_system/Android.mk index d37fa9a9e..61d340a23 100644 --- a/libwifi_system/Android.mk +++ b/libwifi_system/Android.mk @@ -34,8 +34,10 @@ LOCAL_MODULE := libwifi-system LOCAL_CFLAGS := $(wifi_system_cflags) LOCAL_C_INCLUDES := $(LOCAL_PATH)/include LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include +LOCAL_EXPORT_SHARED_LIBRARY_HEADERS := libbase LOCAL_SHARED_LIBRARIES := \ libbase \ + libcrypto \ libcutils \ liblog \ libnetutils \ @@ -49,6 +51,7 @@ LOCAL_SHARED_LIBRARIES += libwpa_client endif LOCAL_SRC_FILES := \ + hostapd_manager.cpp \ interface_tool.cpp \ hal_tool.cpp \ wifi.cpp @@ -68,4 +71,21 @@ LOCAL_EXPORT_C_INCLUDE_DIRS := \ $(LOCAL_PATH)/testlib/include include $(BUILD_STATIC_LIBRARY) + +# Unit tests for libwifi-system +# ============================================================ +include $(CLEAR_VARS) +LOCAL_MODULE := libwifi-system_tests +LOCAL_CPPFLAGS := $(wificond_cpp_flags) +LOCAL_SRC_FILES := \ + tests/main.cpp \ + tests/hostapd_manager_unittest.cpp +LOCAL_STATIC_LIBRARIES := \ + libgmock \ + libgtest +LOCAL_SHARED_LIBRARIES := \ + libbase \ + libwifi-system +include $(BUILD_NATIVE_TEST) + endif diff --git a/libwifi_system/hostapd_manager.cpp b/libwifi_system/hostapd_manager.cpp new file mode 100644 index 000000000..889fd6946 --- /dev/null +++ b/libwifi_system/hostapd_manager.cpp @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "wifi_system/hostapd_manager.h" + +#include <iomanip> +#include <sstream> +#include <string> +#include <vector> + +#include <android-base/file.h> +#include <android-base/logging.h> +#include <android-base/stringprintf.h> +#include <cutils/properties.h> +#include <openssl/evp.h> +#include <openssl/sha.h> +#include <private/android_filesystem_config.h> + +#include "wifi_system/wifi.h" + +using android::base::StringPrintf; +using android::base::WriteStringToFile; +using std::string; +using std::vector; +using std::stringstream; + +namespace android { +namespace wifi_system { +namespace { + +const int kDefaultApChannel = 6; +const char kHostapdServiceName[] = "hostapd"; +const char kHostapdConfigFilePath[] = "/data/misc/wifi/hostapd.conf"; + +string GeneratePsk(const vector<uint8_t>& ssid, + const vector<uint8_t>& passphrase) { + string result; + unsigned char psk[SHA256_DIGEST_LENGTH]; + + // Use the PKCS#5 PBKDF2 with 4096 iterations + if (PKCS5_PBKDF2_HMAC_SHA1(reinterpret_cast<const char*>(passphrase.data()), + passphrase.size(), + ssid.data(), ssid.size(), + 4096, sizeof(psk), psk) != 1) { + LOG(ERROR) << "Cannot generate PSK using PKCS#5 PBKDF2"; + return result; + } + + stringstream ss; + ss << std::hex; + ss << std::setfill('0'); + for (int j = 0; j < SHA256_DIGEST_LENGTH; j++) { + ss << std::setw(2) << static_cast<unsigned int>(psk[j]); + } + result = ss.str(); + + return result; +} + +} // namespace + +bool HostapdManager::StartHostapd() { + if (hostapd_is_running_) { + LOG(ERROR) << "SoftAP is already running"; + return false; + } + + if (ensure_entropy_file_exists() < 0) { + LOG(WARNING) << "Wi-Fi entropy file was not created"; + } + + if (property_set("ctl.start", kHostapdServiceName) != 0) { + LOG(ERROR) << "Failed to start SoftAP"; + return false; + } + + LOG(DEBUG) << "SoftAP started successfully"; + hostapd_is_running_ = true; + return true; +} + +bool HostapdManager::IsHostapdRunning() { + return hostapd_is_running_; +} + +bool HostapdManager::StopHostapd() { + if (!hostapd_is_running_) { + LOG(DEBUG) << "SoftAP is not running"; + return true; // Not really an error, hostapd is already stopped. + } + + LOG(DEBUG) << "Stopping the SoftAP service..."; + + if (property_set("ctl.stop", kHostapdServiceName) < 0) { + LOG(ERROR) << "Failed to stop hostapd service!"; + return false; + } + + LOG(DEBUG) << "SoftAP stopped successfully"; + hostapd_is_running_ = false; + return true; +} + +bool HostapdManager::WriteHostapdConfig(const string& config) { + if (!WriteStringToFile(config, kHostapdConfigFilePath, + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP, + AID_SYSTEM, AID_WIFI)) { + int error = errno; + LOG(ERROR) << "Cannot write hostapd config to \"" + << kHostapdConfigFilePath << "\": " << strerror(error); + return false; + } + return true; +} + +string HostapdManager::CreateHostapdConfig( + const string& interface_name, + const vector<uint8_t> ssid, + bool is_hidden, + int channel, + EncryptionType encryption_type, + const vector<uint8_t> passphrase) { + string result; + + if (channel < 0) { + channel = kDefaultApChannel; + } + + if (ssid.size() > 32) { + LOG(ERROR) << "SSIDs must be <= 32 bytes long"; + return result; + } + + stringstream ss; + ss << std::hex; + ss << std::setfill('0'); + for (uint8_t b : ssid) { + ss << std::setw(2) << static_cast<unsigned int>(b); + } + const string ssid_as_string = ss.str(); + + string encryption_config; + if (encryption_type != EncryptionType::kOpen) { + string psk = GeneratePsk(ssid, passphrase); + if (psk.empty()) { + return result; + } + if (encryption_type == EncryptionType::kWpa) { + encryption_config = StringPrintf("wpa=3\n" + "wpa_pairwise=TKIP CCMP\n" + "wpa_psk=%s\n", psk.c_str()); + } else if (encryption_type == EncryptionType::kWpa2) { + encryption_config = StringPrintf("wpa=2\n" + "rsn_pairwise=CCMP\n" + "wpa_psk=%s\n", psk.c_str()); + } else { + using encryption_t = std::underlying_type<EncryptionType>::type; + LOG(ERROR) << "Unknown encryption type (" + << static_cast<encryption_t>(encryption_type) + << ")"; + return result; + } + } + + result = StringPrintf( + "interface=%s\n" + "driver=nl80211\n" + "ctrl_interface=/data/misc/wifi/hostapd\n" + // ssid2 signals to hostapd that the value is not a literal value + // for use as a SSID. In this case, we're giving it a hex string + // and hostapd needs to expect that. + "ssid2=%s\n" + "channel=%d\n" + "ieee80211n=1\n" + "hw_mode=%c\n" + "ignore_broadcast_ssid=%d\n" + "wowlan_triggers=any\n" + "%s", + interface_name.c_str(), + ssid_as_string.c_str(), + channel, + (channel <= 14) ? 'g' : 'a', + (is_hidden) ? 1 : 0, + encryption_config.c_str()); + return result; +} + +} // namespace wifi_system +} // namespace android diff --git a/libwifi_system/include/wifi_system/hostapd_manager.h b/libwifi_system/include/wifi_system/hostapd_manager.h new file mode 100644 index 000000000..2bffde453 --- /dev/null +++ b/libwifi_system/include/wifi_system/hostapd_manager.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_WIFI_SYSTEM_HOSTAPD_MANAGER_H +#define ANDROID_WIFI_SYSTEM_HOSTAPD_MANAGER_H + +#include <string> +#include <vector> + +#include <android-base/macros.h> + +namespace android { +namespace wifi_system { + +class HostapdManager { + public: + enum class EncryptionType { + kOpen, + kWpa, + kWpa2, // Strongly prefer this if at all possible. + }; + + HostapdManager() = default; + virtual ~HostapdManager() = default; + + // Request that hostapd be started. + // Returns true on success. + virtual bool StartHostapd(); + + // Returns true if hostapd is currently running. + virtual bool IsHostapdRunning(); + + // Request that a running instance of hostapd be stopped. + // Returns true on success. + virtual bool StopHostapd(); + + // Create a string suitable for writing to the hostapd configuration file. + // |interface_name| is a network interface name (e.g. "wlan0"). + // |ssid| is the SSID used by the configurated network. + // |is_hidden| is true iff hostapd should not broadcast the SSID. + // |channel| is the WiFi channel (e.g. 6) or <0 for a default value. + // |encryption_type| defines the encryption of the configured network. + // |passphrase| is ignored for kOpen networks. + // + // Returns an empty string on failure. + virtual std::string CreateHostapdConfig( + const std::string& interface_name, + const std::vector<uint8_t> ssid, + bool is_hidden, + int channel, + EncryptionType encryption, + const std::vector<uint8_t> passphrase); + + // Write out a hostapd configuration file created via CreateHostapdConfig(). + // Future instances of hostapd will use this new configuration. + // Returns true if the configuration file is successfully written. + virtual bool WriteHostapdConfig(const std::string& config_file); + + private: + bool hostapd_is_running_ = false; + + DISALLOW_COPY_AND_ASSIGN(HostapdManager); +}; // class HostapdManager + +} // namespace wifi_system +} // namespace android + +#endif // ANDROID_WIFI_SYSTEM_HOSTAPD_MANAGER_H diff --git a/libwifi_system/tests/hostapd_manager_unittest.cpp b/libwifi_system/tests/hostapd_manager_unittest.cpp new file mode 100644 index 000000000..f8ebd995f --- /dev/null +++ b/libwifi_system/tests/hostapd_manager_unittest.cpp @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2016, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <gmock/gmock.h> +#include <gtest/gtest.h> + +#include <string> +#include <vector> + +#include "wifi_system/hostapd_manager.h" + +using std::string; +using std::vector; + +namespace android { +namespace wifi_system { +namespace { + +const char kTestInterfaceName[] = "foobar0"; +const char kTestSsidStr[] = "helloisitme"; +const char kTestPassphraseStr[] = "yourelookingfor"; +const int kTestChannel = 2; + +#define CONFIG_COMMON_PREFIX \ + "interface=foobar0\n" \ + "driver=nl80211\n" \ + "ctrl_interface=/data/misc/wifi/hostapd\n" \ + "ssid2=68656c6c6f" "6973" "6974" "6d65\n" \ + "channel=2\n" \ + "ieee80211n=1\n" \ + "hw_mode=g\n" + +// If you generate your config file with both the test ssid +// and the test passphrase, you'll get this line in the config. +#define CONFIG_PSK_LINE \ + "wpa_psk=dffa36815281e5a6eca1910f254717fa2528681335e3bbec5966d2aa9221a66e\n" + +#define CONFIG_WPA_SUFFIX \ + "wpa=3\n" \ + "wpa_pairwise=TKIP CCMP\n" \ + CONFIG_PSK_LINE + +#define CONFIG_WPA2_SUFFIX \ + "wpa=2\n" \ + "rsn_pairwise=CCMP\n" \ + CONFIG_PSK_LINE + +const char kExpectedOpenConfig[] = + CONFIG_COMMON_PREFIX + "ignore_broadcast_ssid=0\n" + "wowlan_triggers=any\n"; + +const char kExpectedWpaConfig[] = + CONFIG_COMMON_PREFIX + "ignore_broadcast_ssid=0\n" + "wowlan_triggers=any\n" + CONFIG_WPA_SUFFIX; + +const char kExpectedWpa2Config[] = + CONFIG_COMMON_PREFIX + "ignore_broadcast_ssid=0\n" + "wowlan_triggers=any\n" + CONFIG_WPA2_SUFFIX; + +class HostapdManagerTest : public ::testing::Test { + protected: + string GetConfigForEncryptionType( + HostapdManager::EncryptionType encryption_type) { + return HostapdManager().CreateHostapdConfig( + kTestInterfaceName, + cstr2vector(kTestSsidStr), + false, // not hidden + kTestChannel, + encryption_type, + cstr2vector(kTestPassphraseStr)); + } + + vector<uint8_t> cstr2vector(const char* data) { + return vector<uint8_t>(data, data + strlen(data)); + } +}; // class HostapdManagerTest + +} // namespace + +TEST_F(HostapdManagerTest, GeneratesCorrectOpenConfig) { + string config = GetConfigForEncryptionType( + HostapdManager::EncryptionType::kOpen); + EXPECT_FALSE(config.empty()); + EXPECT_EQ(kExpectedOpenConfig, config); +} + +TEST_F(HostapdManagerTest, GeneratesCorrectWpaConfig) { + string config = GetConfigForEncryptionType( + HostapdManager::EncryptionType::kWpa); + EXPECT_FALSE(config.empty()); + EXPECT_EQ(kExpectedWpaConfig, config); +} + +TEST_F(HostapdManagerTest, GeneratesCorrectWpa2Config) { + string config = GetConfigForEncryptionType( + HostapdManager::EncryptionType::kWpa2); + EXPECT_FALSE(config.empty()); + EXPECT_EQ(kExpectedWpa2Config, config); +} + +TEST_F(HostapdManagerTest, RespectsHiddenSetting) { + string config = HostapdManager().CreateHostapdConfig( + kTestInterfaceName, + cstr2vector(kTestSsidStr), + true, + kTestChannel, + HostapdManager::EncryptionType::kOpen, + vector<uint8_t>()); + EXPECT_FALSE(config.find("ignore_broadcast_ssid=1\n") == string::npos); + EXPECT_TRUE(config.find("ignore_broadcast_ssid=0\n") == string::npos); +} + +TEST_F(HostapdManagerTest, CorrectlyInfersHwMode) { + string config = HostapdManager().CreateHostapdConfig( + kTestInterfaceName, + cstr2vector(kTestSsidStr), + true, + 44, + HostapdManager::EncryptionType::kOpen, + vector<uint8_t>()); + EXPECT_FALSE(config.find("hw_mode=a\n") == string::npos); + EXPECT_TRUE(config.find("hw_mode=g\n") == string::npos); +} + + +} // namespace wifi_system +} // namespace android diff --git a/libwifi_system/tests/main.cpp b/libwifi_system/tests/main.cpp new file mode 100644 index 000000000..51e8dad29 --- /dev/null +++ b/libwifi_system/tests/main.cpp @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <gtest/gtest.h> + +#include <android-base/logging.h> + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + // Force ourselves to always log to stderr + android::base::InitLogging(argv, android::base::StderrLogger); + return RUN_ALL_TESTS(); +} + |