Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions ext/installfiles/linux/zerotier-one-dns.rules
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Allow the zerotier-one service user to configure per-interface DNS
// via systemd-resolved. Required because zerotier-one drops root
// privileges to the zerotier-one user while retaining network
// capabilities, but systemd-resolved checks polkit (not capabilities)
// for DNS configuration changes.
polkit.addRule(function(action, subject) {
if ((action.id == "org.freedesktop.resolve1.set-dns-servers" ||
action.id == "org.freedesktop.resolve1.set-domains" ||
action.id == "org.freedesktop.resolve1.revert") &&
subject.user == "zerotier-one") {
return polkit.Result.YES;
}
});
1 change: 1 addition & 0 deletions make-linux.mk
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ ifeq ($(ZT_EXTOSDEP),1)
ONE_OBJS+=osdep/ExtOsdep.o
override DEFS += -DZT_EXTOSDEP
else
ONE_OBJS+=osdep/LinuxDNSHelper.o
ONE_OBJS+=osdep/LinuxEthernetTap.o
ONE_OBJS+=osdep/LinuxNetLink.o
endif
Expand Down
176 changes: 176 additions & 0 deletions osdep/LinuxDNSHelper.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* (c) ZeroTier, Inc.
* https://www.zerotier.com/
*/

#ifdef __linux__

#include "LinuxDNSHelper.hpp"

#include <dirent.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/prctl.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/wait.h>
#include <unistd.h>

#include <linux/capability.h>

// Minimal PATH for subprocess execution, covering usr-merged and non-merged distros.
#define ZT_SUBPROCESS_SAFE_PATH "/usr/bin:/bin:/usr/sbin:/sbin"

namespace ZeroTier {

namespace {

// Same struct layout as one.cpp — avoids pulling in libcap
struct _zt_cap_header_struct {
__u32 version;
int pid;
};
struct _zt_cap_data_struct {
__u32 effective;
__u32 permitted;
__u32 inheritable;
};

static void _dropAllCapabilities()
{
::prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_CLEAR_ALL, 0, 0, 0);
_zt_cap_header_struct hdr = { _LINUX_CAPABILITY_VERSION_1, 0 };
_zt_cap_data_struct data = { 0, 0, 0 };
::syscall(SYS_capset, &hdr, &data);
}

static void _closeExtraFDs(int minFd)
{
// close_range() syscall directly for portability with older glibc.
#ifdef SYS_close_range
if (::syscall(SYS_close_range, (unsigned int)minFd, ~0U, 0) == 0)
return;
#endif
// Fallback: iterate /proc/self/fd (always available on systemd systems)
DIR* d = ::opendir("/proc/self/fd");
if (!d)
return;
int dirfd_val = ::dirfd(d);
struct dirent* de;
while ((de = ::readdir(d)) != nullptr) {
if (de->d_name[0] == '.')
continue;
int fd = ::atoi(de->d_name);
if (fd >= minFd && fd != dirfd_val)
::close(fd);
}
::closedir(d);
}

} // anonymous namespace

bool LinuxDNSHelper::isSystemdResolved()
{
struct stat sb;
return (::stat("/run/systemd/resolve/stub-resolv.conf", &sb) == 0);
}

int LinuxDNSHelper::runResolvectl(const std::vector<std::string>& args)
{
long p = (long)::fork();
if (p > 0) {
int exitcode = -1;
::waitpid(p, &exitcode, 0);
return WIFEXITED(exitcode) ? WEXITSTATUS(exitcode) : -1;
}
else if (p == 0) {
_dropAllCapabilities();
_closeExtraFDs(STDOUT_FILENO);

// resolvectl only needs D-Bus access to systemd-resolved; a minimal
// environment with a safe PATH is sufficient.
::clearenv();
::setenv("PATH", ZT_SUBPROCESS_SAFE_PATH, 1);

std::vector<const char*> argv;
argv.push_back("resolvectl");
for (size_t i = 0; i < args.size(); ++i) {
argv.push_back(args[i].c_str());
}
argv.push_back(nullptr);
::execvp("resolvectl", const_cast<char* const*>(argv.data()));
::_exit(-1);
}
return -1;
}

void LinuxDNSHelper::setDNS(const char* interfaceName, const char* domain, const std::vector<InetAddress>& servers)
{
if (! isSystemdResolved()) {
fprintf(
stderr,
"WARNING: systemd-resolved not detected. DNS configuration for ZeroTier networks requires systemd-resolved or manual configuration. "
"See https://github.com/zerotier/ZeroTierOne/issues/2492 for details" ZT_EOL_S);
return;
}

if (! interfaceName || ! interfaceName[0] || servers.empty()) {
return;
}

// resolvectl dns <interface> <ip1> <ip2> ...
{
std::vector<std::string> args;
args.push_back("dns");
args.push_back(interfaceName);
for (size_t i = 0; i < servers.size(); ++i) {
char buf[64];
args.push_back(servers[i].toIpString(buf));
}
int rc = runResolvectl(args);
if (rc != 0) {
fprintf(stderr, "WARNING: resolvectl dns failed (exit %d) for interface %s" ZT_EOL_S, rc, interfaceName);
return;
}
}

// resolvectl domain <interface> ~<domain>
if (domain && domain[0]) {
std::vector<std::string> args;
args.push_back("domain");
args.push_back(interfaceName);
args.push_back(std::string("~") + domain);
int rc = runResolvectl(args);
if (rc != 0) {
fprintf(stderr, "WARNING: resolvectl domain failed (exit %d) for interface %s" ZT_EOL_S, rc, interfaceName);
}
}
}

