fix(shoukei): route DNS through the mihomo TUN

Assisted-by: DeepSeek Harness (gpt-6-astra)
This commit is contained in:
Ryan Yin authored and ryan4yin | 二花 committed 2026-10-14 18:34:48 +08:00
1 parent bac43e0e9f
commit b24f30c38c
5 files changed
+87 -3

No files matched your search

+2
View File
@@ -51,6 +51,8 @@ in
inherit (myvars.networking) nameservers;
};
modules.networking.mihomo.resolvedIntegration = true;
fileSystems."/btr_pool" = {
device = "/dev/disk/by-uuid/c2e8b249-240e-4eef-bf4e-81e7dbbf4887";
fsType = "btrfs";
@@ -138,6 +138,17 @@ targets instead of writing a config the core rejects.
`hosts/idols-ai/default.nix`), prefixed with `+` to run outside the unit sandbox. Never set link
DNS statically to mihomo: a dead mihomo would take DNS down with it, and resolved does not fall
back.
- On shoukei, `modules.networking.mihomo.resolvedIntegration` enables resolved and the
NetworkManager resolved backend. Once DNS is listening, the service registers `127.0.0.1:1053` and
`~.` on `Meta`, which NetworkManager leaves unmanaged. Wi-Fi reconnects can update their own DNS
without overwriting this route. Stopping mihomo reverts only `Meta`; removing the TUN also removes
its DNS configuration, leaving the physical links' DNS available. More-specific VPN DNS routes
still win; another VPN claiming `~.` (including Tailscale's full DNS override) would share the
catch-all and must not be used concurrently. This relies on mihomo's default `Meta` device name
and the generator's local DNS port; apply TUN changes with a service restart so the DNS hook runs
for the new interface. After activation, verify normal and reconnected Wi-Fi queries return fake
IPs, and that stopping mihomo restores network DNS. See
[systemd's VPN DNS routing guide](https://github.com/systemd/systemd/blob/main/docs/RESOLVED-VPNS.md).
## Steam download region
@@ -49,8 +49,10 @@ let
'';
in
{
options.modules.networking.mihomo.enable =
lib.mkEnableOption "the native Mihomo core with TUN and a local web UI";
options.modules.networking.mihomo = {
enable = lib.mkEnableOption "the native Mihomo core with TUN and a local web UI";
resolvedIntegration = lib.mkEnableOption "route systemd-resolved DNS through the mihomo TUN interface";
};
config = lib.mkIf cfg.enable {
services.mihomo = {
@@ -67,10 +69,56 @@ in
# and fail with "Failed to set up credentials: No such file or directory".
systemd.services.mihomo.unitConfig.RequiresMountsFor = builtins.dirOf configFile;
services.resolved.enable = lib.mkIf cfg.resolvedIntegration true;
networking.networkmanager =
lib.mkIf (cfg.resolvedIntegration && config.networking.networkmanager.enable)
{
dns = "systemd-resolved";
# NetworkManager owns physical links; mihomo owns this virtual link.
unmanaged = [ "interface-name:Meta" ];
};
systemd.services.mihomo.after = lib.optional cfg.resolvedIntegration "systemd-resolved.service";
systemd.services.mihomo.requires = lib.optional cfg.resolvedIntegration "systemd-resolved.service";
# Force the TUN's v6 address up: left to itself mihomo skips the v6 half when
# no NIC has an IPv6 address at startup, yet still installs its strict-route v6
# blackhole, which locks the host out of IPv6. Upstream knob (`inet6-address`,
# meta-docs tun.md); the full chain is WA-030.
systemd.services.mihomo.serviceConfig.Environment = [ "SKIP_SYSTEM_IPV6_CHECK=1" ];
systemd.services.mihomo.serviceConfig = {
Environment = [ "SKIP_SYSTEM_IPV6_CHECK=1" ];
}
// lib.optionalAttrs cfg.resolvedIntegration {
# Attach the catch-all to the TUN, leaving DHCP and more-specific VPN DNS
# routes untouched. Removing the TUN also removes its resolved settings.
# '+' allows the privileged D-Bus call outside mihomo's service sandbox.
ExecStartPost = "+${pkgs.coreutils}/bin/timeout 30 ${
lib.getExe (
pkgs.writeShellApplication {
name = "mihomo-resolved";
runtimeInputs = [
pkgs.dig
pkgs.systemd
];
text = ''
until (( SECONDS >= 25 )); do
if [[ -e /sys/class/net/Meta ]] \
&& dig +time=1 +tries=1 @127.0.0.1 -p 1053 localhost A >/dev/null \
&& resolvectl dns Meta 127.0.0.1:1053 \
&& resolvectl dnssec Meta no \
&& resolvectl dnsovertls Meta no \
&& resolvectl domain Meta '~.'; then
exit 0
fi
sleep 0.1
done
echo "mihomo TUN or DNS did not become ready" >&2
exit 1
'';
}
)
}";
# The interface may already be gone after a clean stop or a crash.
ExecStopPost = "-+${pkgs.systemd}/bin/resolvectl revert Meta";
};
};
}
@@ -0,0 +1,9 @@
{
resolved = true;
resolvconf = false;
dnsBackend = "systemd-resolved";
tunUnmanaged = true;
dispatcherCount = 0;
resolverOrdered = true;
resolverRequired = true;
}
@@ -0,0 +1,14 @@
{ outputs, ... }:
let
system = outputs.nixosConfigurations.shoukei-niri.config;
nm = system.networking.networkmanager;
in
{
resolved = system.services.resolved.enable;
resolvconf = system.networking.resolvconf.enable;
dnsBackend = nm.dns;
tunUnmanaged = builtins.elem "interface-name:Meta" nm.unmanaged;
dispatcherCount = builtins.length nm.dispatcherScripts;
resolverOrdered = builtins.elem "systemd-resolved.service" system.systemd.services.mihomo.after;
resolverRequired = builtins.elem "systemd-resolved.service" system.systemd.services.mihomo.requires;
}