| // 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 <fcntl.h> |
| #include <linux/sockios.h> |
| #include <sys/ioctl.h> |
| #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); |
| } |