Intercept DNS requests sent by systemd-resolved.

Previously, we would find DNS servers we wish to intercept traffic on
by reading /etc/resolv.conf. On systems using systemd-resolved,
/etc/resolv.conf points to localhost and then systemd-resolved
actually uses the DNS servers listed in
/run/systemd/resolve/resolv.conf. Many programs will route the DNS
traffic through localhost as /etc/resolv.conf indicates and sshuttle
would capture it. However, systemd-resolved also provides other
interfaces for programs to resolve hostnames besides the localhost
server in /etc/resolv.conf.

This patch adds systemd-resolved's servers into the list of DNS
servers when --dns is used.

Note that sshuttle will continue to fail to intercept any traffic sent
to port 853 for DNS over TLS (which systemd-resolved also supports).

For more info, see:
sshuttle issue #535
https://www.freedesktop.org/software/systemd/man/systemd-resolved.service.html
https://github.com/systemd/systemd/issues/6076
This commit is contained in:
Scott Kuhl 2020-10-25 12:29:32 -04:00
parent 5c8c707208
commit 502960d796
2 changed files with 43 additions and 8 deletions

View File

@ -104,10 +104,12 @@ Options
Capture local DNS requests and forward to the remote DNS
server. All queries to any of the local system's DNS
servers (/etc/resolv.conf) will be intercepted and
servers (/etc/resolv.conf and, if it exists,
/run/systemd/resolve/resolv.conf) will be intercepted and
resolved on the remote side of the tunnel instead, there
using the DNS specified via the :option:`--to-ns` option,
if specified.
if specified. Only plain DNS traffic sent to these servers
on port 53 are captured.
.. option:: --ns-hosts=<server1[,server2[,server3[...]]]>

View File

@ -49,12 +49,45 @@ class Fatal(Exception):
def resolvconf_nameservers():
lines = []
for line in open('/etc/resolv.conf'):
words = line.lower().split()
if len(words) >= 2 and words[0] == 'nameserver':
lines.append(family_ip_tuple(words[1]))
return lines
"""Retrieves a list of tuples (address type, address as a string) that
the current system uses to resolve hostnames from /etc/resolv.conf
and possibly other files.
"""
# Historically, we just needed to read /etc/resolv.conf.
#
# If systemd-resolved is active, /etc/resolv.conf will point to
# localhost and the actual DNS servers that systemd-resolved uses
# are stored in /run/systemd/resolve/resolv.conf. For programs
# that use the localhost DNS server, only reading /etc/resolv.conf
# is sufficient. However, resolved provides other ways of
# resolving hostnames (such as via dbus) that may not route
# requests through localhost. So, we retrieve a list of DNS
# servers that resolved uses so we can intercept those as well.
#
# For more information about systemd-resolved, see:
# https://www.freedesktop.org/software/systemd/man/systemd-resolved.service.html
#
# On machines without systemd-resolved, we expect opening the
# second file will fail.
files = ['/etc/resolv.conf', '/run/systemd/resolve/resolv.conf']
nsservers = []
for f in files:
this_file_nsservers = []
try:
for line in open(f):
words = line.lower().split()
if len(words) >= 2 and words[0] == 'nameserver':
this_file_nsservers.append(family_ip_tuple(words[1]))
debug2("Found DNS servers in %s: %s\n" %
(f, [n[1] for n in this_file_nsservers]))
nsservers += this_file_nsservers
except OSError as e:
debug3("Failed to read %s when looking for DNS servers: %s\n" %
(f, e.strerror))
return nsservers
def resolvconf_random_nameserver():