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:
ryan4yin | 二花 authored and GitHub committed 2026-10-09 18:38:20 +08:00
commit 92740ec968
12 files changed
+134 -52

No files matched your search

+4
View File
@@ -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/
+19
View File
@@ -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
View File
@@ -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
-2
View File
@@ -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";
+19 -8
View File
@@ -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.
#
+2
View File
@@ -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