// Copyright (c) 2022 Robin Davies // // Permission is hereby granted, free of charge, to any person obtaining a copy of // this software and associated documentation files (the "Software"), to deal in // the Software without restriction, including without limitation the rights to // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of // the Software, and to permit persons to whom the Software is furnished to do so, // subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include "HotspotManager.hpp" #include #include "Lv2Log.hpp" #include "DBusLog.hpp" #include "DBusEvent.hpp" #include "DBusDispatcher.hpp" #include "NetworkManagerInterfaces.hpp" #include "ss.hpp" #include "WifiConfigSettings.hpp" #include "ServiceConfiguration.hpp" #include #include #include using namespace pipedal; using namespace dbus::networkmanager; namespace fs = std::filesystem; namespace pipedal::impl { #define PIPEDAL_HOTSPOT_NAME "PiPedal Hotspot" class HotspotManagerImpl : public HotspotManager { public: using ssid_t = WifiConfigSettings::ssid_t; HotspotManagerImpl(); virtual ~HotspotManagerImpl() noexcept; virtual void Open() override; virtual void Reload() override; virtual void Close() override; std::vector GetKnownWifiNetworks(); virtual PostHandle Post(PostCallback &&fn) override; virtual PostHandle PostDelayed(const clock::duration &delay, PostCallback &&fn) override; virtual bool CancelPost(PostHandle handle) override; virtual void SetNetworkChangingListener(NetworkChangingListener &&listener) override; virtual void SetHasWifiListener(HasWifiListener &&listener) override; private: enum class State { Initial, WaitingForNetworkManager, Monitoring, HotspotConnecting, HotspotConnected, Error, Closed }; void SetState(State state); State state = State::Initial; bool hasWifi = false; HasWifiListener hasWifiListener; void SetHasWifi(bool hasWifi); virtual bool GetHasWifi() override ; void onClose(); void onError(const std::string &message); void onInitialize(); void onDevicesChanged(); void onDisconnect(); void onReload(); void DisableHotspot(); void EnableHotspot(); void StartHotspot(); void StopHotspot(); void onStartMonitoring(); void onAccessPointChanged(); void onAccessPointsChanged(); void CancelAccessPointsChangedTimer(); void WaitForNetworkManager(); void StartWaitForNetworkManagerTimer(); void CancelWaitForNetworkManagerTimer(); void UpdateNetworkManagerStatus(); void CancelDeviceChangedTimer(); void ReleaseNetworkManager(); void OnEthernetStateChanged(uint32_t state); void OnWlanStateChanged(uint32_t state); std::vector GetAllAccessPoints(); std::vector GetAllAutoConnectSsids(); std::vector GetKnownVisibleAccessPoints(const std::vector &allAccessPoints); void UpdateKnownNetworks( std::vector &knownSsids, std::vector &allAccessPoints); void FireNetworkChanging(); void MaybeStartHotspot(); void StartScanTimer(); void StopScanTimer(); void ScanNow(); Connection::ptr FindExistingConnection(); Device::ptr GetDevice(uint32_t nmDeviceType); std::atomic closed; DBusDispatcher dbusDispatcher; std::unique_ptr thread; void ThreadProc(); WifiConfigSettings wifiConfigSettings; NetworkManager::ptr networkManager; Device::ptr ethernetDevice; Device::ptr wlanDevice; DeviceWireless::ptr wlanWirelessDevice; ActiveConnection::ptr activeConnection; std::recursive_mutex networkChangingListenerMutex; NetworkChangingListener networkChangingListener; std::mutex knownWifiNetworksMutex; std::vector knownWifiNetworks; bool ethernetConnected = true; bool wlanConnected = true; DBusEventHandle onDeviceAddedHandle = INVALID_DBUS_EVENT_HANDLE; DBusEventHandle onDeviceRemovedHandle = INVALID_DBUS_EVENT_HANDLE; DBusDispatcher::PostHandle devicesChangedTimerHandle = 0; DBusDispatcher::PostHandle networkManagerTimerHandle = 0; DBusDispatcher::PostHandle accessPointsChangedTimerHandle = 0; DBusDispatcher::PostHandle scanTimerHandle = 0; }; } using namespace pipedal::impl; HotspotManagerImpl::HotspotManagerImpl() { } void HotspotManagerImpl::Open() { dbusDispatcher.Run(); dbusDispatcher.Post( [this]() { UpdateNetworkManagerStatus(); // waits for valid network manager state, and calls onInitialize when ready. }); } void HotspotManagerImpl::onClose() { this->closed = true; // avoids a memory barrier probelm. CancelDeviceChangedTimer(); CancelWaitForNetworkManagerTimer(); CancelAccessPointsChangedTimer(); if (networkManager && activeConnection) { try { networkManager->DeactivateConnection(activeConnection->getObjectPath()); activeConnection = nullptr; } catch (const std::exception &e) { // nothrow. } } ReleaseNetworkManager(); Lv2Log::debug("HotspotManager: state=Closed"); SetState(State::Closed); } void HotspotManagerImpl::onError(const std::string &message) { Lv2Log::error(message); ReleaseNetworkManager(); Lv2Log::debug("HotspotManager: state=Error"); SetState(State::Error); } void HotspotManagerImpl::SetState(State state) { this->state = state; } void HotspotManagerImpl::onInitialize() { if (closed) return; if (state != State::Initial && state != State::WaitingForNetworkManager) { onError("Invalid state (onInitialize)."); return; } try { wifiConfigSettings.Load(); onStartMonitoring(); } catch (const std::exception &e) { onError(SS("HotspotManager: " << e.what())); } } void HotspotManagerImpl::onDisconnect() { SetHasWifi(false); if (state != State::Initial && state != State::WaitingForNetworkManager) { WaitForNetworkManager(); } } void HotspotManagerImpl::UpdateNetworkManagerStatus() { try { if (!networkManager) { networkManager = NetworkManager::Create(dbusDispatcher); } // turn on wifi if required. auto ethernetDevice = GetDevice(NM_DEVICE_TYPE_ETHERNET); auto wlanDevice = GetDevice(NM_DEVICE_TYPE_WIFI); SetHasWifi(!!wlanDevice); if (ethernetDevice && wlanDevice) { if (state == State::Initial || state == State::WaitingForNetworkManager) { CancelWaitForNetworkManagerTimer(); this->onInitialize(); } return; } // check to see whether we have BOTH an eth0 device and a wlan0 device. } catch (const std::exception &e) { } if (state != State::WaitingForNetworkManager) { this->WaitForNetworkManager(); } else { this->StartWaitForNetworkManagerTimer(); } } void HotspotManagerImpl::CancelDeviceChangedTimer() { if (devicesChangedTimerHandle) { dbusDispatcher.CancelPost(devicesChangedTimerHandle); devicesChangedTimerHandle = 0; } } void HotspotManagerImpl::ReleaseNetworkManager() { StopScanTimer(); CancelAccessPointsChangedTimer(); CancelWaitForNetworkManagerTimer(); CancelDeviceChangedTimer(); DisableHotspot(); ethernetDevice = nullptr; wlanDevice = nullptr; wlanWirelessDevice = nullptr; networkManager = nullptr; this->onDeviceRemovedHandle = INVALID_DBUS_EVENT_HANDLE; this->onDeviceAddedHandle = INVALID_DBUS_EVENT_HANDLE; SetHasWifi(false); } void HotspotManagerImpl::onDevicesChanged() { UpdateNetworkManagerStatus(); } Device::ptr HotspotManagerImpl::GetDevice(uint32_t nmDeviceType) { const auto &allDevices = networkManager->GetAllDevices(); for (const auto &devicePath : allDevices) { auto device = Device::Create(dbusDispatcher, devicePath); if (device->DeviceType() == nmDeviceType) { return device; } } return nullptr; } void HotspotManagerImpl::OnEthernetStateChanged(uint32_t state) { bool newValue = (state == 100); if (newValue != this->ethernetConnected) { this->ethernetConnected = newValue; Lv2Log::debug(SS("HotspotMonitor: ethernetConnected=" << ethernetConnected)); FireNetworkChanging(); MaybeStartHotspot(); } } void HotspotManagerImpl::OnWlanStateChanged(uint32_t state) { this->wlanConnected = (state == 100); Lv2Log::debug(SS("HotspotMonitor: OnWlanStateChanged")); MaybeStartHotspot(); } void HotspotManagerImpl::onStartMonitoring() { try { ReleaseNetworkManager(); this->networkManager = NetworkManager::Create(dbusDispatcher); if (!networkManager->WirelessEnabled()) { networkManager->WirelessEnabled(true); } this->onDeviceAddedHandle = this->networkManager->OnDeviceAdded.add( [this](const sdbus::ObjectPath &) { onDevicesChanged(); }); this->onDeviceRemovedHandle = this->networkManager->OnDeviceRemoved.add( [this](const sdbus::ObjectPath &objectPath) { if (this->ethernetDevice && this->ethernetDevice->getObjectPath() == objectPath) { onDisconnect(); } else if (this->wlanDevice && this->wlanDevice->getObjectPath() == objectPath) { onDisconnect(); } }); ethernetDevice = GetDevice(NM_DEVICE_TYPE_ETHERNET); if (!ethernetDevice) { throw std::runtime_error("ethernet device not found."); } this->ethernetDevice->OnStateChanged.add( [this](uint32_t, uint32_t, uint32_t) { OnEthernetStateChanged(ethernetDevice->State()); }); wlanDevice = GetDevice(NM_DEVICE_TYPE_WIFI); if (!wlanDevice) { throw std::runtime_error("wlan0 device not found."); } wlanWirelessDevice = DeviceWireless::Create(dbusDispatcher, wlanDevice->getObjectPath()); wlanWirelessDevice->OnAccessPointAdded.add( [this](const sdbus::ObjectPath &accessPoint) { onAccessPointChanged(); }); wlanWirelessDevice->OnAccessPointRemoved.add( [this](const sdbus::ObjectPath &accessPoint) { onAccessPointChanged(); }); this->wlanDevice->OnStateChanged.add( [this](uint32_t, uint32_t, uint32_t) { OnWlanStateChanged(ethernetDevice->State()); }); Lv2Log::debug("HotspotManager: state=Monitoring"); SetState(State::Monitoring); Lv2Log::info("HotspotManager: Monitoring network status."); OnEthernetStateChanged(ethernetDevice->State()); OnWlanStateChanged(wlanDevice->State()); SetHasWifi(true); StartScanTimer(); onAccessPointChanged(); } catch (const std::exception &e) { Lv2Log::error(SS("HotspotManager: " << e.what())); WaitForNetworkManager(); } } void HotspotManagerImpl::StartWaitForNetworkManagerTimer() { CancelWaitForNetworkManagerTimer(); networkManagerTimerHandle = dbusDispatcher.PostDelayed( std::chrono::seconds(5), [this]() { networkManagerTimerHandle = 0; this->UpdateNetworkManagerStatus(); }); } void HotspotManagerImpl::CancelWaitForNetworkManagerTimer() { if (networkManagerTimerHandle) { dbusDispatcher.CancelPost(networkManagerTimerHandle); networkManagerTimerHandle = 0; } } void HotspotManagerImpl::WaitForNetworkManager() { Lv2Log::debug("HotspotManager: state=WaitingForNetworkManager"); SetState(State::WaitingForNetworkManager); ReleaseNetworkManager(); StartWaitForNetworkManagerTimer(); } void HotspotManagerImpl::onReload() { if (closed) return; WifiConfigSettings oldSettings = this->wifiConfigSettings; this->wifiConfigSettings.Load(); if (wifiConfigSettings == oldSettings) { return; } switch (state) { case State::Error: Lv2Log::error("NetworkManager is in an error state. Reboot and try again."); return; case State::Initial: case State::WaitingForNetworkManager: // ignore. new config will be picked up once initialization completes. return; case State::Closed: return; case State::HotspotConnecting: case State::HotspotConnected: default: // force a reload. StopHotspot(); SetState(State::Monitoring); MaybeStartHotspot(); return; } } HotspotManagerImpl::~HotspotManagerImpl() { Close(); } void HotspotManagerImpl::ThreadProc() { try { } catch (const std::exception &e) { Lv2Log::error(SS("Hotspot Manager thread terminated. " << e.what())); } } void HotspotManagerImpl::Reload() { dbusDispatcher.Post([this]() { onReload(); }); } void HotspotManagerImpl::Close() { if (!closed) { closed = true; dbusDispatcher.Post([this] { onClose(); }); dbusDispatcher.Stop(); } } HotspotManager::ptr HotspotManager::Create() { return std::make_unique(); } template class VectorHash { public: std::size_t operator()(const std::vector &vec) const { std::size_t hash = vec.size(); for (auto &i : vec) { hash += static_cast(i); hash = ((hash >> 32) ^ hash) * 0x119de1F3; } return hash; } }; struct NetworkSortRecord { std::string ssid; bool connected = false; bool knownNetwork = false; bool visibleNetwork = false; uint8_t strength; }; void HotspotManagerImpl::UpdateKnownNetworks( std::vector &knownSsids, std::vector &allAccessPoints) { std::map map; for (auto &knownSsid : knownSsids) { std::string ssid = ssidToString(knownSsid); if (ssid.length() != 0) { NetworkSortRecord &record = map[ssid]; record.ssid = ssid; record.knownNetwork = true; } } for (auto &accessPoint : allAccessPoints) { try { uint8_t strength = accessPoint->Strength(); auto vSsid = accessPoint->Ssid(); std::string ssid = ssidToString(vSsid); if (ssid.length() != 0) { NetworkSortRecord &record = map[ssid]; record.ssid = ssid; record.visibleNetwork = true; record.strength = strength; } } catch (const std::exception &ignored) { // race to get the info before it changes. np. } } if (this->wlanDevice) { auto activeConnectionPath = this->wlanDevice->ActiveConnection(); if (activeConnectionPath.length() > 2) // "/" -> no connection. Be paranoid about "". { auto activeConnection = ActiveConnection::Create(dbusDispatcher, activeConnectionPath); std::string activeSsid = activeConnection->Id(); auto it = map.find(activeSsid); if (it != map.end()) { it->second.connected = true; } } } std::vector records; records.reserve(map.size()); for (auto &mapEntry : map) { records.push_back(mapEntry.second); } std::sort(records.begin(), records.end(), [](const NetworkSortRecord &left, const NetworkSortRecord &right) { if (left.connected != right.connected) { return left.connected > right.connected; } if (left.knownNetwork != right.knownNetwork) { return left.knownNetwork > right.knownNetwork; } if (left.visibleNetwork != right.visibleNetwork) { return left.visibleNetwork > right.visibleNetwork; } if (left.strength != right.strength) { return left.strength > right.strength; } return false; }); if (records.size() > 10) { records.resize(10); } std::vector result; result.reserve(records.size()); for (auto &record : records) { result.push_back(std::move(record.ssid)); } { std::lock_guard lock{this->knownWifiNetworksMutex}; this->knownWifiNetworks = std::move(result); } } std::vector HotspotManagerImpl::GetAllAccessPoints() { std::vector accessPoints; for (const auto &accessPointPath : wlanWirelessDevice->AccessPoints()) { auto accessPoint = AccessPoint::Create(dbusDispatcher, accessPointPath); accessPoints.push_back(std::move(accessPoint)); } return accessPoints; } std::vector> HotspotManagerImpl::GetKnownVisibleAccessPoints(const std::vector &allAccessPoints) { std::vector> knownSsids = this->GetAllAutoConnectSsids(); std::unordered_set, VectorHash> index{knownSsids.begin(), knownSsids.end()}; std::vector result; std::vector accessPoints; for (const auto &accessPoint : allAccessPoints) { if (index.contains(accessPoint)) { result.push_back(accessPoint); } } return result; } std::vector> HotspotManagerImpl::GetAllAutoConnectSsids() { auto availableConnections = wlanDevice->AvailableConnections(); std::vector> ssids; for (const auto &connectionPath : availableConnections) { auto connection = Connection::Create(dbusDispatcher, connectionPath); try { auto settings = connection->GetSettings(); bool autoConnect = true; if (settings["connect"].count("autoconnect") > 0) { autoConnect = settings["connect"]["autoconnect"]; } bool isInfrastructure = settings["802-11-wireless"].count("mode") > 0 && settings["802-11-wireless"]["mode"].get() == "infrastructure"; if (isInfrastructure && autoConnect) { std::vector ssid = settings["802-11-wireless"]["ssid"]; ssids.push_back(std::move(ssid)); } } catch (const std::exception &ignored) { // not totally sure of structure of all connection types. } } return ssids; } void HotspotManagerImpl::onAccessPointsChanged() { MaybeStartHotspot(); } void HotspotManagerImpl::CancelAccessPointsChangedTimer() { if (accessPointsChangedTimerHandle) { dbusDispatcher.CancelPost(accessPointsChangedTimerHandle); accessPointsChangedTimerHandle = 0; } } void HotspotManagerImpl::onAccessPointChanged() { // coalesce large bursts of AccessPointChanged calls using a timer. if (!accessPointsChangedTimerHandle) { accessPointsChangedTimerHandle = dbusDispatcher.PostDelayed( std::chrono::milliseconds(512), [this]() { accessPointsChangedTimerHandle = 0; onAccessPointsChanged(); // coalesced handling of one or more changes. }); } } static std::vector> GetAccessPointSsids(std::vector &accessPoints) { std::vector> result; for (const auto &accessPoint : accessPoints) { try { result.push_back(accessPoint->Ssid()); } catch (const std::exception &ignored) { // race to get a disappearing ssid. Ignore the error. } } return result; } void HotspotManagerImpl::MaybeStartHotspot() { if (this->state == State::Error) return; if (this->closed) return; if (!wlanDevice || !wlanWirelessDevice) { // devices are transitioning. Do nothing. return; } std::vector allAccessPoints = GetAllAccessPoints(); std::vector> allAccessPointSsids = GetAccessPointSsids(allAccessPoints); std::vector> connectableSsids = GetKnownVisibleAccessPoints(allAccessPointSsids); // all the ssids currently visible for which we have credentials. this->UpdateKnownNetworks(connectableSsids, allAccessPoints); bool wantsHotspot = this->wifiConfigSettings.WantsHotspot( this->ethernetConnected, connectableSsids, allAccessPointSsids); if (this->state == State::Monitoring && wantsHotspot) { StartHotspot(); } else { if ((!wantsHotspot) && (this->state == State::HotspotConnected || this->state == State::HotspotConnecting)) { StopHotspot(); } } } void HotspotManagerImpl::EnableHotspot() { } void HotspotManagerImpl::DisableHotspot() { StopHotspot(); } Connection::ptr HotspotManagerImpl::FindExistingConnection() { if (wlanDevice) { for (const auto &connectionPath : wlanDevice->AvailableConnections()) { auto connection = Connection::Create(dbusDispatcher, connectionPath); auto settings = connection->GetSettings(); bool isHotspot = settings["802-11-wireless"].count("mode") > 0 && settings["802-11-wireless"]["mode"].get() == "ap"; if (isHotspot) { std::string id; if (settings["connection"].count("id") > 0) { id = settings["connection"]["id"].get(); } if (id.starts_with(PIPEDAL_HOTSPOT_NAME)) { return connection; } } } } return nullptr; } void HotspotManagerImpl::StartHotspot() { if (this->state == State::Monitoring) { try { ServiceConfiguration serviceConfiguration; serviceConfiguration.Load(); Lv2Log::debug("HotspotManager: state=HotspotConnecting"); SetState(State::HotspotConnecting); Lv2Log::info("HotspotManager: Enabling PiPedal hotspot."); // do it ONLY if we're in monitoring state. // Create a proxy for NetworkManager // Create connection settings for the hotspot std::map> settings; std::map &connection = settings["connection"]; connection["type"] = "802-11-wireless"; connection["autoconnect"] = false; connection["id"] = PIPEDAL_HOTSPOT_NAME; connection["interface-name"] = wlanDevice->Interface(); connection["uuid"] = serviceConfiguration.uuid; std::map &wireless = settings["802-11-wireless"]; std::string ssid = this->wifiConfigSettings.hotspotName_; std::vector vSsid{ssid.begin(), ssid.end()}; wireless["ssid"] = vSsid; wireless["mode"] = "ap"; uint32_t iChannel = 0; auto channel = this->wifiConfigSettings.channel_; if (channel.length() != 0) { std::stringstream ss{channel}; ss >> iChannel; } if (iChannel <= 1) { wireless["channel"] = iChannel; wireless["band"] = iChannel > 14 ? "a" : "bg"; } else { wireless["band"] = iChannel == 0 ? "bg" : "a"; } std::map &wirelessSecurity = settings["802-11-wireless-security"]; wirelessSecurity["key-mgmt"] = "wpa-psk"; wirelessSecurity["psk"] = wifiConfigSettings.password_; settings["ipv4"]["method"] = "shared"; settings["ipv6"]["method"] = "shared"; settings["ipv6"]["addr-gen-mode"] = 1; // "stable-privacy"; //////////////////////////////////////////////////////////////// // Create connection settings["ipv4"]["method"] = sdbus::Variant(std::string("shared")); // settings["ipv6"]["method"] = sdbus::Variant(std::string("ignore")); settings["ipv4"]["address-data"] = sdbus::Variant(std::vector>{{{"address", sdbus::Variant("192.168.60.1")}, {"prefix", sdbus::Variant(uint32_t(24))}}}); // settings["ipv4"]["gateway'"] = sdbus::Variant(std::string("192.168.60.1")); settings["ipv4"]["dhcp-send-hostname"] = sdbus::Variant(true); settings["ipv4"]["dhcp-hostname"] = sdbus::Variant("raspberrypi"); // settings["ipv4"]["dhcp-options"] = sdbus::Variant(std::vector{ // "option:classless-static-route,192.168.60.0/23,0.0.0.0,0.0.0.0/0,192.168.60.1"}); // settings["ipv4"]["dhcp-options"] = sdbus::Variant(std::vector{ // "option:classless-static-route,192.168.60.0/23,192.168.60.1"}); //////////////////////////////////////////////////////////// settings["ipv6"]["address-data"] = sdbus::Variant(std::vector>{ {{"address", sdbus::Variant("fdd2:e477:7b37:5b09::1")}, {"prefix", sdbus::Variant(uint32_t(64))}}}); settings["ipv6"]["gateway"] = sdbus::Variant("fdd2:e477:7b37:5b09::1"); std::map options; options["persist"] = "disk"; Connection::ptr existingConnection = FindExistingConnection(); if (existingConnection) { existingConnection->Update(settings); auto activeConnectionPath = networkManager->ActivateConnection( existingConnection->getObjectPath(), wlanDevice->getObjectPath(), "/"); this->activeConnection = ActiveConnection::Create(dbusDispatcher, activeConnectionPath); } else { // Call AddAndActivateConnection2 method to create and activate the hotspot sdbus::ObjectPath nullPath("/"); auto result = networkManager->AddAndActivateConnection2( settings, wlanDevice->getObjectPath(), "/", options ); auto connectionPath = std::get<0>(result); auto activeConnectionPath = std::get<1>(result); // auto resultArgs = std::get<2>(result); this->activeConnection = ActiveConnection::Create(dbusDispatcher, activeConnectionPath); } SetState(State::HotspotConnected); Lv2Log::info("HotspotManager: Hotspot activated."); FireNetworkChanging(); } catch (const std::exception &e) { onError(SS("HotspotManager: Activation failed: " << e.what())); } } else { onError("HotspotManager: Illegal state (StartHotspot)"); } } void HotspotManagerImpl::StopHotspot() { // do it regardless of state. try { if (activeConnection) { FireNetworkChanging(); networkManager->DeactivateConnection(activeConnection->getObjectPath()); activeConnection = nullptr; } } catch (const std::exception &e) { Lv2Log::error(SS("HotspotManager: Failed to deactivate hotspot. " << e.what())); activeConnection = nullptr; } if (this->state == State::HotspotConnected || this->state == State::HotspotConnecting) { Lv2Log::debug("HotspotManager: state=HotspotMonitoring"); SetState(State::Monitoring); Lv2Log::info("HotspotManager: PiPedal hotspot disabled."); } } static const std::chrono::seconds scanInterval{60}; void HotspotManagerImpl::StartScanTimer() { StopScanTimer(); ScanNow(); } void HotspotManagerImpl::StopScanTimer() { if (this->scanTimerHandle) { dbusDispatcher.CancelPost(this->scanTimerHandle); this->scanTimerHandle = 0; } } void HotspotManagerImpl::ScanNow() { this->scanTimerHandle = 0; if (wlanWirelessDevice) { std::map options; try { Lv2Log::debug("Scanning"); wlanWirelessDevice->RequestScan(options); } catch (const std::exception &e) { Lv2Log::error(SS("HotspotMonitor: Wi-Fi RequestScan failed." << e.what())); return; } this->scanTimerHandle = this->dbusDispatcher.PostDelayed( std::chrono::duration_cast(scanInterval), [this]() { ScanNow(); }); } } HotspotManagerImpl::PostHandle HotspotManagerImpl::Post(PostCallback &&fn) { return dbusDispatcher.Post(std::move(fn)); } HotspotManagerImpl::PostHandle HotspotManagerImpl::PostDelayed(const clock::duration &delay, PostCallback &&fn) { return dbusDispatcher.PostDelayed(delay, std::move(fn)); } bool HotspotManagerImpl::CancelPost(PostHandle handle) { return dbusDispatcher.CancelPost(handle); } void HotspotManagerImpl::SetHasWifiListener(HasWifiListener &&listener) { std::lock_guard lock{this->networkChangingListenerMutex}; this->hasWifiListener = listener; if (!this->closed && this->hasWifiListener) { this->hasWifiListener(this->hasWifi); } } bool HotspotManagerImpl::GetHasWifi() { std::lock_guard lock{this->networkChangingListenerMutex}; return hasWifi; } void HotspotManagerImpl::SetHasWifi(bool hasWifi) { std::lock_guard lock{this->networkChangingListenerMutex}; if (hasWifi != this->hasWifi) { this->hasWifi = hasWifi; if (this->hasWifiListener) { this->hasWifiListener(this->hasWifi); } } } void HotspotManagerImpl::SetNetworkChangingListener(NetworkChangingListener &&listener) { std::lock_guard lock{this->networkChangingListenerMutex}; this->networkChangingListener = std::move(listener); } void HotspotManagerImpl::FireNetworkChanging() { std::lock_guard lock{this->networkChangingListenerMutex}; if (this->networkChangingListener) { this->networkChangingListener(this->ethernetConnected, !!activeConnection); } } std::vector HotspotManagerImpl::GetKnownWifiNetworks() { std::lock_guard lock(knownWifiNetworksMutex); // pushed off the service thread. return this->knownWifiNetworks; } static bool is_wireless_sysfs(const std::string &ifname) { return fs::exists("/sys/class/net/" + ifname + "/wireless"); } static std::vector get_wireless_interfaces_sysfs() { std::vector wireless_interfaces; const std::string net_path = "/sys/class/net"; try { for (const auto &entry : fs::directory_iterator(net_path)) { std::string ifname = entry.path().filename(); if (is_wireless_sysfs(ifname)) { wireless_interfaces.push_back(ifname); } } } catch (const fs::filesystem_error &e) { std::cerr << "Error accessing " << net_path << ": " << e.what() << std::endl; } return wireless_interfaces; } bool HotspotManager::HasWifiDevice() { // use procfs to decide this, as NetworkManager may not be available yet. return !(get_wireless_interfaces_sysfs().empty()); }