Qubes 4.2 support.

This commit is contained in:
Manuel Amador (Rudd-O) 2024-02-05 21:51:59 +00:00
parent 06c5b1b0ae
commit cf2945e742
13 changed files with 2089 additions and 296 deletions

1
.gitignore vendored
View File

@ -9,3 +9,4 @@ pkgs/
build
*.egg-info
src/*.service
.mypy_cache

View File

@ -16,7 +16,7 @@ ROOT_DIR := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
clean:
cd $(ROOT_DIR) || exit $$? ; find -name '*.pyc' -o -name '*~' -print0 | xargs -0 rm -f
cd $(ROOT_DIR) || exit $$? ; rm -rf *.tar.gz *.rpm
cd $(ROOT_DIR) || exit $$? ; rm -rf *.egg-info build
cd $(ROOT_DIR) || exit $$? ; rm -rf *.egg-info build .mypy_cache
dist: clean
@which rpmspec || { echo 'rpmspec is not available. Please install the rpm-build package with the command `dnf install rpm-build` to continue, then rerun this step.' ; exit 1 ; }
@ -32,12 +32,13 @@ rpm: dist
cd $(ROOT_DIR) ; mv -f builddir.rpm/*/* . && rm -rf builddir.rpm
install-template: all
PYTHONDONTWRITEBYTECODE=1 python3 routingmanagersetup.py install $(PYTHON_PREFIX_ARG) -O0 --root $(DESTDIR)
install -Dm 755 src/qubes-routing-manager -t $(DESTDIR)/$(SBINDIR)/
sed -i "s,^#!.*,#!$(PYTHON)," $(DESTDIR)/$(SBINDIR)/qubes-routing-manager
install -Dm 644 src/qubes-routing-manager.service -t $(DESTDIR)/$(UNITDIR)/
# Python 3 is always used for Qubes admin package.
install-dom0:
PYTHONDONTWRITEBYTECODE=1 python3 setup.py install $(PYTHON_PREFIX_ARG) -O0 --root $(DESTDIR)
PYTHONDONTWRITEBYTECODE=1 python3 networkserversetup.py install $(PYTHON_PREFIX_ARG) -O0 --root $(DESTDIR)
install: install-dom0 install-template

View File

