Last active
June 16, 2020 13:51
-
-
Save Low-power/fbf5e822e2f9e81da1e50e8672f68464 to your computer and use it in GitHub Desktop.
Flow replay tool for tcpflow(1), just like scriptreplay(1) for script(1).
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* Replay flow captured by tcpflow(1) to stdout with timing. | |
Copyright 2015-2020 Rivoreo | |
Permission is hereby granted, free of charge, to any person obtaining | |
a copy of this software and associated documentation files (the | |
"Software"), to deal in the Software without restriction, including | |
without limitation the rights to use, copy, modify, merge, publish, | |
distribute, sublicense, and/or sell copies of the Software, and to | |
permit persons to whom the Software is furnished to do so, subject to | |
the following conditions: | |
The above copyright notice and this permission notice shall be | |
included in all copies or substantial portions of the Software. | |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
NONINFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE | |
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF | |
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | |
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
*/ | |
#include <termios.h> | |
#include <unistd.h> | |
#include <signal.h> | |
#include <string.h> | |
#include <stdlib.h> | |
#include <stdio.h> | |
#include <fcntl.h> | |
#include <errno.h> | |
#include <ctype.h> | |
#include <time.h> | |
#include <locale.h> | |
#ifndef MIN | |
#define MIN(A,B) ((A)<(B)?(A):(B)) | |
#endif | |
static int ff_tty_fd = -1; | |
static struct termios orig_termios; | |
static void __attribute__((__noreturn__)) exit_handler(int sig) { | |
if(ff_tty_fd != -1) tcsetattr(ff_tty_fd, TCSANOW, &orig_termios); | |
if(sig < 0) exit(-sig); | |
if(sig > 0) putchar('\n'); | |
exit(sig ? 128 + sig : 0); | |
// close(2) file descriptors and free(3) memory? who cares? | |
} | |
static void print_usage(const char *name) { | |
fprintf(stderr, "Usage: %s [-q] [ -F <ff-fd> | -t ] { [-I <index-file>] <file> | -I <index-file> -[<fd>] }\n", name); | |
} | |
static int read_index_line(FILE *f, double *t, size_t *count) { | |
static unsigned int nline = 1; | |
if(fscanf(f, "%*u|%lf|%zu\n", t, count) < 2) { | |
if(feof(f)) return 0; | |
fprintf(stderr, "Invalid syntax at line %u of index\n", nline); | |
return -1; | |
} | |
nline++; | |
return 1; | |
} | |
static int transfer(int wfd, int rfd, size_t count) { | |
size_t buffer_size = count > 4096 ? 4096 : count; | |
char buffer[buffer_size]; | |
int ss = 0; | |
do { | |
int s = read(rfd, buffer, MIN(buffer_size, count)); | |
if(s < 0) { | |
if(errno == EINTR) continue; | |
return -1; | |
} | |
if(!s) return ss; | |
ss += s; | |
do { | |
int ws = write(wfd, buffer, s); | |
if(ws < 0) { | |
if(errno == EINTR) continue; | |
return -1; | |
} | |
s -= ws; | |
} while(s > 0); | |
} while(ss < count); | |
return ss; | |
} | |
static void delay(double s, int ff_fd) { | |
struct timeval tv; | |
tv.tv_sec = s; | |
tv.tv_usec = (s - tv.tv_sec) * 1000000; | |
if(ff_fd == -1) { | |
select(1, NULL, NULL, NULL, &tv); | |
} else { | |
char b; | |
fd_set ff_fd_set; | |
FD_ZERO(&ff_fd_set); | |
FD_SET(ff_fd, &ff_fd_set); | |
switch(select(ff_fd + 1, &ff_fd_set, NULL, NULL, &tv)) { | |
case -1: | |
if(errno == EINTR) break; | |
exit_handler(-1); | |
break; | |
case 0: | |
break; | |
default: | |
read(ff_fd, &b, 1); | |
break; | |
} | |
} | |
} | |
int main(int argc, char **argv) { | |
setlocale(LC_ALL, ""); | |
int quiet = 0; | |
int fast_forward_fd = -1; | |
const char *index_file_name = NULL; | |
const char *file_name = NULL; | |
int i = 1; | |
int end_of_options = 0; | |
while(i < argc) { | |
if(!end_of_options && argv[i][0] == '-' && argv[i][1] && !isdigit(argv[i][1])) { | |
const char *o = argv[i] + 1; | |
while(*o) switch(*o++) { | |
case '-': | |
if(o[1]) { | |
fprintf(stderr, "%s: Invalid option '%s'\n", argv[0], argv[i]); | |
return -1; | |
} else end_of_options = 1; | |
break; | |
case 'q': | |
quiet = 1; | |
break; | |
case 'F': | |
if(++i >= argc) { | |
fprintf(stderr, "%s: Option '-F' requires an argument\n", argv[0]); | |
return -1; | |
} | |
{ | |
char *end_p; | |
fast_forward_fd = strtol(argv[i], &end_p, 10); | |
if(*end_p || fast_forward_fd == -1) { | |
fprintf(stderr, "%s: Invalid file description '%s'\n", | |
argv[0], argv[i]); | |
return -1; | |
} | |
} | |
break; | |
case 't': | |
fast_forward_fd = open("/dev/tty", O_RDONLY); | |
if(fast_forward_fd == -1) { | |
perror("/dev/tty"); | |
return 1; | |
} | |
break; | |
case 'I': | |
if(++i >= argc) { | |
fprintf(stderr, "%s: Option '-I' requires an argument\n", argv[0]); | |
return -1; | |
} | |
index_file_name = argv[i]; | |
break; | |
default: | |
fprintf(stderr, "%s: Invalid option '-%c'\n", argv[0], o[-1]); | |
return -1; | |
} | |
} else { | |
if(file_name) { | |
fprintf(stderr, "%s: Only one file can be specified at a time", argv[0]); | |
return -1; | |
} | |
file_name = argv[i]; | |
} | |
i++; | |
} | |
if(!file_name) { | |
print_usage(argv[0]); | |
return -1; | |
} | |
int fd; | |
if(*file_name == '-') { | |
const char *fdn = file_name + 1; | |
if(isdigit(*fdn)) { | |
char *endptr; | |
fd = strtol(fdn, &endptr, 10); | |
if(*endptr) goto open_file_name; | |
} else if(!*fdn) fd = STDIN_FILENO; | |
else goto open_file_name; | |
if(!index_file_name) { | |
fprintf(stderr, "%s: Index file must be specified via option '-I' for use with input FD '%s'\n", | |
argv[0], file_name); | |
return -1; | |
} | |
} else { | |
open_file_name: | |
fd = open(file_name, O_RDONLY); | |
if(fd == -1) { | |
perror(file_name); | |
return 1; | |
} | |
} | |
FILE *index_f; | |
if(index_file_name) { | |
if(*index_file_name == '-') { | |
const char *fdn = index_file_name + 1; | |
if(isdigit(*fdn)) { | |
char *endptr; | |
int fd = strtol(fdn, &endptr, 10); | |
if(*endptr) goto open_index_file_name; | |
index_f = fdopen(fd, "r"); | |
} else if(!*fdn) index_f = stdin; | |
else goto open_index_file_name; | |
} else { | |
open_index_file_name: | |
index_f = fopen(index_file_name, "r"); | |
} | |
if(!index_f) { | |
perror(index_file_name); | |
return 1; | |
} | |
} else { | |
size_t len = strlen(file_name); | |
char index_file_name[len + 7]; | |
memcpy(index_file_name, file_name, len); | |
memcpy(index_file_name + len, ".findx", 7); | |
index_f = fopen(index_file_name, "r"); | |
if(!index_f) { | |
perror(index_file_name); | |
return 1; | |
} | |
} | |
if(fast_forward_fd != -1 && isatty(fast_forward_fd)) { | |
struct sigaction act = { .sa_handler = exit_handler }; | |
if(sigaction(SIGINT, &act, NULL) < 0) { | |
perror("sigaction: SIGINT"); | |
return 1; | |
} | |
tcgetattr(fast_forward_fd, &orig_termios); | |
struct termios new_termios = orig_termios; | |
new_termios.c_lflag &= ~(ICANON | ECHO); | |
tcsetattr(fast_forward_fd, TCSANOW, &new_termios); | |
ff_tty_fd = fast_forward_fd; | |
} | |
double pt; | |
size_t count; | |
switch(read_index_line(index_f, &pt, &count)) { | |
case 0: | |
fputs("Index EOF too early\n", stderr); | |
case -1: | |
exit_handler(-1); | |
} | |
if(!quiet) { | |
time_t t = pt; | |
struct tm *tm = localtime(&t); | |
char buffer[64]; | |
if(strftime(buffer, sizeof buffer, "Flow started on %x %T %z\n", tm)) fputs(buffer, stderr); | |
} | |
if(transfer(STDOUT_FILENO, fd, count) < 0) exit_handler(-1); | |
while(1) { | |
double t; | |
switch(read_index_line(index_f, &t, &count)) { | |
case -1: | |
exit_handler(-1); | |
case 0: | |
exit_handler(0); | |
} | |
delay(t - pt, fast_forward_fd); | |
if(transfer(STDOUT_FILENO, fd, count) < 0) exit_handler(-1); | |
pt = t; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment