From 987a3c940f720f913bf57b27343e8fe4d5f908b0 Mon Sep 17 00:00:00 2001 From: JianDe Date: Thu, 19 Mar 2026 00:15:36 +0800 Subject: [PATCH 1/4] Add sdbus-c++ ConnMan client example Signed-off-by: JianDe --- CMakeLists.txt | 1 + src/connman/CMakeLists.txt | 19 ++++ src/connman/connman_client.cc | 172 ++++++++++++++++++++++++++++++++++ src/connman/connman_client.h | 95 +++++++++++++++++++ src/connman/main.cc | 48 ++++++++++ 5 files changed, 335 insertions(+) create mode 100644 src/connman/CMakeLists.txt create mode 100644 src/connman/connman_client.cc create mode 100644 src/connman/connman_client.h create mode 100644 src/connman/main.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index 29cdc42..1f4963e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,6 +56,7 @@ message(STATUS "libudev ................ found (version ${UDEV_VERSION})") add_subdirectory(src/avahi) add_subdirectory(src/bluez) +add_subdirectory(src/connman) add_subdirectory(src/fwupd) add_subdirectory(src/geoclue2) add_subdirectory(src/hostname1) diff --git a/src/connman/CMakeLists.txt b/src/connman/CMakeLists.txt new file mode 100644 index 0000000..b6127b0 --- /dev/null +++ b/src/connman/CMakeLists.txt @@ -0,0 +1,19 @@ +add_executable(connman_client + main.cc + connman_client.cc + connman_client.h +) + +target_link_libraries(connman_client + PUBLIC + utils + sdbus-c++ + spdlog::spdlog +) + +if (ENABLE_LTO AND IPO_SUPPORT_RESULT) + set_property(TARGET connman_client PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE) +endif () + +install(TARGETS connman_client + RUNTIME DESTINATION share/sdbus-cpp-examples) \ No newline at end of file diff --git a/src/connman/connman_client.cc b/src/connman/connman_client.cc new file mode 100644 index 0000000..2dc2c7a --- /dev/null +++ b/src/connman/connman_client.cc @@ -0,0 +1,172 @@ +// Copyright (c) 2026 Jian De +// +// 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 "connman_client.h" + +#include + +#include "../utils/logging.h" +#include "../utils/utils.h" + +// +// ConnmanManagerClient +// + +ConnmanManagerClient::ConnmanManagerClient(sdbus::IConnection& connection) + : ProxyInterfaces(connection, + sdbus::ServiceName{SERVICE_NAME}, + sdbus::ObjectPath{OBJECT_PATH}) { + registerProxy(); + + // Load initial technologies + for (const auto& tech : GetTechnologies()) { + onTechnologyAdded(tech.get<0>(), tech.get<1>()); + } + + // Load initial services + for (const auto& service : GetServices()) { + const auto& path = service.get<0>(); + if (services_.find(path) == services_.end()) { + services_[path] = std::make_unique( + getProxy().getConnection(), path); + } + } +} + +ConnmanManagerClient::~ConnmanManagerClient() { + unregisterProxy(); +} + +void ConnmanManagerClient::onPropertyChanged(const std::string& name, + const sdbus::Variant& value) { + std::ostringstream os; + Utils::append_property(value, os); + LOG_INFO("Manager PropertyChanged: {} = {}", name, os.str()); +} + +void ConnmanManagerClient::onTechnologyAdded( + const sdbus::ObjectPath& path, + const std::map& properties) { + LOG_INFO("Technology added: {}", path); + if (technologies_.find(path) == technologies_.end()) { + technologies_[path] = std::make_unique( + getProxy().getConnection(), path); + } +} + +void ConnmanManagerClient::onTechnologyRemoved(const sdbus::ObjectPath& path) { + LOG_INFO("Technology removed: {}", path); + technologies_.erase(path); +} + +void ConnmanManagerClient::onServicesChanged( + const std::vector>>& + changed, + const std::vector& removed) { + for (const auto& service : changed) { + const auto& path = service.get<0>(); + const auto& props = service.get<1>(); + + if (services_.find(path) == services_.end()) { + LOG_INFO("Service added: {}", path); + services_[path] = std::make_unique( + getProxy().getConnection(), path); + } + + // Log WiFi info only when Type is explicitly "wifi" in this delta + bool is_wifi = false; + if (auto it = props.find("Type"); + it != props.end() && it->second.containsValueOfType()) { + is_wifi = (it->second.get() == "wifi"); + } + + if (is_wifi) { + std::string ssid = "Unknown"; + if (auto it = props.find("Name"); + it != props.end() && it->second.containsValueOfType()) { + ssid = it->second.get(); + } + LOG_INFO(" WiFi Update: {} [{}]", ssid, path); + } + } + + for (const auto& path : removed) { + LOG_INFO(" Service removed: {}", path); + services_.erase(path); + } +} + +void ConnmanManagerClient::onPeersChanged( + const std::vector>>&, + const std::vector&) {} + +void ConnmanManagerClient::onTetheringClientsChanged( + const std::vector&, + const std::vector&) {} + +// +// ConnmanTechnologyClient +// + +ConnmanTechnologyClient::ConnmanTechnologyClient(sdbus::IConnection& connection, + sdbus::ObjectPath path) + : ProxyInterfaces(connection, + sdbus::ServiceName{SERVICE_NAME}, + std::move(path)) { + registerProxy(); +} + +ConnmanTechnologyClient::~ConnmanTechnologyClient() { + unregisterProxy(); +} + +void ConnmanTechnologyClient::onPropertyChanged(const std::string& name, + const sdbus::Variant& value) { + if (name == "Scanning" && value.containsValueOfType()) { + bool scanning = value.get(); + LOG_INFO("Technology [{}] WiFi Scan: {}", getProxy().getObjectPath(), + scanning ? "STARTED" : "COMPLETED"); + } + + std::ostringstream os; + Utils::append_property(value, os); + LOG_INFO("Technology [{}] PropertyChanged: {} = {}", + getProxy().getObjectPath(), name, os.str()); +} + +// +// ConnmanServiceClient +// + +ConnmanServiceClient::ConnmanServiceClient(sdbus::IConnection& connection, + sdbus::ObjectPath path) + : ProxyInterfaces(connection, + sdbus::ServiceName{SERVICE_NAME}, + std::move(path)) { + registerProxy(); +} + +ConnmanServiceClient::~ConnmanServiceClient() { + unregisterProxy(); +} + +void ConnmanServiceClient::onPropertyChanged(const std::string& name, + const sdbus::Variant& value) { + std::ostringstream os; + Utils::append_property(value, os); + LOG_INFO("Service [{}] PropertyChanged: {} = {}", getProxy().getObjectPath(), + name, os.str()); +} diff --git a/src/connman/connman_client.h b/src/connman/connman_client.h new file mode 100644 index 0000000..0770e7c --- /dev/null +++ b/src/connman/connman_client.h @@ -0,0 +1,95 @@ +// Copyright (c) 2026 Jian De +// +// 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 SRC_CONNMAN_CLIENT_H +#define SRC_CONNMAN_CLIENT_H + +#include +#include +#include +#include + +#include "../proxy/net/connman/Manager/manager_proxy.h" +#include "../proxy/net/connman/Service/service_proxy.h" +#include "../proxy/net/connman/Technology/technology_proxy.h" + +class ConnmanTechnologyClient; +class ConnmanServiceClient; + +class ConnmanManagerClient final + : public sdbus::ProxyInterfaces { + public: + static constexpr auto SERVICE_NAME = "net.connman"; + static constexpr auto OBJECT_PATH = "/"; + + explicit ConnmanManagerClient(sdbus::IConnection& connection); + ~ConnmanManagerClient(); + + void onPropertyChanged(const std::string& name, + const sdbus::Variant& value) override; + + void onTechnologyAdded( + const sdbus::ObjectPath& path, + const std::map& properties) override; + + void onTechnologyRemoved(const sdbus::ObjectPath& path) override; + + void onServicesChanged( + const std::vector>>& + changed, + const std::vector& removed) override; + + void onPeersChanged( + const std::vector>>& + changed, + const std::vector& removed) override; + + void onTetheringClientsChanged( + const std::vector& registered, + const std::vector& removed) override; + + private: + std::map> + technologies_; + std::map> services_; +}; + +class ConnmanTechnologyClient final + : public sdbus::ProxyInterfaces { + public: + static constexpr auto SERVICE_NAME = "net.connman"; + + ConnmanTechnologyClient(sdbus::IConnection& connection, + sdbus::ObjectPath path); + ~ConnmanTechnologyClient(); + + void onPropertyChanged(const std::string& name, + const sdbus::Variant& value) override; +}; + +class ConnmanServiceClient final + : public sdbus::ProxyInterfaces { + public: + static constexpr auto SERVICE_NAME = "net.connman"; + + ConnmanServiceClient(sdbus::IConnection& connection, sdbus::ObjectPath path); + ~ConnmanServiceClient(); + + void onPropertyChanged(const std::string& name, + const sdbus::Variant& value) override; +}; + +#endif // SRC_CONNMAN_CLIENT_H \ No newline at end of file diff --git a/src/connman/main.cc b/src/connman/main.cc new file mode 100644 index 0000000..63276d5 --- /dev/null +++ b/src/connman/main.cc @@ -0,0 +1,48 @@ +// Copyright (c) 2026 Jian De +// +// 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 "../utils/signal_handler.h" +#include "connman_client.h" + +int main() { + try { + logging_config::initializeLogging("connman_client"); + installSignalHandlers(); + + const auto connection = sdbus::createSystemBusConnection(); + connection->enterEventLoopAsync(); + + ConnmanManagerClient client(*connection); + + LOG_INFO("ConnMan monitor daemon running - Press Ctrl+C to exit"); + + auto result = monitorLoop(*connection); + + if (result) { + LOG_ERROR("Exiting due to: {}", *result); + } else { + LOG_INFO("Shutting down..."); + } + + connection->leaveEventLoop(); + return result ? 1 : 0; + + } catch (const sdbus::Error& e) { + LOG_ERROR("D-Bus error: {} - {}", e.getName(), e.getMessage()); + return 1; + } catch (const std::exception& e) { + LOG_ERROR("Exception: {}", e.what()); + return 1; + } +} \ No newline at end of file From 088f3a1a690ce70278f48c389a76b11ffe899047 Mon Sep 17 00:00:00 2001 From: Joel Winarske Date: Wed, 18 Mar 2026 10:40:12 -0700 Subject: [PATCH 2/4] Use established pattern of including the subdirectory + filename Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/connman/connman_client.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/connman/connman_client.h b/src/connman/connman_client.h index 0770e7c..2b39d5f 100644 --- a/src/connman/connman_client.h +++ b/src/connman/connman_client.h @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef SRC_CONNMAN_CLIENT_H -#define SRC_CONNMAN_CLIENT_H +#ifndef SRC_CONNMAN_CONNMAN_CLIENT_H +#define SRC_CONNMAN_CONNMAN_CLIENT_H #include #include @@ -92,4 +92,4 @@ class ConnmanServiceClient final const sdbus::Variant& value) override; }; -#endif // SRC_CONNMAN_CLIENT_H \ No newline at end of file +#endif // SRC_CONNMAN_CONNMAN_CLIENT_H \ No newline at end of file From 4f724e671db586cc3182cc216b9af5da1d9714e3 Mon Sep 17 00:00:00 2001 From: Joel Winarske Date: Thu, 19 Mar 2026 22:50:30 -0700 Subject: [PATCH 3/4] connman reliability fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Constructor exception leak — wrapped post-registerProxy() D-Bus calls in try-catch that calls unregisterProxy() on failure - Unguarded callbacks — added try-catch to all 6 onPropertyChanged/onTechnologyAdded/onTechnologyRemoved/onServicesChanged callbacks - No reconnection — added retry loop with exponential backoff (1s → 30s cap) in main.cc - Unused parameter Signed-off-by: Joel Winarske --- src/connman/connman_client.cc | 144 ++++++++++++++++++++-------------- src/connman/main.cc | 66 +++++++++++----- 2 files changed, 133 insertions(+), 77 deletions(-) diff --git a/src/connman/connman_client.cc b/src/connman/connman_client.cc index 2dc2c7a..ce4269f 100644 --- a/src/connman/connman_client.cc +++ b/src/connman/connman_client.cc @@ -29,18 +29,23 @@ ConnmanManagerClient::ConnmanManagerClient(sdbus::IConnection& connection) sdbus::ObjectPath{OBJECT_PATH}) { registerProxy(); - // Load initial technologies - for (const auto& tech : GetTechnologies()) { - onTechnologyAdded(tech.get<0>(), tech.get<1>()); - } + try { + // Load initial technologies + for (const auto& tech : GetTechnologies()) { + onTechnologyAdded(tech.get<0>(), tech.get<1>()); + } - // Load initial services - for (const auto& service : GetServices()) { - const auto& path = service.get<0>(); - if (services_.find(path) == services_.end()) { - services_[path] = std::make_unique( - getProxy().getConnection(), path); + // Load initial services + for (const auto& service : GetServices()) { + const auto& path = service.get<0>(); + if (services_.find(path) == services_.end()) { + services_[path] = std::make_unique( + getProxy().getConnection(), path); + } } + } catch (...) { + unregisterProxy(); + throw; } } @@ -50,24 +55,37 @@ ConnmanManagerClient::~ConnmanManagerClient() { void ConnmanManagerClient::onPropertyChanged(const std::string& name, const sdbus::Variant& value) { - std::ostringstream os; - Utils::append_property(value, os); - LOG_INFO("Manager PropertyChanged: {} = {}", name, os.str()); + try { + std::ostringstream os; + Utils::append_property(value, os); + LOG_INFO("Manager PropertyChanged: {} = {}", name, os.str()); + } catch (const std::exception& e) { + LOG_ERROR("Exception in Manager onPropertyChanged: {}", e.what()); + } } void ConnmanManagerClient::onTechnologyAdded( const sdbus::ObjectPath& path, const std::map& properties) { - LOG_INFO("Technology added: {}", path); - if (technologies_.find(path) == technologies_.end()) { - technologies_[path] = std::make_unique( - getProxy().getConnection(), path); + (void)properties; + try { + LOG_INFO("Technology added: {}", path); + if (technologies_.find(path) == technologies_.end()) { + technologies_[path] = std::make_unique( + getProxy().getConnection(), path); + } + } catch (const std::exception& e) { + LOG_ERROR("Exception in onTechnologyAdded: {}", e.what()); } } void ConnmanManagerClient::onTechnologyRemoved(const sdbus::ObjectPath& path) { - LOG_INFO("Technology removed: {}", path); - technologies_.erase(path); + try { + LOG_INFO("Technology removed: {}", path); + technologies_.erase(path); + } catch (const std::exception& e) { + LOG_ERROR("Exception in onTechnologyRemoved: {}", e.what()); + } } void ConnmanManagerClient::onServicesChanged( @@ -75,36 +93,40 @@ void ConnmanManagerClient::onServicesChanged( std::map>>& changed, const std::vector& removed) { - for (const auto& service : changed) { - const auto& path = service.get<0>(); - const auto& props = service.get<1>(); - - if (services_.find(path) == services_.end()) { - LOG_INFO("Service added: {}", path); - services_[path] = std::make_unique( - getProxy().getConnection(), path); - } - - // Log WiFi info only when Type is explicitly "wifi" in this delta - bool is_wifi = false; - if (auto it = props.find("Type"); - it != props.end() && it->second.containsValueOfType()) { - is_wifi = (it->second.get() == "wifi"); - } + try { + for (const auto& service : changed) { + const auto& path = service.get<0>(); + const auto& props = service.get<1>(); + + if (services_.find(path) == services_.end()) { + LOG_INFO("Service added: {}", path); + services_[path] = std::make_unique( + getProxy().getConnection(), path); + } - if (is_wifi) { - std::string ssid = "Unknown"; - if (auto it = props.find("Name"); + // Log WiFi info only when Type is explicitly "wifi" in this delta + bool is_wifi = false; + if (auto it = props.find("Type"); it != props.end() && it->second.containsValueOfType()) { - ssid = it->second.get(); + is_wifi = (it->second.get() == "wifi"); + } + + if (is_wifi) { + std::string ssid = "Unknown"; + if (auto it = props.find("Name"); it != props.end() && + it->second.containsValueOfType()) { + ssid = it->second.get(); + } + LOG_INFO(" WiFi Update: {} [{}]", ssid, path); } - LOG_INFO(" WiFi Update: {} [{}]", ssid, path); } - } - for (const auto& path : removed) { - LOG_INFO(" Service removed: {}", path); - services_.erase(path); + for (const auto& path : removed) { + LOG_INFO(" Service removed: {}", path); + services_.erase(path); + } + } catch (const std::exception& e) { + LOG_ERROR("Exception in onServicesChanged: {}", e.what()); } } @@ -135,16 +157,20 @@ ConnmanTechnologyClient::~ConnmanTechnologyClient() { void ConnmanTechnologyClient::onPropertyChanged(const std::string& name, const sdbus::Variant& value) { - if (name == "Scanning" && value.containsValueOfType()) { - bool scanning = value.get(); - LOG_INFO("Technology [{}] WiFi Scan: {}", getProxy().getObjectPath(), - scanning ? "STARTED" : "COMPLETED"); - } + try { + if (name == "Scanning" && value.containsValueOfType()) { + bool scanning = value.get(); + LOG_INFO("Technology [{}] WiFi Scan: {}", getProxy().getObjectPath(), + scanning ? "STARTED" : "COMPLETED"); + } - std::ostringstream os; - Utils::append_property(value, os); - LOG_INFO("Technology [{}] PropertyChanged: {} = {}", - getProxy().getObjectPath(), name, os.str()); + std::ostringstream os; + Utils::append_property(value, os); + LOG_INFO("Technology [{}] PropertyChanged: {} = {}", + getProxy().getObjectPath(), name, os.str()); + } catch (const std::exception& e) { + LOG_ERROR("Exception in Technology onPropertyChanged: {}", e.what()); + } } // @@ -165,8 +191,12 @@ ConnmanServiceClient::~ConnmanServiceClient() { void ConnmanServiceClient::onPropertyChanged(const std::string& name, const sdbus::Variant& value) { - std::ostringstream os; - Utils::append_property(value, os); - LOG_INFO("Service [{}] PropertyChanged: {} = {}", getProxy().getObjectPath(), - name, os.str()); + try { + std::ostringstream os; + Utils::append_property(value, os); + LOG_INFO("Service [{}] PropertyChanged: {} = {}", + getProxy().getObjectPath(), name, os.str()); + } catch (const std::exception& e) { + LOG_ERROR("Exception in Service onPropertyChanged: {}", e.what()); + } } diff --git a/src/connman/main.cc b/src/connman/main.cc index 63276d5..688d20d 100644 --- a/src/connman/main.cc +++ b/src/connman/main.cc @@ -15,34 +15,60 @@ #include "../utils/signal_handler.h" #include "connman_client.h" +inline constexpr auto kInitialReconnectDelay = std::chrono::seconds(1); +inline constexpr auto kMaxReconnectDelay = std::chrono::seconds(30); + int main() { - try { - logging_config::initializeLogging("connman_client"); - installSignalHandlers(); + logging_config::initializeLogging("connman_client"); + installSignalHandlers(); + + auto reconnect_delay = kInitialReconnectDelay; + + while (g_running) { + try { + auto connection = sdbus::createSystemBusConnection(); + connection->enterEventLoopAsync(); + + { + ConnmanManagerClient client(*connection); - const auto connection = sdbus::createSystemBusConnection(); - connection->enterEventLoopAsync(); + LOG_INFO("ConnMan monitor running - Press Ctrl+C to exit"); + reconnect_delay = kInitialReconnectDelay; - ConnmanManagerClient client(*connection); + auto result = monitorLoop(*connection); - LOG_INFO("ConnMan monitor daemon running - Press Ctrl+C to exit"); + if (result) { + LOG_ERROR("Monitor loop: {}", *result); + } else { + LOG_INFO("Shutting down..."); + } + } // client destroyed before leaving event loop - auto result = monitorLoop(*connection); + connection->leaveEventLoop(); - if (result) { - LOG_ERROR("Exiting due to: {}", *result); - } else { - LOG_INFO("Shutting down..."); + if (!g_running) { + return 0; + } + + } catch (const sdbus::Error& e) { + LOG_ERROR("D-Bus error: {} - {}", e.getName(), e.getMessage()); + } catch (const std::exception& e) { + LOG_ERROR("Exception: {}", e.what()); } - connection->leaveEventLoop(); - return result ? 1 : 0; + if (!g_running) { + break; + } - } catch (const sdbus::Error& e) { - LOG_ERROR("D-Bus error: {} - {}", e.getName(), e.getMessage()); - return 1; - } catch (const std::exception& e) { - LOG_ERROR("Exception: {}", e.what()); - return 1; + LOG_INFO("Reconnecting in {} seconds...", + std::chrono::duration_cast(reconnect_delay) + .count()); + std::this_thread::sleep_for(reconnect_delay); + reconnect_delay = + std::min(reconnect_delay * 2, + std::chrono::duration_cast( + kMaxReconnectDelay)); } + + return 1; } \ No newline at end of file From 4062e2bf1302bf0365572d6f58d5c2f0474d6d54 Mon Sep 17 00:00:00 2001 From: Joel Winarske Date: Thu, 19 Mar 2026 23:02:01 -0700 Subject: [PATCH 4/4] clang-format fixes Signed-off-by: Joel Winarske --- src/connman/connman_client.cc | 5 +++-- src/connman/main.cc | 7 +++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/connman/connman_client.cc b/src/connman/connman_client.cc index ce4269f..f8830f6 100644 --- a/src/connman/connman_client.cc +++ b/src/connman/connman_client.cc @@ -113,8 +113,9 @@ void ConnmanManagerClient::onServicesChanged( if (is_wifi) { std::string ssid = "Unknown"; - if (auto it = props.find("Name"); it != props.end() && - it->second.containsValueOfType()) { + if (auto it = props.find("Name"); + it != props.end() && + it->second.containsValueOfType()) { ssid = it->second.get(); } LOG_INFO(" WiFi Update: {} [{}]", ssid, path); diff --git a/src/connman/main.cc b/src/connman/main.cc index 688d20d..86238c4 100644 --- a/src/connman/main.cc +++ b/src/connman/main.cc @@ -64,10 +64,9 @@ int main() { std::chrono::duration_cast(reconnect_delay) .count()); std::this_thread::sleep_for(reconnect_delay); - reconnect_delay = - std::min(reconnect_delay * 2, - std::chrono::duration_cast( - kMaxReconnectDelay)); + reconnect_delay = std::min( + reconnect_delay * 2, + std::chrono::duration_cast(kMaxReconnectDelay)); } return 1;