]> git.ipfire.org Git - fireperf.git/blob - src/main.c
Set socket buffer sizes on both sides for duplex traffic
[fireperf.git] / src / main.c
1 /*#############################################################################
2 # #
3 # fireperf - A network benchmarking tool #
4 # Copyright (C) 2021 IPFire Development Team #
5 # #
6 # This program is free software: you can redistribute it and/or modify #
7 # it under the terms of the GNU General Public License as published by #
8 # the Free Software Foundation, either version 3 of the License, or #
9 # (at your option) any later version. #
10 # #
11 # This program is distributed in the hope that it will be useful, #
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of #
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
14 # GNU General Public License for more details. #
15 # #
16 # You should have received a copy of the GNU General Public License #
17 # along with this program. If not, see <http://www.gnu.org/licenses/>. #
18 # #
19 #############################################################################*/
20
21 #include <arpa/inet.h>
22 #include <errno.h>
23 #include <getopt.h>
24 #include <netinet/in.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <sys/epoll.h>
29 #include <sys/resource.h>
30 #include <sys/time.h>
31 #include <sys/timerfd.h>
32 #include <time.h>
33 #include <unistd.h>
34
35 #include "client.h"
36 #include "main.h"
37 #include "logging.h"
38 #include "random.h"
39 #include "server.h"
40 #include "util.h"
41
42 static int parse_address(const char* string, struct in6_addr* address6) {
43 // Try parsing this address
44 int r = inet_pton(AF_INET6, string, address6);
45
46 // Success!
47 if (r == 1)
48 return 0;
49
50 // Try parsing this as an IPv4 address
51 struct in_addr address4;
52 r = inet_pton(AF_INET, string, &address4);
53 if (r == 1) {
54 // Convert to IPv6-mapped address
55 address6->s6_addr32[0] = htonl(0x0000);
56 address6->s6_addr32[1] = htonl(0x0000);
57 address6->s6_addr32[2] = htonl(0xffff);
58 address6->s6_addr32[3] = address4.s_addr;
59
60 return 0;
61 }
62
63 // Could not parse this
64 return 1;
65 }
66
67 static int check_port(int port) {
68 if (port <= 0 || port >= 65536) {
69 fprintf(stderr, "Invalid port number: %u\n", port);
70 return 2;
71 }
72
73 return 0;
74 }
75
76 static int parse_port_range(struct fireperf_config* conf, const char* optarg) {
77 int first_port, last_port;
78
79 int r = sscanf(optarg, "%d:%d", &first_port, &last_port);
80 if (r != 2)
81 return 1;
82
83 // Check if both ports are in range
84 r = check_port(first_port);
85 if (r)
86 return r;
87
88 r = check_port(last_port);
89 if (r)
90 return r;
91
92 if (first_port > last_port) {
93 fprintf(stderr, "Invalid port range: %s\n", optarg);
94 return 2;
95 }
96
97 conf->port = first_port;
98 conf->listening_sockets = (last_port - first_port) + 1;
99
100 return 0;
101 }
102
103 static int parse_port(struct fireperf_config* conf, const char* optarg) {
104 conf->port = atoi(optarg);
105 conf->listening_sockets = 1;
106
107 return check_port(conf->port);
108 }
109
110 static int set_limits(struct fireperf_config* conf) {
111 struct rlimit limit;
112
113 // Increase limit of open files
114 limit.rlim_cur = limit.rlim_max = conf->parallel + conf->listening_sockets + 128;
115
116 int r = setrlimit(RLIMIT_NOFILE, &limit);
117 if (r) {
118 ERROR(conf, "Could not set open file limit to %lu: %s\n",
119 (unsigned long)limit.rlim_max, strerror(errno));
120 return 1;
121 }
122
123 return 0;
124 }
125
126 static int parse_argv(int argc, char* argv[], struct fireperf_config* conf) {
127 static struct option long_options[] = {
128 {"client", required_argument, 0, 'c'},
129 {"close", no_argument, 0, 'x'},
130 {"debug", no_argument, 0, 'd'},
131 {"duplex", no_argument, 0, 'D'},
132 {"keepalive", no_argument, 0, 'k'},
133 {"parallel", required_argument, 0, 'P'},
134 {"port", required_argument, 0, 'p'},
135 {"server", no_argument, 0, 's'},
136 {"timeout", required_argument, 0, 't'},
137 {"version", no_argument, 0, 'V'},
138 {"zero", no_argument, 0, 'z'},
139 {0, 0, 0, 0},
140 };
141
142 int option_index = 0;
143 int done = 0;
144
145 while (!done) {
146 int c = getopt_long(argc, argv, "c:dkp:st:xzDP:V", long_options, &option_index);
147
148 // End
149 if (c == -1)
150 break;
151
152 switch (c) {
153 case 0:
154 if (long_options[option_index].flag != 0)
155 break;
156
157 printf("option %s", long_options[option_index].name);
158
159 if (optarg)
160 printf(" with arg: %s", optarg);
161
162 printf("\n");
163 break;
164
165 case '?':
166 // getopt_long already printed the error message
167 return 1;
168
169 case 'V':
170 printf("%s %s\n", PACKAGE_NAME, PACKAGE_VERSION);
171 printf("Copyright (C) 2021 The IPFire Project (https://www.ipfire.org/)\n");
172 printf("License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>\n");
173 printf("This is free software: you are free to change and redistribute it\n");
174 printf("There is NO WARRANTY, to the extent permitted by law.\n\n");
175 printf("Written by Michael Tremer\n");
176
177 exit(0);
178 break;
179
180 case 'c':
181 conf->mode = FIREPERF_MODE_CLIENT;
182
183 // Parse the given IP address
184 int r = parse_address(optarg, &conf->address);
185 if (r) {
186 fprintf(stderr, "Could not parse IP address %s\n", optarg);
187 return 2;
188 }
189 break;
190
191 case 'D':
192 conf->duplex = 1;
193 break;
194
195 case 'd':
196 conf->loglevel = LOG_DEBUG;
197 break;
198
199 case 'k':
200 conf->keepalive_only = 1;
201 break;
202
203 case 'P':
204 conf->parallel = strtoul(optarg, NULL, 10);
205
206 if (conf->parallel > MAX_PARALLEL) {
207 fprintf(stderr, "Number of parallel connections is too high: %lu\n",
208 conf->parallel);
209 return 2;
210 }
211 break;
212
213 case 'p':
214 // Try parsing the port range first.
215 // If this fails, we try parsing a single port
216 r = parse_port_range(conf, optarg);
217 if (r == 1)
218 r = parse_port(conf, optarg);
219 if (r)
220 return r;
221
222 break;
223
224 case 's':
225 conf->mode = FIREPERF_MODE_SERVER;
226 break;
227
228 case 't':
229 conf->timeout = strtoul(optarg, NULL, 10);
230 break;
231
232 case 'x':
233 conf->close = 1;
234 break;
235
236 case 'z':
237 conf->zero = 1;
238 break;
239
240 default:
241 done = 1;
242 break;
243 }
244 }
245
246 return 0;
247 }
248
249 int main(int argc, char* argv[]) {
250 struct fireperf_config conf = {
251 .keepalive_count = DEFAULT_KEEPALIVE_COUNT,
252 .keepalive_interval = DEFAULT_KEEPALIVE_INTERVAL,
253 .listening_sockets = DEFAULT_LISTENING_SOCKETS,
254 .loglevel = DEFAULT_LOG_LEVEL,
255 .mode = FIREPERF_MODE_NONE,
256 .port = DEFAULT_PORT,
257 .parallel = DEFAULT_PARALLEL,
258 };
259 struct fireperf_stats stats = { 0 };
260 int r;
261 int timerfd = -1;
262
263 // Parse command line
264 r = parse_argv(argc, argv, &conf);
265 if (r)
266 return r;
267
268 // Initialise random number generator
269 srandom(time(NULL));
270
271 // Set limits
272 r = set_limits(&conf);
273 if (r)
274 return r;
275
276 // Initialize random pool
277 if (!conf.zero) {
278 conf.pool = fireperf_random_pool_create(&conf, DEFAULT_RANDOM_POOL_SIZE);
279 if (!conf.pool) {
280 ERROR(&conf, "Could not allocate random data\n");
281 r = 1;
282 goto ERROR;
283 }
284 }
285
286 // Initialize epoll()
287 int epollfd = epoll_create1(0);
288 if (epollfd < 0) {
289 ERROR(&conf, "Could not initialize epoll(): %s\n", strerror(errno));
290 r = 1;
291 goto ERROR;
292 }
293
294 // Create timerfd() to print statistics
295 timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK|TFD_CLOEXEC);
296 if (timerfd < 0) {
297 ERROR(&conf, "timerfd_create() failed: %s\n", strerror(errno));
298 r = 1;
299 goto ERROR;
300 }
301
302 struct epoll_event ev = {
303 .events = EPOLLIN,
304 .data.fd = timerfd,
305 };
306
307 if (epoll_ctl(epollfd, EPOLL_CTL_ADD, timerfd, &ev)) {
308 ERROR(&conf, "Could not add timerfd to epoll(): %s\n", strerror(errno));
309 r = 1;
310 goto ERROR;
311 }
312
313 // Let the timer ping us once a second
314 struct itimerspec timer = {
315 .it_interval.tv_sec = 1,
316 .it_value.tv_sec = 1,
317 };
318
319 r = timerfd_settime(timerfd, 0, &timer, NULL);
320 if (r) {
321 ERROR(&conf, "Could not set timer: %s\n", strerror(errno));
322 r = 1;
323 goto ERROR;
324 }
325
326 switch (conf.mode) {
327 case FIREPERF_MODE_CLIENT:
328 return fireperf_client(&conf, &stats, epollfd, timerfd);
329
330 case FIREPERF_MODE_SERVER:
331 return fireperf_server(&conf, &stats, epollfd, timerfd);
332
333 case FIREPERF_MODE_NONE:
334 fprintf(stderr, "No mode selected\n");
335 r = 2;
336 break;
337 }
338
339 ERROR:
340 if (epollfd > 0)
341 close(epollfd);
342
343 if (timerfd > 0)
344 close(timerfd);
345
346 if (conf.pool)
347 fireperf_random_pool_free(conf.pool);
348
349 return r;
350 }
351
352 int fireperf_dump_stats(struct fireperf_config* conf, struct fireperf_stats* stats, int mode) {
353 struct timespec now;
354
355 // Fetch the time
356 int r = clock_gettime(CLOCK_REALTIME, &now);
357 if (r) {
358 ERROR(conf, "Could not fetch the time: %s\n", strerror(errno));
359 return 1;
360 }
361
362 double delta = timespec_delta(&now, &stats->last_printed);
363
364 // Called too soon again?
365 if (delta < 0.1)
366 return 0;
367
368 // Format timestamp
369 const char* timestamp = format_timespec(&now);
370
371 INFO(conf, "--- %s -------------------------\n", timestamp);
372 INFO(conf, " : %12s %12s\n", "RX", "TX");
373 INFO(conf, " %-20s: %25u\n", "Open Connection(s)", stats->open_connections);
374 INFO(conf, " %-20s: %23.2f/s\n", "New Connections", stats->connections / delta);
375
376 // Show current bandwidth
377 char* bps_received = format_size(stats->bytes_received * 8 / delta, FIREPERF_FORMAT_BITS);
378 char* bps_sent = format_size(stats->bytes_sent * 8 / delta, FIREPERF_FORMAT_BITS);
379
380 if (bps_received || bps_sent) {
381 INFO(conf, " %-20s: %10s/s %10s/s\n", "Current Bandwidth", bps_received, bps_sent);
382
383 if (bps_received)
384 free(bps_received);
385 if (bps_sent)
386 free(bps_sent);
387 }
388
389 // Total bytes
390 char* total_bytes_received = format_size(stats->total_bytes_received, FIREPERF_FORMAT_BYTES);
391 char* total_bytes_sent = format_size(stats->total_bytes_sent, FIREPERF_FORMAT_BYTES);
392
393 if (total_bytes_received || total_bytes_sent) {
394 INFO(conf, " %-20s: %12s %12s\n", "Total Bytes", total_bytes_received, total_bytes_sent);
395
396 if (total_bytes_received)
397 free(total_bytes_received);
398 if (total_bytes_sent)
399 free(total_bytes_sent);
400 }
401
402 // Empty line
403 INFO(conf, "\n");
404
405 // Remember when this was printed last
406 stats->last_printed = now;
407
408 // Reset statistics
409 stats->connections = 0;
410 stats->bytes_received = 0;
411 stats->bytes_sent = 0;
412
413 return 0;
414 }
415
416 int set_socket_buffer_sizes(struct fireperf_config* conf, int fd) {
417 int r;
418
419 // Set socket buffer sizes
420 int flags = SOCKET_SEND_BUFFER_SIZE;
421 r = setsockopt(fd, SOL_SOCKET, SO_SNDBUF, (void*)&flags, sizeof(flags));
422 if (r) {
423 ERROR(conf, "Could not set send buffer size on socket %d: %s\n",
424 fd, strerror(errno));
425 return 1;
426 }
427
428 // Set receive buffer size
429 flags = SOCKET_RECV_BUFFER_SIZE;
430 r = setsockopt(fd, SOL_SOCKET, SO_RCVBUF, (void*)&flags, sizeof(flags));
431 if (r) {
432 ERROR(conf, "Could not set receive buffer size on socket %d: %s\n",
433 fd, strerror(errno));
434 return 1;
435 }
436
437 return 0;
438 }
439
440 int handle_connection_send(struct fireperf_config* conf,
441 struct fireperf_stats* stats, int fd) {
442 const char* buffer = ZERO;
443 ssize_t bytes_sent;
444
445 if (conf->pool) {
446 buffer = fireperf_random_pool_get_slice(conf->pool, SOCKET_SEND_BUFFER_SIZE);
447 }
448
449 do {
450 bytes_sent = send(fd, buffer, SOCKET_SEND_BUFFER_SIZE, 0);
451 } while (bytes_sent < 0 && (errno == EAGAIN || errno == EWOULDBLOCK));
452
453 // Update statistics
454 stats->bytes_sent += bytes_sent;
455 stats->total_bytes_sent += bytes_sent;
456
457 return 0;
458 }
459
460 int handle_connection_recv(struct fireperf_config* conf,
461 struct fireperf_stats* stats, int fd) {
462 char buffer[SOCKET_RECV_BUFFER_SIZE];
463 ssize_t bytes_read;
464
465 // Try reading into buffer
466 do {
467 bytes_read = recv(fd, buffer, sizeof(buffer), 0);
468 } while (bytes_read < 0 && (errno == EAGAIN || errno == EWOULDBLOCK));
469
470 // Error?
471 if (bytes_read < 0) {
472 ERROR(conf, "Could not read from socket %d: %s\n", fd, strerror(errno));
473 return -1;
474 }
475
476 DEBUG(conf, "Read %zu bytes from socket %d\n", bytes_read, fd);
477
478 // Update statistics
479 stats->bytes_received += bytes_read;
480 stats->total_bytes_received += bytes_read;
481
482 return 0;
483 }