diff --git a/network/qubes-setup-dnat-to-ns b/network/qubes-setup-dnat-to-ns index 7a760a91..0f639da8 100755 --- a/network/qubes-setup-dnat-to-ns +++ b/network/qubes-setup-dnat-to-ns @@ -23,7 +23,8 @@ from __future__ import annotations import dbus import qubesdb from typing import List -from ipaddress import IPv4Address +from itertools import zip_longest +from ipaddress import ip_address, IPv4Address, IPv6Address import os def get_dns_resolv_conf(): @@ -38,7 +39,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 @@ -69,13 +70,13 @@ 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. - return [IPv4Address(bytes(addr)) for _g, family, addr in dns if family == 2] + # systemd-resolved is trusted to return valid addresses. + return [ip_address(bytes(addr)) for _g, family, addr in dns] def install_firewall_rules(dns): qdb = qubesdb.QubesDB() qubesdb_dns = [] + qubesdb_dns6 = [] for i in ('/qubes-netvm-primary-dns', '/qubes-netvm-secondary-dns'): ns_maybe = qdb.read(i) if ns_maybe is None: @@ -84,6 +85,14 @@ def install_firewall_rules(dns): qubesdb_dns.append(IPv4Address(ns_maybe.decode("ascii", "strict"))) except (UnicodeDecodeError, ValueError): pass + for i in ('/qubes-netvm-primary-dns6', '/qubes-netvm-secondary-dns6'): + ns_maybe = qdb.read(i) + if ns_maybe is None: + continue + try: + qubesdb_dns6.append(IPv6Address(ns_maybe.decode("ascii", "strict"))) + except (UnicodeDecodeError, ValueError): + pass res = [ 'add table ip qubes', # Add the chain so that the subsequent delete will work. If the chain already @@ -95,15 +104,60 @@ def install_firewall_rules(dns): # where both are present. 'delete chain ip qubes dnat-dns', 'table ip qubes {', + 'chain custom-dnat-dns {}', + 'chain dnat-dns {', + 'type nat hook prerouting priority dstnat; policy accept;', + 'jump custom-dnat-dns', + ] + for i, (vm_nameserver, dest) in enumerate(zip_longest(qubesdb_dns, + [dns_ip for dns_ip in dns if type(dns_ip) is IPv4Address])): + if i >= len(qubesdb_dns): + break + dns_ = str(dest) + if dest is None or (vm_nameserver == dest and + qdb.read('/qubes-ip') is None): + res += [ + f"ip daddr {vm_nameserver} tcp dport 53 reject with icmp type host-unreachable", + f"ip daddr {vm_nameserver} udp dport 53 reject with icmp type host-unreachable", + ] + else: + res += [ + f"ip daddr {vm_nameserver} udp dport 53 dnat to {dns_}", + f"ip daddr {vm_nameserver} tcp dport 53 dnat to {dns_}", + ] + res += ["}\n}\n"] + res += [ + 'add table ip6 qubes', + # Add the chain so that the subsequent delete will work. If the chain already + # exists this is a harmless no-op. + 'add chain ip6 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 ip6 qubes dnat-dns', + 'table ip6 qubes {', + 'chain custom-dnat-dns {}', 'chain dnat-dns {', 'type nat hook prerouting priority dstnat; policy accept;', + 'jump custom-dnat-dns', ] - for vm_nameserver, dest in zip(qubesdb_dns, get_dns_resolved()): + for i, (vm_nameserver, dest) in enumerate(zip_longest(qubesdb_dns6, + [dns_ip for dns_ip in dns if type(dns_ip) is IPv6Address])): + if i >= len(qubesdb_dns6): + break dns_ = str(dest) - res += [ - f"ip daddr {vm_nameserver} udp dport 53 dnat to {dns_}", - f"ip daddr {vm_nameserver} tcp dport 53 dnat to {dns_}", - ] + if dest is None or (vm_nameserver == dest and + qdb.read('/qubes-primary-dns6') is None): + res += [ + f"ip6 daddr {vm_nameserver} tcp dport 53 reject with icmpv6 type addr-unreachable", + f"ip6 daddr {vm_nameserver} udp dport 53 reject with icmpv6 type addr-unreachable", + ] + else: + res += [ + f"ip6 daddr {vm_nameserver} udp dport 53 dnat to {dns_}", + f"ip6 daddr {vm_nameserver} tcp dport 53 dnat to {dns_}", + ] res += ["}\n}\n"] os.execvp("nft", ("nft", "--", "\n".join(res))) diff --git a/network/setup-ip b/network/setup-ip index 5821e4de..8836d822 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}" /sbin/ip -- address replace "$ip/$netmask" dev "$INTERFACE" if [[ "$custom" = false ]]; then @@ -64,16 +66,21 @@ configure_network () { if [ -h /etc/resolv.conf ]; then rm -f /etc/resolv.conf fi - echo > /etc/resolv.conf + echo -n > /etc/resolv.conf if ! qsvc disable-dns-server ; then - echo "nameserver $primary_dns" > /etc/resolv.conf + if [ -n "$primary_dns6" ]; then + echo "nameserver $primary_dns6" >> /etc/resolv.conf + echo "nameserver $secondary_dns6" >> /etc/resolv.conf + fi + echo "nameserver $primary_dns" >> /etc/resolv.conf echo "nameserver $secondary_dns" >> /etc/resolv.conf 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 +95,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 +128,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} @@ -183,8 +196,21 @@ configure_qubes_ns() { #netmask=$(qubesdb-read /qubes-netvm-netmask) primary_dns=$(qubesdb-read /qubes-netvm-primary-dns 2>/dev/null || echo "$gateway") secondary_dns=$(qubesdb-read /qubes-netvm-secondary-dns) - echo "NS1=$primary_dns" > /var/run/qubes/qubes-ns - echo "NS2=$secondary_dns" >> /var/run/qubes/qubes-ns + primary_dns6=$(qubesdb-read /qubes-netvm-primary-dns6 ||:) + secondary_dns6=$(qubesdb-read /qubes-netvm-secondary-dns6 ||:) + if [ -n "$primary_dns6" ]; then + cat > /var/run/qubes/qubes-ns<< EOF +NS1=$primary_dns6 +NS1=$secondary_dns6 +NS3=$primary_dns +NS4=$secondary_dns +EOF + else + cat > /var/run/qubes/qubes-ns<< EOF +NS1=$primary_dns +NS1=$secondary_dns +EOF + fi /usr/lib/qubes/qubes-setup-dnat-to-ns } @@ -244,6 +270,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= /usr/lib/systemd/systemd-sysctl \ "--prefix=/net/ipv4/conf/all" \ "--prefix=/net/ipv4/neigh/all" \ @@ -257,9 +285,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 5104ca41..d6a99e1b 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 @@ -37,6 +37,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 diff --git a/vm-systemd/network-proxy-setup.sh b/vm-systemd/network-proxy-setup.sh index 0e20b4f5..87b34c79 100755 --- a/vm-systemd/network-proxy-setup.sh +++ b/vm-systemd/network-proxy-setup.sh @@ -19,9 +19,22 @@ if [ "x$network" != "x" ]; then #netmask=$(qubesdb-read /qubes-netvm-netmask) primary_dns=$(qubesdb-read /qubes-netvm-primary-dns 2>/dev/null || echo "$gateway") secondary_dns=$(qubesdb-read /qubes-netvm-secondary-dns) + primary_dns6=$(qubesdb-read /qubes-netvm-primary-dns6 ||:) + secondary_dns6=$(qubesdb-read /qubes-netvm-secondary-dns6 ||:) modprobe netbk 2> /dev/null || modprobe xen-netback || "${modprobe_fail_cmd}" - echo "NS1=$primary_dns" > /var/run/qubes/qubes-ns - echo "NS2=$secondary_dns" >> /var/run/qubes/qubes-ns + if [ -n "$primary_dns6" ]; then + cat > /var/run/qubes/qubes-ns<< EOF +NS1=$primary_dns6 +NS1=$secondary_dns6 +NS3=$primary_dns +NS4=$secondary_dns +EOF + else + cat > /var/run/qubes/qubes-ns<< EOF +NS1=$primary_dns +NS1=$secondary_dns +EOF + fi /usr/lib/qubes/qubes-setup-dnat-to-ns echo "1" > /proc/sys/net/ipv4/ip_forward # enable also IPv6 forwarding, if IPv6 is enabled