Switched to drop-ins for routes
This commit is contained in:
@@ -50,6 +50,7 @@ class Config:
|
|||||||
# This is well below what systemd-networkd will use by default, so these rules will take precedence.
|
# 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!!!
|
# Access through the non-private property for auto-incrementing!!!
|
||||||
_routingpolicyrule_prio = 16000
|
_routingpolicyrule_prio = 16000
|
||||||
|
_route_id = 0
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def routingpolicyrule_prio(self) -> int:
|
def routingpolicyrule_prio(self) -> int:
|
||||||
@@ -57,6 +58,12 @@ class Config:
|
|||||||
self._routingpolicyrule_prio += 1
|
self._routingpolicyrule_prio += 1
|
||||||
return val
|
return val
|
||||||
|
|
||||||
|
@property
|
||||||
|
def route_id(self) -> int:
|
||||||
|
val = self._route_id
|
||||||
|
self._route_id += 1
|
||||||
|
return val
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
||||||
self.vpnd_connection_name = os.environ.get("VPND_CONNECTION_NAME")
|
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."""
|
"""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.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(
|
stream.writelines(
|
||||||
[
|
[
|
||||||
"[RoutingPolicyRule]\n",
|
"[RoutingPolicyRule]\n",
|
||||||
f"To={net}\n",
|
f"To={net}\n",
|
||||||
f"Table={self.split_enforcement_table}\n"
|
f"Table=main\n",
|
||||||
f"Priority={self.routingpolicyrule_prio}\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):
|
def write_config(self, path: Path):
|
||||||
"""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):
|
|
||||||
"""Generate a transient systemd-networkd config file."""
|
"""Generate a transient systemd-networkd config file."""
|
||||||
# This file will not enforce split-tunneling restrictions, and will not shadow existing routes.
|
# 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.
|
# Any route _inclusions_ will be added as [Route]s, while _exclusions_ will be ignored.
|
||||||
|
stream = path.open("w")
|
||||||
if not stream.writable():
|
if not stream.writable():
|
||||||
raise ValueError("Stream is not writable")
|
raise ValueError("Stream is not writable")
|
||||||
stream.writelines(
|
stream.writelines(
|
||||||
@@ -182,10 +199,15 @@ class Config:
|
|||||||
f"Priority={self.routingpolicyrule_prio}\n",
|
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:
|
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.
|
# 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:
|
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
|
# 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.
|
# anything not intended for the gateway IP itself to our routing table.
|
||||||
stream.writelines(
|
stream.writelines(
|
||||||
@@ -197,6 +219,7 @@ class Config:
|
|||||||
f"Priority={self.routingpolicyrule_prio}\n",
|
f"Priority={self.routingpolicyrule_prio}\n",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
stream.close()
|
||||||
|
|
||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
@@ -224,8 +247,7 @@ async def configure_with_networkd():
|
|||||||
|
|
||||||
if config.reason in {Reason.CONNECT, Reason.ATTEMPT_RECONNECT, Reason.RECONNECT}:
|
if config.reason in {Reason.CONNECT, Reason.ATTEMPT_RECONNECT, Reason.RECONNECT}:
|
||||||
# only write out the file if the reason indicates a new connection.
|
# 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(Path("/run/systemd/network") / filename)
|
||||||
config.write_config(f)
|
|
||||||
elif config.reason == Reason.PRE_INIT:
|
elif config.reason == Reason.PRE_INIT:
|
||||||
# idk what this does, but the script does it, so we will too.
|
# idk what this does, but the script does it, so we will too.
|
||||||
try:
|
try:
|
||||||
|
|||||||
Reference in New Issue
Block a user