diff --git a/includes/internal/callback.h b/includes/internal/callback.h new file mode 100644 index 0000000..d609e38 --- /dev/null +++ b/includes/internal/callback.h @@ -0,0 +1,8 @@ +#ifndef PING_CALLBACK_H +#define PING_CALLBACK_H + +#include "icmp.h" + +void ping_callback(const icmp_reply_t *reply, void *userdata); + +#endif diff --git a/includes/internal/loop.h b/includes/internal/loop.h new file mode 100644 index 0000000..5cbb13c --- /dev/null +++ b/includes/internal/loop.h @@ -0,0 +1,9 @@ +#ifndef PING_LOOP_H +#define PING_LOOP_H + +#include +#include "internal/ping_state.h" + +void ping_loop(t_ping_state *state, size_t payload_len); + +#endif diff --git a/includes/internal/output.h b/includes/internal/output.h new file mode 100644 index 0000000..e0ec9da --- /dev/null +++ b/includes/internal/output.h @@ -0,0 +1,21 @@ +#ifndef PING_OUTPUT_H +#define PING_OUTPUT_H + +#include +#include +#include "icmp.h" +#include "ft_ping.h" +#include "internal/ping_state.h" + +void ping_output_start(const t_ping_config *config, + struct in_addr dest, size_t payload_bytes); +void ping_output_packet(const icmp_reply_t *reply, uint16_t seq, + int64_t rtt_ns, size_t payload_bytes, const t_ping_config *config); +void ping_output_error(const icmp_reply_t *reply, + const icmp_offending_packet_t *offending, + uint16_t seq, const t_ping_config *config); +void ping_output_summary(const t_ping_state *state, struct in_addr dest); +void ping_output_flood_dot(void); +void ping_output_flood_erase(void); + +#endif diff --git a/includes/internal/ping_state.h b/includes/internal/ping_state.h new file mode 100644 index 0000000..da1dcc1 --- /dev/null +++ b/includes/internal/ping_state.h @@ -0,0 +1,28 @@ +#ifndef PING_STATE_H +#define PING_STATE_H + +#include +#include +#include +#include +#include "icmp.h" +#include "ft_ping.h" +#include "internal/stats.h" +#include "internal/tracker.h" + +typedef struct s_ping_state { + icmp_handle_t *handle; + const t_ping_config *config; + struct in_addr dest; + struct timespec start_time; + struct timespec linger_start; + uint16_t id; + uint16_t seq; + t_ping_tracker *tracker; + t_ping_stats *stats; + volatile sig_atomic_t send_flag; + volatile sig_atomic_t stop_flag; + size_t nb_errors; +} t_ping_state; + +#endif diff --git a/includes/internal/scheduler.h b/includes/internal/scheduler.h new file mode 100644 index 0000000..ac698da --- /dev/null +++ b/includes/internal/scheduler.h @@ -0,0 +1,12 @@ +#ifndef PING_SCHEDULER_H +#define PING_SCHEDULER_H + +#include +#include "internal/ping_state.h" + +void ping_scheduler_init(t_ping_state *state); +void ping_scheduler_arm(const t_ping_config *config); +void ping_scheduler_select_tv(const t_ping_config *config, + struct timeval *tv); + +#endif diff --git a/includes/internal/send.h b/includes/internal/send.h new file mode 100644 index 0000000..df634e7 --- /dev/null +++ b/includes/internal/send.h @@ -0,0 +1,9 @@ +#ifndef PING_SEND_H +#define PING_SEND_H + +#include +#include "internal/ping_state.h" + +int ping_send_one(t_ping_state *state, size_t payload_len); + +#endif diff --git a/includes/internal/stats.h b/includes/internal/stats.h new file mode 100644 index 0000000..bab76a7 --- /dev/null +++ b/includes/internal/stats.h @@ -0,0 +1,20 @@ +#ifndef PING_STATS_H +#define PING_STATS_H + +#include +#include + +typedef struct s_ping_stats { + int64_t min_ns; + int64_t max_ns; + int64_t sum_ns; + int64_t sum_sq_ns; /* sum of (rtt_ns / 1000)^2 in us^2, to avoid overflow */ + size_t count; +} t_ping_stats; + +void ping_stats_init(t_ping_stats *s); +void ping_stats_update(t_ping_stats *s, int64_t rtt_ns); +void ping_stats_get(const t_ping_stats *s, + double *min_ms, double *max_ms, double *avg_ms, double *mdev_ms); + +#endif diff --git a/includes/internal/tracker.h b/includes/internal/tracker.h new file mode 100644 index 0000000..53a73a1 --- /dev/null +++ b/includes/internal/tracker.h @@ -0,0 +1,29 @@ +#ifndef PING_TRACKER_H +#define PING_TRACKER_H + +#include +#include +#include +#include + +#define PING_TRACKER_SLOTS 128 + +typedef struct s_ping_tracker_slot { + struct timespec ts; + bool used; + bool acked; +} t_ping_tracker_slot; + +typedef struct s_ping_tracker { + t_ping_tracker_slot slots[PING_TRACKER_SLOTS]; + size_t nb_sent; + size_t nb_recv; +} t_ping_tracker; + +void ping_tracker_init(t_ping_tracker *t); +void ping_tracker_record_send(t_ping_tracker *t, uint16_t seq, const + struct timespec *ts); +int64_t ping_tracker_record_recv(t_ping_tracker *t, uint16_t seq, + const struct timespec *ts); + +#endif diff --git a/includes/ping.h b/includes/ping.h new file mode 100644 index 0000000..51b9590 --- /dev/null +++ b/includes/ping.h @@ -0,0 +1,8 @@ +#ifndef PING_H +#define PING_H + +#include "ft_ping.h" + +int ping_run(const t_ping_config *config); + +#endif diff --git a/sources.mk b/sources.mk index 5ebe43f..68b68a1 100644 --- a/sources.mk +++ b/sources.mk @@ -3,6 +3,7 @@ SRC_DIR = src SRCS = $(SRC_DIR)/main.c \ $(SRC_DIR)/cli/parse.c \ $(SRC_DIR)/cli/handlers/handle_count.c \ + $(SRC_DIR)/cli/handlers/handle_deadline.c \ $(SRC_DIR)/cli/handlers/handle_flood.c \ $(SRC_DIR)/cli/handlers/handle_help.c \ $(SRC_DIR)/cli/handlers/handle_interval.c \ @@ -22,6 +23,20 @@ SRCS = $(SRC_DIR)/main.c \ $(SRC_DIR)/cli/messages/help.c \ $(SRC_DIR)/cli/messages/version.c \ $(SRC_DIR)/cli/messages/error.c \ + $(SRC_DIR)/core/ping.c \ + $(SRC_DIR)/core/loop.c \ + $(SRC_DIR)/core/send.c \ + $(SRC_DIR)/core/callback.c \ + $(SRC_DIR)/stats/stats.c \ + $(SRC_DIR)/tracker/init.c \ + $(SRC_DIR)/tracker/record_send.c \ + $(SRC_DIR)/tracker/record_recv.c \ + $(SRC_DIR)/output/start.c \ + $(SRC_DIR)/output/packet.c \ + $(SRC_DIR)/output/error.c \ + $(SRC_DIR)/output/summary.c \ + $(SRC_DIR)/output/flood.c \ + $(SRC_DIR)/scheduler/scheduler.c \ TESTS_DIR = tests TESTS = $(TESTS_DIR)/test_main.c \ @@ -38,4 +53,6 @@ TESTS = $(TESTS_DIR)/test_main.c \ $(TESTS_DIR)/cli/handlers/test_handle_verbose.c \ $(TESTS_DIR)/cli/parse_utils/test_parse_int.c \ $(TESTS_DIR)/cli/parse_utils/test_parse_float.c \ + $(TESTS_DIR)/ping/stats/test_stats.c \ + $(TESTS_DIR)/ping/tracker/test_tracker.c \ diff --git a/src/core/callback.c b/src/core/callback.c new file mode 100644 index 0000000..0b1da8e --- /dev/null +++ b/src/core/callback.c @@ -0,0 +1,93 @@ +#include "icmp.h" +#include "icmp_types.h" +#include "ft_ping_flags.h" +#include "internal/callback.h" +#include "internal/ping_state.h" +#include "internal/stats.h" +#include "internal/tracker.h" +#include "internal/output.h" + +/* Forward declarations */ +static int extract_our_echo(const icmp_reply_t *reply, uint16_t our_id, + uint16_t *seq_out); +static void handle_echo_reply(t_ping_state *state, const icmp_reply_t *reply); +static int extract_our_error(const icmp_reply_t *reply, uint16_t our_id, + icmp_offending_packet_t *out, uint16_t *seq_out); +static void handle_icmp_error(t_ping_state *state, const icmp_reply_t *reply); +/* -------------------- */ + +void +ping_callback(const icmp_reply_t *reply, void *userdata) +{ + t_ping_state *state; + + state = (t_ping_state *)userdata; + if (ICMP_TYPE_ECHO_REPLY == reply->type) + handle_echo_reply(state, reply); + else if (ICMP_TYPE_TIME_EXCEEDED == reply->type + || ICMP_TYPE_DEST_UNREACHABLE == reply->type) + handle_icmp_error(state, reply); +} + +static int +extract_our_echo(const icmp_reply_t *reply, uint16_t our_id, + uint16_t *seq_out) +{ + uint16_t id; + uint16_t seq; + + if (0 > icmp_reply_id_seq(reply, &id, &seq)) + return 0; + if (our_id != id) + return 0; + *seq_out = seq; + return 1; +} + +static void +handle_echo_reply(t_ping_state *state, const icmp_reply_t *reply) +{ + uint16_t seq; + int64_t rtt; + + if (0 == extract_our_echo(reply, state->id, &seq)) + return; + rtt = ping_tracker_record_recv(state->tracker, seq, &reply->timestamp); + if (0 > rtt) + return; + ping_stats_update(state->stats, rtt); + if (HAS_FLAG(state->config->flags, FLAG_FLOOD)) + ping_output_flood_erase(); + else + ping_output_packet(reply, seq, rtt, state->config->packet_size, + state->config); +} + +static int +extract_our_error(const icmp_reply_t *reply, uint16_t our_id, + icmp_offending_packet_t *out, uint16_t *seq_out) +{ + if (0 > icmp_error_extract_offending(reply, out)) + return 0; + if (ICMP_TYPE_ECHO_REQUEST != out->icmp_type) + return 0; + if (our_id != out->rest.echo.id) + return 0; + *seq_out = out->rest.echo.seq; + return 1; +} + +static void +handle_icmp_error(t_ping_state *state, const icmp_reply_t *reply) +{ + icmp_offending_packet_t offending; + uint16_t seq; + + if (0 == extract_our_error(reply, state->id, &offending, &seq)) + return; + if (0 > ping_tracker_record_recv(state->tracker, seq, &reply->timestamp)) + return; + state->nb_errors++; + if (!HAS_FLAG(state->config->flags, FLAG_QUIET)) + ping_output_error(reply, &offending, seq, state->config); +} diff --git a/src/core/loop.c b/src/core/loop.c new file mode 100644 index 0000000..3fb28e8 --- /dev/null +++ b/src/core/loop.c @@ -0,0 +1,128 @@ +#include + +#include "icmp.h" +#include "ft_ping_flags.h" +#include "internal/loop.h" +#include "internal/send.h" +#include "internal/callback.h" +#include "internal/tracker.h" +#include "internal/output.h" +#include "internal/scheduler.h" + +/* Forward declarations */ +static int deadline_expired(const t_ping_state *state); +static int linger_expired(const t_ping_state *state); +static int should_stop(const t_ping_state *state); +static int can_send_more(const t_ping_state *state); +static void do_send(t_ping_state *state, size_t payload_len); +static void try_recv(t_ping_state *state); +static void handle_send_trigger(t_ping_state *state, size_t payload_len); +/* -------------------- */ + +void +ping_loop(t_ping_state *state, size_t payload_len) +{ + do + { + handle_send_trigger(state, payload_len); + if (should_stop(state)) + break; + try_recv(state); + } while (state->send_flag || !should_stop(state)); +} + +static int +deadline_expired(const t_ping_state *state) +{ + struct timespec now; + double elapsed; + + if (0.0 == state->config->deadline) + return 0; + icmp_get_time(&now); + elapsed = (double)(now.tv_sec - state->start_time.tv_sec) + + (double)(now.tv_nsec - state->start_time.tv_nsec) / 1e9; + return elapsed >= state->config->deadline; +} + +static int +linger_expired(const t_ping_state *state) +{ + struct timespec now; + double elapsed; + + if (0 == state->linger_start.tv_sec && 0 == state->linger_start.tv_nsec) + return 0; + icmp_get_time(&now); + elapsed = (double)(now.tv_sec - state->linger_start.tv_sec) + + (double)(now.tv_nsec - state->linger_start.tv_nsec) / 1e9; + return elapsed >= state->config->timeout; +} + +static int +should_stop(const t_ping_state *state) +{ + size_t count; + + if (state->stop_flag) + return 1; + if (deadline_expired(state)) + return 1; + if (linger_expired(state)) + return 1; + count = state->config->count; + if (0 != count && state->tracker->nb_recv >= count) + return 1; + return 0; +} + +static int +can_send_more(const t_ping_state *state) +{ + size_t count; + + count = state->config->count; + return 0 == count || state->tracker->nb_sent < count; +} + +static void +do_send(t_ping_state *state, size_t payload_len) +{ + state->send_flag = 0; + ping_send_one(state, payload_len); + if (HAS_FLAG(state->config->flags, FLAG_FLOOD)) + ping_output_flood_dot(); + ping_scheduler_arm(state->config); +} + +static void +try_recv(t_ping_state *state) +{ + int fd; + fd_set rfds; + struct timeval tv; + int ret; + + fd = icmp_get_fd(state->handle); + ping_scheduler_select_tv(state->config, &tv); + FD_ZERO(&rfds); + FD_SET(fd, &rfds); + ret = select(fd + 1, &rfds, NULL, NULL, &tv); + if (0 < ret) + icmp_process(state->handle, ping_callback, state, 0); +} + +static void +handle_send_trigger(t_ping_state *state, size_t payload_len) +{ + if (!state->send_flag && !HAS_FLAG(state->config->flags, FLAG_FLOOD)) + return; + if (can_send_more(state)) + do_send(state, payload_len); + else + { + if (0 == state->linger_start.tv_sec && 0 == state->linger_start.tv_nsec) + icmp_get_time(&state->linger_start); + state->send_flag = 0; + } +} diff --git a/src/core/ping.c b/src/core/ping.c new file mode 100644 index 0000000..2d5f1b8 --- /dev/null +++ b/src/core/ping.c @@ -0,0 +1,91 @@ +#include +#include + +#include "ping.h" +#include "icmp.h" +#include "ft_ping_flags.h" +#include "internal/ping_state.h" +#include "internal/stats.h" +#include "internal/tracker.h" +#include "internal/output.h" +#include "internal/scheduler.h" +#include "internal/send.h" +#include "internal/loop.h" + +/* Forward declarations */ +static int ping_one(const t_ping_config *config, struct in_addr dest, + icmp_handle_t *handle); +static void ping_init_state(t_ping_state *state, t_ping_stats *stats, + t_ping_tracker *tracker, const t_ping_config *config, struct in_addr + dest, icmp_handle_t *handle); +static void ping_first_send(t_ping_state *state, size_t payload_len); +/* -------------------- */ + +int +ping_run(const t_ping_config *config) +{ + icmp_handle_t *handle; + int ret; + size_t i; + + if (0 == config->nb_destinations) + return 1; + handle = icmp_create(); + if (NULL == handle) + return 1; + ret = 0; + i = 0; + while (i < config->nb_destinations) + { + if (0 != ping_one(config, config->destinations[i], handle)) + ret = 1; + i++; + } + icmp_destroy(handle); + return ret; +} + +static int +ping_one(const t_ping_config *config, struct in_addr dest, + icmp_handle_t *handle) +{ + t_ping_state state; + t_ping_stats stats; + t_ping_tracker tracker; + size_t payload_len; + + ping_init_state(&state, &stats, &tracker, config, dest, handle); + payload_len = config->packet_size; + ping_scheduler_init(&state); + ping_output_start(config, dest, payload_len); + ping_first_send(&state, payload_len); + ping_loop(&state, payload_len); + ping_output_summary(&state, dest); + return tracker.nb_recv <= state.nb_errors; +} + +static void +ping_init_state(t_ping_state *state, t_ping_stats *stats, + t_ping_tracker *tracker, const t_ping_config *config, + struct in_addr dest, icmp_handle_t *handle) +{ + ping_stats_init(stats); + ping_tracker_init(tracker); + memset(state, 0, sizeof(*state)); + state->config = config; + state->dest = dest; + state->id = (uint16_t)((unsigned int)getpid() & 0xFFFFU); + state->tracker = tracker; + state->stats = stats; + state->handle = handle; + icmp_get_time(&state->start_time); +} + +static void +ping_first_send(t_ping_state *state, size_t payload_len) +{ + ping_send_one(state, payload_len); + if (HAS_FLAG(state->config->flags, FLAG_FLOOD)) + ping_output_flood_dot(); + ping_scheduler_arm(state->config); +} diff --git a/src/core/send.c b/src/core/send.c new file mode 100644 index 0000000..d4fe3d6 --- /dev/null +++ b/src/core/send.c @@ -0,0 +1,23 @@ +#include + +#include "icmp.h" +#include "ft_ping_const.h" +#include "internal/send.h" +#include "internal/tracker.h" + +int +ping_send_one(t_ping_state *state, size_t payload_len) +{ + struct timespec ts; + uint8_t payload[MAX_PACKET_SIZE]; + + memset(payload, 0x42, payload_len); + icmp_get_time(&ts); + ping_tracker_record_send(state->tracker, state->seq, &ts); + return (icmp_send_echo(state->handle, + state->dest, + state->id, + state->seq++, + state->config->ttl, + payload, payload_len)); +} diff --git a/src/main.c b/src/main.c index a7543db..3f3e1f1 100644 --- a/src/main.c +++ b/src/main.c @@ -1,9 +1,9 @@ -#include #include #include #include #include "cli.h" +#include "ping.h" #include "version_gen.h" /* Forward declarations */ @@ -15,7 +15,7 @@ int main(int argc, char **argv) { t_ping_config config; - enum e_cli_code ret; + int ret; if (0 != init_prog_name(argv[0])) return EXIT_FAILURE; @@ -23,7 +23,7 @@ main(int argc, char **argv) if (ret != CLI_SUCCESS) goto cleanup; - printf("PING: Not yet implemented\n"); + ret = ping_run(&config); cleanup: cli_config_free(&config); diff --git a/src/output/error.c b/src/output/error.c new file mode 100644 index 0000000..a7da7db --- /dev/null +++ b/src/output/error.c @@ -0,0 +1,34 @@ +#include +#include + +#include "icmp.h" +#include "icmp_types.h" +#include "internal/output.h" + +/* Forward declarations */ +static const char *error_msg_for(const icmp_reply_t *reply); +/* -------------------- */ + +void +ping_output_error(const icmp_reply_t *reply, + const icmp_offending_packet_t *offending, + uint16_t seq, const t_ping_config *config) +{ + char from_str[INET_ADDRSTRLEN]; + + (void)offending; + (void)config; + inet_ntop(AF_INET, &reply->from, from_str, sizeof(from_str)); + fprintf(stderr, "From %s: icmp_seq=%u %s\n", + from_str, (unsigned int)seq, error_msg_for(reply)); +} + +static const char * +error_msg_for(const icmp_reply_t *reply) +{ + if (ICMP_TYPE_TIME_EXCEEDED == reply->type) + return ("Time to live exceeded"); + if (ICMP_CODE_HOST_UNREACHABLE == reply->code) + return ("Destination Host Unreachable"); + return ("Destination Net Unreachable"); +} diff --git a/src/output/flood.c b/src/output/flood.c new file mode 100644 index 0000000..0438d27 --- /dev/null +++ b/src/output/flood.c @@ -0,0 +1,15 @@ +#include + +#include "internal/output.h" + +void +ping_output_flood_dot(void) +{ + write(1, ".", 1); +} + +void +ping_output_flood_erase(void) +{ + write(1, "\b \b", 3); +} diff --git a/src/output/packet.c b/src/output/packet.c new file mode 100644 index 0000000..0bf0c0a --- /dev/null +++ b/src/output/packet.c @@ -0,0 +1,22 @@ +#include +#include + +#include "ft_ping_flags.h" +#include "internal/output.h" + +void +ping_output_packet(const icmp_reply_t *reply, uint16_t seq, + int64_t rtt_ns, size_t payload_bytes, const t_ping_config *config) +{ + char from_str[INET_ADDRSTRLEN]; + + if (HAS_FLAG(config->flags, FLAG_QUIET) || HAS_FLAG(config->flags, FLAG_FLOOD)) + return; + inet_ntop(AF_INET, &reply->from, from_str, sizeof(from_str)); + printf("%zu bytes from %s: icmp_seq=%u ttl=%u time=%.3f ms\n", + payload_bytes + 8, + from_str, + (unsigned int)seq, + (unsigned int)reply->ttl, + (double)rtt_ns / 1e6); +} diff --git a/src/output/start.c b/src/output/start.c new file mode 100644 index 0000000..098dbee --- /dev/null +++ b/src/output/start.c @@ -0,0 +1,24 @@ +#include +#include +#include + +#include "ft_ping_flags.h" +#include "internal/output.h" + +void +ping_output_start(const t_ping_config *config, + struct in_addr dest, size_t payload_bytes) +{ + char dest_str[INET_ADDRSTRLEN]; + + inet_ntop(AF_INET, &dest, dest_str, sizeof(dest_str)); + printf("PING %s (%s): %zu data bytes", dest_str, dest_str, payload_bytes); + if (HAS_FLAG(config->flags, FLAG_VERBOSE)) + { + unsigned int id; + + id = (unsigned int)getpid() & 0xFFFFU; + printf(", id 0x%04x = %u", id, id); + } + printf("\n"); +} diff --git a/src/output/summary.c b/src/output/summary.c new file mode 100644 index 0000000..376d83e --- /dev/null +++ b/src/output/summary.c @@ -0,0 +1,56 @@ +#include +#include + +#include "internal/output.h" +#include "internal/stats.h" +#include "internal/tracker.h" + +/* Forward declarations */ +static void print_loss_line(size_t sent, size_t ok_recv, size_t errors); +static void print_rtt_line(const t_ping_stats *stats); +/* -------------------- */ + +void +ping_output_summary(const t_ping_state *state, struct in_addr dest) +{ + char dest_str[INET_ADDRSTRLEN]; + size_t sent; + size_t errors; + size_t ok_recv; + + inet_ntop(AF_INET, &dest, dest_str, sizeof(dest_str)); + sent = state->tracker->nb_sent; + errors = state->nb_errors; + ok_recv = state->tracker->nb_recv - errors; + printf("--- %s ping statistics ---\n", dest_str); + print_loss_line(sent, ok_recv, errors); + if (0 < ok_recv) + print_rtt_line(state->stats); +} + +static void +print_loss_line(size_t sent, size_t ok_recv, size_t errors) +{ + int loss_pct; + + loss_pct = (0 < sent) ? (int)((sent - ok_recv) * 100 / sent) : 0; + if (0 < errors) + printf("%zu packets transmitted, %zu received, +%zu errors," + " %d%% packet loss\n", sent, ok_recv, errors, loss_pct); + else + printf("%zu packets transmitted, %zu received, %d%% packet loss\n", + sent, ok_recv, loss_pct); +} + +static void +print_rtt_line(const t_ping_stats *stats) +{ + double min_ms; + double max_ms; + double avg_ms; + double mdev_ms; + + ping_stats_get(stats, &min_ms, &max_ms, &avg_ms, &mdev_ms); + printf("round-trip min/avg/max/mdev = %.3f/%.3f/%.3f/%.3f ms\n", + min_ms, avg_ms, max_ms, mdev_ms); +} diff --git a/src/scheduler/scheduler.c b/src/scheduler/scheduler.c new file mode 100644 index 0000000..c3c1d56 --- /dev/null +++ b/src/scheduler/scheduler.c @@ -0,0 +1,72 @@ +#include +#include + +#include "ft_ping_flags.h" +#include "internal/scheduler.h" + +/* Forward declarations */ +static void sigalrm_handler(int sig); +static void sigint_handler(int sig); +static void install_signal(int signum, void (*handler)(int)); +/* -------------------- */ + +static t_ping_state *g_state = NULL; + +void +ping_scheduler_init(t_ping_state *state) +{ + g_state = state; + install_signal(SIGALRM, sigalrm_handler); + install_signal(SIGINT, sigint_handler); +} + +void +ping_scheduler_arm(const t_ping_config *config) +{ + struct itimerval itv; + double interval; + + if (HAS_FLAG(config->flags, FLAG_FLOOD)) + return; + interval = (double)config->interval; + itv.it_value.tv_sec = (time_t)interval; + itv.it_value.tv_usec = (suseconds_t)((interval - (double)(time_t)interval) + * 1e6); + itv.it_interval.tv_sec = 0; + itv.it_interval.tv_usec = 0; + setitimer(ITIMER_REAL, &itv, NULL); +} + +void +ping_scheduler_select_tv(const t_ping_config *config, struct timeval *tv) +{ + tv->tv_usec = 0; + tv->tv_sec = (long)config->timeout * !HAS_FLAG(config->flags, FLAG_FLOOD); +} + +static void +sigalrm_handler(int sig) +{ + (void)sig; + if (NULL != g_state) + g_state->send_flag = 1; +} + +static void +sigint_handler(int sig) +{ + (void)sig; + if (NULL != g_state) + g_state->stop_flag = 1; +} + +static void +install_signal(int signum, void (*handler)(int)) +{ + struct sigaction sa; + + sa.sa_handler = handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sigaction(signum, &sa, NULL); +} diff --git a/src/stats/stats.c b/src/stats/stats.c new file mode 100644 index 0000000..270d49b --- /dev/null +++ b/src/stats/stats.c @@ -0,0 +1,64 @@ +#include +#include + +#include "internal/stats.h" + +/* Forward declarations */ +static double compute_mdev_ms(const t_ping_stats *s, double avg_ms); +/* -------------------- */ + +void +ping_stats_init(t_ping_stats *s) +{ + s->min_ns = INT64_MAX; + s->max_ns = INT64_MIN; + s->sum_ns = 0; + s->sum_sq_ns = 0; + s->count = 0; +} + +void +ping_stats_update(t_ping_stats *s, int64_t rtt_ns) +{ + int64_t rtt_us; + + if (rtt_ns < s->min_ns) + s->min_ns = rtt_ns; + if (rtt_ns > s->max_ns) + s->max_ns = rtt_ns; + s->sum_ns += rtt_ns; + rtt_us = rtt_ns / 1000; + s->sum_sq_ns += rtt_us * rtt_us; + s->count++; +} + +void +ping_stats_get(const t_ping_stats *s, + double *min_ms, double *max_ms, double *avg_ms, double *mdev_ms) +{ + if (0 == s->count) + { + *min_ms = 0.0; + *max_ms = 0.0; + *avg_ms = 0.0; + *mdev_ms = 0.0; + return; + } + *min_ms = (double)s->min_ns / 1e6; + *max_ms = (double)s->max_ns / 1e6; + *avg_ms = (double)s->sum_ns / 1e6 / (double)s->count; + *mdev_ms = compute_mdev_ms(s, *avg_ms); +} + +static double +compute_mdev_ms(const t_ping_stats *s, double avg_ms) +{ + double avg_us; + double var_us2; + + avg_us = avg_ms * 1000.0; + var_us2 = (double)s->sum_sq_ns / (double)s->count - avg_us * avg_us; + if (0.0 > var_us2) + var_us2 = 0.0; + return (sqrt(var_us2) / 1000.0); +} diff --git a/src/tracker/init.c b/src/tracker/init.c new file mode 100644 index 0000000..dcb550b --- /dev/null +++ b/src/tracker/init.c @@ -0,0 +1,9 @@ +#include + +#include "internal/tracker.h" + +void +ping_tracker_init(t_ping_tracker *t) +{ + memset(t, 0, sizeof(*t)); +} diff --git a/src/tracker/record_recv.c b/src/tracker/record_recv.c new file mode 100644 index 0000000..602db17 --- /dev/null +++ b/src/tracker/record_recv.c @@ -0,0 +1,18 @@ +#include + +#include "icmp.h" +#include "internal/tracker.h" + +int64_t +ping_tracker_record_recv(t_ping_tracker *t, uint16_t seq, + const struct timespec *ts) +{ + t_ping_tracker_slot *slot; + + slot = &t->slots[seq % PING_TRACKER_SLOTS]; + if (false == slot->used || true == slot->acked) + return (-1); + slot->acked = true; + t->nb_recv++; + return (icmp_time_diff_ns(&slot->ts, ts)); +} diff --git a/src/tracker/record_send.c b/src/tracker/record_send.c new file mode 100644 index 0000000..c2b65ba --- /dev/null +++ b/src/tracker/record_send.c @@ -0,0 +1,16 @@ +#include + +#include "internal/tracker.h" + +void +ping_tracker_record_send(t_ping_tracker *t, uint16_t seq, + const struct timespec *ts) +{ + t_ping_tracker_slot *slot; + + slot = &t->slots[seq % PING_TRACKER_SLOTS]; + slot->ts = *ts; + slot->used = true; + slot->acked = false; + t->nb_sent++; +} diff --git a/tests/ping/stats/test_stats.c b/tests/ping/stats/test_stats.c new file mode 100644 index 0000000..80341ea --- /dev/null +++ b/tests/ping/stats/test_stats.c @@ -0,0 +1,64 @@ +#include +#include +#include +#include "internal/stats.h" + +Test(ping_stats, init_neutral) +{ + t_ping_stats s; + double min_ms, max_ms, avg_ms, mdev_ms; + + ping_stats_init(&s); + cr_assert_eq(s.count, (size_t)0); + ping_stats_get(&s, &min_ms, &max_ms, &avg_ms, &mdev_ms); + cr_assert_float_eq(min_ms, 0.0, 1e-9); + cr_assert_float_eq(max_ms, 0.0, 1e-9); + cr_assert_float_eq(avg_ms, 0.0, 1e-9); + cr_assert_float_eq(mdev_ms, 0.0, 1e-9); +} + +Test(ping_stats, single_update) +{ + t_ping_stats s; + double min_ms, max_ms, avg_ms, mdev_ms; + + ping_stats_init(&s); + ping_stats_update(&s, 10000000LL); /* 10 ms */ + ping_stats_get(&s, &min_ms, &max_ms, &avg_ms, &mdev_ms); + cr_assert_float_eq(min_ms, 10.0, 1e-6); + cr_assert_float_eq(max_ms, 10.0, 1e-6); + cr_assert_float_eq(avg_ms, 10.0, 1e-6); + cr_assert_float_eq(mdev_ms, 0.0, 1e-3); +} + +Test(ping_stats, multiple_updates) +{ + t_ping_stats s; + double min_ms, max_ms, avg_ms, mdev_ms; + + ping_stats_init(&s); + ping_stats_update(&s, 10000000LL); /* 10 ms */ + ping_stats_update(&s, 20000000LL); /* 20 ms */ + ping_stats_update(&s, 30000000LL); /* 30 ms */ + ping_stats_get(&s, &min_ms, &max_ms, &avg_ms, &mdev_ms); + cr_assert_float_eq(min_ms, 10.0, 1e-6); + cr_assert_float_eq(max_ms, 30.0, 1e-6); + cr_assert_float_eq(avg_ms, 20.0, 1e-6); + cr_assert(mdev_ms > 0.0, "mdev should be > 0 for different values"); +} + +Test(ping_stats, identical_values_mdev_zero) +{ + t_ping_stats s; + double min_ms, max_ms, avg_ms, mdev_ms; + + ping_stats_init(&s); + ping_stats_update(&s, 15000000LL); + ping_stats_update(&s, 15000000LL); + ping_stats_update(&s, 15000000LL); + ping_stats_get(&s, &min_ms, &max_ms, &avg_ms, &mdev_ms); + cr_assert_float_eq(min_ms, 15.0, 1e-6); + cr_assert_float_eq(max_ms, 15.0, 1e-6); + cr_assert_float_eq(avg_ms, 15.0, 1e-6); + cr_assert_float_eq(mdev_ms, 0.0, 1e-3); +} diff --git a/tests/ping/tracker/test_tracker.c b/tests/ping/tracker/test_tracker.c new file mode 100644 index 0000000..f6f91c3 --- /dev/null +++ b/tests/ping/tracker/test_tracker.c @@ -0,0 +1,103 @@ +#include +#include +#include "internal/tracker.h" + +static struct timespec +make_ts(long sec, long nsec) +{ + struct timespec ts; + + ts.tv_sec = sec; + ts.tv_nsec = nsec; + return (ts); +} + +Test(ping_tracker, init) +{ + t_ping_tracker t; + + ping_tracker_init(&t); + cr_assert_eq(ping_tracker_sent(&t), (size_t)0); + cr_assert_eq(ping_tracker_recv(&t), (size_t)0); + cr_assert_eq(ping_tracker_lost(&t), (size_t)0); +} + +Test(ping_tracker, basic_send_recv) +{ + t_ping_tracker t; + struct timespec ts_send; + struct timespec ts_recv; + int64_t rtt; + + ping_tracker_init(&t); + ts_send = make_ts(1, 0); + ts_recv = make_ts(1, 10000000); /* 10 ms later */ + ping_tracker_record_send(&t, 1, &ts_send); + rtt = ping_tracker_record_recv(&t, 1, &ts_recv); + cr_assert_eq(rtt, (int64_t)10000000, "Expected RTT of 10ms in ns"); + cr_assert_eq(ping_tracker_sent(&t), (size_t)1); + cr_assert_eq(ping_tracker_recv(&t), (size_t)1); + cr_assert_eq(ping_tracker_lost(&t), (size_t)0); +} + +Test(ping_tracker, double_recv_ignored) +{ + t_ping_tracker t; + struct timespec ts_send; + struct timespec ts_recv; + int64_t rtt; + + ping_tracker_init(&t); + ts_send = make_ts(0, 0); + ts_recv = make_ts(0, 5000000); + ping_tracker_record_send(&t, 5, &ts_send); + rtt = ping_tracker_record_recv(&t, 5, &ts_recv); + cr_assert(rtt >= 0, "First recv should succeed"); + rtt = ping_tracker_record_recv(&t, 5, &ts_recv); + cr_assert_eq(rtt, (int64_t)-1, "Duplicate recv should return -1"); + cr_assert_eq(ping_tracker_recv(&t), (size_t)1); +} + +Test(ping_tracker, unknown_seq_returns_minus_one) +{ + t_ping_tracker t; + struct timespec ts; + int64_t rtt; + + ping_tracker_init(&t); + ts = make_ts(0, 0); + rtt = ping_tracker_record_recv(&t, 42, &ts); + cr_assert_eq(rtt, (int64_t)-1); +} + +Test(ping_tracker, wraparound_seq) +{ + t_ping_tracker t; + struct timespec ts_send; + struct timespec ts_recv; + int64_t rtt; + + ping_tracker_init(&t); + ts_send = make_ts(0, 0); + ts_recv = make_ts(0, 1000000); + /* seq 128 maps to slot 0 */ + ping_tracker_record_send(&t, 128, &ts_send); + rtt = ping_tracker_record_recv(&t, 128, &ts_recv); + cr_assert_eq(rtt, (int64_t)1000000); +} + +Test(ping_tracker, lost_count) +{ + t_ping_tracker t; + struct timespec ts; + + ping_tracker_init(&t); + ts = make_ts(0, 0); + ping_tracker_record_send(&t, 1, &ts); + ping_tracker_record_send(&t, 2, &ts); + ping_tracker_record_send(&t, 3, &ts); + ping_tracker_record_recv(&t, 2, &ts); + cr_assert_eq(ping_tracker_sent(&t), (size_t)3); + cr_assert_eq(ping_tracker_recv(&t), (size_t)1); + cr_assert_eq(ping_tracker_lost(&t), (size_t)2); +}