NetworkSettingsWidget.cpp 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. /*
  2. * Copyright (c) 2022, Maciej <sppmacd@pm.me>
  3. * Copyright (c) 2023, Fabian Dellwing <fabian@dellwing.net>
  4. *
  5. * SPDX-License-Identifier: BSD-2-Clause
  6. */
  7. #include "NetworkSettingsWidget.h"
  8. #include <AK/DeprecatedString.h>
  9. #include <AK/IPv4Address.h>
  10. #include <AK/JsonObject.h>
  11. #include <AK/JsonParser.h>
  12. #include <Applications/NetworkSettings/NetworkSettingsGML.h>
  13. #include <LibCore/Command.h>
  14. #include <LibCore/System.h>
  15. #include <LibGUI/CheckBox.h>
  16. #include <LibGUI/ComboBox.h>
  17. #include <LibGUI/ItemListModel.h>
  18. #include <LibGUI/MessageBox.h>
  19. #include <LibGUI/Process.h>
  20. #include <LibGUI/SpinBox.h>
  21. #include <LibGUI/TextBox.h>
  22. #include <unistd.h>
  23. namespace NetworkSettings {
  24. static int netmask_to_cidr(IPv4Address const& address)
  25. {
  26. auto address_in_host_representation = AK::convert_between_host_and_network_endian(address.to_u32());
  27. return 32 - count_trailing_zeroes_safe(address_in_host_representation);
  28. }
  29. ErrorOr<NonnullRefPtr<NetworkSettingsWidget>> NetworkSettingsWidget::try_create()
  30. {
  31. auto widget = TRY(adopt_nonnull_ref_or_enomem(new (nothrow) NetworkSettingsWidget()));
  32. TRY(widget->setup());
  33. return widget;
  34. }
  35. ErrorOr<void> NetworkSettingsWidget::setup()
  36. {
  37. TRY(load_from_gml(network_settings_gml));
  38. m_adapters_combobox = *find_descendant_of_type_named<GUI::ComboBox>("adapters_combobox");
  39. m_enabled_checkbox = *find_descendant_of_type_named<GUI::CheckBox>("enabled_checkbox");
  40. m_enabled_checkbox->on_checked = [&](bool value) {
  41. m_current_adapter_data->enabled = value;
  42. on_switch_enabled_or_dhcp();
  43. set_modified(true);
  44. };
  45. m_dhcp_checkbox = *find_descendant_of_type_named<GUI::CheckBox>("dhcp_checkbox");
  46. m_dhcp_checkbox->on_checked = [&](bool value) {
  47. m_current_adapter_data->dhcp = value;
  48. on_switch_enabled_or_dhcp();
  49. set_modified(true);
  50. };
  51. m_ip_address_textbox = *find_descendant_of_type_named<GUI::TextBox>("ip_address_textbox");
  52. m_ip_address_textbox->on_change = [&]() {
  53. m_current_adapter_data->ip_address = m_ip_address_textbox->text();
  54. set_modified(true);
  55. };
  56. m_cidr_spinbox = *find_descendant_of_type_named<GUI::SpinBox>("cidr_spinbox");
  57. m_cidr_spinbox->on_change = [&](int value) {
  58. m_current_adapter_data->cidr = value;
  59. set_modified(true);
  60. };
  61. m_default_gateway_textbox = *find_descendant_of_type_named<GUI::TextBox>("default_gateway_textbox");
  62. m_default_gateway_textbox->on_change = [&]() {
  63. m_current_adapter_data->default_gateway = m_default_gateway_textbox->text();
  64. set_modified(true);
  65. };
  66. auto config_file = TRY(Core::ConfigFile::open_for_system("Network"));
  67. auto proc_net_adapters_file = TRY(Core::File::open("/sys/kernel/net/adapters"sv, Core::File::OpenMode::Read));
  68. auto data = TRY(proc_net_adapters_file->read_until_eof());
  69. JsonParser parser(data);
  70. JsonValue proc_net_adapters_json = TRY(parser.parse());
  71. size_t selected_adapter_index = 0;
  72. size_t index = 0;
  73. proc_net_adapters_json.as_array().for_each([&](auto& value) {
  74. auto& if_object = value.as_object();
  75. auto adapter_name = if_object.get_deprecated_string("name"sv).value();
  76. if (adapter_name == "loop")
  77. return;
  78. bool adapter_exists_in_config = config_file->has_group(adapter_name);
  79. bool enabled = config_file->read_bool_entry(adapter_name, "Enabled", true);
  80. if (enabled)
  81. selected_adapter_index = index;
  82. NetworkAdapterData adapter_data;
  83. adapter_data.enabled = enabled;
  84. adapter_data.dhcp = config_file->read_bool_entry(adapter_name, "DHCP", !adapter_exists_in_config);
  85. adapter_data.ip_address = config_file->read_entry(adapter_name, "IPv4Address");
  86. auto netmask = IPv4Address::from_string(config_file->read_entry(adapter_name, "IPv4Netmask"));
  87. adapter_data.cidr = netmask.has_value() ? netmask_to_cidr(*netmask) : 32;
  88. adapter_data.default_gateway = config_file->read_entry(adapter_name, "IPv4Gateway");
  89. m_network_adapters.set(adapter_name, move(adapter_data));
  90. m_adapter_names.append(adapter_name);
  91. index++;
  92. });
  93. // FIXME: This should be done before creating a window.
  94. if (m_adapter_names.is_empty()) {
  95. GUI::MessageBox::show_error(window(), "No network adapters found!"sv);
  96. ::exit(1);
  97. }
  98. m_adapters_combobox->set_model(GUI::ItemListModel<DeprecatedString>::create(m_adapter_names));
  99. m_adapters_combobox->on_change = [this](DeprecatedString const& text, GUI::ModelIndex const&) {
  100. on_switch_adapter(text);
  101. };
  102. auto const& selected_adapter = selected_adapter_index;
  103. dbgln("{} in {}", selected_adapter, m_adapter_names);
  104. m_adapters_combobox->set_selected_index(selected_adapter);
  105. on_switch_adapter(m_adapter_names[selected_adapter_index]);
  106. return {};
  107. }
  108. void NetworkSettingsWidget::on_switch_adapter(DeprecatedString const& adapter)
  109. {
  110. auto& adapter_data = m_network_adapters.get(adapter).value();
  111. m_current_adapter_data = &adapter_data;
  112. on_switch_enabled_or_dhcp();
  113. m_enabled_checkbox->set_checked(adapter_data.enabled, GUI::AllowCallback::No);
  114. m_dhcp_checkbox->set_checked(adapter_data.dhcp, GUI::AllowCallback::No);
  115. m_ip_address_textbox->set_text(adapter_data.ip_address, GUI::AllowCallback::No);
  116. m_cidr_spinbox->set_value(adapter_data.cidr, GUI::AllowCallback::No);
  117. m_default_gateway_textbox->set_text(adapter_data.default_gateway, GUI::AllowCallback::No);
  118. VERIFY(m_current_adapter_data);
  119. }
  120. void NetworkSettingsWidget::on_switch_enabled_or_dhcp()
  121. {
  122. m_dhcp_checkbox->set_enabled(m_current_adapter_data->enabled);
  123. m_ip_address_textbox->set_enabled(m_current_adapter_data->enabled && !m_current_adapter_data->dhcp);
  124. m_cidr_spinbox->set_enabled(m_current_adapter_data->enabled && !m_current_adapter_data->dhcp);
  125. m_default_gateway_textbox->set_enabled(m_current_adapter_data->enabled && !m_current_adapter_data->dhcp);
  126. }
  127. void NetworkSettingsWidget::apply_settings()
  128. {
  129. auto result = apply_settings_impl();
  130. if (result.is_error()) {
  131. GUI::MessageBox::show_error(window(), result.release_error().string_literal());
  132. return;
  133. }
  134. }
  135. ErrorOr<void> NetworkSettingsWidget::apply_settings_impl()
  136. {
  137. auto maybe_json = TRY(create_settings_object());
  138. if (!maybe_json.has_value() || maybe_json.value().is_empty())
  139. return {};
  140. auto json = maybe_json.release_value();
  141. auto pipefds = TRY(Core::System::pipe2(O_CLOEXEC));
  142. ScopeGuard guard_fd1 { [&] { close(pipefds[1]); } };
  143. {
  144. posix_spawn_file_actions_t file_actions;
  145. posix_spawn_file_actions_init(&file_actions);
  146. posix_spawn_file_actions_adddup2(&file_actions, pipefds[0], STDIN_FILENO);
  147. ScopeGuard guard_fd0_and_file_actions { [&]() {
  148. posix_spawn_file_actions_destroy(&file_actions);
  149. close(pipefds[0]);
  150. } };
  151. char const* argv[] = { "/bin/Escalator", "-I", "-P", "To apply these changes please enter your password:", "/bin/network-settings", nullptr };
  152. (void)TRY(Core::System::posix_spawn("/bin/Escalator"sv, &file_actions, nullptr, const_cast<char**>(argv), environ));
  153. auto outfile = TRY(Core::File::adopt_fd(pipefds[1], Core::File::OpenMode::Write, Core::File::ShouldCloseFileDescriptor::No));
  154. TRY(outfile->write_until_depleted(json.serialized<StringBuilder>().bytes()));
  155. }
  156. return {};
  157. }
  158. ErrorOr<Optional<JsonObject>> NetworkSettingsWidget::create_settings_object()
  159. {
  160. auto json = JsonObject();
  161. for (auto const& adapter_data : m_network_adapters) {
  162. auto netmask = TRY(IPv4Address::netmask_from_cidr(adapter_data.value.cidr).to_string());
  163. if (adapter_data.value.enabled && !adapter_data.value.dhcp) {
  164. if (!IPv4Address::from_string(adapter_data.value.ip_address).has_value()) {
  165. GUI::MessageBox::show_error(window(), TRY(String::formatted("Invalid IPv4 address for adapter {}", adapter_data.key)));
  166. return Optional<JsonObject> {};
  167. }
  168. if (!IPv4Address::from_string(adapter_data.value.default_gateway).has_value()) {
  169. GUI::MessageBox::show_error(window(), TRY(String::formatted("Invalid IPv4 gateway for adapter {}", adapter_data.key)));
  170. return Optional<JsonObject> {};
  171. }
  172. }
  173. auto adapter = JsonObject();
  174. adapter.set("Enabled", adapter_data.value.enabled);
  175. adapter.set("DHCP", adapter_data.value.dhcp);
  176. adapter.set("IPv4Address", adapter_data.value.ip_address);
  177. adapter.set("IPv4Netmask", netmask.to_deprecated_string());
  178. adapter.set("IPv4Gateway", adapter_data.value.default_gateway);
  179. json.set(adapter_data.key, move(adapter));
  180. }
  181. return json;
  182. }
  183. void NetworkSettingsWidget::switch_adapter(DeprecatedString const& adapter)
  184. {
  185. m_adapters_combobox->set_text(adapter, GUI::AllowCallback::No);
  186. on_switch_adapter(adapter);
  187. }
  188. }