1 /*#############################################################################
3 # fireperf - A network benchmarking tool #
4 # Copyright (C) 2021 IPFire Development Team #
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. #
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. #
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/>. #
19 #############################################################################*/
21 #include <arpa/inet.h>
24 #include <netinet/in.h>
28 #include <sys/epoll.h>
29 #include <sys/resource.h>
31 #include <sys/timerfd.h>
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
);
50 // Try parsing this as an IPv4 address
51 struct in_addr address4
;
52 r
= inet_pton(AF_INET
, string
, &address4
);
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
;
63 // Could not parse this
67 static int check_port(int port
) {
68 if (port
<= 0 || port
>= 65536) {
69 fprintf(stderr
, "Invalid port number: %u\n", port
);
76 static int parse_port_range(struct fireperf_config
* conf
, const char* optarg
) {
77 int first_port
, last_port
;
79 int r
= sscanf(optarg
, "%d:%d", &first_port
, &last_port
);
83 // Check if both ports are in range
84 r
= check_port(first_port
);
88 r
= check_port(last_port
);
92 if (first_port
> last_port
) {
93 fprintf(stderr
, "Invalid port range: %s\n", optarg
);
97 conf
->port
= first_port
;
98 conf
->listening_sockets
= (last_port
- first_port
) + 1;
103 static int parse_port(struct fireperf_config
* conf
, const char* optarg
) {
104 conf
->port
= atoi(optarg
);
105 conf
->listening_sockets
= 1;
107 return check_port(conf
->port
);
110 static int set_limits(struct fireperf_config
* conf
) {
113 // Increase limit of open files
114 limit
.rlim_cur
= limit
.rlim_max
= conf
->parallel
+ conf
->listening_sockets
+ 128;
116 int r
= setrlimit(RLIMIT_NOFILE
, &limit
);
118 ERROR(conf
, "Could not set open file limit to %lu: %s\n",
119 (unsigned long)limit
.rlim_max
, strerror(errno
));
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'},
142 int option_index
= 0;
146 int c
= getopt_long(argc
, argv
, "c:dkp:st:xzDP:V", long_options
, &option_index
);
154 if (long_options
[option_index
].flag
!= 0)
157 printf("option %s", long_options
[option_index
].name
);
160 printf(" with arg: %s", optarg
);
166 // getopt_long already printed the error message
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");
181 conf
->mode
= FIREPERF_MODE_CLIENT
;
183 // Parse the given IP address
184 int r
= parse_address(optarg
, &conf
->address
);
186 fprintf(stderr
, "Could not parse IP address %s\n", optarg
);
196 conf
->loglevel
= LOG_DEBUG
;
200 conf
->keepalive_only
= 1;
204 conf
->parallel
= strtoul(optarg
, NULL
, 10);
206 if (conf
->parallel
> MAX_PARALLEL
) {
207 fprintf(stderr
, "Number of parallel connections is too high: %lu\n",
214 // Try parsing the port range first.
215 // If this fails, we try parsing a single port
216 r
= parse_port_range(conf
, optarg
);
218 r
= parse_port(conf
, optarg
);
225 conf
->mode
= FIREPERF_MODE_SERVER
;
229 conf
->timeout
= strtoul(optarg
, NULL
, 10);
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
,
259 struct fireperf_stats stats
= { 0 };
263 // Parse command line
264 r
= parse_argv(argc
, argv
, &conf
);
268 // Initialise random number generator
272 r
= set_limits(&conf
);
276 // Initialize random pool
278 conf
.pool
= fireperf_random_pool_create(&conf
, DEFAULT_RANDOM_POOL_SIZE
);
280 ERROR(&conf
, "Could not allocate random data\n");
286 // Initialize epoll()
287 int epollfd
= epoll_create1(0);
289 ERROR(&conf
, "Could not initialize epoll(): %s\n", strerror(errno
));
294 // Create timerfd() to print statistics
295 timerfd
= timerfd_create(CLOCK_MONOTONIC
, TFD_NONBLOCK
|TFD_CLOEXEC
);
297 ERROR(&conf
, "timerfd_create() failed: %s\n", strerror(errno
));
302 struct epoll_event ev
= {
307 if (epoll_ctl(epollfd
, EPOLL_CTL_ADD
, timerfd
, &ev
)) {
308 ERROR(&conf
, "Could not add timerfd to epoll(): %s\n", strerror(errno
));
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,
319 r
= timerfd_settime(timerfd
, 0, &timer
, NULL
);
321 ERROR(&conf
, "Could not set timer: %s\n", strerror(errno
));
327 case FIREPERF_MODE_CLIENT
:
328 return fireperf_client(&conf
, &stats
, epollfd
, timerfd
);
330 case FIREPERF_MODE_SERVER
:
331 return fireperf_server(&conf
, &stats
, epollfd
, timerfd
);
333 case FIREPERF_MODE_NONE
:
334 fprintf(stderr
, "No mode selected\n");
347 fireperf_random_pool_free(conf
.pool
);
352 int fireperf_dump_stats(struct fireperf_config
* conf
, struct fireperf_stats
* stats
, int mode
) {
356 int r
= clock_gettime(CLOCK_REALTIME
, &now
);
358 ERROR(conf
, "Could not fetch the time: %s\n", strerror(errno
));
362 double delta
= timespec_delta(&now
, &stats
->last_printed
);
364 // Called too soon again?
369 const char* timestamp
= format_timespec(&now
);
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
);
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
);
380 if (bps_received
|| bps_sent
) {
381 INFO(conf
, " %-20s: %10s/s %10s/s\n", "Current Bandwidth", bps_received
, bps_sent
);
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
);
393 if (total_bytes_received
|| total_bytes_sent
) {
394 INFO(conf
, " %-20s: %12s %12s\n", "Total Bytes", total_bytes_received
, total_bytes_sent
);
396 if (total_bytes_received
)
397 free(total_bytes_received
);
398 if (total_bytes_sent
)
399 free(total_bytes_sent
);
405 // Remember when this was printed last
406 stats
->last_printed
= now
;
409 stats
->connections
= 0;
410 stats
->bytes_received
= 0;
411 stats
->bytes_sent
= 0;
416 int set_socket_buffer_sizes(struct fireperf_config
* conf
, int fd
) {
419 // Set socket buffer sizes
420 int flags
= SOCKET_SEND_BUFFER_SIZE
;
421 r
= setsockopt(fd
, SOL_SOCKET
, SO_SNDBUF
, (void*)&flags
, sizeof(flags
));
423 ERROR(conf
, "Could not set send buffer size on socket %d: %s\n",
424 fd
, strerror(errno
));
428 // Set receive buffer size
429 flags
= SOCKET_RECV_BUFFER_SIZE
;
430 r
= setsockopt(fd
, SOL_SOCKET
, SO_RCVBUF
, (void*)&flags
, sizeof(flags
));
432 ERROR(conf
, "Could not set receive buffer size on socket %d: %s\n",
433 fd
, strerror(errno
));
440 int handle_connection_send(struct fireperf_config
* conf
,
441 struct fireperf_stats
* stats
, int fd
) {
442 const char* buffer
= ZERO
;
446 buffer
= fireperf_random_pool_get_slice(conf
->pool
, SOCKET_SEND_BUFFER_SIZE
);
450 bytes_sent
= send(fd
, buffer
, SOCKET_SEND_BUFFER_SIZE
, 0);
451 } while (bytes_sent
< 0 && (errno
== EAGAIN
|| errno
== EWOULDBLOCK
));
454 stats
->bytes_sent
+= bytes_sent
;
455 stats
->total_bytes_sent
+= bytes_sent
;
460 int handle_connection_recv(struct fireperf_config
* conf
,
461 struct fireperf_stats
* stats
, int fd
) {
462 char buffer
[SOCKET_RECV_BUFFER_SIZE
];
465 // Try reading into buffer
467 bytes_read
= recv(fd
, buffer
, sizeof(buffer
), 0);
468 } while (bytes_read
< 0 && (errno
== EAGAIN
|| errno
== EWOULDBLOCK
));
471 if (bytes_read
< 0) {
472 ERROR(conf
, "Could not read from socket %d: %s\n", fd
, strerror(errno
));
476 DEBUG(conf
, "Read %zu bytes from socket %d\n", bytes_read
, fd
);
479 stats
->bytes_received
+= bytes_read
;
480 stats
->total_bytes_received
+= bytes_read
;