@ -1,12 +1,12 @@
# Qubes network server
This software lets you turn your [Qubes OS 4.1](https://www.qubes-os.org/) machine into
This software lets you turn your [Qubes OS 4.2](https://www.qubes-os.org/) machine into
a network server, enjoying all the benefits of Qubes OS (isolation, secure
inter-VM process communication, ease of use) with none of the drawbacks
of setting up your own Xen server.
This release is only intended for use with Qubes OS 4.1. Older Qubes OS releases
will not support it. For Qubes OS 4.0, check branch `r4.0`.
This release is only intended for use with Qubes OS 4.2. Older Qubes OS releases
will not support it. For Qubes OS 4.1, check branch `r4.1`.
## Why?

View File

@ -1 +1 @@
["RELEASE": "q4.1 37"]
["RELEASE": "q4.2 37 38 39"]

26
networkserversetup.py Normal file
View File

@ -0,0 +1,26 @@
import os
import setuptools
if __name__ == "__main__":
version = (
open(os.path.join(os.path.dirname(__file__), "qubes-network-server.spec"))
.read()
.strip()
)
version = [v for v in version.splitlines() if v.startswith("Version:")][0]
version = version.split()[-1]
setuptools.setup(
name="qubesnetworkserver",
version=version,
author="Manuel Amador (Rudd-O)",
author_email="rudd-o@rudd-o.com",
description="Qubes network server dom0 component",
license="GPL2+",
url="https://github.com/Rudd-O/qubes-network-server",
packages=("qubesnetworkserver",),
entry_points={
"qubes.ext": [
"qubesnetworkserver = qubesnetworkserver:QubesNetworkServerExtension",
],
},
)

View File

@ -3,7 +3,7 @@
%define mybuildnumber %{?build_number}%{?!build_number:1}
Name: qubes-network-server
Version: 0.0.19
Version: 0.1.0
Release: %{mybuildnumber}%{?dist}
Summary: Turn your Qubes OS into a network server
BuildArch: noarch
@ -20,9 +20,10 @@ BuildRequires: python3
BuildRequires: python3-rpm-macros
BuildRequires: systemd-rpm-macros
Requires: qubes-core-agent-networking >= 4.1
Requires: qubes-core-agent-networking >= 4.2
Requires: python3
Requires: python3-qubesdb
Requires: nftables
%description
This package lets you turn your Qubes OS into a network server. Install this
@ -44,7 +45,7 @@ BuildRequires: python3-rpm-macros
BuildRequires: python3-setuptools
Requires: python3
Requires: qubes-core-dom0 >= 4.1
Requires: qubes-core-dom0 >= 4.2
%description -n qubes-core-admin-addon-network-server
This package lets you turn your Qubes OS into a network server. Install this
@ -70,6 +71,8 @@ echo 'enable qubes-routing-manager.service' > "$RPM_BUILD_ROOT"/%{_presetdir}/75
%files
%attr(0755, root, root) %{_sbindir}/qubes-routing-manager
%attr(0644, root, root) %{python3_sitelib}/qubesroutingmanager/*
%{python3_sitelib}/qubesroutingmanager-*.egg-info
%attr(0644, root, root) %{_presetdir}/75-%{name}.preset
%config %attr(0644, root, root) %{_unitdir}/qubes-routing-manager.service
%doc README.md TODO

View File

@ -0,0 +1,310 @@
#!/usr/bin/python3
import json
import logging
import subprocess
from typing import TypedDict, Any, cast, Literal
ADDRESS_FAMILIES = Literal["ip"] | Literal["ip6"]
class Chain(TypedDict):
name: str
family: str
table: str
handle: int
type: str
hook: str
prio: int
policy: str
class Table(TypedDict):
family: str
name: str
handle: int
class Metainfo(TypedDict):
version: str
release_name: str
json_schema_version: int
class Rule(TypedDict):
family: str
table: str
chain: str
handle: int
expr: list[dict[str, Any]]
class ChainContainer(TypedDict):
chain: Chain
class MetainfoContainer(TypedDict):
metainfo: Metainfo
class TableContainer(TypedDict):
table: Table
class RuleContainer(TypedDict):
rule: Rule
class NFTablesOutput(TypedDict):
nftables: list[ChainContainer | MetainfoContainer | TableContainer | RuleContainer]
ADDRESS_FAMILY_IPV6 = "ip6"
ADDRESS_FAMILY_IPV4 = "ip"
TABLE_NAME = "qubes"
FORWARD_CHAIN_NAME = "forward"
ROUTING_MANAGER_CHAIN_NAME = "qubes-routing-manager"
NFTABLES_CMD = "nft"
ADD_RULE_AFTER_THIS_RULE = "custom-forward"
def get_table(address_family: ADDRESS_FAMILIES, table: str) -> NFTablesOutput:
return cast(
NFTablesOutput,
json.loads(
subprocess.check_output(
[NFTABLES_CMD, "-n", "-j", "list", "table", address_family, table],
text=True,
)
),
)
def add_chain(address_family: ADDRESS_FAMILIES, table: str, chain: str) -> None:
subprocess.check_output(
[
NFTABLES_CMD,
"-n",
"-j",
"add",
"chain",
address_family,
table,
chain,
],
text=True,
)
def append_rule_at_end(
address_family: ADDRESS_FAMILIES, table: str, chain: str, *rest: str
) -> None:
subprocess.check_output(
[
NFTABLES_CMD,
"-n",
"-j",
"add",
"rule",
address_family,
table,
chain,
]
+ list(rest),
text=True,
)
def append_counter_at_end(
address_family: ADDRESS_FAMILIES, table: str, chain: str, *rest: str
) -> None:
subprocess.check_output(
[
NFTABLES_CMD,
"-n",
"-j",
"add",
"rule",
address_family,
table,
chain,
"counter",
]
+ list(rest),
text=True,
)
def append_rule_after(
address_family: ADDRESS_FAMILIES, table: str, chain: str, handle: int, *rest: str
) -> None:
subprocess.check_output(
[
NFTABLES_CMD,
"-n",
"-j",
"add",
"rule",
address_family,
table,
chain,
"position",
str(handle),
]
+ list(rest),
text=True,
)
def delete_rule(
address_family: ADDRESS_FAMILIES, table: str, chain: str, handle: int
) -> None:
subprocess.check_output(
[
NFTABLES_CMD,
"-n",
"-j",
"delete",
"rule",
address_family,
table,
chain,
"handle",
str(handle),
],
text=True,
)
def setup_plain_forwarding_for_address(source: str, enable: bool, family: int) -> None:
logging.info("Handling forwarding for address %s family %s.", source, family)
af = cast(
ADDRESS_FAMILIES,
ADDRESS_FAMILY_IPV6 if family == 6 else ADDRESS_FAMILY_IPV4,
)
# table ip qubes {
# set downstream {
# type ipv4_addr
# elements = { 10.137.0.10, 10.250.4.13 }
# }
# ...
existing_table_output = get_table(af, TABLE_NAME)
existing_table_items = existing_table_output["nftables"]
existing_chains = [x["chain"] for x in existing_table_items if "chain" in x] # type: ignore
existing_rules = [x["rule"] for x in existing_table_items if "rule" in x] # type: ignore
try:
forward_chain = [x for x in existing_chains if x["name"] == FORWARD_CHAIN_NAME][
0
]
except IndexError:
logging.warn(
"No forward chain in table %s, not setting up forwarding", TABLE_NAME
)
return
qubes_routing_manager_chain: None | Chain = None
try:
qubes_routing_manager_chain = [
x for x in existing_chains if x["name"] == ROUTING_MANAGER_CHAIN_NAME
].pop()
except IndexError:
pass
if not qubes_routing_manager_chain:
logging.info(
"Adding %s chain to table %s", ROUTING_MANAGER_CHAIN_NAME, TABLE_NAME
)
add_chain(af, TABLE_NAME, ROUTING_MANAGER_CHAIN_NAME)
qubes_routing_manager_rule: None | Rule = None
try:
qubes_routing_manager_rule = [
x
for x in existing_rules
if x["chain"] == forward_chain["name"]
and x["family"] == af
and len(x["expr"]) == 1
and x["expr"][0].get("jump", {}).get("target") == ROUTING_MANAGER_CHAIN_NAME
].pop()
except IndexError:
pass
if not qubes_routing_manager_rule:
try:
custom_forwarding_rule = [
x
for x in existing_rules
if x["chain"] == forward_chain["name"]
and len(x["expr"]) == 1
and x["expr"][0].get("jump", {}).get("target")
== ADD_RULE_AFTER_THIS_RULE
][0]
except IndexError:
logging.warn(
"No state forwarding rule in chain %s of table %s, not setting up forwarding",
forward_chain["name"],
TABLE_NAME,
)
logging.info(
"Adding rule to jump to %s to table %s after jump to %s",
ROUTING_MANAGER_CHAIN_NAME,
TABLE_NAME,
ADD_RULE_AFTER_THIS_RULE,
)
append_rule_after(
af,
TABLE_NAME,
forward_chain["name"],
custom_forwarding_rule["handle"],
"jump",
ROUTING_MANAGER_CHAIN_NAME,
)
append_counter_at_end(
af,
TABLE_NAME,
ROUTING_MANAGER_CHAIN_NAME,
)
address_rules = [
x
for x in existing_rules
if x["chain"] == ROUTING_MANAGER_CHAIN_NAME
and len(x["expr"]) == 2
and x["expr"][0].get("match", {}).get("op", {}) == "=="
and x["expr"][0]["match"].get("left", {}).get("payload", {}).get("protocol", "")
== af
and x["expr"][0]["match"]["left"]["payload"].get("field", "") == "daddr"
and x["expr"][0].get("match", {}).get("right", []) == source
and "accept" in x["expr"][1]
]
if enable and not address_rules:
logging.info(
"Adding accept rule on chain %s to allow traffic to %s.",
ROUTING_MANAGER_CHAIN_NAME,
source,
)
append_rule_at_end(
af,
TABLE_NAME,
ROUTING_MANAGER_CHAIN_NAME,
af,
"daddr",
source,
"accept",
)
elif not enable and address_rules:
logging.info(
"Removing %s accept rules from chain %s to stop traffic to %s.",
len(address_rules),
ROUTING_MANAGER_CHAIN_NAME,
source,
)
for rule in reversed(sorted(address_rules, key=lambda r: r["handle"])):
delete_rule(af, TABLE_NAME, ROUTING_MANAGER_CHAIN_NAME, rule["handle"])

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,135 @@
#!/usr/bin/python3
"""
This program reads the /qubes-firewall/{ip}/qubes-routing-method file
for any firewall configuration, then configures the network to obey
the routing method for the VM. If the routing method is "masquerade",
then nothing happens. If, however, the routing method is "forward",
then VM-specific rules are enacted in the VM's attached NetVM to allow
traffic coming from other VMs and the outside world to reach this VM.
"""
import glob
import logging
import os
import socket
import qubesdb
from qubesroutingmanager import setup_plain_forwarding_for_address
FORWARD_ROUTING_METHOD = "forward"
def _s(v):
if isinstance(v, bytes):
return v.decode("utf-8")
return v
class AdjunctWorker(object):
def __init__(self):
self.qdb = qubesdb.QubesDB()
@staticmethod
def is_ip6(addr):
return addr.count(":") > 0
def setup_proxy_arp_ndp(self, enabled, family):
# If any of the IP addresses is assigned the forward routing method,
# then enable proxy ARP/NDP on the upstream interfaces, so that the
# interfaces in question will impersonate the IP addresses in question.
# Ideally, this impersonation would be exclusively done for the
# specific IP addresses in question, but it is not clear to me how
# to cause this outcome to take place.
if family == 6:
globber = "/proc/sys/net/ipv6/conf/*/proxy_ndp"
name = "proxy NDP"
elif family == 4:
globber = "/proc/sys/net/ipv4/conf/*/proxy_arp"
name = "proxy ARP"
else:
return
if enabled:
action = "Enabling"
val = "1\n"
else:
action = "Disabling"
val = "0\n"
matches = glob.glob(globber)
for m in matches:
iface = m.split("/")[6]
if iface in ("all", "lo") or iface.startswith("vif"):
# No need to enable it for "all", or VIFs, or loopback.
continue
with open(m, "w+") as f:
oldval = f.read()
if oldval != val:
logging.info("%s %s on interface %s.", action, name, iface)
f.seek(0)
f.write(val)
def handle_addr(self, addr):
# Setup plain forwarding for this specific address.
routing_method = _s(self.qdb.read("/qubes-routing-method/{}".format(addr)))
setup_plain_forwarding_for_address(
addr,
routing_method == FORWARD_ROUTING_METHOD,
6 if self.is_ip6(addr) else 4,
)
# Manipulate proxy ARP for all known addresses.
methods = [
(_s(k).split("/")[2], _s(v))
for k, v in self.qdb.multiread("/qubes-routing-method/").items()
]
mmethods = {
4: [m[1] for m in methods if not self.is_ip6(m[0])],
6: [m[1] for m in methods if self.is_ip6(m[0])],
}
for family, methods in mmethods.items():
self.setup_proxy_arp_ndp(
FORWARD_ROUTING_METHOD in methods,
family,
)
def list_targets(self):
return set(_s(t).split("/")[2] for t in self.qdb.list("/qubes-routing-method/"))
def sd_notify(self, state):
"""Send notification to systemd, if available"""
# based on sdnotify python module
if "NOTIFY_SOCKET" not in os.environ:
return
addr = os.environ["NOTIFY_SOCKET"]
if addr[0] == "@":
addr = "\0" + addr[1:]
try:
sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
sock.connect(addr)
sock.sendall(state.encode())
except BaseException:
# generally ignore error on systemd notification
pass
def main(self):
logging.basicConfig(level=logging.INFO)
self.qdb.watch("/qubes-routing-method/")
for source_addr in self.list_targets():
self.handle_addr(source_addr)
self.sd_notify("READY=1")
try:
for watch_path in iter(self.qdb.read_watch, None):
# ignore writing rules itself - wait for final write at
# source_addr level empty write (/qubes-firewall/SOURCE_ADDR)
watch_path = _s(watch_path)
if watch_path.count("/") != 2:
continue
source_addr = watch_path.split("/")[2]
self.handle_addr(source_addr)
except OSError: # EINTR
# signal received, don't continue the loop
return

21
routingmanagersetup.py Normal file
View File

@ -0,0 +1,21 @@
import os
import setuptools
if __name__ == "__main__":
version = (
open(os.path.join(os.path.dirname(__file__), "qubes-network-server.spec"))
.read()
.strip()
)
version = [v for v in version.splitlines() if v.startswith("Version:")][0]
version = version.split()[-1]
setuptools.setup(
name="qubesroutingmanager",
version=version,
author="Manuel Amador (Rudd-O)",
author_email="rudd-o@rudd-o.com",
description="Qubes network server network qube (template) component",
license="GPL2+",
url="https://github.com/Rudd-O/qubes-network-server",
packages=("qubesroutingmanager",),
)

View File

@ -1,24 +0,0 @@
import os
import setuptools
if __name__ == '__main__':
version = open(os.path.join(os.path.dirname(__file__), 'qubes-network-server.spec')).read().strip()
version = [v for v in version.splitlines() if v.startswith("Version:")][0]
version = version.split()[-1]
setuptools.setup(
name='qubesnetworkserver',
version=version,
author='Manuel Amador (Rudd-O)',
author_email='rudd-o@rudd-o.com',
description='Qubes network server dom0 component',
license='GPL2+',
url='https://github.com/Rudd-O/qubes-network-server',
packages=('qubesnetworkserver',),
entry_points={
'qubes.ext': [
'qubesnetworkserver = qubesnetworkserver:QubesNetworkServerExtension',
],
}
)

View File

@ -1,265 +1,8 @@
#!/usr/bin/python3
"""
This program reads the /qubes-firewall/{ip}/qubes-routing-method file
for any firewall configuration, then configures the network to obey
the routing method for the VM. If the routing method is "masquerade",
then nothing happens. If, however, the routing method is "forward",
then VM-specific rules are enacted in the VM's attached NetVM to allow
traffic coming from other VMs and the outside world to reach this VM.
"""
import glob
import logging
import os
import socket
import subprocess
import qubesdb
def _s(v):
if isinstance(v, bytes):
return v.decode("utf-8")
return v
FORWARD_ROUTING_METHOD = "forward"
class AdjunctWorker(object):
def __init__(self):
self.qdb = qubesdb.QubesDB()
@staticmethod
def is_ip6(addr):
return addr.count(":") > 0
def setup_plain_forwarding_for_address(self, source, enable, family):
def find_pos_of_first_rule(table, startswith):
rules = [n for n, l in enumerate(table) if l.startswith(startswith)]
if rules:
return rules[0]
return None
cmd = "ip6tables" if family == 6 else "iptables"
mask = "/128" if family == 6 else "/32"
def run_ipt(*args):
return subprocess.check_call([cmd, "-w"] + list(args))
out_nat = subprocess.check_output(
[cmd + "-save", "-t", "nat"], universal_newlines=True
).splitlines()
out_filter = subprocess.check_output(
[cmd + "-save", "-t", "filter"], universal_newlines=True
).splitlines()
if enable:
# Create necessary prerouting chain.
if not find_pos_of_first_rule(out_nat, ":PR-PLAIN-FORWARDING - "):
logging.info("Creating chain PR-PLAIN-FORWARDING on table nat.")
run_ipt("-t", "nat", "-N", "PR-PLAIN-FORWARDING")
# Route prerouting traffic to necessary chain.
if not find_pos_of_first_rule(out_nat, "-A POSTROUTING -j PR-PLAIN-FORWARDING"):
rule_num = find_pos_of_first_rule(out_nat, "-A POSTROUTING -j MASQUERADE")
if not rule_num:
# This table does not contain the masquerading rule.
# Accordingly, we will not do anything.
return
first_rule_num = find_pos_of_first_rule(out_nat, "-A POSTROUTING")
pos = rule_num - first_rule_num + 1
logging.info("Adding chain PR-PLAIN-FORWARDING to chain POSTROUTING on table nat.")
run_ipt(
"-t",
"nat",
"-I",
"POSTROUTING",
str(pos),
"-j",
"PR-PLAIN-FORWARDING",
)
# Create necessary forward chain.
if not find_pos_of_first_rule(out_filter, ":PLAIN-FORWARDING - "):
logging.info("Creating chain PLAIN-FORWARDING on table filter.")
run_ipt("-t", "filter", "-N", "PLAIN-FORWARDING")
# Route forward traffic to necessary chain.
if not find_pos_of_first_rule(out_filter, "-A FORWARD -j PLAIN-FORWARDING"):
rule_num = find_pos_of_first_rule(
out_filter, "-A FORWARD -i vif+ -o vif+ -j DROP"
)
if not rule_num:
# This table does not contain the masquerading rule.
# Accordingly, we will not do anything.
return
first_rule_num = find_pos_of_first_rule(out_filter, "-A FORWARD")
pos = rule_num - first_rule_num + 1
logging.info("Adding chain PLAIN-FORWARDING to chain FORWARD on table filter.")
run_ipt(
"-t", "filter", "-I", "FORWARD", str(pos), "-j", "PLAIN-FORWARDING"
)
rule = find_pos_of_first_rule(
out_nat, "-A PR-PLAIN-FORWARDING -s {}{} -j ACCEPT".format(source, mask)
)
if enable:
if rule:
pass
else:
logging.info(
"Adding PR-PLAIN-FORWARDING rule on table nat to forward traffic from %s.", source
)
run_ipt(
"-t",
"nat",
"-A",
"PR-PLAIN-FORWARDING",
"-s",
"{}{}".format(source, mask),
"-j",
"ACCEPT",
)
else:
if rule:
first_rule = find_pos_of_first_rule(out_nat, "-A PR-PLAIN-FORWARDING")
pos = rule - first_rule + 1
logging.info(
"Removing PR-PLAIN-FORWARDING rule on table nat forwarding traffic from %s.", source
)
run_ipt("-t", "nat", "-D", "PR-PLAIN-FORWARDING", str(pos))
else:
pass
rule = find_pos_of_first_rule(
out_filter, "-A PLAIN-FORWARDING -d {}{} -o vif+ -j ACCEPT".format(source, mask)
)
if enable:
if rule:
pass
else:
logging.info("Adding PLAIN-FORWARDING rule on table filter to allow traffic to %s.", source)
run_ipt(
"-t",
"filter",
"-A",
"PLAIN-FORWARDING",
"-d",
"{}{}".format(source, mask),
"-o",
"vif+",
"-j",
"ACCEPT",
)
else:
if rule:
logging.info("Removing PLAIN-FORWARDING rule on table filter allowing traffic to %s.", source)
first_rule = find_pos_of_first_rule(out_filter, "-A PLAIN-FORWARDING")
pos = rule - first_rule + 1
run_ipt("-t", "filter", "-D", "PLAIN-FORWARDING", str(pos))
else:
pass
def setup_proxy_arp_ndp(self, enabled, family):
# If any of the IP addresses is assigned the forward routing method,
# then enable proxy ARP/NDP on the upstream interfaces, so that the
# interfaces in question will impersonate the IP addresses in question.
# Ideally, this impersonation would be exclusively done for the
# specific IP addresses in question, but it is not clear to me how
# to cause this outcome to take place.
if family == 6:
globber = "/proc/sys/net/ipv6/conf/*/proxy_ndp"
name = "proxy NDP"
elif family == 4:
globber = "/proc/sys/net/ipv4/conf/*/proxy_arp"
name = "proxy ARP"
else:
return
if enabled:
action = "Enabling"
val = "1\n"
else:
action = "Disabling"
val = "0\n"
matches = glob.glob(globber)
for m in matches:
iface = m.split("/")[6]
if iface in ("all", "lo") or iface.startswith("vif"):
# No need to enable it for "all", or VIFs, or loopback.
continue
with open(m, "w+") as f:
oldval = f.read()
if oldval != val:
logging.info("%s %s on interface %s.", action, name, iface)
f.seek(0)
f.write(val)
def handle_addr(self, addr):
# Setup plain forwarding for this specific address.
routing_method = _s(self.qdb.read("/qubes-routing-method/{}".format(addr)))
self.setup_plain_forwarding_for_address(
addr,
routing_method == FORWARD_ROUTING_METHOD,
6 if self.is_ip6(addr) else 4,
)
# Manipulate proxy ARP for all known addresses.
methods = [
(_s(k).split("/")[2], _s(v))
for k, v in self.qdb.multiread("/qubes-routing-method/").items()
]
mmethods = {
4: [m[1] for m in methods if not self.is_ip6(m[0])],
6: [m[1] for m in methods if self.is_ip6(m[0])],
}
for family, methods in mmethods.items():
self.setup_proxy_arp_ndp(
FORWARD_ROUTING_METHOD in methods, family,
)
def list_targets(self):
return set(_s(t).split("/")[2] for t in self.qdb.list("/qubes-routing-method/"))
def sd_notify(self, state):
"""Send notification to systemd, if available"""
# based on sdnotify python module
if not "NOTIFY_SOCKET" in os.environ:
return
addr = os.environ["NOTIFY_SOCKET"]
if addr[0] == "@":
addr = "\0" + addr[1:]
try:
sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
sock.connect(addr)
sock.sendall(state.encode())
except:
# generally ignore error on systemd notification
pass
def main(self):
logging.basicConfig(level=logging.INFO)
self.qdb.watch("/qubes-routing-method/")
for source_addr in self.list_targets():
self.handle_addr(source_addr)
self.sd_notify("READY=1")
try:
for watch_path in iter(self.qdb.read_watch, None):
# ignore writing rules itself - wait for final write at
# source_addr level empty write (/qubes-firewall/SOURCE_ADDR)
watch_path = _s(watch_path)
if watch_path.count("/") != 2:
continue
source_addr = watch_path.split("/")[2]
self.handle_addr(source_addr)
except OSError: # EINTR
# signal received, don't continue the loop
return
if __name__ == "__main__":
from qubesroutingmanager.worker import AdjunctWorker
w = AdjunctWorker()
w.main()

View File

@ -1,13 +1,12 @@
[Unit]
Description=Configure the network to allow network server VMs
Documentation=https://github.com/Rudd-O/qubes-network-server
ConditionPathExists=/var/run/qubes-service/qubes-firewall
After=qubes-firewall.service
BindsTo=qubes-firewall.service
After=qubes-iptables.service
BindsTo=qubes-iptables.service
[Service]
Type=notify
ExecStart=@SBINDIR@/qubes-routing-manager
[Install]
WantedBy=multi-user.target
WantedBy=qubes-iptables.service