diff --git a/network/qubes-setup-dnat-to-ns b/network/qubes-setup-dnat-to-ns index aba447c77..61b3a23d4 100755 --- a/network/qubes-setup-dnat-to-ns +++ b/network/qubes-setup-dnat-to-ns @@ -21,13 +21,14 @@ from __future__ import annotations +from typing import List from itertools import cycle import subprocess import sys import dbus import qubesdb -from ipaddress import IPv4Address +from ipaddress import ip_address, IPv4Address, IPv6Address import os def get_dns_resolv_conf(): @@ -42,7 +43,7 @@ def get_dns_resolv_conf(): if len(tokens) < 2 or tokens[0] != "nameserver": continue try: - nameservers.append(IPv4Address(tokens[1])) + nameservers.append(ip_address(tokens[1])) except ValueError: pass return nameservers @@ -74,74 +75,82 @@ def get_dns_resolved(): raise # Use global entries first dns.sort(key=lambda x: x[0] != 0) - # Only keep IPv4 entries. systemd-resolved is trusted to return valid - # addresses. + # systemd-resolved is trusted to return valid addresses. # ToDo: We only need abridged IPv4 DNS entries for ifindex == 0. # to ensure static DNS of disconnected network interfaces are not added. - return [IPv4Address(bytes(addr)) for ifindex, family, addr in dns - if family == 2] + return [ip_address(bytes(addr)) for ifindex, family, addr in dns] -def install_firewall_rules(dns): +def install_firewall_rules(): qdb = qubesdb.QubesDB() - qubesdb_dns = [] - for i in ('/qubes-netvm-primary-dns', '/qubes-netvm-secondary-dns'): - ns_maybe = qdb.read(i) - if ns_maybe is None: - continue + nft_cmd: List[str] = [] + dns_resolved: List[IPv4Address | IPv6Address] = get_dns_resolved() + for family in [4, 6]: + ip46 = '6' if family == 6 else '' + dns_servers = [dns_ip for dns_ip in dns_resolved if dns_ip.version == family] + qubesdb_dns: List[IPv4Address | IPv6Address] = [] + for i in (f"/qubes-netvm-primary-dns{ip46}", f"/qubes-netvm-secondary-dns{ip46}"): + ns_maybe = qdb.read(i) + if ns_maybe is None: + continue + try: + qubesdb_dns.append(ip_address(ns_maybe.decode("ascii", "strict"))) + except (UnicodeDecodeError, ValueError): + pass + preamble = [ + f"add table ip{ip46} qubes", + # Add the chain so that the subsequent delete will work. If the chain already + # exists this is a harmless no-op. + f"add chain ip{ip46} qubes dnat-dns", + # Delete the chain so that if the chain already exists, it will be removed. + # The removal of the old chain and addition of the new one happen as a single + # atomic operation, so there is no period where neither chain is present or + # where both are present. + f"delete chain ip{ip46} qubes dnat-dns", + ] + rules = [ + f"table ip{ip46} qubes {{", + 'chain custom-dnat-dns {}', + 'chain dnat-dns {', + 'type nat hook prerouting priority dstnat; policy accept;', + 'jump custom-dnat-dns', + ] + if not dns_servers: + # User has no DNS set in NetVM. + # Or maybe user wants to enforce DNS-Over-HTTPS. + # Drop DNS requests to qubesdb_dns addresses. + for vm_nameserver in qubesdb_dns: + vm_ns_ = str(vm_nameserver) + rules += [ + f"ip{ip46} daddr {vm_ns_} udp dport 53 drop", + f"ip{ip46} daddr {vm_ns_} tcp dport 53 drop", + ] + else: + for (vm_nameserver, dest) in zip(qubesdb_dns, cycle(dns_servers)): + vm_ns_ = str(vm_nameserver) + dns_ = str(dest) + rules += [ + f"ip{ip46} daddr {vm_ns_} udp dport 53 dnat to {dns_}", + f"ip{ip46} daddr {vm_ns_} tcp dport 53 dnat to {dns_}", + ] + rules += ["}", "}"] + + # check if new rules are the same as the old ones - if so, don't reload + # and return that info via exit code try: - qubesdb_dns.append(IPv4Address(ns_maybe.decode("ascii", "strict"))) - except (UnicodeDecodeError, ValueError): - pass - preamble = [ - 'add table ip qubes', - # Add the chain so that the subsequent delete will work. If the chain already - # exists this is a harmless no-op. - 'add chain ip qubes dnat-dns', - # Delete the chain so that if the chain already exists, it will be removed. - # The removal of the old chain and addition of the new one happen as a single - # atomic operation, so there is no period where neither chain is present or - # where both are present. - 'delete chain ip qubes dnat-dns', - ] - rules = [ - 'table ip qubes {', - 'chain dnat-dns {', - 'type nat hook prerouting priority dstnat; policy accept;', - ] - dns_resolved = get_dns_resolved() - if not dns_resolved: - # User has no IPv4 DNS set in sys-net. Maybe IPv6 only environment. - # Or maybe user wants to enforce DNS-Over-HTTPS. - # Drop IPv4 DNS requests to qubesdb_dns addresses. - for vm_nameserver in qubesdb_dns: - vm_ns_ = str(vm_nameserver) - rules += [ - f"ip daddr {vm_ns_} udp dport 53 drop", - f"ip daddr {vm_ns_} tcp dport 53 drop", - ] - else: - for vm_nameserver, dest in zip(qubesdb_dns, cycle(dns_resolved)): - vm_ns_ = str(vm_nameserver) - dns_ = str(dest) - rules += [ - f"ip daddr {vm_ns_} udp dport 53 dnat to {dns_}", - f"ip daddr {vm_ns_} tcp dport 53 dnat to {dns_}", - ] - rules += ["}", "}"] + old_rules = subprocess.check_output( + ["nft", "list", "chain", f"ip{ip46}", "qubes", "dnat-dns"]).decode().splitlines() + except subprocess.CalledProcessError: + old_rules = [] + old_rules = [line.strip() for line in old_rules] - # check if new rules are the same as the old ones - if so, don't reload - # and return that info via exit code - try: - old_rules = subprocess.check_output( - ["nft", "list", "chain", "ip", "qubes", "dnat-dns"]).decode().splitlines() - except subprocess.CalledProcessError: - old_rules = [] - old_rules = [line.strip() for line in old_rules] + if old_rules == rules: + continue - if old_rules == rules: - sys.exit(100) + nft_cmd += [*preamble, *rules] - os.execvp("nft", ("nft", "--", "\n".join(preamble + rules))) + if not nft_cmd: + sys.exit(100) + os.execvp("nft", ("nft", "--", "\n".join(nft_cmd))) if __name__ == '__main__': - install_firewall_rules(get_dns_resolved()) + install_firewall_rules() diff --git a/network/setup-ip b/network/setup-ip index 7ee3c2d41..a51d4fb2c 100755 --- a/network/setup-ip +++ b/network/setup-ip @@ -25,7 +25,9 @@ configure_network () { local gateway6="$8" local primary_dns="$9" local secondary_dns="${10}" - local custom="${11}" + local primary_dns6="${11}" + local secondary_dns6="${12}" + local custom="${13}" ip -- address replace "$ip/$netmask" dev "$INTERFACE" if [[ "$custom" = false ]]; then @@ -64,16 +66,22 @@ configure_network () { if [ -h /etc/resolv.conf ]; then rm -f /etc/resolv.conf fi - echo > /etc/resolv.conf - if ! qsvc disable-dns-server ; then - echo "nameserver $primary_dns" > /etc/resolv.conf - echo "nameserver $secondary_dns" >> /etc/resolv.conf + if qsvc disable-dns-server ; then + echo -n > /etc/resolv.conf + else + cat < /etc/resolv.conf +nameserver ${primary_dns6} +nameserver ${secondary_dns6} +nameserver ${primary_dns} +nameserver ${secondary_dns} +EOF fi fi if [ -x /usr/bin/resolvectl ] && \ systemctl is-enabled -q systemd-resolved.service && \ ! qsvc disable-dns-server ; then - resolvectl dns "$INTERFACE" "$primary_dns" "$secondary_dns" + resolvectl dns "$INTERFACE" "$primary_dns6" "$secondary_dns6" \ + "$primary_dns" "$secondary_dns" fi } @@ -88,7 +96,9 @@ configure_network_nm () { local gateway6="$8" local primary_dns="$9" local secondary_dns="${10}" - local custom="${11}" + local primary_dns6="${11}" + local secondary_dns6="${12}" + local custom="${13}" local prefix local prefix6 @@ -119,6 +129,10 @@ __EOF__ if ! qsvc disable-dns-server ; then ip4_nm_config="${ip4_nm_config} dns=${primary_dns};${secondary_dns}" + if [ -n "$primary_dns6" ]; then + ip6_nm_config="${ip6_nm_config} +dns=${primary_dns6};${secondary_dns6}" + fi fi if ! qsvc disable-default-route ; then ip4_nm_config="${ip4_nm_config} @@ -240,6 +254,8 @@ if [ "$ACTION" == "add" ]; then primary_dns=$(/usr/bin/qubesdb-read /qubes-primary-dns 2>/dev/null) || primary_dns= secondary_dns=$(/usr/bin/qubesdb-read /qubes-secondary-dns 2>/dev/null) || secondary_dns= + primary_dns6=$(/usr/bin/qubesdb-read /qubes-primary-dns6 2>/dev/null) || primary_dns6= + secondary_dns6=$(/usr/bin/qubesdb-read /qubes-secondary-dns6 2>/dev/null) || secondary_dns6= /lib/systemd/systemd-sysctl \ "--prefix=/net/ipv4/conf/all" \ "--prefix=/net/ipv4/neigh/all" \ @@ -253,9 +269,9 @@ if [ "$ACTION" == "add" ]; then if [ -n "$ip4" ]; then # If NetworkManager is enabled, let it configure the network if qsvc network-manager && [ -e /usr/bin/nmcli ]; then - configure_network_nm "$MAC" "$INTERFACE" "$ip4" "$ip6" "$netmask" "$netmask6" "$gateway" "$gateway6" "$primary_dns" "$secondary_dns" "$custom" + configure_network_nm "$MAC" "$INTERFACE" "$ip4" "$ip6" "$netmask" "$netmask6" "$gateway" "$gateway6" "$primary_dns" "$secondary_dns" "$primary_dns6" "$secondary_dns6" "$custom" else - configure_network "$MAC" "$INTERFACE" "$ip4" "$ip6" "$netmask" "$netmask6" "$gateway" "$gateway6" "$primary_dns" "$secondary_dns" "$custom" + configure_network "$MAC" "$INTERFACE" "$ip4" "$ip6" "$netmask" "$netmask6" "$gateway" "$gateway6" "$primary_dns" "$secondary_dns" "$primary_dns6" "$secondary_dns6" "$custom" fi network=$(qubesdb-read /qubes-netvm-network 2>/dev/null) || network= diff --git a/qubes-rpc/post-install.d/10-qubes-core-agent-features.sh b/qubes-rpc/post-install.d/10-qubes-core-agent-features.sh index f9d85fcbd..e065b72e5 100755 --- a/qubes-rpc/post-install.d/10-qubes-core-agent-features.sh +++ b/qubes-rpc/post-install.d/10-qubes-core-agent-features.sh @@ -119,6 +119,8 @@ advertise_systemd_service() { done } +qvm-features-request supported-feature.ipv6dns=1 + advertise_systemd_service network-manager NetworkManager.service \ network-manager.service advertise_systemd_service modem-manager ModemManager.service