// 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 * * repair.c - Interface (server) for passt-repair, set/clear TCP_REPAIR * * Copyright (c) 2025 Red Hat GmbH * Author: Stefano Brivio */ #include #include #include "util.h" #include "ip.h" #include "passt.h" #include "inany.h" #include "flow.h" #include "flow_table.h" #include "repair.h" #define SCM_MAX_FD 253 /* From Linux kernel (include/net/scm.h), not in UAPI */ static int fds[SCM_MAX_FD]; static int current_cmd; static int nfds; /** * repair_sock_init() - Start listening for connections on helper socket * @c: Execution context */ void repair_sock_init(const struct ctx *c) { union epoll_ref ref = { .type = EPOLL_TYPE_REPAIR_LISTEN }; struct epoll_event ev = { 0 }; listen(c->fd_repair_listen, 0); ref.fd = c->fd_repair_listen; ev.events = EPOLLIN | EPOLLHUP | EPOLLET; ev.data.u64 = ref.u64; epoll_ctl(c->epollfd, EPOLL_CTL_ADD, c->fd_repair_listen, &ev); } /** * repair_listen_handler() - Handle events on TCP_REPAIR helper listening socket * @c: Execution context * @events: epoll events */ void repair_listen_handler(struct ctx *c, uint32_t events) { union epoll_ref ref = { .type = EPOLL_TYPE_REPAIR }; struct epoll_event ev = { 0 }; struct ucred ucred; socklen_t len; if (events != EPOLLIN) { debug("Spurious event 0x%04x on TCP_REPAIR helper socket", events); return; } len = sizeof(ucred); /* Another client is already connected: accept and close right away. */ if (c->fd_repair != -1) { int discard = accept4(c->fd_repair_listen, NULL, NULL, SOCK_NONBLOCK); if (discard == -1) return; if (!getsockopt(discard, SOL_SOCKET, SO_PEERCRED, &ucred, &len)) info("Discarding TCP_REPAIR helper, PID %i", ucred.pid); close(discard); return; } c->fd_repair = accept4(c->fd_repair_listen, NULL, NULL, 0); if (!getsockopt(c->fd_repair, SOL_SOCKET, SO_PEERCRED, &ucred, &len)) info("Accepted TCP_REPAIR helper, PID %i", ucred.pid); ref.fd = c->fd_repair; ev.events = EPOLLHUP | EPOLLET; ev.data.u64 = ref.u64; epoll_ctl(c->epollfd, EPOLL_CTL_ADD, c->fd_repair, &ev); } /** * repair_close() - Close connection to TCP_REPAIR helper * @c: Execution context */ void repair_close(struct ctx *c) { debug("Closing TCP_REPAIR helper socket"); epoll_ctl(c->epollfd, EPOLL_CTL_DEL, c->fd_repair, NULL); close(c->fd_repair); c->fd_repair = -1; } /** * repair_handler() - Handle EPOLLHUP and EPOLLERR on TCP_REPAIR helper socket * @c: Execution context * @events: epoll events */ void repair_handler(struct ctx *c, uint32_t events) { (void)events; repair_close(c); } /** * repair_flush() - Flush current set of sockets to helper, with current command * @c: Execution context * * Return: 0 on success, negative error code on failure */ int repair_flush(struct ctx *c) { struct iovec iov = { &((int8_t){ current_cmd }), sizeof(int8_t) }; char buf[CMSG_SPACE(sizeof(int) * SCM_MAX_FD)] __attribute__ ((aligned(__alignof__(struct cmsghdr)))); struct cmsghdr *cmsg; struct msghdr msg; int ret = 0; if (!nfds) return 0; msg = (struct msghdr){ NULL, 0, &iov, 1, buf, CMSG_SPACE(sizeof(int) * nfds), 0 }; cmsg = CMSG_FIRSTHDR(&msg); cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_RIGHTS; cmsg->cmsg_len = CMSG_LEN(sizeof(int) * nfds); memcpy(CMSG_DATA(cmsg), fds, sizeof(int) * nfds); nfds = 0; if (sendmsg(c->fd_repair, &msg, 0) < 0) { ret = -errno; err_perror("Failed to send sockets to TCP_REPAIR helper"); repair_close(c); } if (recv(c->fd_repair, &((int8_t){ 0 }), 1, 0) < 0) { ret = -errno; err_perror("Failed to receive reply from TCP_REPAIR helper"); repair_close(c); } return ret; } /** * repair_flush() - Add socket to TCP_REPAIR set with given command * @c: Execution context * @s: Socket to add * @cmd: TCP_REPAIR_ON, TCP_REPAIR_OFF, or TCP_REPAIR_OFF_NO_WP * * Return: 0 on success, negative error code on failure */ /* cppcheck-suppress unusedFunction */ int repair_set(struct ctx *c, int s, int cmd) { int rc; if (nfds && current_cmd != cmd) { if ((rc = repair_flush(c))) return rc; } current_cmd = cmd; fds[nfds++] = s; if (nfds >= SCM_MAX_FD) { if ((rc = repair_flush(c))) return rc; } return 0; }