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 must_create(); + +private: + ProcFSRoute(); + virtual ErrorOr try_generate(KBufferBuilder& builder) override + { + auto array = TRY(JsonArraySerializer<>::try_create(builder)); + TRY(routing_table().with([&](auto const& table) -> ErrorOr { + 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 must_create(); @@ -217,6 +245,10 @@ UNMAP_AFTER_INIT NonnullRefPtr ProcFSARP::must_create() { return adopt_ref_if_nonnull(new (nothrow) ProcFSARP).release_nonnull(); } +UNMAP_AFTER_INIT NonnullRefPtr ProcFSRoute::must_create() +{ + return adopt_ref_if_nonnull(new (nothrow) ProcFSRoute).release_nonnull(); +} UNMAP_AFTER_INIT NonnullRefPtr ProcFSTCP::must_create() { return adopt_ref_if_nonnull(new (nothrow) ProcFSTCP).release_nonnull(); @@ -235,6 +267,7 @@ UNMAP_AFTER_INIT NonnullRefPtr 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 + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +ErrorOr 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 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 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 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(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; +}