// SPDX-License-Identifier: GPL-2.0 /* This is over-simplified TCP_REPAIR for TCP_ESTABLISHED sockets * It tests that TCP-AO enabled connection can be restored. * For the proper socket repair see: * https://github.com/checkpoint-restore/criu/blob/criu-dev/soccr/soccr.h */ #include #include #include #include "aolib.h" #ifndef TCPOPT_MAXSEG # define TCPOPT_MAXSEG 2 #endif #ifndef TCPOPT_WINDOW # define TCPOPT_WINDOW 3 #endif #ifndef TCPOPT_SACK_PERMITTED # define TCPOPT_SACK_PERMITTED 4 #endif #ifndef TCPOPT_TIMESTAMP # define TCPOPT_TIMESTAMP 8 #endif enum { TCP_ESTABLISHED = 1, TCP_SYN_SENT, TCP_SYN_RECV, TCP_FIN_WAIT1, TCP_FIN_WAIT2, TCP_TIME_WAIT, TCP_CLOSE, TCP_CLOSE_WAIT, TCP_LAST_ACK, TCP_LISTEN, TCP_CLOSING, /* Now a valid state */ TCP_NEW_SYN_RECV, TCP_MAX_STATES /* Leave at the end! */ }; static void test_sock_checkpoint_queue(int sk, int queue, int qlen, struct tcp_sock_queue *q) { socklen_t len; int ret; if (setsockopt(sk, SOL_TCP, TCP_REPAIR_QUEUE, &queue, sizeof(queue))) test_error("setsockopt(TCP_REPAIR_QUEUE)"); len = sizeof(q->seq); ret = getsockopt(sk, SOL_TCP, TCP_QUEUE_SEQ, &q->seq, &len); if (ret || len != sizeof(q->seq)) test_error("getsockopt(TCP_QUEUE_SEQ): %d", (int)len); if (!qlen) { q->buf = NULL; return; } q->buf = malloc(qlen); if (q->buf == NULL) test_error("malloc()"); ret = recv(sk, q->buf, qlen, MSG_PEEK | MSG_DONTWAIT); if (ret != qlen) test_error("recv(%d): %d", qlen, ret); } void __test_sock_checkpoint(int sk, struct tcp_sock_state *state, void *addr, size_t addr_size) { socklen_t len = sizeof(state->info); int ret; memset(state, 0, sizeof(*state)); ret = getsockopt(sk, SOL_TCP, TCP_INFO, &state->info, &len); if (ret || len != sizeof(state->info)) test_error("getsockopt(TCP_INFO): %d", (int)len); len = addr_size; if (getsockname(sk, addr, &len) || len != addr_size) test_error("getsockname(): %d", (int)len); len = sizeof(state->trw); ret = getsockopt(sk, SOL_TCP, TCP_REPAIR_WINDOW, &state->trw, &len); if (ret || len != sizeof(state->trw)) test_error("getsockopt(TCP_REPAIR_WINDOW): %d", (int)len); if (ioctl(sk, SIOCOUTQ, &state->outq_len)) test_error("ioctl(SIOCOUTQ)"); if (ioctl(sk, SIOCOUTQNSD, &state->outq_nsd_len)) test_error("ioctl(SIOCOUTQNSD)"); test_sock_checkpoint_queue(sk, TCP_SEND_QUEUE, state->outq_len, &state->out); if (ioctl(sk, SIOCINQ, &state->inq_len)) test_error("ioctl(SIOCINQ)"); test_sock_checkpoint_queue(sk, TCP_RECV_QUEUE, state->inq_len, &state->in); if (state->info.tcpi_state == TCP_CLOSE) state->outq_len = state->outq_nsd_len = 0; len = sizeof(state->mss); ret = getsockopt(sk, SOL_TCP, TCP_MAXSEG, &state->mss, &len); if (ret || len != sizeof(state->mss)) test_error("getsockopt(TCP_MAXSEG): %d", (int)len); len = sizeof(state->timestamp); ret = getsockopt(sk, SOL_TCP, TCP_TIMESTAMP, &state->timestamp, &len); if (ret || len != sizeof(state->timestamp)) test_error("getsockopt(TCP_TIMESTAMP): %d", (int)len); } void test_ao_checkpoint(int sk, struct tcp_ao_repair *state) { socklen_t len = sizeof(*state); int ret; memset(state, 0, sizeof(*state)); ret = getsockopt(sk, SOL_TCP, TCP_AO_REPAIR, state, &len); if (ret || len != sizeof(*state)) test_error("getsockopt(TCP_AO_REPAIR): %d", (int)len); } static void test_sock_restore_seq(int sk, int queue, uint32_t seq) { if (setsockopt(sk, SOL_TCP, TCP_REPAIR_QUEUE, &queue, sizeof(queue))) test_error("setsockopt(TCP_REPAIR_QUEUE)"); if (setsockopt(sk, SOL_TCP, TCP_QUEUE_SEQ, &seq, sizeof(seq))) test_error("setsockopt(TCP_QUEUE_SEQ)"); } static void test_sock_restore_queue(int sk, int queue, void *buf, int len) { int chunk = len; size_t off = 0; if (len == 0) return; if (setsockopt(sk, SOL_TCP, TCP_REPAIR_QUEUE, &queue, sizeof(queue))) test_error("setsockopt(TCP_REPAIR_QUEUE)"); do { int ret; ret = send(sk, buf + off, chunk, 0); if (ret <= 0) { if (chunk > 1024) { chunk >>= 1; continue; } test_error("send()"); } off += ret; len -= ret; } while (len > 0); } void __test_sock_restore(int sk, const char *device, struct tcp_sock_state *state, void *saddr, void *daddr, size_t addr_size) { struct tcp_repair_opt opts[4]; unsigned int opt_nr = 0; long flags; if (bind(sk, saddr, addr_size)) test_error("bind()"); flags = fcntl(sk, F_GETFL); if ((flags < 0) || (fcntl(sk, F_SETFL, flags | O_NONBLOCK) < 0)) test_error("fcntl()"); test_sock_restore_seq(sk, TCP_RECV_QUEUE, state->in.seq - state->inq_len); test_sock_restore_seq(sk, TCP_SEND_QUEUE, state->out.seq - state->outq_len); if (device != NULL && setsockopt(sk, SOL_SOCKET, SO_BINDTODEVICE, device, strlen(device) + 1)) test_error("setsockopt(SO_BINDTODEVICE, %s)", device); if (connect(sk, daddr, addr_size)) test_error("connect()"); if (state->info.tcpi_options & TCPI_OPT_SACK) { opts[opt_nr].opt_code = TCPOPT_SACK_PERMITTED; opts[opt_nr].opt_val = 0; opt_nr++; } if (state->info.tcpi_options & TCPI_OPT_WSCALE) { opts[opt_nr].opt_code = TCPOPT_WINDOW; opts[opt_nr].opt_val = state->info.tcpi_snd_wscale + (state->info.tcpi_rcv_wscale << 16); opt_nr++; } if (state->info.tcpi_options & TCPI_OPT_TIMESTAMPS) { opts[opt_nr].opt_code = TCPOPT_TIMESTAMP; opts[opt_nr].opt_val = 0; opt_nr++; } opts[opt_nr].opt_code = TCPOPT_MAXSEG; opts[opt_nr].opt_val = state->mss; opt_nr++; if (setsockopt(sk, SOL_TCP, TCP_REPAIR_OPTIONS, opts, opt_nr * sizeof(opts[0]))) test_error("setsockopt(TCP_REPAIR_OPTIONS)"); if (state->info.tcpi_options & TCPI_OPT_TIMESTAMPS) { if (setsockopt(sk, SOL_TCP, TCP_TIMESTAMP, &state->timestamp, opt_nr * sizeof(opts[0]))) test_error("setsockopt(TCP_TIMESTAMP)"); } test_sock_restore_queue(sk, TCP_RECV_QUEUE, state->in.buf, state->inq_len); test_sock_restore_queue(sk, TCP_SEND_QUEUE, state->out.buf, state->outq_len); if (setsockopt(sk, SOL_TCP, TCP_REPAIR_WINDOW, &state->trw, sizeof(state->trw))) test_error("setsockopt(TCP_REPAIR_WINDOW)"); } void test_ao_restore(int sk, struct tcp_ao_repair *state) { if (setsockopt(sk, SOL_TCP, TCP_AO_REPAIR, state, sizeof(*state))) test_error("setsockopt(TCP_AO_REPAIR)"); } void test_sock_state_free(struct tcp_sock_state *state) { free(state->out.buf); free(state->in.buf); } void test_enable_repair(int sk) { int val = TCP_REPAIR_ON; if (setsockopt(sk, SOL_TCP, TCP_REPAIR, &val, sizeof(val))) test_error("setsockopt(TCP_REPAIR)"); } void test_disable_repair(int sk) { int val = TCP_REPAIR_OFF_NO_WP; if (setsockopt(sk, SOL_TCP, TCP_REPAIR, &val, sizeof(val))) test_error("setsockopt(TCP_REPAIR)"); } void test_kill_sk(int sk) { test_enable_repair(sk); close(sk); }