| // SPDX-License-Identifier: GPL-2.0 |
| /* Author: Dmitry Safonov <dima@arista.com> */ |
| #include <inttypes.h> |
| #include "../../../../include/linux/kernel.h" |
| #include "aolib.h" |
| |
| const size_t nr_packets = 20; |
| const size_t msg_len = 100; |
| const size_t quota = nr_packets * msg_len; |
| union tcp_addr wrong_addr; |
| #define SECOND_PASSWORD "at all times sincere friends of freedom have been rare" |
| #define fault(type) (inj == FAULT_ ## type) |
| |
| static const int test_vrf_ifindex = 200; |
| static const uint8_t test_vrf_tabid = 42; |
| static void setup_vrfs(void) |
| { |
| int err; |
| |
| if (!kernel_config_has(KCONFIG_NET_VRF)) |
| return; |
| |
| err = add_vrf("ksft-vrf", test_vrf_tabid, test_vrf_ifindex, -1); |
| if (err) |
| test_error("Failed to add a VRF: %d", err); |
| |
| err = link_set_up("ksft-vrf"); |
| if (err) |
| test_error("Failed to bring up a VRF"); |
| |
| err = ip_route_add_vrf(veth_name, TEST_FAMILY, |
| this_ip_addr, this_ip_dest, test_vrf_tabid); |
| if (err) |
| test_error("Failed to add a route to VRF"); |
| } |
| |
| |
| static int prepare_sk(union tcp_addr *addr, uint8_t sndid, uint8_t rcvid) |
| { |
| int sk = socket(test_family, SOCK_STREAM, IPPROTO_TCP); |
| |
| if (sk < 0) |
| test_error("socket()"); |
| |
| if (test_add_key(sk, DEFAULT_TEST_PASSWORD, this_ip_dest, |
| DEFAULT_TEST_PREFIX, 100, 100)) |
| test_error("test_add_key()"); |
| |
| if (addr && test_add_key(sk, SECOND_PASSWORD, *addr, |
| DEFAULT_TEST_PREFIX, sndid, rcvid)) |
| test_error("test_add_key()"); |
| |
| return sk; |
| } |
| |
| static int prepare_lsk(union tcp_addr *addr, uint8_t sndid, uint8_t rcvid) |
| { |
| int sk = prepare_sk(addr, sndid, rcvid); |
| |
| if (listen(sk, 10)) |
| test_error("listen()"); |
| |
| return sk; |
| } |
| |
| static int test_del_key(int sk, uint8_t sndid, uint8_t rcvid, bool async, |
| int current_key, int rnext_key) |
| { |
| struct tcp_ao_info_opt ao_info = {}; |
| struct tcp_ao_getsockopt key = {}; |
| struct tcp_ao_del del = {}; |
| sockaddr_af sockaddr; |
| int err; |
| |
| tcp_addr_to_sockaddr_in(&del.addr, &this_ip_dest, 0); |
| del.prefix = DEFAULT_TEST_PREFIX; |
| del.sndid = sndid; |
| del.rcvid = rcvid; |
| |
| if (current_key >= 0) { |
| del.set_current = 1; |
| del.current_key = (uint8_t)current_key; |
| } |
| if (rnext_key >= 0) { |
| del.set_rnext = 1; |
| del.rnext = (uint8_t)rnext_key; |
| } |
| |
| err = setsockopt(sk, IPPROTO_TCP, TCP_AO_DEL_KEY, &del, sizeof(del)); |
| if (err < 0) |
| return -errno; |
| |
| if (async) |
| return 0; |
| |
| tcp_addr_to_sockaddr_in(&sockaddr, &this_ip_dest, 0); |
| err = test_get_one_ao(sk, &key, &sockaddr, sizeof(sockaddr), |
| DEFAULT_TEST_PREFIX, sndid, rcvid); |
| if (!err) |
| return -EEXIST; |
| if (err != -E2BIG) |
| test_error("getsockopt()"); |
| if (current_key < 0 && rnext_key < 0) |
| return 0; |
| if (test_get_ao_info(sk, &ao_info)) |
| test_error("getsockopt(TCP_AO_INFO) failed"); |
| if (current_key >= 0 && ao_info.current_key != (uint8_t)current_key) |
| return -ENOTRECOVERABLE; |
| if (rnext_key >= 0 && ao_info.rnext != (uint8_t)rnext_key) |
| return -ENOTRECOVERABLE; |
| return 0; |
| } |
| |
| static void try_delete_key(char *tst_name, int sk, uint8_t sndid, uint8_t rcvid, |
| bool async, int current_key, int rnext_key, |
| fault_t inj) |
| { |
| int err; |
| |
| err = test_del_key(sk, sndid, rcvid, async, current_key, rnext_key); |
| if ((err == -EBUSY && fault(BUSY)) || (err == -EINVAL && fault(CURRNEXT))) { |
| test_ok("%s: key deletion was prevented", tst_name); |
| return; |
| } |
| if (err && fault(FIXME)) { |
| test_xfail("%s: failed to delete the key %u:%u %d", |
| tst_name, sndid, rcvid, err); |
| return; |
| } |
| if (!err) { |
| if (fault(BUSY) || fault(CURRNEXT)) { |
| test_fail("%s: the key was deleted %u:%u %d", tst_name, |
| sndid, rcvid, err); |
| } else { |
| test_ok("%s: the key was deleted", tst_name); |
| } |
| return; |
| } |
| test_fail("%s: can't delete the key %u:%u %d", tst_name, sndid, rcvid, err); |
| } |
| |
| static int test_set_key(int sk, int current_keyid, int rnext_keyid) |
| { |
| struct tcp_ao_info_opt ao_info = {}; |
| int err; |
| |
| if (current_keyid >= 0) { |
| ao_info.set_current = 1; |
| ao_info.current_key = (uint8_t)current_keyid; |
| } |
| if (rnext_keyid >= 0) { |
| ao_info.set_rnext = 1; |
| ao_info.rnext = (uint8_t)rnext_keyid; |
| } |
| |
| err = test_set_ao_info(sk, &ao_info); |
| if (err) |
| return err; |
| if (test_get_ao_info(sk, &ao_info)) |
| test_error("getsockopt(TCP_AO_INFO) failed"); |
| if (current_keyid >= 0 && ao_info.current_key != (uint8_t)current_keyid) |
| return -ENOTRECOVERABLE; |
| if (rnext_keyid >= 0 && ao_info.rnext != (uint8_t)rnext_keyid) |
| return -ENOTRECOVERABLE; |
| return 0; |
| } |
| |
| static int test_add_current_rnext_key(int sk, const char *key, uint8_t keyflags, |
| union tcp_addr in_addr, uint8_t prefix, |
| bool set_current, bool set_rnext, |
| uint8_t sndid, uint8_t rcvid) |
| { |
| struct tcp_ao_add tmp = {}; |
| int err; |
| |
| err = test_prepare_key(&tmp, DEFAULT_TEST_ALGO, in_addr, |
| set_current, set_rnext, |
| prefix, 0, sndid, rcvid, 0, keyflags, |
| strlen(key), key); |
| if (err) |
| return err; |
| |
| |
| err = setsockopt(sk, IPPROTO_TCP, TCP_AO_ADD_KEY, &tmp, sizeof(tmp)); |
| if (err < 0) |
| return -errno; |
| |
| return test_verify_socket_key(sk, &tmp); |
| } |
| |
| static int __try_add_current_rnext_key(int sk, const char *key, uint8_t keyflags, |
| union tcp_addr in_addr, uint8_t prefix, |
| bool set_current, bool set_rnext, |
| uint8_t sndid, uint8_t rcvid) |
| { |
| struct tcp_ao_info_opt ao_info = {}; |
| int err; |
| |
| err = test_add_current_rnext_key(sk, key, keyflags, in_addr, prefix, |
| set_current, set_rnext, sndid, rcvid); |
| if (err) |
| return err; |
| |
| if (test_get_ao_info(sk, &ao_info)) |
| test_error("getsockopt(TCP_AO_INFO) failed"); |
| if (set_current && ao_info.current_key != sndid) |
| return -ENOTRECOVERABLE; |
| if (set_rnext && ao_info.rnext != rcvid) |
| return -ENOTRECOVERABLE; |
| return 0; |
| } |
| |
| static void try_add_current_rnext_key(char *tst_name, int sk, const char *key, |
| uint8_t keyflags, |
| union tcp_addr in_addr, uint8_t prefix, |
| bool set_current, bool set_rnext, |
| uint8_t sndid, uint8_t rcvid, fault_t inj) |
| { |
| int err; |
| |
| err = __try_add_current_rnext_key(sk, key, keyflags, in_addr, prefix, |
| set_current, set_rnext, sndid, rcvid); |
| if (!err && !fault(CURRNEXT)) { |
| test_ok("%s", tst_name); |
| return; |
| } |
| if (err == -EINVAL && fault(CURRNEXT)) { |
| test_ok("%s", tst_name); |
| return; |
| } |
| test_fail("%s", tst_name); |
| } |
| |
| static void check_closed_socket(void) |
| { |
| int sk; |
| |
| sk = prepare_sk(&this_ip_dest, 200, 200); |
| try_delete_key("closed socket, delete a key", sk, 200, 200, 0, -1, -1, 0); |
| try_delete_key("closed socket, delete all keys", sk, 100, 100, 0, -1, -1, 0); |
| close(sk); |
| |
| sk = prepare_sk(&this_ip_dest, 200, 200); |
| if (test_set_key(sk, 100, 200)) |
| test_error("failed to set current/rnext keys"); |
| try_delete_key("closed socket, delete current key", sk, 100, 100, 0, -1, -1, FAULT_BUSY); |
| try_delete_key("closed socket, delete rnext key", sk, 200, 200, 0, -1, -1, FAULT_BUSY); |
| close(sk); |
| |
| sk = prepare_sk(&this_ip_dest, 200, 200); |
| if (test_add_key(sk, "Glory to heros!", this_ip_dest, |
| DEFAULT_TEST_PREFIX, 10, 11)) |
| test_error("test_add_key()"); |
| if (test_add_key(sk, "Glory to Ukraine!", this_ip_dest, |
| DEFAULT_TEST_PREFIX, 12, 13)) |
| test_error("test_add_key()"); |
| try_delete_key("closed socket, delete a key + set current/rnext", sk, 100, 100, 0, 10, 13, 0); |
| try_delete_key("closed socket, force-delete current key", sk, 10, 11, 0, 200, -1, 0); |
| try_delete_key("closed socket, force-delete rnext key", sk, 12, 13, 0, -1, 200, 0); |
| try_delete_key("closed socket, delete current+rnext key", sk, 200, 200, 0, -1, -1, FAULT_BUSY); |
| close(sk); |
| |
| sk = prepare_sk(&this_ip_dest, 200, 200); |
| if (test_set_key(sk, 100, 200)) |
| test_error("failed to set current/rnext keys"); |
| try_add_current_rnext_key("closed socket, add + change current key", |
| sk, "Laaaa! Lalala-la-la-lalala...", 0, |
| this_ip_dest, DEFAULT_TEST_PREFIX, |
| true, false, 10, 20, 0); |
| try_add_current_rnext_key("closed socket, add + change rnext key", |
| sk, "Laaaa! Lalala-la-la-lalala...", 0, |
| this_ip_dest, DEFAULT_TEST_PREFIX, |
| false, true, 20, 10, 0); |
| close(sk); |
| } |
| |
| static void assert_no_current_rnext(const char *tst_msg, int sk) |
| { |
| struct tcp_ao_info_opt ao_info = {}; |
| |
| if (test_get_ao_info(sk, &ao_info)) |
| test_error("getsockopt(TCP_AO_INFO) failed"); |
| |
| errno = 0; |
| if (ao_info.set_current || ao_info.set_rnext) { |
| test_xfail("%s: the socket has current/rnext keys: %d:%d", |
| tst_msg, |
| (ao_info.set_current) ? ao_info.current_key : -1, |
| (ao_info.set_rnext) ? ao_info.rnext : -1); |
| } else { |
| test_ok("%s: the socket has no current/rnext keys", tst_msg); |
| } |
| } |
| |
| static void assert_no_tcp_repair(void) |
| { |
| struct tcp_ao_repair ao_img = {}; |
| socklen_t len = sizeof(ao_img); |
| int sk, err; |
| |
| sk = prepare_sk(&this_ip_dest, 200, 200); |
| test_enable_repair(sk); |
| if (listen(sk, 10)) |
| test_error("listen()"); |
| errno = 0; |
| err = getsockopt(sk, SOL_TCP, TCP_AO_REPAIR, &ao_img, &len); |
| if (err && errno == EPERM) |
| test_ok("listen socket, getsockopt(TCP_AO_REPAIR) is restricted"); |
| else |
| test_fail("listen socket, getsockopt(TCP_AO_REPAIR) works"); |
| errno = 0; |
| err = setsockopt(sk, SOL_TCP, TCP_AO_REPAIR, &ao_img, sizeof(ao_img)); |
| if (err && errno == EPERM) |
| test_ok("listen socket, setsockopt(TCP_AO_REPAIR) is restricted"); |
| else |
| test_fail("listen socket, setsockopt(TCP_AO_REPAIR) works"); |
| close(sk); |
| } |
| |
| static void check_listen_socket(void) |
| { |
| int sk, err; |
| |
| sk = prepare_lsk(&this_ip_dest, 200, 200); |
| try_delete_key("listen socket, delete a key", sk, 200, 200, 0, -1, -1, 0); |
| try_delete_key("listen socket, delete all keys", sk, 100, 100, 0, -1, -1, 0); |
| close(sk); |
| |
| sk = prepare_lsk(&this_ip_dest, 200, 200); |
| err = test_set_key(sk, 100, -1); |
| if (err == -EINVAL) |
| test_ok("listen socket, setting current key not allowed"); |
| else |
| test_fail("listen socket, set current key"); |
| err = test_set_key(sk, -1, 200); |
| if (err == -EINVAL) |
| test_ok("listen socket, setting rnext key not allowed"); |
| else |
| test_fail("listen socket, set rnext key"); |
| close(sk); |
| |
| sk = prepare_sk(&this_ip_dest, 200, 200); |
| if (test_set_key(sk, 100, 200)) |
| test_error("failed to set current/rnext keys"); |
| if (listen(sk, 10)) |
| test_error("listen()"); |
| assert_no_current_rnext("listen() after current/rnext keys set", sk); |
| try_delete_key("listen socket, delete current key from before listen()", sk, 100, 100, 0, -1, -1, FAULT_FIXME); |
| try_delete_key("listen socket, delete rnext key from before listen()", sk, 200, 200, 0, -1, -1, FAULT_FIXME); |
| close(sk); |
| |
| assert_no_tcp_repair(); |
| |
| sk = prepare_lsk(&this_ip_dest, 200, 200); |
| if (test_add_key(sk, "Glory to heros!", this_ip_dest, |
| DEFAULT_TEST_PREFIX, 10, 11)) |
| test_error("test_add_key()"); |
| if (test_add_key(sk, "Glory to Ukraine!", this_ip_dest, |
| DEFAULT_TEST_PREFIX, 12, 13)) |
| test_error("test_add_key()"); |
| try_delete_key("listen socket, delete a key + set current/rnext", sk, |
| 100, 100, 0, 10, 13, FAULT_CURRNEXT); |
| try_delete_key("listen socket, force-delete current key", sk, |
| 10, 11, 0, 200, -1, FAULT_CURRNEXT); |
| try_delete_key("listen socket, force-delete rnext key", sk, |
| 12, 13, 0, -1, 200, FAULT_CURRNEXT); |
| try_delete_key("listen socket, delete a key", sk, |
| 200, 200, 0, -1, -1, 0); |
| close(sk); |
| |
| sk = prepare_lsk(&this_ip_dest, 200, 200); |
| try_add_current_rnext_key("listen socket, add + change current key", |
| sk, "Laaaa! Lalala-la-la-lalala...", 0, |
| this_ip_dest, DEFAULT_TEST_PREFIX, |
| true, false, 10, 20, FAULT_CURRNEXT); |
| try_add_current_rnext_key("listen socket, add + change rnext key", |
| sk, "Laaaa! Lalala-la-la-lalala...", 0, |
| this_ip_dest, DEFAULT_TEST_PREFIX, |
| false, true, 20, 10, FAULT_CURRNEXT); |
| close(sk); |
| } |
| |
| static const char *fips_fpath = "/proc/sys/crypto/fips_enabled"; |
| static bool is_fips_enabled(void) |
| { |
| static int fips_checked = -1; |
| FILE *fenabled; |
| int enabled; |
| |
| if (fips_checked >= 0) |
| return !!fips_checked; |
| if (access(fips_fpath, R_OK)) { |
| if (errno != ENOENT) |
| test_error("Can't open %s", fips_fpath); |
| fips_checked = 0; |
| return false; |
| } |
| fenabled = fopen(fips_fpath, "r"); |
| if (!fenabled) |
| test_error("Can't open %s", fips_fpath); |
| if (fscanf(fenabled, "%d", &enabled) != 1) |
| test_error("Can't read from %s", fips_fpath); |
| fclose(fenabled); |
| fips_checked = !!enabled; |
| return !!fips_checked; |
| } |
| |
| struct test_key { |
| char password[TCP_AO_MAXKEYLEN]; |
| const char *alg; |
| unsigned int len; |
| uint8_t client_keyid; |
| uint8_t server_keyid; |
| uint8_t maclen; |
| uint8_t matches_client : 1, |
| matches_server : 1, |
| matches_vrf : 1, |
| is_current : 1, |
| is_rnext : 1, |
| used_on_server_tx : 1, |
| used_on_client_tx : 1, |
| skip_counters_checks : 1; |
| }; |
| |
| struct key_collection { |
| unsigned int nr_keys; |
| struct test_key *keys; |
| }; |
| |
| static struct key_collection collection; |
| |
| #define TEST_MAX_MACLEN 16 |
| const char *test_algos[] = { |
| "cmac(aes128)", |
| "hmac(sha1)", "hmac(sha512)", "hmac(sha384)", "hmac(sha256)", |
| "hmac(sha224)", "hmac(sha3-512)", |
| /* only if !CONFIG_FIPS */ |
| #define TEST_NON_FIPS_ALGOS 2 |
| "hmac(rmd160)", "hmac(md5)" |
| }; |
| const unsigned int test_maclens[] = { 1, 4, 12, 16 }; |
| #define MACLEN_SHIFT 2 |
| #define ALGOS_SHIFT 4 |
| |
| static unsigned int make_mask(unsigned int shift, unsigned int prev_shift) |
| { |
| unsigned int ret = BIT(shift) - 1; |
| |
| return ret << prev_shift; |
| } |
| |
| static void init_key_in_collection(unsigned int index, bool randomized) |
| { |
| struct test_key *key = &collection.keys[index]; |
| unsigned int algos_nr, algos_index; |
| |
| /* Same for randomized and non-randomized test flows */ |
| key->client_keyid = index; |
| key->server_keyid = 127 + index; |
| key->matches_client = 1; |
| key->matches_server = 1; |
| key->matches_vrf = 1; |
| /* not really even random, but good enough for a test */ |
| key->len = rand() % (TCP_AO_MAXKEYLEN - TEST_TCP_AO_MINKEYLEN); |
| key->len += TEST_TCP_AO_MINKEYLEN; |
| randomize_buffer(key->password, key->len); |
| |
| if (randomized) { |
| key->maclen = (rand() % TEST_MAX_MACLEN) + 1; |
| algos_index = rand(); |
| } else { |
| unsigned int shift = MACLEN_SHIFT; |
| |
| key->maclen = test_maclens[index & make_mask(shift, 0)]; |
| algos_index = index & make_mask(ALGOS_SHIFT, shift); |
| } |
| algos_nr = ARRAY_SIZE(test_algos); |
| if (is_fips_enabled()) |
| algos_nr -= TEST_NON_FIPS_ALGOS; |
| key->alg = test_algos[algos_index % algos_nr]; |
| } |
| |
| static int init_default_key_collection(unsigned int nr_keys, bool randomized) |
| { |
| size_t key_sz = sizeof(collection.keys[0]); |
| |
| if (!nr_keys) { |
| free(collection.keys); |
| collection.keys = NULL; |
| return 0; |
| } |
| |
| /* |
| * All keys have uniq sndid/rcvid and sndid != rcvid in order to |
| * check for any bugs/issues for different keyids, visible to both |
| * peers. Keyid == 254 is unused. |
| */ |
| if (nr_keys > 127) |
| test_error("Test requires too many keys, correct the source"); |
| |
| collection.keys = reallocarray(collection.keys, nr_keys, key_sz); |
| if (!collection.keys) |
| return -ENOMEM; |
| |
| memset(collection.keys, 0, nr_keys * key_sz); |
| collection.nr_keys = nr_keys; |
| while (nr_keys--) |
| init_key_in_collection(nr_keys, randomized); |
| |
| return 0; |
| } |
| |
| static void test_key_error(const char *msg, struct test_key *key) |
| { |
| test_error("%s: key: { %s, %u:%u, %u, %u:%u:%u:%u:%u (%u)}", |
| msg, key->alg, key->client_keyid, key->server_keyid, |
| key->maclen, key->matches_client, key->matches_server, |
| key->matches_vrf, key->is_current, key->is_rnext, key->len); |
| } |
| |
| static int test_add_key_cr(int sk, const char *pwd, unsigned int pwd_len, |
| union tcp_addr addr, uint8_t vrf, |
| uint8_t sndid, uint8_t rcvid, |
| uint8_t maclen, const char *alg, |
| bool set_current, bool set_rnext) |
| { |
| struct tcp_ao_add tmp = {}; |
| uint8_t keyflags = 0; |
| int err; |
| |
| if (!alg) |
| alg = DEFAULT_TEST_ALGO; |
| |
| if (vrf) |
| keyflags |= TCP_AO_KEYF_IFINDEX; |
| err = test_prepare_key(&tmp, alg, addr, set_current, set_rnext, |
| DEFAULT_TEST_PREFIX, vrf, sndid, rcvid, maclen, |
| keyflags, pwd_len, pwd); |
| if (err) |
| return err; |
| |
| err = setsockopt(sk, IPPROTO_TCP, TCP_AO_ADD_KEY, &tmp, sizeof(tmp)); |
| if (err < 0) |
| return -errno; |
| |
| return test_verify_socket_key(sk, &tmp); |
| } |
| |
| static void verify_current_rnext(const char *tst, int sk, |
| int current_keyid, int rnext_keyid) |
| { |
| struct tcp_ao_info_opt ao_info = {}; |
| |
| if (test_get_ao_info(sk, &ao_info)) |
| test_error("getsockopt(TCP_AO_INFO) failed"); |
| |
| errno = 0; |
| if (current_keyid >= 0) { |
| if (!ao_info.set_current) |
| test_fail("%s: the socket doesn't have current key", tst); |
| else if (ao_info.current_key != current_keyid) |
| test_fail("%s: current key is not the expected one %d != %u", |
| tst, current_keyid, ao_info.current_key); |
| else |
| test_ok("%s: current key %u as expected", |
| tst, ao_info.current_key); |
| } |
| if (rnext_keyid >= 0) { |
| if (!ao_info.set_rnext) |
| test_fail("%s: the socket doesn't have rnext key", tst); |
| else if (ao_info.rnext != rnext_keyid) |
| test_fail("%s: rnext key is not the expected one %d != %u", |
| tst, rnext_keyid, ao_info.rnext); |
| else |
| test_ok("%s: rnext key %u as expected", tst, ao_info.rnext); |
| } |
| } |
| |
| |
| static int key_collection_socket(bool server, unsigned int port) |
| { |
| unsigned int i; |
| int sk; |
| |
| if (server) |
| sk = test_listen_socket(this_ip_addr, port, 1); |
| else |
| sk = socket(test_family, SOCK_STREAM, IPPROTO_TCP); |
| if (sk < 0) |
| test_error("socket()"); |
| |
| for (i = 0; i < collection.nr_keys; i++) { |
| struct test_key *key = &collection.keys[i]; |
| union tcp_addr *addr = &wrong_addr; |
| uint8_t sndid, rcvid, vrf; |
| bool set_current = false, set_rnext = false; |
| |
| if (key->matches_vrf) |
| vrf = 0; |
| else |
| vrf = test_vrf_ifindex; |
| if (server) { |
| if (key->matches_client) |
| addr = &this_ip_dest; |
| sndid = key->server_keyid; |
| rcvid = key->client_keyid; |
| } else { |
| if (key->matches_server) |
| addr = &this_ip_dest; |
| sndid = key->client_keyid; |
| rcvid = key->server_keyid; |
| key->used_on_client_tx = set_current = key->is_current; |
| key->used_on_server_tx = set_rnext = key->is_rnext; |
| } |
| |
| if (test_add_key_cr(sk, key->password, key->len, |
| *addr, vrf, sndid, rcvid, key->maclen, |
| key->alg, set_current, set_rnext)) |
| test_key_error("setsockopt(TCP_AO_ADD_KEY)", key); |
| #ifdef DEBUG |
| test_print("%s [%u/%u] key: { %s, %u:%u, %u, %u:%u:%u:%u (%u)}", |
| server ? "server" : "client", i, collection.nr_keys, |
| key->alg, rcvid, sndid, key->maclen, |
| key->matches_client, key->matches_server, |
| key->is_current, key->is_rnext, key->len); |
| #endif |
| } |
| return sk; |
| } |
| |
| static void verify_counters(const char *tst_name, bool is_listen_sk, bool server, |
| struct tcp_ao_counters *a, struct tcp_ao_counters *b) |
| { |
| unsigned int i; |
| |
| __test_tcp_ao_counters_cmp(tst_name, a, b, TEST_CNT_GOOD); |
| |
| for (i = 0; i < collection.nr_keys; i++) { |
| struct test_key *key = &collection.keys[i]; |
| uint8_t sndid, rcvid; |
| bool rx_cnt_expected; |
| |
| if (key->skip_counters_checks) |
| continue; |
| if (server) { |
| sndid = key->server_keyid; |
| rcvid = key->client_keyid; |
| rx_cnt_expected = key->used_on_client_tx; |
| } else { |
| sndid = key->client_keyid; |
| rcvid = key->server_keyid; |
| rx_cnt_expected = key->used_on_server_tx; |
| } |
| |
| test_tcp_ao_key_counters_cmp(tst_name, a, b, |
| rx_cnt_expected ? TEST_CNT_KEY_GOOD : 0, |
| sndid, rcvid); |
| } |
| test_tcp_ao_counters_free(a); |
| test_tcp_ao_counters_free(b); |
| test_ok("%s: passed counters checks", tst_name); |
| } |
| |
| static struct tcp_ao_getsockopt *lookup_key(struct tcp_ao_getsockopt *buf, |
| size_t len, int sndid, int rcvid) |
| { |
| size_t i; |
| |
| for (i = 0; i < len; i++) { |
| if (sndid >= 0 && buf[i].sndid != sndid) |
| continue; |
| if (rcvid >= 0 && buf[i].rcvid != rcvid) |
| continue; |
| return &buf[i]; |
| } |
| return NULL; |
| } |
| |
| static void verify_keys(const char *tst_name, int sk, |
| bool is_listen_sk, bool server) |
| { |
| socklen_t len = sizeof(struct tcp_ao_getsockopt); |
| struct tcp_ao_getsockopt *keys; |
| bool passed_test = true; |
| unsigned int i; |
| |
| keys = calloc(collection.nr_keys, len); |
| if (!keys) |
| test_error("calloc()"); |
| |
| keys->nkeys = collection.nr_keys; |
| keys->get_all = 1; |
| |
| if (getsockopt(sk, IPPROTO_TCP, TCP_AO_GET_KEYS, keys, &len)) { |
| free(keys); |
| test_error("getsockopt(TCP_AO_GET_KEYS)"); |
| } |
| |
| for (i = 0; i < collection.nr_keys; i++) { |
| struct test_key *key = &collection.keys[i]; |
| struct tcp_ao_getsockopt *dump_key; |
| bool is_kdf_aes_128_cmac = false; |
| bool is_cmac_aes = false; |
| uint8_t sndid, rcvid; |
| bool matches = false; |
| |
| if (server) { |
| if (key->matches_client) |
| matches = true; |
| sndid = key->server_keyid; |
| rcvid = key->client_keyid; |
| } else { |
| if (key->matches_server) |
| matches = true; |
| sndid = key->client_keyid; |
| rcvid = key->server_keyid; |
| } |
| if (!key->matches_vrf) |
| matches = false; |
| /* no keys get removed on the original listener socket */ |
| if (is_listen_sk) |
| matches = true; |
| |
| dump_key = lookup_key(keys, keys->nkeys, sndid, rcvid); |
| if (matches != !!dump_key) { |
| test_fail("%s: key %u:%u %s%s on the socket", |
| tst_name, sndid, rcvid, |
| key->matches_vrf ? "" : "[vrf] ", |
| matches ? "disappeared" : "yet present"); |
| passed_test = false; |
| goto out; |
| } |
| if (!dump_key) |
| continue; |
| |
| if (!strcmp("cmac(aes128)", key->alg)) { |
| is_kdf_aes_128_cmac = (key->len != 16); |
| is_cmac_aes = true; |
| } |
| |
| if (is_cmac_aes) { |
| if (strcmp(dump_key->alg_name, "cmac(aes)")) { |
| test_fail("%s: key %u:%u cmac(aes) has unexpected alg %s", |
| tst_name, sndid, rcvid, |
| dump_key->alg_name); |
| passed_test = false; |
| continue; |
| } |
| } else if (strcmp(dump_key->alg_name, key->alg)) { |
| test_fail("%s: key %u:%u has unexpected alg %s != %s", |
| tst_name, sndid, rcvid, |
| dump_key->alg_name, key->alg); |
| passed_test = false; |
| continue; |
| } |
| if (is_kdf_aes_128_cmac) { |
| if (dump_key->keylen != 16) { |
| test_fail("%s: key %u:%u cmac(aes128) has unexpected len %u", |
| tst_name, sndid, rcvid, |
| dump_key->keylen); |
| continue; |
| } |
| } else if (dump_key->keylen != key->len) { |
| test_fail("%s: key %u:%u changed password len %u != %u", |
| tst_name, sndid, rcvid, |
| dump_key->keylen, key->len); |
| passed_test = false; |
| continue; |
| } |
| if (!is_kdf_aes_128_cmac && |
| memcmp(dump_key->key, key->password, key->len)) { |
| test_fail("%s: key %u:%u has different password", |
| tst_name, sndid, rcvid); |
| passed_test = false; |
| continue; |
| } |
| if (dump_key->maclen != key->maclen) { |
| test_fail("%s: key %u:%u changed maclen %u != %u", |
| tst_name, sndid, rcvid, |
| dump_key->maclen, key->maclen); |
| passed_test = false; |
| continue; |
| } |
| } |
| |
| if (passed_test) |
| test_ok("%s: The socket keys are consistent with the expectations", |
| tst_name); |
| out: |
| free(keys); |
| } |
| |
| static int start_server(const char *tst_name, unsigned int port, size_t quota, |
| struct tcp_ao_counters *begin, |
| unsigned int current_index, unsigned int rnext_index) |
| { |
| struct tcp_ao_counters lsk_c1, lsk_c2; |
| ssize_t bytes; |
| int sk, lsk; |
| |
| synchronize_threads(); /* 1: key collection initialized */ |
| lsk = key_collection_socket(true, port); |
| if (test_get_tcp_ao_counters(lsk, &lsk_c1)) |
| test_error("test_get_tcp_ao_counters()"); |
| synchronize_threads(); /* 2: MKTs added => connect() */ |
| if (test_wait_fd(lsk, TEST_TIMEOUT_SEC, 0)) |
| test_error("test_wait_fd()"); |
| |
| sk = accept(lsk, NULL, NULL); |
| if (sk < 0) |
| test_error("accept()"); |
| if (test_get_tcp_ao_counters(sk, begin)) |
| test_error("test_get_tcp_ao_counters()"); |
| |
| synchronize_threads(); /* 3: accepted => send data */ |
| if (test_get_tcp_ao_counters(lsk, &lsk_c2)) |
| test_error("test_get_tcp_ao_counters()"); |
| verify_keys(tst_name, lsk, true, true); |
| close(lsk); |
| |
| bytes = test_server_run(sk, quota, TEST_TIMEOUT_SEC); |
| if (bytes != quota) |
| test_fail("%s: server served: %zd", tst_name, bytes); |
| else |
| test_ok("%s: server alive", tst_name); |
| |
| verify_counters(tst_name, true, true, &lsk_c1, &lsk_c2); |
| |
| return sk; |
| } |
| |
| static void end_server(const char *tst_name, int sk, |
| struct tcp_ao_counters *begin) |
| { |
| struct tcp_ao_counters end; |
| |
| if (test_get_tcp_ao_counters(sk, &end)) |
| test_error("test_get_tcp_ao_counters()"); |
| verify_keys(tst_name, sk, false, true); |
| |
| synchronize_threads(); /* 4: verified => closed */ |
| close(sk); |
| |
| verify_counters(tst_name, false, true, begin, &end); |
| synchronize_threads(); /* 5: counters */ |
| } |
| |
| static void try_server_run(const char *tst_name, unsigned int port, size_t quota, |
| unsigned int current_index, unsigned int rnext_index) |
| { |
| struct tcp_ao_counters tmp; |
| int sk; |
| |
| sk = start_server(tst_name, port, quota, &tmp, |
| current_index, rnext_index); |
| end_server(tst_name, sk, &tmp); |
| } |
| |
| static void server_rotations(const char *tst_name, unsigned int port, |
| size_t quota, unsigned int rotations, |
| unsigned int current_index, unsigned int rnext_index) |
| { |
| struct tcp_ao_counters tmp; |
| unsigned int i; |
| int sk; |
| |
| sk = start_server(tst_name, port, quota, &tmp, |
| current_index, rnext_index); |
| |
| for (i = current_index + 1; rotations > 0; i++, rotations--) { |
| ssize_t bytes; |
| |
| if (i >= collection.nr_keys) |
| i = 0; |
| bytes = test_server_run(sk, quota, TEST_TIMEOUT_SEC); |
| if (bytes != quota) { |
| test_fail("%s: server served: %zd", tst_name, bytes); |
| return; |
| } |
| verify_current_rnext(tst_name, sk, |
| collection.keys[i].server_keyid, -1); |
| synchronize_threads(); /* verify current/rnext */ |
| } |
| end_server(tst_name, sk, &tmp); |
| } |
| |
| static int run_client(const char *tst_name, unsigned int port, |
| unsigned int nr_keys, int current_index, int rnext_index, |
| struct tcp_ao_counters *before, |
| const size_t msg_sz, const size_t msg_nr) |
| { |
| int sk; |
| |
| synchronize_threads(); /* 1: key collection initialized */ |
| sk = key_collection_socket(false, port); |
| |
| if (current_index >= 0 || rnext_index >= 0) { |
| int sndid = -1, rcvid = -1; |
| |
| if (current_index >= 0) |
| sndid = collection.keys[current_index].client_keyid; |
| if (rnext_index >= 0) |
| rcvid = collection.keys[rnext_index].server_keyid; |
| if (test_set_key(sk, sndid, rcvid)) |
| test_error("failed to set current/rnext keys"); |
| } |
| if (before && test_get_tcp_ao_counters(sk, before)) |
| test_error("test_get_tcp_ao_counters()"); |
| |
| synchronize_threads(); /* 2: MKTs added => connect() */ |
| if (test_connect_socket(sk, this_ip_dest, port++) <= 0) |
| test_error("failed to connect()"); |
| if (current_index < 0) |
| current_index = nr_keys - 1; |
| if (rnext_index < 0) |
| rnext_index = nr_keys - 1; |
| collection.keys[current_index].used_on_client_tx = 1; |
| collection.keys[rnext_index].used_on_server_tx = 1; |
| |
| synchronize_threads(); /* 3: accepted => send data */ |
| if (test_client_verify(sk, msg_sz, msg_nr, TEST_TIMEOUT_SEC)) { |
| test_fail("verify failed"); |
| close(sk); |
| if (before) |
| test_tcp_ao_counters_free(before); |
| return -1; |
| } |
| |
| return sk; |
| } |
| |
| static int start_client(const char *tst_name, unsigned int port, |
| unsigned int nr_keys, int current_index, int rnext_index, |
| struct tcp_ao_counters *before, |
| const size_t msg_sz, const size_t msg_nr) |
| { |
| if (init_default_key_collection(nr_keys, true)) |
| test_error("Failed to init the key collection"); |
| |
| return run_client(tst_name, port, nr_keys, current_index, |
| rnext_index, before, msg_sz, msg_nr); |
| } |
| |
| static void end_client(const char *tst_name, int sk, unsigned int nr_keys, |
| int current_index, int rnext_index, |
| struct tcp_ao_counters *start) |
| { |
| struct tcp_ao_counters end; |
| |
| /* Some application may become dependent on this kernel choice */ |
| if (current_index < 0) |
| current_index = nr_keys - 1; |
| if (rnext_index < 0) |
| rnext_index = nr_keys - 1; |
| verify_current_rnext(tst_name, sk, |
| collection.keys[current_index].client_keyid, |
| collection.keys[rnext_index].server_keyid); |
| if (start && test_get_tcp_ao_counters(sk, &end)) |
| test_error("test_get_tcp_ao_counters()"); |
| verify_keys(tst_name, sk, false, false); |
| synchronize_threads(); /* 4: verify => closed */ |
| close(sk); |
| if (start) |
| verify_counters(tst_name, false, false, start, &end); |
| synchronize_threads(); /* 5: counters */ |
| } |
| |
| static void try_unmatched_keys(int sk, int *rnext_index) |
| { |
| struct test_key *key; |
| unsigned int i = 0; |
| int err; |
| |
| do { |
| key = &collection.keys[i]; |
| if (!key->matches_server) |
| break; |
| } while (++i < collection.nr_keys); |
| if (key->matches_server) |
| test_error("all keys on client match the server"); |
| |
| err = test_add_key_cr(sk, key->password, key->len, wrong_addr, |
| 0, key->client_keyid, key->server_keyid, |
| key->maclen, key->alg, 0, 0); |
| if (!err) { |
| test_fail("Added a key with non-matching ip-address for established sk"); |
| return; |
| } |
| if (err == -EINVAL) |
| test_ok("Can't add a key with non-matching ip-address for established sk"); |
| else |
| test_error("Failed to add a key"); |
| |
| err = test_add_key_cr(sk, key->password, key->len, this_ip_dest, |
| test_vrf_ifindex, |
| key->client_keyid, key->server_keyid, |
| key->maclen, key->alg, 0, 0); |
| if (!err) { |
| test_fail("Added a key with non-matching VRF for established sk"); |
| return; |
| } |
| if (err == -EINVAL) |
| test_ok("Can't add a key with non-matching VRF for established sk"); |
| else |
| test_error("Failed to add a key"); |
| |
| for (i = 0; i < collection.nr_keys; i++) { |
| key = &collection.keys[i]; |
| if (!key->matches_client) |
| break; |
| } |
| if (key->matches_client) |
| test_error("all keys on server match the client"); |
| if (test_set_key(sk, -1, key->server_keyid)) |
| test_error("Can't change the current key"); |
| if (test_client_verify(sk, msg_len, nr_packets, TEST_TIMEOUT_SEC)) |
| test_fail("verify failed"); |
| *rnext_index = i; |
| } |
| |
| static int client_non_matching(const char *tst_name, unsigned int port, |
| unsigned int nr_keys, |
| int current_index, int rnext_index, |
| const size_t msg_sz, const size_t msg_nr) |
| { |
| unsigned int i; |
| |
| if (init_default_key_collection(nr_keys, true)) |
| test_error("Failed to init the key collection"); |
| |
| for (i = 0; i < nr_keys; i++) { |
| /* key (0, 0) matches */ |
| collection.keys[i].matches_client = !!((i + 3) % 4); |
| collection.keys[i].matches_server = !!((i + 2) % 4); |
| if (kernel_config_has(KCONFIG_NET_VRF)) |
| collection.keys[i].matches_vrf = !!((i + 1) % 4); |
| } |
| |
| return run_client(tst_name, port, nr_keys, current_index, |
| rnext_index, NULL, msg_sz, msg_nr); |
| } |
| |
| static void check_current_back(const char *tst_name, unsigned int port, |
| unsigned int nr_keys, |
| unsigned int current_index, unsigned int rnext_index, |
| unsigned int rotate_to_index) |
| { |
| struct tcp_ao_counters tmp; |
| int sk; |
| |
| sk = start_client(tst_name, port, nr_keys, current_index, rnext_index, |
| &tmp, msg_len, nr_packets); |
| if (sk < 0) |
| return; |
| if (test_set_key(sk, collection.keys[rotate_to_index].client_keyid, -1)) |
| test_error("Can't change the current key"); |
| if (test_client_verify(sk, msg_len, nr_packets, TEST_TIMEOUT_SEC)) |
| test_fail("verify failed"); |
| /* There is a race here: between setting the current_key with |
| * setsockopt(TCP_AO_INFO) and starting to send some data - there |
| * might have been a segment received with the desired |
| * RNext_key set. In turn that would mean that the first outgoing |
| * segment will have the desired current_key (flipped back). |
| * Which is what the user/test wants. As it's racy, skip checking |
| * the counters, yet check what are the resulting current/rnext |
| * keys on both sides. |
| */ |
| collection.keys[rotate_to_index].skip_counters_checks = 1; |
| |
| end_client(tst_name, sk, nr_keys, current_index, rnext_index, &tmp); |
| } |
| |
| static void roll_over_keys(const char *tst_name, unsigned int port, |
| unsigned int nr_keys, unsigned int rotations, |
| unsigned int current_index, unsigned int rnext_index) |
| { |
| struct tcp_ao_counters tmp; |
| unsigned int i; |
| int sk; |
| |
| sk = start_client(tst_name, port, nr_keys, current_index, rnext_index, |
| &tmp, msg_len, nr_packets); |
| if (sk < 0) |
| return; |
| for (i = rnext_index + 1; rotations > 0; i++, rotations--) { |
| if (i >= collection.nr_keys) |
| i = 0; |
| if (test_set_key(sk, -1, collection.keys[i].server_keyid)) |
| test_error("Can't change the Rnext key"); |
| if (test_client_verify(sk, msg_len, nr_packets, TEST_TIMEOUT_SEC)) { |
| test_fail("verify failed"); |
| close(sk); |
| test_tcp_ao_counters_free(&tmp); |
| return; |
| } |
| verify_current_rnext(tst_name, sk, -1, |
| collection.keys[i].server_keyid); |
| collection.keys[i].used_on_server_tx = 1; |
| synchronize_threads(); /* verify current/rnext */ |
| } |
| end_client(tst_name, sk, nr_keys, current_index, rnext_index, &tmp); |
| } |
| |
| static void try_client_run(const char *tst_name, unsigned int port, |
| unsigned int nr_keys, int current_index, int rnext_index) |
| { |
| struct tcp_ao_counters tmp; |
| int sk; |
| |
| sk = start_client(tst_name, port, nr_keys, current_index, rnext_index, |
| &tmp, msg_len, nr_packets); |
| if (sk < 0) |
| return; |
| end_client(tst_name, sk, nr_keys, current_index, rnext_index, &tmp); |
| } |
| |
| static void try_client_match(const char *tst_name, unsigned int port, |
| unsigned int nr_keys, |
| int current_index, int rnext_index) |
| { |
| int sk; |
| |
| sk = client_non_matching(tst_name, port, nr_keys, current_index, |
| rnext_index, msg_len, nr_packets); |
| if (sk < 0) |
| return; |
| try_unmatched_keys(sk, &rnext_index); |
| end_client(tst_name, sk, nr_keys, current_index, rnext_index, NULL); |
| } |
| |
| static void *server_fn(void *arg) |
| { |
| unsigned int port = test_server_port; |
| |
| setup_vrfs(); |
| try_server_run("server: Check current/rnext keys unset before connect()", |
| port++, quota, 19, 19); |
| try_server_run("server: Check current/rnext keys set before connect()", |
| port++, quota, 10, 10); |
| try_server_run("server: Check current != rnext keys set before connect()", |
| port++, quota, 5, 10); |
| try_server_run("server: Check current flapping back on peer's RnextKey request", |
| port++, quota * 2, 5, 10); |
| server_rotations("server: Rotate over all different keys", port++, |
| quota, 20, 0, 0); |
| try_server_run("server: Check accept() => established key matching", |
| port++, quota * 2, 0, 0); |
| |
| synchronize_threads(); /* don't race to exit: client exits */ |
| return NULL; |
| } |
| |
| static void check_established_socket(void) |
| { |
| unsigned int port = test_server_port; |
| |
| setup_vrfs(); |
| try_client_run("client: Check current/rnext keys unset before connect()", |
| port++, 20, -1, -1); |
| try_client_run("client: Check current/rnext keys set before connect()", |
| port++, 20, 10, 10); |
| try_client_run("client: Check current != rnext keys set before connect()", |
| port++, 20, 10, 5); |
| check_current_back("client: Check current flapping back on peer's RnextKey request", |
| port++, 20, 10, 5, 2); |
| roll_over_keys("client: Rotate over all different keys", port++, |
| 20, 20, 0, 0); |
| try_client_match("client: Check connect() => established key matching", |
| port++, 20, 0, 0); |
| } |
| |
| static void *client_fn(void *arg) |
| { |
| if (inet_pton(TEST_FAMILY, TEST_WRONG_IP, &wrong_addr) != 1) |
| test_error("Can't convert ip address %s", TEST_WRONG_IP); |
| check_closed_socket(); |
| check_listen_socket(); |
| check_established_socket(); |
| return NULL; |
| } |
| |
| int main(int argc, char *argv[]) |
| { |
| test_init(120, server_fn, client_fn); |
| return 0; |
| } |