diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 00000000..1aa7ef79 Binary files /dev/null and b/.DS_Store differ diff --git a/CMakeLists.txt b/CMakeLists.txt index 0b73f231..768ab64c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,6 +54,7 @@ get_directory_property(SEVICES_DEFINES COMPILE_DEFINITIONS) option(ENABLE_LEGACY_PLUGINS "Enable Legacy Plugins" ON) option(USE_RDK_LOGGER "Enable RDK Logger for logging" OFF ) option(ENABLE_UNIT_TESTING "Enable unit tests" OFF) +option(USE_TELEMETRY "Enable Telemetry support for NetworkConnectionStats" ON) add_subdirectory(interface) @@ -64,6 +65,8 @@ if (ENABLE_LEGACY_PLUGINS) add_subdirectory(legacy) endif (ENABLE_LEGACY_PLUGINS) +add_subdirectory(networkstats) + add_subdirectory(tools) if(ENABLE_UNIT_TESTING) diff --git a/networkstats/CMakeLists.txt b/networkstats/CMakeLists.txt new file mode 100644 index 00000000..1c6329dc --- /dev/null +++ b/networkstats/CMakeLists.txt @@ -0,0 +1,37 @@ +############################################################################# +# If not stated otherwise in this file or this component's LICENSE file the +# following copyright and licenses apply: +# +# Copyright 2025 RDK Management +# +# 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. +############################################################################# +cmake_minimum_required(VERSION 3.3) + +project(NetworkConnectionStats) + +find_package(WPEFramework QUIET CONFIG) + +message("Building ${PROJECT_NAME} Thunder Plugin (Internal-only, no external APIs)") + +# Set project version +set(PROJECT_VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}) + +# Build interface (ProxyStub generation) +add_subdirectory(interface) + +# Build definition (JSON-RPC documentation) +#add_subdirectory(definition) + +# Build plugin +add_subdirectory(plugin) diff --git a/networkstats/NetworkStatsDesign_ver1.md b/networkstats/NetworkStatsDesign_ver1.md new file mode 100644 index 00000000..564bab73 --- /dev/null +++ b/networkstats/NetworkStatsDesign_ver1.md @@ -0,0 +1,455 @@ +# NetworkStats Module Design Document + +## Overview +NetworkStats is a Thunder plugin that runs out-of-process and monitors network connectivity by subscribing to NetworkManager plugin events. It periodically collects network statistics and sends T2 telemetry events. + +## Architecture + +### Key Components +- **NetworkStats Plugin**: Main Thunder plugin (out-of-process) +- **NetworkManager Interface**: COM-RPC connection to NetworkManager plugin +- **Timer Thread**: Periodic statistics collection (10-minute interval) +- **T2 Telemetry Client**: Sends telemetry events +- **Event Subscription Manager**: Manages NetworkManager event subscriptions + +--- + +## Sequence Diagram + +```mermaid +sequenceDiagram + participant Thunder as Thunder Framework + participant NS as NetworkStats Plugin + participant NM as NetworkManager Plugin + participant Timer as Timer Thread + participant T2 as T2 Telemetry + + Note over Thunder,T2: Plugin Initialization + Thunder->>NS: Initialize(IShell* service) + activate NS + NS->>NS: Create NetworkStatsImplementation + NS->>Thunder: Register notification callbacks + NS->>NM: Establish COM-RPC connection + activate NM + NM-->>NS: Connection established + NS->>NM: Register(¬ification) + NS->>NM: Subscribe to events
(onInterfaceStateChange,
onIPAddressStatusChanged,
onInternetStatusChange) + NM-->>NS: Subscription confirmed + + Note over NS,Timer: Timer Thread Creation + NS->>Timer: Create timer thread
(10 min interval) + activate Timer + Timer->>Timer: Initialize in stopped state + NS-->>Thunder: Initialization complete + deactivate NS + + Note over NM,Timer: Network Connection Event + NM->>NS: onInterfaceStateChange(INTERFACE_CONNECTED) + activate NS + NS->>NS: Process connection event + NS->>Timer: Start timer thread + Timer->>Timer: Begin 10-min countdown + deactivate NS + + Note over Timer,T2: Periodic Statistics Collection + loop Every 10 minutes + Timer->>Timer: Timer expired + Timer->>NS: Collect network statistics + activate NS + NS->>NM: GetIPSettings() + NM-->>NS: IP configuration + NS->>NM: GetActiveInterface() + NM-->>NS: Interface details + NS->>NS: Aggregate statistics + NS->>T2: Send telemetry event
(connection type, IP, DNS, etc.) + activate T2 + T2-->>NS: Event sent + deactivate T2 + Timer->>Timer: Restart 10-min timer + deactivate NS + end + + Note over NM,Timer: Network Disconnection Event + NM->>NS: onInterfaceStateChange(INTERFACE_DISCONNECTED) + activate NS + NS->>NS: Process disconnection event + NS->>Timer: Stop timer thread + Timer->>Timer: Halt countdown + deactivate NS + + Note over NM,Timer: Network Reconnection Event + NM->>NS: onInternetStatusChange(INTERNET_CONNECTED) + activate NS + NS->>NS: Process reconnection + NS->>Timer: Resume timer thread + Timer->>Timer: Resume 10-min countdown + deactivate NS + + Note over Thunder,T2: Plugin Deinitialization + Thunder->>NS: Deinitialize() + activate NS + NS->>Timer: Stop timer thread + deactivate Timer + Timer-->>NS: Thread stopped + NS->>NM: Unregister notifications + NS->>NM: Unsubscribe from events + NM-->>NS: Unsubscribed + NS->>NM: Release COM-RPC connection + deactivate NM + NS->>NS: Cleanup resources + NS-->>Thunder: Deinitialization complete + deactivate NS +``` + +--- + +## Flowchart + +```mermaid +flowchart TD + Start([Thunder Starts NetworkStats Plugin]) --> Init[Initialize Plugin] + Init --> CreateImpl[Create NetworkStatsImplementation
Out-of-Process] + CreateImpl --> RegNotif[Register Notification Callbacks] + RegNotif --> EstablishComRPC[Establish COM-RPC Connection
to NetworkManager] + + EstablishComRPC --> CheckConn{Connection
Successful?} + CheckConn -->|No| LogError[Log Error & Return Failure] + CheckConn -->|Yes| Subscribe[Subscribe to NetworkManager Events:
- onInterfaceStateChange
- onIPAddressStatusChanged
- onInternetStatusChange] + + Subscribe --> CreateTimer[Create Timer Thread
10-min interval, stopped] + CreateTimer --> WaitEvent[Wait for Network Events] + + WaitEvent --> EventReceived{Event
Received?} + + EventReceived -->|Interface Connected| StartTimer[Start Timer Thread] + EventReceived -->|Interface Disconnected| StopTimer[Stop Timer Thread] + EventReceived -->|Internet Connected| ResumeTimer[Resume Timer Thread] + EventReceived -->|Internet Disconnected| PauseTimer[Pause Timer Thread] + + StartTimer --> TimerActive[Timer Thread Active] + ResumeTimer --> TimerActive + + TimerActive --> TimerLoop{Timer
Expired?} + TimerLoop -->|No| TimerActive + TimerLoop -->|Yes| CollectStats[Collect Network Statistics] + + CollectStats --> GetIPSettings[Call NetworkManager:
GetIPSettings] + GetIPSettings --> GetInterface[Call NetworkManager:
GetActiveInterface] + GetInterface --> GetDNS[Call NetworkManager:
GetNameServers] + GetDNS --> AggregateData[Aggregate Statistics Data] + + AggregateData --> SendT2[Send T2 Telemetry Event:
- Connection Type
- IP Addresses
- DNS Entries
- Interface Status] + SendT2 --> LogTelemetry[Log Telemetry Event] + LogTelemetry --> RestartTimer[Restart 10-min Timer] + RestartTimer --> TimerActive + + StopTimer --> TimerStopped[Timer Thread Stopped] + PauseTimer --> TimerStopped + TimerStopped --> WaitEvent + + EventReceived -->|Deinitialize| Shutdown[Thunder Shutdown Signal] + Shutdown --> StopTimerThread[Stop Timer Thread] + StopTimerThread --> UnsubEvents[Unsubscribe from All Events] + UnsubEvents --> UnregNotif[Unregister Notifications] + UnregNotif --> ReleaseComRPC[Release COM-RPC Connection] + ReleaseComRPC --> Cleanup[Cleanup Resources] + Cleanup --> End([Plugin Deinitialized]) + + LogError --> End + + style Start fill:#90EE90 + style End fill:#FFB6C1 + style EstablishComRPC fill:#FFD700 + style Subscribe fill:#FFD700 + style CollectStats fill:#87CEEB + style SendT2 fill:#DDA0DD + style Shutdown fill:#FFA07A +``` + +--- + +## Component Details + +### 1. NetworkStats Plugin Structure + +```cpp +namespace WPEFramework { + namespace Plugin { + class NetworkStats : public PluginHost::IPlugin, public PluginHost::JSONRPC { + class Notification : public RPC::IRemoteConnection::INotification, + public Exchange::INetworkManager::INotification { + // Event handlers for NetworkManager events + void onInterfaceStateChange(const Exchange::INetworkManager::InterfaceState state, + const string interface) override; + void onIPAddressStatusChanged(const string& interface, const string& ipAddress, + const Exchange::INetworkManager::IPStatus status) override; + void onInternetStatusChange(const Exchange::INetworkManager::InternetStatus prevState, + const Exchange::INetworkManager::InternetStatus currState) override; + // ... other event handlers + }; + + private: + uint32_t _connectionId; + PluginHost::IShell* _service; + Exchange::INetworkManager* _networkManager; + Core::Sink _notification; + + // Timer management + std::thread _statsCollectionThread; + std::atomic _timerRunning; + std::mutex _timerMutex; + std::condition_variable _timerCondVar; + }; + } +} +``` + +### 2. Timer Thread State Machine + +``` +States: +┌─────────┐ Start Event ┌─────────┐ +│ STOPPED │ ───────────────► │ RUNNING │ +└─────────┘ └─────────┘ + ▲ │ + │ │ + │ Stop/Disconnect │ Pause Event + │ Event │ + │ ▼ + │ ┌────────┐ + └────────────────────────│ PAUSED │ + Resume Event └────────┘ +``` + +### 3. Statistics Collection Flow + +**Data Collected Every 10 Minutes:** +- Active network interface (eth0/wlan0) +- Connection type (Ethernet/WiFi) +- IPv4 address and subnet +- IPv6 address (if available) +- DNS server entries (primary/secondary) +- Default gateway +- Interface state (UP/DOWN) +- Internet connectivity status + +**T2 Telemetry Event Structure:** +```json +{ + "timestamp": "2025-01-23T10:30:00Z", + "interface": "eth0", + "connectionType": "ETHERNET", + "ipv4Address": "192.168.1.100", + "ipv6Address": "fe80::1234:5678:90ab:cdef", + "dnsServers": ["8.8.8.8", "8.8.4.4"], + "gateway": "192.168.1.1", + "internetStatus": "CONNECTED", + "signalQuality": "EXCELLENT" +} +``` + +--- + +## Implementation Classes + +### NetworkStatsImplementation.cpp +- **Purpose**: Out-of-process implementation +- **Key Methods**: + - `Configure()`: Parses config, initializes COM-RPC connection + - `StartStatisticsCollection()`: Starts timer thread + - `StopStatisticsCollection()`: Stops timer thread + - `CollectAndSendStatistics()`: Gathers data and sends T2 events + - `OnNetworkEvent()`: Handles NetworkManager notifications + +### NetworkStatsTimer.cpp +- **Purpose**: Manages periodic statistics collection +- **Key Methods**: + - `Start()`: Begin 10-minute countdown + - `Stop()`: Halt timer thread + - `Pause()`: Suspend timer without destroying thread + - `Resume()`: Continue from paused state + - `OnTimerExpired()`: Callback for statistics collection + +### T2TelemetryClient.cpp +- **Purpose**: Interface with T2 telemetry system +- **Key Methods**: + - `SendEvent()`: Sends JSON telemetry data + - `FormatStatistics()`: Converts statistics to T2 format + +--- + +## Configuration + +### NetworkStats.config +```json +{ + "locator": "libWPEFrameworkNetworkStats.so", + "classname": "NetworkStats", + "callsign": "org.rdk.NetworkStats", + "autostart": true, + "configuration": { + "root": { + "outofprocess": true, + "locator": "libWPEFrameworkNetworkStatsImpl.so" + }, + "collectionInterval": 600, + "networkManagerCallsign": "org.rdk.NetworkManager", + "enableT2Telemetry": true, + "telemetryMarker": "NETWORK_STATS" + } +} +``` + +--- + +## Event Subscription Logic + +### Subscribed Events +| Event | Action | +|-------|--------| +| `onInterfaceStateChange(CONNECTED)` | Start timer thread | +| `onInterfaceStateChange(DISCONNECTED)` | Stop timer thread | +| `onInternetStatusChange(CONNECTED)` | Resume timer thread | +| `onInternetStatusChange(DISCONNECTED)` | Pause timer thread | +| `onIPAddressStatusChanged` | Log IP change, continue timer | + +--- + +## Error Handling + +1. **COM-RPC Connection Failure**: + - Retry connection up to 5 times with exponential backoff + - Log fatal error if all retries fail + - Return initialization failure to Thunder + +2. **Timer Thread Crash**: + - Log exception details + - Attempt to restart timer thread once + - Notify Thunder of degraded operation + +3. **T2 Telemetry Failure**: + - Log warning (non-fatal) + - Cache statistics for next successful transmission + - Continue timer operation + +--- + +## Threading Model + +``` +Thunder Main Process +│ +└─► NetworkStats Plugin (Main Thread) + │ + ├─► COM-RPC Communication Thread (Thunder managed) + │ + └─► Out-of-Process Instance + │ + ├─► Event Notification Thread (Thunder managed) + │ + └─► Statistics Collection Timer Thread (Custom) + │ + └─► T2 Telemetry Thread (System) +``` + +--- + +## Shutdown Sequence + +1. Thunder sends `Deinitialize()` signal +2. NetworkStats sets `_timerRunning = false` +3. Notify timer condition variable to wake thread +4. Join timer thread (wait for completion) +5. Unregister from NetworkManager notifications +6. Call `_networkManager->Unregister(&_notification)` +7. Release COM-RPC interface: `_networkManager->Release()` +8. Cleanup Thunder connection: `_service->Release()` +9. Return control to Thunder + +--- + +## Testing Strategy + +### L1 Unit Tests (`tests/l1Test/networkstats_l1_test.cpp`) +- Timer thread start/stop/pause/resume logic +- Statistics data aggregation +- T2 telemetry formatting + +### L2 Integration Tests (`tests/l2Test/networkstats_l2_test.cpp`) +- Mock NetworkManager COM-RPC interface +- Simulate network events (connect/disconnect) +- Verify timer behavior with event triggers +- Validate T2 event payload structure + +--- + +## Dependencies + +- **Thunder Framework**: COM-RPC infrastructure +- **NetworkManager Plugin**: Network state provider +- **T2 Library**: Telemetry event transmission +- **libnm/GDBus** (indirect): Via NetworkManager backend + +--- + +## Build Integration + +### CMakeLists.txt +```cmake +add_library(NetworkStats SHARED + plugin/networkstats/NetworkStats.cpp + plugin/networkstats/NetworkStatsImplementation.cpp + plugin/networkstats/NetworkStatsTimer.cpp + plugin/networkstats/T2TelemetryClient.cpp +) + +target_link_libraries(NetworkStats + ${NAMESPACE}Plugins::${NAMESPACE}Plugins + ${NAMESPACE}Definitions::${NAMESPACE}Definitions + NetworkManagerInterfaces # For INetworkManager + t2log # T2 Telemetry +) + +install(TARGETS NetworkStats DESTINATION lib/${NAMESPACE}/plugins) +install(FILES NetworkStats.config DESTINATION ${CMAKE_INSTALL_PREFIX}/etc/${NAMESPACE}/plugins) +``` + +--- + +## Logging + +Use `NetworkManagerLogger.h` macros: + +```cpp +NMLOG_INFO("NetworkStats timer started (interval: %d sec)", interval); +NMLOG_WARNING("Failed to collect statistics, retry scheduled"); +NMLOG_ERROR("COM-RPC connection to NetworkManager lost"); +NMLOG_DEBUG("Telemetry event sent: %s", jsonData.c_str()); +``` + +--- + +## Future Enhancements + +1. **Configurable Collection Interval**: Allow runtime adjustment via JSON-RPC +2. **Historical Data Storage**: Cache last N statistics for trend analysis +3. **Bandwidth Monitoring**: Track data usage per interface +4. **Alert Thresholds**: Trigger events on connection quality degradation +5. **Multi-Interface Support**: Collect statistics from all interfaces simultaneously + +--- + +## References + +- NetworkManager Plugin Documentation +- [Thunder Plugin Architecture](https://github.com/WebPlatformForEmbedded/Thunder) +- COM-RPC Interface Guide +- [T2 Telemetry Specification](https://github.com/rdkcentral/rdk-telemetry) + +--- + +## Revision History + +| Version | Date | Author | Changes | +|---------|------|--------|---------| +| 1.0 | 2025-12-17 | Initial Design | Created sequence diagram and flowchart | diff --git a/networkstats/README_INTERNAL_PLUGIN.md b/networkstats/README_INTERNAL_PLUGIN.md new file mode 100644 index 00000000..be6cce23 --- /dev/null +++ b/networkstats/README_INTERNAL_PLUGIN.md @@ -0,0 +1,287 @@ +# NetworkConnectionStats - Internal Thunder Plugin (No External APIs) + +## Design Philosophy + +This plugin is designed to run **internally within Thunder framework** without exposing any external APIs. It automatically performs network diagnostics and reports telemetry data in the background. + +## Architecture + +``` +┌─────────────────────────────────────────┐ +│ Thunder/WPEFramework Process │ +│ ┌───────────────────────────────────┐ │ +│ │ NetworkConnectionStats │ │ +│ │ (Plugin - IPlugin only) │ │ +│ │ - Auto-activates │ │ +│ │ - No JSONRPC APIs │ │ +│ │ - Spawns out-of-process │ │ +│ └──────────┬────────────────────────┘ │ +└─────────────┼───────────────────────────┘ + │ COM-RPC (internal) + ▼ +┌─────────────────────────────────────────┐ +│ Out-of-Process (Background Service) │ +│ ┌───────────────────────────────────┐ │ +│ │ NetworkConnectionStatsImpl │ │ +│ │ - Auto-starts periodic reporting │ │ +│ │ - Runs every N minutes (default10)│ │ +│ │ - Generates diagnostic reports │ │ +│ │ - Sends T2 telemetry events │ │ +│ │ - No external APIs exposed │ │ +│ └───────────────────────────────────┘ │ +└─────────────────────────────────────────┘ +``` + +## What This Plugin Does + +### On Activation +1. Thunder activates the plugin automatically (`startmode: "Activated"`) +2. Plugin spawns out-of-process implementation +3. Implementation initializes network provider +4. **Generates initial diagnostic report** +5. **Starts periodic reporting thread** (runs every N minutes) + +### Periodic Diagnostics (Every N Minutes) +The plugin automatically runs these checks: + +1. **Connection Type Check** + - Detects Ethernet vs WiFi + - Logs to telemetry: `Connection_Type` + +2. **IP Configuration Check** + - Gets interface name, IPv4/IPv6 addresses + - Gets IPv4/IPv6 gateways and DNS servers + - Logs to telemetry: `Network_Interface_Info`, `IPv4_Gateway_DNS`, `IPv6_Gateway_DNS` + +3. **IPv4 Route Check** + - Validates IPv4 gateway availability + - Traces warnings if no gateway found + +4. **IPv6 Route Check** + - Validates IPv6 gateway availability + - Traces warnings if no gateway found + +5. **Gateway Packet Loss Check** + - Pings IPv4 gateway (5 packets, 30s timeout) + - Pings IPv6 gateway (5 packets, 30s timeout) + - Measures packet loss percentage and average RTT + - Logs to telemetry: `Gateway_Ping_Stats` + +6. **DNS Configuration Check** + - Verifies DNS servers are configured + - Logs warnings if no DNS found + +### Telemetry Events Sent + +All events use the T2 telemetry framework: + +| Event Name | Data Format | Example | +|------------|-------------|---------| +| `Connection_Type` | String | `"Ethernet"` or `"WiFi"` | +| `Network_Interface_Info` | CSV | `"eth0,192.168.1.100,fe80::1"` | +| `IPv4_Gateway_DNS` | CSV | `"192.168.1.1,8.8.8.8"` | +| `IPv6_Gateway_DNS` | CSV | `"fe80::1,2001:4860:4860::8888"` | +| `Gateway_Ping_Stats` | CSV | `"IPv4,192.168.1.1,0.0,2.5"` (version,gateway,loss%,avgRTT) | +| `DNS_Status` | String | `"No DNS configured"` (only on error) | + +## Configuration + +### NetworkConnectionStats.config +```json +{ + "locator": "libWPEFrameworkNetworkConnectionStats.so", + "classname": "NetworkConnectionStats", + "startmode": "Activated", + "configuration": { + "outofprocess": true, + "root": { "mode": "Local" }, + "reportingInterval": 10, + "autoStart": true + } +} +``` + +### Configuration Parameters + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `reportingInterval` | number | 10 | Diagnostic interval in minutes | +| `autoStart` | boolean | true | Auto-start periodic reporting on plugin activation | +| `outofprocess` | boolean | true | Run implementation out-of-process (recommended) | + +## Files Structure + +### Core Plugin Files +- **Module.h/cpp** - Thunder module definition +- **INetworkConnectionStats.h** - Minimal COM-RPC interface (no methods) +- **NetworkConnectionStats.h** - Plugin class (IPlugin only) +- **NetworkConnectionStats.cpp** - Plugin implementation +- **NetworkConnectionStatsImplementation.h** - Out-of-process implementation header +- **NetworkConnectionStatsImplementation.cpp** - All diagnostic logic + +### Supporting Files +- **INetworkData.h** - Network data provider interface +- **ThunderJsonRPCProvider.h/cpp** - NetworkManager plugin client +- **NetworkConnectionStats.config** - Plugin configuration +- **NetworkConnectionStats.conf.in** - CMake config template +- **CMakeLists.txt** - Build system + +## No External APIs + +### What Was Removed +- ❌ JSON-RPC API methods +- ❌ External event notifications +- ❌ Public getter/setter methods +- ❌ NetworkConnectionStats.json API specification +- ❌ INotification interface + +### What Remains +- ✅ IPlugin interface (Initialize, Deinitialize, Information) +- ✅ IConfiguration interface (for plugin config) +- ✅ Internal diagnostic methods +- ✅ Telemetry logging +- ✅ Periodic reporting thread + +## How It Works + +### Startup Sequence +``` +1. Thunder starts +2. Thunder loads NetworkConnectionStats plugin +3. Plugin Initialize() called +4. Plugin spawns out-of-process NetworkConnectionStatsImplementation +5. Implementation Configure() called +6. NetworkJsonRPCProvider initialized +7. Initial diagnostic report generated immediately +8. Periodic reporting thread started (if autoStart=true) +9. Thread sleeps for 'reportingInterval' minutes +10. Thread wakes up, generates report +11. Repeat steps 9-10 until plugin deactivated +``` + +### Logging + +All diagnostic output goes to Thunder trace logs: +```bash +# View logs +journalctl -u wpeframework -f | grep NetworkConnectionStats + +# Example output: +INFO: NetworkConnectionStats Constructor +INFO: NetworkConnectionStatsImplementation::Configure +INFO: Periodic reporting started with 10 minute interval +INFO: Connection type: Ethernet +INFO: Interface: eth0, IPv4: 192.168.1.100, IPv6: fe80::1 +INFO: IPv4 Gateway: 192.168.1.1, DNS: 8.8.8.8 +INFO: Pinging IPv4 gateway: 192.168.1.1 +INFO: IPv4 gateway ping - Loss: 0.0%, RTT: 2.5ms +INFO: Network diagnostics report completed +INFO: Periodic report generated +``` + +## Building + +```bash +cmake -B build \ + -DPLUGIN_NETWORKCONNECTIONSTATS=ON \ + -DPLUGIN_NETWORKCONNECTIONSTATS_OUTOFPROCESS=ON \ + -DUSE_TELEMETRY=ON \ + -DPLUGIN_NETWORKCONNECTIONSTATS_REPORTING_INTERVAL=10 + +cmake --build build +sudo cmake --install build +``` + +## Deployment + +### Install Plugin +```bash +# Libraries installed to: +/usr/lib/wpeframework/plugins/libWPEFrameworkNetworkConnectionStats.so +/usr/lib/wpeframework/plugins/libWPEFrameworkNetworkConnectionStatsImplementation.so + +# Config installed to: +/etc/wpeframework/plugins/NetworkConnectionStats.json +``` + +### Thunder Auto-Activation +Plugin automatically activates on Thunder startup (no manual activation needed). + +## Monitoring + +### Check Plugin Status +```bash +curl http://localhost:9998/jsonrpc -d '{ + "jsonrpc": "2.0", + "id": 1, + "method": "Controller.1.status@NetworkConnectionStats" +}' +``` + +### Expected Response +```json +{ + "jsonrpc": "2.0", + "id": 1, + "result": [ + { + "callsign": "NetworkConnectionStats", + "locator": "libWPEFrameworkNetworkConnectionStats.so", + "classname": "NetworkConnectionStats", + "autostart": true, + "state": "activated" + } + ] +} +``` + +### View Telemetry Events +Telemetry events are sent via T2 framework and uploaded to backend systems. Check T2 logs: +```bash +journalctl -u t2 -f +``` + +## Advantages of Internal-Only Design + +1. **Simplicity** - No API surface to maintain or secure +2. **Background Operation** - Runs autonomously without external triggers +3. **Zero Configuration** - Works out-of-the-box with defaults +4. **Resource Efficient** - Periodic checks minimize CPU/network usage +5. **Telemetry Integration** - Data flows automatically to analytics backend +6. **Process Isolation** - Out-of-process prevents Thunder crashes +7. **Automatic Recovery** - Thunder restarts implementation if it crashes + +## Use Cases + +This plugin is ideal for: +- **Continuous network monitoring** - Track connection health 24/7 +- **Proactive diagnostics** - Detect issues before users report them +- **Analytics/BigData** - Feed network metrics to data pipelines +- **Fleet management** - Monitor thousands of devices remotely +- **SLA tracking** - Measure uptime and connectivity quality + +## Comparison: Standalone vs Thunder Plugin + +| Aspect | Standalone (Old) | Thunder Plugin (New) | +|--------|------------------|----------------------| +| **Execution** | Separate process with main() | Thunder-managed lifecycle | +| **Startup** | Manual launch | Auto-activated by Thunder | +| **APIs** | None | None (internal-only) | +| **Logging** | printf/fprintf | Thunder TRACE framework | +| **Telemetry** | T2 direct calls | T2 direct calls (same) | +| **Configuration** | Hardcoded | Thunder config system | +| **Monitoring** | None | Thunder Controller API | +| **Integration** | Isolated | Part of Thunder ecosystem | + +## Summary + +NetworkConnectionStats is now a **self-contained Thunder plugin** that: +- ✅ Runs automatically in the background +- ✅ Performs periodic network diagnostics (every N minutes) +- ✅ Sends telemetry data to T2 framework +- ✅ Requires zero external interaction +- ✅ Has no exposed APIs (internal-only) +- ✅ Integrates seamlessly with Thunder framework +- ✅ Provides continuous network health monitoring + +**Perfect for production deployments that need passive network monitoring without exposing attack surfaces!** diff --git a/networkstats/THUNDER_PLUGIN_CONVERSION.md b/networkstats/THUNDER_PLUGIN_CONVERSION.md new file mode 100644 index 00000000..9c011574 --- /dev/null +++ b/networkstats/THUNDER_PLUGIN_CONVERSION.md @@ -0,0 +1,247 @@ +# NetworkConnectionStats Thunder Plugin Conversion + +## Overview +Successfully transformed NetworkConnectionStats from a standalone application into a full-fledged WPEFramework/Thunder plugin module, following the Telemetry plugin architecture pattern. + +## Architecture Changes + +### 1. Thunder Plugin Framework Integration +The plugin now follows the standard Thunder plugin architecture with: +- **In-process plugin** (NetworkConnectionStats.cpp) - Runs in WPEFramework process +- **Out-of-process implementation** (NetworkConnectionStatsImplementation.cpp) - Runs as separate process for isolation and stability + +### 2. Key Components Created + +#### Module Definition +- **Module.h** - Defines plugin module name and includes Thunder headers +- **Module.cpp** - Module registration macro + +#### COM-RPC Interface +- **INetworkConnectionStats.h** - Defines the COM-RPC interface with: + - INetworkConnectionStats interface with all network diagnostic methods + - INotification sub-interface for event notifications + - Proper interface IDs and stubgen annotations + +#### Plugin Main Class +- **NetworkConnectionStats.h** - Plugin header with: + - Inherits from `PluginHost::IPlugin` and `PluginHost::JSONRPC` + - Notification handler for RPC connection lifecycle and events + - Interface aggregation for COM-RPC + +- **NetworkConnectionStats.cpp** - Plugin implementation with: + - IPlugin lifecycle methods (Initialize, Deinitialize, Information) + - RPC connection management + - JSON-RPC registration + - Notification forwarding + +#### Out-of-Process Implementation +- **NetworkConnectionStatsImplementation.h/.cpp** - Business logic implementation: + - All original diagnostic functionality preserved + - Implements INetworkConnectionStats and IConfiguration interfaces + - Notification management for event subscribers + - Periodic reporting thread with configurable interval + - Telemetry integration (T2) + - Network provider integration (NetworkJsonRPCProvider) + +#### API Specification +- **NetworkConnectionStats.json** - JSON-RPC API definition: + - Method definitions with parameters and return types + - Event definitions (onReportGenerated) + - Schema-compliant for Thunder code generation + +#### Configuration Files +- **NetworkConnectionStats.config** - Plugin configuration (JSON format) +- **NetworkConnectionStats.conf.in** - CMake template for configuration generation + +#### Build System +- **CMakeLists.txt** - Thunder-compliant build system: + - Interface library for COM-RPC definitions + - Plugin library (in-process) + - Implementation library (out-of-process) + - Proper dependency management + - Installation rules for plugins, headers, and API specs + +## API Methods Available + +### Diagnostic Methods +1. **generateReport()** - Generate full network diagnostics report +2. **getConnectionType()** - Get Ethernet/WiFi connection type +3. **getIpv4Address(interface)** - Get IPv4 address +4. **getIpv6Address(interface)** - Get IPv6 address +5. **getIpv4Gateway()** - Get IPv4 gateway +6. **getIpv6Gateway()** - Get IPv6 gateway +7. **getIpv4Dns()** - Get IPv4 DNS server +8. **getIpv6Dns()** - Get IPv6 DNS server +9. **getInterface()** - Get active network interface +10. **pingGateway(endpoint, ipversion, count, timeout)** - Ping gateway with stats + +### Configuration Methods +11. **setPeriodicReporting(enable)** - Enable/disable periodic reports +12. **setReportingInterval(intervalMinutes)** - Set reporting interval + +### Events +- **onReportGenerated** - Fired when report is generated + +## Key Features Preserved + +### Network Diagnostics +- Connection type detection (Ethernet/WiFi) +- IPv4/IPv6 address retrieval +- Gateway/route validation +- DNS configuration checks +- Packet loss monitoring +- RTT measurements + +### Telemetry Integration +- T2 telemetry events for network metrics +- Conditional compilation with USE_TELEMETRY flag +- Event logging for connection type, IP info, gateway stats + +### Periodic Reporting +- Configurable reporting interval (default 10 minutes) +- Background thread for periodic diagnostics +- Thread-safe start/stop control + +## Migration from Standalone to Plugin + +### What Was Removed +- `main()` function - No longer needed in plugin architecture +- Singleton pattern - Thunder manages plugin lifecycle +- Standalone initialization - Replaced with IPlugin::Initialize() + +### What Was Added +- Thunder plugin lifecycle (IPlugin interface) +- RPC connection management +- Notification system for events +- COM-RPC and JSON-RPC interfaces +- Configuration interface (IConfiguration) +- Proper Thunder logging (TRACE macros) + +### What Was Transformed +- Constructor/Destructor - Now plugin-aware +- Initialize/Deinitialize - Now IPlugin methods with PluginHost::IShell parameter +- Network provider - Integrated with out-of-process implementation +- Diagnostic methods - Exposed via COM-RPC interface + +## Build Configuration + +### CMake Options +```cmake +-DPLUGIN_NETWORKCONNECTIONSTATS=ON # Build the plugin +-DPLUGIN_NETWORKCONNECTIONSTATS_OUTOFPROCESS=ON # Out-of-process mode +-DUSE_TELEMETRY=ON # Enable T2 telemetry +-DPLUGIN_NETWORKCONNECTIONSTATS_REPORTING_INTERVAL=10 # Default interval +``` + +### Dependencies +- WPEFramework (Thunder) +- libjsonrpccpp-client/common +- jsoncpp +- T2 (optional, for telemetry) +- pthread + +## Usage Example + +### Activate Plugin +```bash +curl http://localhost:9998/jsonrpc -d '{ + "jsonrpc": "2.0", + "id": 1, + "method": "Controller.1.activate", + "params": {"callsign": "NetworkConnectionStats"} +}' +``` + +### Generate Report +```bash +curl http://localhost:9998/jsonrpc -d '{ + "jsonrpc": "2.0", + "id": 2, + "method": "NetworkConnectionStats.1.generateReport" +}' +``` + +### Enable Periodic Reporting +```bash +curl http://localhost:9998/jsonrpc -d '{ + "jsonrpc": "2.0", + "id": 3, + "method": "NetworkConnectionStats.1.setPeriodicReporting", + "params": {"enable": true} +}' +``` + +### Subscribe to Events +```bash +curl http://localhost:9998/jsonrpc -d '{ + "jsonrpc": "2.0", + "id": 4, + "method": "NetworkConnectionStats.1.register", + "params": {"event": "onReportGenerated", "id": "client1"} +}' +``` + +## File Structure +``` +networkstats/ +├── Module.h # Thunder module definition +├── Module.cpp # Module registration +├── INetworkConnectionStats.h # COM-RPC interface +├── NetworkConnectionStats.h # Plugin header +├── NetworkConnectionStats.cpp # Plugin implementation +├── NetworkConnectionStatsImplementation.h # Out-of-process header +├── NetworkConnectionStatsImplementation.cpp # Out-of-process implementation +├── NetworkConnectionStats.json # JSON-RPC API spec +├── NetworkConnectionStats.config # Plugin config +├── NetworkConnectionStats.conf.in # CMake config template +├── CMakeLists.txt # Thunder build system +├── INetworkData.h # Network provider interface (existing) +├── ThunderJsonRPCProvider.h/cpp # Network provider impl (existing) +└── *.old # Backup files +``` + +## Integration with Thunder Framework + +The plugin is now a first-class Thunder citizen with: +- **Lifecycle Management** - Thunder controls activation/deactivation +- **RPC Communication** - Both COM-RPC and JSON-RPC supported +- **Process Isolation** - Out-of-process execution for stability +- **Event System** - Notification framework for async events +- **Configuration** - Dynamic configuration via Thunder config system +- **Security** - Runs with Thunder's security policies + +## Next Steps + +1. **Code Generation**: Run Thunder's code generator to create JSON-RPC stubs: + ```bash + JsonGenerator.py --code NetworkConnectionStats.json + ``` + +2. **Build**: Integrate into main CMake build system + +3. **Testing**: + - Unit tests for individual methods + - Integration tests with Thunder framework + - End-to-end API testing + +4. **Documentation**: Generate API documentation from JSON spec + +## Comparison: Before vs After + +| Aspect | Standalone | Thunder Plugin | +|--------|-----------|----------------| +| Execution | Single process with main() | Multi-process (in-process + out-of-process) | +| Lifecycle | Manual start/stop | Thunder-managed activation | +| API | Internal C++ only | COM-RPC + JSON-RPC | +| Configuration | Hardcoded or command-line | Thunder config system | +| Events | None | Notification framework | +| Logging | printf/fprintf | Thunder TRACE macros | +| Integration | Standalone binary | Thunder plugin ecosystem | + +## Notes + +- Original implementation preserved in `*.cpp.old` files for reference +- All network diagnostic functionality maintained +- Telemetry integration (T2) remains optional via compile flag +- Thread-safe implementation with proper locking +- Compatible with Thunder R2 and R4 APIs diff --git a/networkstats/THUNDER_PLUGIN_QUICK_REFERENCE.md b/networkstats/THUNDER_PLUGIN_QUICK_REFERENCE.md new file mode 100644 index 00000000..6a8a7abc --- /dev/null +++ b/networkstats/THUNDER_PLUGIN_QUICK_REFERENCE.md @@ -0,0 +1,317 @@ +# NetworkConnectionStats Thunder Plugin - Quick Reference + +## Architecture Pattern (Following Telemetry) + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Thunder/WPEFramework Process │ +│ ┌────────────────────────────────────────────────────────────┐ │ +│ │ NetworkConnectionStats (Plugin) │ │ +│ │ - Inherits IPlugin + JSONRPC │ │ +│ │ - Manages RPC connection │ │ +│ │ - Forwards JSON-RPC calls │ │ +│ │ - Handles notifications │ │ +│ └────────────────┬───────────────────────────────────────────┘ │ +│ │ COM-RPC │ +└───────────────────┼──────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ Out-of-Process (Separate Process) │ +│ ┌────────────────────────────────────────────────────────────┐ │ +│ │ NetworkConnectionStatsImplementation │ │ +│ │ - Implements INetworkConnectionStats │ │ +│ │ - Implements IConfiguration │ │ +│ │ - Contains business logic │ │ +│ │ - Manages network diagnostics │ │ +│ │ - Sends notifications via INotification │ │ +│ │ ┌──────────────────────────────────────────────────────┐ │ │ +│ │ │ NetworkJsonRPCProvider (INetworkData) │ │ │ +│ │ │ - Calls NetworkManager plugin APIs │ │ │ +│ │ │ - Gets network interface info │ │ │ +│ │ │ - Performs ping tests │ │ │ +│ │ └──────────────────────────────────────────────────────┘ │ │ +│ └────────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +## File Mapping: Telemetry → NetworkConnectionStats + +| Telemetry | NetworkConnectionStats | Purpose | +|-----------|------------------------|---------| +| Module.h/cpp | Module.h/cpp | Thunder module definition | +| ITelemetry.h | INetworkConnectionStats.h | COM-RPC interface | +| Telemetry.h | NetworkConnectionStats.h | Plugin class header | +| Telemetry.cpp | NetworkConnectionStats.cpp | Plugin implementation | +| TelemetryImplementation.h | NetworkConnectionStatsImplementation.h | Out-of-process header | +| TelemetryImplementation.cpp | NetworkConnectionStatsImplementation.cpp | Out-of-process impl | +| Telemetry.json | NetworkConnectionStats.json | JSON-RPC API spec | +| Telemetry.config | NetworkConnectionStats.config | Config file | + +## Key Classes & Interfaces + +### 1. NetworkConnectionStats (Plugin) +**File**: NetworkConnectionStats.h/cpp +**Inherits**: PluginHost::IPlugin, PluginHost::JSONRPC +**Purpose**: Main plugin class running in Thunder process + +**Key Methods**: +- `Initialize(PluginHost::IShell*)` - Plugin activation +- `Deinitialize(PluginHost::IShell*)` - Plugin cleanup +- `Information()` - Plugin description +- `Deactivated(RPC::IRemoteConnection*)` - Handle out-of-process crash + +**Inner Class**: `Notification` +- Implements `RPC::IRemoteConnection::INotification` +- Implements `INetworkConnectionStats::INotification` +- Forwards events to JSON-RPC clients + +### 2. INetworkConnectionStats (Interface) +**File**: INetworkConnectionStats.h +**Purpose**: COM-RPC interface definition + +**Sub-Interface**: `INotification` +- `OnReportGenerated(const string&)` - Event notification + +**Main Interface Methods**: +- `Register/Unregister` - Notification subscription +- `GenerateReport()` - Trigger diagnostics +- `GetConnectionType()` - Network type +- `GetIpv4Address/GetIpv6Address` - IP addresses +- `GetIpv4Gateway/GetIpv6Gateway` - Gateways +- `GetIpv4Dns/GetIpv6Dns` - DNS servers +- `GetInterface()` - Active interface +- `PingGateway()` - Gateway connectivity test +- `SetPeriodicReporting()` - Enable/disable periodic reports +- `SetReportingInterval()` - Configure interval + +### 3. NetworkConnectionStatsImplementation +**File**: NetworkConnectionStatsImplementation.h/cpp +**Inherits**: INetworkConnectionStats, IConfiguration +**Purpose**: Out-of-process business logic + +**Key Methods**: +- `Configure(PluginHost::IShell*)` - Initialize from config +- All INetworkConnectionStats interface methods +- `connectionTypeCheck()` - Internal diagnostic +- `connectionIpCheck()` - Internal diagnostic +- `defaultIpv4RouteCheck()` - Internal diagnostic +- `defaultIpv6RouteCheck()` - Internal diagnostic +- `gatewayPacketLossCheck()` - Internal diagnostic +- `networkDnsCheck()` - Internal diagnostic +- `logTelemetry()` - T2 event logging +- `periodicReportingThread()` - Background thread + +## Interface Macros Explained + +### BEGIN_INTERFACE_MAP / END_INTERFACE_MAP +Thunder's interface query mechanism (like COM's QueryInterface): + +```cpp +BEGIN_INTERFACE_MAP(NetworkConnectionStats) + INTERFACE_ENTRY(PluginHost::IPlugin) // Standard plugin interface + INTERFACE_ENTRY(PluginHost::IDispatcher) // JSON-RPC dispatcher + INTERFACE_AGGREGATE(Exchange::INetworkConnectionStats, _networkStats) // Delegate to out-of-process +END_INTERFACE_MAP +``` + +**INTERFACE_AGGREGATE**: Forwards interface queries to another object (_networkStats = out-of-process impl) + +## RPC Connection Flow + +1. **Plugin Activation** (In-process): + ```cpp + Initialize(service) { + _networkStats = service->Root(_connectionId, 5000, "NetworkConnectionStatsImplementation"); + // Spawns out-of-process, creates proxy + } + ``` + +2. **Out-of-process Creation**: + - Thunder spawns new process + - Loads NetworkConnectionStatsImplementation library + - Creates instance via SERVICE_REGISTRATION macro + - Establishes RPC channel + +3. **API Call Flow**: + ``` + JSON-RPC Client → Thunder → NetworkConnectionStats (plugin) + → COM-RPC Proxy → Out-of-process → NetworkConnectionStatsImplementation + → NetworkJsonRPCProvider → NetworkManager Plugin + ``` + +4. **Event Flow**: + ``` + NetworkConnectionStatsImplementation::notifyReportGenerated() + → INotification::OnReportGenerated() + → COM-RPC channel + → NetworkConnectionStats::Notification::OnReportGenerated() + → JNetworkConnectionStats::Event::OnReportGenerated() + → JSON-RPC Event to subscribed clients + ``` + +## Critical Thunder Patterns + +### 1. SERVICE_REGISTRATION +Registers class with Thunder's factory: +```cpp +SERVICE_REGISTRATION(NetworkConnectionStats, 1, 0, 0); +``` + +### 2. Metadata Declaration +Plugin version and lifecycle info: +```cpp +static Plugin::Metadata metadata( + 1, 0, 0, // Major, Minor, Patch + {}, // Preconditions + {}, // Terminations + {} // Controls +); +``` + +### 3. Notification Pattern +```cpp +// Plugin side +Core::Sink _notification; + +// Implementation side +std::list _notifications; + +Register() { + _notifications.push_back(notification); + notification->AddRef(); // Reference counting! +} + +Unregister() { + notification->Release(); + _notifications.erase(notification); +} +``` + +### 4. Thread Safety +```cpp +Core::CriticalSection _adminLock; + +void SomeMethod() { + _adminLock.Lock(); + // Critical section + _adminLock.Unlock(); +} +``` + +### 5. Reference Counting +```cpp +_service->AddRef(); // Increment ref count +_service->Release(); // Decrement ref count +// Object destroyed when ref count reaches 0 +``` + +## Configuration File Format + +### NetworkConnectionStats.config +```json +{ + "locator": "libWPEFrameworkNetworkConnectionStats.so", // Plugin library + "classname": "NetworkConnectionStats", // Class name + "startmode": "Activated", // Auto-start + "configuration": { + "outofprocess": true, // Run out-of-process + "root": { + "mode": "Local" // Process mode + }, + "reportingInterval": 10 // Custom config + } +} +``` + +## Build Integration + +### Add to Parent CMakeLists.txt +```cmake +add_subdirectory(networkstats) +``` + +### Build Commands +```bash +cd build +cmake .. -DPLUGIN_NETWORKCONNECTIONSTATS=ON -DUSE_TELEMETRY=ON +make NetworkConnectionStats +sudo make install +``` + +## Debugging Tips + +### 1. Enable Traces +Set log level in Thunder config or via Controller plugin: +```bash +curl http://localhost:9998/jsonrpc -d '{ + "jsonrpc": "2.0", + "id": 1, + "method": "Controller.1.configuration@NetworkConnectionStats", + "params": {"trace": ["Information", "Error"]} +}' +``` + +### 2. Check Process +```bash +ps aux | grep NetworkConnectionStatsImplementation +``` + +### 3. Monitor Logs +```bash +journalctl -u wpeframework -f | grep NetworkConnectionStats +``` + +### 4. Verify Plugin Load +```bash +curl http://localhost:9998/jsonrpc -d '{ + "jsonrpc": "2.0", + "id": 1, + "method": "Controller.1.status@NetworkConnectionStats" +}' +``` + +## Common Issues & Solutions + +### Issue: Plugin doesn't load +**Check**: +- Library in correct path (`/usr/lib/wpeframework/plugins/`) +- Dependencies present (`ldd libWPEFrameworkNetworkConnectionStats.so`) +- Config file syntax valid + +### Issue: Out-of-process crashes +**Check**: +- NetworkJsonRPCProvider can reach NetworkManager plugin +- Null pointer checks in implementation +- Thread synchronization + +### Issue: Events not received +**Check**: +- Client subscribed to event +- Notification registered in implementation +- RPC connection active + +### Issue: "Interface not found" +**Check**: +- INTERFACE_AGGREGATE correctly set +- Out-of-process implementation exports interface +- SERVICE_REGISTRATION present + +## Testing Checklist + +- [ ] Plugin activates successfully +- [ ] All JSON-RPC methods callable +- [ ] Events received by subscribers +- [ ] Periodic reporting works +- [ ] Out-of-process survives restarts +- [ ] Telemetry events logged (if enabled) +- [ ] No memory leaks (valgrind) +- [ ] Thread-safe under concurrent calls +- [ ] Graceful deactivation/cleanup + +## Resources + +- **Thunder Documentation**: https://rdkcentral.github.io/Thunder/ +- **Plugin Template**: Telemetry plugin (reference implementation) +- **API Generator**: JsonGenerator.py in Thunder tools +- **Code Examples**: Other Thunder plugins in RDK repositories diff --git a/networkstats/definition/CMakeLists.txt b/networkstats/definition/CMakeLists.txt new file mode 100644 index 00000000..a31b8adc --- /dev/null +++ b/networkstats/definition/CMakeLists.txt @@ -0,0 +1,28 @@ +############################################################################# +# If not stated otherwise in this file or this component's LICENSE file the +# following copyright and licenses apply: +# +# Copyright 2025 RDK Management +# +# 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. +############################################################################# +message("Generate JsonRPC Documentation for NetworkConnectionStats.json") + +find_package(JsonGenerator REQUIRED) + +if(NOT GENERATOR_SEARCH_PATH) + set(GENERATOR_SEARCH_PATH ${CMAKE_SYSROOT}${CMAKE_INSTALL_PREFIX}/include/${NAMESPACE}) +endif() + +set(JSON_FILE "${CMAKE_CURRENT_SOURCE_DIR}/NetworkConnectionStats.json") +JsonGenerator(CODE INPUT ${JSON_FILE} OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/generated" INCLUDE_PATH ${GENERATOR_SEARCH_PATH} CPPIFDIR "${CMAKE_CURRENT_SOURCE_DIR}" DOCS) diff --git a/networkstats/definition/NetworkConnectionStats.json b/networkstats/definition/NetworkConnectionStats.json new file mode 100644 index 00000000..31abe279 --- /dev/null +++ b/networkstats/definition/NetworkConnectionStats.json @@ -0,0 +1,256 @@ +{ + "$schema": "interface.schema.json", + "jsonrpc": "2.0", + "info": { + "title": "NetworkConnectionStats API", + "class": "NetworkConnectionStats", + "description": "NetworkConnectionStats plugin for network diagnostics and monitoring" + }, + "definitions": { + "success": { + "summary": "Whether the request succeeded", + "type": "boolean", + "example": true + }, + "pingresult": { + "type": "object", + "properties": { + "packetLoss": { + "type": "string", + "description": "Packet loss percentage" + }, + "avgRtt": { + "type": "string", + "description": "Average round-trip time in milliseconds" + } + }, + "required": ["packetLoss", "avgRtt"] + } + }, + "methods": { + "generateReport": { + "summary": "Generate network diagnostics report", + "description": "Triggers a full network diagnostic scan including connection type, IP configuration, routes, and DNS", + "result": { + "$ref": "#/definitions/success" + } + }, + "getConnectionType": { + "summary": "Get current connection type", + "description": "Returns whether the device is using Ethernet or WiFi", + "result": { + "type": "object", + "properties": { + "connectionType": { + "type": "string", + "description": "Connection type (Ethernet/WiFi)", + "example": "Ethernet" + } + }, + "required": ["connectionType"] + } + }, + "getIpv4Address": { + "summary": "Get IPv4 address for interface", + "params": { + "type": "object", + "properties": { + "interface": { + "type": "string", + "description": "Network interface name", + "example": "eth0" + } + }, + "required": ["interface"] + }, + "result": { + "type": "object", + "properties": { + "ipv4Address": { + "type": "string", + "description": "IPv4 address", + "example": "192.168.1.100" + } + }, + "required": ["ipv4Address"] + } + }, + "getIpv6Address": { + "summary": "Get IPv6 address for interface", + "params": { + "type": "object", + "properties": { + "interface": { + "type": "string", + "description": "Network interface name", + "example": "eth0" + } + }, + "required": ["interface"] + }, + "result": { + "type": "object", + "properties": { + "ipv6Address": { + "type": "string", + "description": "IPv6 address", + "example": "fe80::1" + } + }, + "required": ["ipv6Address"] + } + }, + "getIpv4Gateway": { + "summary": "Get IPv4 gateway address", + "result": { + "type": "object", + "properties": { + "gateway": { + "type": "string", + "description": "IPv4 gateway address", + "example": "192.168.1.1" + } + }, + "required": ["gateway"] + } + }, + "getIpv6Gateway": { + "summary": "Get IPv6 gateway address", + "result": { + "type": "object", + "properties": { + "gateway": { + "type": "string", + "description": "IPv6 gateway address", + "example": "fe80::1" + } + }, + "required": ["gateway"] + } + }, + "getIpv4Dns": { + "summary": "Get IPv4 DNS server", + "result": { + "type": "object", + "properties": { + "dns": { + "type": "string", + "description": "IPv4 DNS server address", + "example": "8.8.8.8" + } + }, + "required": ["dns"] + } + }, + "getIpv6Dns": { + "summary": "Get IPv6 DNS server", + "result": { + "type": "object", + "properties": { + "dns": { + "type": "string", + "description": "IPv6 DNS server address", + "example": "2001:4860:4860::8888" + } + }, + "required": ["dns"] + } + }, + "getInterface": { + "summary": "Get current active interface name", + "result": { + "type": "object", + "properties": { + "interface": { + "type": "string", + "description": "Active network interface name", + "example": "eth0" + } + }, + "required": ["interface"] + } + }, + "pingGateway": { + "summary": "Ping gateway to check connectivity", + "params": { + "type": "object", + "properties": { + "endpoint": { + "type": "string", + "description": "Gateway IP address to ping", + "example": "192.168.1.1" + }, + "ipversion": { + "type": "string", + "description": "IP version (IPv4 or IPv6)", + "example": "IPv4" + }, + "count": { + "type": "number", + "description": "Number of ping packets to send", + "example": 5 + }, + "timeout": { + "type": "number", + "description": "Timeout in seconds", + "example": 30 + } + }, + "required": ["endpoint", "ipversion", "count", "timeout"] + }, + "result": { + "$ref": "#/definitions/pingresult" + } + }, + "setPeriodicReporting": { + "summary": "Enable or disable periodic reporting", + "params": { + "type": "object", + "properties": { + "enable": { + "type": "boolean", + "description": "Enable (true) or disable (false) periodic reporting", + "example": true + } + }, + "required": ["enable"] + }, + "result": { + "$ref": "#/definitions/success" + } + }, + "setReportingInterval": { + "summary": "Set periodic reporting interval", + "params": { + "type": "object", + "properties": { + "intervalMinutes": { + "type": "number", + "description": "Reporting interval in minutes", + "example": 10 + } + }, + "required": ["intervalMinutes"] + }, + "result": { + "$ref": "#/definitions/success" + } + } + }, + "events": { + "onReportGenerated": { + "summary": "Notification when a network report is generated", + "params": { + "type": "object", + "properties": { + "status": { + "type": "string", + "description": "Report generation status message", + "example": "Report generated successfully" + } + }, + "required": ["status"] + } + } + } +} diff --git a/networkstats/interface/CMakeLists.txt b/networkstats/interface/CMakeLists.txt new file mode 100644 index 00000000..e19aa82a --- /dev/null +++ b/networkstats/interface/CMakeLists.txt @@ -0,0 +1,61 @@ +############################################################################# +# If not stated otherwise in this file or this component's LICENSE file the +# following copyright and licenses apply: +# +# Copyright 2025 RDK Management +# +# 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. +############################################################################# +message("Generate ProxyStub for INetworkConnectionStats.h") + +find_package(WPEFramework) +find_package(${NAMESPACE}Core REQUIRED) +find_package(${NAMESPACE}Plugins REQUIRED) +find_package(CompileSettingsDebug CONFIG REQUIRED) +find_package(ProxyStubGenerator REQUIRED) + +set(PROXYLIB ${NAMESPACE}NetworkConnectionStatsProxy) +set(ProxyStubGenerator_DIR ${CMAKE_SYSROOT}${CMAKE_INSTALL_PREFIX}/tools/cmake ${ProxyStubGenerator_DIR}) + +if(NOT GENERATOR_SEARCH_PATH) + set(GENERATOR_SEARCH_PATH ${CMAKE_SYSROOT}${CMAKE_INSTALL_PREFIX}/include/${NAMESPACE}) +endif() + +file(GLOB INTERFACE_HEADER ${CMAKE_CURRENT_SOURCE_DIR}) + +ProxyStubGenerator(INPUT "${INTERFACE_HEADER}" OUTDIR "${CMAKE_CURRENT_BINARY_DIR}/generated" INCLUDE_PATH ${GENERATOR_SEARCH_PATH}) + +file(GLOB PROXY_STUB_SOURCES "${CMAKE_CURRENT_BINARY_DIR}/generated/ProxyStubs*.cpp") + +add_library(${PROXYLIB} SHARED ${PROXY_STUB_SOURCES} + ../plugin/Module.cpp) + +target_include_directories(${PROXYLIB} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/../plugin/) + +target_link_libraries(${PROXYLIB} PRIVATE + ${NAMESPACE}Core::${NAMESPACE}Core + ${NAMESPACE}COM::${NAMESPACE}COM + CompileSettingsDebug::CompileSettingsDebug) + +set_target_properties(${PROXYLIB} PROPERTIES + CXX_STANDARD 11 + CXX_STANDARD_REQUIRED YES + FRAMEWORK FALSE) + +install(TARGETS ${PROXYLIB} + DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/${STORAGE_DIRECTORY}/proxystubs + COMPONENT ${NAMESPACE}_Runtime) + +install(FILES INetworkConnectionStats.h + DESTINATION ${CMAKE_INSTALL_PREFIX}/include/${NAMESPACE}/interfaces + COMPONENT ${NAMESPACE}_Development) diff --git a/networkstats/interface/INetworkConnectionStats.h b/networkstats/interface/INetworkConnectionStats.h new file mode 100644 index 00000000..66872ff8 --- /dev/null +++ b/networkstats/interface/INetworkConnectionStats.h @@ -0,0 +1,52 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2025 RDK Management +* +* 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. +**/ + +#pragma once + +#include "Module.h" + +namespace WPEFramework { +namespace Exchange { + enum myIDs { + ID_NETWORKCONNECTIONSTATS = 0x800004F0, + ID_NETWORKCONNECTIONSTATS_NOTIFICATION = ID_NETWORKCONNECTIONSTATS + 1 + }; + + // Interface for network connection statistics + struct EXTERNAL INetworkConnectionStats : virtual public Core::IUnknown { + enum { ID = ID_NETWORKCONNECTIONSTATS }; + + // Configuration method + virtual uint32_t Configure(const string configLine) = 0; + + /* @event */ + struct EXTERNAL INotification : virtual public Core::IUnknown { + enum { ID = ID_NETWORKCONNECTIONSTATS_NOTIFICATION }; + + // Placeholder for future notification events if needed + // Currently no events are exposed as this is an internal plugin + }; + + // Allow other processes to register/unregister for notifications + virtual uint32_t Register(INetworkConnectionStats::INotification* notification) = 0; + virtual uint32_t Unregister(INetworkConnectionStats::INotification* notification) = 0; + }; + +} // namespace Exchange +} // namespace WPEFramework diff --git a/networkstats/plugin/CMakeLists.txt b/networkstats/plugin/CMakeLists.txt new file mode 100644 index 00000000..f8b68c36 --- /dev/null +++ b/networkstats/plugin/CMakeLists.txt @@ -0,0 +1,123 @@ +############################################################################# +# If not stated otherwise in this file or this component's LICENSE file the +# following copyright and licenses apply: +# +# Copyright 2025 RDK Management +# +# 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. +############################################################################# +message("Build NetworkConnectionStats v${PROJECT_VERSION}") + +set(PLUGIN_NAME NetworkConnectionStats) +set(MODULE_NAME ${NAMESPACE}${PLUGIN_NAME}) +set(MODULE_IMPL_NAME ${NAMESPACE}${PLUGIN_NAME}Implementation) + +find_package(${NAMESPACE}Core REQUIRED) +find_package(${NAMESPACE}Plugins REQUIRED) +find_package(${NAMESPACE}Definitions REQUIRED) +find_package(CompileSettingsDebug CONFIG REQUIRED) + +# Optional Telemetry support +if (USE_TELEMETRY) + find_package(T2 REQUIRED) + add_definitions(-DUSE_TELEMETRY) + message("Telemetry support enabled") +endif() + +set(PLUGIN_BUILD_REFERENCE ${PROJECT_VERSION} CACHE STRING "To Set the Hash for the plugin") +add_definitions(-DPLUGIN_BUILD_REFERENCE=${PLUGIN_BUILD_REFERENCE}) + +# Include directories - use CMAKE_CURRENT_SOURCE_DIR for relative paths +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../interface) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) + +# Main Plugin Library (in-process) +add_library(${MODULE_NAME} SHARED + Module.cpp + NetworkConnectionStats.cpp + NetworkConnectionStatsLogger.cpp +) + +target_link_libraries(${MODULE_NAME} + PRIVATE + CompileSettingsDebug::CompileSettingsDebug + ${NAMESPACE}Core::${NAMESPACE}Core + ${NAMESPACE}Plugins::${NAMESPACE}Plugins + ${NAMESPACE}Definitions::${NAMESPACE}Definitions +) + +target_include_directories(${MODULE_NAME} + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/../interface +) + +set_target_properties(${MODULE_NAME} PROPERTIES + CXX_STANDARD 11 + CXX_STANDARD_REQUIRED YES + FRAMEWORK FALSE +) + +# Out-of-process Implementation Library +# Note: Both providers are compiled; selection is done at runtime via config +add_library(${MODULE_IMPL_NAME} SHARED + Module.cpp + NetworkConnectionStatsImplementation.cpp + NetworkConnectionStatsLogger.cpp + ThunderComRPCProvider.cpp + ThunderJsonRPCProvider.cpp +) + +target_link_libraries(${MODULE_IMPL_NAME} + PRIVATE + CompileSettingsDebug::CompileSettingsDebug + ${NAMESPACE}Core::${NAMESPACE}Core + ${NAMESPACE}Plugins::${NAMESPACE}Plugins + ${NAMESPACE}Definitions::${NAMESPACE}Definitions + pthread +) + +if (USE_TELEMETRY) + target_link_libraries(${MODULE_IMPL_NAME} PRIVATE ${T2_LIBRARIES}) + target_include_directories(${MODULE_IMPL_NAME} PRIVATE ${T2_INCLUDE_DIRS}) +endif() + +target_include_directories(${MODULE_IMPL_NAME} + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/../interface +) + +set_target_properties(${MODULE_IMPL_NAME} PROPERTIES + CXX_STANDARD 11 + CXX_STANDARD_REQUIRED YES + FRAMEWORK FALSE + OUTPUT_NAME ${MODULE_NAME}Implementation +) + +# Install plugin libraries +install( + TARGETS ${MODULE_NAME} ${MODULE_IMPL_NAME} + DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/${STORAGE_DIRECTORY}/plugins + COMPONENT ${NAMESPACE}_Runtime +) + +# Configuration variables for plugin +set(PLUGIN_NETWORKCONNECTIONSTATS_AUTOSTART "false" CACHE STRING "Automatically start the plugin") +set(PLUGIN_NETWORKCONNECTIONSTATS_OUTOFPROCESS "true" CACHE STRING "Run plugin out-of-process") +set(PLUGIN_NETWORKCONNECTIONSTATS_STARTUPORDER "50" CACHE STRING "Plugin startup order") +set(PLUGIN_NETWORKCONNECTIONSTATS_REPORTING_INTERVAL "90" CACHE STRING "Reporting interval in seconds (default: 90 seconds)") +set(PLUGIN_NETWORKCONNECTIONSTATS_PROVIDER_TYPE "comrpc" CACHE STRING "Network data provider type: 'comrpc' or 'jsonrpc' (default: comrpc)") + +# Install configuration +write_config(PLUGINS NetworkConnectionStats) diff --git a/networkstats/plugin/INetworkData.h b/networkstats/plugin/INetworkData.h new file mode 100644 index 00000000..1b7c6c91 --- /dev/null +++ b/networkstats/plugin/INetworkData.h @@ -0,0 +1,102 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2025 RDK Management +* +* 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 __INETWORKDATA_H__ +#define __INETWORKDATA_H__ + +#include +#include +#include + +class INetworkData +{ + public: + virtual ~INetworkData() {} + + /* @brief Initialize the network data provider + * @return true if initialization successful, false otherwise + */ + virtual bool Initialize() = 0; + + /* @brief Retrieve IPv4 address for specified interface + * @param interface_name Interface name (e.g., eth0, wlan0) + * @return IPv4 address string + */ + virtual std::string getIpv4Address(std::string interface_name) = 0; + + /* @brief Retrieve IPv6 address for specified interface + * @param interface_name Interface name (e.g., eth0, wlan0) + * @return IPv6 address string + */ + virtual std::string getIpv6Address(std::string interface_name) = 0; + + /* @brief Get IPv4 gateway/route address from last getIpv4Address call */ + virtual std::string getIpv4Gateway() = 0; + + /* @brief Get IPv6 gateway/route address from last getIpv6Address call */ + virtual std::string getIpv6Gateway() = 0; + + /* @brief Get IPv4 primary DNS from last getIpv4Address call */ + virtual std::string getIpv4PrimaryDns() = 0; + + /* @brief Get IPv6 primary DNS from last getIpv6Address call */ + virtual std::string getIpv6PrimaryDns() = 0; + + /* @brief Get current network connection type */ + virtual std::string getConnectionType() = 0; + + /* @brief Get DNS server entries */ + virtual std::string getDnsEntries() = 0; + + /* @brief Populate network interface data */ + virtual void populateNetworkData() = 0; + + /* @brief Get current active interface name */ + virtual std::string getInterface() = 0; + + /* @brief Ping to gateway to check packet loss + * @param endpoint Gateway IP address to ping + * @param ipversion Either "IPv4" or "IPv6" + * @param count Number of ping packets to send + * @param timeout Timeout in seconds + * @return true if ping successful, false otherwise + */ + virtual bool pingToGatewayCheck(std::string endpoint, std::string ipversion, int count, int timeout) = 0; + + /* @brief Get packet loss from last ping call */ + virtual std::string getPacketLoss() = 0; + + /* @brief Get average RTT from last ping call */ + virtual std::string getAvgRtt() = 0; + + /* @brief Subscribe to NetworkManager events + * @param eventName Name of the event (e.g., "onInterfaceStateChange") + * @param callback Callback function to be called when event fires + * @return Error code (Core::ERROR_NONE on success) + */ + virtual uint32_t SubscribeToEvent(const std::string& eventName, + std::function callback) = 0; + + protected: + INetworkData() {} + +}; + + +#endif /* __INETWORKDATA_H__ */ diff --git a/networkstats/plugin/Module.cpp b/networkstats/plugin/Module.cpp new file mode 100644 index 00000000..a05f0666 --- /dev/null +++ b/networkstats/plugin/Module.cpp @@ -0,0 +1,22 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2025 RDK Management +* +* 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 "Module.h" + +MODULE_NAME_DECLARATION(BUILD_REFERENCE) diff --git a/networkstats/plugin/Module.h b/networkstats/plugin/Module.h new file mode 100644 index 00000000..f6c56b0e --- /dev/null +++ b/networkstats/plugin/Module.h @@ -0,0 +1,30 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2025 RDK Management +* +* 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. +**/ + +#pragma once +#ifndef MODULE_NAME +#define MODULE_NAME NetworkConnectionStats +#endif + +#include +#include +#include + +#undef EXTERNAL +#define EXTERNAL diff --git a/networkstats/plugin/NetworkConnectionStats.conf.in b/networkstats/plugin/NetworkConnectionStats.conf.in new file mode 100644 index 00000000..9dc64a05 --- /dev/null +++ b/networkstats/plugin/NetworkConnectionStats.conf.in @@ -0,0 +1,13 @@ +callsign = "org.rdk.NetworkConnectionStats" +startuporder = "@PLUGIN_NETWORKCONNECTIONSTATS_STARTUPORDER@" +autostart = "@PLUGIN_NETWORKCONNECTIONSTATS_AUTOSTART@" + +process = JSON() +process.add("outofprocess", "@PLUGIN_NETWORKCONNECTIONSTATS_OUTOFPROCESS@") +process.add("locator", "lib@MODULE_IMPL_NAME@.so") + +configuration = JSON() +configuration.add("root", process) +configuration.add("reportingInterval", "@PLUGIN_NETWORKCONNECTIONSTATS_REPORTING_INTERVAL@") +configuration.add("autoStart", True) +configuration.add("providerType", "@PLUGIN_NETWORKCONNECTIONSTATS_PROVIDER_TYPE@") diff --git a/networkstats/plugin/NetworkConnectionStats.config b/networkstats/plugin/NetworkConnectionStats.config new file mode 100644 index 00000000..e751992c --- /dev/null +++ b/networkstats/plugin/NetworkConnectionStats.config @@ -0,0 +1,16 @@ +{ + "callsign": "org.rdk.NetworkConnectionStats", + "locator": "libWPEFrameworkNetworkConnectionStats.so", + "classname": "NetworkConnectionStats", + "startmode": "Activated", + "precondition": ["Platform"], + "configuration": { + "outofprocess": true, + "root": { + "mode": "Local", + "locator": "libWPEFrameworkNetworkConnectionStatsImplementation.so" + }, + "reportingInterval": 600, + "autoStart": true + } +} diff --git a/networkstats/plugin/NetworkConnectionStats.cpp b/networkstats/plugin/NetworkConnectionStats.cpp new file mode 100644 index 00000000..cc79a037 --- /dev/null +++ b/networkstats/plugin/NetworkConnectionStats.cpp @@ -0,0 +1,176 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2025 RDK Management +* +* 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 "NetworkConnectionStats.h" +#include "NetworkConnectionStatsLogger.h" + +using namespace NetworkConnectionStatsLogger; + +#define API_VERSION_NUMBER_MAJOR 1 +#define API_VERSION_NUMBER_MINOR 0 +#define API_VERSION_NUMBER_PATCH 0 + +namespace WPEFramework { + + namespace { + static Plugin::Metadata metadata( + // Version (Major, Minor, Patch) + API_VERSION_NUMBER_MAJOR, API_VERSION_NUMBER_MINOR, API_VERSION_NUMBER_PATCH, + // Preconditions + { PluginHost::ISubSystem::subsystem::PLATFORM }, + // Terminations + {}, + // Controls + {} + ); + } + + namespace Plugin { + + /* + * Register NetworkConnectionStats module as WPEFramework plugin + * This plugin runs internally without exposing external APIs + */ + SERVICE_REGISTRATION(NetworkConnectionStats, API_VERSION_NUMBER_MAJOR, API_VERSION_NUMBER_MINOR, API_VERSION_NUMBER_PATCH); + + NetworkConnectionStats::NetworkConnectionStats() + : _service(nullptr) + , _connectionId(0) + , _networkStats(nullptr) + , _notification(this) + { + NSLOG_INFO("NetworkConnectionStats Constructor"); + } + + NetworkConnectionStats::~NetworkConnectionStats() + { + NSLOG_INFO("NetworkConnectionStats Destructor"); + } + + const string NetworkConnectionStats::Initialize(PluginHost::IShell* service) + { + string message; + + if (nullptr == service) { + NSLOG_ERROR("Initialize: service parameter is nullptr"); + return _T("Invalid service parameter"); + } + if (nullptr != _service) { + NSLOG_ERROR("Initialize: plugin already initialized (_service is not nullptr)"); + return _T("Plugin already initialized"); + } + if (nullptr != _networkStats) { + NSLOG_ERROR("Initialize: plugin already initialized (_networkStats is not nullptr)"); + return _T("Plugin already initialized"); + } + if (0 != _connectionId) { + NSLOG_ERROR("Initialize: plugin already initialized (_connectionId is not 0)"); + return _T("Plugin already initialized"); + } + + SYSLOG(Logging::Startup, (_T("Initializing NetworkConnectionStats"))); + NetworkConnectionStatsLogger::Init(); + + // Register the Connection::Notification first + _service = service; + _service->Register(&_notification); + + // Spawn out-of-process implementation + _networkStats = _service->Root(_connectionId, 25000, _T("NetworkConnectionStatsImplementation")); + + NSLOG_INFO("NetworkConnectionStats::Initialize: PID=%u, ConnectionID=%u", getpid(), _connectionId); + + if (nullptr != _networkStats) { + SYSLOG(Logging::Startup, (_T("Configuring NetworkConnectionStats"))); + if (_networkStats->Configure(_service->ConfigLine()) != Core::ERROR_NONE) + { + SYSLOG(Logging::Shutdown, (_T("NetworkConnectionStats failed to configure"))); + message = _T("NetworkConnectionStats could not be configured"); + } + else + { + SYSLOG(Logging::Startup, (_T("NetworkConnectionStats configured successfully"))); + } + } else { + // Something went wrong, clean up + SYSLOG(Logging::Shutdown, (_T("NetworkConnectionStats out-of-process creation failed"))); + NSLOG_ERROR("Failed to initialize NetworkConnectionStats"); + _service->Unregister(&_notification); + _service = nullptr; + message = _T("NetworkConnectionStats plugin could not be initialised"); + } + + return message; + } + + void NetworkConnectionStats::Deinitialize(PluginHost::IShell* service) + { + if (_service != service) { + NSLOG_ERROR("Deinitialize: service parameter mismatch"); + return; + } + if (nullptr == _networkStats) { + NSLOG_ERROR("Deinitialize: _networkStats is nullptr"); + return; + } + + SYSLOG(Logging::Shutdown, (_T("Deinitializing NetworkConnectionStats"))); + + if (_networkStats != nullptr) + { + SYSLOG(Logging::Shutdown, (_T("Unregister Thunder Notifications for NetworkConnectionStats"))); + _service->Unregister(&_notification); + + RPC::IRemoteConnection* connection = service->RemoteConnection(_connectionId); + + SYSLOG(Logging::Shutdown, (_T("Release of COMRPC Interface of NetworkConnectionStats"))); + _networkStats->Release(); + + if (connection != nullptr) + { + connection->Terminate(); + connection->Release(); + } + } + + // Set everything back to default + _connectionId = 0; + _service = nullptr; + _networkStats = nullptr; + } + + string NetworkConnectionStats::Information() const + { + return ("Internal network diagnostics and monitoring plugin - no external APIs"); + } + + void NetworkConnectionStats::Deactivated(RPC::IRemoteConnection* connection) + { + if (connection->Id() == _connectionId) { + if (nullptr == _service) { + NSLOG_ERROR("Deactivated: _service is nullptr"); + return; + } + Core::IWorkerPool::Instance().Submit(PluginHost::IShell::Job::Create(_service, + PluginHost::IShell::DEACTIVATED, PluginHost::IShell::FAILURE)); + } + } + +} // namespace Plugin +} // namespace WPEFramework diff --git a/networkstats/plugin/NetworkConnectionStats.h b/networkstats/plugin/NetworkConnectionStats.h new file mode 100644 index 00000000..a42af25c --- /dev/null +++ b/networkstats/plugin/NetworkConnectionStats.h @@ -0,0 +1,88 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2025 RDK Management +* +* 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. +**/ + +#pragma once + +#include "Module.h" +#include "INetworkConnectionStats.h" +#include + +namespace WPEFramework { + namespace Plugin { + // Internal-only plugin - no external APIs exposed + class NetworkConnectionStats : public PluginHost::IPlugin { + private: + class Notification : public RPC::IRemoteConnection::INotification { + private: + Notification() = delete; + Notification(const Notification&) = delete; + Notification& operator=(const Notification&) = delete; + + public: + explicit Notification(NetworkConnectionStats* parent) + : _parent(*parent) { + ASSERT(parent != nullptr); + } + + virtual ~Notification() {} + + BEGIN_INTERFACE_MAP(Notification) + INTERFACE_ENTRY(RPC::IRemoteConnection::INotification) + END_INTERFACE_MAP + + void Activated(RPC::IRemoteConnection*) override { + TRACE(Trace::Information, (_T("NetworkConnectionStats Activated"))); + } + + void Deactivated(RPC::IRemoteConnection* connection) override { + TRACE(Trace::Information, (_T("NetworkConnectionStats Deactivated"))); + _parent.Deactivated(connection); + } + + private: + NetworkConnectionStats& _parent; + }; + + public: + NetworkConnectionStats(const NetworkConnectionStats&) = delete; + NetworkConnectionStats& operator=(const NetworkConnectionStats&) = delete; + NetworkConnectionStats(); + virtual ~NetworkConnectionStats(); + + BEGIN_INTERFACE_MAP(NetworkConnectionStats) + INTERFACE_ENTRY(PluginHost::IPlugin) + INTERFACE_AGGREGATE(Exchange::INetworkConnectionStats, _networkStats) + END_INTERFACE_MAP + + // IPlugin methods + const string Initialize(PluginHost::IShell* service) override; + void Deinitialize(PluginHost::IShell* service) override; + string Information() const override; + + private: + void Deactivated(RPC::IRemoteConnection* connection); + + private: + PluginHost::IShell* _service{}; + uint32_t _connectionId{}; + Exchange::INetworkConnectionStats* _networkStats{}; + Core::Sink _notification; + }; + } // namespace Plugin +} // namespace WPEFramework diff --git a/networkstats/plugin/NetworkConnectionStatsImplementation.cpp b/networkstats/plugin/NetworkConnectionStatsImplementation.cpp new file mode 100644 index 00000000..24b14b10 --- /dev/null +++ b/networkstats/plugin/NetworkConnectionStatsImplementation.cpp @@ -0,0 +1,552 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2025 RDK Management +* +* 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 "NetworkConnectionStatsImplementation.h" +#include "NetworkConnectionStatsLogger.h" +#include + +using namespace NetworkConnectionStatsLogger; + +#define API_VERSION_NUMBER_MAJOR 1 +#define API_VERSION_NUMBER_MINOR 0 +#define API_VERSION_NUMBER_PATCH 0 +#define SUBSCRIPTION_RETRY_INTERVAL_MS 500 + +namespace WPEFramework { +namespace Plugin { + + SERVICE_REGISTRATION(NetworkConnectionStatsImplementation, 1, 0); + + NetworkConnectionStatsImplementation::NetworkConnectionStatsImplementation() + : _adminLock() + , m_provider(nullptr) + , m_interface("") + , m_ipv4Address("") + , m_ipv6Address("") + , m_ipv4Route("") + , m_ipv6Route("") + , m_ipv4Dns("") + , m_ipv6Dns("") + , _periodicReportingEnabled(true) // Auto-enabled + , _reportingIntervalSeconds(600) // Default 10 minutes (600 seconds) + , _stopReporting(false) + , _stopSubscriptionRetry(false) + , m_subsIfaceStateChange(false) + , m_subsActIfaceChange(false) + , m_subsIPAddrChange(false) + { + + NSLOG_INFO("NetworkConnectionStatsImplemen tation Constructor"); + /* Set NetworkManager Out-Process name to be NWMgrPlugin */ + Core::ProcessInfo().Name("NetworkConnectionStats"); + NSLOG_INFO((_T("NetworkConnectionStats Out-Of-Process Instantiation; SHA: " _T(EXPAND_AND_QUOTE(PLUGIN_BUILD_REFERENCE))))); +#if USE_TELEMETRY + t2_init("networkstats"); +#endif + } + + NetworkConnectionStatsImplementation::~NetworkConnectionStatsImplementation() + { + NSLOG_INFO("NetworkConnectionStatsImplementation Destructor"); + + // Stop subscription retry thread + _stopSubscriptionRetry = true; + if (_subscriptionRetryThread.joinable()) { + _subscriptionRetryThread.join(); + } + + // Stop reporting threads + _stopReporting = true; + + // Push STOP message to unblock consumer thread + { + std::lock_guard lock(_queueMutex); + _messageQueue.push({MessageType::STOP}); + } + _queueCondition.notify_one(); + + // Wait for threads to finish + if (_timerThread.joinable()) { + _timerThread.join(); + } + if (_reportingThread.joinable()) { + _reportingThread.join(); + } + + if (m_provider) { + delete m_provider; + m_provider = nullptr; + } + } + + /** + * Register a notification callback + */ + uint32_t NetworkConnectionStatsImplementation::Register(INetworkConnectionStats::INotification* notification) + { + if (nullptr == notification) { + NSLOG_ERROR("Register: notification parameter is nullptr"); + return Core::ERROR_BAD_REQUEST; + } + + NSLOG_INFO("Register::Enter"); + _notificationLock.Lock(); + + // Make sure we can't register the same notification callback multiple times + if (std::find(_notificationCallbacks.begin(), _notificationCallbacks.end(), notification) == _notificationCallbacks.end()) { + _notificationCallbacks.push_back(notification); + notification->AddRef(); + } + + _notificationLock.Unlock(); + + return Core::ERROR_NONE; + } + + /** + * Unregister a notification callback + */ + uint32_t NetworkConnectionStatsImplementation::Unregister(INetworkConnectionStats::INotification* notification) + { + if (nullptr == notification) { + NSLOG_ERROR("Unregister: notification parameter is nullptr"); + return Core::ERROR_BAD_REQUEST; + } + + NSLOG_INFO("Unregister::Enter"); + _notificationLock.Lock(); + + // Remove the notification callback if it exists + auto itr = std::find(_notificationCallbacks.begin(), _notificationCallbacks.end(), notification); + if (itr != _notificationCallbacks.end()) { + (*itr)->Release(); + _notificationCallbacks.erase(itr); + } + + _notificationLock.Unlock(); + + return Core::ERROR_NONE; + } + + + uint32_t NetworkConnectionStatsImplementation::Configure(const string configLine) + { + NSLOG_INFO("NetworkConnectionStatsImplementation::Configure"); + uint32_t result = Core::ERROR_NONE; + + // Parse configuration + JsonObject config; + config.FromString(configLine); + + if (config.HasLabel("reportingInterval")) { + _reportingIntervalSeconds = config["reportingInterval"].Number(); + NSLOG_INFO("Reporting interval set to %u seconds (%u minutes)", + _reportingIntervalSeconds.load(), _reportingIntervalSeconds.load() / 60); + } + + if (config.HasLabel("autoStart")) { + _periodicReportingEnabled = config["autoStart"].Boolean(); + NSLOG_INFO("Auto-start set to %s", _periodicReportingEnabled ? "true" : "false"); + } + + // Get provider type from configuration (default: comrpc) + std::string providerType = "comrpc"; + if (config.HasLabel("providerType")) { + providerType = config["providerType"].Value(); + } + + // Create network provider using factory + m_provider = NetworkDataProviderFactory::CreateProvider(providerType); + if (m_provider == nullptr) { + NSLOG_ERROR("Failed to create network provider for type: %s", providerType.c_str()); + result = Core::ERROR_GENERAL; + } else { + auto factoryType = NetworkDataProviderFactory::ParseProviderType(providerType); + std::string typeName = NetworkDataProviderFactory::GetProviderTypeName(factoryType); + NSLOG_INFO("%s provider created", typeName.c_str()); + + // Initialize the provider + if (m_provider->Initialize()) { + NSLOG_INFO("%s provider initialized successfully", typeName.c_str()); + + // Subscribe to NetworkManager events (with automatic retry) + _stopSubscriptionRetry = false; + subscribeToEvents(); + _subscriptionRetryThread = std::thread(&NetworkConnectionStatsImplementation::subscriptionRetryThread, this); + + // Generate initial report + generateReport(); + + // Start timer and consumer threads if enabled + if (_periodicReportingEnabled) { + _stopReporting = false; + _timerThread = std::thread(&NetworkConnectionStatsImplementation::timerThread, this); + _reportingThread = std::thread(&NetworkConnectionStatsImplementation::periodicReportingThread, this); + NSLOG_INFO("Timer and reporting threads started with %u second interval (%u minutes)", + _reportingIntervalSeconds.load(), _reportingIntervalSeconds.load() / 60); + } + } else { + auto factoryType = NetworkDataProviderFactory::ParseProviderType(providerType); + std::string typeName = NetworkDataProviderFactory::GetProviderTypeName(factoryType); + NSLOG_ERROR("Failed to initialize %s provider", typeName.c_str()); + result = Core::ERROR_GENERAL; + } + } + + return result; + } + + void NetworkConnectionStatsImplementation::timerThread() + { + NSLOG_INFO("Timer thread started"); + + while (!_stopReporting && _periodicReportingEnabled) { + // Sleep for configured interval + std::this_thread::sleep_for(std::chrono::seconds(_reportingIntervalSeconds.load())); + + if (_stopReporting || !_periodicReportingEnabled) { + break; + } + + // Queue report generation + queueReportGeneration("Timer"); + } + + NSLOG_INFO("Timer thread stopped"); + } + + void NetworkConnectionStatsImplementation::periodicReportingThread() + { + NSLOG_INFO("Periodic reporting thread (consumer) started"); + + while (!_stopReporting && _periodicReportingEnabled) { + Message msg; + + // Wait for message in queue + { + std::unique_lock lock(_queueMutex); + _queueCondition.wait(lock, [this] { + return !_messageQueue.empty() || _stopReporting; + }); + + if (_stopReporting) { + break; + } + + if (_messageQueue.empty()) { + continue; + } + + msg = _messageQueue.front(); + _messageQueue.pop(); + } + + // Process message + if (msg.type == MessageType::GENERATE_REPORT) { + NSLOG_INFO("Consumer: Processing GENERATE_REPORT message"); + generateReport(); + NSLOG_INFO("Consumer: Periodic report generated"); + } else if (msg.type == MessageType::STOP) { + NSLOG_INFO("Consumer: Received STOP message"); + break; + } + } + + NSLOG_INFO("Periodic reporting thread (consumer) stopped"); + } + + void NetworkConnectionStatsImplementation::generateReport() + { + NSLOG_INFO("Generating network diagnostics report"); + + _adminLock.Lock(); + + // Run all diagnostic checks + connectionTypeCheck(); + connectionIpCheck(); + defaultIpv4RouteCheck(); + defaultIpv6RouteCheck(); + networkDnsCheck(); + gatewayPacketLossCheck(); + + _adminLock.Unlock(); + + NSLOG_INFO("Network diagnostics report completed"); + } + + void NetworkConnectionStatsImplementation::logTelemetry(const std::string& eventName, const std::string& message) + { + NSLOG_INFO("NS_T2: %s:%s", eventName.c_str(), message.c_str()); +#if USE_TELEMETRY + T2ERROR t2error = t2_event_s(eventName.c_str(), (char*)message.c_str()); + if (t2error != T2ERROR_SUCCESS) { + NSLOG_ERROR("t2_event_s(\"%s\", \"%s\") failed with error %d", + eventName.c_str(), message.c_str(), t2error); + } +#endif + } + + void NetworkConnectionStatsImplementation::connectionTypeCheck() + { + if (m_provider) { + std::string connType = m_provider->getConnectionType(); + NSLOG_INFO("Connection type: %s", connType.c_str()); + logTelemetry("Connection_Type", connType); + } + } + + void NetworkConnectionStatsImplementation::connectionIpCheck() + { + if (m_provider) { + m_interface = m_provider->getInterface(); + + // Get IPv4 settings + m_ipv4Address = m_provider->getIpv4Address(m_interface); + m_ipv4Route = m_provider->getIpv4Gateway(); + m_ipv4Dns = m_provider->getIpv4PrimaryDns(); + + // Get IPv6 settings + m_ipv6Address = m_provider->getIpv6Address(m_interface); + m_ipv6Route = m_provider->getIpv6Gateway(); + m_ipv6Dns = m_provider->getIpv6PrimaryDns(); + + NSLOG_INFO("Interface: %s, IPv4: %s, IPv6: %s", + m_interface.c_str(), m_ipv4Address.c_str(), m_ipv6Address.c_str()); + NSLOG_INFO("IPv4 Gateway: %s, DNS: %s", + m_ipv4Route.c_str(), m_ipv4Dns.c_str()); + NSLOG_INFO("IPv6 Gateway: %s, DNS: %s", + m_ipv6Route.c_str(), m_ipv6Dns.c_str()); + + // Log telemetry events + logTelemetry("Network_Interface", m_interface); + logTelemetry("Network_IPv4_Address", m_ipv4Address); + logTelemetry("Network_IPv6_Address", m_ipv6Address); + logTelemetry("IPv4_DNS", m_ipv4Dns); + logTelemetry("IPv6_DNS", m_ipv6Dns); + } + } + + void NetworkConnectionStatsImplementation::defaultIpv4RouteCheck() + { + if (!m_ipv4Address.empty() && m_ipv4Address != "0.0.0.0") { + if (!m_ipv4Route.empty() && m_ipv4Route != "0.0.0.0") { + NSLOG_INFO("IPv4: Interface %s has gateway %s", + m_interface.c_str(), m_ipv4Route.c_str()); + logTelemetry("IPv4_Route_Check", "Success," + m_interface + "," + m_ipv4Route); + } else { + NSLOG_INFO("IPv4: No valid gateway for interface %s", m_interface.c_str()); + logTelemetry("IPv4_Route_Check", "Failed,No valid gateway," + m_interface); + } + } + } + + void NetworkConnectionStatsImplementation::defaultIpv6RouteCheck() + { + if (!m_ipv6Address.empty() && m_ipv6Address != "::") { + if (!m_ipv6Route.empty() && m_ipv6Route != "::") { + NSLOG_INFO("IPv6: Interface %s has gateway %s", + m_interface.c_str(), m_ipv6Route.c_str()); + logTelemetry("IPv6_Route_Check", "Success," + m_interface + "," + m_ipv6Route); + } else { + NSLOG_INFO("IPv6: No valid gateway for interface %s", m_interface.c_str()); + logTelemetry("IPv6_Route_Check", "Failed,No valid gateway," + m_interface); + } + } + } + + void NetworkConnectionStatsImplementation::gatewayPacketLossCheck() + { + if (!m_provider) { + return; + } + + // Check IPv4 gateway packet loss + if (!m_ipv4Route.empty() && m_ipv4Route != "0.0.0.0") { + NSLOG_INFO("Pinging IPv4 gateway: %s", m_ipv4Route.c_str()); + bool success = m_provider->pingToGatewayCheck(m_ipv4Route, "IPv4", 10, 30); + std::string packetLoss = m_provider->getPacketLoss(); + std::string avgRtt = m_provider->getAvgRtt(); + if (success) { + NSLOG_INFO("IPv4 gateway ping - Loss: %s%%, RTT: %sms", + packetLoss.c_str(), avgRtt.c_str()); + + logTelemetry("IPv4_Gateway_Packet_Loss", packetLoss); + logTelemetry("IPv4_Gateway_RTT", avgRtt); + } else { + NSLOG_ERROR("IPv4 gateway ping failed"); + logTelemetry("IPv4_Gateway_Packet_Loss", packetLoss); + logTelemetry("IPv4_Gateway_RTT", avgRtt); + } + } + + // Check IPv6 gateway packet loss + if (!m_ipv6Route.empty() && m_ipv6Route != "::") { + // Append zone ID for link-local IPv6 addresses + std::string ipv6Gateway = m_ipv6Route; + if (ipv6Gateway.find("fe80::") == 0 && !m_interface.empty()) { + ipv6Gateway += "%" + m_interface; + } + NSLOG_INFO("Pinging IPv6 gateway: %s", ipv6Gateway.c_str()); + bool success = m_provider->pingToGatewayCheck(ipv6Gateway, "IPv6", 10, 30); + std::string packetLoss = m_provider->getPacketLoss(); + std::string avgRtt = m_provider->getAvgRtt(); + if (success) { + NSLOG_INFO("IPv6 gateway ping - Loss: %s%%, RTT: %sms", + packetLoss.c_str(), avgRtt.c_str()); + + logTelemetry("IPv6_Gateway_Packet_Loss", packetLoss); + logTelemetry("IPv6_Gateway_RTT", avgRtt); + } else { + NSLOG_ERROR("IPv6 gateway ping failed"); + logTelemetry("IPv6_Gateway_Packet_Loss", packetLoss); + logTelemetry("IPv6_Gateway_RTT", avgRtt); + } + } + } + + void NetworkConnectionStatsImplementation::networkDnsCheck() + { + bool hasDns = false; + + if (!m_ipv4Dns.empty()) { + NSLOG_INFO("IPv4 DNS: %s", m_ipv4Dns.c_str()); + hasDns = true; + } + + if (!m_ipv6Dns.empty()) { + NSLOG_INFO("IPv6 DNS: %s", m_ipv6Dns.c_str()); + hasDns = true; + } + + if (hasDns) { + NSLOG_INFO("DNS configuration present"); + logTelemetry("DNS_Status", "DNS configured"); + } else { + NSLOG_WARNING("No DNS configuration found"); + logTelemetry("DNS_Status", "No DNS configured"); + } + } + + /** NetworkManager Event Subscription */ + void NetworkConnectionStatsImplementation::subscribeToEvents() + { + uint32_t errCode = Core::ERROR_GENERAL; + if (m_provider) + { + if (!m_subsIfaceStateChange) + { + errCode = m_provider->SubscribeToEvent("onInterfaceStateChange", + [this](const WPEFramework::Core::JSON::VariantContainer& parameters) { + this->ReportonInterfaceStateChange(parameters); + }); + if (Core::ERROR_NONE == errCode) + m_subsIfaceStateChange = true; + else + NSLOG_ERROR("Subscribe to onInterfaceStateChange failed, errCode: %u", errCode); + } + + if (!m_subsActIfaceChange) + { + errCode = m_provider->SubscribeToEvent("onActiveInterfaceChange", + [this](const WPEFramework::Core::JSON::VariantContainer& parameters) { + this->ReportonActiveInterfaceChange(parameters); + }); + if (Core::ERROR_NONE == errCode) + m_subsActIfaceChange = true; + else + NSLOG_ERROR("Subscribe to onActiveInterfaceChange failed, errCode: %u", errCode); + } + + if (!m_subsIPAddrChange) + { + errCode = m_provider->SubscribeToEvent("onIPAddressChange", + [this](const WPEFramework::Core::JSON::VariantContainer& parameters) { + this->ReportonIPAddressChange(parameters); + }); + if (Core::ERROR_NONE == errCode) + m_subsIPAddrChange = true; + else + NSLOG_ERROR("Subscribe to onIPAddressChange failed, errCode: %u", errCode); + } + } + else + NSLOG_ERROR("m_provider is null"); + } + + void NetworkConnectionStatsImplementation::subscriptionRetryThread() + { + NSLOG_INFO("Subscription retry thread started"); + + while (!_stopSubscriptionRetry) + { + // Check if all subscriptions are successful + if (m_subsIfaceStateChange && m_subsActIfaceChange && m_subsIPAddrChange) + { + NSLOG_INFO("All required events subscribed; Stopping retry thread"); + break; + } + + // Wait before retry + std::this_thread::sleep_for(std::chrono::milliseconds(SUBSCRIPTION_RETRY_INTERVAL_MS)); + + if (_stopSubscriptionRetry) + break; + + // Retry subscription + NSLOG_INFO("Retrying event subscriptions..."); + subscribeToEvents(); + } + + NSLOG_INFO("Subscription retry thread stopped"); + } + + /** Event Handling - Report methods */ + void NetworkConnectionStatsImplementation::ReportonInterfaceStateChange(const WPEFramework::Core::JSON::VariantContainer& parameters) + { + queueReportGeneration("Event: onInterfaceStateChange", ¶meters); + } + + void NetworkConnectionStatsImplementation::ReportonActiveInterfaceChange(const WPEFramework::Core::JSON::VariantContainer& parameters) + { + queueReportGeneration("Event: onActiveInterfaceChange", ¶meters); + } + + void NetworkConnectionStatsImplementation::ReportonIPAddressChange(const WPEFramework::Core::JSON::VariantContainer& parameters) + { + queueReportGeneration("Event: onIPAddressChange", ¶meters); + } + + void NetworkConnectionStatsImplementation::queueReportGeneration(const std::string& source, const WPEFramework::Core::JSON::VariantContainer* parameters) + { + // Log event parameters if provided + if (parameters != nullptr) { + string json; + parameters->ToString(json); + NSLOG_INFO("%s - params: %s", source.c_str(), json.c_str()); + } + + // Queue report generation + std::lock_guard lock(_queueMutex); + _messageQueue.push({MessageType::GENERATE_REPORT}); + NSLOG_INFO("%s: Pushed GENERATE_REPORT to queue", source.c_str()); + _queueCondition.notify_one(); + } + +} // namespace Plugin +} // namespace WPEFramework diff --git a/networkstats/plugin/NetworkConnectionStatsImplementation.h b/networkstats/plugin/NetworkConnectionStatsImplementation.h new file mode 100644 index 00000000..c790ee8b --- /dev/null +++ b/networkstats/plugin/NetworkConnectionStatsImplementation.h @@ -0,0 +1,134 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2025 RDK Management +* +* 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. +**/ + +#pragma once + +#include "Module.h" +#include "INetworkConnectionStats.h" +#include "INetworkData.h" +#include "NetworkDataProviderFactory.h" + +#include +#include +#include +#include +#include + +#if USE_TELEMETRY +#include +#endif + +namespace WPEFramework { +namespace Plugin { + + // Internal implementation - runs network diagnostics automatically + // No external APIs exposed + class NetworkConnectionStatsImplementation + : public Exchange::INetworkConnectionStats { + + public: + + NetworkConnectionStatsImplementation(const NetworkConnectionStatsImplementation&) = delete; + NetworkConnectionStatsImplementation& operator=(const NetworkConnectionStatsImplementation&) = delete; + NetworkConnectionStatsImplementation(); + virtual ~NetworkConnectionStatsImplementation(); + + BEGIN_INTERFACE_MAP(NetworkConnectionStatsImplementation) + INTERFACE_ENTRY(Exchange::INetworkConnectionStats) + END_INTERFACE_MAP + + // Handle Notification registration/removal + uint32_t Register(INetworkConnectionStats::INotification* notification) override; + uint32_t Unregister(INetworkConnectionStats::INotification* notification) override; + + // Configuration method + uint32_t Configure(const string configLine) override; + + private: + // Message queue types + enum class MessageType { + GENERATE_REPORT, + STOP + }; + + struct Message { + MessageType type; + }; + + // Internal diagnostic methods + void connectionTypeCheck(); + void connectionIpCheck(); + void defaultIpv4RouteCheck(); + void defaultIpv6RouteCheck(); + void gatewayPacketLossCheck(); + void networkDnsCheck(); + void generateReport(); + void logTelemetry(const std::string& eventName, const std::string& message); + void timerThread(); + void periodicReportingThread(); + + // NetworkManager event subscription + void subscribeToEvents(); + void subscriptionRetryThread(); + void ReportonInterfaceStateChange(const WPEFramework::Core::JSON::VariantContainer& parameters); + void ReportonActiveInterfaceChange(const WPEFramework::Core::JSON::VariantContainer& parameters); + void ReportonIPAddressChange(const WPEFramework::Core::JSON::VariantContainer& parameters); + void queueReportGeneration(const std::string& source, const WPEFramework::Core::JSON::VariantContainer* parameters = nullptr); + + private: + mutable Core::CriticalSection _adminLock; + + // Notification callbacks + std::list _notificationCallbacks; + Core::CriticalSection _notificationLock; + + // Network data provider + INetworkData* m_provider; + + // Cached network data + std::string m_interface; + std::string m_ipv4Address; + std::string m_ipv6Address; + std::string m_ipv4Route; + std::string m_ipv6Route; + std::string m_ipv4Dns; + std::string m_ipv6Dns; + + // Periodic reporting + std::atomic _periodicReportingEnabled; + std::atomic _reportingIntervalSeconds; + std::thread _timerThread; + std::thread _reportingThread; + std::atomic _stopReporting; + + // Message queue system + std::queue _messageQueue; + std::mutex _queueMutex; + std::condition_variable _queueCondition; + + // NetworkManager event subscription + std::thread _subscriptionRetryThread; + std::atomic _stopSubscriptionRetry; + bool m_subsIfaceStateChange; + bool m_subsActIfaceChange; + bool m_subsIPAddrChange; + }; + +} // namespace Plugin +} // namespace WPEFramework diff --git a/networkstats/plugin/NetworkConnectionStatsLogger.cpp b/networkstats/plugin/NetworkConnectionStatsLogger.cpp new file mode 100644 index 00000000..13fc6375 --- /dev/null +++ b/networkstats/plugin/NetworkConnectionStatsLogger.cpp @@ -0,0 +1,139 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2025 RDK Management +* +* 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 "NetworkConnectionStatsLogger.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef USE_RDK_LOGGER +#include "rdk_debug.h" +#endif + +namespace NetworkConnectionStatsLogger { + static LogLevel gDefaultLogLevel = INFO_LEVEL; + + +#ifdef USE_RDK_LOGGER + rdk_LogLevel mapTordkLogLevel(LogLevel level) + { + rdk_LogLevel rdklevel = RDK_LOG_INFO; + switch (level) + { + case FATAL_LEVEL: + rdklevel = RDK_LOG_FATAL; + break; + case ERROR_LEVEL: + rdklevel = RDK_LOG_ERROR; + break; + case WARNING_LEVEL: + rdklevel = RDK_LOG_WARN; + break; + case INFO_LEVEL: + rdklevel = RDK_LOG_INFO; + break; + case DEBUG_LEVEL: + rdklevel = RDK_LOG_DEBUG; + break; + } + return rdklevel; + } +#endif + + const char* trimPath(const char* s) + { + if (!s) + return s; + + const char* t = strrchr(s, (int) '/'); + if (t) t++; + if (!t) t = s; + + return t; + } + + void Init() + { +#ifdef USE_RDK_LOGGER + rdk_logger_init(0 == access("/opt/debug.ini", R_OK) ? "/opt/debug.ini" : "/etc/debug.ini"); +#endif + } + + void logPrint(LogLevel level, const char* file, const char* func, int line, const char* format, ...) + { + size_t n = 0; + const short kFormatMessageSize = 1024; + char formattedLog[kFormatMessageSize] = {0}; + + va_list args; + + va_start(args, format); + n = vsnprintf(formattedLog, (kFormatMessageSize - 1), format, args); + va_end(args); + + if (n > (kFormatMessageSize - 1)) + { + formattedLog[kFormatMessageSize - 4] = '.'; + formattedLog[kFormatMessageSize - 3] = '.'; + formattedLog[kFormatMessageSize - 2] = '.'; + } + formattedLog[kFormatMessageSize - 1] = '\0'; +#ifdef USE_RDK_LOGGER + RDK_LOG(mapTordkLogLevel(level), "LOG.RDK.NETSTATS", "%s\n", formattedLog); +#else + const char* levelMap[] = {"Fatal", "Error", "Warn", "Info", "Debug"}; + struct timeval tv; + struct tm* lt; + const char* fileName = trimPath(file); + + if (gDefaultLogLevel < level) + return; + + gettimeofday(&tv, NULL); + lt = localtime(&tv.tv_sec); + + printf("%.2d:%.2d:%.2d.%.6lld [%-5s] [PID=%d] [TID=%d] [%s +%d] %s : %s\n", lt->tm_hour, lt->tm_min, lt->tm_sec, (long long int)tv.tv_usec, levelMap[level], getpid(), gettid(), fileName, line, func, formattedLog); + fflush(stdout); +#endif + } + + void SetLevel(LogLevel level) + { + gDefaultLogLevel = level; + NSLOG_INFO("NetworkConnectionStats logLevel:%d", level); +#ifdef USE_RDK_LOGGER + // TODO : Inform RDKLogger to change the log level +#endif + } + + void GetLevel(LogLevel& level) + { + level = gDefaultLogLevel; +#ifdef USE_RDK_LOGGER + // TODO : Inform RDKLogger to change the log level +#endif + } +} // namespace NetworkConnectionStatsLogger diff --git a/networkstats/plugin/NetworkConnectionStatsLogger.h b/networkstats/plugin/NetworkConnectionStatsLogger.h new file mode 100644 index 00000000..283400b4 --- /dev/null +++ b/networkstats/plugin/NetworkConnectionStatsLogger.h @@ -0,0 +1,73 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2025 RDK Management +* +* 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 NS_LOGGER_H +#define NS_LOGGER_H + +#include +#include +#include +#include +#include +#include + +namespace NetworkConnectionStatsLogger { +/** + * Logging level with an increasing order of refinement + * (DEBUG_LEVEL = Finest logging) + * It is essental to start with 0 and increase w/o gaps as the value + * can be used for indexing in a mapping table. + */ +enum LogLevel {FATAL_LEVEL = 0, ERROR_LEVEL, WARNING_LEVEL, INFO_LEVEL, DEBUG_LEVEL}; + +/** + * @brief Init logging + * Should be called once per program run before calling log-functions + */ +void Init(); + +/** + * @brief To set log level while runtime + */ +void SetLevel(LogLevel level); + +/** + * @brief To get log level while runtime + */ +void GetLevel(LogLevel& level); + +/** + * @brief Log a message + * The function is defined by logging backend. + * Currently 2 variants are supported: RDKLOGGER & stdout(default) + */ +void logPrint(LogLevel level, const char* file, const char* func, int line, const char* format, ...) __attribute__ ((format (printf, 5, 6))); + + +#define NSLOG_DEBUG(FMT, ...) logPrint(NetworkConnectionStatsLogger::DEBUG_LEVEL, __FILE__, __func__, __LINE__, FMT, ##__VA_ARGS__) +#define NSLOG_INFO(FMT, ...) logPrint(NetworkConnectionStatsLogger::INFO_LEVEL, __FILE__, __func__, __LINE__, FMT, ##__VA_ARGS__) +#define NSLOG_WARNING(FMT, ...) logPrint(NetworkConnectionStatsLogger::WARNING_LEVEL, __FILE__, __func__, __LINE__, FMT, ##__VA_ARGS__) +#define NSLOG_ERROR(FMT, ...) logPrint(NetworkConnectionStatsLogger::ERROR_LEVEL, __FILE__, __func__, __LINE__, FMT, ##__VA_ARGS__) +#define NSLOG_FATAL(FMT, ...) logPrint(NetworkConnectionStatsLogger::FATAL_LEVEL, __FILE__,__func__, __LINE__, FMT, ##__VA_ARGS__) + +#define LOG_ENTRY_FUNCTION() { NSLOG_DEBUG("Entering"); } + +} // namespace NetworkConnectionStatsLogger + +#endif // NS_LOGGER_H diff --git a/networkstats/plugin/NetworkDataProviderFactory.h b/networkstats/plugin/NetworkDataProviderFactory.h new file mode 100644 index 00000000..0a4c33ae --- /dev/null +++ b/networkstats/plugin/NetworkDataProviderFactory.h @@ -0,0 +1,120 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2025 RDK Management +* +* 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. +**/ + +#pragma once + +#include "INetworkData.h" +#include "ThunderComRPCProvider.h" +#include "ThunderJsonRPCProvider.h" +#include +#include +#include + +namespace WPEFramework { +namespace Plugin { + + /** + * @brief Factory class for creating network data providers + * + * This factory creates the appropriate provider instance based on configuration. + * Supports both COM-RPC and JSON-RPC providers. + */ + class NetworkDataProviderFactory { + public: + enum class ProviderType { + COMRPC, + JSONRPC, + UNKNOWN + }; + + /** + * @brief Create a network data provider based on type string + * @param providerType String identifying the provider type ("comrpc" or "jsonrpc") + * @return Pointer to INetworkData implementation, or nullptr on failure + */ + static INetworkData* CreateProvider(const std::string& providerType) { + ProviderType type = ParseProviderType(providerType); + return CreateProvider(type); + } + + /** + * @brief Create a network data provider based on enum type + * @param type ProviderType enum value + * @return Pointer to INetworkData implementation, or nullptr on failure + */ + static INetworkData* CreateProvider(ProviderType type) { + INetworkData* provider = nullptr; + + switch (type) { + case ProviderType::COMRPC: + provider = new NetworkComRPCProvider(); + break; + + case ProviderType::JSONRPC: + provider = new NetworkJsonRPCProvider(); + break; + + case ProviderType::UNKNOWN: + default: + // Return nullptr for unknown types + break; + } + + return provider; + } + + /** + * @brief Parse provider type string to enum + * @param providerTypeStr String to parse (case-insensitive) + * @return ProviderType enum value + */ + static ProviderType ParseProviderType(const std::string& providerTypeStr) { + std::string lowerType = providerTypeStr; + std::transform(lowerType.begin(), lowerType.end(), lowerType.begin(), + [](unsigned char c) { return std::tolower(c); }); + + if (lowerType == "comrpc" || lowerType == "com-rpc") { + return ProviderType::COMRPC; + } else if (lowerType == "jsonrpc" || lowerType == "json-rpc") { + return ProviderType::JSONRPC; + } else { + return ProviderType::UNKNOWN; + } + } + + /** + * @brief Get string representation of provider type + * @param type ProviderType enum value + * @return String name of the provider type + */ + static std::string GetProviderTypeName(ProviderType type) { + switch (type) { + case ProviderType::COMRPC: + return "COM-RPC"; + case ProviderType::JSONRPC: + return "JSON-RPC"; + case ProviderType::UNKNOWN: + default: + return "Unknown"; + } + } + }; + +} // namespace Plugin +} // namespace WPEFramework diff --git a/networkstats/plugin/ThunderComRPCProvider.cpp b/networkstats/plugin/ThunderComRPCProvider.cpp new file mode 100644 index 00000000..5f9ac9db --- /dev/null +++ b/networkstats/plugin/ThunderComRPCProvider.cpp @@ -0,0 +1,555 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2025 RDK Management +* +* 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 "ThunderComRPCProvider.h" +#include "NetworkConnectionStatsLogger.h" +#include + +using namespace std; +using namespace NetworkConnectionStatsLogger; + +/* @brief Constructor */ +NetworkComRPCProvider::NetworkComRPCProvider() + : m_networkManagerClient(nullptr) +{ + NSLOG_INFO("NetworkComRPCProvider constructed"); +} + +/* @brief Destructor */ +NetworkComRPCProvider::~NetworkComRPCProvider() +{ + m_networkManagerClient.reset(); +} + +/* @brief Initialize the provider with Thunder COM-RPC connection */ +bool NetworkComRPCProvider::Initialize() +{ + try { + NSLOG_INFO("Creating JSON-RPC client for callsign: %s over COM-RPC", NETWORK_MANAGER_CALLSIGN); + + // For COM-RPC, don't set THUNDER_ACCESS - let it use the default connection + // The plugin framework handles the routing automatically + m_networkManagerClient = std::make_shared>( + _T(NETWORK_MANAGER_CALLSIGN) + ); + + if (m_networkManagerClient) { + NSLOG_INFO("NetworkManager COM-RPC client initialized successfully"); + return true; + } else { + NSLOG_ERROR("Failed to create NetworkManager COM-RPC client"); + return false; + } + } + catch (const std::exception& e) { + NSLOG_ERROR("Exception during initialization: %s", e.what()); + return false; + } +} + +/* @brief Retrieve IPv4 address for specified interface */ +std::string NetworkComRPCProvider::getIpv4Address(std::string interface_name) +{ + std::string ipv4_address = ""; + + // Initialize member variables + m_ipv4Gateway = ""; + m_ipv4PrimaryDns = ""; + + if (!m_networkManagerClient) { + NSLOG_ERROR("NetworkManager client not initialized"); + return ""; + } + + WPEFramework::Core::JSON::VariantContainer params; + params["interface"] = interface_name; + params["ipversion"] = "IPv4"; + + WPEFramework::Core::JSON::VariantContainer result; + + uint32_t rc = m_networkManagerClient->Invoke( + 5000, "GetIPSettings", params, result); + + if (rc == WPEFramework::Core::ERROR_NONE) { + if (result.HasLabel("ipaddress")) { + ipv4_address = result["ipaddress"].String(); + NSLOG_INFO("IPv4 address retrieved: %s", ipv4_address.c_str()); + } + if (result.HasLabel("gateway")) { + m_ipv4Gateway = result["gateway"].String(); + NSLOG_INFO("IPv4 Route: %s", m_ipv4Gateway.c_str()); + } + if (result.HasLabel("primarydns")) { + m_ipv4PrimaryDns = result["primarydns"].String(); + NSLOG_INFO("DNS entry: %s", m_ipv4PrimaryDns.c_str()); + } + } else { + NSLOG_ERROR("GetIPSettings COM-RPC call failed with error code: %u", rc); + } + + return ipv4_address; +} + +/* @brief Retrieve IPv6 address for specified interface */ +std::string NetworkComRPCProvider::getIpv6Address(std::string interface_name) +{ + std::string ipv6_address = ""; + + // Initialize member variables + m_ipv6Gateway = ""; + m_ipv6PrimaryDns = ""; + + if (!m_networkManagerClient) { + NSLOG_ERROR("NetworkManager client not initialized"); + return ""; + } + + WPEFramework::Core::JSON::VariantContainer params; + params["interface"] = interface_name; + params["ipversion"] = "IPv6"; + + WPEFramework::Core::JSON::VariantContainer result; + + uint32_t rc = m_networkManagerClient->Invoke( + 5000, "GetIPSettings", params, result); + + if (rc == WPEFramework::Core::ERROR_NONE) { + if (result.HasLabel("ipaddress")) { + ipv6_address = result["ipaddress"].String(); + NSLOG_INFO("IPv6 address retrieved: %s", ipv6_address.c_str()); + } + if (result.HasLabel("gateway")) { + m_ipv6Gateway = result["gateway"].String(); + NSLOG_INFO("IPv6 Route: %s", m_ipv6Gateway.c_str()); + } + if (result.HasLabel("primarydns")) { + m_ipv6PrimaryDns = result["primarydns"].String(); + NSLOG_INFO("DNS entry: %s", m_ipv6PrimaryDns.c_str()); + } + } else { + NSLOG_ERROR("GetIPSettings COM-RPC call failed with error code: %u", rc); + } + + return ipv6_address; +} + +/* @brief Get current network connection type */ +std::string NetworkComRPCProvider::getConnectionType() +{ + std::string interface = getInterface(); + + if (interface.empty()) { + NSLOG_ERROR("Error: Unable to retrieve interface"); + return ""; + } + + std::string connectionType; + + // Check if interface starts with "eth" or "eno" + if (interface.find("eth") == 0 || interface.find("eno") == 0) { + connectionType = "Ethernet"; + NSLOG_INFO("Connection type: %s (interface: %s)", connectionType.c_str(), interface.c_str()); + } + // Check if interface starts with "wlan" + else if (interface.find("wlan") == 0) { + connectionType = "WiFi"; + NSLOG_INFO("Connection type: %s (interface: %s)", connectionType.c_str(), interface.c_str()); + } + else { + connectionType = "Unknown"; + NSLOG_INFO("Connection type: %s (interface: %s)", connectionType.c_str(), interface.c_str()); + } + + return connectionType; +} + +/* @brief Get DNS server entries */ +std::string NetworkComRPCProvider::getDnsEntries() +{ + // Return combined DNS entries from IPv4 and IPv6 + std::string dnsEntries = ""; + + if (!m_ipv4PrimaryDns.empty()) { + dnsEntries += m_ipv4PrimaryDns; + } + + if (!m_ipv6PrimaryDns.empty()) { + if (!dnsEntries.empty()) { + dnsEntries += ","; + } + dnsEntries += m_ipv6PrimaryDns; + } + + return dnsEntries; +} + +/* @brief Populate network interface data */ +void NetworkComRPCProvider::populateNetworkData() +{ + std::string interface = getInterface(); + + if (!interface.empty()) { + getIpv4Address(interface); + getIpv6Address(interface); + NSLOG_INFO("Network data populated for interface: %s", interface.c_str()); + } +} + +/* @brief Get current active interface name */ +std::string NetworkComRPCProvider::getInterface() +{ + if (!m_networkManagerClient) { + NSLOG_ERROR("NetworkManager client not initialized"); + return ""; + } + + WPEFramework::Core::JSON::VariantContainer params; + WPEFramework::Core::JSON::VariantContainer result; + + uint32_t rc = m_networkManagerClient->Invoke( + 5000, "GetPrimaryInterface", params, result); + + std::string interface = ""; + if (rc == WPEFramework::Core::ERROR_NONE) { + if (result.HasLabel("interface")) { + interface = result["interface"].String(); + NSLOG_INFO("Primary interface retrieved: %s", interface.c_str()); + } + } else { + NSLOG_ERROR("GetPrimaryInterface COM-RPC call failed with error code: %u", rc); + } + + return interface; +} + +/* @brief Ping to gateway to check packet loss */ +bool NetworkComRPCProvider::pingToGatewayCheck(std::string endpoint, std::string ipversion, int count, int timeout) +{ + // Initialize member variables + m_packetLoss = ""; + m_avgRtt = ""; + + if (!m_networkManagerClient) { + NSLOG_ERROR("NetworkManager client not initialized"); + return false; + } + + WPEFramework::Core::JSON::VariantContainer params; + params["endpoint"] = endpoint; + params["ipversion"] = ipversion; + params["count"] = count; + params["timeout"] = timeout; + params["guid"] = "network-stats-ping"; + + WPEFramework::Core::JSON::VariantContainer result; + + uint32_t rc = m_networkManagerClient->Invoke( + 30000, "Ping", params, result); + + if (rc != WPEFramework::Core::ERROR_NONE) { + NSLOG_ERROR("Ping COM-RPC call failed with error code: %u", rc); + return false; + } + + NSLOG_INFO("Ping request sent to %s (%s)", endpoint.c_str(), ipversion.c_str()); + + // Check if ping was successful + if (result.HasLabel("success")) { + bool success = result["success"].Boolean(); + + // Extract ping statistics + if (result.HasLabel("packetsTransmitted")) { + NSLOG_INFO("Packets transmitted: %d", static_cast(result["packetsTransmitted"].Number())); + } + if (result.HasLabel("packetsReceived")) { + NSLOG_INFO("Packets received: %d", static_cast(result["packetsReceived"].Number())); + } + if (result.HasLabel("packetLoss")) { + m_packetLoss = result["packetLoss"].String(); + NSLOG_INFO("Packet loss: %s", m_packetLoss.c_str()); + } + if (result.HasLabel("tripMin")) { + NSLOG_INFO("RTT min: %s ms", result["tripMin"].String().c_str()); + } + if (result.HasLabel("tripAvg")) { + m_avgRtt = result["tripAvg"].String(); + NSLOG_INFO("RTT avg: %s ms", m_avgRtt.c_str()); + } + if (result.HasLabel("tripMax")) { + NSLOG_INFO("RTT max: %s ms", result["tripMax"].String().c_str()); + } + + return success; + } + else { + NSLOG_ERROR("Error: 'success' field not found in response"); + return false; + } +} + +/* @brief Get IPv4 gateway/route address from last getIpv4Address call */ +std::string NetworkComRPCProvider::getIpv4Gateway() +{ + return m_ipv4Gateway; +} + +/* @brief Get IPv6 gateway/route address from last getIpv6Address call */ +std::string NetworkComRPCProvider::getIpv6Gateway() +{ + return m_ipv6Gateway; +} + +/* @brief Get IPv4 primary DNS from last getIpv4Address call */ +std::string NetworkComRPCProvider::getIpv4PrimaryDns() +{ + return m_ipv4PrimaryDns; +} + +/* @brief Get IPv6 primary DNS from last getIpv6Address call */ +std::string NetworkComRPCProvider::getIpv6PrimaryDns() +{ + return m_ipv6PrimaryDns; +} + +/* @brief Get packet loss from last ping call */ +std::string NetworkComRPCProvider::getPacketLoss() +{ + return m_packetLoss; +} + +/* @brief Get average RTT from last ping call */ +std::string NetworkComRPCProvider::getAvgRtt() +{ + return m_avgRtt; +} + +/* @brief Subscribe to NetworkManager events */ +uint32_t NetworkComRPCProvider::SubscribeToEvent(const std::string& eventName, + std::function callback) +{ + if (!m_networkManagerClient) { + NSLOG_ERROR("NetworkManager client not initialized"); + return WPEFramework::Core::ERROR_UNAVAILABLE; + } + + try { + // Subscribe to the event using Thunder's Subscribe API + // Note: The callback will be invoked when the event is received + uint32_t result = m_networkManagerClient->Subscribe( + 5000, // timeout in milliseconds + eventName, + [callback](const WPEFramework::Core::JSON::VariantContainer& parameters) { + callback(parameters); + } + ); + + if (result == WPEFramework::Core::ERROR_NONE) { + NSLOG_INFO("Successfully subscribed to event: %s", eventName.c_str()); + } else { + NSLOG_ERROR("Failed to subscribe to event %s, error code: %u", eventName.c_str(), result); + } + + return result; + } catch (const std::exception& e) { + NSLOG_ERROR("Exception subscribing to event %s: %s", eventName.c_str(), e.what()); + return WPEFramework::Core::ERROR_GENERAL; + } +} + +#ifdef TEST_MAIN +/* Test main function to validate NetworkComRPCProvider member functions */ +int main() +{ + NetworkComRPCProvider provider; + int choice; + std::string interface_name; + bool running = true; + + std::cout << "\n========================================\n"; + std::cout << "NetworkComRPCProvider Test Suite\n"; + std::cout << "========================================\n"; + + // Initialize COM-RPC connection + std::cout << "\nInitializing COM-RPC connection to NetworkManager...\n"; + if (!provider.Initialize()) { + std::cout << "Warning: Failed to initialize COM-RPC connection.\n"; + std::cout << "Tests may fail if NetworkManager plugin is not running.\n"; + } else { + std::cout << "COM-RPC connection initialized successfully.\n"; + } + + while (running) + { + std::cout << "\nMenu:\n"; + std::cout << "1. Test getIpv4Address()\n"; + std::cout << "2. Test getIpv6Address()\n"; + std::cout << "3. Test getConnectionType()\n"; + std::cout << "4. Test getDnsEntries()\n"; + std::cout << "5. Test populateNetworkData()\n"; + std::cout << "6. Test getInterface()\n"; + std::cout << "7. Test pingToGatewayCheck()\n"; + std::cout << "8. Run all tests\n"; + std::cout << "0. Exit\n"; + std::cout << "\nEnter your choice: "; + std::cin >> choice; + + switch (choice) + { + case 1: + std::cout << "\nTesting getIpv4Address()...\n"; + { + interface_name = provider.getInterface(); + std::cout << "Using interface: " << (interface_name.empty() ? "(empty)" : interface_name) << "\n"; + std::string ipv4 = provider.getIpv4Address(interface_name); + std::cout << "Result: " << (ipv4.empty() ? "(empty)" : ipv4) << "\n"; + std::cout << "Gateway: " << (provider.getIpv4Gateway().empty() ? "(empty)" : provider.getIpv4Gateway()) << "\n"; + std::cout << "Primary DNS: " << (provider.getIpv4PrimaryDns().empty() ? "(empty)" : provider.getIpv4PrimaryDns()) << "\n"; + } + break; + + case 2: + std::cout << "\nTesting getIpv6Address()...\n"; + { + interface_name = provider.getInterface(); + std::cout << "Using interface: " << (interface_name.empty() ? "(empty)" : interface_name) << "\n"; + std::string ipv6 = provider.getIpv6Address(interface_name); + std::cout << "Result: " << (ipv6.empty() ? "(empty)" : ipv6) << "\n"; + std::cout << "Gateway: " << (provider.getIpv6Gateway().empty() ? "(empty)" : provider.getIpv6Gateway()) << "\n"; + std::cout << "Primary DNS: " << (provider.getIpv6PrimaryDns().empty() ? "(empty)" : provider.getIpv6PrimaryDns()) << "\n"; + } + break; + + case 3: + std::cout << "\nTesting getConnectionType()...\n"; + { + std::string connType = provider.getConnectionType(); + std::cout << "Result: " << (connType.empty() ? "(empty)" : connType) << "\n"; + } + break; + + case 4: + std::cout << "\nTesting getDnsEntries()...\n"; + { + std::string dns = provider.getDnsEntries(); + std::cout << "Result: " << (dns.empty() ? "(empty)" : dns) << "\n"; + } + break; + + case 5: + std::cout << "\nTesting populateNetworkData()...\n"; + provider.populateNetworkData(); + std::cout << "Network data population completed\n"; + break; + + case 6: + std::cout << "\nTesting getInterface()...\n"; + { + std::string iface = provider.getInterface(); + std::cout << "Result: " << (iface.empty() ? "(empty)" : iface) << "\n"; + } + break; + + case 7: + std::cout << "\nTesting pingToGatewayCheck()...\n"; + { + std::string gateway; + std::string ipver; + std::cout << "Enter gateway IP (default: 8.8.8.8): "; + std::cin.ignore(); + std::getline(std::cin, gateway); + if (gateway.empty()) gateway = "8.8.8.8"; + + std::cout << "Enter IP version (IPv4/IPv6, default: IPv4): "; + std::getline(std::cin, ipver); + if (ipver.empty()) ipver = "IPv4"; + + bool success = provider.pingToGatewayCheck(gateway, ipver, 5, 30); + std::cout << "Ping " << (success ? "successful" : "failed") << "\n"; + std::cout << "Packet Loss: " << (provider.getPacketLoss().empty() ? "(empty)" : provider.getPacketLoss()) << "\n"; + std::cout << "Average RTT: " << (provider.getAvgRtt().empty() ? "(empty)" : provider.getAvgRtt()) << " ms\n"; + } + break; + + case 8: + std::cout << "\n========================================\n"; + std::cout << "Running All Tests\n"; + std::cout << "========================================\n"; + + std::cout << "\n--- Test 1/7: getInterface() ---\n"; + { + std::string iface = provider.getInterface(); + std::cout << "Result: " << (iface.empty() ? "(empty)" : iface) << "\n"; + } + + std::cout << "\n--- Test 2/7: getIpv4Address() ---\n"; + { + interface_name = provider.getInterface(); + std::string ipv4 = provider.getIpv4Address(interface_name); + std::cout << "Result: " << (ipv4.empty() ? "(empty)" : ipv4) << "\n"; + } + + std::cout << "\n--- Test 3/7: getIpv6Address() ---\n"; + { + interface_name = provider.getInterface(); + std::string ipv6 = provider.getIpv6Address(interface_name); + std::cout << "Result: " << (ipv6.empty() ? "(empty)" : ipv6) << "\n"; + } + + std::cout << "\n--- Test 4/7: getConnectionType() ---\n"; + { + std::string connType = provider.getConnectionType(); + std::cout << "Result: " << (connType.empty() ? "(empty)" : connType) << "\n"; + } + + std::cout << "\n--- Test 5/7: getDnsEntries() ---\n"; + { + std::string dns = provider.getDnsEntries(); + std::cout << "Result: " << (dns.empty() ? "(empty)" : dns) << "\n"; + } + + std::cout << "\n--- Test 6/7: populateNetworkData() ---\n"; + provider.populateNetworkData(); + std::cout << "Network data population completed\n"; + + std::cout << "\n--- Test 7/7: pingToGatewayCheck() ---\n"; + { + bool success = provider.pingToGatewayCheck("8.8.8.8", "IPv4", 5, 30); + std::cout << "Ping " << (success ? "successful" : "failed") << "\n"; + } + + std::cout << "\n========================================\n"; + std::cout << "All Tests Completed\n"; + std::cout << "========================================\n"; + break; + + case 0: + std::cout << "\nExiting test suite...\n"; + running = false; + break; + + default: + std::cout << "\nInvalid choice. Please enter a number between 0-8.\n"; + break; + } + } + + return 0; +} + +#endif // TEST_MAIN diff --git a/networkstats/plugin/ThunderComRPCProvider.h b/networkstats/plugin/ThunderComRPCProvider.h new file mode 100644 index 00000000..8c2f8017 --- /dev/null +++ b/networkstats/plugin/ThunderComRPCProvider.h @@ -0,0 +1,113 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2025 RDK Management +* +* 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 __THUNDERCOMRPCPROVIDER_H__ +#define __THUNDERCOMRPCPROVIDER_H__ + +#include "INetworkData.h" +#include "Module.h" +#include +#include + +#define NETWORK_MANAGER_CALLSIGN "org.rdk.NetworkManager" + +class NetworkComRPCProvider : public INetworkData +{ +public: + NetworkComRPCProvider(); + virtual ~NetworkComRPCProvider(); + + /* @brief Initialize COM-RPC connection to NetworkManager + * @return true if connection successful, false otherwise + */ + bool Initialize(); + + /* @brief Retrieve IPv4 address for specified interface + * @param interface_name Interface name (e.g., eth0, wlan0) + * @return IPv4 address string + */ + std::string getIpv4Address(std::string interface_name) override; + + /* @brief Retrieve IPv6 address for specified interface + * @param interface_name Interface name (e.g., eth0, wlan0) + * @return IPv6 address string + */ + std::string getIpv6Address(std::string interface_name) override; + + /* @brief Get IPv4 gateway/route address from last getIpv4Address call */ + std::string getIpv4Gateway() override; + + /* @brief Get IPv6 gateway/route address from last getIpv6Address call */ + std::string getIpv6Gateway() override; + + /* @brief Get IPv4 primary DNS from last getIpv4Address call */ + std::string getIpv4PrimaryDns() override; + + /* @brief Get IPv6 primary DNS from last getIpv6Address call */ + std::string getIpv6PrimaryDns() override; + + /* @brief Get current network connection type */ + std::string getConnectionType() override; + + /* @brief Get DNS server entries */ + std::string getDnsEntries() override; + + /* @brief Populate network interface data */ + void populateNetworkData() override; + + /* @brief Get current active interface name */ + std::string getInterface() override; + + /* @brief Ping to gateway to check packet loss + * @param endpoint Gateway IP address to ping + * @param ipversion Either "IPv4" or "IPv6" + * @param count Number of ping packets to send + * @param timeout Timeout in seconds + * @return true if ping successful, false otherwise + */ + bool pingToGatewayCheck(std::string endpoint, std::string ipversion, int count, int timeout) override; + + /* @brief Get packet loss from last ping call */ + std::string getPacketLoss() override; + + /* @brief Get average RTT from last ping call */ + std::string getAvgRtt() override; + + /* @brief Subscribe to NetworkManager events + * @param eventName Name of the event (e.g., "onInterfaceStateChange") + * @param callback Callback function to be called when event fires + * @return Error code (Core::ERROR_NONE on success) + */ + uint32_t SubscribeToEvent(const std::string& eventName, + std::function callback) override; + +private: + // Cached data from last API calls + std::string m_ipv4Gateway; + std::string m_ipv6Gateway; + std::string m_ipv4PrimaryDns; + std::string m_ipv6PrimaryDns; + std::string m_packetLoss; + std::string m_avgRtt; + + // Thunder JSON-RPC client for inter-plugin communication + std::shared_ptr> m_networkManagerClient; +}; + +#endif /* __THUNDERCOMRPCPROVIDER_H__ */ \ No newline at end of file diff --git a/networkstats/plugin/ThunderJsonRPCProvider.cpp b/networkstats/plugin/ThunderJsonRPCProvider.cpp new file mode 100644 index 00000000..05fc19cf --- /dev/null +++ b/networkstats/plugin/ThunderJsonRPCProvider.cpp @@ -0,0 +1,558 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2025 RDK Management +* +* 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 "ThunderJsonRPCProvider.h" +#include "NetworkConnectionStatsLogger.h" +#include + +using namespace std; +using namespace NetworkConnectionStatsLogger; + +#define NETWORK_MANAGER_CALLSIGN "org.rdk.NetworkManager" + +/* @brief Constructor */ +NetworkJsonRPCProvider::NetworkJsonRPCProvider() + : m_networkManagerClient(nullptr) +{ + NSLOG_INFO("NetworkJsonRPCProvider constructed"); +} + +/* @brief Destructor */ +NetworkJsonRPCProvider::~NetworkJsonRPCProvider() +{ + m_networkManagerClient.reset(); +} + +/* @brief Initialize the provider with Thunder connection */ +bool NetworkJsonRPCProvider::Initialize() +{ + try { + // Set Thunder access point + WPEFramework::Core::SystemInfo::SetEnvironment(_T("THUNDER_ACCESS"), _T("127.0.0.1:9998")); + + // Create JSON-RPC client connection to NetworkManager + m_networkManagerClient = std::make_shared>( + _T(NETWORK_MANAGER_CALLSIGN), + _T(""), // No specific version + _T("") // No token needed for inter-plugin communication + ); + + if (m_networkManagerClient) { + NSLOG_INFO("NetworkManager JSON-RPC client initialized successfully"); + return true; + } else { + NSLOG_ERROR("Failed to create NetworkManager JSON-RPC client"); + return false; + } + } + catch (const std::exception& e) { + NSLOG_ERROR("Exception during initialization: %s", e.what()); + return false; + } +} + +/* @brief Retrieve IPv4 address for specified interface */ +std::string NetworkJsonRPCProvider::getIpv4Address(std::string interface_name) +{ + std::string ipv4_address = ""; + + // Initialize member variables + m_ipv4Gateway = ""; + m_ipv4PrimaryDns = ""; + + if (!m_networkManagerClient) { + NSLOG_ERROR("NetworkManager client not initialized"); + return ""; + } + + WPEFramework::Core::JSON::VariantContainer params; + params["interface"] = interface_name; + params["ipversion"] = "IPv4"; + + WPEFramework::Core::JSON::VariantContainer result; + + uint32_t rc = m_networkManagerClient->Invoke( + 5000, "GetIPSettings", params, result); + + if (rc == WPEFramework::Core::ERROR_NONE) { + if (result.HasLabel("ipaddress")) { + ipv4_address = result["ipaddress"].String(); + NSLOG_INFO("IPv4 address retrieved: %s", ipv4_address.c_str()); + } + if (result.HasLabel("gateway")) { + m_ipv4Gateway = result["gateway"].String(); + NSLOG_INFO("IPv4 Route: %s", m_ipv4Gateway.c_str()); + } + if (result.HasLabel("primarydns")) { + m_ipv4PrimaryDns = result["primarydns"].String(); + NSLOG_INFO("DNS entry: %s", m_ipv4PrimaryDns.c_str()); + } + } else { + NSLOG_ERROR("GetIPSettings JSON-RPC call failed with error code: %u", rc); + } + + return ipv4_address; +} + +/* @brief Retrieve IPv6 address for specified interface */ +std::string NetworkJsonRPCProvider::getIpv6Address(std::string interface_name) +{ + std::string ipv6_address = ""; + + // Initialize member variables + m_ipv6Gateway = ""; + m_ipv6PrimaryDns = ""; + + if (!m_networkManagerClient) { + NSLOG_ERROR("NetworkManager client not initialized"); + return ""; + } + + WPEFramework::Core::JSON::VariantContainer params; + params["interface"] = interface_name; + params["ipversion"] = "IPv6"; + + WPEFramework::Core::JSON::VariantContainer result; + + uint32_t rc = m_networkManagerClient->Invoke( + 5000, "GetIPSettings", params, result); + + if (rc == WPEFramework::Core::ERROR_NONE) { + if (result.HasLabel("ipaddress")) { + ipv6_address = result["ipaddress"].String(); + NSLOG_INFO("IPv6 address retrieved: %s", ipv6_address.c_str()); + } + if (result.HasLabel("gateway")) { + m_ipv6Gateway = result["gateway"].String(); + NSLOG_INFO("IPv6 Route: %s", m_ipv6Gateway.c_str()); + } + if (result.HasLabel("primarydns")) { + m_ipv6PrimaryDns = result["primarydns"].String(); + NSLOG_INFO("DNS entry: %s", m_ipv6PrimaryDns.c_str()); + } + } else { + NSLOG_ERROR("GetIPSettings JSON-RPC call failed with error code: %u", rc); + } + + return ipv6_address; +} + +/* @brief Get current network connection type */ +std::string NetworkJsonRPCProvider::getConnectionType() +{ + std::string interface = getInterface(); + + if (interface.empty()) { + NSLOG_ERROR("Error: Unable to retrieve interface"); + return ""; + } + + std::string connectionType; + + // Check if interface starts with "eth" or "eno" + if (interface.find("eth") == 0 || interface.find("eno") == 0) { + connectionType = "Ethernet"; + NSLOG_INFO("Connection type: %s (interface: %s)", connectionType.c_str(), interface.c_str()); + } + // Check if interface starts with "wlan" + else if (interface.find("wlan") == 0) { + connectionType = "WiFi"; + NSLOG_INFO("Connection type: %s (interface: %s)", connectionType.c_str(), interface.c_str()); + } + else { + connectionType = "Unknown"; + NSLOG_INFO("Connection type: %s (interface: %s)", connectionType.c_str(), interface.c_str()); + } + + return connectionType; +} + +/* @brief Get DNS server entries */ +std::string NetworkJsonRPCProvider::getDnsEntries() +{ + // TODO: Implement DNS entries retrieval + return ""; +} + +/* @brief Populate network interface data */ +void NetworkJsonRPCProvider::populateNetworkData() +{ + // TODO: Implement network data population +} + +/* @brief Get current active interface name */ +std::string NetworkJsonRPCProvider::getInterface() +{ + if (!m_networkManagerClient) { + NSLOG_ERROR("NetworkManager client not initialized"); + return ""; + } + + WPEFramework::Core::JSON::VariantContainer params; + WPEFramework::Core::JSON::VariantContainer result; + + uint32_t rc = m_networkManagerClient->Invoke( + 5000, "GetPrimaryInterface", params, result); + + std::string interface = ""; + if (rc == WPEFramework::Core::ERROR_NONE) { + if (result.HasLabel("interface")) { + interface = result["interface"].String(); + NSLOG_INFO("Primary interface retrieved: %s", interface.c_str()); + } + } else { + NSLOG_ERROR("GetPrimaryInterface JSON-RPC call failed with error code: %u", rc); + } + + return interface; +} + +/* @brief Ping to gateway to check packet loss */ +bool NetworkJsonRPCProvider::pingToGatewayCheck(std::string endpoint, std::string ipversion, int count, int timeout) +{ + // Initialize member variables + m_packetLoss = ""; + m_avgRtt = ""; + + if (!m_networkManagerClient) { + NSLOG_ERROR("NetworkManager client not initialized"); + return false; + } + + WPEFramework::Core::JSON::VariantContainer params; + params["endpoint"] = endpoint; + params["ipversion"] = ipversion; + params["count"] = count; + params["timeout"] = timeout; + params["guid"] = "network-stats-ping"; + + WPEFramework::Core::JSON::VariantContainer result; + + uint32_t rc = m_networkManagerClient->Invoke( + 30000, "Ping", params, result); + + if (rc != WPEFramework::Core::ERROR_NONE) { + NSLOG_ERROR("Ping JSON-RPC call failed with error code: %u", rc); + return false; + } + + NSLOG_INFO("Ping request sent to %s (%s)", endpoint.c_str(), ipversion.c_str()); + + // Check if ping was successful + if (result.HasLabel("success")) { + bool success = result["success"].Boolean(); + + // Extract ping statistics from JSON + if (result.HasLabel("packetsTransmitted")) { + NSLOG_INFO("Packets transmitted: %d", static_cast(result["packetsTransmitted"].Number())); + } + if (result.HasLabel("packetsReceived")) { + NSLOG_INFO("Packets received: %d", static_cast(result["packetsReceived"].Number())); + } + if (result.HasLabel("packetLoss")) { + m_packetLoss = result["packetLoss"].String(); + NSLOG_INFO("Packet loss: %s", m_packetLoss.c_str()); + } + if (result.HasLabel("tripMin")) { + NSLOG_INFO("RTT min: %s ms", result["tripMin"].String().c_str()); + } + if (result.HasLabel("tripAvg")) { + m_avgRtt = result["tripAvg"].String(); + NSLOG_INFO("RTT avg: %s ms", m_avgRtt.c_str()); + } + if (result.HasLabel("tripMax")) { + NSLOG_INFO("RTT max: %s ms", result["tripMax"].String().c_str()); + } + + return success; + } + else { + NSLOG_ERROR("Error: 'success' field not found in response"); + return false; + } +} + +/* @brief Get IPv4 gateway/route address from last getIpv4Address call */ +std::string NetworkJsonRPCProvider::getIpv4Gateway() +{ + return m_ipv4Gateway; +} + +/* @brief Get IPv6 gateway/route address from last getIpv6Address call */ +std::string NetworkJsonRPCProvider::getIpv6Gateway() +{ + return m_ipv6Gateway; +} + +/* @brief Get IPv4 primary DNS from last getIpv4Address call */ +std::string NetworkJsonRPCProvider::getIpv4PrimaryDns() +{ + return m_ipv4PrimaryDns; +} + +/* @brief Get IPv6 primary DNS from last getIpv6Address call */ +std::string NetworkJsonRPCProvider::getIpv6PrimaryDns() +{ + return m_ipv6PrimaryDns; +} + +/* @brief Get packet loss from last ping call */ +std::string NetworkJsonRPCProvider::getPacketLoss() +{ + return m_packetLoss; +} + +/* @brief Get average RTT from last ping call */ +std::string NetworkJsonRPCProvider::getAvgRtt() +{ + return m_avgRtt; +} + +/* @brief Subscribe to NetworkManager events */ +uint32_t NetworkJsonRPCProvider::SubscribeToEvent(const std::string& eventName, + std::function callback) +{ + if (!m_networkManagerClient) { + NSLOG_ERROR("NetworkManager client not initialized"); + return WPEFramework::Core::ERROR_UNAVAILABLE; + } + + try { + // Subscribe to the event using Thunder's Subscribe API + // Note: The callback will be invoked when the event is received + uint32_t result = m_networkManagerClient->Subscribe( + 5000, // timeout in milliseconds + eventName, + [callback](const WPEFramework::Core::JSON::VariantContainer& parameters) { + callback(parameters); + } + ); + + if (result == WPEFramework::Core::ERROR_NONE) { + NSLOG_INFO("Successfully subscribed to event: %s", eventName.c_str()); + } else { + NSLOG_ERROR("Failed to subscribe to event %s, error code: %u", eventName.c_str(), result); + } + + return result; + } catch (const std::exception& e) { + NSLOG_ERROR("Exception subscribing to event %s: %s", eventName.c_str(), e.what()); + return WPEFramework::Core::ERROR_GENERAL; + } +} + +#ifdef TEST_MAIN +/* Test main function to validate NetworkJsonRPCProvider member functions */ +int main() +{ + NetworkJsonRPCProvider provider; + int choice; + std::string interface_name; + bool running = true; + + std::cout << "\n========================================\n"; + std::cout << "NetworkJsonRPCProvider Test Suite\n"; + std::cout << "========================================\n"; + + while (running) + { + std::cout << "\nMenu:\n"; + std::cout << "1. Test getIpv4Address()\n"; + std::cout << "2. Test getIpv6Address()\n"; + std::cout << "3. Test getConnectionType()\n"; + std::cout << "4. Test getDnsEntries()\n"; + std::cout << "5. Test populateNetworkData()\n"; + std::cout << "6. Test getInterface()\n"; + std::cout << "7. Test pingToGatewayCheck()\n"; + std::cout << "8. Run all tests\n"; + std::cout << "0. Exit\n"; + std::cout << "\nEnter your choice: "; + std::cin >> choice; + + switch (choice) + { + case 1: + std::cout << "\nTesting getIpv4Address()...\n"; + { + //interface_name = provider.getInterface(); + interface_name = "eno1"; // Hardcoded for testing + std::cout << "Using interface: " << (interface_name.empty() ? "(empty)" : interface_name) << "\n"; + std::string ipv4 = provider.getIpv4Address(interface_name); + std::cout << "Result: " << (ipv4.empty() ? "(empty)" : ipv4) << "\n"; + std::string gateway = provider.getIpv4Gateway(); + std::string primaryDns = provider.getIpv4PrimaryDns(); + std::cout << "Gateway: " << (gateway.empty() ? "(empty)" : gateway) << "\n"; + std::cout << "Primary DNS: " << (primaryDns.empty() ? "(empty)" : primaryDns) << "\n"; + } + break; + + case 2: + std::cout << "\nTesting getIpv6Address()...\n"; + { + //interface_name = provider.getInterface(); + interface_name = "eno1"; // Hardcoded for testing + std::cout << "Using interface: " << (interface_name.empty() ? "(empty)" : interface_name) << "\n"; + std::string ipv6 = provider.getIpv6Address(interface_name); + std::cout << "Result: " << (ipv6.empty() ? "(empty)" : ipv6) << "\n"; + std::string gateway = provider.getIpv6Gateway(); + std::string primaryDns = provider.getIpv6PrimaryDns(); + std::cout << "Gateway: " << (gateway.empty() ? "(empty)" : gateway) << "\n"; + std::cout << "Primary DNS: " << (primaryDns.empty() ? "(empty)" : primaryDns) << "\n"; + } + break; + + case 3: + std::cout << "\nTesting getConnectionType()...\n"; + { + std::string connType = provider.getConnectionType(); + std::cout << "Result: " << (connType.empty() ? "(empty)" : connType) << "\n"; + } + break; + + case 4: + std::cout << "\nTesting getDnsEntries()...\n"; + { + std::string dns = provider.getDnsEntries(); + std::cout << "Result: " << (dns.empty() ? "(empty)" : dns) << "\n"; + } + break; + + case 5: + std::cout << "\nTesting populateNetworkData()...\n"; + provider.populateNetworkData(); + std::cout << "populateNetworkData() executed successfully.\n"; + break; + + case 6: + std::cout << "\nTesting getInterface()...\n"; + { + std::string iface = provider.getInterface(); + std::cout << "Result: " << (iface.empty() ? "(empty)" : iface) << "\n"; + } + break; + + case 7: + std::cout << "\nTesting pingToGatewayCheck()...\n"; + { + std::string gateway; + std::cout << "Enter gateway IP address (or press Enter for default 8.8.8.8): "; + std::cin.ignore(); + std::getline(std::cin, gateway); + if (gateway.empty()) { + gateway = "8.8.8.8"; + } + + std::string ipver; + std::cout << "Enter IP version (IPv4/IPv6, default: IPv4): "; + std::getline(std::cin, ipver); + if (ipver.empty()) { + ipver = "IPv4"; + } + + bool result = provider.pingToGatewayCheck(gateway, ipver, 5, 30); + std::cout << "Ping result: " << (result ? "SUCCESS" : "FAILED") << "\n"; + std::string packetLoss = provider.getPacketLoss(); + std::string avgRtt = provider.getAvgRtt(); + std::cout << "Packet Loss: " << (packetLoss.empty() ? "(empty)" : packetLoss) << "\n"; + std::cout << "Average RTT: " << (avgRtt.empty() ? "(empty)" : avgRtt) << " ms\n"; + } + break; + + case 8: + std::cout << "\n========================================\n"; + std::cout << "Running all tests...\n"; + std::cout << "========================================\n"; + + std::cout << "\n[Test 1/6] getConnectionType()\n"; + { + std::string connType = provider.getConnectionType(); + std::cout << "Result: " << (connType.empty() ? "(empty)" : connType) << "\n"; + } + + std::cout << "\n[Test 2/6] getDnsEntries()\n"; + { + std::string dns = provider.getDnsEntries(); + std::cout << "Result: " << (dns.empty() ? "(empty)" : dns) << "\n"; + } + + std::cout << "\n[Test 3/6] getInterface()\n"; + { + std::string iface = provider.getInterface(); + std::cout << "Result: " << (iface.empty() ? "(empty)" : iface) << "\n"; + } + + std::cout << "\n[Test 4/6] populateNetworkData()\n"; + provider.populateNetworkData(); + std::cout << "populateNetworkData() executed successfully.\n"; + std::cout << "\n[Test 5/6] getIpv4Address()\n"; + { + std::string test_iface = provider.getInterface(); + std::cout << "Using interface: " << (test_iface.empty() ? "(empty)" : test_iface) << "\n"; + std::string ipv4 = provider.getIpv4Address(test_iface); + std::cout << "Result: " << (ipv4.empty() ? "(empty)" : ipv4) << "\n"; + std::string gateway = provider.getIpv4Gateway(); + std::string primaryDns = provider.getIpv4PrimaryDns(); + std::cout << "Gateway: " << (gateway.empty() ? "(empty)" : gateway) << "\n"; + std::cout << "Primary DNS: " << (primaryDns.empty() ? "(empty)" : primaryDns) << "\n"; + } + + std::cout << "\n[Test 6/7] getIpv6Address()\n"; + { + std::string test_iface = provider.getInterface(); + std::cout << "Using interface: " << (test_iface.empty() ? "(empty)" : test_iface) << "\n"; + std::string ipv6 = provider.getIpv6Address(test_iface); + std::cout << "Result: " << (ipv6.empty() ? "(empty)" : ipv6) << "\n"; + std::string gateway = provider.getIpv6Gateway(); + std::string primaryDns = provider.getIpv6PrimaryDns(); + std::cout << "Gateway: " << (gateway.empty() ? "(empty)" : gateway) << "\n"; + std::cout << "Primary DNS: " << (primaryDns.empty() ? "(empty)" : primaryDns) << "\n"; + } + + std::cout << "\n[Test 7/7] pingToGatewayCheck()\n"; + { + bool pingResult = provider.pingToGatewayCheck("8.8.8.8", "IPv4", 5, 30); + std::cout << "Result: " << (pingResult ? "SUCCESS" : "FAILED") << "\n"; + std::string packetLoss = provider.getPacketLoss(); + std::string avgRtt = provider.getAvgRtt(); + std::cout << "Packet Loss: " << (packetLoss.empty() ? "(empty)" : packetLoss) << "\n"; + std::cout << "Average RTT: " << (avgRtt.empty() ? "(empty)" : avgRtt) << " ms\n"; + } + + std::cout << "\n========================================\n"; + std::cout << "All tests completed.\n"; + std::cout << "========================================\n"; + break; + + case 0: + std::cout << "\nExiting test suite...\n"; + running = false; + break; + + default: + std::cout << "\nInvalid choice! Please select 0-8.\n"; + break; + } + } + + return 0; +} + +#endif // TEST_MAIN + + diff --git a/networkstats/plugin/ThunderJsonRPCProvider.h b/networkstats/plugin/ThunderJsonRPCProvider.h new file mode 100644 index 00000000..cc452a8d --- /dev/null +++ b/networkstats/plugin/ThunderJsonRPCProvider.h @@ -0,0 +1,108 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2025 RDK Management +* +* 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 __THUNDERPROVIDER_H__ +#define __THUNDERPROVIDER_H__ + +#include "INetworkData.h" +#include "Module.h" +#include +#include + +class NetworkJsonRPCProvider : public INetworkData +{ + public: + NetworkJsonRPCProvider(); + ~NetworkJsonRPCProvider(); + + /* @brief Initialize the provider with Thunder connection */ + bool Initialize(); + /* @brief Retrieve IPv4 address for specified interface + * @param interface_name Interface name (e.g., eth0, wlan0) + * @return IPv4 address string + */ + std::string getIpv4Address(std::string interface_name) override; + + /* @brief Retrieve IPv6 address for specified interface + * @param interface_name Interface name (e.g., eth0, wlan0) + * @return IPv6 address string + */ + std::string getIpv6Address(std::string interface_name) override; + + /* @brief Get IPv4 gateway/route address from last getIpv4Address call */ + std::string getIpv4Gateway() override; + + /* @brief Get IPv6 gateway/route address from last getIpv6Address call */ + std::string getIpv6Gateway() override; + + /* @brief Get IPv4 primary DNS from last getIpv4Address call */ + std::string getIpv4PrimaryDns() override; + + /* @brief Get IPv6 primary DNS from last getIpv6Address call */ + std::string getIpv6PrimaryDns() override; + + /* @brief Get current network connection type */ + std::string getConnectionType() override; + + /* @brief Get DNS server entries */ + std::string getDnsEntries() override; + + /* @brief Populate network interface data */ + void populateNetworkData() override; + + /* @brief Get current active interface name */ + std::string getInterface() override; + + /* @brief Ping to gateway to check packet loss + * @param endpoint Gateway IP address to ping + * @param ipversion Either "IPv4" or "IPv6" + * @param count Number of ping packets to send + * @param timeout Timeout in seconds + * @return true if ping successful, false otherwise + */ + bool pingToGatewayCheck(std::string endpoint, std::string ipversion, int count, int timeout) override; + + /* @brief Get packet loss from last ping call */ + std::string getPacketLoss() override; + + /* @brief Get average RTT from last ping call */ + std::string getAvgRtt() override; + + /* @brief Subscribe to NetworkManager events + * @param eventName Name of the event (e.g., "onInterfaceStateChange") + * @param callback Callback function to be called when event fires + * @return Error code (Core::ERROR_NONE on success) + */ + uint32_t SubscribeToEvent(const std::string& eventName, + std::function callback) override; + +private: + // Cached data from last API calls + std::string m_ipv4Gateway; + std::string m_ipv6Gateway; + std::string m_ipv4PrimaryDns; + std::string m_ipv6PrimaryDns; + std::string m_packetLoss; + std::string m_avgRtt; + + // Thunder JSON-RPC client for NetworkManager communication + std::shared_ptr> m_networkManagerClient; +}; + +#endif /* __THUNDERPROVIDER_H__ */