From mboxrd@z Thu Jan 1 00:00:00 1970 Authentication-Results: passt.top; dmarc=fail (p=quarantine dis=none) header.from=redhat.com Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by passt.top (Postfix) with ESMTPS id 51CAA5A0265 for ; Fri, 13 Nov 2026 01:09:36 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1794528575; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=LZgf6mUSPzhmxgD9wBo9ju83ckaKypqwAZE+lU4igbI=; b=HqwLLQM5+/0Ge5gRXAWo21r5+PZEJ5zanTQRIcWuvih0vKQcThZUDU35NzL8+5tjCW+Wba XNBk4XBcf3opxT+SnhuQ4PnbrIcsn5YSSkoNMOQFYszpqQwoakCN+tBjnkv08Wg2dQn/1i 92r9uBD54Il9MbZNfBlx3vD1dMnCERg= Received: from mail-wr1-f69.google.com (mail-wr1-f69.google.com [209.85.221.69]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-665-1Ku0KGbMPgWovVyMu90TZw-1; Thu, 12 Nov 2026 19:09:33 -0500 X-MC-Unique: 1Ku0KGbMPgWovVyMu90TZw-1 X-Mimecast-MFC-AGG-ID: 1Ku0KGbMPgWovVyMu90TZw_1794528572 Received: by mail-wr1-f69.google.com with SMTP id ffacd0b85a97d-4903fc8537aso149001f8f.0 for ; Thu, 12 Nov 2026 16:09:33 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1794528572; x=1795133372; h=date:content-transfer-encoding:content-type:mime-version :organization:references:in-reply-to:message-id:subject:cc:to:from :x-gm-gg:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to:content-type; bh=LZgf6mUSPzhmxgD9wBo9ju83ckaKypqwAZE+lU4igbI=; b=DjPQ76TlVm8DKMl+iXS3XPKemPpWyJ9bDkvxyAsT0wlQuqVmWyug41Odu7ug1DI53z KJ2w52cRCOUOxfe/FVna7qMquBB5Kq2jd4CRPv1kGDazkXfOu2oTmdwySLaULOjrt5Ix tQgeRnUehAMiGJrPItGRQ//w4fHX5y2Lb77GAKTDIgDzHGm4+fM/xn2SbCxYGWos20eS dkcgbgBrym250FJboKygzmj/ExYWGVJQY6uWgz8tHMhxd6c4MdNSc3xuofiZss42mcnn XmBl5bg3ZEuHlbthHGuulGVPFBlUeNwGvNLrI2giS0ZmbLeycUJdB1kMdM2L6Vd4WVSZ +lTA== X-Gm-Message-State: APuU4Z8bx/25+z7cO5dB56xUo2iDZY0ROPGnHVr3tdncuAiwI33n/JfY 0Zy4cBz/UOBzGYJxKZ7rXkOnXWCjgeKeBFvb05ruQ5ANrefHDkc9eV2m8Jrt0DoGBkhz1+DBb12 AGXlgE06fZc8Pa6m8Y2wDfjB3QCvfCy8z+W2CIayfKJVe+lncs5Ex1Q== X-Gm-Gg: AYBFou0vOAPAbP57+vTSbHwx+yHSSUJsgc8aZaoGNFM7K0eqJ7VYqmrlLZirCEwqfhQ w+tbU0LKJhWB+xF24HWkwPTLr+2GRqTkfMJlzzNuhri4QkTSV1jm05Gg/0ShodGAHBvmc0xu8eu VQ251XxDoGZOs2cJmyz37I17oUTrsNkafBLtyrCGUEqgLtO/nPpRLvOxSVLXrojOivxFJUWCSF0 JiR0tqhR1WmcdR5juQx6GqVozAQQM8W9ZBwlb90zDG6dKD32NJiSvLNm4ohKq2qeDPITOnzikO6 /FXz7IMqsaKSgIADv6+kqq8eRUMuHMXMnjb2SO65lNIy9Ct3/ay175Tgm22mpriMUtf2LlvyCWV PnVkt2tXA+4C0ksoPVb6M3qpvsfZ0xNroLDhjxYOIno2f4DX0/S/LoTTEVL/wvtZwHw8iMgm+x2 NSNVeXOxGB X-Received: by 2002:a05:6000:3102:b0:48e:4300:c6d4 with SMTP id ffacd0b85a97d-49c747700e5mr2313598f8f.53.1794528571674; Thu, 12 Nov 2026 16:09:31 -0800 (PST) X-Received: by 2002:a05:6000:3102:b0:48e:4300:c6d4 with SMTP id ffacd0b85a97d-49c747700e5mr2313524f8f.53.1794528570764; Thu, 12 Nov 2026 16:09:30 -0800 (PST) Received: from maya.myfinge.rs (ifcgrfdd.trafficplex.cloud. [176.103.220.4]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-49c60f8d5ffsm1875717f8f.55.2026.11.12.16.09.29 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 12 Nov 2026 16:09:30 -0800 (PST) From: Stefano Brivio To: Aditya Merchant Subject: Re: [PATCH] conf: Follow changes to the host nameserver used for DNS forwarding Message-ID: <20261113010928.6872aca9@elisabeth> In-Reply-To: <20261101213354.15868-1-adityamerchant08@gmail.com> References: <20261101213354.15868-1-adityamerchant08@gmail.com> Organization: Red Hat X-Mailer: Claws Mail 4.2.0 (GTK 3.24.49; x86_64-pc-linux-gnu) MIME-Version: 1.0 Date: Fri, 13 Nov 2026 01:09:29 +0100 (CET) X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: mn4sZ7L6D4VWrtVQxv4QbJDBZXr2HQUof91VDNNn9Dg_1794528572 X-Mimecast-Originator: redhat.com Content-Type: text/plain; charset=US-ASCII Content-Transfer-Encoding: 7bit Message-ID-Hash: 36QM5CCLMIDCQ6QDUFATW6OQUTHHWVXM X-Message-ID-Hash: 36QM5CCLMIDCQ6QDUFATW6OQUTHHWVXM X-MailFrom: sbrivio@redhat.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; emergency; loop; banned-address; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: passt-dev@passt.top, David Gibson , Paul Holzinger X-Mailman-Version: 3.3.8 Precedence: list List-Id: Development discussion and patches for passt Archived-At: Archived-At: List-Archive: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Hi Aditya, On Mon, 2 Nov 2026 03:03:54 +0530 Aditya Merchant wrote: > We read the host's /etc/resolv.conf once, at start, and forward queries > sent to the --dns-forward address to the first nameserver listed there. > If the host then joins another network, connects to a VPN, or pasta > starts before the network is configured, that nameserver goes stale and > name resolution in the namespace stops working until pasta restarts. > With rootless Podman, that means stopping every container using the > rootless network namespace. > > In pasta mode, if we forward DNS queries for an IP version, and the host > nameserver for it comes from resolv.conf (no --dns-host or --dns), watch > resolv.conf with inotify and read it again as it changes, so that new > DNS flows go to its current first nameserver. Watch the directory, not > the file, as NetworkManager and others replace resolv.conf by renaming > a new file over it. If resolv.conf is a symbolic link, watch the > directory of its target. Thanks a lot for contributing this important feature, and sorry for the delay. I think it's extremely valuable (as you found out, the first request in this sense came almost four years ago), even though the implementation is a bit scary as it exposes parts of the host filesystem at runtime. On the other hand, I don't have a better idea about how to implement this, except perhaps for some countermeasures I indicated as part of my review comments inline, below. > Once sandboxed, we can't open files by path, and keeping a descriptor > for the directory would undo the filesystem isolation: lookups of ".." > from it reach the rest of the host filesystem. Instead, right after > isolate_prefork() unshares our mount namespace, and before mounts become > unbindable, take a detached copy of the directory with open_tree() and > OPEN_TREE_CLONE: lookups from its root can't go above it, so the > directory holding resolv.conf is all we can still see. Where possible > (Linux 5.12 or later), make that copy read-only, too. Cloning needs > Linux 5.2 or later: if it fails, or if we can't read resolv.conf through > the copy, we just don't follow changes. > > AppArmor resolves paths of files on a detached mount relative to their > filesystem, so allow the usual locations of resolv.conf under /run with > paths relative to /run, too. > > Flows that already exist keep their target, so a client reusing the > same source port keeps reaching the old nameserver until its flow > expires. > > Link: https://github.com/podman-container-tools/podman/issues/29130 > Link: https://archives.passt.top/passt-dev/20230121104703.3ebcc753@elisabeth/ > Signed-off-by: Aditya Merchant > --- > > Notes: > Tested on Kali (arm64, Linux 7.1) with rootless Podman 5.8.6, and with > pasta alone, with the AppArmor profile enforcing: resolv.conf replaced > by rename, rewritten in place, empty at start and filled in later, and > a symbolic link into /run. With --dns-host, the address stays fixed, as > before. I also checked that, from the copy, ".." doesn't lead anywhere, > and that files can't be written or created. I didn't try kernels older > than 5.12 or 5.2. > > I couldn't test SELinux: pasta.te doesn't allow 'watch' on etc_t or on > the directories under /run holding resolv.conf, so on Fedora I'd expect > inotify_add_watch() to be denied, and changes not to be followed (by > default, Fedora points resolv.conf to systemd-resolved on 127.0.0.53, > which doesn't change). I can add rules if you tell me which types or > interfaces you'd prefer. Thanks for looking into this part. It looks like sysnet_watch_config() is exactly what we need here, so I think we should add that on top of the existing sysnet_read_config() rules. Note that I haven't tested this (yet). > > There's no test under test/ yet, as the tests can't change the host's > resolv.conf. I could try one using a separate mount namespace, if you > think it's worth it. I think it *might* be worth it, but I'm not sure how complicated that is, so I'm fine either way. If it's a few lines (20 or so?) I would suggest giving it a try. > > conf.c | 239 ++++++++++++++++++++++++++-- > conf.h | 2 + > contrib/apparmor/abstractions/pasta | 5 + > epoll_type.h | 2 + > isolation.c | 6 +- > isolation.h | 2 +- > linux_dep.h | 30 ++++ > passt.1 | 4 +- > passt.c | 5 + > passt.h | 9 ++ > 10 files changed, 291 insertions(+), 13 deletions(-) > > diff --git a/conf.c b/conf.c > index 149a4c7..63b4b9a 100644 > --- a/conf.c > +++ b/conf.c > @@ -19,6 +19,9 @@ > #include > #include > #include > +#include > +#include > +#include > #include > #include > #include > @@ -36,6 +39,7 @@ > #include > > #include "util.h" > +#include "linux_dep.h" > #include "bitmap.h" > #include "ip.h" > #include "passt.h" > @@ -199,6 +203,31 @@ static void add_dns_resolv(struct ctx *c, const char *nameserver, > add_dns_resolv6(c, &ns6, idx6); > } > > +/** > + * resolv_nameserver() - Get address from a nameserver line of resolv.conf > + * @line: Line from resolv.conf, terminated, might be modified > + * > + * Return: address as string, NULL if @line is not a nameserver line > + */ > +static const char *resolv_nameserver(char *line) > +{ > + const char *p; > + char *end; > + > + if (strstr(line, "nameserver ") != line) > + return NULL; > + > + p = strrchr(line, ' '); > + if (!p) > + return NULL; > + > + end = strpbrk(line, "%\n"); > + if (end) > + *end = 0; > + > + return p + 1; > +} > + > /** > * get_dns() - Get nameserver addresses from local /etc/resolv.conf > * @c: Execution context > @@ -218,6 +247,14 @@ static void get_dns(struct ctx *c) > dnss_set = !!*s->n || c->no_dns_search; > dns_set = (dns4_set && dns6_set) || c->no_dns; > > + /* Unless given with --dns-host, or unless we don't read nameservers > + * at all, host nameservers we forward queries to come from resolv.conf > + */ > + c->ip4.dns_host_resolv = !c->no_dns && !dns4_set && > + IN4_IS_ADDR_UNSPECIFIED(&c->ip4.dns_host); > + c->ip6.dns_host_resolv = !c->no_dns && !dns6_set && > + IN6_IS_ADDR_UNSPECIFIED(&c->ip6.dns_host); > + > if (dns_set && dnss_set) > return; > > @@ -226,16 +263,8 @@ static void get_dns(struct ctx *c) > > lineread_init(&resolvconf, fd); > while ((line_len = lineread_get(&resolvconf, &line)) > 0) { > - if (!dns_set && strstr(line, "nameserver ") == line) { > - p = strrchr(line, ' '); > - if (!p) > - continue; > - > - end = strpbrk(line, "%\n"); > - if (end) > - *end = 0; > - > - add_dns_resolv(c, p + 1, > + if (!dns_set && (p = resolv_nameserver(line))) { > + add_dns_resolv(c, p, > dns4_set ? NULL : &dns4_idx, > dns6_set ? NULL : &dns6_idx); > } else if (!dnss_set && strstr(line, "search ") == line && > @@ -280,6 +309,196 @@ out: > } > } > > +/** > + * conf_resolv_update() - Update host nameservers from host's resolv.conf > + * @c: Execution context > + * > + * Return: 0 on success, negative error code if the file can't be opened > + * > + * #syscalls:pasta openat > + */ > +static int conf_resolv_update(struct ctx *c) > +{ > + bool found4 = false, found6 = false; > + char addr[INET6_ADDRSTRLEN]; > + struct lineread resolvconf; > + struct in6_addr ns6; > + struct in_addr ns4; > + char *line; > + int fd; > + > + fd = openat(c->resolv_dir_fd, c->resolv_base, O_RDONLY | O_CLOEXEC); This is the scary part, so I'm trying to think of some ways to make this less scary. One quick idea would be to make sure that c->resolv_base is "resolv.conf", instead of something else that somebody managed to somehow write to it. Two ways to achieve that: - memcmp() (or strcmp()) and assert() - just get rid of it and hardcode that, parsing only the directory part in conf_resolv_watch()? Can we safely do that? > + if (fd < 0) > + return -errno; > + > + lineread_init(&resolvconf, fd); > + while (lineread_get(&resolvconf, &line) > 0) { > + const char *p = resolv_nameserver(line); > + > + if (!p) > + continue; > + > + if (!found4) > + found4 = inet_pton(AF_INET, p, &ns4) == 1; > + if (!found6) > + found6 = inet_pton(AF_INET6, p, &ns6) == 1; > + } > + close(fd); > + > + if (c->ip4.dns_host_resolv && found4 && > + !IN4_ARE_ADDR_EQUAL(&ns4, &c->ip4.dns_host)) { > + c->ip4.dns_host = ns4; > + info("Forwarding DNS queries to new host nameserver %s", > + inet_ntop(AF_INET, &ns4, addr, sizeof(addr))); > + } > + > + if (c->ip6.dns_host_resolv && found6 && > + !IN6_ARE_ADDR_EQUAL(&ns6, &c->ip6.dns_host)) { > + c->ip6.dns_host = ns6; > + info("Forwarding DNS queries to new host nameserver %s", > + inet_ntop(AF_INET6, &ns6, addr, sizeof(addr))); > + } > + > + return 0; > +} > + > +/** > + * conf_resolv_watch() - Watch host's resolv.conf to follow nameserver changes > + * @c: Execution context > + * > + * The host nameserver we forward DNS queries to can change at any time, for > + * example as the host joins a different network or connects to a VPN. Watch > + * the directory containing resolv.conf with inotify, as the file is usually > + * replaced by renaming a new one over it, not rewritten in place. > + * > + * Once sandboxed, we can't open resolv.conf by path, and a descriptor for the > + * directory itself would let lookups of ".." reach the whole filesystem. Keep > + * a detached, read-only copy of the directory instead: lookups can't go above > + * its root. Cloning needs our own mount namespace, before its mounts are made > + * unbindable: see isolate_prefork(). > + */ > +void conf_resolv_watch(struct ctx *c) > +{ > + struct mount_attr attr = { > + .attr_set = MOUNT_ATTR_RDONLY | MOUNT_ATTR_NOSUID | > + MOUNT_ATTR_NODEV | MOUNT_ATTR_NOEXEC, > + .propagation = MS_PRIVATE, > + }; > + union epoll_ref ref = { .type = EPOLL_TYPE_RESOLV_INOTIFY }; > + char dir[PATH_MAX], *base; > + int fd, dir_fd, rc; > + > + if (c->mode != MODE_PASTA) > + return; Couldn't this feature be useful for passt(1) as well, by the way? > + > + if (IN4_IS_ADDR_UNSPECIFIED(&c->ip4.dns_match)) > + c->ip4.dns_host_resolv = false; > + if (IN6_IS_ADDR_UNSPECIFIED(&c->ip6.dns_match)) > + c->ip6.dns_host_resolv = false; > + > + if (!c->ip4.dns_host_resolv && !c->ip6.dns_host_resolv) > + return; > + > + /* With systemd-resolved, resolvconf, or NetworkManager in some > + * configurations, resolv.conf is a symbolic link: watch its target > + */ > + if (!realpath("/etc/resolv.conf", dir)) { > + debug_perror("Not following changes to /etc/resolv.conf"); > + return; > + } > + > + /* Absolute path from realpath(): split it into directory and file */ > + base = strrchr(dir, '/'); > + if (!base || snprintf_check(c->resolv_base, sizeof(c->resolv_base), > + "%s", base + 1)) > + return; > + *(base == dir ? base + 1 : base) = 0; > + > + /* AT_RECURSIVE: mounts we inherited in our user namespace are locked, > + * and the kernel refuses to clone a subtree without its submounts > + */ > + dir_fd = syscall(SYS_open_tree, AT_FDCWD, dir, > + OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC | AT_RECURSIVE); > + if (dir_fd < 0) { > + debug_perror("Can't clone %s, not following changes", dir); > + return; > + } > + > + /* Best effort, needs Linux 5.12: we only need to read from the copy */ ...but we don't know if it's failing because of an older kernel, or because of some other issue that we shouldn't ignore. And in any case, it's probably not a good idea to introduce this mechanism in a less secure way for kernel versions between 5.2 and 5.12: I'd rather goto fail; here. > + if (syscall(SYS_mount_setattr, dir_fd, "", AT_EMPTY_PATH | AT_RECURSIVE, > + &attr, sizeof(attr))) > + debug_perror("Can't make copy of %s read-only", dir); > + > + if ((fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC)) < 0) { > + debug_perror("Can't follow changes to resolv.conf"); > + goto fail; > + } > + > + if (inotify_add_watch(fd, dir, IN_CLOSE_WRITE | IN_MOVED_TO) < 0) { > + debug_perror("Can't watch %s for changes", dir); > + goto fail; > + } > + > + /* With the watch in place, read the file again: it might have changed > + * since get_dns() read it. This also checks that we can read it through > + * the copy, which security policies might not allow. > + */ > + c->resolv_dir_fd = dir_fd; > + if ((rc = conf_resolv_update(c))) { > + debug("Can't read %s/%s through a copy: %s", dir, c->resolv_base, > + strerror_(-rc)); > + goto fail; > + } > + > + if (fd > FD_REF_MAX) { > + debug("resolv.conf watch file number %i too big", fd); > + goto fail; > + } > + > + ref.fd = fd; > + if (epoll_add(c->epollfd, EPOLLIN, ref)) > + goto fail; > + > + return; > + > +fail: > + c->resolv_dir_fd = -1; > + if (fd >= 0) > + close(fd); > + close(dir_fd); > +} > + > +/** > + * conf_resolv_handler() - Handle inotify events for host's resolv.conf > + * @c: Execution context > + * @fd: inotify file descriptor watching directory of resolv.conf > + */ > +void conf_resolv_handler(struct ctx *c, int fd) > +{ > + char buf[sizeof(struct inotify_event) + NAME_MAX + 1] > + __attribute__ ((aligned(__alignof__(struct inotify_event)))); > + const struct inotify_event *ev; > + bool changed = false; > + ssize_t n; > + char *p; > + int rc; > + > + if ((n = read(fd, buf, sizeof(buf))) < (ssize_t)sizeof(*ev)) > + return; > + For robustness, we should make sure that 'buf' is zero-terminated here, see how passt-repair's wait_for_socket() handles this. True, pasta_netns_quit_inotify_handler() doesn't do that (and we should fix that as well), but at least it uses strncmp(). > + for (p = buf; p < buf + n; p += sizeof(*ev) + ev->len) { > + ev = (const struct inotify_event *)p; > + > + if ((ev->mask & IN_Q_OVERFLOW) || > + (ev->len && !strcmp(ev->name, c->resolv_base))) > + changed = true; Shouldn't we break here? There's no point in continuing parsing if we just set 'changed', and, if IN_Q_OVERFLOW is set, we shouldn't expect the event queue to be (entirely) valid anyway. > + } > + > + if (changed && (rc = conf_resolv_update(c))) > + warn("Can't read %s to update nameservers: %s", > + c->resolv_base, strerror_(-rc)); > +} > + > /** > * conf_netns_opt() - Parse --netns option > * @netns: buffer of size PATH_MAX, updated with netns path > diff --git a/conf.h b/conf.h > index 19bf9bc..15eb226 100644 > --- a/conf.h > +++ b/conf.h > @@ -11,5 +11,7 @@ int conf_tap_fd(int argc, char **argv); > void conf(struct ctx *c, int argc, char **argv); > void conf_listen_handler(struct ctx *c, uint32_t events); > void conf_handler(struct ctx *c, uint32_t events); > +void conf_resolv_watch(struct ctx *c); > +void conf_resolv_handler(struct ctx *c, int fd); > > #endif /* CONF_H */ > diff --git a/contrib/apparmor/abstractions/pasta b/contrib/apparmor/abstractions/pasta > index 89fa427..8711f9f 100644 > --- a/contrib/apparmor/abstractions/pasta > +++ b/contrib/apparmor/abstractions/pasta > @@ -29,6 +29,11 @@ > > @{run}/user/@{uid}/** rw, # pasta_open_ns() > > + # Host's resolv.conf in /run, read via a detached copy of its directory, > + # with paths relative to /run: conf_resolv_watch(), conf_resolv_update() > + /{NetworkManager,connman,netconfig,resolvconf}/resolv.conf r, > + /systemd/resolve/{,stub-}resolv.conf r, I'm wondering if we shouldn't include instead. It's much broader but probably more compatible and also the right thing to do. I haven't checked whether that works, though. But anyway, what you are adding here should already cover virtually all our users, and if it doesn't it's quick to fix, so that's not a strong preference on my side. > + > @{PROC}/[0-9]*/ns/ r, # pasta_netns_quit_init(), > @{PROC}/[0-9]*/ns/net r, # pasta_wait_for_ns(), > @{PROC}/[0-9]*/ns/user r, # conf_pasta_ns() > diff --git a/epoll_type.h b/epoll_type.h > index 061325a..a42944f 100644 > --- a/epoll_type.h > +++ b/epoll_type.h > @@ -50,6 +50,8 @@ enum epoll_type { > EPOLL_TYPE_CONF_LISTEN, > /* Configuration socket */ > EPOLL_TYPE_CONF, > + /* inotify fd watching host's resolv.conf (pasta) */ > + EPOLL_TYPE_RESOLV_INOTIFY, > > EPOLL_NUM_TYPES, > }; > diff --git a/isolation.c b/isolation.c > index a00475f..6c42db7 100644 > --- a/isolation.c > +++ b/isolation.c > @@ -470,11 +470,12 @@ void isolate_user(const struct ctx *c, uid_t uid, gid_t gid, bool use_userns, > * Should: > * - Move us to our own IPC and UTS namespaces > * - Move us to a mount namespace with only an empty directory > + * - Keep a detached copy of the directory of resolv.conf, if needed > * - Drop unneeded capabilities (in the new user namespace) > * Mustn't: > * - Remove syscalls we need to daemonise > */ > -int isolate_prefork(const struct ctx *c) > +int isolate_prefork(struct ctx *c) > { > int flags = CLONE_NEWIPC | CLONE_NEWNS | CLONE_NEWUTS; > uint64_t ns_caps = 0; > @@ -491,6 +492,9 @@ int isolate_prefork(const struct ctx *c) > return -errno; > } > > + /* This clones a mount: do it before they all become unbindable */ > + conf_resolv_watch(c); > + > if (mount("", "/", "", MS_UNBINDABLE | MS_REC, NULL)) { > err_perror("Failed to remount /"); > return -errno; > diff --git a/isolation.h b/isolation.h > index ec47038..4b8f822 100644 > --- a/isolation.h > +++ b/isolation.h > @@ -14,7 +14,7 @@ void isolate_initial(void); > int isolate_fds(int argc, char **argv); > void isolate_user(const struct ctx *c, uid_t uid, gid_t gid, bool use_userns, > const char *userns); > -int isolate_prefork(const struct ctx *c); > +int isolate_prefork(struct ctx *c); > void isolate_postfork(const struct ctx *c); > > #endif /* ISOLATION_H */ > diff --git a/linux_dep.h b/linux_dep.h > index fa539ce..57ebc0d 100644 > --- a/linux_dep.h > +++ b/linux_dep.h > @@ -138,6 +138,36 @@ struct tcp_info_linux { > #define CLOSE_RANGE_UNSHARE (1U << 1) > #endif > > +#include > + > +/* Older C libraries, or kernel headers < 5.2 and < 5.12, need these */ > +#ifndef SYS_open_tree > +#define SYS_open_tree 428 > +#endif > +#ifndef OPEN_TREE_CLONE > +#define OPEN_TREE_CLONE 1 > +#define OPEN_TREE_CLOEXEC O_CLOEXEC > +#endif > +#ifndef AT_RECURSIVE > +#define AT_RECURSIVE 0x8000 > +#endif > +#ifndef SYS_mount_setattr > +#define SYS_mount_setattr 442 > +#endif > +#ifndef MOUNT_ATTR_RDONLY > +#define MOUNT_ATTR_RDONLY 0x00000001 > +#define MOUNT_ATTR_NOSUID 0x00000002 > +#define MOUNT_ATTR_NODEV 0x00000004 > +#define MOUNT_ATTR_NOEXEC 0x00000008 > + > +struct mount_attr { > + uint64_t attr_set; > + uint64_t attr_clr; > + uint64_t propagation; > + uint64_t userns_fd; > +}; > +#endif > + > #ifndef TCP_REPAIR_ON > #define TCP_REPAIR_ON 1 > #define TCP_REPAIR_OFF 0 > diff --git a/passt.1 b/passt.1 > index 0b780fe..126e692 100644 > --- a/passt.1 > +++ b/passt.1 > @@ -287,7 +287,9 @@ Configure the host nameserver which guest or namespace queries to the > \fB\-\-dns-forward\fR address will be redirected to. This option can > be specified zero to two times (once for IPv4, once for IPv6). > By default, the first nameserver from the host's > -\fI/etc/resolv.conf\fR. > +\fI/etc/resolv.conf\fR. In pasta mode, this default follows changes to > +that file: new DNS flows go to its current first nameserver of the same IP > +version, while existing flows keep their target until they expire. > > .TP > .BR \-S ", " \-\-search " " \fIlist > diff --git a/passt.c b/passt.c > index 5054551..3081166 100644 > --- a/passt.c > +++ b/passt.c > @@ -69,6 +69,7 @@ struct ctx passt_ctx = { > .fd_control_listen = -1, > .fd_repair_listen = -1, > .pasta_netns_fd = -1, > + .resolv_dir_fd = -1, > .device_state_fd = -1, > }; > > @@ -92,6 +93,7 @@ char *epoll_type_str[] = { > [EPOLL_TYPE_NL_NEIGH] = "netlink neighbour notifier socket", > [EPOLL_TYPE_CONF_LISTEN] = "configuration listening socket", > [EPOLL_TYPE_CONF] = "configuration socket", > + [EPOLL_TYPE_RESOLV_INOTIFY] = "resolv.conf inotify watch", > }; > static_assert(ARRAY_SIZE(epoll_type_str) == EPOLL_NUM_TYPES, > "epoll_type_str[] doesn't match enum epoll_type"); > @@ -302,6 +304,9 @@ static void passt_worker(void *opaque, int nfds, struct epoll_event *events) > case EPOLL_TYPE_CONF: > conf_handler(c, eventmask); > break; > + case EPOLL_TYPE_RESOLV_INOTIFY: > + conf_resolv_handler(c, ref.fd); > + break; > default: > /* Can't happen */ > assert(0); > diff --git a/passt.h b/passt.h > index afd8e9f..d807222 100644 > --- a/passt.h > +++ b/passt.h > @@ -80,6 +80,7 @@ enum passt_modes { > * @dns_host: Use this DNS on the host for forwarding > * @addr_out: Optional source address for outbound traffic > * @ifname_out: Optional interface name to bind outbound sockets to > + * @dns_host_resolv: @dns_host comes from host's resolv.conf: follow changes > * @no_copy_routes: Don't copy all routes when configuring target namespace > * @no_copy_addrs: Don't copy all addresses when configuring namespace > * @addr_fixed: Address was given explicitly (-a): don't update > @@ -103,6 +104,7 @@ struct ip4_ctx { > > char ifname_out[IFNAMSIZ]; > > + bool dns_host_resolv; > bool no_copy_routes; > bool no_copy_addrs; > bool addr_fixed; > @@ -125,6 +127,7 @@ struct ip4_ctx { > * @dns_host: Use this DNS on the host for forwarding > * @addr_out: Optional source address for outbound traffic > * @ifname_out: Optional interface name to bind outbound sockets to > + * @dns_host_resolv: @dns_host comes from host's resolv.conf: follow changes > * @no_copy_routes: Don't copy all routes when configuring target namespace > * @no_copy_addrs: Don't copy all addresses when configuring namespace > * @addr_fixed: Address was given explicitly (-a): don't update > @@ -149,6 +152,7 @@ struct ip6_ctx { > > char ifname_out[IFNAMSIZ]; > > + bool dns_host_resolv; > bool no_copy_routes; > bool no_copy_addrs; > bool addr_fixed; > @@ -175,6 +179,8 @@ struct ip6_ctx { > * @no_netns_quit: In pasta mode, don't exit if fs-bound namespace is gone > * @netns_base: Base name for fs-bound namespace, if any, in pasta mode > * @netns_dir: Directory of fs-bound namespace, if any, in pasta mode > + * @resolv_dir_fd: Directory of host's resolv.conf, if watched for changes > + * @resolv_base: Base name of host's resolv.conf in @resolv_dir_fd > * @epollfd: File descriptor for epoll instance > * @fd_tap_listen: File descriptor for listening AF_UNIX socket, if any > * @fd_tap: AF_UNIX socket, tuntap device, or pre-opened socket > @@ -252,6 +258,9 @@ struct ctx { > char netns_base[PATH_MAX]; > char netns_dir[PATH_MAX]; > > + int resolv_dir_fd; > + char resolv_base[NAME_MAX + 1]; > + > int epollfd; > int fd_tap_listen; > int fd_tap; -- Stefano