mirror of
https://github.com/ryan4yin/nix-config.git
synced 2026-11-13 12:13:46 +01:00
fix(mihomo): answer AAAA on a public fake-ip pool, pin guests to a cluster ULA (#420)
* fix(microvm): pin guest IPv6 identity to a cluster ULA The ISP retires delegated prefixes, so a guest that registers its WAN prefix loses it and Cilium never refreshes the node address. Derive the identity from the IPv4 octets inside fd05:5::/64 and trust that ULA in the guest firewall. * feat(mihomo): answer AAAA and keep the tailnet off the TUN fake-ip needs one pool per family, and the family a client picks does not pick the egress family. Exclude the cluster ULA and the Tailscale ranges from the TUN; the ssh pin and the WA-028 record cover the fake-AAAA stall. * feat(mihomo): generate the config from the repo, not a copy in the live dir `just mihomo-gen` runs generate.nu from the module and validates with the core the service runs. ~/.config/mihomo keeps only sources.yaml and config.yaml. * fix(mihomo): keep the fake IPv6 pool out of fc00::/7 The v6 fake pool is a lookup key, not the egress path, so it can be a public range: the shipped ULA default reads as "local" to browsers and prompts for local-network access on every site. The gvisor stack override goes with it -- mips is upstream's default, and nftables rules out the kernel stack here. * feat(mihomo): sniff on the gateway, not on the desktop LAN clients using DoH dial the IPs they resolved, and a node may not reach an IP a foreign resolver handed out. The gateway recovers the name from the handshake; on the desktop the fake-ip table already names every flow. Its binary is a static release build, not a package: update it by hand and validate the live config with the new core. * fix(mihomo): name the Singapore nodes instead of matching 新 The single-char keyword also matched 新西兰, so the Singapore group held a New Zealand node and url-test flipped the exit country mid-session.
This commit is contained in:
12 files changed
+134
-52
No files matched your search
@@ -32,3 +32,7 @@ env/
|
||||
.ruff_cache/
|
||||
.coverage
|
||||
htmlcov/
|
||||
|
||||
# mihomo's private input and its subscription snapshots live in ~/.config/mihomo; guard a stray copy
|
||||
modules/nixos/desktop/networking/mihomo/sources.yaml
|
||||
modules/nixos/desktop/networking/mihomo/snapshots/
|
||||
@@ -290,6 +290,25 @@ dsh-web:
|
||||
NO_PROXY: "localhost,127.0.0.1,::1,[::1],192.168.5.100,192.168.5.178,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16"
|
||||
} { ^dsh web --no-open }
|
||||
|
||||
# Render the mihomo config from this repo and validate it with the live core.
|
||||
[linux]
|
||||
[group('services')]
|
||||
mihomo-gen:
|
||||
#!/usr/bin/env nu
|
||||
# ~/.config/mihomo holds only the private sources.yaml and the generated
|
||||
# config.yaml. The generator and policy.yaml stay in the repository, so the
|
||||
# live directory can never keep a stale copy of them.
|
||||
let mod = ("{{ justfile() }}" | path dirname | path join modules nixos desktop networking mihomo)
|
||||
nu ($mod | path join generate.nu)
|
||||
# Validate with the core the service actually runs; a nixpkgs one may differ.
|
||||
let core = (systemctl show mihomo.service -p ExecStart | str trim | split row "path=" | get 1 | split row " " | get 0)
|
||||
let check = (^$core -t -f ($env.HOME | path join .config mihomo config.yaml) | complete)
|
||||
if $check.exit_code != 0 {
|
||||
print --stderr ($check.stderr | str trim)
|
||||
exit $check.exit_code
|
||||
}
|
||||
print "validated -- restart: sudo systemctl restart mihomo.service"
|
||||
|
||||
# Seed mihomo's geodata into the service's state dir, so a cold start needs no DNS.
|
||||
[linux]
|
||||
[group('services')]
|
||||
|
||||
+12
-10
@@ -33,16 +33,18 @@ A version or revision pinned for a reason that can go away.
|
||||
|
||||
A setting that works around a bug or a hardware quirk.
|
||||
|
||||
| ID | What & where | Why | Removal condition | Added | Revisit | Status |
|
||||
| ------ | ------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | ---------- | ------------------------------------------------------------------------------ | ------ |
|
||||
| WA-008 | Relaxed NVMe APST (`nvme_core.default_ps_max_latency_us=100000`) — `hosts/idols-ai/hardware-configuration.nix` | the KINGBANK KP260 (DRAM-less QLC) froze on slow APST wake-ups | Drop the override once kernel/firmware fixes or replacement hardware make default APST reliable | 2026-10-03 | watch NVMe timeout/reset/AER in the journal | active |
|
||||
| WA-009 | USB-SATA bridge mitigations (`usbcore.autosuspend=-1`, `usb-storage.delay_use=10`) — `hosts/12kingdoms-youko/default.nix` | flaky JMicron JMS567 resets with UDMA-CRC and I/O errors | Drop once the bridge/controller is replaced; the signal is smartctl CRC/media errors | 2026-09-21 | on hardware change or CRC errors | active |
|
||||
| WA-010 | Insta360 Link USB reset on S3 resume — `hosts/idols-ai/default.nix` | the camera stays enumerated with a stalled UVC endpoint after resume | Drop after the Insta360 firmware or the kernel `uvcvideo` fixes it | 2026-08-01 | on kernel or firmware change | active |
|
||||
| WA-011 | Kernel module blacklist (`esp4`, `esp6`, `rxrpc`) — `hardening/kernel-hardening.nix` | attack surface for the Dirty Frag LPE (CVE-2026-43284 / CVE-2026-43500) | Kept as defence in depth; drop only if something needs those modules | 2026-10-03 | rarely | active |
|
||||
| WA-017 | keyd right-hand modifier passthrough — `modules/nixos/desktop/peripherals.nix` | keyd folds right Ctrl/Shift/Meta into their left keys | Drop once keyd or fcitx5 keeps right-hand modifiers distinct | 2026-10-05 | on keyd bump | active |
|
||||
| WA-018 | user-owned `~/.ssh/config` + disabled systemd ssh-proxy include — `home/base/tui/ssh.nix`, `modules/nixos/base/ssh.nix` | bwrap exposes root-owned store files as `nobody` | Drop once sandboxes preserve store ownership | 2026-10-06 | when sandbox layout changes | active |
|
||||
| WA-019 | programs.steam.extraCompatPackages = [ pkgs.dwproton-bin pkgs.proton-ge-bin ] — modules/nixos/desktop/gaming.nix | ACE games fail on GE/mainline Proton, work with DW-Proton | Drop once mainline or GE-Proton runs these games (GE-Proton#433) | 2026-10-07 | on Proton/GE bump | active |
|
||||
| WA-024 | Wi-Fi power saving disabled (`networking.networkmanager.wifi.powersave = false`) — `hosts/12kingdoms-shoukei/apple-silicon.nix` | On this BCM4378/brcmfmac laptop, powersave is a common source of intermittent ASSOC-REJECT / authentication-timeout failures; NetworkManager then mistakes the failure for a bad key, re-prompts, and the client (Noctalia, nmtui, nmcli) saves the answer as a duplicate `<SSID> 1` profile | Drop once the kernel carries the brcmfmac `port_authorized` fix (WA-025) and no association failures remain | 2026-10-07 | watch `journalctl -u wpa_supplicant` for ASSOC-REJECT / authentication timeout | active |
|
||||
| ID | What & where | Why | Removal condition | Added | Revisit | Status |
|
||||
| ------ | --------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | ---------- | --------------------------------------------------------------------------------------------------- | ------ |
|
||||
| WA-008 | Relaxed NVMe APST (`nvme_core.default_ps_max_latency_us=100000`) — `hosts/idols-ai/hardware-configuration.nix` | the KINGBANK KP260 (DRAM-less QLC) froze on slow APST wake-ups | Drop the override once kernel/firmware fixes or replacement hardware make default APST reliable | 2026-10-03 | watch NVMe timeout/reset/AER in the journal | active |
|
||||
| WA-009 | USB-SATA bridge mitigations (`usbcore.autosuspend=-1`, `usb-storage.delay_use=10`) — `hosts/12kingdoms-youko/default.nix` | flaky JMicron JMS567 resets with UDMA-CRC and I/O errors | Drop once the bridge/controller is replaced; the signal is smartctl CRC/media errors | 2026-09-21 | on hardware change or CRC errors | active |
|
||||
| WA-010 | Insta360 Link USB reset on S3 resume — `hosts/idols-ai/default.nix` | the camera stays enumerated with a stalled UVC endpoint after resume | Drop after the Insta360 firmware or the kernel `uvcvideo` fixes it | 2026-08-01 | on kernel or firmware change | active |
|
||||
| WA-011 | Kernel module blacklist (`esp4`, `esp6`, `rxrpc`) — `hardening/kernel-hardening.nix` | attack surface for the Dirty Frag LPE (CVE-2026-43284 / CVE-2026-43500) | Kept as defence in depth; drop only if something needs those modules | 2026-10-03 | rarely | active |
|
||||
| WA-017 | keyd right-hand modifier passthrough — `modules/nixos/desktop/peripherals.nix` | keyd folds right Ctrl/Shift/Meta into their left keys | Drop once keyd or fcitx5 keeps right-hand modifiers distinct | 2026-10-05 | on keyd bump | active |
|
||||
| WA-018 | user-owned `~/.ssh/config` + disabled systemd ssh-proxy include — `home/base/tui/ssh.nix`, `modules/nixos/base/ssh.nix` | bwrap exposes root-owned store files as `nobody` | Drop once sandboxes preserve store ownership | 2026-10-06 | when sandbox layout changes | active |
|
||||
| WA-019 | programs.steam.extraCompatPackages = [ pkgs.dwproton-bin pkgs.proton-ge-bin ] — modules/nixos/desktop/gaming.nix | ACE games fail on GE/mainline Proton, work with DW-Proton | Drop once mainline or GE-Proton runs these games (GE-Proton#433) | 2026-10-07 | on Proton/GE bump | active |
|
||||
| WA-024 | Wi-Fi power saving disabled (`networking.networkmanager.wifi.powersave = false`) — `hosts/12kingdoms-shoukei/apple-silicon.nix` | On this BCM4378/brcmfmac laptop, powersave is a common source of intermittent ASSOC-REJECT / authentication-timeout failures; NetworkManager then mistakes the failure for a bad key, re-prompts, and the client (Noctalia, nmtui, nmcli) saves the answer as a duplicate `<SSID> 1` profile | Drop once the kernel carries the brcmfmac `port_authorized` fix (WA-025) and no association failures remain | 2026-10-07 | watch `journalctl -u wpa_supplicant` for ASSOC-REJECT / authentication timeout | active |
|
||||
| WA-026 | guest deploys run `installOnHost` then `systemctl restart microvm@<guest>`, not `just microvm-deploy` — `lib/genMicrovmGuestModule.nix` | the sshSwitch step runs `nix-store --load-db` against the guest's read-only virtiofs `/nix/store` and dies on `cannot remount "/nix/store" writable` (nix 2.35) | Drop the manual path once microvm.nix or nix handles a read-only store switch | 2026-10-09 | on microvm.nix or nix bump | active |
|
||||
| WA-029 | mihomo `fake-ip-range6` is `2001:2::/64`, not the shipped ULA default — `modules/nixos/desktop/networking/mihomo/generate.nu` | the default `fdfe:dcba:9876::/64` sits in `fc00::/7`, which Local Network Access calls local, so a public page loaded over the v4 fake pool and pulling a dual-stack subresource prompts for local-network access (Chrome 154, Firefox). The same range also stalled ssh: a session to a fake AAAA authenticated and then never closed, on both `gvisor` and `mips`. `2001:2::/48` is RFC 5180 benchmarking -- the v6 twin of the `198.18.0.0/15` v4 default, absent from that table -- and both symptoms went with the move | Go back to the upstream default once mihomo ships a pool outside the LNA non-public table; the ULA default brings the ssh stall back | 2026-10-09 | on a browser or mihomo bump -- that table already grew to cover `2001:db8::/32` and `198.18.0.0/15` | active |
|
||||
|
||||
If WA-008 freezes recur, the fallback is to restore
|
||||
`nvme_core.default_ps_max_latency_us=0 nvme_core.io_timeout=4294967295 pcie_aspm=off`; this is a
|
||||
|
||||
@@ -27,8 +27,6 @@
|
||||
|
||||
settings = {
|
||||
"github.com" = {
|
||||
# avoid mihomo fake-IP6 hang, see modules/nixos/desktop/networking/mihomo/
|
||||
AddressFamily = "inet";
|
||||
HostName = "ssh.github.com";
|
||||
Port = 443;
|
||||
User = "git";
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
...
|
||||
}:
|
||||
let
|
||||
inherit (networking) proxyGateway proxyGateway6;
|
||||
inherit (networking) proxyGateway proxyGateway6 clusterULA6;
|
||||
inherit (networking.hostsAddr.${hostName}) ipv4;
|
||||
ipv4WithMask = "${ipv4}/24";
|
||||
|
||||
@@ -29,6 +29,9 @@ let
|
||||
if builtins.stringLength h == 1 then "0${h}" else h;
|
||||
mac = "02:00:00:00:${toHex (builtins.elemAt octets 2)}:${toHex (builtins.elemAt octets 3)}";
|
||||
|
||||
# Same octets as the MAC: 192.168.5.111 -> fd05:5::05:6f. Survives ISP prefix changes.
|
||||
ula = pkgs.lib.toLower "${clusterULA6}${toHex (builtins.elemAt octets 2)}:${toHex (builtins.elemAt octets 3)}/64";
|
||||
|
||||
# tap iface name on the host (IFNAMSIZ is 16, so <= 15 chars). All homelab VMs
|
||||
# are 192.168.5.0/24, so the last octet is unique per host.
|
||||
tapId = "vm${builtins.elemAt octets 3}";
|
||||
@@ -94,17 +97,18 @@ in
|
||||
systemd.network.networks."10-${hostName}" = {
|
||||
matchConfig.MACAddress = [ mac ];
|
||||
networkConfig = {
|
||||
Address = [ ipv4WithMask ];
|
||||
Address = [
|
||||
ipv4WithMask
|
||||
ula
|
||||
];
|
||||
DNS = [ proxyGateway ];
|
||||
DHCP = "ipv6";
|
||||
IPv6AcceptRA = true;
|
||||
# No SLAAC/DHCPv6: the WAN /64 churns and Cilium keeps probing whatever it
|
||||
# registered at agent start. The ULA above is the node's IPv6 identity.
|
||||
DHCP = false;
|
||||
IPv6AcceptRA = false;
|
||||
IPv6PrivacyExtensions = false;
|
||||
LinkLocalAddressing = "ipv6";
|
||||
};
|
||||
# Keep the static resolver below as the only DNS server; router advertisements and DHCPv6
|
||||
# still provide addresses and routes but must not add their own resolver entries.
|
||||
dhcpV6Config.UseDNS = false;
|
||||
ipv6AcceptRAConfig.UseDNS = false;
|
||||
routes = [
|
||||
{
|
||||
Destination = "0.0.0.0/0";
|
||||
@@ -119,6 +123,13 @@ in
|
||||
linkConfig.RequiredForOnline = "routable";
|
||||
};
|
||||
|
||||
# No ULA counterpart to the LAN IPv4 accept in
|
||||
# modules/nixos/base/networking/firewall.nix, so Cilium's node health probe
|
||||
# (TCP 4240) between nodes is dropped. Trust the node plane like the LAN.
|
||||
networking.firewall.extraInputRules = pkgs.lib.mkAfter ''
|
||||
ip6 saddr ${clusterULA6}/64 accept
|
||||
'';
|
||||
|
||||
# journald on tmpfs -- keep it small
|
||||
services.journald.settings.Journal = {
|
||||
SystemMaxUse = "512M";
|
||||
|
||||
@@ -8,11 +8,14 @@ Native mihomo core + metacubexd dashboard, replacing the Clash Verge GUI. Enable
|
||||
```sh
|
||||
cp sources.example.yaml ~/.config/mihomo/sources.yaml && chmod 600 ~/.config/mihomo/sources.yaml
|
||||
$EDITOR ~/.config/mihomo/sources.yaml # fill in: secret, private_domains, providers
|
||||
nu generate.nu # -> ~/.config/mihomo/config.yaml, mode 0600
|
||||
mihomo -t -f ~/.config/mihomo/config.yaml # validate before restarting
|
||||
just mihomo-gen # -> ~/.config/mihomo/config.yaml, mode 0600
|
||||
sudo systemctl restart mihomo.service # dashboard: http://127.0.0.1:9090/ui
|
||||
```
|
||||
|
||||
`~/.config/mihomo` holds only `sources.yaml` and the generated `config.yaml` (plus geodata and the
|
||||
cache); the generator, `policy.yaml` and `gateway-config.nu` are run from here and never copied
|
||||
over. `just mihomo-gen` renders the config and validates it with the core the service runs.
|
||||
|
||||
`sources.yaml` holds everything private (subscription URLs, secret, private domains) and stays out
|
||||
of the repo and the Nix store; the module loads `config.yaml` via `LoadCredential`. Generate the
|
||||
config before enabling the module, or the service will not start.
|
||||
@@ -31,7 +34,10 @@ Rule order: `private_domains`, your `rules`, imported rules, then `policy.yaml`,
|
||||
|
||||
The gateway (`suzi`, not a host in this flake) runs the same config with a fixed set of deltas --
|
||||
`gateway-config.nu` renders it, `--redact` for a shareable template. Its output carries the
|
||||
subscriptions, so never commit it; the secret is filled in on the box, not here.
|
||||
subscriptions, so never commit it; the secret is filled in on the box, not here. Its binary is a
|
||||
static release build under `/usr/local/bin`, not a package: update it by hand, validate the live
|
||||
config with the new core (`mihomo -t -d /etc/mihomo -f <candidate>`) before swapping, and keep the
|
||||
previous binary. The release ships no checksums, so TLS plus `-v` is the whole verification.
|
||||
|
||||
## Why generate
|
||||
|
||||
@@ -43,17 +49,39 @@ targets instead of writing a config the core rejects.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- `ipv6: false`: nodes without IPv6 egress + AAAA = routing black hole (WeChat/JD/Taobao images);
|
||||
mihomo's IPv6 fake-ip also hung ssh/git locally for ~2 minutes, so it stays off.
|
||||
- `ipv6`, `dns.ipv6`, `fake-ip-range6`: the fake v6 pool is a lookup key, so the family the client
|
||||
picks never picks the egress family -- a fake-IPv6 client dials an IPv4 proxy target, and CN
|
||||
services answer identically over `-6` and `-4`. What does break is a fake v6 pool on a line whose
|
||||
IPv6 is dead: apps that resolve for themselves (WeChat) dial a real AAAA, stall, and do not fall
|
||||
back. mihomo has no IPv6 reachability probe (mihomo#2233), so the config has to match the line.
|
||||
SSH over a fake AAAA is the local exception: it connects, authenticates, then never closes --
|
||||
WA-028's IPv4 pin covers it. The pool is `2001:2::/64`, not the shipped `fdfe:dcba:9876::/64`:
|
||||
that is ULA (`fc00::/7`), which Local Network Access calls **local**, so a public page pulling a
|
||||
dual-stack subresource prompts. `2001:2::/48` is RFC 5180 benchmarking, the v6 twin of the
|
||||
`198.18.0.0/15` v4 default. Never grant that prompt broadly -- it also covers `192.168.5.0/24`,
|
||||
`fd05:5::/64` and Tailscale. Moving the pool leaves mihomo's own TUN addresses (`198.18.0.1`,
|
||||
`fdfe:dcba:9876::1`) alone; no client is ever handed those as an answer.
|
||||
- `tun.stack` stays at upstream's default `mips`; nftables rules out `system` and `mixed`. The
|
||||
gateway runs `mixed`.
|
||||
- The gateway sniffs, the desktop does not. LAN clients that use DoH dial the IP they resolved
|
||||
themselves, and a node may not reach an IP a foreign resolver handed out, so the gateway sniffs
|
||||
the name out of the handshake (`sniffer`, TLS and QUIC on 443, HTTP on 80) and dials that. The
|
||||
cost: a bare-IP connection is judged by its SNI, so `IP-CIDR`/`IP-ASN` rules stop applying to it.
|
||||
On the desktop the fake-ip table already names every flow.
|
||||
- `private_domains`: each entry becomes a DIRECT rule + a `fake-ip-filter` entry.
|
||||
- WeChat/QQ media go over bare CDN IPs, hence `multimedia.nt.qq.com.cn` in `fake-ip-filter`. If
|
||||
media still stalls: `tun_exclude_process: [wechat, WeChatAppEx, qq]` (Linux names; the fix that
|
||||
won clash-verge-rev#1762).
|
||||
- WeChat resolves for itself, but its queries still cross the TUN, so `dns-hijack: any:53` catches
|
||||
them and the fake-ip table hands the name back to the rules: `mmbiz.qpic.cn` measured DIRECT.
|
||||
`multimedia.nt.qq.com.cn` stays in `fake-ip-filter` for the same reason. The exclude-process fix
|
||||
that won clash-verge-rev#1762 is not available here: `hardening/bwraps/wechat.nix` sets
|
||||
`unsharePid = true` and the sandbox reports uid 0, so no process name resolves.
|
||||
- `find-process-mode: off` unless `PROCESS-*` rules or `tun_exclude_process` need it.
|
||||
- `tun_exclude_address`: a destination that can never be a proxy target should not pay a fake-ip
|
||||
round trip -- RFC1918, RFC 6598's `100.64.0.0/10` (where Tailscale addresses come from),
|
||||
link-local, multicast, reserved and documentation prefixes, `fc00::/7`, `fe80::/10`. Never exclude
|
||||
`198.18.0.0/15`, the fake-ip range itself; `tun_exclude_address: []` puts everything back in TUN.
|
||||
link-local, multicast, reserved and documentation prefixes, the cluster ULA `fd05:5::/64` and
|
||||
Tailscale's `fd7a:115c:a1e0::/48`. Never exclude a range that holds a fake-ip pool: that is
|
||||
`198.18.0.0/16` and `2001:2::/64`. The two ULAs are spelled out instead of taking all of
|
||||
`fc00::/7`, which would also exempt any other ULA a client dials; `tun_exclude_address: []` puts
|
||||
everything back in TUN.
|
||||
- Steam's download caches follow the public IP Steam sees on the login (CM) connection, so a proxied
|
||||
Steam downloads from Tokyo/Singapore/HK/Los Angeles. `policy.yaml` pins the CM hosts,
|
||||
`steamcontent.com` and `IP-ASN,32590` DIRECT and keeps `steamcommunity.com` on the proxy -- Steam
|
||||
@@ -106,9 +134,12 @@ grep -oE 'cache[0-9]+-[a-z]{3}[0-9]+' ~/.local/share/Steam/logs/content_log.txt
|
||||
## References
|
||||
|
||||
- [clash-verge-rev#1762](https://github.com/clash-verge-rev/clash-verge-rev/issues/1762) — WeChat
|
||||
media under TUN: fake-ip-filter, qlogo/qpic, the exclude-process fix.
|
||||
- [WeChat-under-TUN retrospective](https://x.com/realchendahuang/status/2104381806862795161) — the
|
||||
IPv6 black hole logic; JD and Taobao break the same way.
|
||||
media under TUN: fake-ip-filter, qlogo/qpic, the exclude-process fix. Its closing comment is the
|
||||
one that matters: a fake v6 pool works on a line with working IPv6 and hangs on a line without.
|
||||
- [mihomo#2233](https://github.com/MetaCubeX/mihomo/issues/2233) — open: the core has no IPv6
|
||||
reachability probe, so `ipv6` cannot stay on for a line that lost IPv6.
|
||||
- [WeChat-under-TUN retrospective](https://x.com/realchendahuang/status/2104381806862795161) — five
|
||||
community fixes; "turn IPv6 off" only ever worked where the line's IPv6 was already broken.
|
||||
- [mihomo wiki](https://wiki.metacubex.one/),
|
||||
[proxy providers](https://wiki.metacubex.one/config/proxy-providers/) — why subscription rules are
|
||||
dropped.
|
||||
|
||||
@@ -11,10 +11,9 @@
|
||||
# The config stays OUT of the Nix store (it holds subscription URLs and the
|
||||
# controller secret) and reaches the service through systemd's LoadCredential.
|
||||
# This module only wires up the service; generate the file with ./generate.nu.
|
||||
# See README.md for usage and the routing notes, in particular why the config
|
||||
# must keep `ipv6: false`: mihomo does not proxy TCP to an IPv6 fake-IP
|
||||
# (fdfe:dcba:9876::/64), so ssh/git connections hang until they time out
|
||||
# (home/base/tui/ssh.nix keeps `AddressFamily inet` as a per-client fallback).
|
||||
# See README.md for usage and the routing notes, in particular the IPv6
|
||||
# condition: the fake-ip v6 pool needs a line whose IPv6 actually works, and
|
||||
# mihomo has no reachability probe to notice when the line loses it.
|
||||
let
|
||||
cfg = config.modules.networking.mihomo;
|
||||
configFile = "${config.users.users.${myvars.username}.home}/.config/mihomo/config.yaml";
|
||||
|
||||
@@ -31,18 +31,23 @@ def main [
|
||||
|
||||
let gw = (open --raw $tmp
|
||||
| from yaml
|
||||
| update ipv6 true
|
||||
| update allow-lan true
|
||||
| update external-controller $"($address):9090"
|
||||
| insert tproxy-port 7893
|
||||
| insert external-ui "ui"
|
||||
| insert external-ui-url $UI_URL
|
||||
| update secret "GATEWAY-SECRET-PLACEHOLDER"
|
||||
| update tun.stack "mixed"
|
||||
# the base leaves the stack at upstream's default; this box has run the kernel stack for months
|
||||
| upsert tun.stack "mixed"
|
||||
| update tun.strict-route false
|
||||
| insert tun.route-address ["0.0.0.0/1", "128.0.0.0/1", "::/1", "8000::/1"]
|
||||
# the desktop's resolver is loopback; a gateway serves the LAN instead
|
||||
| update dns.listen ":1053"
|
||||
| update dns.ipv6 true)
|
||||
# LAN clients that use DoH dial the IPs they resolved; sniff so the node dials a name
|
||||
| insert sniffer {
|
||||
enable: true,
|
||||
"parse-pure-ip": true,
|
||||
sniff: { TLS: { ports: [443] }, HTTP: { ports: [80] }, QUIC: { ports: [443] } }
|
||||
})
|
||||
|
||||
let gw = (if $redact {
|
||||
# a file-backed provider (localyaml) has no url to hide
|
||||
|
||||
@@ -496,10 +496,10 @@ def main [
|
||||
let fpm = (process_mode $all_rules $excluded)
|
||||
|
||||
let config = {
|
||||
# IPv6 stays off on purpose: most proxy nodes have no IPv6 egress, so an
|
||||
# AAAA record becomes a routing black hole that apps such as WeChat, JD,
|
||||
# Taobao and Feishu sit on until they fall back to IPv4.
|
||||
ipv6: false
|
||||
# IPv6 on. fake-ip answers AAAA from a v6 pool, so the family the client
|
||||
# picks never decides the egress family: mihomo dials the proxy, the proxy
|
||||
# dials IPv4. Taobao, JD, WeChat and Bilibili answer identically over -6 and -4.
|
||||
ipv6: true
|
||||
mode: "rule"
|
||||
"mixed-port": 7897
|
||||
"allow-lan": false
|
||||
@@ -517,7 +517,6 @@ def main [
|
||||
tun: (
|
||||
clean ({
|
||||
enable: true
|
||||
stack: "gvisor"
|
||||
"auto-route": true
|
||||
"strict-route": ($spec | get -o tun_strict_route | default true)
|
||||
"auto-detect-interface": true
|
||||
@@ -533,9 +532,12 @@ def main [
|
||||
# answers over loopback (systemd-resolved) need their resolver taken over
|
||||
# while mihomo runs, or fake-ip is bypassed entirely.
|
||||
listen: "127.0.0.1:1053"
|
||||
ipv6: false
|
||||
ipv6: true
|
||||
"enhanced-mode": "fake-ip"
|
||||
"fake-ip-range": "198.18.0.1/16"
|
||||
# AAAA needs its own pool (fake-ip-range6, mihomo >= 1.19.16) and must stay out of
|
||||
# route-exclude-address. Why this pool and not the ULA default: README.md.
|
||||
"fake-ip-range6": "2001:2::1/64"
|
||||
"fake-ip-filter-mode": "blacklist"
|
||||
"prefer-h3": false
|
||||
"respect-rules": false
|
||||
|
||||
@@ -14,8 +14,9 @@
|
||||
# `filter` (Go regex, case-insensitive). The generator also adds "🌍 其他地区"
|
||||
# (manual, everything not matched below) and "🌐 全部地区" (manual, all nodes);
|
||||
# AUTO is the all-node auto pick. Edit the regions freely; the single-char
|
||||
# keywords (港/台/日/新/美/韩) deliberately catch naming variants like
|
||||
# 美西, 日服 and 狮城.
|
||||
# keywords (港/台/日/美/韩) deliberately catch naming variants like 美西, 日服
|
||||
# and 狮城. Spell a country out where one character would also catch a
|
||||
# neighbour: 新 alone catches 新西兰.
|
||||
node_groups:
|
||||
- name: "🇭🇰 香港"
|
||||
filter: "(?i)(港|hk|hong ?kong|🇭🇰)"
|
||||
@@ -24,7 +25,7 @@ node_groups:
|
||||
- name: "🇯🇵 日本"
|
||||
filter: "(?i)(日|jp|japan|东京|🇯🇵)"
|
||||
- name: "🇸🇬 新加坡"
|
||||
filter: "(?i)(新|狮城|sg|singapore|🇸🇬)"
|
||||
filter: "(?i)(新加坡|狮城|sg|singapore|🇸🇬)"
|
||||
- name: "🇰🇷 韩国"
|
||||
filter: "(?i)(韩|kr|korea|🇰🇷)"
|
||||
- name: "🇺🇸 美国"
|
||||
@@ -63,7 +64,8 @@ tun_exclude_address:
|
||||
- 203.0.113.0/24
|
||||
- 224.0.0.0/4 # multicast: mDNS, SSDP
|
||||
- 240.0.0.0/4 # reserved, includes limited broadcast
|
||||
- fc00::/7 # ULA: home network and Tailscale IPv6
|
||||
- fd05:5::/64 # clusterULA6: the k3s node plane, on-link on the LAN
|
||||
- fd7a:115c:a1e0::/48 # Tailscale IPv6 CGNAT: stays on tailscale0
|
||||
- fe80::/10
|
||||
- 2001:db8::/32 # IPv6 documentation
|
||||
- 2002::/16 # deprecated 6to4
|
||||
@@ -126,6 +128,9 @@ rules:
|
||||
|
||||
# local ranges, by address as well as by name
|
||||
- DOMAIN-SUFFIX,lan,DIRECT
|
||||
# MagicDNS names are dialed by name, so the tailnet CIDR rules below cannot see
|
||||
# them; a proxied tailnet peer is unreachable either way.
|
||||
- DOMAIN-SUFFIX,ts.net,DIRECT
|
||||
- IP-CIDR,0.0.0.0/8,DIRECT,no-resolve
|
||||
- IP-CIDR,10.0.0.0/8,DIRECT,no-resolve
|
||||
- IP-CIDR,100.64.0.0/10,DIRECT,no-resolve
|
||||
@@ -134,6 +139,10 @@ rules:
|
||||
- IP-CIDR,192.168.0.0/16,DIRECT,no-resolve
|
||||
- IP-CIDR,224.0.0.0/4,DIRECT,no-resolve
|
||||
- IP-CIDR6,fe80::/10,DIRECT,no-resolve
|
||||
# IPv6 counterparts of the LAN and tailnet CIDR rules above; a belt for the
|
||||
# route-exclude-address entries, which keep these off the TUN already.
|
||||
- IP-CIDR6,fd05:5::/64,DIRECT,no-resolve
|
||||
- IP-CIDR6,fd7a:115c:a1e0::/48,DIRECT,no-resolve
|
||||
|
||||
# everything else that is CN by geolocation, then the catch-all
|
||||
- GEOSITE,cn,DIRECT
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Copy to ~/.config/mihomo/sources.yaml, fill it in, then run `nu generate.nu`.
|
||||
# Copy to ~/.config/mihomo/sources.yaml, fill it in, then run `just mihomo-gen`.
|
||||
# Keep it mode 0600 and never commit it: subscription URLs, the dashboard
|
||||
# password and private domain names all live here.
|
||||
#
|
||||
|
||||
@@ -8,6 +8,8 @@ rec {
|
||||
# it's a subrouter with a transparent proxy
|
||||
proxyGateway = "192.168.5.178";
|
||||
proxyGateway6 = "fe80::8";
|
||||
# node identity must not ride the WAN /64: the ISP retires it, Cilium never refreshes it.
|
||||
clusterULA6 = "fd05:5::";
|
||||
nameservers = [
|
||||
# IPv4
|
||||
"119.29.29.29" # DNSPod https://www.dnspod.cn/Products/publicdns
|
||||
|
||||
Reference in new issue
Block a user