diff --git a/definition/NetworkManager.json b/definition/NetworkManager.json index fbde9789..b43c4c81 100644 --- a/definition/NetworkManager.json +++ b/definition/NetworkManager.json @@ -1299,9 +1299,35 @@ "success" ] } + }, + "SetHostname": { + "summary": "To configure a custom DHCP hostname instead of the default (which is typically the device name).", + "params": { + "type": "object", + "properties": { + "hostname": { + "summary": "The hostname to be set for the device (maximum 32 characters)", + "type": "string", + "example": "RDK-Device" + } + }, + "required": [ + "hostname" + ] + }, + "result": { + "type": "object", + "properties": { + "success": { + "$ref": "#/definitions/success" + } + }, + "required": [ + "success" + ] + } } }, - "events": { "onInterfaceStateChange":{ "summary": "Triggered when an interface state is changed. The possible states are \n* 'INTERFACE_ADDED' \n* 'INTERFACE_LINK_UP' \n* 'INTERFACE_LINK_DOWN' \n* 'INTERFACE_ACQUIRING_IP' \n* 'INTERFACE_REMOVED' \n* 'INTERFACE_DISABLED' \n", diff --git a/docs/NetworkManagerPlugin.md b/docs/NetworkManagerPlugin.md index 93137805..4dcf7807 100644 --- a/docs/NetworkManagerPlugin.md +++ b/docs/NetworkManagerPlugin.md @@ -102,6 +102,7 @@ NetworkManager interface methods: | [GetWiFiSignalQuality](#method.GetWiFiSignalQuality) | Get WiFi signal quality of currently connected SSID | | [GetSupportedSecurityModes](#method.GetSupportedSecurityModes) | Returns the Wifi security modes that the device supports | | [GetWifiState](#method.GetWifiState) | Returns the current Wifi State | +| [SetHostname](#method.SetHostname) | To configure a custom DHCP hostname instead of the default (which is typically the device name). | ## *SetLogLevel [method](#head.Methods)* @@ -1667,6 +1668,52 @@ This method takes no parameters. } ``` + +## *SetHostname [method](#head.Methods)* + +To configure a custom DHCP hostname instead of the default (which is typically the device name), note that the change will only take effect after a device reboot, creating a new Wi-Fi connection, reactivation of existing NetworkManager connection, or renewal of the DHCP lease. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params.hostname | string | The hostname to be set for the device (maximum 32 characters) | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 42, + "method": "org.rdk.NetworkManager.1.SetHostname", + "params": { + "hostname": "RDK-Device" + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 42, + "result": { + "success": true + } +} +``` + # Notifications diff --git a/interface/INetworkManager.h b/interface/INetworkManager.h index 65c982a2..edf369ff 100644 --- a/interface/INetworkManager.h +++ b/interface/INetworkManager.h @@ -233,6 +233,8 @@ namespace WPEFramework /* @brief Request for trace get the response in as event. The GUID used in the request will be returned in the event. */ virtual uint32_t Trace (const string ipversion /* @in */, const string endpoint /* @in */, const uint32_t nqueries /* @in */, const string guid /* @in */, string& response /* @out */) = 0; + /* @brief Set the dhcp hostname */ + virtual uint32_t SetHostname(const string& hostname /* @in */) = 0; // WiFi Specific Methods /* @brief Initiate a WIFI Scan; This is Async method and returns the scan results as Event */ diff --git a/plugin/NetworkManager.h b/plugin/NetworkManager.h index 98007e8c..1abce8d0 100644 --- a/plugin/NetworkManager.h +++ b/plugin/NetworkManager.h @@ -250,6 +250,7 @@ namespace WPEFramework uint32_t GetPublicIP(const JsonObject& parameters, JsonObject& response); uint32_t Ping(const JsonObject& parameters, JsonObject& response); uint32_t Trace(const JsonObject& parameters, JsonObject& response); + uint32_t SetHostname (const JsonObject& parameters, JsonObject& response); uint32_t StartWiFiScan(const JsonObject& parameters, JsonObject& response); uint32_t StopWiFiScan(const JsonObject& parameters, JsonObject& response); uint32_t GetKnownSSIDs(const JsonObject& parameters, JsonObject& response); diff --git a/plugin/NetworkManagerImplementation.h b/plugin/NetworkManagerImplementation.h index d6077e8d..aff37e44 100644 --- a/plugin/NetworkManagerImplementation.h +++ b/plugin/NetworkManagerImplementation.h @@ -245,6 +245,9 @@ namespace WPEFramework uint32_t GetSupportedSecurityModes(ISecurityModeIterator*& security /* @out */) const override; + /* @brief Set the dhcp hostname */ + uint32_t SetHostname(const string& hostname /* @in */) override; + /* @brief Set the network manager plugin log level */ uint32_t SetLogLevel(const Logging& level /* @in */) override; uint32_t GetLogLevel(Logging& level /* @out */) override; diff --git a/plugin/NetworkManagerJsonRpc.cpp b/plugin/NetworkManagerJsonRpc.cpp index 07a4fc30..5ac09c56 100644 --- a/plugin/NetworkManagerJsonRpc.cpp +++ b/plugin/NetworkManagerJsonRpc.cpp @@ -69,6 +69,7 @@ namespace WPEFramework Register("GetPublicIP", &NetworkManager::GetPublicIP, this); Register("Ping", &NetworkManager::Ping, this); Register("Trace", &NetworkManager::Trace, this); + Register("SetHostname", &NetworkManager::SetHostname, this); Register("StartWiFiScan", &NetworkManager::StartWiFiScan, this); Register("StopWiFiScan", &NetworkManager::StopWiFiScan, this); Register("GetKnownSSIDs", &NetworkManager::GetKnownSSIDs, this); @@ -105,6 +106,7 @@ namespace WPEFramework Unregister("GetPublicIP"); Unregister("Ping"); Unregister("Trace"); + Unregister("SetHostname"); Unregister("StartWiFiScan"); Unregister("StopWiFiScan"); Unregister("GetKnownSSIDs"); @@ -608,6 +610,29 @@ namespace WPEFramework returnJson(rc); } + uint32_t NetworkManager::SetHostname (const JsonObject& parameters, JsonObject& response) + { + LOG_INPARAM(); + uint32_t rc = Core::ERROR_GENERAL; + string hostname{}; + + if (parameters.HasLabel("hostname") && !parameters["hostname"].String().empty()) + { + hostname = parameters["hostname"].String(); + if (_networkManager) + rc = _networkManager->SetHostname(hostname); + else + rc = Core::ERROR_UNAVAILABLE; + } + else + { + NMLOG_ERROR("Invalid hostname length: %zu", hostname.length()); + rc = Core::ERROR_BAD_REQUEST; + } + + returnJson(rc); + } + uint32_t NetworkManager::StartWiFiScan(const JsonObject& parameters, JsonObject& response) { LOG_INPARAM(); diff --git a/plugin/gnome/NetworkManagerGnomeProxy.cpp b/plugin/gnome/NetworkManagerGnomeProxy.cpp index 0a48feb3..1431fdc2 100644 --- a/plugin/gnome/NetworkManagerGnomeProxy.cpp +++ b/plugin/gnome/NetworkManagerGnomeProxy.cpp @@ -64,6 +64,191 @@ namespace WPEFramework return deviceState; } + static bool setHostname(NMConnection *connection, const std::string& hostname) + { + GError *error = NULL; + NMSettingIPConfig *sIPv4 = NULL; + NMSettingIPConfig *sIPv6 = NULL; + bool needChange = false; + + if(connection == NULL) { + NMLOG_ERROR("Connection is NULL"); + return false; + } + + sIPv4 = nm_connection_get_setting_ip4_config(connection); + if (sIPv4) { + const char* existingHostname = nm_setting_ip_config_get_dhcp_hostname(sIPv4); + gboolean sendHostname = nm_setting_ip_config_get_dhcp_send_hostname(sIPv4); + + if (!sendHostname || !existingHostname || hostname != existingHostname) { + needChange = true; + } + } + else + needChange = true; + + sIPv6 = nm_connection_get_setting_ip6_config(connection); + if (sIPv6) { + const char* existingHostname = nm_setting_ip_config_get_dhcp_hostname(sIPv6); + gboolean sendHostname = nm_setting_ip_config_get_dhcp_send_hostname(sIPv6); + + if (!sendHostname || !existingHostname || hostname != existingHostname) { + needChange = true; + } + } + else + needChange = true; + + if (!needChange) { + NMLOG_DEBUG("Hostname already set to '%s', no changes needed", hostname.c_str()); + return true; + } + + NMLOG_DEBUG("Setting hostname to '%s' for connection '%s'", hostname.c_str(), nm_connection_get_id(connection)); + + if (!sIPv4) { + sIPv4 = NM_SETTING_IP_CONFIG(nm_setting_ip4_config_new()); + nm_connection_add_setting(connection, NM_SETTING(sIPv4)); + } + g_object_set(sIPv4, + NM_SETTING_IP_CONFIG_DHCP_HOSTNAME, hostname.c_str(), + NM_SETTING_IP_CONFIG_DHCP_SEND_HOSTNAME, TRUE, + NULL); + + if (!sIPv6) { + sIPv6 = NM_SETTING_IP_CONFIG(nm_setting_ip6_config_new()); + nm_connection_add_setting(connection, NM_SETTING(sIPv6)); + } + g_object_set(sIPv6, + NM_SETTING_IP_CONFIG_DHCP_HOSTNAME, hostname.c_str(), + NM_SETTING_IP_CONFIG_DHCP_SEND_HOSTNAME, TRUE, + NULL); + + nm_remote_connection_commit_changes(NM_REMOTE_CONNECTION(connection), TRUE, NULL, &error); + if(error) { + NMLOG_ERROR("Failed to commit changes: %s", error->message); + g_error_free(error); + return false; + } + + return true; + } + + static bool modifyDefaultConnConfig(NMClient *client) + { + GError *error = NULL; + const GPtrArray *connections = NULL; + NMConnection *connection = NULL; + std::string hostname{}; + + if (client == nullptr) { + NMLOG_ERROR("NMClient is NULL"); + return false; + } + + // read persistent hostname if exist + if(!nmUtils::readPersistentHostname(hostname)) + { + hostname = nmUtils::deviceHostname(); // default hostname as device name + } + + connections = nm_client_get_connections(client); + if (connections == NULL || connections->len == 0) { + NMLOG_ERROR("Could not get nm connections"); + return false; + } + + for (uint32_t i = 0; i < connections->len; i++) + { + connection = NM_CONNECTION(connections->pdata[i]); + if(connection == NULL) + { + NMLOG_ERROR("Connection at index %d is NULL", i); + continue; + } + + const char *iface = nm_connection_get_interface_name(connection); + if(!iface) + { + NMLOG_WARNING("Failed to get connection interface name !"); + continue; + } + + std::string interface = iface; + if(interface != nmUtils::ethIface() && interface != nmUtils::wlanIface()) { + NMLOG_DEBUG("Skipping non-ethernet/wifi connection type: %s", interface.c_str()); + continue; + } + + if(!setHostname(connection, hostname)) { + NMLOG_WARNING("Failed to set hostname for connection at index %d", i); + } + } + + return true; + } + + /* @brief Set the dhcp hostname */ + uint32_t NetworkManagerImplementation::SetHostname(const string& hostname /* @in */) + { + const GPtrArray *connections = NULL; + NMConnection *connection = NULL; + + if (client == nullptr) { + NMLOG_ERROR("NMClient is NULL"); + return Core::ERROR_GENERAL; + } + + if(hostname.length() < 1 || hostname.length() > 32) + { + NMLOG_ERROR("Invalid hostname length: %zu", hostname.length()); + return Core::ERROR_BAD_REQUEST; + } + + connections = nm_client_get_connections(client); + if (connections == NULL || connections->len == 0) + { + NMLOG_ERROR("Could not get nm connections"); + return Core::ERROR_GENERAL; + } + + NMLOG_INFO("Setting DHCP hostname to %s", hostname.c_str()); + + for (uint32_t i = 0; i < connections->len; i++) + { + connection = NM_CONNECTION(connections->pdata[i]); + if(connection != NULL) + { + const char *iface = nm_connection_get_interface_name(connection); + if(!iface) + { + NMLOG_WARNING("Failed to get connection interface name !"); + continue; + } + + std::string interface = iface; + if(interface != nmUtils::ethIface() && interface != nmUtils::wlanIface()) { + NMLOG_DEBUG("Skipping non-ethernet/wifi connection type: %s", interface.c_str()); + continue; + } + + if(!setHostname(connection, hostname)) + { + NMLOG_ERROR("Failed to set hostname for connection at index %d", i); + return Core::ERROR_GENERAL; + } + } + else + NMLOG_ERROR("Connection at index %d is NULL", i); + } + + // Write the hostname to persistent storage + nmUtils::writePersistentHostname(hostname); + + return Core::ERROR_NONE; + } + void NetworkManagerImplementation::platform_logging(const NetworkManagerLogger::LogLevel& level) { /* set networkmanager daemon log level based on current plugin log level */ @@ -76,7 +261,7 @@ namespace WPEFramework { ::_instance = this; GError *error = NULL; - + // initialize the NMClient object client = nm_client_new(NULL, &error); if (client == NULL) { @@ -87,7 +272,8 @@ namespace WPEFramework return; } - nmUtils::getInterfacesName(); // get interface name form '/etc/device.proprties' + nmUtils::getDeviceProperties(); // get interface name form '/etc/device.proprties' + modifyDefaultConnConfig(client); NMDeviceState ethState = ifaceState(client, nmUtils::ethIface()); if(ethState > NM_DEVICE_STATE_DISCONNECTED && ethState < NM_DEVICE_STATE_DEACTIVATING) m_defaultInterface = nmUtils::ethIface(); diff --git a/plugin/gnome/NetworkManagerGnomeUtils.cpp b/plugin/gnome/NetworkManagerGnomeUtils.cpp index 51aca0a2..3910b6c3 100644 --- a/plugin/gnome/NetworkManagerGnomeUtils.cpp +++ b/plugin/gnome/NetworkManagerGnomeUtils.cpp @@ -37,9 +37,11 @@ namespace WPEFramework { static std::string m_ethifname = "eth0"; static std::string m_wlanifname = "wlan0"; + static std::string m_deviceHostname = "rdk-device"; // Device name can be empty if not set in /etc/device.properties const char* nmUtils::wlanIface() {return m_wlanifname.c_str();} const char* nmUtils::ethIface() {return m_ethifname.c_str();} + const char* nmUtils::deviceHostname() {return m_deviceHostname.c_str();} uint8_t nmUtils::wifiSecurityModeFromAp(const std::string& ssid, guint32 flags, guint32 wpaFlags, guint32 rsnFlags, bool doPrint) { @@ -205,11 +207,12 @@ namespace WPEFramework return upperStr1 == upperStr2; } - bool nmUtils::getInterfacesName() + bool nmUtils::getDeviceProperties() { std::string line; std::string wifiIfname; std::string ethIfname; // cached interface name + std::string deviceHostname; std::ifstream file("/etc/device.properties"); if (!file.is_open()) { @@ -243,12 +246,24 @@ namespace WPEFramework wifiIfname = "wlan0_missing"; // means device doesnot have wifi interface } } + + if (line.find("DEVICE_NAME=") != std::string::npos) { + deviceHostname = line.substr(line.find('=') + 1); + deviceHostname.erase(deviceHostname.find_last_not_of("\r\n\t") + 1); + deviceHostname.erase(0, deviceHostname.find_first_not_of("\r\n\t")); + if(deviceHostname.empty()) + { + NMLOG_WARNING("DEVICE_NAME is empty in /etc/device.properties"); + deviceHostname = ""; // set empty device name + } + } } file.close(); m_wlanifname = wifiIfname; m_ethifname = ethIfname; - NMLOG_INFO("/etc/device.properties eth: %s, wlan: %s", m_ethifname.c_str(), m_wlanifname.c_str()); + m_deviceHostname = deviceHostname; + NMLOG_INFO("/etc/device.properties eth: %s, wlan: %s, device name: %s", m_ethifname.c_str(), m_wlanifname.c_str(), m_deviceHostname.c_str()); return true; } @@ -303,6 +318,8 @@ namespace WPEFramework } } + + bool nmUtils::isInterfaceEnabled(const std::string& interface) { std::string markerFile; @@ -322,5 +339,57 @@ namespace WPEFramework return isAllowed; } + bool nmUtils::writePersistentHostname(const std::string& hostname) + { + if (hostname.empty()) { + NMLOG_ERROR("Cannot write empty hostname to persistent storage"); + return false; + } + + std::ofstream file(HostnameFile); + if (!file.is_open()) { + NMLOG_ERROR("Failed to open %s for writing", HostnameFile); + return false; + } + + file << hostname; + bool success = !file.fail(); + file.close(); + + if (success) + NMLOG_DEBUG("Successfully wrote hostname '%s' to %s",hostname.c_str(), HostnameFile); + else + NMLOG_ERROR("Error writing hostname to %s", HostnameFile); + + return success; + } + + bool nmUtils::readPersistentHostname(std::string& hostname) + { + std::ifstream file(HostnameFile); + if (!file.is_open()) { + NMLOG_DEBUG("Could not open %s - file may not exist yet", HostnameFile); + hostname = ""; + return false; + } + + std::string line; + if (std::getline(file, line)) { + // Remove any whitespace, newlines, etc. + line.erase(line.find_last_not_of("\r\n\t") + 1); + line.erase(0, line.find_first_not_of("\r\n\t")); + hostname = line; + file.close(); + + NMLOG_INFO("Read persistent hostname: '%s'", hostname.c_str()); + return true; + } + + NMLOG_WARNING("Persistent hostname file exists but is empty"); + hostname = ""; + file.close(); + return false; + } + } // Plugin } // WPEFramework diff --git a/plugin/gnome/NetworkManagerGnomeUtils.h b/plugin/gnome/NetworkManagerGnomeUtils.h index 35688d46..aab7daf0 100644 --- a/plugin/gnome/NetworkManagerGnomeUtils.h +++ b/plugin/gnome/NetworkManagerGnomeUtils.h @@ -31,13 +31,15 @@ namespace WPEFramework { constexpr const char* EthernetDisableMarker = "/opt/persistent/ethernet.interface.disable"; constexpr const char* WiFiDisableMarker = "/opt/persistent/wifi.interface.disable"; + constexpr const char* HostnameFile = "/opt/persistent/nm.plugin.hostname"; class nmUtils { public: - static bool getInterfacesName(); + static bool getDeviceProperties(); static const char* wlanIface(); static const char* ethIface(); + static const char* deviceHostname(); static const char* convertPercentageToSignalStrengtStr(int percentage); static bool caseInsensitiveCompare(const std::string& str1, const std::string& str2); static uint8_t wifiSecurityModeFromAp(const std::string& ssid, guint32 flags, guint32 wpaFlags, guint32 rsnFlags, bool doPrint = true); @@ -46,6 +48,8 @@ namespace WPEFramework static bool setNetworkManagerlogLevelToTrace(); static void setMarkerFile(const char* filename, bool unmark = false); static bool isInterfaceEnabled(const std::string& interface); + static bool writePersistentHostname(const std::string& hostname); + static bool readPersistentHostname(std::string& hostname); }; } } diff --git a/plugin/gnome/NetworkManagerGnomeWIFI.cpp b/plugin/gnome/NetworkManagerGnomeWIFI.cpp index 49454ba8..7d572556 100644 --- a/plugin/gnome/NetworkManagerGnomeWIFI.cpp +++ b/plugin/gnome/NetworkManagerGnomeWIFI.cpp @@ -630,14 +630,27 @@ namespace WPEFramework } } - /* Build up the 'ipv4' Setting */ + std::string hostname; + if(!nmUtils::readPersistentHostname(hostname)) + { + hostname = nmUtils::deviceHostname(); + NMLOG_DEBUG("no persistent hostname found taking device name as hostname !"); + } + + NMLOG_INFO("dhcp hostname: %s", hostname.c_str()); + + /* Build up the 'IPv4' Setting */ NMSettingIP4Config *sIpv4Conf = (NMSettingIP4Config *) nm_setting_ip4_config_new(); g_object_set(G_OBJECT(sIpv4Conf), NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL); // autoconf = true + g_object_set(G_OBJECT(sIpv4Conf), NM_SETTING_IP_CONFIG_DHCP_HOSTNAME, hostname.c_str(), NULL); + g_object_set(G_OBJECT(sIpv4Conf), NM_SETTING_IP_CONFIG_DHCP_SEND_HOSTNAME, TRUE, NULL); // hostname send enabled nm_connection_add_setting(m_connection, NM_SETTING(sIpv4Conf)); - /* Build up the 'ipv6' Setting */ + /* Build up the 'IPv6' Setting */ NMSettingIP6Config *sIpv6Conf = (NMSettingIP6Config *) nm_setting_ip6_config_new(); g_object_set(G_OBJECT(sIpv6Conf), NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_AUTO, NULL); // autoconf = true + g_object_set(G_OBJECT(sIpv6Conf), NM_SETTING_IP_CONFIG_DHCP_HOSTNAME, hostname.c_str(), NULL); + g_object_set(G_OBJECT(sIpv6Conf), NM_SETTING_IP_CONFIG_DHCP_SEND_HOSTNAME, TRUE, NULL); // hostname send enabled nm_connection_add_setting(m_connection, NM_SETTING(sIpv6Conf)); return true; } diff --git a/plugin/rdk/NetworkManagerRDKProxy.cpp b/plugin/rdk/NetworkManagerRDKProxy.cpp index e85ae4f9..543d014e 100644 --- a/plugin/rdk/NetworkManagerRDKProxy.cpp +++ b/plugin/rdk/NetworkManagerRDKProxy.cpp @@ -433,6 +433,13 @@ namespace WPEFramework return; } + /* @brief Set the dhcp hostname */ + uint32_t NetworkManagerImplementation::SetHostname(const string& hostname /* @in */) + { + // TODO: Implement setting the DHCP hostname for netsrvmgr + return Core::ERROR_NONE; + } + void NetworkManagerImplementation::platform_init() { LOG_ENTRY_FUNCTION(); diff --git a/tests/mocks/INetworkManagerMock.h b/tests/mocks/INetworkManagerMock.h index 7aad39b3..eb359627 100644 --- a/tests/mocks/INetworkManagerMock.h +++ b/tests/mocks/INetworkManagerMock.h @@ -50,6 +50,7 @@ class MockINetworkManager : public WPEFramework::Exchange::INetworkManager { MOCK_METHOD(uint32_t, GetWiFiSignalQuality, (string& ssid, string& strength, string& noise, string& snr, WPEFramework::Exchange::INetworkManager::WiFiSignalQuality& quality), (override)); MOCK_METHOD(uint32_t, GetSupportedSecurityModes, (ISecurityModeIterator*& modes), (const)); MOCK_METHOD(uint32_t, SetLogLevel, (const Logging& level), (override)); + MOCK_METHOD(uint32_t, SetHostname, (const string& hostname), (override)); MOCK_METHOD(uint32_t, GetLogLevel, (Logging& level), (override)); MOCK_METHOD(uint32_t, Configure, (WPEFramework::PluginHost::IShell* service), (override)); MOCK_METHOD(uint32_t, Register, (WPEFramework::Exchange::INetworkManager::INotification* notification), (override));