Serenity Operating System
1/*
2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7#include <AK/ByteBuffer.h>
8#include <AK/HashTable.h>
9#include <LibCore/ArgsParser.h>
10#include <LibCore/EventLoop.h>
11#include <LibCore/Socket.h>
12#include <LibCore/System.h>
13#include <LibMain/Main.h>
14#include <arpa/inet.h>
15#include <errno.h>
16#include <netdb.h>
17#include <netinet/in.h>
18#include <stdio.h>
19#include <string.h>
20#include <sys/select.h>
21#include <sys/socket.h>
22#include <sys/time.h>
23#include <sys/types.h>
24#include <unistd.h>
25
26// NOTE: `warnln` is used instead of `outln` because we want to redirect all
27// output to stderr to allow for commands like:
28//
29// nc -l someport > out.file
30//
31// Below man page was considered to come up default bounds
32// for SO_RCVBUF
33// https://man7.org/linux/man-pages/man7/socket.7.html
34static constexpr size_t maximum_tcp_receive_buffer_size_upper_bound = 212992;
35static constexpr size_t maximum_tcp_receive_buffer_size_lower_bound = 256;
36
37static size_t get_maximum_tcp_buffer_size(size_t input_buf_size)
38{
39 if (input_buf_size < maximum_tcp_receive_buffer_size_lower_bound)
40 return maximum_tcp_receive_buffer_size_lower_bound;
41 if (input_buf_size > maximum_tcp_receive_buffer_size_upper_bound)
42 return maximum_tcp_receive_buffer_size_upper_bound;
43 return input_buf_size;
44};
45
46ErrorOr<int> serenity_main(Main::Arguments arguments)
47{
48 bool should_listen = false;
49 bool verbose = false;
50 bool should_close = false;
51 bool udp_mode = false;
52 DeprecatedString target;
53 int port = 0;
54 int maximum_tcp_receive_buffer_size_input = -1;
55
56 Core::ArgsParser args_parser;
57 args_parser.set_general_help("Network cat: Connect to network sockets as if it were a file.");
58 args_parser.add_option(should_listen, "Listen instead of connecting", "listen", 'l');
59 args_parser.add_option(verbose, "Log everything that's happening", "verbose", 'v');
60 args_parser.add_option(udp_mode, "UDP mode", "udp", 'u');
61 args_parser.add_option(should_close, "Close connection after reading stdin to the end", nullptr, 'N');
62 args_parser.add_option(maximum_tcp_receive_buffer_size_input, "Set maximum tcp receive buffer size", "length", 'I', nullptr);
63 args_parser.add_positional_argument(target, "Address to listen on, or the address or hostname to connect to", "target");
64 args_parser.add_positional_argument(port, "Port to connect to or listen on", "port");
65 args_parser.parse(arguments);
66
67 if (udp_mode) {
68 if (should_listen) {
69 warnln("listening on UDP not yet supported");
70 return 1;
71 }
72
73 Core::EventLoop loop;
74 auto socket = TRY(Core::UDPSocket::connect(target, port));
75
76 if (verbose)
77 warnln("connected to {}:{}", target, port);
78
79 Array<u8, 1024> buffer;
80 for (;;) {
81 Bytes buffer_span = buffer.span();
82 auto nread = TRY(Core::System::read(STDIN_FILENO, buffer_span));
83 buffer_span = buffer_span.trim(nread);
84
85 TRY(socket->write_until_depleted({ buffer_span.data(), static_cast<size_t>(nread) }));
86 }
87 }
88
89 int fd = -1;
90 int listen_fd = -1;
91
92 if (should_listen) {
93 listen_fd = TRY(Core::System::socket(AF_INET, SOCK_STREAM, 0));
94
95 sockaddr_in sa {};
96 sa.sin_family = AF_INET;
97 sa.sin_port = htons(port);
98 sa.sin_addr.s_addr = htonl(INADDR_ANY);
99 if (!target.is_empty()) {
100 if (inet_pton(AF_INET, target.characters(), &sa.sin_addr) <= 0) {
101 perror("inet_pton");
102 return 1;
103 }
104 }
105
106 TRY(Core::System::bind(listen_fd, (struct sockaddr*)&sa, sizeof(sa)));
107 TRY(Core::System::listen(listen_fd, 1));
108
109 char addr_str[INET_ADDRSTRLEN];
110 sockaddr_in sin;
111 socklen_t len;
112
113 len = sizeof(sin);
114 TRY(Core::System::getsockname(listen_fd, (struct sockaddr*)&sin, &len));
115
116 if (verbose)
117 warnln("waiting for a connection on {}:{}", inet_ntop(sin.sin_family, &sin.sin_addr, addr_str, sizeof(addr_str) - 1), ntohs(sin.sin_port));
118
119 } else {
120 fd = TRY(Core::System::socket(AF_INET, SOCK_STREAM, 0));
121
122 struct timeval timeout {
123 3, 0
124 };
125 TRY(Core::System::setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)));
126 TRY(Core::System::setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)));
127
128 auto* hostent = gethostbyname(target.characters());
129 if (!hostent) {
130 warnln("Socket::connect: Unable to resolve '{}'", target);
131 return 1;
132 }
133
134 sockaddr_in dst_addr {};
135 dst_addr.sin_family = AF_INET;
136 dst_addr.sin_port = htons(port);
137 dst_addr.sin_addr.s_addr = *(in_addr_t const*)hostent->h_addr_list[0];
138
139 if (verbose) {
140 char addr_str[INET_ADDRSTRLEN];
141 warnln("connecting to {}:{}", inet_ntop(dst_addr.sin_family, &dst_addr.sin_addr, addr_str, sizeof(addr_str) - 1), ntohs(dst_addr.sin_port));
142 }
143
144 TRY(Core::System::connect(fd, (struct sockaddr*)&dst_addr, sizeof(dst_addr)));
145 if (verbose)
146 warnln("connected!");
147 }
148
149 HashTable<int> connected_clients;
150
151 bool stdin_closed = false;
152 bool fd_closed = false;
153 bool listen_fd_closed = false;
154
155 fd_set readfds, writefds, exceptfds;
156
157 size_t receive_buffer_size = get_maximum_tcp_buffer_size(maximum_tcp_receive_buffer_size_input);
158 if (verbose && (maximum_tcp_receive_buffer_size_input != -1)) {
159 warnln("receive_buffer_size set to {}", receive_buffer_size);
160 }
161
162 while (!stdin_closed || !fd_closed || !listen_fd_closed) {
163 FD_ZERO(&readfds);
164 FD_ZERO(&writefds);
165 FD_ZERO(&exceptfds);
166
167 int highest_fd = 0;
168
169 if (!stdin_closed) {
170 FD_SET(STDIN_FILENO, &readfds);
171 FD_SET(STDIN_FILENO, &exceptfds);
172 highest_fd = max(highest_fd, STDIN_FILENO);
173 }
174 if (!fd_closed && fd) {
175 FD_SET(fd, &readfds);
176 FD_SET(fd, &exceptfds);
177 highest_fd = max(highest_fd, fd);
178 }
179
180 if (!listen_fd_closed && listen_fd) {
181 FD_SET(listen_fd, &readfds);
182 FD_SET(listen_fd, &exceptfds);
183 highest_fd = max(highest_fd, listen_fd);
184 }
185
186 bool has_clients = (should_listen && !connected_clients.is_empty());
187 if (has_clients) {
188 for (auto const& client_fd : connected_clients) {
189 FD_SET(client_fd, &readfds);
190 FD_SET(client_fd, &exceptfds);
191 highest_fd = max(highest_fd, client_fd);
192 }
193 }
194
195 int ready = select(highest_fd + 1, &readfds, &writefds, &exceptfds, nullptr);
196 if (ready == -1) {
197 if (errno == EINTR)
198 continue;
199
200 perror("select");
201 return 1;
202 }
203
204 if (!stdin_closed && FD_ISSET(STDIN_FILENO, &readfds)) {
205 Array<u8, 1024> buffer;
206 Bytes buffer_span = buffer.span();
207 auto nread = TRY(Core::System::read(STDIN_FILENO, buffer_span));
208 buffer_span = buffer_span.trim(nread);
209
210 // stdin closed
211 if (nread == 0) {
212 stdin_closed = true;
213 if (verbose)
214 warnln("stdin closed");
215 if (should_close) {
216 if (should_listen) {
217 TRY(Core::System::close(listen_fd));
218 listen_fd_closed = true;
219 } else {
220 TRY(Core::System::close(fd));
221 fd_closed = true;
222 }
223 }
224 } else {
225 if (should_listen && has_clients) {
226 for (auto const& client_fd : connected_clients)
227 TRY(Core::System::write(client_fd, buffer_span));
228 } else {
229 TRY(Core::System::write(fd, buffer_span));
230 }
231 }
232 }
233
234 if (!fd_closed && FD_ISSET(fd, &readfds)) {
235 auto buffer = TRY(ByteBuffer::create_uninitialized(receive_buffer_size));
236 Bytes buffer_span = buffer.bytes();
237 auto nread = TRY(Core::System::read(fd, buffer_span));
238 buffer_span = buffer_span.trim(nread);
239
240 // remote end closed
241 if (nread == 0) {
242 close(STDIN_FILENO);
243 stdin_closed = true;
244 fd_closed = true;
245 if (verbose)
246 warnln("remote closed");
247 } else {
248 TRY(Core::System::write(STDOUT_FILENO, buffer_span));
249 }
250 }
251
252 if (!listen_fd_closed && FD_ISSET(listen_fd, &readfds)) {
253 char client_str[INET_ADDRSTRLEN];
254 sockaddr_in client;
255 socklen_t clientlen = sizeof(client);
256
257 int new_client = TRY(Core::System::accept(listen_fd, (struct sockaddr*)&client, &clientlen));
258 connected_clients.set(new_client);
259
260 if (verbose)
261 warnln("got connection from {}:{}", inet_ntop(client.sin_family, &client.sin_addr, client_str, sizeof(client_str) - 1), ntohs(client.sin_port));
262 }
263
264 if (has_clients) {
265 for (auto const client_fd : connected_clients) {
266 if (FD_ISSET(client_fd, &readfds)) {
267 Array<u8, 1024> buffer;
268 Bytes buffer_span = buffer.span();
269 auto nread = TRY(Core::System::read(client_fd, buffer_span));
270 buffer_span = buffer_span.trim(nread);
271
272 if (nread == 0) {
273 if (verbose) {
274 struct sockaddr_in client;
275 socklen_t clientlen = sizeof(client);
276 TRY(Core::System::getpeername(client_fd, (struct sockaddr*)&client, &clientlen));
277 warnln("remote connection closed {}:{}", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
278 }
279 connected_clients.remove(client_fd);
280 close(client_fd);
281 FD_CLR(client_fd, &readfds);
282 FD_CLR(client_fd, &exceptfds);
283 } else {
284 TRY(Core::System::write(STDOUT_FILENO, buffer_span));
285 }
286 }
287 }
288 }
289 }
290
291 return 0;
292}