void LinuxDNSHelper::removeDNS(const char* interfaceName)
{
if (! isSystemdResolved()) {
return;
}

if (! interfaceName || ! interfaceName[0]) {
return;
}

std::vector<std::string> args;
args.push_back("revert");
args.push_back(interfaceName);
int rc = runResolvectl(args);
if (rc != 0) {
fprintf(stderr, "WARNING: resolvectl revert failed (exit %d) for interface %s" ZT_EOL_S, rc, interfaceName);
}
}

} // namespace ZeroTier

#endif
31 changes: 31 additions & 0 deletions osdep/LinuxDNSHelper.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* (c) ZeroTier, Inc.
* https://www.zerotier.com/
*/

#ifndef LINUX_DNS_HELPER_H_
#define LINUX_DNS_HELPER_H_

#include "../node/InetAddress.hpp"

#include <string>
#include <vector>

namespace ZeroTier {

class LinuxDNSHelper {
public:
static void setDNS(const char* interfaceName, const char* domain, const std::vector<InetAddress>& servers);
static void removeDNS(const char* interfaceName);

private:
static bool isSystemdResolved();
static int runResolvectl(const std::vector<std::string>& args);
};

} // namespace ZeroTier

#endif
7 changes: 7 additions & 0 deletions osdep/LinuxEthernetTap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "../node/Dictionary.hpp"
#include "../node/Mutex.hpp"
#include "../node/Utils.hpp"
#include "LinuxDNSHelper.hpp"
#include "LinuxEthernetTap.hpp"
#include "LinuxNetLink.hpp"
#include "OSUtils.hpp"
Expand Down Expand Up @@ -358,6 +359,7 @@ LinuxEthernetTap::LinuxEthernetTap(
LinuxEthernetTap::~LinuxEthernetTap()
{
_run = false;
LinuxDNSHelper::removeDNS(_dev.c_str());
(void)::write(_shutdownSignalPipe[1], "\0", 1);
::close(_fd);
::close(_shutdownSignalPipe[0]);
Expand Down Expand Up @@ -589,6 +591,11 @@ void LinuxEthernetTap::setMtu(unsigned int mtu)
}
}

void LinuxEthernetTap::setDns(const char* domain, const std::vector<InetAddress>& servers)
{
LinuxDNSHelper::setDNS(_dev.c_str(), domain, servers);
}

} // namespace ZeroTier

#endif // __LINUX__
5 changes: 1 addition & 4 deletions osdep/LinuxEthernetTap.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,7 @@ class LinuxEthernetTap : public EthernetTap {
virtual void setFriendlyName(const char* friendlyName);
virtual void scanMulticastGroups(std::vector<MulticastGroup>& added, std::vector<MulticastGroup>& removed);
virtual void setMtu(unsigned int mtu);
virtual void setDns(const char* domain, const std::vector<InetAddress>& servers)
{
fprintf(stderr, "WARNING: ignoring call to LinuxEthernetTap::setDns on Linux. This is not implemented yet. See https://github.com/zerotier/ZeroTierOne/issues/2492 for details" ZT_EOL_S);
}
virtual void setDns(const char* domain, const std::vector<InetAddress>& servers);

private:
void (*_handler)(void*, void*, uint64_t, const MAC&, const MAC&, unsigned int, unsigned int, const void*, unsigned int);
Expand Down
6 changes: 6 additions & 0 deletions service/OneService.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ namespace sdkresource = opentelemetry::v1::sdk::resource;
#elif defined(__WINDOWS__)
#include "../osdep/WinDNSHelper.hpp"
#include "../osdep/WinFWHelper.hpp"
#elif defined(__linux__)
#include "../osdep/LinuxDNSHelper.hpp"
#endif

#ifdef ZT_USE_SYSTEM_HTTP_PARSER
Expand Down Expand Up @@ -3207,6 +3209,10 @@ class OneServiceImpl : public OneService {
MacDNSHelper::removeDNS(n.config().nwid);
#elif defined(__WINDOWS__)
WinDNSHelper::removeDNS(n.config().nwid);
#elif defined(__linux__)
if (n.tap()) {
LinuxDNSHelper::removeDNS(n.tap()->deviceName().c_str());
}
#endif
}
}
Expand Down
3 changes: 3 additions & 0 deletions zerotier-one.spec
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ make ZT_USE_MINIUPNPC=1 %{?_smp_mflags} ZT_OFFICIAL=1 ZT_NONFREE=1 one
make install DESTDIR=$RPM_BUILD_ROOT
mkdir -p $RPM_BUILD_ROOT%{_unitdir}
cp %{getenv:PWD}/debian/zerotier-one.service $RPM_BUILD_ROOT%{_unitdir}/%{name}.service
mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/polkit-1/rules.d
cp %{getenv:PWD}/ext/installfiles/linux/zerotier-one-dns.rules $RPM_BUILD_ROOT%{_sysconfdir}/polkit-1/rules.d/49-zerotier-one-dns.rules
%else
rm -rf $RPM_BUILD_ROOT
pushd %{getenv:PWD}
Expand All @@ -137,6 +139,7 @@ chmod 0755 $RPM_BUILD_ROOT/etc/init.d/zerotier-one
/etc/init.d/zerotier-one
%else
%{_unitdir}/%{name}.service
%{_sysconfdir}/polkit-1/rules.d/49-zerotier-one-dns.rules
%endif

%post
Expand Down