// SPDX-License-Identifier: GPL-2.0 #include #include #include "aolib.h" static const char *trace_event_names[__MAX_TRACE_EVENTS] = { /* TCP_HASH_EVENT */ "tcp_hash_bad_header", "tcp_hash_md5_required", "tcp_hash_md5_unexpected", "tcp_hash_md5_mismatch", "tcp_hash_ao_required", /* TCP_AO_EVENT */ "tcp_ao_handshake_failure", "tcp_ao_wrong_maclen", "tcp_ao_mismatch", "tcp_ao_key_not_found", "tcp_ao_rnext_request", /* TCP_AO_EVENT_SK */ "tcp_ao_synack_no_key", /* TCP_AO_EVENT_SNE */ "tcp_ao_snd_sne_update", "tcp_ao_rcv_sne_update" }; struct expected_trace_point { /* required */ enum trace_events type; int family; union tcp_addr src; union tcp_addr dst; /* optional */ int src_port; int dst_port; int L3index; int fin; int syn; int rst; int psh; int ack; int keyid; int rnext; int maclen; int sne; size_t matched; }; static struct expected_trace_point *exp_tps; static size_t exp_tps_nr; static size_t exp_tps_size; static pthread_mutex_t exp_tps_mutex = PTHREAD_MUTEX_INITIALIZER; int __trace_event_expect(enum trace_events type, int family, union tcp_addr src, union tcp_addr dst, int src_port, int dst_port, int L3index, int fin, int syn, int rst, int psh, int ack, int keyid, int rnext, int maclen, int sne) { struct expected_trace_point new_tp = { .type = type, .family = family, .src = src, .dst = dst, .src_port = src_port, .dst_port = dst_port, .L3index = L3index, .fin = fin, .syn = syn, .rst = rst, .psh = psh, .ack = ack, .keyid = keyid, .rnext = rnext, .maclen = maclen, .sne = sne, .matched = 0, }; int ret = 0; if (!kernel_config_has(KCONFIG_FTRACE)) return 0; pthread_mutex_lock(&exp_tps_mutex); if (exp_tps_nr == exp_tps_size) { struct expected_trace_point *tmp; if (exp_tps_size == 0) exp_tps_size = 10; else exp_tps_size = exp_tps_size * 1.6; tmp = reallocarray(exp_tps, exp_tps_size, sizeof(exp_tps[0])); if (!tmp) { ret = -ENOMEM; goto out; } exp_tps = tmp; } exp_tps[exp_tps_nr] = new_tp; exp_tps_nr++; out: pthread_mutex_unlock(&exp_tps_mutex); return ret; } static void free_expected_events(void) { /* We're from the process destructor - not taking the mutex */ exp_tps_size = 0; exp_tps = NULL; free(exp_tps); } struct trace_point { int family; union tcp_addr src; union tcp_addr dst; unsigned int src_port; unsigned int dst_port; int L3index; unsigned int fin:1, syn:1, rst:1, psh:1, ack:1; unsigned int keyid; unsigned int rnext; unsigned int maclen; unsigned int sne; }; static bool lookup_expected_event(int event_type, struct trace_point *e) { size_t i; pthread_mutex_lock(&exp_tps_mutex); for (i = 0; i < exp_tps_nr; i++) { struct expected_trace_point *p = &exp_tps[i]; size_t sk_size; if (p->type != event_type) continue; if (p->family != e->family) continue; if (p->family == AF_INET) sk_size = sizeof(p->src.a4); else sk_size = sizeof(p->src.a6); if (memcmp(&p->src, &e->src, sk_size)) continue; if (memcmp(&p->dst, &e->dst, sk_size)) continue; if (p->src_port >= 0 && p->src_port != e->src_port) continue; if (p->dst_port >= 0 && p->dst_port != e->dst_port) continue; if (p->L3index >= 0 && p->L3index != e->L3index) continue; if (p->fin >= 0 && p->fin != e->fin) continue; if (p->syn >= 0 && p->syn != e->syn) continue; if (p->rst >= 0 && p->rst != e->rst) continue; if (p->psh >= 0 && p->psh != e->psh) continue; if (p->ack >= 0 && p->ack != e->ack) continue; if (p->keyid >= 0 && p->keyid != e->keyid) continue; if (p->rnext >= 0 && p->rnext != e->rnext) continue; if (p->maclen >= 0 && p->maclen != e->maclen) continue; if (p->sne >= 0 && p->sne != e->sne) continue; p->matched++; pthread_mutex_unlock(&exp_tps_mutex); return true; } pthread_mutex_unlock(&exp_tps_mutex); return false; } static int check_event_type(const char *line) { size_t i; /* * This should have been a set or hashmap, but it's a selftest, * so... KISS. */ for (i = 0; i < __MAX_TRACE_EVENTS; i++) { if (!strncmp(trace_event_names[i], line, strlen(trace_event_names[i]))) return i; } return -1; } static bool event_has_flags(enum trace_events event) { switch (event) { case TCP_HASH_BAD_HEADER: case TCP_HASH_MD5_REQUIRED: case TCP_HASH_MD5_UNEXPECTED: case TCP_HASH_MD5_MISMATCH: case TCP_HASH_AO_REQUIRED: case TCP_AO_HANDSHAKE_FAILURE: case TCP_AO_WRONG_MACLEN: case TCP_AO_MISMATCH: case TCP_AO_KEY_NOT_FOUND: case TCP_AO_RNEXT_REQUEST: return true; default: return false; } } static int tracer_ip_split(int family, char *src, char **addr, char **port) { char *p; if (family == AF_INET) { /* fomat is :port, i.e.: 10.0.254.1:7015 */ *addr = src; p = strchr(src, ':'); if (!p) { test_print("Couldn't parse trace event addr:port %s", src); return -EINVAL; } *p++ = '\0'; *port = p; return 0; } if (family != AF_INET6) return -EAFNOSUPPORT; /* format is []:port, i.e.: [2001:db8:254::1]:7013 */ *addr = strchr(src, '['); p = strchr(src, ']'); if (!p || !*addr) { test_print("Couldn't parse trace event [addr]:port %s", src); return -EINVAL; } *addr = *addr + 1; /* '[' */ *p++ = '\0'; /* ']' */ if (*p != ':') { test_print("Couldn't parse trace event :port %s", p); return -EINVAL; } *p++ = '\0'; /* ':' */ *port = p; return 0; } static int tracer_scan_address(int family, char *src, union tcp_addr *dst, unsigned int *port) { char *addr, *port_str; int ret; ret = tracer_ip_split(family, src, &addr, &port_str); if (ret) return ret; if (inet_pton(family, addr, dst) != 1) { test_print("Couldn't parse trace event addr %s", addr); return -EINVAL; } errno = 0; *port = (unsigned int)strtoul(port_str, NULL, 10); if (errno != 0) { test_print("Couldn't parse trace event port %s", port_str); return -errno; } return 0; } static int tracer_scan_event(const char *line, enum trace_events event, struct trace_point *out) { char *src = NULL, *dst = NULL, *family = NULL; char fin, syn, rst, psh, ack; int nr_matched, ret = 0; uint64_t netns_cookie; switch (event) { case TCP_HASH_BAD_HEADER: case TCP_HASH_MD5_REQUIRED: case TCP_HASH_MD5_UNEXPECTED: case TCP_HASH_MD5_MISMATCH: case TCP_HASH_AO_REQUIRED: { nr_matched = sscanf(line, "%*s net=%" PRIu64 " state%*s family=%ms src=%ms dest=%ms L3index=%d [%c%c%c%c%c]", &netns_cookie, &family, &src, &dst, &out->L3index, &fin, &syn, &rst, &psh, &ack); if (nr_matched != 10) test_print("Couldn't parse trace event, matched = %d/10", nr_matched); break; } case TCP_AO_HANDSHAKE_FAILURE: case TCP_AO_WRONG_MACLEN: case TCP_AO_MISMATCH: case TCP_AO_KEY_NOT_FOUND: case TCP_AO_RNEXT_REQUEST: { nr_matched = sscanf(line, "%*s net=%" PRIu64 " state%*s family=%ms src=%ms dest=%ms L3index=%d [%c%c%c%c%c] keyid=%u rnext=%u maclen=%u", &netns_cookie, &family, &src, &dst, &out->L3index, &fin, &syn, &rst, &psh, &ack, &out->keyid, &out->rnext, &out->maclen); if (nr_matched != 13) test_print("Couldn't parse trace event, matched = %d/13", nr_matched); break; } case TCP_AO_SYNACK_NO_KEY: { nr_matched = sscanf(line, "%*s net=%" PRIu64 " state%*s family=%ms src=%ms dest=%ms keyid=%u rnext=%u", &netns_cookie, &family, &src, &dst, &out->keyid, &out->rnext); if (nr_matched != 6) test_print("Couldn't parse trace event, matched = %d/6", nr_matched); break; } case TCP_AO_SND_SNE_UPDATE: case TCP_AO_RCV_SNE_UPDATE: { nr_matched = sscanf(line, "%*s net=%" PRIu64 " state%*s family=%ms src=%ms dest=%ms sne=%u", &netns_cookie, &family, &src, &dst, &out->sne); if (nr_matched != 5) test_print("Couldn't parse trace event, matched = %d/5", nr_matched); break; } default: return -1; } if (family) { if (!strcmp(family, "AF_INET")) { out->family = AF_INET; } else if (!strcmp(family, "AF_INET6")) { out->family = AF_INET6; } else { test_print("Couldn't parse trace event family %s", family); ret = -EINVAL; goto out_free; } } if (event_has_flags(event)) { out->fin = (fin == 'F'); out->syn = (syn == 'S'); out->rst = (rst == 'R'); out->psh = (psh == 'P'); out->ack = (ack == '.'); if ((fin != 'F' && fin != ' ') || (syn != 'S' && syn != ' ') || (rst != 'R' && rst != ' ') || (psh != 'P' && psh != ' ') || (ack != '.' && ack != ' ')) { test_print("Couldn't parse trace event flags %c%c%c%c%c", fin, syn, rst, psh, ack); ret = -EINVAL; goto out_free; } } if (src && tracer_scan_address(out->family, src, &out->src, &out->src_port)) { ret = -EINVAL; goto out_free; } if (dst && tracer_scan_address(out->family, dst, &out->dst, &out->dst_port)) { ret = -EINVAL; goto out_free; } if (netns_cookie != ns_cookie1 && netns_cookie != ns_cookie2) { test_print("Net namespace filter for trace event didn't work: %" PRIu64 " != %" PRIu64 " OR %" PRIu64, netns_cookie, ns_cookie1, ns_cookie2); ret = -EINVAL; } out_free: free(src); free(dst); free(family); return ret; } static enum ftracer_op aolib_tracer_process_event(const char *line) { int event_type = check_event_type(line); struct trace_point tmp = {}; if (event_type < 0) return FTRACER_LINE_PRESERVE; if (tracer_scan_event(line, event_type, &tmp)) return FTRACER_LINE_PRESERVE; return lookup_expected_event(event_type, &tmp) ? FTRACER_LINE_DISCARD : FTRACER_LINE_PRESERVE; } static void dump_trace_event(struct expected_trace_point *e) { char src[INET6_ADDRSTRLEN], dst[INET6_ADDRSTRLEN]; if (!inet_ntop(e->family, &e->src, src, INET6_ADDRSTRLEN)) test_error("inet_ntop()"); if (!inet_ntop(e->family, &e->dst, dst, INET6_ADDRSTRLEN)) test_error("inet_ntop()"); test_print("trace event filter %s [%s:%d => %s:%d, L3index %d, flags: %s%s%s%s%s, keyid: %d, rnext: %d, maclen: %d, sne: %d] = %zu", trace_event_names[e->type], src, e->src_port, dst, e->dst_port, e->L3index, (e->fin > 0) ? "F" : (e->fin == 0) ? "!F" : "", (e->syn > 0) ? "S" : (e->syn == 0) ? "!S" : "", (e->rst > 0) ? "R" : (e->rst == 0) ? "!R" : "", (e->psh > 0) ? "P" : (e->psh == 0) ? "!P" : "", (e->ack > 0) ? "." : (e->ack == 0) ? "!." : "", e->keyid, e->rnext, e->maclen, e->sne, e->matched); } static void print_match_stats(bool unexpected_events) { size_t matches_per_type[__MAX_TRACE_EVENTS] = {}; bool expected_but_none = false; size_t i, total_matched = 0; char *stat_line = NULL; for (i = 0; i < exp_tps_nr; i++) { struct expected_trace_point *e = &exp_tps[i]; total_matched += e->matched; matches_per_type[e->type] += e->matched; if (!e->matched) expected_but_none = true; } for (i = 0; i < __MAX_TRACE_EVENTS; i++) { if (!matches_per_type[i]) continue; stat_line = test_sprintf("%s%s[%zu] ", stat_line ?: "", trace_event_names[i], matches_per_type[i]); if (!stat_line) test_error("test_sprintf()"); } if (unexpected_events || expected_but_none) { for (i = 0; i < exp_tps_nr; i++) dump_trace_event(&exp_tps[i]); } if (unexpected_events) return; if (expected_but_none) test_fail("Some trace events were expected, but didn't occur"); else if (total_matched) test_ok("Trace events matched expectations: %zu %s", total_matched, stat_line); else test_ok("No unexpected trace events during the test run"); } #define dump_events(fmt, ...) \ __test_print(__test_msg, fmt, ##__VA_ARGS__) static void check_free_events(struct test_ftracer *tracer) { const char **lines; size_t nr; if (!kernel_config_has(KCONFIG_FTRACE)) { test_skip("kernel config doesn't have ftrace - no checks"); return; } nr = tracer_get_savedlines_nr(tracer); lines = tracer_get_savedlines(tracer); print_match_stats(!!nr); if (!nr) return; errno = 0; test_xfail("Trace events [%zu] were not expected:", nr); while (nr) dump_events("\t%s", lines[--nr]); } static int setup_tcp_trace_events(struct test_ftracer *tracer) { char *filter; size_t i; int ret; filter = test_sprintf("net_cookie == %zu || net_cookie == %zu", ns_cookie1, ns_cookie2); if (!filter) return -ENOMEM; for (i = 0; i < __MAX_TRACE_EVENTS; i++) { char *event_name = test_sprintf("tcp/%s", trace_event_names[i]); if (!event_name) { ret = -ENOMEM; break; } ret = setup_trace_event(tracer, event_name, filter); free(event_name); if (ret) break; } free(filter); return ret; } static void aolib_tracer_destroy(struct test_ftracer *tracer) { check_free_events(tracer); free_expected_events(); } static bool aolib_tracer_expecting_more(void) { size_t i; for (i = 0; i < exp_tps_nr; i++) if (!exp_tps[i].matched) return true; return false; } int setup_aolib_ftracer(void) { struct test_ftracer *f; f = create_ftracer("aolib", aolib_tracer_process_event, aolib_tracer_destroy, aolib_tracer_expecting_more, DEFAULT_FTRACE_BUFFER_KB, DEFAULT_TRACER_LINES_ARR); if (!f) return -1; return setup_tcp_trace_events(f); }