@@ -5,6 +5,7 @@ TEST_BOTH_AF += connect-deny
TEST_BOTH_AF += icmps-accept icmps-discard
TEST_BOTH_AF += restore
TEST_BOTH_AF += rst
+TEST_BOTH_AF += self-connect
TEST_BOTH_AF += seq-ext
TEST_BOTH_AF += setsockopt-closed
TEST_BOTH_AF += unsigned-md5
new file mode 100644
@@ -0,0 +1,197 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Author: Dmitry Safonov <dima@arista.com> */
+#include <inttypes.h>
+#include "aolib.h"
+
+static union tcp_addr local_addr;
+
+static void __setup_lo_intf(const char *lo_intf,
+ const char *addr_str, uint8_t prefix)
+{
+ if (inet_pton(TEST_FAMILY, addr_str, &local_addr) != 1)
+ test_error("Can't convert local ip address");
+
+ if (ip_addr_add(lo_intf, TEST_FAMILY, local_addr, prefix))
+ test_error("Failed to add %s ip address", lo_intf);
+
+ if (link_set_up(lo_intf))
+ test_error("Failed to bring %s up", lo_intf);
+}
+
+static void setup_lo_intf(const char *lo_intf)
+{
+#ifdef IPV6_TEST
+ __setup_lo_intf(lo_intf, "::1", 128);
+#else
+ __setup_lo_intf(lo_intf, "127.0.0.1", 8);
+#endif
+}
+
+static void tcp_self_connect(const char *tst, unsigned int port,
+ bool different_keyids, bool check_restore)
+{
+ uint64_t before_challenge_ack, after_challenge_ack;
+ uint64_t before_syn_challenge, after_syn_challenge;
+ struct tcp_ao_counters before_ao, after_ao;
+ uint64_t before_aogood, after_aogood;
+ struct netstat *ns_before, *ns_after;
+ const size_t nr_packets = 20;
+ struct tcp_ao_repair ao_img;
+ struct tcp_sock_state img;
+ sockaddr_af addr;
+ int sk;
+
+ tcp_addr_to_sockaddr_in(&addr, &local_addr, htons(port));
+
+ sk = socket(test_family, SOCK_STREAM, IPPROTO_TCP);
+ if (sk < 0)
+ test_error("socket()");
+
+ if (different_keyids) {
+ if (test_add_key(sk, DEFAULT_TEST_PASSWORD, local_addr, -1, 5, 7))
+ test_error("setsockopt(TCP_AO_ADD_KEY)");
+ if (test_add_key(sk, DEFAULT_TEST_PASSWORD, local_addr, -1, 7, 5))
+ test_error("setsockopt(TCP_AO_ADD_KEY)");
+ } else {
+ if (test_add_key(sk, DEFAULT_TEST_PASSWORD, local_addr, -1, 100, 100))
+ test_error("setsockopt(TCP_AO_ADD_KEY)");
+ }
+
+ if (bind(sk, (struct sockaddr *)&addr, sizeof(addr)) < 0)
+ test_error("bind()");
+
+ ns_before = netstat_read();
+ before_aogood = netstat_get(ns_before, "TCPAOGood", NULL);
+ before_challenge_ack = netstat_get(ns_before, "TCPChallengeACK", NULL);
+ before_syn_challenge = netstat_get(ns_before, "TCPSYNChallenge", NULL);
+ if (test_get_tcp_ao_counters(sk, &before_ao))
+ test_error("test_get_tcp_ao_counters()");
+
+ if (__test_connect_socket(sk, "lo", (struct sockaddr *)&addr,
+ sizeof(addr), TEST_TIMEOUT_SEC) < 0) {
+ ns_after = netstat_read();
+ netstat_print_diff(ns_before, ns_after);
+ test_error("failed to connect()");
+ }
+
+ if (test_client_verify(sk, 100, nr_packets, TEST_TIMEOUT_SEC)) {
+ test_fail("%s: tcp connection verify failed", tst);
+ close(sk);
+ return;
+ }
+
+ ns_after = netstat_read();
+ after_aogood = netstat_get(ns_after, "TCPAOGood", NULL);
+ after_challenge_ack = netstat_get(ns_after, "TCPChallengeACK", NULL);
+ after_syn_challenge = netstat_get(ns_after, "TCPSYNChallenge", NULL);
+ if (test_get_tcp_ao_counters(sk, &after_ao))
+ test_error("test_get_tcp_ao_counters()");
+ if (!check_restore) {
+ /* to debug: netstat_print_diff(ns_before, ns_after); */
+ netstat_free(ns_before);
+ }
+ netstat_free(ns_after);
+
+ if (after_aogood <= before_aogood) {
+ test_fail("%s: TCPAOGood counter mismatch: %zu <= %zu",
+ tst, after_aogood, before_aogood);
+ close(sk);
+ return;
+ }
+ if (after_challenge_ack <= before_challenge_ack ||
+ after_syn_challenge <= before_syn_challenge) {
+ /*
+ * It's also meant to test simultaneous open, so check
+ * these counters as well.
+ */
+ test_fail("%s: Didn't challenge SYN or ACK: %zu <= %zu OR %zu <= %zu",
+ tst, after_challenge_ack, before_challenge_ack,
+ after_syn_challenge, before_syn_challenge);
+ close(sk);
+ return;
+ }
+
+ if (test_tcp_ao_counters_cmp(tst, &before_ao, &after_ao, TEST_CNT_GOOD)) {
+ close(sk);
+ return;
+ }
+
+ if (!check_restore) {
+ test_ok("%s: connect TCPAOGood %" PRIu64 " => %" PRIu64,
+ tst, before_aogood, after_aogood);
+ close(sk);
+ return;
+ }
+
+ test_enable_repair(sk);
+ test_sock_checkpoint(sk, &img, &addr);
+#ifdef IPV6_TEST
+ addr.sin6_port = htons(port + 1);
+#else
+ addr.sin_port = htons(port + 1);
+#endif
+ test_ao_checkpoint(sk, &ao_img);
+ test_kill_sk(sk);
+
+ sk = socket(test_family, SOCK_STREAM, IPPROTO_TCP);
+ if (sk < 0)
+ test_error("socket()");
+
+ test_enable_repair(sk);
+ __test_sock_restore(sk, "lo", &img, &addr, &addr, sizeof(addr));
+ if (different_keyids) {
+ if (test_add_repaired_key(sk, DEFAULT_TEST_PASSWORD, 0,
+ local_addr, -1, 7, 5))
+ test_error("setsockopt(TCP_AO_ADD_KEY)");
+ if (test_add_repaired_key(sk, DEFAULT_TEST_PASSWORD, 0,
+ local_addr, -1, 5, 7))
+ test_error("setsockopt(TCP_AO_ADD_KEY)");
+ } else {
+ if (test_add_repaired_key(sk, DEFAULT_TEST_PASSWORD, 0,
+ local_addr, -1, 100, 100))
+ test_error("setsockopt(TCP_AO_ADD_KEY)");
+ }
+ test_ao_restore(sk, &ao_img);
+ test_disable_repair(sk);
+ test_sock_state_free(&img);
+ if (test_client_verify(sk, 100, nr_packets, TEST_TIMEOUT_SEC)) {
+ test_fail("%s: tcp connection verify failed", tst);
+ close(sk);
+ return;
+ }
+ ns_after = netstat_read();
+ after_aogood = netstat_get(ns_after, "TCPAOGood", NULL);
+ /* to debug: netstat_print_diff(ns_before, ns_after); */
+ netstat_free(ns_before);
+ netstat_free(ns_after);
+ close(sk);
+ if (after_aogood <= before_aogood) {
+ test_fail("%s: TCPAOGood counter mismatch: %zu <= %zu",
+ tst, after_aogood, before_aogood);
+ return;
+ }
+ test_ok("%s: connect TCPAOGood %" PRIu64 " => %" PRIu64,
+ tst, before_aogood, after_aogood);
+}
+
+static void *client_fn(void *arg)
+{
+ unsigned int port = test_server_port;
+
+ setup_lo_intf("lo");
+
+ tcp_self_connect("self-connect(same keyids)", port++, false, false);
+ tcp_self_connect("self-connect(different keyids)", port++, true, false);
+ tcp_self_connect("self-connect(restore)", port, false, true);
+ port += 2;
+ tcp_self_connect("self-connect(restore, different keyids)", port, true, true);
+ port += 2;
+
+ return NULL;
+}
+
+int main(int argc, char *argv[])
+{
+ test_init(4, client_fn, NULL);
+ return 0;
+}