diff --git a/passt.c b/passt.c index aaa8e58..a90568f 100644 --- a/passt.c +++ b/passt.c @@ -61,17 +61,18 @@ char pkt_buf[PKT_BUF_BYTES] __attribute__ ((aligned(PAGE_SIZE))); char *epoll_type_str[] = { - [EPOLL_TYPE_TCP] = "connected TCP socket", - [EPOLL_TYPE_TCP_SPLICE] = "connected spliced TCP socket", - [EPOLL_TYPE_TCP_LISTEN] = "listening TCP socket", - [EPOLL_TYPE_TCP_TIMER] = "TCP timer", - [EPOLL_TYPE_UDP] = "UDP socket", - [EPOLL_TYPE_ICMP] = "ICMP socket", - [EPOLL_TYPE_ICMPV6] = "ICMPv6 socket", - [EPOLL_TYPE_NSQUIT] = "namespace inotify", - [EPOLL_TYPE_TAP_PASTA] = "/dev/net/tun device", - [EPOLL_TYPE_TAP_PASST] = "connected qemu socket", - [EPOLL_TYPE_TAP_LISTEN] = "listening qemu socket", + [EPOLL_TYPE_TCP] = "connected TCP socket", + [EPOLL_TYPE_TCP_SPLICE] = "connected spliced TCP socket", + [EPOLL_TYPE_TCP_LISTEN] = "listening TCP socket", + [EPOLL_TYPE_TCP_TIMER] = "TCP timer", + [EPOLL_TYPE_UDP] = "UDP socket", + [EPOLL_TYPE_ICMP] = "ICMP socket", + [EPOLL_TYPE_ICMPV6] = "ICMPv6 socket", + [EPOLL_TYPE_NSQUIT_INOTIFY] = "namespace inotify watch", + [EPOLL_TYPE_NSQUIT_TIMER] = "namespace timer watch", + [EPOLL_TYPE_TAP_PASTA] = "/dev/net/tun device", + [EPOLL_TYPE_TAP_PASST] = "connected qemu socket", + [EPOLL_TYPE_TAP_LISTEN] = "listening qemu socket", }; static_assert(ARRAY_SIZE(epoll_type_str) == EPOLL_NUM_TYPES, "epoll_type_str[] doesn't match enum epoll_type"); @@ -201,7 +202,7 @@ void exit_handler(int signal) */ int main(int argc, char **argv) { - int nfds, i, devnull_fd = -1, pidfile_fd = -1, quit_fd; + int nfds, i, devnull_fd = -1, pidfile_fd = -1; struct epoll_event events[EPOLL_EVENTS]; char *log_name, argv0[PATH_MAX], *name; struct ctx c = { 0 }; @@ -274,7 +275,7 @@ int main(int argc, char **argv) if (c.force_stderr || isatty(fileno(stdout))) __openlog(log_name, LOG_PERROR, LOG_DAEMON); - quit_fd = pasta_netns_quit_init(&c); + pasta_netns_quit_init(&c); tap_sock_init(&c); @@ -370,8 +371,11 @@ loop: case EPOLL_TYPE_TAP_LISTEN: tap_listen_handler(&c, eventmask); break; - case EPOLL_TYPE_NSQUIT: - pasta_netns_quit_handler(&c, quit_fd); + case EPOLL_TYPE_NSQUIT_INOTIFY: + pasta_netns_quit_inotify_handler(&c, ref.fd); + break; + case EPOLL_TYPE_NSQUIT_TIMER: + pasta_netns_quit_timer_handler(&c, ref); break; case EPOLL_TYPE_TCP: tcp_sock_handler(&c, ref, eventmask); diff --git a/passt.h b/passt.h index a9e8f15..fb729b6 100644 --- a/passt.h +++ b/passt.h @@ -64,7 +64,9 @@ enum epoll_type { /* ICMPv6 sockets */ EPOLL_TYPE_ICMPV6, /* inotify fd watching for end of netns (pasta) */ - EPOLL_TYPE_NSQUIT, + EPOLL_TYPE_NSQUIT_INOTIFY, + /* timer fd watching for end of netns, fallback for inotify (pasta) */ + EPOLL_TYPE_NSQUIT_TIMER, /* tuntap character device */ EPOLL_TYPE_TAP_PASTA, /* socket connected to qemu */ @@ -84,6 +86,7 @@ enum epoll_type { * @udp: UDP-specific reference part * @icmp: ICMP-specific reference part * @data: Data handled by protocol handlers + * @nsdir_fd: netns dirfd for fallback timer checking if namespace is gone * @u64: Opaque reference for epoll_ctl() and epoll_wait() */ union epoll_ref { @@ -99,6 +102,7 @@ union epoll_ref { union udp_epoll_ref udp; union icmp_epoll_ref icmp; uint32_t data; + int nsdir_fd; }; }; uint64_t u64; diff --git a/pasta.c b/pasta.c index 94807a3..01d1511 100644 --- a/pasta.c +++ b/pasta.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -357,46 +358,78 @@ void pasta_ns_conf(struct ctx *c) } /** - * pasta_netns_quit_init() - Watch network namespace to quit once it's gone - * @c: Execution context + * pasta_netns_quit_timer() - Set up fallback timer to monitor namespace * - * Return: inotify file descriptor, -1 on failure or if not needed/applicable + * Return: timerfd file descriptor, negative error code on failure */ -int pasta_netns_quit_init(const struct ctx *c) +static int pasta_netns_quit_timer(void) { - int flags = O_NONBLOCK | O_CLOEXEC; - union epoll_ref ref = { .type = EPOLL_TYPE_NSQUIT }; - struct epoll_event ev = { - .events = EPOLLIN - }; - int inotify_fd; + int fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC); + struct itimerspec it = { { 1, 0 }, { 1, 0 } }; /* one-second interval */ - if (c->mode != MODE_PASTA || c->no_netns_quit || !*c->netns_base) - return -1; - - if ((inotify_fd = inotify_init1(flags)) < 0) { - perror("inotify_init(): won't quit once netns is gone"); - return -1; + if (fd == -1) { + err("timerfd_create(): %s", strerror(errno)); + return -errno; } - if (inotify_add_watch(inotify_fd, c->netns_dir, IN_DELETE) < 0) { - perror("inotify_add_watch(): won't quit once netns is gone"); - return -1; + if (timerfd_settime(fd, 0, &it, NULL) < 0) { + err("timerfd_settime(): %s", strerror(errno)); + close(fd); + return -errno; } - ref.fd = inotify_fd; - ev.data.u64 = ref.u64; - epoll_ctl(c->epollfd, EPOLL_CTL_ADD, inotify_fd, &ev); - - return inotify_fd; + return fd; } /** - * pasta_netns_quit_handler() - Handle ns directory events, exit if ns is gone + * pasta_netns_quit_init() - Watch network namespace to quit once it's gone + * @c: Execution context + */ +void pasta_netns_quit_init(const struct ctx *c) +{ + union epoll_ref ref = { .type = EPOLL_TYPE_NSQUIT_INOTIFY }; + struct epoll_event ev = { .events = EPOLLIN }; + int flags = O_NONBLOCK | O_CLOEXEC; + int fd; + + if (c->mode != MODE_PASTA || c->no_netns_quit || !*c->netns_base) + return; + + if ((fd = inotify_init1(flags)) < 0) + warn("inotify_init1(): %s, use a timer", strerror(errno)); + + if (fd >= 0 && inotify_add_watch(fd, c->netns_dir, IN_DELETE) < 0) { + warn("inotify_add_watch(): %s, use a timer", + strerror(errno)); + close(fd); + fd = -1; + } + + if (fd < 0) { + if ((fd = pasta_netns_quit_timer()) < 0) + die("Failed to set up fallback netns timer, exiting"); + + ref.nsdir_fd = open(c->netns_dir, O_CLOEXEC | O_RDONLY); + if (ref.nsdir_fd < 0) + die("netns dir open: %s, exiting", strerror(errno)); + + ref.type = EPOLL_TYPE_NSQUIT_TIMER; + } + + if (fd > FD_REF_MAX) + die("netns monitor file number %i too big, exiting", fd); + + ref.fd = fd; + ev.data.u64 = ref.u64; + epoll_ctl(c->epollfd, EPOLL_CTL_ADD, fd, &ev); +} + +/** + * pasta_netns_quit_inotify_handler() - Handle inotify watch, exit if ns is gone * @c: Execution context * @inotify_fd: inotify file descriptor with watch on namespace directory */ -void pasta_netns_quit_handler(struct ctx *c, int inotify_fd) +void pasta_netns_quit_inotify_handler(struct ctx *c, int inotify_fd) { char buf[sizeof(struct inotify_event) + NAME_MAX + 1]; const struct inotify_event *in_ev = (struct inotify_event *)buf; @@ -410,3 +443,29 @@ void pasta_netns_quit_handler(struct ctx *c, int inotify_fd) info("Namespace %s is gone, exiting", c->netns_base); exit(EXIT_SUCCESS); } + +/** + * pasta_netns_quit_timer_handler() - Handle timer, exit if ns is gone + * @c: Execution context + * @ref: epoll reference for timer descriptor + */ +void pasta_netns_quit_timer_handler(struct ctx *c, union epoll_ref ref) +{ + uint64_t expirations; + ssize_t n; + int fd; + + n = read(ref.fd, &expirations, sizeof(expirations)); + if (n < 0) + die("Namespace watch timer read() error: %s", strerror(errno)); + if ((size_t)n < sizeof(expirations)) + warn("Namespace watch timer: short read(): %zi", n); + + fd = openat(ref.nsdir_fd, c->netns_base, O_PATH | O_CLOEXEC); + if (fd < 0) { + info("Namespace %s is gone, exiting", c->netns_base); + exit(EXIT_SUCCESS); + } + + close(fd); +} diff --git a/pasta.h b/pasta.h index 5d20063..4b063d1 100644 --- a/pasta.h +++ b/pasta.h @@ -13,7 +13,8 @@ void pasta_start_ns(struct ctx *c, uid_t uid, gid_t gid, int argc, char *argv[]); void pasta_ns_conf(struct ctx *c); void pasta_child_handler(int signal); -int pasta_netns_quit_init(const struct ctx *c); -void pasta_netns_quit_handler(struct ctx *c, int inotify_fd); +void pasta_netns_quit_init(const struct ctx *c); +void pasta_netns_quit_inotify_handler(struct ctx *c, int inotify_fd); +void pasta_netns_quit_timer_handler(struct ctx *c, union epoll_ref ref); #endif /* PASTA_H */