Skip to content
Draft
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
135 changes: 72 additions & 63 deletions network/qubes-setup-dnat-to-ns
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand All @@ -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
Expand Down Expand Up @@ -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()
34 changes: 25 additions & 9 deletions network/setup-ip
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 <<EOF | grep -v 'nameserver\s*$' > /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
}

Expand All @@ -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
Expand Down Expand Up @@ -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}
Expand Down Expand Up @@ -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" \
Expand 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=
Expand Down
2 changes: 2 additions & 0 deletions qubes-rpc/post-install.d/10-qubes-core-agent-features.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down