feat: implement ping core
This commit is contained in:
parent
a4ed2780f2
commit
04e1f3f15b
27 changed files with 996 additions and 3 deletions
8
includes/internal/callback.h
Normal file
8
includes/internal/callback.h
Normal file
|
|
@ -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
|
||||
9
includes/internal/loop.h
Normal file
9
includes/internal/loop.h
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
#ifndef PING_LOOP_H
|
||||
#define PING_LOOP_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include "internal/ping_state.h"
|
||||
|
||||
void ping_loop(t_ping_state *state, size_t payload_len);
|
||||
|
||||
#endif
|
||||
21
includes/internal/output.h
Normal file
21
includes/internal/output.h
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
#ifndef PING_OUTPUT_H
|
||||
#define PING_OUTPUT_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <netinet/in.h>
|
||||
#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
|
||||
28
includes/internal/ping_state.h
Normal file
28
includes/internal/ping_state.h
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
#ifndef PING_STATE_H
|
||||
#define PING_STATE_H
|
||||
|
||||
#include <netinet/in.h>
|
||||
#include <stdint.h>
|
||||
#include <signal.h>
|
||||
#include <time.h>
|
||||
#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
|
||||
12
includes/internal/scheduler.h
Normal file
12
includes/internal/scheduler.h
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
#ifndef PING_SCHEDULER_H
|
||||
#define PING_SCHEDULER_H
|
||||
|
||||
#include <sys/time.h>
|
||||
#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
|
||||
9
includes/internal/send.h
Normal file
9
includes/internal/send.h
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
#ifndef PING_SEND_H
|
||||
#define PING_SEND_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include "internal/ping_state.h"
|
||||
|
||||
int ping_send_one(t_ping_state *state, size_t payload_len);
|
||||
|
||||
#endif
|
||||
20
includes/internal/stats.h
Normal file
20
includes/internal/stats.h
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
#ifndef PING_STATS_H
|
||||
#define PING_STATS_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
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
|
||||
29
includes/internal/tracker.h
Normal file
29
includes/internal/tracker.h
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
#ifndef PING_TRACKER_H
|
||||
#define PING_TRACKER_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <time.h>
|
||||
|
||||
#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
|
||||
8
includes/ping.h
Normal file
8
includes/ping.h
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
#ifndef PING_H
|
||||
#define PING_H
|
||||
|
||||
#include "ft_ping.h"
|
||||
|
||||
int ping_run(const t_ping_config *config);
|
||||
|
||||
#endif
|
||||
17
sources.mk
17
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 \
|
||||
|
||||
|
|
|
|||
93
src/core/callback.c
Normal file
93
src/core/callback.c
Normal file
|
|
@ -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);
|
||||
}
|
||||
128
src/core/loop.c
Normal file
128
src/core/loop.c
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
#include <sys/select.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
}
|
||||
91
src/core/ping.c
Normal file
91
src/core/ping.c
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
23
src/core/send.c
Normal file
23
src/core/send.c
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
#include <string.h>
|
||||
|
||||
#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));
|
||||
}
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
#include <stdio.h>
|
||||
#include <libgen.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#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);
|
||||
|
|
|
|||
34
src/output/error.c
Normal file
34
src/output/error.c
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
#include <stdio.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#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");
|
||||
}
|
||||
15
src/output/flood.c
Normal file
15
src/output/flood.c
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
#include <unistd.h>
|
||||
|
||||
#include "internal/output.h"
|
||||
|
||||
void
|
||||
ping_output_flood_dot(void)
|
||||
{
|
||||
write(1, ".", 1);
|
||||
}
|
||||
|
||||
void
|
||||
ping_output_flood_erase(void)
|
||||
{
|
||||
write(1, "\b \b", 3);
|
||||
}
|
||||
22
src/output/packet.c
Normal file
22
src/output/packet.c
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
#include <stdio.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
24
src/output/start.c
Normal file
24
src/output/start.c
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#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");
|
||||
}
|
||||
56
src/output/summary.c
Normal file
56
src/output/summary.c
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
#include <stdio.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
72
src/scheduler/scheduler.c
Normal file
72
src/scheduler/scheduler.c
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
64
src/stats/stats.c
Normal file
64
src/stats/stats.c
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
#include <math.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
9
src/tracker/init.c
Normal file
9
src/tracker/init.c
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
#include <string.h>
|
||||
|
||||
#include "internal/tracker.h"
|
||||
|
||||
void
|
||||
ping_tracker_init(t_ping_tracker *t)
|
||||
{
|
||||
memset(t, 0, sizeof(*t));
|
||||
}
|
||||
18
src/tracker/record_recv.c
Normal file
18
src/tracker/record_recv.c
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
#include <stdbool.h>
|
||||
|
||||
#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));
|
||||
}
|
||||
16
src/tracker/record_send.c
Normal file
16
src/tracker/record_send.c
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#include <stdbool.h>
|
||||
|
||||
#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++;
|
||||
}
|
||||
64
tests/ping/stats/test_stats.c
Normal file
64
tests/ping/stats/test_stats.c
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
#include <criterion/criterion.h>
|
||||
#include <stdint.h>
|
||||
#include <math.h>
|
||||
#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);
|
||||
}
|
||||
103
tests/ping/tracker/test_tracker.c
Normal file
103
tests/ping/tracker/test_tracker.c
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
#include <criterion/criterion.h>
|
||||
#include <stdint.h>
|
||||
#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);
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue