diff options
author | brapru <brapru@pm.me> | 2022-03-12 14:16:44 -0500 |
---|---|---|
committer | Brian Gianforcaro <b.gianfo@gmail.com> | 2022-04-28 08:41:11 -0700 |
commit | 19912a0b32393ba9f1535bfd518fa7a4fec9d00b (patch) | |
tree | 86b84f3535c86e5e204255a1d147350674975323 | |
parent | 419cd479e285240f9a14261dac0566ae59dec1bd (diff) | |
download | serenity-19912a0b32393ba9f1535bfd518fa7a4fec9d00b.zip |
Kernel+Utilities: Add the route utility
This exposes the global routing table in the /proc directory and adds
the userspace utility to query dynamically add from the table.
-rw-r--r-- | Kernel/GlobalProcessExposed.cpp | 37 | ||||
-rw-r--r-- | Userland/Utilities/CMakeLists.txt | 3 | ||||
-rw-r--r-- | Userland/Utilities/route.cpp | 190 |
3 files changed, 229 insertions, 1 deletions
diff --git a/Kernel/GlobalProcessExposed.cpp b/Kernel/GlobalProcessExposed.cpp index 45574cc781..7f2af55ba2 100644 --- a/Kernel/GlobalProcessExposed.cpp +++ b/Kernel/GlobalProcessExposed.cpp @@ -100,6 +100,34 @@ private: } }; +class ProcFSRoute final : public ProcFSGlobalInformation { +public: + static NonnullRefPtr<ProcFSRoute> must_create(); + +private: + ProcFSRoute(); + virtual ErrorOr<void> try_generate(KBufferBuilder& builder) override + { + auto array = TRY(JsonArraySerializer<>::try_create(builder)); + TRY(routing_table().with([&](auto const& table) -> ErrorOr<void> { + for (auto& it : table) { + auto obj = TRY(array.add_object()); + auto destination = TRY(it.destination.to_string()); + TRY(obj.add("destination", destination->view())); + auto gateway = TRY(it.gateway.to_string()); + TRY(obj.add("gateway", gateway->view())); + auto netmask = TRY(it.netmask.to_string()); + TRY(obj.add("genmask", netmask->view())); + TRY(obj.add("interface", it.adapter->name())); + TRY(obj.finish()); + } + return {}; + })); + TRY(array.finish()); + return {}; + } +}; + class ProcFSTCP final : public ProcFSGlobalInformation { public: static NonnullRefPtr<ProcFSTCP> must_create(); @@ -217,6 +245,10 @@ UNMAP_AFTER_INIT NonnullRefPtr<ProcFSARP> ProcFSARP::must_create() { return adopt_ref_if_nonnull(new (nothrow) ProcFSARP).release_nonnull(); } +UNMAP_AFTER_INIT NonnullRefPtr<ProcFSRoute> ProcFSRoute::must_create() +{ + return adopt_ref_if_nonnull(new (nothrow) ProcFSRoute).release_nonnull(); +} UNMAP_AFTER_INIT NonnullRefPtr<ProcFSTCP> ProcFSTCP::must_create() { return adopt_ref_if_nonnull(new (nothrow) ProcFSTCP).release_nonnull(); @@ -235,6 +267,7 @@ UNMAP_AFTER_INIT NonnullRefPtr<ProcFSNetworkDirectory> ProcFSNetworkDirectory::m auto directory = adopt_ref(*new (nothrow) ProcFSNetworkDirectory(parent_directory)); directory->m_components.append(ProcFSAdapters::must_create()); directory->m_components.append(ProcFSARP::must_create()); + directory->m_components.append(ProcFSRoute::must_create()); directory->m_components.append(ProcFSTCP::must_create()); directory->m_components.append(ProcFSLocalNet::must_create()); directory->m_components.append(ProcFSUDP::must_create()); @@ -249,6 +282,10 @@ UNMAP_AFTER_INIT ProcFSARP::ProcFSARP() : ProcFSGlobalInformation("arp"sv) { } +UNMAP_AFTER_INIT ProcFSRoute::ProcFSRoute() + : ProcFSGlobalInformation("route"sv) +{ +} UNMAP_AFTER_INIT ProcFSTCP::ProcFSTCP() : ProcFSGlobalInformation("tcp"sv) { diff --git a/Userland/Utilities/CMakeLists.txt b/Userland/Utilities/CMakeLists.txt index d6026c07ec..a857f87856 100644 --- a/Userland/Utilities/CMakeLists.txt +++ b/Userland/Utilities/CMakeLists.txt @@ -3,7 +3,7 @@ list(APPEND SPECIAL_TARGETS test install) list(APPEND REQUIRED_TARGETS arp base64 basename cat chmod chown clear comm cp cut date dd df diff dirname dmesg du echo env expr false fgrep file find grep groups head host hostname id ifconfig kill killall ln logout ls mkdir mount mv nproc - pgrep pidof ping pmap ps readlink realpath reboot rm rmdir seq shutdown sleep sort stat stty su tail test + pgrep pidof ping pmap ps readlink realpath reboot rm rmdir route seq shutdown sleep sort stat stty su tail test touch tr true umount uname uniq uptime w wc which whoami xargs yes less ) list(APPEND RECOMMENDED_TARGETS @@ -177,6 +177,7 @@ target_link_libraries(reboot LibMain) target_link_libraries(rev LibMain) target_link_libraries(rm LibMain) target_link_libraries(rmdir LibMain) +target_link_libraries(route LibMain) target_link_libraries(run-tests LibRegex LibCoredump LibMain) target_link_libraries(seq LibMain) target_link_libraries(shot LibGUI LibMain) diff --git a/Userland/Utilities/route.cpp b/Userland/Utilities/route.cpp new file mode 100644 index 0000000000..2b41e0de1f --- /dev/null +++ b/Userland/Utilities/route.cpp @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2022, Brandon Pruitt <brapru@pm.me> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <AK/IPv4Address.h> +#include <AK/JsonArray.h> +#include <AK/JsonObject.h> +#include <AK/QuickSort.h> +#include <AK/String.h> +#include <AK/StringView.h> +#include <LibCore/ArgsParser.h> +#include <LibCore/File.h> +#include <LibCore/ProcessStatisticsReader.h> +#include <LibCore/System.h> +#include <LibMain/Main.h> +#include <net/if.h> +#include <net/route.h> +#include <netinet/in.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <unistd.h> + +ErrorOr<int> serenity_main(Main::Arguments arguments) +{ + TRY(Core::System::pledge("stdio rpath inet")); + TRY(Core::System::unveil("/proc/net", "r")); + TRY(Core::System::unveil(nullptr, nullptr)); + + StringView modify_action; + StringView value_host_address; + StringView value_network_address; + StringView value_gateway_address; + StringView value_netmask_address; + StringView value_interface; + + Core::ArgsParser args_parser; + args_parser.set_general_help("Display kernel routing table"); + args_parser.add_positional_argument(modify_action, "Modify the global routing table { add | del }", "action", Core::ArgsParser::Required::No); + args_parser.add_option(value_host_address, "Target destination is an IPv4 address", "host", 'h', "host"); + args_parser.add_option(value_network_address, "Target destination is a network address", "net", 'n', "net"); + args_parser.add_option(value_gateway_address, "Route packets via a gateway", "gw", 'g', "gw"); + args_parser.add_option(value_netmask_address, "The netmask to be used when adding a network route", "netmask", 'm', "netmask"); + args_parser.add_option(value_interface, "Force the route to be associated with the specified device interface", "interface", 'i', "interface"); + args_parser.parse(arguments); + + enum class Alignment { + Left, + Right + }; + + struct Column { + String title; + Alignment alignment { Alignment::Left }; + int width { 0 }; + String buffer; + }; + + Vector<Column> columns; + + int destination_column = -1; + int gateway_column = -1; + int genmask_column = -1; + int interface_column = -1; + + auto add_column = [&](auto title, auto alignment, auto width) { + columns.append({ title, alignment, width, {} }); + return columns.size() - 1; + }; + + destination_column = add_column("Destination", Alignment::Left, 15); + gateway_column = add_column("Gateway", Alignment::Left, 15); + genmask_column = add_column("Genmask", Alignment::Left, 15); + interface_column = add_column("Interface", Alignment::Left, 9); + + auto print_column = [](auto& column, auto& string) { + if (!column.width) { + out("{}", string); + return; + } + if (column.alignment == Alignment::Right) { + out("{:>{1}} "sv, string, column.width); + } else { + out("{:<{1}} "sv, string, column.width); + } + }; + + if (modify_action.is_empty()) { + auto file = TRY(Core::File::open("/proc/net/route", Core::OpenMode::ReadOnly)); + auto file_contents = file->read_all(); + auto json = TRY(JsonValue::from_string(file_contents)); + + outln("Kernel IP routing table"); + + for (auto& column : columns) + print_column(column, column.title); + outln(); + + Vector<JsonValue> sorted_regions = json.as_array().values(); + quick_sort(sorted_regions, [](auto& a, auto& b) { + return a.as_object().get("destination").to_string() < b.as_object().get("destination").to_string(); + }); + + for (auto& value : sorted_regions) { + auto& if_object = value.as_object(); + + auto destination = if_object.get("destination").to_string(); + auto gateway = if_object.get("gateway").to_string(); + auto genmask = if_object.get("genmask").to_string(); + auto interface = if_object.get("interface").to_string(); + + if (destination_column != -1) + columns[destination_column].buffer = destination; + if (gateway_column != -1) + columns[gateway_column].buffer = gateway; + if (genmask_column != -1) + columns[genmask_column].buffer = genmask; + if (interface_column != -1) + columns[interface_column].buffer = interface; + + for (auto& column : columns) + print_column(column, column.buffer); + outln(); + }; + } + + if (!modify_action.is_empty()) { + bool const action_add = (modify_action == "add"); + bool const action_del = (modify_action == "del"); + + if (!action_add && !action_del) { + warnln("Invalid modify action: {}", modify_action); + return 1; + } + + if (value_host_address.is_empty() && value_network_address.is_empty()) { + warnln("No target host or network specified"); + return 1; + } + + Optional<IPv4Address> destination; + + if (!value_host_address.is_empty()) + destination = AK::IPv4Address::from_string(value_host_address); + + if (!value_network_address.is_empty()) + destination = AK::IPv4Address::from_string(value_network_address); + + if (!destination.has_value()) { + warnln("Invalid destination IPv4 address"); + return 1; + } + + auto gateway = AK::IPv4Address::from_string(value_gateway_address); + if (!gateway.has_value()) { + warnln("Invalid gateway IPv4 address: '{}'", value_gateway_address); + return 1; + } + + auto genmask = AK::IPv4Address::from_string(value_netmask_address); + if (!genmask.has_value()) { + warnln("Invalid genmask IPv4 address: '{}'", value_netmask_address); + return 1; + } + + int fd = TRY(Core::System::socket(AF_INET, SOCK_DGRAM, IPPROTO_IP)); + + rtentry rt {}; + memset(&rt, 0, sizeof(rt)); + + rt.rt_dev = const_cast<char*>(value_interface.characters_without_null_termination()); + rt.rt_gateway.sa_family = AF_INET; + ((sockaddr_in&)rt.rt_dst).sin_addr.s_addr = destination.value().to_in_addr_t(); + ((sockaddr_in&)rt.rt_gateway).sin_addr.s_addr = gateway.value().to_in_addr_t(); + ((sockaddr_in&)rt.rt_genmask).sin_addr.s_addr = genmask.value().to_in_addr_t(); + rt.rt_flags = RTF_UP | RTF_GATEWAY; + + if (action_add) + TRY(Core::System::ioctl(fd, SIOCADDRT, &rt)); + + // FIXME: Add support for route deletion. + if (action_del) { + warnln("Route deletion currently not implemented."); + return 1; + } + } + + return 0; +} |