diff options
Diffstat (limited to 'doc')
-rw-r--r-- | doc/migration/.gitignore | 2 | ||||
-rw-r--r-- | doc/migration/Makefile | 20 | ||||
-rw-r--r-- | doc/migration/README | 51 | ||||
-rw-r--r-- | doc/migration/source.c | 92 | ||||
-rw-r--r-- | doc/migration/target.c | 102 | ||||
-rw-r--r-- | doc/platform-requirements/.gitignore | 4 | ||||
-rw-r--r-- | doc/platform-requirements/Makefile | 45 | ||||
-rw-r--r-- | doc/platform-requirements/README | 18 | ||||
-rw-r--r-- | doc/platform-requirements/common.c | 66 | ||||
-rw-r--r-- | doc/platform-requirements/common.h | 48 | ||||
-rw-r--r-- | doc/platform-requirements/listen-vs-repair.c | 128 | ||||
-rw-r--r-- | doc/platform-requirements/recv-zero.c | 118 | ||||
-rw-r--r-- | doc/platform-requirements/reuseaddr-priority.c | 240 | ||||
-rw-r--r-- | doc/platform-requirements/udp-close-dup.c | 105 |
14 files changed, 1039 insertions, 0 deletions
diff --git a/doc/migration/.gitignore b/doc/migration/.gitignore new file mode 100644 index 0000000..59cb765 --- /dev/null +++ b/doc/migration/.gitignore @@ -0,0 +1,2 @@ +/source +/target diff --git a/doc/migration/Makefile b/doc/migration/Makefile new file mode 100644 index 0000000..04f6891 --- /dev/null +++ b/doc/migration/Makefile @@ -0,0 +1,20 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# PASST - Plug A Simple Socket Transport +# for qemu/UNIX domain socket mode +# +# PASTA - Pack A Subtle Tap Abstraction +# for network namespace/tap device mode +# +# Copyright (c) 2025 Red Hat GmbH +# Author: Stefano Brivio <sbrivio@redhat.com> + +TARGETS = source target +CFLAGS = -Wall -Wextra -pedantic + +all: $(TARGETS) + +$(TARGETS): %: %.c + +clean: + rm -f $(TARGETS) diff --git a/doc/migration/README b/doc/migration/README new file mode 100644 index 0000000..375603b --- /dev/null +++ b/doc/migration/README @@ -0,0 +1,51 @@ +<!--- +SPDX-License-Identifier: GPL-2.0-or-later +Copyright (c) 2025 Red Hat GmbH +Author: Stefano Brivio <sbrivio@redhat.com> +--> + +Migration +========= + +These test programs show a migration of a TCP connection from one process to +another using the TCP_REPAIR socket option. + +The two processes are a mock of the matching implementation in passt(1), and run +unprivileged, so they rely on the passt-repair helper to connect to them and set +or clear TCP_REPAIR on the connection socket, transferred to the helper using +SCM_RIGHTS. + +The passt-repair helper needs to have the CAP_NET_ADMIN capability, or run as +root. + +Example of usage +---------------- + +* Start the test server + + $ nc -l 9999 + +* Start the source side of the TCP client (mock of the source instance of passt) + + $ ./source 127.0.0.1 9999 9998 /tmp/repair.sock + +* The client sends a test string, and waits for a connection from passt-repair + + # passt-repair /tmp/repair.sock + +* The socket is now in repair mode, and `source` dumps sequences, then exits + + sending sequence: 3244673313 + receiving sequence: 2250449386 + +* Continue the connection on the target side, restarting from those sequences + + $ ./target 127.0.0.1 9999 9998 /tmp/repair.sock 3244673313 2250449386 + +* The target side now waits for a connection from passt-repair + + # passt-repair /tmp/repair.sock + +* The target side asks passt-repair to switch the socket to repair mode, sets up + the TCP sequences, then asks passt-repair to clear repair mode, and sends a + test string to the server diff --git a/doc/migration/source.c b/doc/migration/source.c new file mode 100644 index 0000000..d44ebf1 --- /dev/null +++ b/doc/migration/source.c @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +/* PASST - Plug A Simple Socket Transport + * for qemu/UNIX domain socket mode + * + * PASTA - Pack A Subtle Tap Abstraction + * for network namespace/tap device mode + * + * doc/migration/source.c - Mock of TCP migration source, use with passt-repair + * + * Copyright (c) 2025 Red Hat GmbH + * Author: Stefano Brivio <sbrivio@redhat.com> + */ + +#include <arpa/inet.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <limits.h> +#include <unistd.h> +#include <netdb.h> +#include <netinet/tcp.h> + +int main(int argc, char **argv) +{ + struct sockaddr_in a = { AF_INET, htons(atoi(argv[3])), { 0 }, { 0 } }; + struct addrinfo hints = { 0, AF_UNSPEC, SOCK_STREAM, 0, 0, + NULL, NULL, NULL }; + struct sockaddr_un a_helper = { AF_UNIX, { 0 } }; + int seq, s, s_helper; + int8_t cmd; + struct iovec iov = { &cmd, sizeof(cmd) }; + char buf[CMSG_SPACE(sizeof(int))]; + struct msghdr msg = { NULL, 0, &iov, 1, buf, sizeof(buf), 0 }; + struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); + socklen_t seqlen = sizeof(int); + struct addrinfo *r; + + (void)argc; + + if (argc != 5) { + fprintf(stderr, "%s DST_ADDR DST_PORT SRC_PORT HELPER_PATH\n", + argv[0]); + return -1; + } + + strcpy(a_helper.sun_path, argv[4]); + getaddrinfo(argv[1], argv[2], &hints, &r); + + /* Connect socket to server and send some data */ + s = socket(r->ai_family, SOCK_STREAM, IPPROTO_TCP); + setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &((int){ 1 }), sizeof(int)); + bind(s, (struct sockaddr *)&a, sizeof(a)); + connect(s, r->ai_addr, r->ai_addrlen); + send(s, "before migration\n", sizeof("before migration\n"), 0); + + /* Wait for helper */ + s_helper = socket(AF_UNIX, SOCK_STREAM, 0); + unlink(a_helper.sun_path); + bind(s_helper, (struct sockaddr *)&a_helper, sizeof(a_helper)); + listen(s_helper, 1); + s_helper = accept(s_helper, NULL, NULL); + + /* Set up message for helper, with socket */ + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(int)); + memcpy(CMSG_DATA(cmsg), &s, sizeof(s)); + + /* Send command to helper: turn repair mode on, wait for reply */ + cmd = TCP_REPAIR_ON; + sendmsg(s_helper, &msg, 0); + recv(s_helper, &((int8_t){ 0 }), 1, 0); + + /* Terminate helper */ + close(s_helper); + + /* Get sending sequence */ + seq = TCP_SEND_QUEUE; + setsockopt(s, SOL_TCP, TCP_REPAIR_QUEUE, &seq, sizeof(seq)); + getsockopt(s, SOL_TCP, TCP_QUEUE_SEQ, &seq, &seqlen); + fprintf(stdout, "%u ", seq); + + /* Get receiving sequence */ + seq = TCP_RECV_QUEUE; + setsockopt(s, SOL_TCP, TCP_REPAIR_QUEUE, &seq, sizeof(seq)); + getsockopt(s, SOL_TCP, TCP_QUEUE_SEQ, &seq, &seqlen); + fprintf(stdout, "%u\n", seq); +} diff --git a/doc/migration/target.c b/doc/migration/target.c new file mode 100644 index 0000000..f7d3108 --- /dev/null +++ b/doc/migration/target.c @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +/* PASST - Plug A Simple Socket Transport + * for qemu/UNIX domain socket mode + * + * PASTA - Pack A Subtle Tap Abstraction + * for network namespace/tap device mode + * + * doc/migration/target.c - Mock of TCP migration target, use with passt-repair + * + * Copyright (c) 2025 Red Hat GmbH + * Author: Stefano Brivio <sbrivio@redhat.com> + */ + +#include <arpa/inet.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <limits.h> +#include <unistd.h> +#include <netdb.h> +#include <netinet/tcp.h> + +int main(int argc, char **argv) +{ + struct sockaddr_in a = { AF_INET, htons(atoi(argv[3])), { 0 }, { 0 } }; + struct addrinfo hints = { 0, AF_UNSPEC, SOCK_STREAM, 0, 0, + NULL, NULL, NULL }; + struct sockaddr_un a_helper = { AF_UNIX, { 0 } }; + int s, s_helper, seq; + int8_t cmd; + struct iovec iov = { &cmd, sizeof(cmd) }; + char buf[CMSG_SPACE(sizeof(int))]; + struct msghdr msg = { NULL, 0, &iov, 1, buf, sizeof(buf), 0 }; + struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); + struct addrinfo *r; + + (void)argc; + + strcpy(a_helper.sun_path, argv[4]); + getaddrinfo(argv[1], argv[2], &hints, &r); + + if (argc != 7) { + fprintf(stderr, + "%s DST_ADDR DST_PORT SRC_PORT HELPER_PATH SSEQ RSEQ\n", + argv[0]); + return -1; + } + + /* Prepare socket, bind to source port */ + s = socket(r->ai_family, SOCK_STREAM, IPPROTO_TCP); + setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &((int){ 1 }), sizeof(int)); + bind(s, (struct sockaddr *)&a, sizeof(a)); + + /* Wait for helper */ + s_helper = socket(AF_UNIX, SOCK_STREAM, 0); + unlink(a_helper.sun_path); + bind(s_helper, (struct sockaddr *)&a_helper, sizeof(a_helper)); + listen(s_helper, 1); + s_helper = accept(s_helper, NULL, NULL); + + /* Set up message for helper, with socket */ + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(int)); + memcpy(CMSG_DATA(cmsg), &s, sizeof(s)); + + /* Send command to helper: turn repair mode on, wait for reply */ + cmd = TCP_REPAIR_ON; + sendmsg(s_helper, &msg, 0); + recv(s_helper, &((int){ 0 }), 1, 0); + + /* Set sending sequence */ + seq = TCP_SEND_QUEUE; + setsockopt(s, SOL_TCP, TCP_REPAIR_QUEUE, &seq, sizeof(seq)); + seq = atoi(argv[5]); + setsockopt(s, SOL_TCP, TCP_QUEUE_SEQ, &seq, sizeof(seq)); + + /* Set receiving sequence */ + seq = TCP_RECV_QUEUE; + setsockopt(s, SOL_TCP, TCP_REPAIR_QUEUE, &seq, sizeof(seq)); + seq = atoi(argv[6]); + setsockopt(s, SOL_TCP, TCP_QUEUE_SEQ, &seq, sizeof(seq)); + + /* Connect setting kernel state only, without actual SYN / handshake */ + connect(s, r->ai_addr, r->ai_addrlen); + + /* Send command to helper: turn repair mode off, wait for reply */ + cmd = TCP_REPAIR_OFF; + sendmsg(s_helper, &msg, 0); + + recv(s_helper, &((int8_t){ 0 }), 1, 0); + + /* Terminate helper */ + close(s_helper); + + /* Send some more data */ + send(s, "after migration\n", sizeof("after migration\n"), 0); +} diff --git a/doc/platform-requirements/.gitignore b/doc/platform-requirements/.gitignore new file mode 100644 index 0000000..f6272cf --- /dev/null +++ b/doc/platform-requirements/.gitignore @@ -0,0 +1,4 @@ +/listen-vs-repair +/reuseaddr-priority +/recv-zero +/udp-close-dup diff --git a/doc/platform-requirements/Makefile b/doc/platform-requirements/Makefile new file mode 100644 index 0000000..83930ef --- /dev/null +++ b/doc/platform-requirements/Makefile @@ -0,0 +1,45 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright Red Hat +# Author: David Gibson <david@gibson.dropbear.id.au> + +TARGETS = reuseaddr-priority recv-zero udp-close-dup listen-vs-repair +SRCS = reuseaddr-priority.c recv-zero.c udp-close-dup.c listen-vs-repair.c +CFLAGS = -Wall + +all: cppcheck clang-tidy $(TARGETS:%=check-%) + +$(TARGETS): %: %.c common.c common.h + +check-%: % + ./$< + +cppcheck: + cppcheck --std=c11 --error-exitcode=1 --enable=all --force \ + --check-level=exhaustive --inline-suppr \ + --inconclusive --library=posix --quiet \ + --suppress=missingIncludeSystem \ + $(SRCS) + +clang-tidy: + clang-tidy --checks=*,\ + -altera-id-dependent-backward-branch,\ + -altera-unroll-loops,\ + -bugprone-easily-swappable-parameters,\ + -clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling,\ + -concurrency-mt-unsafe,\ + -cppcoreguidelines-avoid-non-const-global-variables,\ + -cppcoreguidelines-init-variables,\ + -cppcoreguidelines-macro-to-enum,\ + -google-readability-braces-around-statements,\ + -hicpp-braces-around-statements,\ + -llvmlibc-restrict-system-libc-headers,\ + -misc-include-cleaner,\ + -modernize-macro-to-enum,\ + -readability-braces-around-statements,\ + -readability-identifier-length,\ + -readability-isolate-declaration \ + $(SRCS) + +clean: + rm -f $(TARGETS) *.o *~ diff --git a/doc/platform-requirements/README b/doc/platform-requirements/README new file mode 100644 index 0000000..3914d22 --- /dev/null +++ b/doc/platform-requirements/README @@ -0,0 +1,18 @@ +Platform Requirements +===================== + +TODO: document the various Linux specific features we currently require + + +Test Programs +------------- + +In some places we rely on quite specific behaviour of sockets. +Although Linux, at least, seems to behave as required, It's not always +clear from the available documentation if this is required by POSIX or +some other specification. + +To specifically document those expectations this directory has some +test programs which explicitly check for the behaviour we need. +When/if we attempt a port to a new platform, running these to check +behaviour would be a good place to start. diff --git a/doc/platform-requirements/common.c b/doc/platform-requirements/common.c new file mode 100644 index 0000000..d687377 --- /dev/null +++ b/doc/platform-requirements/common.c @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +/* common.c + * + * Common helper functions for testing SO_REUSEADDR behaviour + * + * Copyright Red Hat + * Author: David Gibson <david@gibson.dropbear.id.au> + */ + +#include <errno.h> +#include <netinet/in.h> +#include <string.h> +#include <sys/socket.h> + +#include "common.h" + +int sock_reuseaddr(void) +{ + int y = 1; + int s; + + + s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (s < 0) + die("socket(): %s\n", strerror(errno)); + + if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &y, sizeof(y)) , 0) + die("SO_REUSEADDR: %s\n", strerror(errno)); + + return s; +} + +/* Send a token via the given connected socket */ +void send_token(int s, long token) +{ + ssize_t rc; + + rc = send(s, &token, sizeof(token), 0); + if (rc < 0) + die("send(): %s\n", strerror(errno)); + if (rc < sizeof(token)) + die("short send()\n"); +} + +/* Attempt to receive a token via the given socket. + * + * Returns true if we received the token, false if we got an EAGAIN, dies in any + * other case */ +bool recv_token(int s, long token) +{ + ssize_t rc; + long buf; + + rc = recv(s, &buf, sizeof(buf), MSG_DONTWAIT); + if (rc < 0) { + if (errno == EWOULDBLOCK) + return false; + die("recv(): %s\n", strerror(errno)); + } + if (rc < sizeof(buf)) + die("short recv()\n"); + if (buf != token) + die("data mismatch\n"); + return true; +} diff --git a/doc/platform-requirements/common.h b/doc/platform-requirements/common.h new file mode 100644 index 0000000..e85fc2b --- /dev/null +++ b/doc/platform-requirements/common.h @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +/* common.h + * + * Useful shared functions + * + * Copyright Red Hat + * Author: David Gibson <david@gibson.dropbear.id.au> + */ +#ifndef REUSEADDR_COMMON_H +#define REUSEADDR_COMMON_H + +#include <stdarg.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> + +__attribute__((format(printf, 1, 2), noreturn)) +static inline void die(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + (void)vfprintf(stderr, fmt, ap); + va_end(ap); + exit(EXIT_FAILURE); +} + +#if __BYTE_ORDER == __BIG_ENDIAN +#define htons_constant(x) (x) +#define htonl_constant(x) (x) +#else +#define htons_constant(x) (__bswap_constant_16(x)) +#define htonl_constant(x) (__bswap_constant_32(x)) +#endif + +#define SOCKADDR_INIT(addr, port) \ + { \ + .sin_family = AF_INET, \ + .sin_addr = { .s_addr = htonl_constant(addr) }, \ + .sin_port = htons_constant(port), \ + } + +int sock_reuseaddr(void); +void send_token(int s, long token); +bool recv_token(int s, long token); + +#endif /* REUSEADDR_COMMON_H */ diff --git a/doc/platform-requirements/listen-vs-repair.c b/doc/platform-requirements/listen-vs-repair.c new file mode 100644 index 0000000..d31fe3f --- /dev/null +++ b/doc/platform-requirements/listen-vs-repair.c @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +/* liste-vs-repair.c + * + * Do listening sockets have address conflicts with sockets under repair + * ==================================================================== + * + * When we accept() an incoming connection the accept()ed socket will have the + * same local address as the listening socket. This can be a complication on + * migration. On the migration target we've already set up listening sockets + * according to the command line. However to restore connections that we're + * migrating in we need to bind the new sockets to the same address, which would + * be an address conflict on the face of it. This test program verifies that + * enabling repair mode before bind() correctly suppresses that conflict. + * + * Copyright Red Hat + * Author: David Gibson <david@gibson.dropbear.id.au> + */ + +/* NOLINTNEXTLINE(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp) */ +#define _GNU_SOURCE + +#include <arpa/inet.h> +#include <errno.h> +#include <linux/netlink.h> +#include <linux/rtnetlink.h> +#include <net/if.h> +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <sched.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "common.h" + +#define PORT 13256U +#define CPORT 13257U + +/* 127.0.0.1:PORT */ +static const struct sockaddr_in addr = SOCKADDR_INIT(INADDR_LOOPBACK, PORT); + +/* 127.0.0.1:CPORT */ +static const struct sockaddr_in caddr = SOCKADDR_INIT(INADDR_LOOPBACK, CPORT); + +/* Put ourselves into a network sandbox */ +static void net_sandbox(void) +{ + /* NOLINTNEXTLINE(altera-struct-pack-align) */ + const struct req_t { + struct nlmsghdr nlh; + struct ifinfomsg ifm; + } __attribute__((packed)) req = { + .nlh.nlmsg_type = RTM_NEWLINK, + .nlh.nlmsg_flags = NLM_F_REQUEST, + .nlh.nlmsg_len = sizeof(req), + .nlh.nlmsg_seq = 1, + .ifm.ifi_family = AF_UNSPEC, + .ifm.ifi_index = 1, + .ifm.ifi_flags = IFF_UP, + .ifm.ifi_change = IFF_UP, + }; + int nl; + + if (unshare(CLONE_NEWUSER | CLONE_NEWNET)) + die("unshare(): %s\n", strerror(errno)); + + /* Bring up lo in the new netns */ + nl = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE); + if (nl < 0) + die("Can't create netlink socket: %s\n", strerror(errno)); + + if (send(nl, &req, sizeof(req), 0) < 0) + die("Netlink send(): %s\n", strerror(errno)); + close(nl); +} + +static void check(void) +{ + int s1, s2, op; + + s1 = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (s1 < 0) + die("socket() 1: %s\n", strerror(errno)); + + if (bind(s1, (struct sockaddr *)&addr, sizeof(addr))) + die("bind() 1: %s\n", strerror(errno)); + + if (listen(s1, 0)) + die("listen(): %s\n", strerror(errno)); + + s2 = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (s2 < 0) + die("socket() 2: %s\n", strerror(errno)); + + op = TCP_REPAIR_ON; + if (setsockopt(s2, SOL_TCP, TCP_REPAIR, &op, sizeof(op))) + die("TCP_REPAIR: %s\n", strerror(errno)); + + if (bind(s2, (struct sockaddr *)&addr, sizeof(addr))) + die("bind() 2: %s\n", strerror(errno)); + + if (connect(s2, (struct sockaddr *)&caddr, sizeof(caddr))) + die("connect(): %s\n", strerror(errno)); + + op = TCP_REPAIR_OFF_NO_WP; + if (setsockopt(s2, SOL_TCP, TCP_REPAIR, &op, sizeof(op))) + die("TCP_REPAIR: %s\n", strerror(errno)); + + close(s1); + close(s2); +} + +int main(int argc, char *argv[]) +{ + (void)argc; + (void)argv; + + net_sandbox(); + + check(); + + printf("Repair mode appears to properly suppress conflicts with listening sockets\n"); + + exit(0); +} diff --git a/doc/platform-requirements/recv-zero.c b/doc/platform-requirements/recv-zero.c new file mode 100644 index 0000000..2a2a561 --- /dev/null +++ b/doc/platform-requirements/recv-zero.c @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +/* recv-zero.c + * + * Verify that we're able to discard datagrams by recv()ing into a zero-length + * buffer. + * + * Copyright Red Hat + * Author: David Gibson <david@gibson.dropbear.id.au> + */ + +#include <arpa/inet.h> +#include <errno.h> +#include <net/if.h> +#include <netinet/in.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "common.h" + +#define DSTPORT 13257U + +enum discard_method { + DISCARD_NULL_BUF, + DISCARD_ZERO_IOV, + DISCARD_NULL_IOV, + NUM_METHODS, +}; + +/* 127.0.0.1:DSTPORT */ +static const struct sockaddr_in lo_dst = SOCKADDR_INIT(INADDR_LOOPBACK, DSTPORT); + +static void test_discard(enum discard_method method) +{ + struct iovec zero_iov = { .iov_base = NULL, .iov_len = 0, }; + struct msghdr mh_zero = { + .msg_iov = &zero_iov, + .msg_iovlen = 1, + }; + struct msghdr mh_null = { + .msg_iov = NULL, + .msg_iovlen = 0, + }; + long token1, token2; + int recv_s, send_s; + ssize_t rc; + + token1 = random(); + token2 = random(); + + recv_s = sock_reuseaddr(); + if (bind(recv_s, (struct sockaddr *)&lo_dst, sizeof(lo_dst)) < 0) + die("bind(): %s\n", strerror(errno)); + + send_s = sock_reuseaddr(); + if (connect(send_s, (struct sockaddr *)&lo_dst, sizeof(lo_dst)) < 0) + die("connect(): %s\n", strerror(errno)); + + send_token(send_s, token1); + send_token(send_s, token2); + + switch (method) { + case DISCARD_NULL_BUF: + /* cppcheck-suppress nullPointer */ + rc = recv(recv_s, NULL, 0, MSG_DONTWAIT); + if (rc < 0) + die("discarding recv(): %s\n", strerror(errno)); + break; + + case DISCARD_ZERO_IOV: + rc = recvmsg(recv_s, &mh_zero, MSG_DONTWAIT); + if (rc < 0) + die("recvmsg() with zero-length buffer: %s\n", + strerror(errno)); + if (!((unsigned)mh_zero.msg_flags & MSG_TRUNC)) + die("Missing MSG_TRUNC flag\n"); + break; + + case DISCARD_NULL_IOV: + rc = recvmsg(recv_s, &mh_null, MSG_DONTWAIT); + if (rc < 0) + die("recvmsg() with zero-length iov: %s\n", + strerror(errno)); + if (!((unsigned)mh_null.msg_flags & MSG_TRUNC)) + die("Missing MSG_TRUNC flag\n"); + break; + + default: + die("Bad method\n"); + } + + recv_token(recv_s, token2); + + /* cppcheck-suppress nullPointer */ + rc = recv(recv_s, NULL, 0, MSG_DONTWAIT); + if (rc < 0 && errno != EAGAIN) + die("redundant discarding recv(): %s\n", strerror(errno)); + if (rc >= 0) + die("Unexpected receive: rc=%zd\n", rc); +} + +int main(int argc, char *argv[]) +{ + enum discard_method method; + + (void)argc; + (void)argv; + + for (method = 0; method < NUM_METHODS; method++) + test_discard(method); + + printf("Discarding datagrams with 0-length receives seems to work\n"); + + exit(0); +} diff --git a/doc/platform-requirements/reuseaddr-priority.c b/doc/platform-requirements/reuseaddr-priority.c new file mode 100644 index 0000000..af39a39 --- /dev/null +++ b/doc/platform-requirements/reuseaddr-priority.c @@ -0,0 +1,240 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +/* reuseaddr-priority.c + * + * Verify which SO_REUSEADDR UDP sockets get priority to receive + * ============================================================= + * + * SO_REUSEADDR allows multiple sockets to bind to overlapping addresses, so + * there can be multiple sockets eligible to receive the same packet. The exact + * semantics of which socket will receive in this circumstance isn't very well + * documented. + * + * This program verifies that things behave the way we expect. Specifically we + * expect: + * + * - If both a connected and an unconnected socket could receive a datagram, the + * connected one will receive it in preference to the unconnected one. + * + * - If an unconnected socket bound to a specific address and an unconnected + * socket bound to the "any" address (0.0.0.0 or ::) could receive a datagram, + * then the one with a specific address will receive it in preference to the + * other. + * + * These should be true regardless of the order the sockets are created in, or + * the order they're polled in. + * + * Copyright Red Hat + * Author: David Gibson <david@gibson.dropbear.id.au> + */ + +#include <arpa/inet.h> +#include <errno.h> +#include <net/if.h> +#include <netinet/in.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "common.h" + +#define SRCPORT 13246U +#define DSTPORT 13247U + +/* Different cases for receiving socket configuration */ +enum sock_type { + /* Socket is bound to 0.0.0.0:DSTPORT and not connected */ + SOCK_BOUND_ANY, + + /* Socket is bound to 127.0.0.1:DSTPORT and not connected */ + SOCK_BOUND_LO, + + /* Socket is bound to 0.0.0.0:DSTPORT and connected to 127.0.0.1:SRCPORT */ + SOCK_CONNECTED, + + NUM_SOCK_TYPES, +}; + +typedef enum sock_type order_t[NUM_SOCK_TYPES]; + +static order_t orders[] = { + {0, 1, 2}, {0, 2, 1}, {1, 0, 2}, {1, 2, 0}, {2, 0, 1}, {2, 1, 0}, +}; + +/* 127.0.0.2 */ +#define INADDR_LOOPBACK2 ((in_addr_t)(0x7f000002)) + +/* 0.0.0.0:DSTPORT */ +static const struct sockaddr_in any_dst = SOCKADDR_INIT(INADDR_ANY, DSTPORT); +/* 127.0.0.1:DSTPORT */ +static const struct sockaddr_in lo_dst = SOCKADDR_INIT(INADDR_LOOPBACK, DSTPORT); + +/* 127.0.0.2:DSTPORT */ +static const struct sockaddr_in lo2_dst = SOCKADDR_INIT(INADDR_LOOPBACK2, DSTPORT); + +/* 127.0.0.1:SRCPORT */ +static const struct sockaddr_in lo_src = SOCKADDR_INIT(INADDR_LOOPBACK, SRCPORT); + +/* Random token to send in datagram */ +static long token; + +/* Get a socket of the specified type for receiving */ +static int sock_recv(enum sock_type type) +{ + const struct sockaddr *connect_sa = NULL; + const struct sockaddr *bind_sa = NULL; + int s; + + s = sock_reuseaddr(); + + switch (type) { + case SOCK_CONNECTED: + connect_sa = (struct sockaddr *)&lo_src; + /* fallthrough */ + case SOCK_BOUND_ANY: + bind_sa = (struct sockaddr *)&any_dst; + break; + + case SOCK_BOUND_LO: + bind_sa = (struct sockaddr *)&lo_dst; + break; + + default: + die("bug"); + } + + if (bind_sa) + if (bind(s, bind_sa, sizeof(struct sockaddr_in)) < 0) + die("bind(): %s\n", strerror(errno)); + if (connect_sa) + if (connect(s, connect_sa, sizeof(struct sockaddr_in)) < 0) + die("connect(): %s\n", strerror(errno)); + + return s; +} + +/* Get a socket suitable for sending to the given type of receiving socket */ +static int sock_send(enum sock_type type) +{ + const struct sockaddr *connect_sa = NULL; + const struct sockaddr *bind_sa = NULL; + int s; + + s = sock_reuseaddr(); + + switch (type) { + case SOCK_BOUND_ANY: + connect_sa = (struct sockaddr *)&lo2_dst; + break; + + case SOCK_CONNECTED: + bind_sa = (struct sockaddr *)&lo_src; + /* fallthrough */ + case SOCK_BOUND_LO: + connect_sa = (struct sockaddr *)&lo_dst; + break; + + default: + die("bug"); + } + + if (bind_sa) + if (bind(s, bind_sa, sizeof(struct sockaddr_in)) < 0) + die("bind(): %s\n", strerror(errno)); + if (connect_sa) + if (connect(s, connect_sa, sizeof(struct sockaddr_in)) < 0) + die("connect(): %s\n", strerror(errno)); + + return s; +} + +/* Check for expected behaviour with one specific ordering for various operations: + * + * @recv_create_order: Order to create receiving sockets in + * @send_create_order: Order to create sending sockets in + * @test_order: Order to test the behaviour of different types + * @recv_order: Order to check the receiving sockets + */ +static void check_one_order(const order_t recv_create_order, + const order_t send_create_order, + const order_t test_order, + const order_t recv_order) +{ + int rs[NUM_SOCK_TYPES]; + int ss[NUM_SOCK_TYPES]; + int nfds = 0; + int i, j; + + for (i = 0; i < NUM_SOCK_TYPES; i++) { + enum sock_type t = recv_create_order[i]; + int s; + + s = sock_recv(t); + if (s >= nfds) + nfds = s + 1; + + rs[t] = s; + } + + for (i = 0; i < NUM_SOCK_TYPES; i++) { + enum sock_type t = send_create_order[i]; + + ss[t] = sock_send(t); + } + + for (i = 0; i < NUM_SOCK_TYPES; i++) { + enum sock_type ti = test_order[i]; + int recv_via = -1; + + send_token(ss[ti], token); + + for (j = 0; j < NUM_SOCK_TYPES; j++) { + enum sock_type tj = recv_order[j]; + + if (recv_token(rs[tj], token)) { + if (recv_via != -1) + die("Received token more than once\n"); + recv_via = tj; + } + } + + if (recv_via == -1) + die("Didn't receive token at all\n"); + if (recv_via != ti) + die("Received token via unexpected socket\n"); + } + + for (i = 0; i < NUM_SOCK_TYPES; i++) { + close(rs[i]); + close(ss[i]); + } +} + +static void check_all_orders(void) +{ + int norders = sizeof(orders) / sizeof(orders[0]); + int i, j, k, l; + + for (i = 0; i < norders; i++) + for (j = 0; j < norders; j++) + for (k = 0; k < norders; k++) + for (l = 0; l < norders; l++) + check_one_order(orders[i], orders[j], + orders[k], orders[l]); +} + +int main(int argc, char *argv[]) +{ + (void)argc; + (void)argv; + + token = random(); + + check_all_orders(); + + printf("SO_REUSEADDR receive priorities seem to work as expected\n"); + + exit(0); +} diff --git a/doc/platform-requirements/udp-close-dup.c b/doc/platform-requirements/udp-close-dup.c new file mode 100644 index 0000000..99060fc --- /dev/null +++ b/doc/platform-requirements/udp-close-dup.c @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +/* udp-close-dup.c + * + * Verify that closing one dup() of a UDP socket won't stop other dups from + * receiving packets. + * + * Copyright Red Hat + * Author: David Gibson <david@gibson.dropbear.id.au> + */ + +#include <arpa/inet.h> +#include <errno.h> +#include <fcntl.h> +#include <net/if.h> +#include <netinet/in.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "common.h" + +#define DSTPORT 13257U + +/* 127.0.0.1:DSTPORT */ +static const struct sockaddr_in lo_dst = SOCKADDR_INIT(INADDR_LOOPBACK, DSTPORT); + +enum dup_method { + DUP_DUP, + DUP_FCNTL, + NUM_METHODS, +}; + +static void test_close_dup(enum dup_method method) +{ + long token; + int s1, s2, send_s; + ssize_t rc; + + s1 = sock_reuseaddr(); + if (bind(s1, (struct sockaddr *)&lo_dst, sizeof(lo_dst)) < 0) + die("bind(): %s\n", strerror(errno)); + + send_s = sock_reuseaddr(); + if (connect(send_s, (struct sockaddr *)&lo_dst, sizeof(lo_dst)) < 0) + die("connect(): %s\n", strerror(errno)); + + /* Receive before duplicating */ + token = random(); + send_token(send_s, token); + recv_token(s1, token); + + switch (method) { + case DUP_DUP: + /* NOLINTNEXTLINE(android-cloexec-dup) */ + s2 = dup(s1); + if (s2 < 0) + die("dup(): %s\n", strerror(errno)); + break; + case DUP_FCNTL: + s2 = fcntl(s1, F_DUPFD_CLOEXEC, 0); + if (s2 < 0) + die("F_DUPFD_CLOEXEC: %s\n", strerror(errno)); + break; + default: + die("Bad method\n"); + } + + /* Receive via original handle */ + token = random(); + send_token(send_s, token); + recv_token(s1, token); + + /* Receive via duplicated handle */ + token = random(); + send_token(send_s, token); + recv_token(s2, token); + + /* Close duplicate */ + rc = close(s2); + if (rc < 0) + die("close() dup: %s\n", strerror(errno)); + + /* Receive after closing duplicate */ + token = random(); + send_token(send_s, token); + recv_token(s1, token); +} + +int main(int argc, char *argv[]) +{ + enum dup_method method; + + (void)argc; + (void)argv; + + for (method = 0; method < NUM_METHODS; method++) + test_close_dup(method); + + printf("Closing dup()ed UDP sockets seems to work as expected\n"); + + exit(0); +} |