Serenity Operating System
1/*
2 * Copyright (c) 2022, Brandon Pruitt <brapru@pm.me>
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7#include <AK/DeprecatedString.h>
8#include <AK/IPv4Address.h>
9#include <AK/JsonArray.h>
10#include <AK/JsonObject.h>
11#include <AK/QuickSort.h>
12#include <AK/StringView.h>
13#include <LibCore/ArgsParser.h>
14#include <LibCore/File.h>
15#include <LibCore/ProcessStatisticsReader.h>
16#include <LibCore/System.h>
17#include <LibMain/Main.h>
18#include <net/if.h>
19#include <net/route.h>
20#include <netinet/in.h>
21#include <sys/ioctl.h>
22#include <sys/socket.h>
23#include <unistd.h>
24
25ErrorOr<int> serenity_main(Main::Arguments arguments)
26{
27 TRY(Core::System::pledge("stdio rpath inet"));
28 TRY(Core::System::unveil("/sys/kernel/net", "r"));
29 TRY(Core::System::unveil(nullptr, nullptr));
30
31 StringView modify_action;
32 StringView value_host_address;
33 StringView value_network_address;
34 StringView value_gateway_address;
35 StringView value_netmask_address;
36 StringView value_interface;
37
38 Core::ArgsParser args_parser;
39 args_parser.set_general_help("Display kernel routing table");
40 args_parser.add_positional_argument(modify_action, "Modify the global routing table { add | del }", "action", Core::ArgsParser::Required::No);
41 args_parser.add_option(value_host_address, "Target destination is an IPv4 address", "host", 'h', "host");
42 args_parser.add_option(value_network_address, "Target destination is a network address", "net", 'n', "net");
43 args_parser.add_option(value_gateway_address, "Route packets via a gateway", "gw", 'g', "gw");
44 args_parser.add_option(value_netmask_address, "The netmask to be used when adding a network route", "netmask", 'm', "netmask");
45 args_parser.add_option(value_interface, "Force the route to be associated with the specified device interface", "interface", 'i', "interface");
46 args_parser.parse(arguments);
47
48 enum class Alignment {
49 Left,
50 Right
51 };
52
53 struct Column {
54 DeprecatedString title;
55 Alignment alignment { Alignment::Left };
56 int width { 0 };
57 DeprecatedString buffer;
58 };
59
60 Vector<Column> columns;
61
62 int destination_column = -1;
63 int gateway_column = -1;
64 int genmask_column = -1;
65 int flags_column = -1;
66 int interface_column = -1;
67
68 auto add_column = [&](auto title, auto alignment, auto width) {
69 columns.append({ title, alignment, width, {} });
70 return columns.size() - 1;
71 };
72
73 destination_column = add_column("Destination", Alignment::Left, 15);
74 gateway_column = add_column("Gateway", Alignment::Left, 15);
75 genmask_column = add_column("Genmask", Alignment::Left, 15);
76 flags_column = add_column("Flags", Alignment::Left, 5);
77 interface_column = add_column("Interface", Alignment::Left, 9);
78
79 auto print_column = [](auto& column, auto& string) {
80 if (!column.width) {
81 out("{}", string);
82 return;
83 }
84 if (column.alignment == Alignment::Right) {
85 out("{:>{1}} "sv, string, column.width);
86 } else {
87 out("{:<{1}} "sv, string, column.width);
88 }
89 };
90
91 if (modify_action.is_empty()) {
92 auto file = TRY(Core::File::open("/sys/kernel/net/route"sv, Core::File::OpenMode::Read));
93 auto file_contents = TRY(file->read_until_eof());
94 auto json = TRY(JsonValue::from_string(file_contents));
95
96 outln("Kernel IP routing table");
97
98 for (auto& column : columns)
99 print_column(column, column.title);
100 outln();
101
102 Vector<JsonValue> sorted_regions = json.as_array().values();
103 quick_sort(sorted_regions, [](auto& a, auto& b) {
104 return a.as_object().get_deprecated_string("destination"sv).value_or({}) < b.as_object().get_deprecated_string("destination"sv).value_or({});
105 });
106
107 for (auto& value : sorted_regions) {
108 auto& if_object = value.as_object();
109
110 auto destination = if_object.get_deprecated_string("destination"sv).value_or({});
111 auto gateway = if_object.get_deprecated_string("gateway"sv).value_or({});
112 auto genmask = if_object.get_deprecated_string("genmask"sv).value_or({});
113 auto interface = if_object.get_deprecated_string("interface"sv).value_or({});
114 auto flags = if_object.get_u32("flags"sv).value_or(0);
115
116 StringBuilder flags_builder;
117 if (flags & RTF_UP)
118 flags_builder.append('U');
119 if (flags & RTF_GATEWAY)
120 flags_builder.append('G');
121 if (flags & RTF_HOST)
122 flags_builder.append('H');
123
124 if (destination_column != -1)
125 columns[destination_column].buffer = destination;
126 if (gateway_column != -1)
127 columns[gateway_column].buffer = gateway;
128 if (genmask_column != -1)
129 columns[genmask_column].buffer = genmask;
130 if (flags_column != -1)
131 columns[flags_column].buffer = flags_builder.string_view();
132 if (interface_column != -1)
133 columns[interface_column].buffer = interface;
134
135 for (auto& column : columns)
136 print_column(column, column.buffer);
137 outln();
138 };
139 }
140
141 if (!modify_action.is_empty()) {
142 bool const action_add = (modify_action == "add");
143 bool const action_del = (modify_action == "del");
144
145 if (!action_add && !action_del) {
146 warnln("Invalid modify action: {}", modify_action);
147 return 1;
148 }
149
150 if (value_host_address.is_empty() && value_network_address.is_empty()) {
151 warnln("No target host or network specified");
152 return 1;
153 }
154
155 Optional<IPv4Address> destination;
156
157 if (!value_host_address.is_empty())
158 destination = AK::IPv4Address::from_string(value_host_address);
159
160 StringView address;
161 StringView cidr;
162 if (!value_network_address.is_empty()) {
163 // Check if a CIDR notation was provided and parse accordingly
164 if (auto position = value_network_address.find('/'); position.has_value()) {
165 address = value_network_address.substring_view(0, position.value());
166 cidr = value_network_address.substring_view(position.value() + 1);
167 } else {
168 address = value_network_address;
169 }
170 destination = AK::IPv4Address::from_string(address);
171 }
172
173 if (!destination.has_value()) {
174 warnln("Invalid destination IPv4 address");
175 return 1;
176 }
177
178 auto gateway = AK::IPv4Address::from_string(value_gateway_address);
179 if (action_add && !gateway.has_value()) {
180 warnln("Invalid gateway IPv4 address: '{}'", value_gateway_address);
181 return 1;
182 }
183
184 Optional<IPv4Address> genmask;
185 if (auto cidr_int = cidr.to_int(); cidr_int.has_value())
186 genmask = AK::IPv4Address::netmask_from_cidr(cidr_int.value());
187 else
188 genmask = AK::IPv4Address::from_string(value_netmask_address);
189
190 if (!genmask.has_value()) {
191 warnln("Invalid genmask IPv4 address: '{}'", value_netmask_address);
192 return 1;
193 }
194
195 int fd = TRY(Core::System::socket(AF_INET, SOCK_DGRAM, IPPROTO_IP));
196
197 rtentry rt {};
198 memset(&rt, 0, sizeof(rt));
199
200 rt.rt_dev = const_cast<char*>(value_interface.characters_without_null_termination());
201 rt.rt_gateway.sa_family = AF_INET;
202 ((sockaddr_in&)rt.rt_dst).sin_addr.s_addr = destination.value().to_in_addr_t();
203 ((sockaddr_in&)rt.rt_gateway).sin_addr.s_addr = gateway.value_or(IPv4Address {}).to_in_addr_t();
204 ((sockaddr_in&)rt.rt_genmask).sin_addr.s_addr = genmask.value().to_in_addr_t();
205 rt.rt_flags = RTF_UP;
206
207 if (!value_host_address.is_empty())
208 rt.rt_flags |= RTF_HOST;
209
210 rt.rt_flags |= RTF_GATEWAY;
211
212 if (action_add)
213 TRY(Core::System::ioctl(fd, SIOCADDRT, &rt));
214
215 if (action_del)
216 TRY(Core::System::ioctl(fd, SIOCDELRT, &rt));
217 }
218
219 return 0;
220}