Switched to drop-ins for routes

This commit is contained in:
Ezri Brimhall 2026-02-03 14:55:21 -07:00
parent fc44f65e07
commit c0e692048a
Signed by: ezri
GPG Key ID: 058A78E5680C6F24

View File

@ -50,6 +50,7 @@ class Config:
# This is well below what systemd-networkd will use by default, so these rules will take precedence.
# Access through the non-private property for auto-incrementing!!!
_routingpolicyrule_prio = 16000
_route_id = 0
@property
def routingpolicyrule_prio(self) -> int:
@ -57,6 +58,12 @@ class Config:
self._routingpolicyrule_prio += 1
return val
@property
def route_id(self) -> int:
val = self._route_id
self._route_id += 1
return val
def __init__(self):
self.vpnd_connection_name = os.environ.get("VPND_CONNECTION_NAME")
@ -113,46 +120,56 @@ class Config:
)
)
def write_route(self, stream: TextIOBase, net: ipaddress.IPv4Network):
def write_route(self, dir: Path, net: ipaddress.IPv4Network):
"""Write a systemd-networkd route config for the given network to the given stream."""
route_id = self.route_id
if self.vpnd_enforce_split_tunnel:
if self.vpn_gateway in net:
# Refuse to create a policy rule that would block the uplink.
# this is probably a global route, which we will already have.
return
with (dir / f"inclusion-{route_id}.conf").open("w") as stream:
stream.writelines(
[
"[RoutingPolicyRule]\n",
f"To={net}\n",
f"Table={self.split_enforcement_table}\n"
f"Priority={self.routingpolicyrule_prio}\n",
]
)
else:
with (dir / f"route-{route_id}.conf").open("w") as stream:
stream.writelines(
[
"[Route]\n",
"Gateway=0.0.0.0\n",
f"To={net}\n",
# systemd-networkd defaults to a route metric of 1024. We set a very low metric to shadow any routes that aren't meant
# to explicitly override this, but still allows explicit overrides if desired.
"Metric=64\n",
]
)
def write_exclusion(self, dir: Path, net: ipaddress.IPv4Network):
"""Write a routing policy rule to exclude the given network from our routing table."""
if not self.vpnd_enforce_split_tunnel:
return
route_id = self.route_id
with (dir / f"exclusion-{route_id}.conf").open("w") as stream:
stream.writelines(
[
"[RoutingPolicyRule]\n",
f"To={net}\n",
f"Table={self.split_enforcement_table}\n"
f"Table=main\n",
f"Priority={self.routingpolicyrule_prio}\n",
]
)
else:
stream.writelines(
[
"[Route]\n",
"Gateway=0.0.0.0\n",
f"To={net}\n",
# systemd-networkd defaults to a route metric of 1024. We set a very low metric to shadow any routes that aren't meant
# to explicitly override this, but still allows explicit overrides if desired.
"Metric=64\n",
]
)
def write_exclusion(self, stream: TextIOBase, net: ipaddress.IPv4Network):
"""Write a routing policy rule to exclude the given network from our routing table."""
if not self.vpnd_enforce_split_tunnel:
return
stream.writelines(
[
"[RoutingPolicyRule]\n",
f"To={net}\n",
f"Table=main\n",
f"Priority={self.routingpolicyrule_prio}\n",
]
)
def write_config(self, stream: TextIOBase):
def write_config(self, path: Path):
"""Generate a transient systemd-networkd config file."""
# This file will not enforce split-tunneling restrictions, and will not shadow existing routes.
# Any route _inclusions_ will be added as [Route]s, while _exclusions_ will be ignored.
stream = path.open("w")
if not stream.writable():
raise ValueError("Stream is not writable")
stream.writelines(
@ -182,10 +199,15 @@ class Config:
f"Priority={self.routingpolicyrule_prio}\n",
]
)
dir = path.with_suffix(".d")
if not dir.exists():
dir.mkdir()
for net in self.split_tunnel_inclusions:
self.write_route(dir, net)
if self.vpnd_enforce_split_tunnel:
# configure exclusions after inclusions. inclusions don't make sense without this, since they'd just be captured by the default.
for net in self.split_tunnel_exclusions:
self.write_exclusion(stream, net)
self.write_exclusion(dir, net)
# Set a final catch-all rule (which should still have significantly lower priority than any auto-numbered rules) which will redirect
# anything not intended for the gateway IP itself to our routing table.
stream.writelines(
@ -197,6 +219,7 @@ class Config:
f"Priority={self.routingpolicyrule_prio}\n",
]
)
stream.close()
async def main():
@ -224,8 +247,7 @@ async def configure_with_networkd():
if config.reason in {Reason.CONNECT, Reason.ATTEMPT_RECONNECT, Reason.RECONNECT}:
# only write out the file if the reason indicates a new connection.
with open(Path("/run/systemd/network") / filename, "w") as f:
config.write_config(f)
config.write_config(Path("/run/systemd/network") / filename)
elif config.reason == Reason.PRE_INIT:
# idk what this does, but the script does it, so we will too.
try: