1110 lines
33 KiB
C++
1110 lines
33 KiB
C++
// 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 <thread>
|
|
#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 <unordered_set>
|
|
#include <mutex>
|
|
#include <algorithm>
|
|
|
|
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<std::string> 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<AccessPoint::ptr> GetAllAccessPoints();
|
|
std::vector<ssid_t> GetAllAutoConnectSsids();
|
|
std::vector<ssid_t> GetKnownVisibleAccessPoints(const std::vector<ssid_t> &allAccessPoints);
|
|
|
|
void UpdateKnownNetworks(
|
|
std::vector<ssid_t> &knownSsids,
|
|
std::vector<AccessPoint::ptr> &allAccessPoints);
|
|
|
|
void FireNetworkChanging();
|
|
|
|
void MaybeStartHotspot();
|
|
|
|
void StartScanTimer();
|
|
void StopScanTimer();
|
|
void ScanNow();
|
|
|
|
Connection::ptr FindExistingConnection();
|
|
|
|
Device::ptr GetDevice(uint32_t nmDeviceType);
|
|
std::atomic<bool> closed;
|
|
DBusDispatcher dbusDispatcher;
|
|
|
|
std::unique_ptr<std::thread> 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<std::string> 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("Wi-Fi 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<HotspotManagerImpl>();
|
|
}
|
|
|
|
template <typename T>
|
|
class VectorHash
|
|
{
|
|
public:
|
|
std::size_t operator()(const std::vector<T> &vec) const
|
|
{
|
|
std::size_t hash = vec.size();
|
|
for (auto &i : vec)
|
|
{
|
|
hash += static_cast<size_t>(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<ssid_t> &knownSsids,
|
|
std::vector<AccessPoint::ptr> &allAccessPoints)
|
|
{
|
|
std::map<std::string, NetworkSortRecord> 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<NetworkSortRecord> 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<std::string> 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<AccessPoint::ptr> HotspotManagerImpl::GetAllAccessPoints()
|
|
{
|
|
std::vector<AccessPoint::ptr> accessPoints;
|
|
|
|
for (const auto &accessPointPath : wlanWirelessDevice->AccessPoints())
|
|
{
|
|
auto accessPoint = AccessPoint::Create(dbusDispatcher, accessPointPath);
|
|
accessPoints.push_back(std::move(accessPoint));
|
|
}
|
|
return accessPoints;
|
|
}
|
|
std::vector<std::vector<uint8_t>> HotspotManagerImpl::GetKnownVisibleAccessPoints(const std::vector<ssid_t> &allAccessPoints)
|
|
{
|
|
std::vector<std::vector<uint8_t>> knownSsids = this->GetAllAutoConnectSsids();
|
|
|
|
std::unordered_set<std::vector<uint8_t>, VectorHash<uint8_t>> index{knownSsids.begin(), knownSsids.end()};
|
|
|
|
std::vector<ssid_t> result;
|
|
std::vector<AccessPoint::ptr> accessPoints;
|
|
|
|
for (const auto &accessPoint : allAccessPoints)
|
|
{
|
|
if (index.contains(accessPoint))
|
|
{
|
|
result.push_back(accessPoint);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::vector<std::vector<uint8_t>> HotspotManagerImpl::GetAllAutoConnectSsids()
|
|
{
|
|
auto availableConnections = wlanDevice->AvailableConnections();
|
|
std::vector<std::vector<uint8_t>> 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<std::string>() == "infrastructure";
|
|
if (isInfrastructure && autoConnect)
|
|
{
|
|
std::vector<uint8_t> 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<std::vector<uint8_t>> GetAccessPointSsids(std::vector<AccessPoint::ptr> &accessPoints)
|
|
{
|
|
std::vector<std::vector<uint8_t>> 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<AccessPoint::ptr> allAccessPoints = GetAllAccessPoints();
|
|
std::vector<std::vector<uint8_t>> allAccessPointSsids = GetAccessPointSsids(allAccessPoints);
|
|
std::vector<std::vector<uint8_t>> 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<std::string>() == "ap";
|
|
if (isHotspot)
|
|
{
|
|
std::string id;
|
|
if (settings["connection"].count("id") > 0)
|
|
{
|
|
id = settings["connection"]["id"].get<std::string>();
|
|
}
|
|
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<std::string, std::map<std::string, sdbus::Variant>> settings;
|
|
|
|
std::map<std::string, sdbus::Variant> &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<std::string, sdbus::Variant> &wireless = settings["802-11-wireless"];
|
|
|
|
std::string ssid = this->wifiConfigSettings.hotspotName_;
|
|
std::vector<uint8_t> 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<std::string, sdbus::Variant> &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<std::map<std::string, sdbus::Variant>>{{{"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<std::string>{
|
|
// "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<std::string>{
|
|
// "option:classless-static-route,192.168.60.0/23,192.168.60.1"});
|
|
|
|
////////////////////////////////////////////////////////////
|
|
|
|
settings["ipv6"]["address-data"] = sdbus::Variant(std::vector<std::map<std::string, sdbus::Variant>>{
|
|
{{"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<std::string, sdbus::Variant> 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<std::string, sdbus::Variant> 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<std::chrono::steady_clock::duration>(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<std::recursive_mutex> lock{this->networkChangingListenerMutex};
|
|
this->hasWifiListener = listener;
|
|
if (!this->closed && this->hasWifiListener)
|
|
{
|
|
this->hasWifiListener(this->hasWifi);
|
|
}
|
|
}
|
|
bool HotspotManagerImpl::GetHasWifi() {
|
|
std::lock_guard<std::recursive_mutex> lock{this->networkChangingListenerMutex};
|
|
return hasWifi;
|
|
}
|
|
void HotspotManagerImpl::SetHasWifi(bool hasWifi)
|
|
{
|
|
std::lock_guard<std::recursive_mutex> 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<std::recursive_mutex> lock{this->networkChangingListenerMutex};
|
|
this->networkChangingListener = std::move(listener);
|
|
}
|
|
void HotspotManagerImpl::FireNetworkChanging()
|
|
{
|
|
std::lock_guard<std::recursive_mutex> lock{this->networkChangingListenerMutex};
|
|
if (this->networkChangingListener)
|
|
{
|
|
this->networkChangingListener(this->ethernetConnected, !!activeConnection);
|
|
}
|
|
}
|
|
|
|
std::vector<std::string> 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<std::string> get_wireless_interfaces_sysfs()
|
|
{
|
|
std::vector<std::string> 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());
|
|
}
|