Skip to content

Commit 982add7

Browse files
author
3np
committed
Add support for IPv6 Virtual DNS ([#462](#462))
- Merge '1cho1ce/add-ipv6-dnat-to-ns' into master - fix: properly assign primary/secondary DNS - fix: check ipv4 dns presence by qdb /qubes-primary-dns instead of /qubes-ip - qubes-setup-dnat-to-ns: unify ipv4/ipv6 firewall rule generation
1 parent 3dbe9c3 commit 982add7

File tree

3 files changed

+99
-68
lines changed

3 files changed

+99
-68
lines changed

network/qubes-setup-dnat-to-ns

Lines changed: 75 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import sys
2727

2828
import dbus
2929
import qubesdb
30-
from ipaddress import IPv4Address
30+
from ipaddress import ip_address
3131
import os
3232

3333
def get_dns_resolv_conf():
@@ -42,7 +42,7 @@ def get_dns_resolv_conf():
4242
if len(tokens) < 2 or tokens[0] != "nameserver":
4343
continue
4444
try:
45-
nameservers.append(IPv4Address(tokens[1]))
45+
nameservers.append(ip_address(tokens[1]))
4646
except ValueError:
4747
pass
4848
return nameservers
@@ -74,74 +74,88 @@ def get_dns_resolved():
7474
raise
7575
# Use global entries first
7676
dns.sort(key=lambda x: x[0] != 0)
77-
# Only keep IPv4 entries. systemd-resolved is trusted to return valid
78-
# addresses.
77+
# systemd-resolved is trusted to return valid addresses.
7978
# ToDo: We only need abridged IPv4 DNS entries for ifindex == 0.
8079
# to ensure static DNS of disconnected network interfaces are not added.
81-
return [IPv4Address(bytes(addr)) for ifindex, family, addr in dns
82-
if family == 2]
80+
return [ip_address(bytes(addr)) for ifindex, family, addr in dns]
8381

8482
def install_firewall_rules(dns):
8583
qdb = qubesdb.QubesDB()
86-
qubesdb_dns = []
87-
for i in ('/qubes-netvm-primary-dns', '/qubes-netvm-secondary-dns'):
88-
ns_maybe = qdb.read(i)
89-
if ns_maybe is None:
90-
continue
91-
try:
92-
qubesdb_dns.append(IPv4Address(ns_maybe.decode("ascii", "strict")))
93-
except (UnicodeDecodeError, ValueError):
94-
pass
95-
preamble = [
96-
'add table ip qubes',
97-
# Add the chain so that the subsequent delete will work. If the chain already
98-
# exists this is a harmless no-op.
99-
'add chain ip qubes dnat-dns',
100-
# Delete the chain so that if the chain already exists, it will be removed.
101-
# The removal of the old chain and addition of the new one happen as a single
102-
# atomic operation, so there is no period where neither chain is present or
103-
# where both are present.
104-
'delete chain ip qubes dnat-dns',
105-
]
106-
rules = [
107-
'table ip qubes {',
108-
'chain dnat-dns {',
109-
'type nat hook prerouting priority dstnat; policy accept;',
110-
]
84+
nft_cmd = []
11185
dns_resolved = get_dns_resolved()
112-
if not dns_resolved:
113-
# User has no IPv4 DNS set in sys-net. Maybe IPv6 only environment.
114-
# Or maybe user wants to enforce DNS-Over-HTTPS.
115-
# Drop IPv4 DNS requests to qubesdb_dns addresses.
116-
for vm_nameserver in qubesdb_dns:
117-
vm_ns_ = str(vm_nameserver)
118-
rules += [
119-
f"ip daddr {vm_ns_} udp dport 53 drop",
120-
f"ip daddr {vm_ns_} tcp dport 53 drop",
121-
]
122-
else:
123-
for vm_nameserver, dest in zip(qubesdb_dns, cycle(dns_resolved)):
124-
vm_ns_ = str(vm_nameserver)
125-
dns_ = str(dest)
126-
rules += [
127-
f"ip daddr {vm_ns_} udp dport 53 dnat to {dns_}",
128-
f"ip daddr {vm_ns_} tcp dport 53 dnat to {dns_}",
129-
]
130-
rules += ["}", "}"]
86+
for family in [4, 6]:
87+
ip46 = '6' if family == 6 else ''
88+
dns_servers = [dns_ip for dns_ip in dns_resolved if dns_ip.version == family]
89+
qubesdb_dns = []
90+
for i in (f"/qubes-netvm-primary-dns{ip46}", f"/qubes-netvm-secondary-dns{ip46}"):
91+
ns_maybe = qdb.read(i)
92+
if ns_maybe is None:
93+
continue
94+
try:
95+
qubesdb_dns.append(ip_address(ns_maybe.decode("ascii", "strict")))
96+
except (UnicodeDecodeError, ValueError):
97+
pass
98+
preamble = [
99+
f"add table ip{ip46} qubes",
100+
# Add the chain so that the subsequent delete will work. If the chain already
101+
# exists this is a harmless no-op.
102+
f"add chain ip{ip46} qubes dnat-dns",
103+
# Delete the chain so that if the chain already exists, it will be removed.
104+
# The removal of the old chain and addition of the new one happen as a single
105+
# atomic operation, so there is no period where neither chain is present or
106+
# where both are present.
107+
f"delete chain ip{ip46} qubes dnat-dns",
108+
]
109+
rules = [
110+
f"table ip{ip46} qubes {{",
111+
'chain custom-dnat-dns {}',
112+
'chain dnat-dns {',
113+
'type nat hook prerouting priority dstnat; policy accept;',
114+
'jump custom-dnat-dns',
115+
]
116+
if not dns_servers:
117+
# User has no DNS set in sys-net.
118+
# Or maybe user wants to enforce DNS-Over-HTTPS.
119+
# Drop IPv4 DNS requests to qubesdb_dns addresses.
120+
for vm_nameserver in qubesdb_dns:
121+
vm_ns_ = str(vm_nameserver)
122+
rules += [
123+
f"ip{ip46} daddr {vm_ns_} udp dport 53 drop",
124+
f"ip{ip46} daddr {vm_ns_} tcp dport 53 drop",
125+
]
126+
else:
127+
for (vm_nameserver, dest) in zip(qubesdb_dns, cycle(dns_servers)):
128+
vm_ns_ = str(vm_nameserver)
129+
dns_ = str(dest)
130+
if dest is None or (vm_nameserver == dest and len(qubesdb_dns) == 0):
131+
rules += [
132+
f"ip{ip46} daddr {vm_ns_} tcp dport 53 reject with icmp{ip46} type host-unreachable",
133+
f"ip{ip46} daddr {vm_ns_} udp dport 53 reject with icmp{ip46} type host-unreachable",
134+
]
135+
else:
136+
rules += [
137+
f"ip{ip46} daddr {vm_ns_} udp dport 53 dnat to {dns_}",
138+
f"ip{ip46} daddr {vm_ns_} tcp dport 53 dnat to {dns_}",
139+
]
140+
rules += ["}", "}"]
131141

132-
# check if new rules are the same as the old ones - if so, don't reload
133-
# and return that info via exit code
134-
try:
135-
old_rules = subprocess.check_output(
136-
["nft", "list", "chain", "ip", "qubes", "dnat-dns"]).decode().splitlines()
137-
except subprocess.CalledProcessError:
138-
old_rules = []
139-
old_rules = [line.strip() for line in old_rules]
142+
# check if new rules are the same as the old ones - if so, don't reload
143+
# and return that info via exit code
144+
try:
145+
old_rules = subprocess.check_output(
146+
["nft", "list", "chain", f"ip{ip46}", "qubes", "dnat-dns"]).decode().splitlines()
147+
except subprocess.CalledProcessError:
148+
old_rules = []
149+
old_rules = [line.strip() for line in old_rules]
140150

141-
if old_rules == rules:
142-
sys.exit(100)
151+
if old_rules == rules:
152+
continue
143153

144-
os.execvp("nft", ("nft", "--", "\n".join(preamble + rules)))
154+
nft_cmd += [preamble, rules]
155+
156+
if not nft_cmd:
157+
sys.exit(100)
158+
os.execvp("nft", ("nft", "--", "\n".join(nft_cmd)))
145159

146160
if __name__ == '__main__':
147161
install_firewall_rules(get_dns_resolved())

network/setup-ip

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ configure_network () {
2525
local gateway6="$8"
2626
local primary_dns="$9"
2727
local secondary_dns="${10}"
28-
local custom="${11}"
28+
local primary_dns6="${11}"
29+
local secondary_dns6="${12}"
30+
local custom="${13}"
2931

3032
ip -- address replace "$ip/$netmask" dev "$INTERFACE"
3133
if [[ "$custom" = false ]]; then
@@ -64,16 +66,21 @@ configure_network () {
6466
if [ -h /etc/resolv.conf ]; then
6567
rm -f /etc/resolv.conf
6668
fi
67-
echo > /etc/resolv.conf
69+
echo -n > /etc/resolv.conf
6870
if ! qsvc disable-dns-server ; then
69-
echo "nameserver $primary_dns" > /etc/resolv.conf
71+
if [ -n "$primary_dns6" ]; then
72+
echo "nameserver $primary_dns6" >> /etc/resolv.conf
73+
echo "nameserver $secondary_dns6" >> /etc/resolv.conf
74+
fi
75+
echo "nameserver $primary_dns" >> /etc/resolv.conf
7076
echo "nameserver $secondary_dns" >> /etc/resolv.conf
7177
fi
7278
fi
7379
if [ -x /usr/bin/resolvectl ] && \
7480
systemctl is-enabled -q systemd-resolved.service && \
7581
! qsvc disable-dns-server ; then
76-
resolvectl dns "$INTERFACE" "$primary_dns" "$secondary_dns"
82+
resolvectl dns "$INTERFACE" "$primary_dns6" "$secondary_dns6" \
83+
"$primary_dns" "$secondary_dns"
7784
fi
7885
}
7986

@@ -88,7 +95,9 @@ configure_network_nm () {
8895
local gateway6="$8"
8996
local primary_dns="$9"
9097
local secondary_dns="${10}"
91-
local custom="${11}"
98+
local primary_dns6="${11}"
99+
local secondary_dns6="${12}"
100+
local custom="${13}"
92101

93102
local prefix
94103
local prefix6
@@ -119,6 +128,10 @@ __EOF__
119128
if ! qsvc disable-dns-server ; then
120129
ip4_nm_config="${ip4_nm_config}
121130
dns=${primary_dns};${secondary_dns}"
131+
if [ -n "$primary_dns6" ]; then
132+
ip6_nm_config="${ip6_nm_config}
133+
dns=${primary_dns6};${secondary_dns6}"
134+
fi
122135
fi
123136
if ! qsvc disable-default-route ; then
124137
ip4_nm_config="${ip4_nm_config}
@@ -240,6 +253,8 @@ if [ "$ACTION" == "add" ]; then
240253

241254
primary_dns=$(/usr/bin/qubesdb-read /qubes-primary-dns 2>/dev/null) || primary_dns=
242255
secondary_dns=$(/usr/bin/qubesdb-read /qubes-secondary-dns 2>/dev/null) || secondary_dns=
256+
primary_dns6=$(/usr/bin/qubesdb-read /qubes-primary-dns6 2>/dev/null) || primary_dns6=
257+
secondary_dns6=$(/usr/bin/qubesdb-read /qubes-secondary-dns6 2>/dev/null) || secondary_dns6=
243258
/lib/systemd/systemd-sysctl \
244259
"--prefix=/net/ipv4/conf/all" \
245260
"--prefix=/net/ipv4/neigh/all" \
@@ -253,9 +268,9 @@ if [ "$ACTION" == "add" ]; then
253268
if [ -n "$ip4" ]; then
254269
# If NetworkManager is enabled, let it configure the network
255270
if qsvc network-manager && [ -e /usr/bin/nmcli ]; then
256-
configure_network_nm "$MAC" "$INTERFACE" "$ip4" "$ip6" "$netmask" "$netmask6" "$gateway" "$gateway6" "$primary_dns" "$secondary_dns" "$custom"
271+
configure_network_nm "$MAC" "$INTERFACE" "$ip4" "$ip6" "$netmask" "$netmask6" "$gateway" "$gateway6" "$primary_dns" "$secondary_dns" "$primary_dns6" "$secondary_dns6" "$custom"
257272
else
258-
configure_network "$MAC" "$INTERFACE" "$ip4" "$ip6" "$netmask" "$netmask6" "$gateway" "$gateway6" "$primary_dns" "$secondary_dns" "$custom"
273+
configure_network "$MAC" "$INTERFACE" "$ip4" "$ip6" "$netmask" "$netmask6" "$gateway" "$gateway6" "$primary_dns" "$secondary_dns" "$primary_dns6" "$secondary_dns6" "$custom"
259274
fi
260275

261276
network=$(qubesdb-read /qubes-netvm-network 2>/dev/null) || network=

qubes-rpc/post-install.d/10-qubes-core-agent-features.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@ advertise_systemd_service() {
119119
done
120120
}
121121

122+
qvm-features-request supported-feature.ipv6dns=1
123+
122124
advertise_systemd_service network-manager NetworkManager.service \
123125
network-manager.service
124126
advertise_systemd_service modem-manager ModemManager.service

0 commit comments

Comments
 (0)