/* SPDX-License-Identifier: Unlicense */ #include "debug.h" #include "proto.h" #include "proto_io.h" #include "timespec/timespec.h" #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-function" #define OPTPARSE_IMPLEMENTATION #define OPTPARSE_API static #include "optparse/optparse.h" #pragma GCC diagnostic pop #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct connection { int fd; uint16_t port; uint32_t addr; bool connected; uint32_t connection_id; struct timespec last_syn; uint32_t server_roundtrip_prev; uint32_t client_roundtrip_prev; }; static volatile bool g_should_exit = false; struct connection g_conn = { .fd = -1, }; static inline bool IsDigit(char c) { return c >= '0' && c <= '9'; } static void HandleFrame( struct connection *self, struct frame const *frame, struct timespec now) { if (frame->type == FT_HANDSHAKE_RESP) { self->connection_id = frame->connection_id; self->connected = true; } else if (frame->type == FT_SYN && self->connected) { if (g_log_level >= LOG_LEVEL_DEBUG) { fprintf(stderr, "client_rt %5" PRIu32 "\n", self->client_roundtrip_prev); } struct frame outgoing = *frame; outgoing.type = FT_SYNACK, outgoing.syn = (struct frame_data_syn){ .tick = frame->syn.tick, .roundtrip_prev = self->client_roundtrip_prev, .prev_is_lost = false, }; SendFrame(self->fd, &outgoing); self->last_syn = now; self->server_roundtrip_prev = frame->syn.roundtrip_prev; } else if (frame->type == FT_ACK && self->connected) { long const ms = timespec_to_ms(timespec_sub(now, self->last_syn)); assert(ms >= 0); self->client_roundtrip_prev = ms; } else if (frame->type == FT_RST) { // TODO } } static uint32_t ParseAddress(char const *str, FILE *log_stream) { uint32_t addr = 0; unsigned byte_counter = 0, byte = 0, digits_counter = 0; for (size_t i = 0;; i++) { char const c = str[i]; if (c == '\0') { if (byte_counter != 3) { fprintf(log_stream, "Invalid address specified: %u bytes of 4 expected are specified\n", byte_counter + 1); return INADDR_NONE; } if (digits_counter == 0) { fprintf(log_stream, "Invalid address specified: no digits on byte %u\n", byte_counter + 1); return INADDR_NONE; } addr |= byte << (8 * (3 - byte_counter)); break; } else if (IsDigit(c)) { digits_counter++; if (digits_counter > 3) { fprintf(log_stream, "Invalid address specified: too many digits for byte %u\n", byte_counter + 1); return INADDR_NONE; } byte = byte * 10 + (c - '0'); if (byte > UINT8_MAX) { fprintf(log_stream, "Invalid address specified: too big number for byte %u\n", byte_counter + 1); return INADDR_NONE; } } else if (c == '.') { if (digits_counter == 0) { fprintf(log_stream, "Invalid address specified: no digits on byte %u\n", byte_counter + 1); return INADDR_NONE; } digits_counter = 0; addr |= byte << (8 * (3 - byte_counter)); byte = 0; byte_counter++; if (byte_counter > 3) { fprintf(log_stream, "Invalid address specified: unexpected dot after 4th byte\n"); return INADDR_NONE; } } else { fprintf(log_stream, "Invalid address specified: char '%c' (0x%02x) is not allowed\n", c, c); return INADDR_NONE; } } if (addr == INADDR_NONE) { fprintf(log_stream, "Invalid address specified\n"); } return addr; } static void sigintHandler(int a) { (void) a; } static void PrintUsage(FILE *s, const char *argv0) { // Please, keep all lines in 80 columns range when printed. fprintf(s, "Usage: %s [options]
\n" "Options:\n" " -h, --help, Show this message.\n" " -d, --debug-level=,\n" " Set id of debug log level: 0 means no logs, \n" " 1 means info, 2 means some debug,\n" " 3 is for maximum trace\n" , argv0); } int main(int argc, char *argv[]) { (void) argc; struct optparse_long longopts[] = { {"help", 'h', OPTPARSE_NONE}, {"debug-level", 'd', OPTPARSE_REQUIRED}, {0}, }; struct optparse options; optparse_init(&options, argv); // Parse opts int option; while ((option = optparse_long(&options, longopts, NULL)) != -1) { switch (option) { case 'h': PrintUsage(stdout, argv[0]); return EXIT_SUCCESS; break; case 'd': g_log_level = atoi(options.optarg); break; case '?': fprintf(stderr, "main: optparse_long: Error: \"%s\"\n", options.errmsg); return EXIT_FAILURE; } } char const *const addr_arg = optparse_arg(&options); char const *const port_arg = optparse_arg(&options); if (addr_arg == NULL || port_arg == NULL) { PrintUsage(stdout, argv[0]); return EXIT_SUCCESS; } unsigned const addr = ParseAddress(addr_arg, stderr); if (addr == INADDR_NONE) { return 1; } int const port = atoi(port_arg); if (port <= 0 || port > UINT16_MAX) { fprintf(stderr, "Invalid port specified\n"); return 1; } int const fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd < 0) { perror("socket failed"); return 1; } fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK); g_conn.addr = addr; g_conn.port = port; g_conn.fd = fd; struct frame const handshake = { .addr = addr, .port = port, .type = FT_HANDSHAKE, .handshake = { .tick_period = 200, }, }; SendFrame(fd, &handshake); struct sigaction sa = { .sa_handler = sigintHandler, }; sigaction(SIGINT, &sa, NULL); struct pollfd fds[1] = { { .fd = fd, .events = POLLIN, }, }; while (!g_should_exit) { int const pollret = poll(fds, sizeof(fds)/sizeof(*fds), -1); int const poll_err = errno; struct timespec start; if (-1 == clock_gettime(CLOCK_MONOTONIC, &start)) { int const err = errno; fprintf(stderr, "clock_gettime(CLOCK_MONOTONIC, &start) failed (%d): \"%s\"", err, strerror(err)); } if (pollret > 0) { if (fds[0].revents & POLLIN) { while (1) { struct frame frame = { .type = FT_NONE, }; int const ret = ReceiveFrame(fds[0].fd, &frame); if (ret) { break; } HandleFrame(&g_conn, &frame, start); } } if (fds[0].revents & POLLRDNORM) { fprintf(stderr, "POLLRDNORM, ignoring...\n"); } if (fds[0].revents & POLLRDBAND) { fprintf(stderr, "POLLRDBAND, ignoring...\n"); } if (fds[0].revents & POLLPRI) { fprintf(stderr, "POLLPRI, ignoring...\n"); } if (fds[0].revents & POLLPRI) { fprintf(stderr, "POLLPRI, ignoring...\n"); } if (fds[0].revents & POLLHUP) { fprintf(stderr, "POLLHUP, ignoring...\n"); } if (fds[0].revents & POLLERR) { fprintf(stderr, "POLLERR, ignoring...\n"); } if (fds[0].revents & POLLNVAL) { fprintf(stderr, "POLLNVAL, exiting...\n"); return 1; } } else if (pollret < 0) { if (poll_err == EINTR) { fprintf(stderr, "SIGINT received\n"); if (g_conn.connected) { struct frame const rst = { .addr = g_conn.addr, .port = g_conn.port, .type = FT_RST, }; SendFrame(g_conn.fd, &rst); } break; } fprintf(stderr, "poll() failed (%d): \"%s\"", poll_err, strerror(poll_err)); } } close(fd); }