diff --git a/docs/manpage.rst b/docs/manpage.rst index 9c59c17..b5adfb6 100644 --- a/docs/manpage.rst +++ b/docs/manpage.rst @@ -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= diff --git a/sshuttle/client.py b/sshuttle/client.py index 85694fc..bd06e83 100644 --- a/sshuttle/client.py +++ b/sshuttle/client.py @@ -597,7 +597,7 @@ def main(listenip_v6, listenip_v4, # redirect packets outgoing to this server to the remote host # instead. if dns: - nslist += resolvconf_nameservers() + nslist += resolvconf_nameservers(True) if to_nameserver is not None: to_nameserver = "%s@%s" % tuple(to_nameserver[1:]) else: diff --git a/sshuttle/helpers.py b/sshuttle/helpers.py index 185bf0c..9f51586 100644 --- a/sshuttle/helpers.py +++ b/sshuttle/helpers.py @@ -49,17 +49,64 @@ class Fatal(Exception): pass -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 +def resolvconf_nameservers(systemd_resolved): + """Retrieves a list of tuples (address type, address as a string) of + the DNS servers used by the system to resolve hostnames. + + If parameter is False, DNS servers are retrieved from only + /etc/resolv.conf. This behavior makes sense for the sshuttle + server. + + If parameter is True, we retrieve information from both + /etc/resolv.conf and /run/systemd/resolve/resolv.conf (if it + exists). This behavior makes sense for the sshuttle client. + + """ + + # 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, having sshuttle read + # /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'] + if systemd_resolved: + files += ['/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(): - lines = resolvconf_nameservers() +def resolvconf_random_nameserver(systemd_resolved): + """Return a random nameserver selected from servers produced by + resolvconf_nameservers(). See documentation for + resolvconf_nameservers() for a description of the parameter. + """ + lines = resolvconf_nameservers(systemd_resolved) if lines: if len(lines) > 1: # don't import this unless we really need it diff --git a/sshuttle/server.py b/sshuttle/server.py index 88483d4..fe84ba4 100644 --- a/sshuttle/server.py +++ b/sshuttle/server.py @@ -183,7 +183,7 @@ class DnsProxy(Handler): self.tries += 1 if self.to_nameserver is None: - _, peer = resolvconf_random_nameserver() + _, peer = resolvconf_random_nameserver(False) port = 53 else: peer = self.to_ns_peer diff --git a/tests/client/test_helpers.py b/tests/client/test_helpers.py index 3ed588f..732f38a 100644 --- a/tests/client/test_helpers.py +++ b/tests/client/test_helpers.py @@ -131,7 +131,7 @@ nameserver 2404:6800:4004:80c::3 nameserver 2404:6800:4004:80c::4 """) - ns = sshuttle.helpers.resolvconf_nameservers() + ns = sshuttle.helpers.resolvconf_nameservers(False) assert ns == [ (AF_INET, u'192.168.1.1'), (AF_INET, u'192.168.2.1'), (AF_INET, u'192.168.3.1'), (AF_INET, u'192.168.4.1'), @@ -156,7 +156,7 @@ nameserver 2404:6800:4004:80c::2 nameserver 2404:6800:4004:80c::3 nameserver 2404:6800:4004:80c::4 """) - ns = sshuttle.helpers.resolvconf_random_nameserver() + ns = sshuttle.helpers.resolvconf_random_nameserver(False) assert ns in [ (AF_INET, u'192.168.1.1'), (AF_INET, u'192.168.2.1'), (AF_INET, u'192.168.3.1'), (AF_INET, u'192.168.4.1'),