<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>技术 on 亂筆</title>
        <link>https://blog.l3zc.com/categories/tech/</link>
        <description>Recent content in 技术 on 亂筆</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>zh</language><atom:link href="https://blog.l3zc.com/categories/tech/index.xml" rel="self" type="application/rss+xml" /><item>
            <title>关于 DNS 泄露及其相关误解的说明</title>
            <link>https://blog.l3zc.com/2026/05/dns-leak-misunderstanding/</link>
            <pubDate>Mon, 18 May 2026 00:46:00 +0800</pubDate>
            <guid>https://blog.l3zc.com/2026/05/dns-leak-misunderstanding/</guid>
            <description>&lt;p&gt;点进这篇文章的人想必基本都对 DNS 泄露这一概念有所耳闻了，其中也不乏谈 DNS 泄露色变者，由于评论区和 Issue 里提出相关问题的人已经有数位，鄙人这次写一篇说明，尽可能将这个问题讲清楚，澄清一些大家对这个概念可能的误解，也为各位编写自己的代理配置提供一个参考。&lt;/p&gt;&#xA;&lt;p&gt;这个概念已经被一些 VPN 厂商和一大堆 YouTuber 讲烂了，基本的意思是：当你在代理时，电脑却通过本地网络向本地 DNS 请求你正在访问的网址，使得运营商可以依据你的 DNS 请求记录得知你的浏览记录。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-mermaid&#34;&gt;flowchart LR&#xA;    U[用户设备&lt;br/&gt;浏览器 / App] --&gt; VPN[VPN 客户端&lt;br/&gt;加密隧道]&#xA;&#xA;    VPN --&gt;|正常情况：DNS 请求走 VPN| VPNDNS[VPN 提供的 DNS 服务器]&#xA;    VPNDNS --&gt; NET[目标网站 / 互联网]&#xA;&#xA;    U -.-&gt;|异常情况：DNS 泄露| ISP[本地 ISP DNS 服务器]&#xA;    ISP -.-&gt; LOG[ISP / 第三方可记录&lt;br/&gt;访问过的域名]&#xA;&#xA;    NET --&gt; SITE[example.com]&#xA;&#xA;    subgraph Normal[正常 DNS 解析路径]&#xA;        VPN&#xA;        VPNDNS&#xA;        NET&#xA;    end&#xA;&#xA;    subgraph Leak[DNS 泄露路径]&#xA;        ISP&#xA;        LOG&#xA;    end&#xA;&#xA;    classDef safe fill:#e8f7ee,stroke:#2e7d32,stroke-width:2px,color:#1b5e20;&#xA;    classDef danger fill:#fdecea,stroke:#c62828,stroke-width:2px,color:#7f0000;&#xA;    classDef user fill:#e3f2fd,stroke:#1565c0,stroke-width:2px,color:#0d47a1;&#xA;&#xA;    class U user;&#xA;    class VPN,VPNDNS,NET,SITE safe;&#xA;    class ISP,LOG danger;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;搞清楚需求&#34;&gt;搞清楚需求&#xA;&lt;/h2&gt;&lt;p&gt;首先需要想明白的是，我们煞费苦心地防止 DNS 泄露究竟是为了什么——大家的第一反应可能都是：「那还用说！肯定是为了不让审查者看到我们通过代理访问了哪些网站啊！」&lt;/p&gt;&#xA;&lt;p&gt;其实这个说法并不完全准确，具体还得看情况。不妨想一想：让审查者知道你在刷抖音、聊微信会有什么实质性的危害吗？恐怕没有。但如果让审查者发现你在访问维基百科、浏览 X（推特）或者其他带有明确政治倾向的敏感内容，那问题就有点大了。防止 DNS 泄露的目的，从来不是为了让你在所谓的「DNS 泄露测试」网站上拿到一个全绿的满分，也不是为了隐藏你使用国内常用服务的事实，而是为了不让审查者知道你在访问那些被封禁、被视为敏感的网站和服务。&lt;/p&gt;&#xA;&lt;p&gt;对于生活在墙内的我们来说，区分什么是敏感内容其实非常简单：直接参考 GFWList&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; 即可——能让 GFW 专门将其重点关照的，自然就是敏感内容。&lt;/p&gt;&#xA;&lt;p&gt;那些 DNS 泄露测试的网站，测试用的随机域名甚至连 GFWList 的边都沾不上，本身就毫无敏感性可言。对于一些经过优化的正常代理配置来说，这类域名连看都不想多看一眼，通常会直接被略过并扔给最后的兜底规则（比如 &lt;code&gt;MATCH, DIRECT&lt;/code&gt;）去处理，这个过程自然而然地触发了本地 DNS 解析。于是，测试网站便如获至宝般地亮起红灯，警告你「存在 DNS 泄露」。但明白机制的我们自然清楚，很多时候这种所谓的泄露完全是无关痛痒的，只要配置得当，你真正需要保护的敏感访问记录，早就匹配到前面的分流规则被安全地送进加密隧道了。&lt;/p&gt;&#xA;&lt;p&gt;更有甚者，诸如 Surfshark、ExpressVPN 这类海外商业 VPN 厂商，以及一众跟着恰饭的无良 YouTuber，成天拿这种毫无实际意义的测试结果制造隐私焦虑。可笑的是，这些传统的商业 VPN 客户端往往连最基础的域名分流规则功能都不具备，全靠粗暴的一把梭将所有网络请求强行塞进虚拟网卡。而更讽刺的是，即便是这种简单粗暴的全局代理，他们过去还曾因为自身客户端软件的缺陷，真真切切地导致过 DNS 泄露问题。所以，与其听信这些贩卖焦虑的恰饭营销，不如自己弄懂代理软件的运作原理。&lt;/p&gt;&#xA;&lt;h2 id=&#34;出现泄露的环节&#34;&gt;出现泄露的环节&#xA;&lt;/h2&gt;&lt;p&gt;其实现在主流的代理软件都比较成熟，配置得当完全不需要担心 DNS 泄露的问题。问题出就出在很多配置根本就不得当。接下来我将会简要地解释代理软件在系统代理模式和透明代理（虚拟网卡）模式下可能出现 DNS 泄露的环节。&lt;/p&gt;&#xA;&lt;h3 id=&#34;代理软件本身&#34;&gt;代理软件本身&#xA;&lt;/h3&gt;&lt;p&gt;在使用系统代理的情况下（假设代理地址是&lt;code&gt;socks5://127.0.0.1:7890&lt;/code&gt;&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;）用浏览器访问&lt;code&gt;https://www.example.com&lt;/code&gt;，会经历如下的请求过程：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;第一步：浏览器会把网络请求打包发送给&lt;code&gt;socks5://127.0.0.1:7890&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;第二步：代理软件收到请求后根据配置的分流规则匹配节点&lt;/li&gt;&#xA;&lt;li&gt;第三步：代理软件根据匹配结果向节点转发整个网络请求&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-mermaid&#34;&gt;sequenceDiagram&#xA;    participant B as 浏览器&#xA;    participant P as 代理软件&lt;br/&gt;(127.0.0.1:7890)&#xA;    participant R as 分流规则引擎&#xA;    participant N as 远端代理节点&#xA;&#xA;    B-&gt;&gt;P: ① 将请求打包发送&lt;br/&gt;(携带域名, 不做 DNS 解析)&#xA;    Note over B,P: 浏览器不发起 DNS 查询&lt;br/&gt;不会泄露&#xA;    P-&gt;&gt;R: ② 根据域名等信息&lt;br/&gt;匹配分流规则&#xA;    Note over P,R: 规则匹配过程&lt;br/&gt;可能触发 DNS 查询 ⚠️&#xA;    R--&gt;&gt;P: 返回匹配结果&lt;br/&gt;(PROXY / DIRECT / REJECT)&#xA;    P-&gt;&gt;N: ③ 加密转发请求至节点&#xA;    Note over P,N: 加密隧道传输&lt;br/&gt;不会泄露&#xA;    N--&gt;&gt;P: 返回响应&#xA;    P--&gt;&gt;B: 返回响应&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;在整个发起请求的过程中，浏览器将网络请求打包交给代理，不会向本地 DNS 发出请求，这一环节不会产生 DNS 泄露；代理软件将网络请求转发给服务器，整个过程加密，也不会产生 DNS 泄露；真正产生 DNS 泄露的环节，是根据分流规则匹配的过程。&lt;/p&gt;&#xA;&lt;p&gt;向本地 DNS 发起域名查询这件事情本身并不一定是坏事，但配置不当的分流规则会让代理软件在拿到域名后过早地向本地 DNS 发起查询，导致泄露不应泄露的隐私信息。假设在极端情况下，盖世太保从运营商调取所有人的 DNS 查询记录，凡是查询过域名包含 GFW 封锁列表中的项目者都拉出去枪毙，在这种情况下，为了保全自身，我们需要恰当地配置分流规则。&lt;/p&gt;&#xA;&lt;p&gt;以使用广泛的 Mihomo 内核为例，在作为客户端开启系统代理模式、使用 Chrome 进行到前文所述的第二步时，内核已知的信息有：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;主机&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;：&lt;code&gt;example.com&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;目标端口：443&lt;/li&gt;&#xA;&lt;li&gt;网络协议：TCP&lt;/li&gt;&#xA;&lt;li&gt;发起请求的进程的名称和具体路径：&lt;code&gt;C:\Application\chrome.exe&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;入站 IP：&lt;code&gt;127.0.0.1&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;入站名称：&lt;code&gt;DEFAULT-MIXED&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;入站端口：随机的高位端口&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;内核不会进行多余的 DNS 请求，根据已知的信息，内核可以在不发起 DNS 解析的情况下，匹配所有基于域名、端口、进程名称以及网络协议匹配的路由规则：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;rules:&#xA;  # 基于域名的匹配规则&#xA;  - DOMAIN,ad.com,REJECT&#xA;  - DOMAIN-SUFFIX,google.com,auto&#xA;  - DOMAIN-KEYWORD,google,auto&#xA;  - DOMAIN-WILDCARD,*.google.com,auto&#xA;  - DOMAIN-REGEX,^abc.*com,PROXY&#xA;  - GEOSITE,youtube,PROXY&#xA;  # 基于端口的匹配规则&#xA;  - DST-PORT,80,DIRECT&#xA;  - SRC-PORT,7777,DIRECT&#xA;  # 基于进程名称的匹配规则&#xA;  - PROCESS-PATH,/usr/bin/wget,PROXY&#xA;  - PROCESS-PATH,C:\Application\chrome.exe,PROXY&#xA;  - PROCESS-NAME,curl,PROXY&#xA;  - PROCESS-NAME,chrome.exe,PROXY&#xA;  # 基于网络协议的匹配规则&#xA;  - NETWORK,UDP,REJECT&#xA;  # 不依赖任何信息的匹配规则&#xA;  - MATCH,DIRECT&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;因此，上述这些种类的规则不会产生 DNS 泄露，因为内核匹配它们的时候根本没必要发起 DNS 解析。&lt;/p&gt;&#xA;&lt;p&gt;对于剩下种类的规则，需要在第二步获知目标 IP 信息才能让匹配进行下去：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;rules:&#xA;  - GEOIP,cn,DIRECT&#xA;  - IP-CIDR,127.0.0.0/8,DIRECT&#xA;  - IP-SUFFIX,8.8.8.8/24,PROXY&#xA;  - IP-ASN,13335,DIRECT&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;在默认情况下，为了确保匹配的准确性，进行到第二步时，内核不知道目标 IP，只知道目标主机地址&lt;code&gt;www.example.com&lt;/code&gt;，此时内核不得不对&lt;code&gt;www.example.com&lt;/code&gt;的 IP 地址进行查询，这也就造成了 DNS 泄露。如果在规则的末尾加上&lt;code&gt;no-resolve&lt;/code&gt;&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;，则匹配到基于 IP 的规则时会直接跳过，这就使得&lt;code&gt;no-resolve&lt;/code&gt;很适合用来分流 Telegram 等软件的裸 IP 请求。&lt;/p&gt;&#xA;&lt;h3 id=&#34;透明代理的问题tun-模式&#34;&gt;透明代理的问题（TUN 模式&lt;sup id=&#34;fnref:5&#34;&gt;&lt;a href=&#34;#fn:5&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt;）&#xA;&lt;/h3&gt;&lt;p&gt;透明代理顾名思义即被代理的系统/软件感知不到自己被代理了的代理方式。这种方式非常省事，TUN 模式一开，所有软件通通都能用，无须繁琐的逐一配置不遵循系统代理的软件。代价就是这种省事本身——目标系统/软件都不知道自己被代理了，他们在正常网络上该有的行为都会照常进行，其中自然也包括 DNS 解析，可是目标系统/软件并不知道自己被代理了，他们想要和远端建立连接，总得要先拿到一个 IP 才行吧。为了解决这个问题，以 Mihomo 为例，有两种 DNS 劫持模式，&lt;code&gt;redir-host&lt;/code&gt;和&lt;code&gt;fake-ip&lt;/code&gt;。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;redir-host 模式：&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-mermaid&#34;&gt;flowchart TD&#xA;    A1[目标软件发起 DNS 查询] --&gt; A2[代理软件向上游 DNS&lt;br/&gt;查询真实 IP]&#xA;    A2 --&gt; A3[上游 DNS 返回真实 IP]&#xA;    A3 --&gt; A4[将真实 IP 返回给目标软件]&#xA;    A4 --&gt; A5[目标软件用真实 IP 建立连接]&#xA;    A5 --&gt; A6[代理软件拦截并分流]&#xA;&#xA;    A2 -.- LEAK[&#34;⚠️ 真实 DNS 请求泄露到外部网络&#34;]&#xA;&#xA;    classDef danger fill:#fdecea,stroke:#c62828,stroke-width:2px,color:#7f0000;&#xA;    class LEAK danger;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;fake-ip 模式：&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-mermaid&#34;&gt;flowchart TD&#xA;    B1[目标软件发起 DNS 查询] --&gt; B2[代理软件立即返回假 IP&lt;br/&gt;如 198.18.0.x]&#xA;    B2 --&gt; B3[目标软件用假 IP 建立连接]&#xA;    B3 --&gt; B4[代理软件拦截, 查映射表&lt;br/&gt;还原真实域名]&#xA;    B4 --&gt; B5[通过加密隧道转发请求]&#xA;&#xA;    classDef safe fill:#e8f7ee,stroke:#2e7d32,stroke-width:2px,color:#1b5e20;&#xA;    class B2,B4,B5 safe;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;其中&lt;code&gt;redir-host&lt;/code&gt;比较老实，既然目标系统/软件发起了 DNS 查询，那就老老实实通过上游 DNS 去查，把查询到的真实的 IP 返回给目标系统/软件。由于这种模式下系统必定会发生一次真实的 DNS 解析请求寻址过程，存在着先天的结构性缺陷，不可避免地会造成 DNS 泄露。原来的 Clash 核心在后续的版本更新中干脆直接砍掉了 &lt;code&gt;redir-host&lt;/code&gt; 模式。&lt;/p&gt;&#xA;&lt;p&gt;不过，继承了 Clash Meta 核心的 Mihomo 内核选择了将其保留下来，并且加入了&lt;code&gt;sniffer&lt;/code&gt;&lt;sup id=&#34;fnref:6&#34;&gt;&lt;a href=&#34;#fn:6&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;6&lt;/a&gt;&lt;/sup&gt;对其进行增强。在&lt;code&gt;redir-host&lt;/code&gt;模式下，&lt;code&gt;sniffer&lt;/code&gt;依靠嗅探数据包中的特征（比如从 TLS 的 SNI 字段&lt;sup id=&#34;fnref:7&#34;&gt;&lt;a href=&#34;#fn:7&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;7&lt;/a&gt;&lt;/sup&gt;）尝试将被解析成裸 IP 的流量恢复成连接域名，再用这个域名走一遍分流规则的匹配，补齐了&lt;code&gt;redir-host&lt;/code&gt;模式在分流上的诸多短板。但需要特别注意：&lt;code&gt;sniffer&lt;/code&gt; 解决的是分流准确性的问题，&lt;strong&gt;并不能消除 DNS 泄露本身&lt;/strong&gt;——DNS 查询请求在 sniffer 介入之前就已经发出去了，运营商该记录的早就记录了。&lt;/p&gt;&#xA;&lt;p&gt;但这里需要提醒：如果你在透明代理环境下仍然坚持使用&lt;code&gt;redir-host&lt;/code&gt;模式，就必须在配置一套无污染的 DNS，具体的例子可以参考我的&lt;a class=&#34;link&#34; href=&#34;https://blog.l3zc.com/2025/02/what-i-have-done-on-my-dns/&#34; &gt;另一篇文章&lt;/a&gt;，否则你的 DNS 访问记录依然会泄露。&lt;/p&gt;&#xA;&lt;p&gt;相对而言，&lt;code&gt;fake-ip&lt;/code&gt;模式则采用了截然不同的解决思路，不仅完美规避了透明代理环境下的 DNS 泄露问题，甚至还能顺带提升浏览器的网页响应速度。&lt;/p&gt;&#xA;&lt;h2 id=&#34;如何规避-dns-泄露&#34;&gt;如何规避 DNS 泄露&#xA;&lt;/h2&gt;&lt;h3 id=&#34;优化喂给代理软件的分流规则&#34;&gt;优化喂给代理软件的分流规则&#xA;&lt;/h3&gt;&lt;p&gt;还是以 Mihomo 内核为例，规则的匹配是从上到下顺序进行的，内核每收到一个请求都会按顺序从上到下一条一条地匹配，直到匹配到目标规则为止。这也就意味着如果一个域名已经被第一条规则匹配，之后的第二、第三、乃至第一千万条规则的匹配流程都不会触发。&lt;/p&gt;&#xA;&lt;p&gt;基于这一点，我们编写规则时应当要&lt;strong&gt;遵循「域名规则在前，IP规则在后」的总原则&lt;/strong&gt;，即使是针对某个特定服务的规则集，也要为其中的 IP 规则加入&lt;code&gt;no-resolve&lt;/code&gt;标记，一个典型的例子如下：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-text&#34;&gt;DOMAIN-SUFFIX,e-hentai.org&#xA;DOMAIN-SUFFIX,ehgt.org&#xA;DOMAIN-SUFFIX,ehwiki.org&#xA;DOMAIN-SUFFIX,exhentai.org&#xA;DOMAIN-SUFFIX,hath.network&#xA;DOMAIN-SUFFIX,hentaiverse.org&#xA;IP-CIDR,178.175.128.0/21,no-resolve&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;当使用 GeoSite 和 GeoIP 数据库时，也应当为靠前的 IP 规则添加&lt;code&gt;no-resolve&lt;/code&gt;标记。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;rules:&#xA;  - GEOSITE,telegram,Telegram&#xA;  - GEOIP,telegram,Telegram,no-resolve&#xA;  - GEOSITE,gfw,Proxy&#xA;  - GEOIP,cn,DIRECT&#xA;  - Match,Proxy&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;就以上面的配置规则为例，当我们的设备发起不同类型的网络请求时，Mihomo 内核会按照自上而下的顺序进行规则匹配。具体流程可以通过下面的图表来梳理：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-mermaid&#34;&gt;flowchart TD&#xA;    Start[发起网络请求&lt;br/&gt;目标: 域名或 IP] --&gt; Rule1{匹配 GEOSITE,telegram?}&#xA;&#xA;    Rule1 -- 是 --&gt; ProxyTG[走 Telegram 代理节点]&#xA;    Rule1 -- 否 --&gt; Rule2{匹配 GEOIP,telegram,no-resolve?}&#xA;&#xA;    Rule2 -- 是&lt;br/&gt;(仅限直接请求IP) --&gt; ProxyTG&#xA;    Rule2 -- 否 --&gt; Rule3{匹配 GEOSITE,gfw?}&#xA;&#xA;    Rule3 -- 是 --&gt; ProxyGFW[走 Proxy 代理节点]&#xA;    Rule3 -- 否 --&gt; NeedIP[⚠️ 需要 IP 地址进行后续匹配&lt;br/&gt;会触发本地 DNS 解析]&#xA;&#xA;    NeedIP --&gt; Rule4{匹配 GEOIP,cn?}&#xA;&#xA;    Rule4 -- 是 --&gt; Direct[走 DIRECT 直连出站]&#xA;    Rule4 -- 否 --&gt; Rule5[匹配 Match,Proxy]&#xA;&#xA;    Rule5 --&gt; ProxyOther[走 Proxy 代理节点]&#xA;&#xA;    classDef warn fill:#fff3e0,stroke:#ef6c00,stroke-width:3px,color:#8a4b00;&#xA;    class NeedIP warn;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;从上述流程图中我们可以清晰地看到规则顺序和 &lt;code&gt;no-resolve&lt;/code&gt; 属性的作用。当我们访问 Telegram 服务时，由于命中了靠前的 &lt;code&gt;GEOSITE&lt;/code&gt; 或带有 &lt;code&gt;no-resolve&lt;/code&gt; 的 &lt;code&gt;GEOIP&lt;/code&gt; 规则，请求被直接路由至代理，完全避免了在这一环节产生 DNS 解析。&lt;/p&gt;&#xA;&lt;p&gt;同理，当我们访问其他任何在 GFW 列表上的网站时，会在匹配到 &lt;code&gt;GEOIP,cn,DIRECT&lt;/code&gt; 这条不得不触发本地 DNS 解析的规则之前，就率先匹配到了 &lt;code&gt;GEOSITE,gfw,Proxy&lt;/code&gt; 规则。此时代理软件直接将请求转发至节点，后续基于 IP 的规则因为不再执行，相关的 DNS 解析请求也就被成功避免，杜绝了因为 DNS 请求导致敏感浏览记录泄露的可能性，审查者再也不会知道你在访问敏感的网站和服务了。&lt;/p&gt;&#xA;&lt;h3 id=&#34;用-fake-ip-规避透明代理问题&#34;&gt;用 Fake-IP 规避透明代理问题&#xA;&lt;/h3&gt;&lt;p&gt;相比优化分流规则，&lt;code&gt;fake-ip&lt;/code&gt; 模式则从根源上解决了透明代理下的 DNS 泄露问题，不仅完美规避了泄露风险，甚至还能顺带提升浏览器的网页响应速度。&lt;/p&gt;&#xA;&lt;p&gt;原理非常巧妙：当目标软件发起 DNS 查询时，代理软件并不会傻乎乎地去本地或远端上游查询真实 IP，而是直接在本地瞬间返回一个假的内网保留 IP（例如&lt;code&gt;198.18.0.2&lt;/code&gt;&lt;sup id=&#34;fnref:8&#34;&gt;&lt;a href=&#34;#fn:8&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;8&lt;/a&gt;&lt;/sup&gt;）。目标软件拿到这个假 IP 后信以为真，紧接着就会拿着这个假 IP 尝试建立连接。&lt;/p&gt;&#xA;&lt;p&gt;而在代理软件这里，它只需要拦截所有发往自己签发过的这些假 IP 的请求即可。由于代理软件内部维护了一张详细的「假 IP 对应真实域名」的映射表，所以即便目标软件发来的是往假 IP 发送的包，代理软件依然心知肚明你其实想访问的是什么域名。于是，代理软件直接提取出原始域名，将网络请求顺着加密代理隧道发往远端节点去处理。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-mermaid&#34;&gt;sequenceDiagram&#xA;    participant App as 目标软件&lt;br/&gt;(浏览器等)&#xA;    participant TUN as TUN 虚拟网卡&#xA;    participant Proxy as 代理软件&lt;br/&gt;(Mihomo)&#xA;    participant Node as 远端代理节点&#xA;    participant Web as 目标网站&#xA;&#xA;    App-&gt;&gt;TUN: DNS 查询: google.com 的 IP 是?&#xA;    TUN-&gt;&gt;Proxy: 转发 DNS 查询&#xA;    Note over Proxy: 不向外部发起任何 DNS 请求!&lt;br/&gt;从 198.18.0.0/15 池中分配假 IP&lt;br/&gt;记录映射: 198.18.0.5 → google.com&#xA;    Proxy--&gt;&gt;TUN: 返回假 IP: 198.18.0.5&#xA;    TUN--&gt;&gt;App: DNS 响应: 198.18.0.5&#xA;&#xA;    App-&gt;&gt;TUN: 连接 198.18.0.5:443 (HTTPS)&#xA;    TUN-&gt;&gt;Proxy: 转发连接请求&#xA;    Note over Proxy: 查映射表:&lt;br/&gt;198.18.0.5 → google.com&lt;br/&gt;匹配分流规则 → PROXY&#xA;    Proxy-&gt;&gt;Node: 加密转发: 请连接 google.com:443&#xA;    Node-&gt;&gt;Web: 建立真实连接&#xA;    Web--&gt;&gt;Node: 返回网页内容&#xA;    Node--&gt;&gt;Proxy: 加密回传&#xA;    Proxy--&gt;&gt;TUN: 返回内容&#xA;    TUN--&gt;&gt;App: 收到响应&#xA;&#xA;    Note over App,Web: 全程无真实 DNS 请求泄露到外部网络 ✓&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;在整个&lt;code&gt;fake-ip&lt;/code&gt;模式的精妙配合下，你的设备对外不仅没有泄露隐私的 DNS 请求，甚至可以说针对这些由代理软件接管的流量根本就没有向外部发起过常规的 DNS 查询，审查者自然无从得知你要访问何处。同时由于免去了本地苦等 DNS 真实响应的时间，还能顺带大幅度降低网页访问的首字节加载延迟（TTFB&lt;sup id=&#34;fnref:9&#34;&gt;&lt;a href=&#34;#fn:9&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;9&lt;/a&gt;&lt;/sup&gt;），一石二鸟。&lt;/p&gt;&#xA;&lt;h2 id=&#34;太长不想读小结&#34;&gt;太长不想读（小结）&#xA;&lt;/h2&gt;&lt;p&gt;DNS 泄露本身并不可怕，可怕的是不明就里地被焦虑营销牵着鼻子走。理解了机制之后，应对思路其实很清晰：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;系统代理模式&lt;/strong&gt;：遵循「域名规则在前，IP 规则在后」的原则，为所有 IP 类规则加上 &lt;code&gt;no-resolve&lt;/code&gt;，敏感域名在触发 DNS 解析之前就已经被分流走了。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;TUN 透明代理模式&lt;/strong&gt;：优先选用 &lt;code&gt;fake-ip&lt;/code&gt;，从根源上杜绝 DNS 查询外泄，同时还能获得更低的 TTFB。如果坚持使用 &lt;code&gt;redir-host&lt;/code&gt;，必须搭配无污染的上游 DNS，且要清楚 sniffer 只能改善分流准确性，无法阻止 DNS 请求本身的泄露。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;不要迷信泄露测试网站&lt;/strong&gt;：测试用的随机域名根本不在 GFW 封锁列表内，触发本地解析是正常且无害的行为，绿灯与否不是衡量配置质量的标准。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;被中国大陆防火长城（Great Firewall, GFW）封锁的域名列表。&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:2&#34;&gt;&#xA;&lt;p&gt;SOCKS5 是一种网络代理协议（RFC 1928），工作在会话层，支持 TCP 和 UDP 转发，且允许客户端将域名直接传递给代理服务器解析，而非在本地解析后再连接——这正是它在防止 DNS 泄露方面优于普通 HTTP 代理（非 CONNECT 隧道模式）的关键特性。&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:3&#34;&gt;&#xA;&lt;p&gt;可以理解为域名。&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:4&#34;&gt;&#xA;&lt;p&gt;&lt;code&gt;no-resolve&lt;/code&gt; 是附加在 IP 类规则末尾的标记，作用是告诉内核：当流量的目标是域名而非 IP 时，跳过此规则，不要为了匹配它而主动发起 DNS 解析。但如果流量本身就是直接连接 IP（如 Telegram 客户端直连服务器 IP），则该规则仍然会正常参与匹配。&amp;#160;&lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:5&#34;&gt;&#xA;&lt;p&gt;也就是我们所说的虚拟网卡模式&amp;#160;&lt;a href=&#34;#fnref:5&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:6&#34;&gt;&#xA;&lt;p&gt;流量嗅探（Sniffer）是指代理软件通过检查数据包的协议特征来还原目标域名的技术。例如，HTTPS 连接在握手阶段会以明文发送目标域名（即 SNI），HTTP 请求头中也包含 Host 字段，代理软件可以从这些特征中提取出真实的目标域名。&amp;#160;&lt;a href=&#34;#fnref:6&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:7&#34;&gt;&#xA;&lt;p&gt;Server Name Indication，TLS 协议的扩展字段。由于同一 IP 上可能托管多个 HTTPS 站点，客户端在 TLS 握手的 ClientHello 阶段需要以明文告知服务器自己要访问哪个域名，以便服务器返回正确的证书。这也是为什么即使使用了 HTTPS，中间人仍然能看到你访问的域名（但看不到具体路径和内容）。&amp;#160;&lt;a href=&#34;#fnref:7&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:8&#34;&gt;&#xA;&lt;p&gt;Mihomo 默认使用 &lt;code&gt;198.18.0.0/15&lt;/code&gt; 地址段分配假 IP。该地址段由 IANA 在 RFC 2544 中保留，专门用于网络设备基准测试，不会出现在正常的互联网路由表中，因此不会与任何真实的公网地址冲突。整个 /15 段可提供约 131,072 个地址供映射使用。&amp;#160;&lt;a href=&#34;#fnref:8&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:9&#34;&gt;&#xA;&lt;p&gt;Time To First Byte，即从客户端发出请求到收到服务器响应第一个字节所经过的时间。在传统模式下，浏览器需要先等待 DNS 解析完成（通常耗时 20-120ms）才能发起 TCP 连接；而 fake-ip 模式下 DNS 响应几乎是瞬时的（&amp;lt;1ms），因此能显著缩短整体的页面加载时间。&amp;#160;&lt;a href=&#34;#fnref:9&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/div&gt;&#xA;</description>
        </item><item>
            <title>Terraform IaC 初体验</title>
            <link>https://blog.l3zc.com/2026/04/iac-with-terraform/</link>
            <pubDate>Thu, 09 Apr 2026 15:13:35 +0800</pubDate>
            <guid>https://blog.l3zc.com/2026/04/iac-with-terraform/</guid>
            <description>&lt;img src=&#34;https://blog.l3zc.com/2026/04/iac-with-terraform/cover_hu_a8e83f97ed61569c.webp&#34; alt=&#34;Featured image of post Terraform IaC 初体验&#34; /&gt;&lt;p&gt;Terraform 是一款 IaC 工具，所谓 IaC 就是「基础设施即代码」，将我们的基础设施以声明式的代码写出来，随后使用&lt;code&gt;terraform apply&lt;/code&gt;即可完成基础设施的部署，同一份配置，部署出来的一定是一模一样的基础设施（Nix OS 用户狂喜）。&lt;/p&gt;&#xA;&lt;h2 id=&#34;为什么要用-terraform&#34;&gt;为什么要用 Terraform&#xA;&lt;/h2&gt;&lt;p&gt;传统的基础设施管理绝大部分基于人力和各种云服务商的 Dashboard，这就带来了下面这些痛点：&lt;/p&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th style=&#34;text-align: left&#34;&gt;痛点&lt;/th&gt;&#xA;          &lt;th style=&#34;text-align: left&#34;&gt;说明&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;&lt;strong&gt;难以重现&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;通过 Dashboard 点点点进行配置，容易改错或者改漏，而且难以复现&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;&lt;strong&gt;环境漂移&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;手动操作导致生产和测试环境配置逐渐偏离，导致极端情况下测试没问题生产直接崩&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;&lt;strong&gt;难以扩展&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;新增一套环境需要重复大量手工操作，耗时且容易出错&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;&lt;strong&gt;难以审计&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;没有变更记录，&lt;del&gt;出了事不好甩锅&lt;/del&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;&lt;strong&gt;难以协作&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;基础设施由少数「懂的人」掌控，谁想要改都得去找这些人，效率低&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;为了解决这些问题，「基础设施即代码」&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;的概念被提了出来，Terraform 就是其中一个著名的解决方案。&lt;/p&gt;&#xA;&lt;p&gt;假设一个现实的使用场景，例如你每年都会购买新的 GCP 的 $300 试用金账号，虽然便宜，但是每年都要重新跑到 GCP 的面板里开机器。而有了 Terraform，想要部署同样配置的机器，只要在每次更换账号之后，将 API Token 替换成新的，随后&lt;code&gt;terraform apply&lt;/code&gt;，只需要几分钟就能开出和你上个账号一模一样的机器、VPC、S3、防火墙配置等等。&lt;/p&gt;&#xA;&lt;p&gt;再比如 Cloudflare DNS 和 Tunnel 的管理和迁移，也只需要将原 Tunnel 的 Ingress Rule 复制到新 Tunnel 的 Ingress Rule。即使将来要迁移到 AliDNS、Route 53 等其他服务商，我们也可以原封不动的将数据复制过去&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;。&lt;/p&gt;&#xA;&lt;p&gt;尤其是到了多人协作的时候，配合 Git，每次更改都有迹可循，更不需要担心合并冲突，PR 可以自动生成更改预览，出现问题可以立刻回滚到上一个版本，这些都是传统依赖人力去直接操作服务提供商 Dashboard 的做法完全无法做到的。&lt;/p&gt;&#xA;&lt;h2 id=&#34;terraform-的安装&#34;&gt;Terraform 的安装&#xA;&lt;/h2&gt;&lt;p&gt;Terraform 是用 Go 写的，编译出来的产物自然也是单个可执行文件，所以安装起来也非常容易，Windows 下可以直接使用 Winget 安装：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-pwsh&#34;&gt;winget install Hashicorp.Terraform&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;如果不想使用 Winget，也可以使用 Scoop 等其他包管理器，或者将预编译的二进制文件放入&lt;code&gt;$PATH&lt;/code&gt;，即可完成安装。&lt;/p&gt;&#xA;&lt;p&gt;如果你在 Linux 环境下，则使用对应的包管理器安装即可，如果使用将二进制文件放入&lt;code&gt;$PATH&lt;/code&gt;的安装方法，则要记得&lt;code&gt;sudo chmod +x terraform&lt;/code&gt;赋予文件的执行权限。&lt;/p&gt;&#xA;&lt;h2 id=&#34;terraform-的两个基本概念&#34;&gt;Terraform 的两个基本概念&#xA;&lt;/h2&gt;&lt;h3 id=&#34;providers&#34;&gt;Provider(s)&#xA;&lt;/h3&gt;&lt;p&gt;我们前面已经提到，Terraform 是一个款础设施即代码工具，作为一个工具本身，他并不绑定某个平台，而是通过 Provider(s) 与各个平台对接，要查看有哪些 Provider(s)，可以浏览 &lt;a class=&#34;link&#34; href=&#34;https://registry.terraform.io/browse/providers&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;Terraform 的 Registry&lt;/a&gt;。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2026/04/iac-with-terraform/image_hu_463ebc576c22c691.webp&#34; alt=&#34;Terraform 拥有丰富的 Provider(s) 生态&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;h3 id=&#34;状态管理&#34;&gt;状态管理&#xA;&lt;/h3&gt;&lt;p&gt;Terraform 将每次执行基础设施变更操作时的状态信息保存在一个状态文件中，默认情况下会保存在当前工作目录下的&lt;code&gt;terraform.tfstate&lt;/code&gt;文件里&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;，我们也可以修改配置文件自行指定其他的存储后端，例如 S3、Postgres。每次我们执行&lt;code&gt;terraform apply&lt;/code&gt;时，Terraform 都会将目前配置文件声明的状态同现有的状态文件进行比对，从而计算变动的部分，自动确定调整顺序之后操作 Provider 落实状态变动。&lt;/p&gt;&#xA;&lt;h2 id=&#34;terraform-的资源导入难题&#34;&gt;Terraform 的资源导入难题&#xA;&lt;/h2&gt;&lt;p&gt;现有资源的导入一直是 Terraform 为人诟病的难题，Hashi Corp. 似乎一直在坚持着一个固执且愚蠢的观点：你所有的基础设施从一开始就应当是用我们的 Terraform 创建的，所以也不存在什么资源导入的问题。&lt;/p&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;时间&lt;/th&gt;&#xA;          &lt;th&gt;版本&lt;/th&gt;&#xA;          &lt;th&gt;进展&lt;/th&gt;&#xA;          &lt;th&gt;问题&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;2014–2022&lt;/td&gt;&#xA;          &lt;td&gt;v0.x–v1.4&lt;/td&gt;&#xA;          &lt;td&gt;只有 &lt;code&gt;terraform import&lt;/code&gt;，一次一条，而且完全不生成配置&lt;/td&gt;&#xA;          &lt;td&gt;导入完还得自己手写 HCL&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;2023.06&lt;/td&gt;&#xA;          &lt;td&gt;v1.5&lt;/td&gt;&#xA;          &lt;td&gt;引入 &lt;code&gt;import&lt;/code&gt; 块和 &lt;code&gt;-generate-config-out&lt;/code&gt;参数，可以生成配置了&lt;/td&gt;&#xA;          &lt;td&gt;然而还是得一条一条写，而且还得自己提供现有资源的 ID&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;2024.01&lt;/td&gt;&#xA;          &lt;td&gt;v1.7&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;import&lt;/code&gt; 块支持 &lt;code&gt;for_each&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;批量了，但 ID 还得自己搞&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;2024 下半年&lt;/td&gt;&#xA;          &lt;td&gt;v1.12&lt;/td&gt;&#xA;          &lt;td&gt;引入了 &lt;code&gt;terraform query&lt;/code&gt; 和 &lt;code&gt;list&lt;/code&gt; 块，终于实现了自动发现资源的功能&lt;/td&gt;&#xA;          &lt;td&gt;然而这个功能要 Provider 要自己实现，大量 Provider 根本没跟上&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;这么多年的时间，社区一直在骂，然而，「我有一堆现有资源，怎么导入 Terraform」这个问题一直没有被认真对待。Terraform 作为「基础设施即代码」工具，设计哲学就是声明式和幂等性&lt;sup id=&#34;fnref:5&#34;&gt;&lt;a href=&#34;#fn:5&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt;，Hashi Corp. 他们笃信「一切以代码声明的状态为准，就应该用 Terraform 从零开始创建资源」。但现实是绝大多数公司都有大量历史存量资源，先有资源、后有代码才是常态。这个矛盾 Hashi Corp. 承认得很晚，&lt;code&gt;v1.12&lt;/code&gt; 的 &lt;code&gt;terraform query&lt;/code&gt; 才算是官方第一次认真面对这个问题——但 Provider 生态的跟进嘛……真的是一言难尽。&lt;/p&gt;&#xA;&lt;h2 id=&#34;terraform-的文件结构&#34;&gt;Terraform 的文件结构&#xA;&lt;/h2&gt;&lt;p&gt;Terraform 的文件结构很简单，其主程序运行时会无脑读取工作目录下所有的&lt;code&gt;.tf&lt;/code&gt;文件，只要信息齐全，想给文件取什么名字都可以，比如我的文件结构就长这样：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;❯ tree -a -I .git&#xA;.&#xA;├── .editorconfig&#xA;├── .github&#xA;│   ├── dependabot.yml&#xA;│   └── workflows&#xA;│       ├── terraform-apply.yml&#xA;│       ├── terraform-plan.yml&#xA;│       └── your-fork.yml&#xA;├── .gitignore&#xA;├── .terraform.lock.hcl&#xA;├── cf_dns_zones.tf&#xA;├── cf_tunnel.tf&#xA;├── dns_example_com.tf&#xA;├── dns_example_net.tf&#xA;├── dns_example_top.tf&#xA;├── dns_example_cn.tf&#xA;├── main.tf                 # 基础配置（terraform 块）&#xA;├── moved.tf                # 移动过的资源&#xA;├── provider.tf             # 每个 Provider 的配置&#xA;├── README.md&#xA;├── rename_resources.ps1&#xA;└── variables.tf            # 自定义的变量&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;main.tf&lt;/code&gt;用于存放基础配置:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-hcl&#34;&gt;terraform {&#xA;  required_providers {&#xA;    cloudflare = {&#xA;      source  = &#34;cloudflare/cloudflare&#34;&#xA;      version = &#34;~&gt; 5&#34;&#xA;    }&#xA;&#xA;    tencentcloud = {&#xA;      source  = &#34;tencentcloudstack/tencentcloud&#34;&#xA;      version = &#34;&gt;= 1.81.43&#34;&#xA;    }&#xA;  }&#xA;}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;provider.tf&lt;/code&gt;用于存放每个 Provider 的配置：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-hcl&#34;&gt;provider &#34;cloudflare&#34; {}&#xA;provider &#34;tencentcloud&#34; {}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这里留空是因为我们可以通过环境变量来传递 Credentials，而不是直接将 Credentials 写在配置文件中，例如 Cloudflare 的 Provider 就接受&lt;code&gt;CLOUDFLARE_API_TOKEN&lt;/code&gt;作为&lt;code&gt;api_token&lt;/code&gt;这个变量的替代。&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;variables.tf&lt;/code&gt;用于声明自定义的变量：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-hcl&#34;&gt;variable &#34;cloudflare_zone_example_com&#34; {&#xA;  description = &#34;Cloudflare zone ID for example.com&#34;&#xA;  type        = string&#xA;}&#xA;&#xA;variable &#34;cloudflare_zone_example_top&#34; {&#xA;  description = &#34;Cloudflare zone ID for example.top&#34;&#xA;  type        = string&#xA;}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这些变量可以通过前缀为&lt;code&gt;TF_VAR_&lt;/code&gt;的环境变量传入，Terraform 也会自动读取&lt;code&gt;terraform.tfvars&lt;/code&gt;文件中的变量，变量的主要用途是在别的配置文件中调用，例如：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-hcl&#34;&gt;resource &#34;cloudflare_dns_record&#34; &#34;example_cname&#34; {&#xA;  content = &#34;${cloudflare_zero_trust_tunnel_cloudflared.Production_Tunnel.id}.cfargotunnel.com&#34;&#xA;  name    = &#34;example.example.com&#34;&#xA;  proxied = true&#xA;  tags    = []&#xA;  ttl     = 1&#xA;  type    = &#34;CNAME&#34;&#xA;  zone_id = var.cloudflare_zone_id_example_com  #这里调用了cloudflare_zone_example_com这个变量&#xA;  settings = {&#xA;    flatten_cname = false&#xA;  }&#xA;}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;有了这些基础配置，我们就可以运行&lt;code&gt;terraform init&lt;/code&gt;初始化 Terraform 环境并锁定依赖版本，剩下的就都是我们资源的声明了。&lt;/p&gt;&#xA;&lt;h2 id=&#34;将现有资源导入-terraform&#34;&gt;将现有资源导入 Terraform&#xA;&lt;/h2&gt;&lt;p&gt;前文提到过 Terraform 的状态管理这个概念，状态管理虽好，但在我们初始化时也带来了一个问题——在默认状态下，Terraform 的状态文件显然是空的。&lt;/p&gt;&#xA;&lt;p&gt;此时我们需要做的是将云服务提供商上现有资源的状态导入 Terraform，这样 Terraform 才能无缝接手并管理我们的基础设施。相信大家也都看到了前文对 Terraform 资源导入问题的吐槽。以导入 Cloudflare 上托管的 DNS 记录为例，前文提到过，如果 Provider 支持，可以使用 Terraform 在 1.12 版本之后引入的&lt;code&gt;terraform query&lt;/code&gt;，然而大多数情况下，Providers 都是没有跟进这个新功能的，Cloudflare 就属于不支持的那一类。&lt;/p&gt;&#xA;&lt;p&gt;好在虽然 Hashi Corp. 不认真解决问题，各路高强度使用 Terraform 管理基础设施的公司就各显神通。Cloudflare 就维护了名为 &lt;a class=&#34;link&#34; href=&#34;https://github.com/cloudflare/cf-terraforming&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;cf-terraforming&lt;/a&gt; 的导入工具，免去了很多手动导入的麻烦。首先安装&lt;code&gt;cf-terraforming&lt;/code&gt;，这个工具是用 Go 语言编写的，需要在有 Go 语言环境的情况下安装，或者将官方编译好的二进制文件其放入&lt;code&gt;$PATH&lt;/code&gt;并赋予其执行权限。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;go install github.com/cloudflare/cf-terraforming/cmd/cf-terraforming@latest&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这个工具的用法还是比较简单的，首先你需要以下环境变量：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;# 如果你使用 API Token&#xA;export CLOUDFLARE_API_TOKEN=&#39;Hzsq3Vub-7Y-hSTlAaLH3Jq_YfTUOCcgf22_Fs-j&#39;&#xA;&#xA;# 如果你使用 API Key&#xA;export CLOUDFLARE_EMAIL=&#39;user@example.com&#39;&#xA;export CLOUDFLARE_API_KEY=&#39;1150bed3f45247b99f7db9696fffa17cbx9&#39;&#xA;&#xA;# 指定需要导入的域名的区域 ID，如果导入的是账户资源（例如 Cloudflare Tunnel）则不需要&#xA;export CLOUDFLARE_ZONE_ID=&#39;81b06ss3228f488fh84e5e993c2dc17&#39;&lt;/code&gt;&lt;/pre&gt;&lt;blockquote class=&#34;alert alert-tip&#34;&gt;&#xA;        &lt;div class=&#34;alert-header&#34;&gt;&#xA;            &lt;span class=&#34;alert-icon&#34;&gt;💡&lt;/span&gt;&#xA;            &lt;span class=&#34;alert-title&#34;&gt;提示&lt;/span&gt;&#xA;        &lt;/div&gt;&#xA;        &lt;div class=&#34;alert-body&#34;&gt;&#xA;            &lt;p&gt;此处的命令假设你正在使用 Bash，如果使用的是与 Bash 语法不兼容的 Shell，则需要做出调整，例如对于 Windows 上的 PowerShell，导入环境变量的语法如下：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-pwsh&#34;&gt;$env:CLOUDFLARE_API_TOKEN=&#39;Hzsq3Vub-7Y-hSTlAaLH3Jq_YfTUOCcgf22_Fs-j&#39;&lt;/code&gt;&lt;/pre&gt;&#xA;        &lt;/div&gt;&#xA;    &lt;/blockquote&gt;&#xA;&lt;p&gt;通常我们只需要设置&lt;code&gt;CLOUDFLARE_API_TOKEN&lt;/code&gt;和&lt;code&gt;CLOUDFLARE_ZONE_ID&lt;/code&gt;就可以了，在控制台创建 API Token 时，记得赋予这个 Token 必要的权限，本次我们只是导入 DNS 记录，所以只赋予编辑区域 DNS 的权限即可。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2026/04/iac-with-terraform/image-1_hu_15298d4bd366a377.webp&#34; alt=&#34;赋予操作资源所需要的权限&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;好了，准备工作全部完成，现在可以开始生成配置文件：&lt;/p&gt;&#xA;&lt;p&gt;首先导入账户中域名的配置，也就是&lt;code&gt;cloudflare_zone&lt;/code&gt;：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;cf-terraforming generate \&#xA;  --key $CLOUDFLARE_API_KEY \&#xA;  --resource-type &#34;cloudflare_zone&#34; &gt; zone.tf&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这一步会在当前目录下生成一个名为&lt;code&gt;zone.tf&lt;/code&gt;的文件，里面会有如下格式的内容：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-hcl&#34;&gt;resource &#34;cloudflare_zone&#34; &#34;REDACTED&#34; {&#xA;  name                = &#34;REDACTED&#34;&#xA;  paused              = false&#xA;  type                = &#34;full&#34;&#xA;  vanity_name_servers = []&#xA;  account = {&#xA;    id   = &#34;REDACTED&#34;&#xA;    name = &#34;REDACTED&#34;&#xA;  }&#xA;}&#xA;&#xA;resource &#34;cloudflare_zone&#34; &#34;REDACTED&#34; {&#xA;  name                = &#34;REDACTED&#34;&#xA;  paused              = false&#xA;  type                = &#34;full&#34;&#xA;  vanity_name_servers = []&#xA;  account = {&#xA;    id   = &#34;REDACTED&#34;&#xA;    name = &#34;REDACTED&#34;&#xA;  }&#xA;}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;现在域名资源已经导入了，但是里面的配置并没有。接下来，导入域名下的 DNS 记录：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;cf-terraforming generate \&#xA;  --zone $CLOUDFLARE_ZONE_ID \&#xA;  --key $CLOUDFLARE_API_KEY \&#xA;  --resource-type &#34;cloudflare_dns_record&#34; &gt;&gt; dns.tf&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这一步会在当前目录下生成一个名为&lt;code&gt;dns.tf&lt;/code&gt;的配置文件，里面会有如下格式的内容：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-hcl&#34;&gt;resource &#34;cloudflare_dns_record&#34; &#34;terraform_managed_resource_5deb14xxxxxb629bf123xxxxxxxc8f_0&#34; {&#xA;  content  = &#34;67.24.33.108&#34;&#xA;  name     = &#34;example.example.com&#34;&#xA;  proxied  = true&#xA;  tags     = []&#xA;  ttl      = 1&#xA;  type     = &#34;A&#34;&#xA;  zone_id  = &#34;81c7f2de8dfxxxxxx52629xxxxxxfc&#34;&#xA;  settings = {}&#xA;}&#xA;&#xA;resource &#34;cloudflare_dns_record&#34; &#34;terraform_managed_resource_89xxxxx0bf9cxxxxxx9a_1&#34; {&#xA;  content  = &#34;35.27.108.33&#34;&#xA;  name     = &#34;terraform.example.com&#34;&#xA;  proxied  = true&#xA;  tags     = []&#xA;  ttl      = 1&#xA;  type     = &#34;A&#34;&#xA;  zone_id  = &#34;8xxxxxx7644e428526xxxxxx&#34;&#xA;  settings = {}&#xA;}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;如果有多个域名需要导入，则多次分别设置环境变量&lt;code&gt;CLOUDFLARE_ZONE_ID&lt;/code&gt;再重复运行命令即可。&lt;/p&gt;&#xA;&lt;p&gt;这里生成的配置文件可以直接使用，也就是我们之后需要的 Terraform 配置文件。然而，此时我们仅仅只是生成了配置文件，但是目前 Terraform 的状态依然是空的，这时要是直接&lt;code&gt;terraform apply&lt;/code&gt;，Terraform 会不管三七二十一将我们刚刚导入的声明一律视为新增资源，然后甩出一大堆「Alredy Exists」报错。所以接下来，我们需要将生成的配置文件导入 Terraform 的&lt;code&gt;terraform.tfstate&lt;/code&gt;状态。&lt;/p&gt;&#xA;&lt;p&gt;Terraform 在 1.5 版本引入了&lt;code&gt;import&lt;/code&gt;块，相比以往一行一行输入命令的方式更加现代。其导入流程是先生成一个包含&lt;code&gt;import&lt;/code&gt;块的&lt;code&gt;.tf&lt;/code&gt;文件，下次进行&lt;code&gt;terraform apply&lt;/code&gt;时，Terraform 就会自动为我们执行导入操作。&lt;/p&gt;&#xA;&lt;p&gt;生成&lt;code&gt;cloudflare_zone&lt;/code&gt;的&lt;code&gt;import&lt;/code&gt;块：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;cf-terraforming import \&#xA;  --resource-type &#34;cloudflare_zone&#34; \&#xA;  --modern-import-block \&#xA;  --key $CLOUDFLARE_API_KEY \&#xA;  --zone $CLOUDFLARE_ZONE_ID &gt;&gt; import.tf&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;生成&lt;code&gt;cloudflare_dns_record&lt;/code&gt;的&lt;code&gt;import&lt;/code&gt;块：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;cf-terraforming import \&#xA;  --resource-type &#34;cloudflare_dns_record&#34; \&#xA;  --modern-import-block \&#xA;  --key $CLOUDFLARE_API_KEY \&#xA;  --zone $CLOUDFLARE_ZONE_ID &gt;&gt; import.tf&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这一步会在当前目录下生成&lt;code&gt;import.tf&lt;/code&gt;，它包含了所需要的导入信息，作用就是告诉 Terraform 上一步生成的每个&lt;code&gt;resource&lt;/code&gt;块到底对应的是哪一个云服务提供商的资源 ID。这个 ID 是云服务提供商内部标记资源的代码，平常是不会在控制面板上显示的，只有在用 API 特别请求时才会知道。Terraform 在导入过程中需要用到这个 ID 以确认本地的定义对应的云端资源，以实现严格的幂等性。&lt;/p&gt;&#xA;&lt;p&gt;好了，现在我们运行&lt;code&gt;terraform plan&lt;/code&gt;：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;$ terraform plan&#xA;cloudflare_dns_record.minio_a: Refreshing state... [id=xxxxxxxxxxx53]&#xA;cloudflare_zero_trust_tunnel_cloudflared_config.raspberrypi: Refreshing state...&#xA;......&#xA;&#xA;Terraform will perform the following actions:&#xA;&#xA;  # cloudflare_dns_record.terraform_managed_resource_0 will be imported&#xA;    resource &#34;cloudflare_dns_record&#34; &#34;terraform_managed_resource_REDACTED_0&#34; {&#xA;        content     = &#34;67.24.33.108&#34;&#xA;        created_on  = &#34;2026-04-08T10:18:12Z&#34;&#xA;        id          = &#34;5deb14c21xxxxxxx20f1c8f&#34;&#xA;        meta        = jsonencode({})&#xA;        modified_on = &#34;2026-04-08T10:18:12Z&#34;&#xA;        name        = &#34;example.example.com&#34;&#xA;        proxiable   = true&#xA;        proxied     = true&#xA;        settings    = {}&#xA;        tags        = []&#xA;        ttl         = 1&#xA;        type        = &#34;A&#34;&#xA;        zone_id     = &#34;REDACTED&#34;&#xA;    }&#xA;&#xA;  # cloudflare_dns_record.terraform_managed_resource_1 will be imported&#xA;    resource &#34;cloudflare_dns_record&#34; &#34;terraform_managed_resource_89c149exxxxxxxxxxxba13xxxxxa_1&#34; {&#xA;        content     = &#34;35.27.108.33&#34;&#xA;        created_on  = &#34;2026-04-08T10:17:54Z&#34;&#xA;        id          = &#34;89cxxxxxxxxxxxxxxxxxx09a&#34;&#xA;        meta        = jsonencode({})&#xA;        modified_on = &#34;2026-04-08T10:17:54Z&#34;&#xA;        name        = &#34;terraform.example.com&#34;&#xA;        proxiable   = true&#xA;        proxied     = true&#xA;        settings    = {}&#xA;        tags        = []&#xA;        ttl         = 1&#xA;        type        = &#34;A&#34;&#xA;        zone_id     = &#34;81xxxxxxxxxxxxxxxxxxxxxfc&#34;&#xA;    }&#xA;&#xA;Plan: 2 to import, 0 to add, 0 to change, 0 to destroy.&#xA;&#xA;────────────────────────────────────────────────────────────────────────────────────────────────────────&#xA;&#xA;Note: You didn&#39;t use the -out option to save this plan, so Terraform can&#39;t guarantee to take exactly&#xA;these actions if you run &#34;terraform apply&#34; now.&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;如果需要 Add、Change 和 Destroy 的资源数量都是 0，说明我们的导入操作没有问题，直接&lt;code&gt;terraform apply --auto-approve&lt;/code&gt;，资源就导入完成了。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;$ terraform apply --auto-approve&#xA;cloudflare_dns_record.push_a: Refreshing state... [id=REDACTED]&#xA;&#xA;Terraform will perform the following actions:&#xA;&#xA;  # cloudflare_dns_record.terraform_managed_resource_REDACTED_0 will be imported&#xA;    resource &#34;cloudflare_dns_record&#34; &#34;terraform_managed_resource_REDACTED_0&#34; {&#xA;        content     = &#34;67.24.33.108&#34;&#xA;        created_on  = &#34;2026-04-08T10:18:12Z&#34;&#xA;        id          = &#34;REDACTED&#34;&#xA;        meta        = jsonencode({})&#xA;        modified_on = &#34;2026-04-08T10:18:12Z&#34;&#xA;        name        = &#34;example.example.com&#34;&#xA;        proxiable   = true&#xA;        proxied     = true&#xA;        settings    = {}&#xA;        tags        = []&#xA;        ttl         = 1&#xA;        type        = &#34;A&#34;&#xA;        zone_id     = &#34;REDACTED&#34;&#xA;    }&#xA;&#xA;  # cloudflare_dns_record.terraform_managed_resource_REDACTED_1 will be imported&#xA;    resource &#34;cloudflare_dns_record&#34; &#34;terraform_managed_resource_REDACTED_1&#34; {&#xA;        content     = &#34;35.27.108.33&#34;&#xA;        created_on  = &#34;2026-04-08T10:17:54Z&#34;&#xA;        id          = &#34;REDACTED&#34;&#xA;        meta        = jsonencode({})&#xA;        modified_on = &#34;2026-04-08T10:17:54Z&#34;&#xA;        name        = &#34;terraform.example.com&#34;&#xA;        proxiable   = true&#xA;        proxied     = true&#xA;        settings    = {}&#xA;        tags        = []&#xA;        ttl         = 1&#xA;        type        = &#34;A&#34;&#xA;        zone_id     = &#34;REDACTED&#34;&#xA;    }&#xA;&#xA;Plan: 2 to import, 0 to add, 0 to change, 0 to destroy.&#xA;cloudflare_dns_record.terraform_managed_resource_REDACTED_1: Importing... [id=REDACTED/REDACTED]&#xA;cloudflare_dns_record.terraform_managed_resource_REDACTED_1: Import complete [id=REDACTED/REDACTED]&#xA;cloudflare_dns_record.terraform_managed_resource_REDACTED_0: Importing... [id=REDACTED/REDACTED]&#xA;cloudflare_dns_record.terraform_managed_resource_REDACTED_0: Import complete [id=REDACTED/REDACTED]&#xA;&#xA;Apply complete! Resources: 2 imported, 0 added, 0 changed, 0 destroyed.&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;状态存储和持续集成&#34;&gt;状态存储和持续集成&#xA;&lt;/h2&gt;&lt;p&gt;IaC 的核心价值之一就在于可以轻易实现基于 Git 的多人协作，以及 CI 的持续集成，但是在此之前，又有一个新的问题需要解决——&lt;code&gt;terraform.tfstate&lt;/code&gt;到底放哪里：没人想要换一次环境辛辛苦苦导入的状态就丢一次。&lt;/p&gt;&#xA;&lt;p&gt;Terraform 目前支持以下几种保存状态的后端：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;local&lt;/li&gt;&#xA;&lt;li&gt;remote&lt;/li&gt;&#xA;&lt;li&gt;azurerm&lt;/li&gt;&#xA;&lt;li&gt;consul&lt;/li&gt;&#xA;&lt;li&gt;cos&lt;/li&gt;&#xA;&lt;li&gt;gcs&lt;/li&gt;&#xA;&lt;li&gt;http&lt;/li&gt;&#xA;&lt;li&gt;Kubernetes&lt;/li&gt;&#xA;&lt;li&gt;oci&lt;/li&gt;&#xA;&lt;li&gt;oss&lt;/li&gt;&#xA;&lt;li&gt;pg&lt;/li&gt;&#xA;&lt;li&gt;s3&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;没有特殊需求可以像我一样选择&lt;code&gt;s3&lt;/code&gt;，毕竟 Cloudflare R2 有免费额度，不用白不用。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-hcl&#34;&gt;terraform {&#xA;  backend &#34;s3&#34; {&#xA;    bucket = &#34;terraform&#34;&#xA;    key    = &#34;terraform.tfstate&#34;&#xA;    region = &#34;auto&#34;&#xA;    endpoints = {&#xA;      s3 = &#34;https://REDACTED.r2.cloudflarestorage.com&#34;&#xA;    }&#xA;&#xA;    # R2 不需要这些 AWS 的验证&#xA;    skip_credentials_validation = true&#xA;    skip_metadata_api_check     = true&#xA;    skip_region_validation      = true&#xA;    skip_requesting_account_id  = true&#xA;    use_path_style              = true&#xA;  }&#xA;}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;对于 S3 的后端，建议用&lt;code&gt;AWS_ACCESS_KEY_ID&lt;/code&gt;和&lt;code&gt;AWS_SECRET_ACCESS_KEY&lt;/code&gt;两个环境变量来存储 Credentials：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;export AWS_ACCESS_KEY_ID=&#39;REDACTED&#39;&#xA;export AWS_SECRET_ACCESS_KEY=&#39;REDACTED&#39;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;配置完成，运行&lt;code&gt;terraform init -migrate-state&lt;/code&gt;，配置就成功存储到云上了，以后不论在何处修改配置、运行&lt;code&gt;terraform apply&lt;/code&gt;都无须担心 Terraform 的 State 不同步的问题。&lt;/p&gt;&#xA;&lt;p&gt;接下来就是 Github CI 的配置，其实很简单，无非就是每次&lt;code&gt;git push&lt;/code&gt;时触发一次&lt;code&gt;terraform init&lt;/code&gt;、&lt;code&gt;terraform fmt&lt;/code&gt;和&lt;code&gt;terraform apply&lt;/code&gt;，以下是我的&lt;code&gt;.github/workflows/apply.yml&lt;/code&gt;：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;name: &#34;Terraform Apply&#34;&#xA;&#xA;on:&#xA;  push:&#xA;    branches:&#xA;      - main&#xA;&#xA;env:&#xA;  TF_IN_AUTOMATION: &#34;true&#34;&#xA;  CLOUDFLARE_API_TOKEN: &#34;${{ secrets.CLOUDFLARE_API_TOKEN }}&#34;&#xA;  AWS_ACCESS_KEY_ID: &#34;${{ secrets.AWS_ACCESS_KEY_ID }}&#34;&#xA;  AWS_SECRET_ACCESS_KEY: &#34;${{ secrets.AWS_SECRET_ACCESS_KEY }}&#34;&#xA;  TF_VAR_cloudflare_zone_id_example_com: &#34;${{ vars.TF_VAR_CLOUDFLARE_ZONE_ID_EXAMPLE_COM }}&#34;&#xA;  TF_VAR_cloudflare_zone_id_example_top: ${{ vars.TF_VAR_CLOUDFLARE_ZONE_ID_EXAMPLE_TOP }}&#xA;&#xA;jobs:&#xA;  terraform:&#xA;    name: &#34;Terraform Apply&#34;&#xA;    runs-on: ubuntu-latest&#xA;    permissions:&#xA;      contents: read&#xA;    concurrency:&#xA;      group: terraform-apply&#xA;      cancel-in-progress: false&#xA;    steps:&#xA;      - name: Checkout&#xA;        uses: actions/checkout@v6&#xA;&#xA;      - name: Setup Terraform&#xA;        uses: hashicorp/setup-terraform@v4&#xA;&#xA;      - name: Terraform Init&#xA;        run: terraform init -input=false&#xA;&#xA;      - name: Terraform Apply&#xA;        run: terraform apply -input=false -auto-approve&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;对于 PR 则应当让 CI 自动为每次 PR 附上&lt;code&gt;terraform plan&lt;/code&gt;的输出：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;name: Terraform Plan&#xA;&#xA;on:&#xA;  pull_request:&#xA;    paths:&#xA;      - &#34;**/*.tf&#34;&#xA;      - &#34;.github/workflows/terraform-plan.yml&#34;&#xA;&#xA;permissions:&#xA;  contents: read&#xA;  pull-requests: write&#xA;&#xA;env:&#xA;  TF_IN_AUTOMATION: &#34;true&#34;&#xA;  CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}&#xA;  AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}&#xA;  AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}&#xA;  TF_VAR_cloudflare_zone_id_example_com: &#34;${{ vars.TF_VAR_CLOUDFLARE_ZONE_ID_EXAMPLE_COM }}&#34;&#xA;  TF_VAR_cloudflare_zone_id_example_top: ${{ vars.TF_VAR_CLOUDFLARE_ZONE_ID_EXAMPLE_TOP }}&#xA;&#xA;jobs:&#xA;  plan:&#xA;    name: Terraform Plan&#xA;    runs-on: ubuntu-latest&#xA;    steps:&#xA;      - uses: actions/checkout@v6&#xA;&#xA;      - uses: hashicorp/setup-terraform@v4&#xA;&#xA;      - name: Terraform fmt&#xA;        id: fmt&#xA;        run: terraform fmt -check -recursive&#xA;        continue-on-error: true&#xA;&#xA;      - name: Terraform Init&#xA;        id: init&#xA;        run: terraform init -input=false&#xA;&#xA;      - name: Terraform Validate&#xA;        id: validate&#xA;        run: terraform validate -no-color&#xA;&#xA;      - name: Terraform Plan&#xA;        id: plan&#xA;        run: terraform plan -input=false -no-color&#xA;        continue-on-error: true&#xA;&#xA;      - name: Post Plan to PR&#xA;        uses: actions/github-script@v8&#xA;        with:&#xA;          github-token: ${{ secrets.GITHUB_TOKEN }}&#xA;          script: |&#xA;            const { data: comments } = await github.rest.issues.listComments({&#xA;              owner: context.repo.owner,&#xA;              repo: context.repo.repo,&#xA;              issue_number: context.issue.number,&#xA;            });&#xA;            const botComment = comments.find(c =&gt;&#xA;              c.user.type === &#39;Bot&#39; &amp;&amp; c.body.includes(&#39;&lt;!-- terraform-plan --&gt;&#39;)&#xA;            );&#xA;&#xA;            const planOutput = `${{ steps.plan.outputs.stdout }}`.substring(0, 65000);&#xA;&#xA;            const body = `&lt;!-- terraform-plan --&gt;&#xA;            #### Terraform Plan&#xA;&#xA;            | Step     | Result                            |&#xA;            | -------- | --------------------------------- |&#xA;            | fmt      | \`${{ steps.fmt.outcome }}\`      |&#xA;            | init     | \`${{ steps.init.outcome }}\`     |&#xA;            | validate | \`${{ steps.validate.outcome }}\` |&#xA;            | plan     | \`${{ steps.plan.outcome }}\`     |&#xA;&#xA;            &lt;details&gt;&lt;summary&gt;展开 Plan 详情&lt;/summary&gt;&#xA;&#xA;            \`\`\`terraform&#xA;            ${planOutput}&#xA;            \`\`\`&#xA;            &lt;/details&gt;`;&#xA;&#xA;            if (botComment) {&#xA;              await github.rest.issues.updateComment({&#xA;                owner: context.repo.owner,&#xA;                repo: context.repo.repo,&#xA;                comment_id: botComment.id,&#xA;                body&#xA;              });&#xA;            } else {&#xA;              await github.rest.issues.createComment({&#xA;                issue_number: context.issue.number,&#xA;                owner: context.repo.owner,&#xA;                repo: context.repo.repo,&#xA;                body&#xA;              });&#xA;            }&#xA;&#xA;      - name: Fail if plan failed&#xA;        if: steps.plan.outcome == &#39;failure&#39;&#xA;        run: exit 1&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2026/04/iac-with-terraform/image-2_hu_c922b7d4de19a009.webp&#34; alt=&#34;每次 PR 都会有 Plan 的输出&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;h2 id=&#34;后续的工作流程&#34;&gt;后续的工作流程&#xA;&lt;/h2&gt;&lt;p&gt;到这一步，Terraform 的「接管初始化」已经结束了，后面就进入了日常维护阶段。这个阶段其实就三件事：&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;创建新资源&lt;/li&gt;&#xA;&lt;li&gt;修改现有的资源&lt;/li&gt;&#xA;&lt;li&gt;删除不再需要的资源&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;常用的操作就两个：&lt;code&gt;terraform plan&lt;/code&gt;和&lt;code&gt;terraform apply&lt;/code&gt;，如果是个人使用，小的改动直接提交就算了；如果是团队协作，每次修改则应当遵循能 PR 就不直接 Commit 的原则。&lt;/p&gt;&#xA;&lt;h3 id=&#34;创建基础设施&#34;&gt;创建基础设施&#xA;&lt;/h3&gt;&lt;p&gt;假设你现在要新增一条 DNS 记录，或者新建一个 Tunnel、一个对象存储桶，流程如下：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-mermaid&#34;&gt;flowchart TD&#xA;  C1[新建分支&#xA;如 feat/add-minio-record] --&gt; C2[新增 resource 块]&#xA;  C2 --&gt; C3[terraform fmt + validate]&#xA;  C3 --&gt; C4[terraform plan]&#xA;  C4 --&gt; C5{仅新增预期资源?}&#xA;  C5 -- 否 --&gt; C6[修正配置后重跑 plan]&#xA;  C6 --&gt; C4&#xA;  C5 -- 是 --&gt; C7[提交 PR]&#xA;  C7 --&gt; C8[合并后 CI apply]&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;最理想的&lt;code&gt;plan&lt;/code&gt;输出是：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;X to add&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;0 to change&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;0 to destroy&lt;/code&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;如果你只是想加东西，结果出现了&lt;code&gt;to destroy&lt;/code&gt;，那就先别冲动，通常是引用写错、变量搞错，或者不小心改了资源地址，仔细检查是什么地方出了问题。&lt;/p&gt;&#xA;&lt;h3 id=&#34;修改与删除基础设施&#34;&gt;修改与删除基础设施&#xA;&lt;/h3&gt;&lt;p&gt;修改流程和创建类似，但要多一步——评估变更是否会触发重建。&lt;/p&gt;&#xA;&lt;p&gt;因为很多 Provider 字段是&lt;code&gt;ForceNew&lt;/code&gt;，你以为只是改个字段，Terraform 看完说：「好的，删了重建。」这在我们修改 DNS 的这个场景并不是什么很大的问题，但是到了云实例这种资源，如果删除重建势必会造成损失。&lt;/p&gt;&#xA;&lt;p&gt;建议按下面这个顺序来：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-mermaid&#34;&gt;flowchart TD&#xA;  M1[修改 .tf] --&gt; M2[terraform plan]&#xA;  M2 --&gt; M3{出现 replace/destroy?}&#xA;  M3 -- 否 --&gt; M7[确认影响范围]&#xA;  M7 --&gt; M8[terraform apply]&#xA;  M3 -- 是 --&gt; M4[暂停并复核变更]&#xA;  M4 --&gt; M5[必要时加 lifecycle 保护]&#xA;  M5 --&gt; M6[安排变更窗口]&#xA;  M6 --&gt; M8&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;对生产环境来说，创建、修改得慢一点并不是什么很大的问题，最应当看重的是操作的正确性，慢一点，不要出错。IaC 不是比手速，IaC 比的是可预期性。&lt;/p&gt;&#xA;&lt;p&gt;如果是删除资源（比如下线某个 DNS 记录、清理废弃 Tunnel），走下面这个流程&lt;sup id=&#34;fnref:6&#34;&gt;&lt;a href=&#34;#fn:6&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;6&lt;/a&gt;&lt;/sup&gt;：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-mermaid&#34;&gt;flowchart TD&#xA;  D1[确认资源已废弃&#xA;  检查业务/监控/脚本依赖] --&gt; D2[删除 resource 块或调整 count/for_each]&#xA;  D2 --&gt; D3[terraform plan]&#xA;  D3 --&gt; D4{to destroy 是否符合预期?}&#xA;  D4 -- 否 --&gt; D5[回滚修改并继续排查依赖]&#xA;  D5 --&gt; D1&#xA;  D4 -- 是 --&gt; D6[准备回滚方案并选低峰窗口]&#xA;  D6 --&gt; D7[PR 审核通过]&#xA;  D7 --&gt; D8[terraform apply]&#xA;  D8 --&gt; D9[删除资源后的可用性检查]&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;日常协作建议&#34;&gt;日常协作建议&#xA;&lt;/h3&gt;&lt;ul&gt;&#xA;&lt;li&gt;把 Credentials 放环境变量或 CI Secret，别写进&lt;code&gt;.tf&lt;/code&gt;和仓库&lt;/li&gt;&#xA;&lt;li&gt;对关键资源开启保护策略，防止误删&lt;/li&gt;&#xA;&lt;li&gt;将目录按资源类型拆分&lt;/li&gt;&#xA;&lt;li&gt;定期执行&lt;code&gt;terraform plan&lt;/code&gt;做基础设施漂移检查&lt;sup id=&#34;fnref:7&#34;&gt;&lt;a href=&#34;#fn:7&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;7&lt;/a&gt;&lt;/sup&gt;和校正，避免在面板误操作手动修改&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;虽然这套流程看起来很麻烦，但每一次变更都有记录、可审计、可回滚，最重要的是可复现，这才是 IaC 最有价值的地方。&lt;/p&gt;&#xA;&lt;h2 id=&#34;参考&#34;&gt;参考&#xA;&lt;/h2&gt;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://candinya.com/posts/manage-cloudflare-dns-with-terraform/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;使用 Terraform 管理 CloudFlare 上的 DNS 解析记录 - Candinya&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://developer.hashicorp.com/terraform/tutorials/automation/github-actions&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;Automate Terraform with GitHub Actions&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://developer.hashicorp.com/terraform/language/files/tfquery&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;Query configuration files&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://developer.hashicorp.com/terraform/language/block/tfquery/list&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;list block reference&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://github.com/cloudflare/cf-terraforming&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;cloudflare/cf-terraforming&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://developers.cloudflare.com/terraform/advanced-topics/import-cloudflare-resources/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;Import Cloudflare resources&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;Cloudflare Provider - Terraform Registry&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;&lt;a class=&#34;link&#34; href=&#34;https://en.wikipedia.org/wiki/Infrastructure_as_code&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;Infrastructure as Code&lt;/a&gt;，是指采用机器可读的配置文件定义所需要的基础设施的部署方法。&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:2&#34;&gt;&#xA;&lt;p&gt;每个供应商的配置文件字段命名和格式都有区别，但这可以很容易的编写脚本进行转换。&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:3&#34;&gt;&#xA;&lt;p&gt;需要特别注意的是，状态文件中可能包含数据库密码、API 密钥等明文存储的敏感信息，因此绝对不要将 &lt;code&gt;.tfstate&lt;/code&gt; 文件提交到公开的代码仓库中。&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:4&#34;&gt;&#xA;&lt;p&gt;HashiCorp Configuration Language，HashiCorp 自家开发的一种声明式配置语言，旨在兼顾机器可读性与人类可读性。&amp;#160;&lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:5&#34;&gt;&#xA;&lt;p&gt;幂等性（Idempotence）指计算机系统或接口在接收到同一请求的多次操作时，产生的影响与一次执行的结果相同，不论执行多少次，系统的最终状态始终保持一致。&amp;#160;&lt;a href=&#34;#fnref:5&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:6&#34;&gt;&#xA;&lt;p&gt;如果你仅仅是想让 Terraform 不再管理某个资源，而不是真正在云端销毁它，应该使用 &lt;code&gt;terraform state rm&lt;/code&gt; 命令，而不是在代码中删掉资源块然后 &lt;code&gt;apply&lt;/code&gt;，否则云环境上的真实资源也会被一并销毁。&amp;#160;&lt;a href=&#34;#fnref:6&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:7&#34;&gt;&#xA;&lt;p&gt;基础设施漂移（Infrastructure Drift）是指现实中通过控制台点按等非 IaC 途径修改了基础设施，导致其实际状态与代码中声明的状态不一致的情况。&amp;#160;&lt;a href=&#34;#fnref:7&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/div&gt;&#xA;</description>
        </item><item>
            <title>抛弃所谓「客户端」，直接使用 Mihomo 内核</title>
            <link>https://blog.l3zc.com/2025/07/switch-to-pure-mihomo-kernel/</link>
            <pubDate>Thu, 03 Jul 2025 18:30:00 +0800</pubDate>
            <guid>https://blog.l3zc.com/2025/07/switch-to-pure-mihomo-kernel/</guid>
            <description>&lt;img src=&#34;https://blog.l3zc.com/2025/07/switch-to-pure-mihomo-kernel/cover_hu_1a93b6f26c22bbfe.webp&#34; alt=&#34;Featured image of post 抛弃所谓「客户端」，直接使用 Mihomo 内核&#34; /&gt;&lt;p&gt;自从开始使用 Clash/Mihomo，我和大多数人一样选择了基于其内核的图形化客户端——图方便。实际上，这些 Mihomo 客户端本质上都差不多：内核都是同一个，他们主要负责提供一个易用的界面、管理配置文件、订阅更新，以及 GUI 下的系统代理设置。基于这一点，我认为判断一个 Mihomo 客户端优劣的关键，便是看它的「覆写」功能做得如何。每一个通过客户端下载或者订阅的配置文件，都会经过一系列「覆写」过程，比如更换 &lt;code&gt;mixed-port&lt;/code&gt;、添加 &lt;code&gt;sniffer&lt;/code&gt; 配置等，最后生成用于启动 Mihomo 内核的最终配置。&lt;/p&gt;&#xA;&lt;p&gt;然而，并非所有客户端都能胜任这一核心工作。比如 ShellCrash，其覆写功能经常莫名其妙出岔子，说到底还是实现得太粗糙。如果连覆盖和修改配置都做不好，这种客户端实在难称合格。&lt;/p&gt;&#xA;&lt;p&gt;与其依赖这些&lt;del&gt;屎一样的&lt;/del&gt;不尽如人意的 Mihomo 客户端，不如自己动手，直接自己编写、管理配置文件交给内核启动，而不是依赖「黑箱」一般的各种「客户端」，不仅更纯净、更稳定，而且更可控。&lt;/p&gt;&#xA;&lt;h2 id=&#34;需要的预备知识&#34;&gt;需要的预备知识&#xA;&lt;/h2&gt;&lt;ul&gt;&#xA;&lt;li&gt;基本的 Linux 操作&lt;/li&gt;&#xA;&lt;li&gt;知道如何使用 CLI 编辑器，如 nano&lt;/li&gt;&#xA;&lt;li&gt;已经搭建 Substore（可选）&lt;/li&gt;&#xA;&lt;li&gt;默认使用 root 用户操作，非 root 用户请自行注意提权&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;安装-mihomo-内核&#34;&gt;安装 Mihomo 内核&#xA;&lt;/h2&gt;&lt;p&gt;对于基于 Debian 的分支，可以使用预编译的&lt;code&gt;.deb&lt;/code&gt;包安装，对于其他使用&lt;code&gt;systemd&lt;/code&gt;的系统，下载&lt;a class=&#34;link&#34; href=&#34;https://github.com/MetaCubeX/mihomo/releases&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;编译好的二进制文件&lt;/a&gt;，重命名为&lt;code&gt;mihomo&lt;/code&gt;，放到&lt;code&gt;/usr/local/bin&lt;/code&gt;：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;curl -o /usr/local/bin/mihomo &lt;下载链接&gt;&#xA;chmod +x /usr/local/bin/mihomo&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;然后新建&lt;code&gt;/etc/systemd/system/mihomo.service&lt;/code&gt;：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-systemd&#34;&gt;[Unit]&#xA;Description=mihomo Daemon, Another Clash Kernel.&#xA;After=network.target NetworkManager.service systemd-networkd.service iwd.service&#xA;&#xA;[Service]&#xA;Type=simple&#xA;LimitNPROC=500&#xA;LimitNOFILE=1000000&#xA;CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_TIME CAP_SYS_PTRACE CAP_DAC_READ_SEARCH CAP_DAC_OVERRIDE&#xA;AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_TIME CAP_SYS_PTRACE CAP_DAC_READ_SEARCH CAP_DAC_OVERRIDE&#xA;Restart=always&#xA;ExecStartPre=/usr/bin/sleep 1s&#xA;ExecStart=/usr/local/bin/mihomo -d /etc/mihomo&#xA;ExecReload=/bin/kill -HUP $MAINPID&#xA;&#xA;[Install]&#xA;WantedBy=multi-user.target&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;使用&lt;code&gt;systemctl daemon-reload&lt;/code&gt;重新加载&lt;code&gt;systemd&lt;/code&gt;，此时还没有配置文件，不能直接启动内核，但可以使用&lt;code&gt;systemctl enable mihomo&lt;/code&gt;让内核开机自启，方便后续使用。&lt;/p&gt;&#xA;&lt;h2 id=&#34;配置文件&#34;&gt;配置文件&#xA;&lt;/h2&gt;&lt;p&gt;内核启动时会加载&lt;code&gt;/etc/mihomo/config.yaml&lt;/code&gt;，没有了黑箱一样的碍事「客户端」，配置文件可以随心所欲的定制。部分机场会下发完整的配置文件，直接用&lt;code&gt;curl&lt;/code&gt;下载即可。&lt;/p&gt;&#xA;&lt;p&gt;对于订阅的管理，我目前使用 Substore，我之前分享过&lt;a class=&#34;link&#34; href=&#34;https://blog.l3zc.com/2025/03/clash-subscription-convert/&#34; &gt;「最速 Substore 订阅管理指南」&lt;/a&gt;，可以直接参考这篇文章来定制自己的订阅。为了实现纯内核启动，现在我的 Substore &lt;a class=&#34;link&#34; href=&#34;https://github.com/powerfullz/override-rules/blob/main/convert.js&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;JS 格式覆写&lt;/a&gt;已经加入了&lt;code&gt;full&lt;/code&gt;参数，可以生成完整的配置文件，包括各种端口设置、统一延迟和外部控制器等，开箱即用。&lt;/p&gt;&#xA;&lt;p&gt;在 Substore 配置完成以后便可以下载配置文件并启动内核：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;sudo curl -o /etc/mihomo/config.yaml 配置文件链接&#xA;sudo systemctl start mihomo&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;自定义覆写&#34;&gt;自定义覆写&#xA;&lt;/h3&gt;&lt;p&gt;我的配置文件不能满足你的所有需求？没问题！你可以自己添加覆写，想要什么加什么。&lt;/p&gt;&#xA;&lt;p&gt;我用过各种覆写规则，后来也开始自己从零开始编写覆写规则，即使使用自己的覆写规则能满足 99% 的场景，但有极个别的域名还是会在规则中有遗漏，更不用说用别人写的覆写规则了。这些遗漏的规则大多是形如我服务器的非标准端口 SSH 的前置代理等相对隐私的规则，直接上传到 Github 公开显然不太合适，那么在自定义规则的基础上再添加覆写就成了唯一的选择。&lt;/p&gt;&#xA;&lt;p&gt;好在 Substore 可以添加多个脚本操作，只需要在生成配置文件时额外添加一个脚本操作就能解决我们的问题。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-js&#34;&gt;function main(config) {&#xA;  config[&#34;rules&#34;].unshift(&#34;DOMAIN-SUFFIX,xxx,DIRECT&#34;)&#xA;  return config&#xA;}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;需要注意的是覆写&lt;code&gt;rules&lt;/code&gt;时必须要使用&lt;code&gt;.unshift()&lt;/code&gt;放在最前面，而不是用&lt;code&gt;.push()&lt;/code&gt;放到最后面，因为放在&lt;code&gt;MATCH&lt;/code&gt;后面的规则是永远都匹配不到的。&lt;/p&gt;&#xA;&lt;h3 id=&#34;自定义配置文件&#34;&gt;自定义配置文件&#xA;&lt;/h3&gt;&lt;p&gt;完全不喜欢我的覆写规则？不会用/不喜欢用 Substore？没问题！你也可以参考 &lt;a class=&#34;link&#34; href=&#34;https://wiki.metacubex.one/config/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;Mihomo 文档&lt;/a&gt;，自己从头开始手搓配置文件：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;mode: rule&#xA;mixed-port: 7890&#xA;redir-port: 7892&#xA;tproxy-port: 7893&#xA;allow-lan: true&#xA;log-level: info&#xA;ipv6: true&#xA;external-controller: 127.0.0.1:8000&#xA;# secret: yoursecret&#xA;unified-delay: true&#xA;routing-mark: 7894&#xA;tcp-concurrent: true&#xA;disable-keep-alive: true # 推荐在给移动设备代理时启用，可以解决待机异常耗电的问题&#xA;&#xA;dns:&#xA;  # 你的 DNS 配置&#xA;&#xA;sniffer:&#xA;  # 你的域名嗅探配置&#xA;&#xA;geodata-mode: true&#xA;geox-url:&#xA;  # 自定义 Geodata 文件 URL&#xA;&#xA;proxy-providers:&#xA;  # 你的机场订阅&#xA;&#xA;rule-providers:&#xA;  # 外部规则&#xA;&#xA;rules:&#xA;  # 分流规则&#xA;&#xA;proxy-groups:&#xA;  # 自定义代理分组&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;管理面板&#34;&gt;管理面板&#xA;&lt;/h2&gt;&lt;p&gt;管理面板可以根据个人喜好选择，以 Zashboard 为例，我在使用 mihomo 自带的&lt;code&gt;external-ui&lt;/code&gt;时遇到了一些莫名其妙的问题，所以干脆直接运行一个 Docker 容器，毕竟这东西就真的只是一个 Web 面板，只要确保内核 API 使用 HTTP 时，Web 面板也使用 HTTP 即可，如果此时 Web 面板使用 HTTPS，则会因为 CORS 策略问题无法连接。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;$ mkdir zashboard &amp;&amp; cd zashboard&#xA;$ nano compose.yml&#xA;$ docker compose up -d&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;compose.yml&lt;/code&gt;内容：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;services:&#xA;  zashboard:&#xA;    image: ghcr.io/zephyruso/zashboard:latest&#xA;    ports:&#xA;      - &#34;8899:80&#34;&#xA;    restart: &#34;unless-stopped&#34;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;自动维护&#34;&gt;自动维护&#xA;&lt;/h2&gt;&lt;p&gt;内核已经运行起来，自动更新订阅这种功能怎么实现？&lt;/p&gt;&#xA;&lt;p&gt;答曰：自行编写一个 Shell 脚本，配合 Crontab 即可实现自动更新订阅的功能。例如我希望每天凌晨 3 点自动更新订阅并重启服务，遂编写&lt;code&gt;/etc/mihomo/auto_update.sh&lt;/code&gt;：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;#!/bin/bash&#xA;&#xA;# === 配置信息 ===&#xA;CONFIG_URL=&#34;&#34;&#xA;CONFIG_PATH=&#34;/etc/mihomo/config.yaml&#34;&#xA;BACKUP_DIR=&#34;/etc/mihomo&#34;&#xA;BACKUP_PREFIX=&#34;config.yaml&#34;&#xA;MAX_BACKUPS=7&#xA;TMP_PATH=&#34;/tmp/config.yaml.tmp&#34;&#xA;LOG_FILE=&#34;/var/log/mihomo_update.log&#34;&#xA;&#xA;# === 日志 ===&#xA;log() {&#xA;    echo &#34;$(date &#39;+%F %T&#39;) $1&#34; | tee -a &#34;$LOG_FILE&#34;&#xA;}&#xA;&#xA;# === 备份现有配置，并自动清理旧备份 ===&#xA;backup_config() {&#xA;    if [ -f &#34;$CONFIG_PATH&#34; ]; then&#xA;        backup_file=&#34;$BACKUP_DIR/${BACKUP_PREFIX}.$(date &#39;+%Y%m%d_%H%M%S&#39;).bak&#34;&#xA;        cp &#34;$CONFIG_PATH&#34; &#34;$backup_file&#34;&#xA;        log &#34;配置文件已备份到 $backup_file&#34;&#xA;        # 清理多余的备份，只保留最新的 $MAX_BACKUPS 个&#xA;        old_backups=$(ls -1t $BACKUP_DIR/${BACKUP_PREFIX}.*.bak 2&gt;/dev/null | tail -n +$(($MAX_BACKUPS+1)))&#xA;        for f in $old_backups; do&#xA;            rm -f &#34;$f&#34; &amp;&amp; log &#34;已删除旧备份 $f&#34;&#xA;        done&#xA;    else&#xA;        log &#34;未找到现有配置文件，无需备份&#34;&#xA;    fi&#xA;}&#xA;&#xA;# === 下载新配置 ===&#xA;download_config() {&#xA;    log &#34;开始下载新配置...&#34;&#xA;    curl -fsSL -o &#34;$TMP_PATH&#34; &#34;$CONFIG_URL&#34;&#xA;    if [ $? -ne 0 ]; then&#xA;        log &#34;下载配置失败，请检查网络或地址&#34;&#xA;        return 1&#xA;    fi&#xA;    # 基本校验：检测文件体积&#xA;    if [ ! -s &#34;$TMP_PATH&#34; ]; then&#xA;        log &#34;下载文件为空，停止更新&#34;&#xA;        return 2&#xA;    fi&#xA;    log &#34;配置下载完成&#34;&#xA;    return 0&#xA;}&#xA;&#xA;# === 更新配置文件 ===&#xA;replace_config() {&#xA;    mv &#34;$TMP_PATH&#34; &#34;$CONFIG_PATH&#34;&#xA;    log &#34;配置文件已更新&#34;&#xA;}&#xA;&#xA;# === 重启 mihomo 服务 ===&#xA;restart_service() {&#xA;    systemctl restart mihomo&#xA;    if [ $? -eq 0 ]; then&#xA;        log &#34;mihomo 服务已重启&#34;&#xA;    else&#xA;        log &#34;mihomo 服务重启失败，请手动检查&#34;&#xA;    fi&#xA;}&#xA;&#xA;main() {&#xA;    backup_config&#xA;&#xA;    download_config&#xA;    DL_STATUS=$?&#xA;    if [ &#34;$DL_STATUS&#34; -ne 0 ]; then&#xA;        log &#34;操作终止：配置文件未更新，保留原有配置&#34;&#xA;        exit 1&#xA;    fi&#xA;&#xA;    replace_config&#xA;&#xA;    restart_service&#xA;&#xA;    log &#34;=== 更新流程完成 ===&#34;&#xA;}&#xA;&#xA;main &#34;$@&#34;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;使用&lt;code&gt;crontab -e&lt;/code&gt;编辑 Crontab，设置如下 Crontab 即可在每天凌晨三点自动更新配置：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-crontab&#34;&gt;0 3 * * * /etc/mihomo/update_config.sh&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;防火墙&#34;&gt;防火墙&#xA;&lt;/h2&gt;&lt;p&gt;手动配置防火墙把流量劫持到 Mihomo 内核其实并不是什么难事，我之前在「&lt;a class=&#34;link&#34; href=&#34;https://blog.l3zc.com/2025/04/tailscale-setup-recap/#%E5%9C%A8-exit-node-%E5%8A%AB%E6%8C%81%E6%B5%81%E9%87%8F&#34; &gt;从入门到进阶：Tailscale + ShellCrash 异地组网和科学上网&lt;/a&gt;」（以下简称「Tailscale 那篇文章」中的两个小节中提到过具体的操作方法，这里只提操作，不做解说。&lt;/p&gt;&#xA;&lt;p&gt;根据我的情况，我需要把&lt;code&gt;tailscale0&lt;/code&gt;上的网卡的所有流量劫持到 Mihomo 内核，其它的情况（例如本机代理）操作也大同小异。如果只是想劫持 TCP 流量，那么用&lt;code&gt;iptables&lt;/code&gt;的 REDIRECT 功能已经足够，但若还想劫持 UDP、QUIC 等流量，则必须用到 Tproxy。&lt;strong&gt;最后，不要忘了 IPV6。&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;以下是我的防火墙配置：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;# 创建自定义链&#xA;iptables -t mangle -N MIHOMO&#xA;&#xA;# 根据自己的需要忽略本地流量&#xA;iptables -t mangle -A MIHOMO -d 127.0.0.1/8 -j RETURN&#xA;iptables -t mangle -A MIHOMO -d 100.64.0.0/10 -j RETURN&#xA;iptables -t mangle -A MIHOMO -d 192.168.1.0/24 -j RETURN&#xA;iptables -t mangle -A MIHOMO -d 172.17.0.0/16 -j RETURN&#xA;&#xA;# mark UDP 和 TCP 到代理&#xA;iptables -t mangle -A MIHOMO -p tcp -j TPROXY --on-port 7893 --tproxy-mark 233&#xA;iptables -t mangle -A MIHOMO -p udp -j TPROXY --on-port 7893 --tproxy-mark 233&#xA;&#xA;# 接口跳转&#xA;iptables -t mangle -A PREROUTING -i tailscale0 -j MIHOMO&#xA;&#xA;# 路由表配置&#xA;echo &#34;233 mihomo&#34; | tee -a /etc/iproute2/rt_tables&#xA;ip rule add fwmark 233 lookup mihomo&#xA;ip route add local 0.0.0.0/0 dev lo table mihomo&#xA;&#xA;# IPv6&#xA;# 创建链&#xA;ip6tables -t mangle -N MIHOMO6&#xA;&#xA;# 跳过本地地址&#xA;ip6tables -t mangle -A MIHOMO6 -d ::1/128 -j RETURN&#xA;ip6tables -t mangle -A MIHOMO6 -d fd7a:115c:a1e0::/48 -j RETURN&#xA;&#xA;# 标记 TCP/UDP&#xA;ip6tables -t mangle -A MIHOMO6 -i tailscale0 -p tcp -j TPROXY --on-port 7893 --tproxy-mark 233&#xA;ip6tables -t mangle -A MIHOMO6 -i tailscale0 -p udp -j TPROXY --on-port 7893 --tproxy-mark 233&#xA;&#xA;# 接口跳转&#xA;ip6tables -t mangle -A PREROUTING -i tailscale0 -j MIHOMO6&#xA;&#xA;# 路由表配置&#xA;echo &#34;233 mihomo&#34; | tee -a /etc/iproute2/rt_tables&#xA;ip -6 rule add fwmark 233 lookup mihomo&#xA;ip -6 route add local ::/0 dev lo table mihomo&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;大多数系统默认不会保存防火墙规则，关于规则持久化的内容我已经分别在 Tailscale 那篇文章的「&lt;a class=&#34;link&#34; href=&#34;https://blog.l3zc.com/2025/04/tailscale-setup-recap/#%E8%B7%AF%E7%94%B1%E8%A7%84%E5%88%99%E6%8C%81%E4%B9%85%E5%8C%96&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;路由规则持久化&lt;/a&gt;」和「&lt;a class=&#34;link&#34; href=&#34;https://blog.l3zc.com/2025/04/tailscale-setup-recap/#iptables-%E8%A7%84%E5%88%99%E6%8C%81%E4%B9%85%E5%8C%96&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;iptables 规则持久化&lt;/a&gt;」两小节做了详细说明，直接参考即可。&lt;/p&gt;&#xA;&lt;h3 id=&#34;本机代理怎么配置&#34;&gt;本机代理怎么配置？&#xA;&lt;/h3&gt;&lt;p&gt;大多数代理软件默认的配置（其实就是 ShellCrash 的默认配置）是 REDIRECT，用它也基本能满足大多数需求，REDIRECT 的示例如下：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;# IPv4 劫持 eth0&#xA;iptables -t nat -A PREROUTING -i eth0 -p tcp -j REDIRECT --to-ports 7892&#xA;&#xA;# IPv6 劫持 eth0&#xA;ip6tables -t nat -A PREROUTING -i eth0 -p tcp -j REDIRECT --to-ports 7892&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;其中 7892 要和 Mihomo 配置中的&lt;code&gt;redir-port&lt;/code&gt;一致，&lt;code&gt;eth0&lt;/code&gt;就是想要劫持的 Interface，相比 Tproxy 那可真是简单太多了。&lt;/p&gt;&#xA;&lt;p&gt;实际上我个人并不喜欢用把本机所有流量都劫持到防火墙，我更喜欢在需要时直接通过环境变量、&lt;code&gt;proxy-chains&lt;/code&gt;和各种软件自带的代理配置把流量指向 Mihomo 内核，例如想让 Docker 走代理，则可以直接编辑 Docker 的&lt;code&gt;/etc/docker/daemon.json&lt;/code&gt;来指定代理，而不是直接一股脑把网卡上的所有流量都劫持到代理：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;{&#xA;  &#34;proxies&#34;: {&#xA;    &#34;http-proxy&#34;: &#34;http://127.0.0.1:7890&#34;,&#xA;    &#34;https-proxy&#34;: &#34;http://127.0.0.1:7890&#34;,&#xA;    &#34;no-proxy&#34;: &#34;127.0.0.0/8&#34;&#xA;  }&#xA;}&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;这么折腾何必呢用客户端不香吗&#34;&gt;这么折腾，何必呢？用客户端不香吗？&#xA;&lt;/h2&gt;&lt;p&gt;别问，问就是玩虚空终端玩的。&lt;/p&gt;&#xA;</description>
        </item><item>
            <title>从入门到进阶：Tailscale &#43; ShellCrash 异地组网和科学上网</title>
            <link>https://blog.l3zc.com/2025/04/tailscale-setup-recap/</link>
            <pubDate>Mon, 14 Apr 2025 18:10:25 +0800</pubDate>
            <guid>https://blog.l3zc.com/2025/04/tailscale-setup-recap/</guid>
            <description>&lt;img src=&#34;https://blog.l3zc.com/2025/04/tailscale-setup-recap/tailscale_hu_62168b00e213c4a1.webp&#34; alt=&#34;Featured image of post 从入门到进阶：Tailscale + ShellCrash 异地组网和科学上网&#34; /&gt;&lt;p&gt;在几次旅途中，我试图将新拍的照片上传到长沙家中的 Immich 服务器。过去我用 Cloudflare Tunnel 做反代，但由于国内特殊的网络环境，连接慢速、频繁中断、上传失败几乎成了常态。后来我开始尝试 Tailscale —— 一个基于 WireGuard 的内网穿透工具，它终于让我在全国各地都能稳定、高速地访问家中的 NAS 和照片库。&lt;/p&gt;&#xA;&lt;p&gt;但新的问题也随之而来：Tailscale 在 Android 手机上运行时，需要作为 VPN 服务接管系统流量，而我平时使用的 Clash 同样依赖 VPN 接口进行分流。由于 Android 系统限制（只能启用一个 VPN 服务），两者无法共存，导致我必须在「访问家中服务」和「科学上网」之间二选一。&lt;/p&gt;&#xA;&lt;p&gt;这篇文章从 Tailscale 的原理讲起，到自建 DERP 服务器优化连接质量，再到如何用 iptables 劫持 Tailscale Exit Node 的流量并转发给 ShellCrash，实现流量的灵活转发与安全穿透。无论你是想访问家庭局域网上的 NAS、照片库，还是希望在陌生网络中保护自己的数据安全，这篇文章都能为你提供一套实用、稳定的解决方案。&lt;/p&gt;&#xA;&lt;h2 id=&#34;什么是-tailscale&#34;&gt;什么是 Tailscale&#xA;&lt;/h2&gt;&lt;p&gt;Tailscale 是一款基于 WireGuard 协议的零配置虚拟局域网工具，它能够让分布在不同网络环境中的设备像处于同一个安全内网中一样互联互通。通过自动穿透 NAT、防火墙等网络障碍，Tailscale 让你无需公网 IP、无需端口转发，也能轻松访问家中的 NAS、个人服务器、开发环境等内网资源。它的核心优势在于简单、安全、稳定，启动即用，数据传输全程加密，适合个人开发者、远程办公者、家庭用户等多种场景使用。&lt;/p&gt;&#xA;&lt;p&gt;Tailscale 的技术实现非常巧妙：其构建在 WireGuard 加密协议之上，却颠覆了传统 VPN 的 IP 分配逻辑。每个设备通过 SSO/OAuth2 完成身份认证后，会获得一个终身绑定的节点密钥。这种基于身份的组网模式，让「长沙的 NAS」和「香港的手机」在虚拟网络中如同办公室同事般直接对话。&lt;/p&gt;&#xA;&lt;h2 id=&#34;tailscale-的连接过程原理&#34;&gt;Tailscale 的连接过程原理&#xA;&lt;/h2&gt;&lt;h3 id=&#34;中心控制服务器control-server&#34;&gt;中心控制服务器（Control Server）&#xA;&lt;/h3&gt;&lt;p&gt;每个 Tailscale 客户端在启动后，首先会连接控制服务器（controlplane），进行身份验证，并拉取整个网络中其他节点的信息，包括每台设备的公网 IP、端口、NAT 类型等。这一步相当于是「认识朋友」。&lt;/p&gt;&#xA;&lt;p&gt;Tailscale 的控制服务器不会转发任何数据，只负责协调连接 —— 类似一个调度中心。&lt;/p&gt;&#xA;&lt;h3 id=&#34;derp-服务器&#34;&gt;DERP 服务器&#xA;&lt;/h3&gt;&lt;p&gt;说到 Tailscale 能保持高连接成功率的关键，就不得不提到 Tailscale 自研的中转协议 DERP，在 Tailscale 的网络架构里，DERP（Designated Encrypted Relay for Packets）是一个很重要但通常只在必要时介入的组件。简单来说，它就是一个基于 HTTP 的加密中继服务器，用来在两台设备无法直接通信时，作为它们之间的「中转站」。&lt;/p&gt;&#xA;&lt;p&gt;所有客户端之间的连接都是先选择 DERP 模式（中继模式），这意味着连接立即就能建立，用户无需等待。然后连接双方开始并行地进行路径发现，通常几秒钟之后，Tailscale 就能发现一条更优路径，然后将现有连接透明升级（upgrade）过去，变成点对点连接（直连）。&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;&#xA;&lt;p&gt;需要注意的是：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;所有通过 DERP 的数据都是端到端加密的，DERP 服务器无法查看内容；&lt;/li&gt;&#xA;&lt;li&gt;Tailscale 会尽可能少地使用 DERP，一旦直连建立成功，就会自动切换过去；&lt;/li&gt;&#xA;&lt;li&gt;官方部署了多个分布式 DERP 节点，客户端会自动选择延迟最低的那个；&lt;/li&gt;&#xA;&lt;li&gt;你也可以自建 DERP 节点（比如在国内），来解决延迟高或连接不稳定的问题。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;可以把 DERP 理解为一个兜底机制，虽然性能不如直连，但确保了即便不能打洞成功，设备之间也始终能保持连接。&lt;/p&gt;&#xA;&lt;h3 id=&#34;nat-穿透nat-traversal&#34;&gt;NAT 穿透（NAT Traversal）&#xA;&lt;/h3&gt;&lt;p&gt;拿到对端的地址信息后，Tailscale 会尝试通过 NAT 穿透来建立点对点（P2P）连接。这个过程使用了 STUN 协议，双方互相发送探测包，尝试在 NAT 路由器上打出一条直连的通道。如果双方的网络条件允许，就可以成功建立起一个 UDP 的直连隧道，数据走直连，速度快、延迟低。&lt;/p&gt;&#xA;&lt;p&gt;受制于篇幅，我无法完整细致的讲述 NAT 穿透的原理，若对这部分感兴趣，可以阅读 Tailscale 官方的「&lt;a class=&#34;link&#34; href=&#34;https://tailscale.com/blog/how-nat-traversal-works&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;How NAT traversal works&lt;/a&gt;」一文。&lt;/p&gt;&#xA;&lt;h3 id=&#34;完整连接流程图示&#34;&gt;完整连接流程图示&#xA;&lt;/h3&gt;&lt;pre&gt;&lt;code class=&#34;language-mermaid&#34;&gt;flowchart TD&#xA;    A[设备 A 启动 Tailscale] --&gt; B[通过 DERP 服务器建立初始连接]&#xA;    B --&gt; C[交换网络信息和 WireGuard 密钥]&#xA;    C --&gt; D[双方并行进行 NAT 类型探测]&#xA;    D --&gt; E{能否直连？}&#xA;    E -- 是 --&gt; F[建立 P2P 直连隧道]&#xA;    F --&gt; G[定期检测连接质量]&#xA;    G --&gt; H{直连优于 DERP？}&#xA;    H -- 是 --&gt; I[切换大部分流量至直连通道]&#xA;    H -- 否 --&gt; J[继续通过 DERP 转发部分或全部流量]&#xA;    E -- 否 --&gt; J&#xA;&#xA;    style B fill:#e3f2fd,stroke:#2196f3,color:#000&#xA;    style F fill:#e8f5e9,stroke:#4caf50,color:#000&#xA;    style J fill:#fff3e0,stroke:#ff9800,color:#000&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;自建-derp&#34;&gt;自建 DERP&#xA;&lt;/h2&gt;&lt;p&gt;Tailscale 的安装在各个平台上都相对简单，官方文档已经提供了详细的操作指南。本文将不再赘述安装过程，以下内容默认你已经在相关设备上成功安装并登录了 Tailscale。&lt;/p&gt;&#xA;&lt;h3 id=&#34;为什么要自建-derp&#34;&gt;为什么要自建 DERP？&#xA;&lt;/h3&gt;&lt;p&gt;Tailscale 在全球部署了众多 DERP &lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;中继服务器，用于在打洞失败时接管流量中转。但由于众所周知的原因，中国大陆并没有官方部署的 DERP 节点。这意味着：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;一旦 NAT 打洞失败，所有流量都必须绕行海外的 DERP 节点，延迟高、速度慢，体验极差；&lt;/li&gt;&#xA;&lt;li&gt;某些官方 DERP 节点容易被 GFW 干扰，可能出现连接中断、握手失败等问题；&lt;/li&gt;&#xA;&lt;li&gt;即使打洞成功，Tailscale 仍需通过 DERP 交换路由信息和 WireGuard 密钥，如果 DERP 不可达，连接质量也会受到影响。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;因此，在国内网络环境下，自建一个本地 DERP 节点，不仅可以显著提高连接稳定性和传输性能，还能规避部分网络封锁所带来的不可预期问题，是一个非常值得做的优化。&lt;/p&gt;&#xA;&lt;h3 id=&#34;准备工作&#34;&gt;准备工作&#xA;&lt;/h3&gt;&lt;p&gt;前面提到，DERP 是基于 HTTP 的，所以你需要准备好一个 HTTP 反代服务，并自行解决 SSL 证书的签发等基础问题。本文使用 Docker 部署，在部署开始之前，你需要在你的服务器上装好 Docker 以及 Docker Compose 等附加组件。为了编辑配置文件，你当然也得知道如何使用 nano 之类的编辑器。为了最好的效果，你的服务器最好拥有静态公网 IPv4+IPv6 双栈地址。&lt;/p&gt;&#xA;&lt;p&gt;如果以上条件都具备，就可以开始部署了。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;mkdir tailscale-derp &amp;&amp; cd tailscale-derp&#xA;nano docker-compose.yml&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;docker-composeyml&#34;&gt;docker-compose.yml&#xA;&lt;/h3&gt;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;services:&#xA;  derper:&#xA;    name: tailscale-derp&#xA;    image: fredliang/derper&#xA;    environment:&#xA;      - DERP_DOMAIN=derp.nightcity.pub&#xA;      - DERP_VERIFY_CLIENTS=true&#xA;      - DERP_ADDR=:4433&#xA;    network_mode: host&#xA;    restart: unless-stopped&#xA;    volumes:&#xA;      - &#34;/var/run/tailscale/tailscaled.sock:/var/run/tailscale/tailscaled.sock&#34;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;network_mode: host&lt;/code&gt;是一个关键配置，表示容器将共享宿主机的网络栈。如果使用 Docker 默认的 bridge 网络模式，容器的网络会经过 Docker 内网转发，会造成 DERP 的 STUN 服务识别到 Docker &lt;code&gt;172.17.0.0/16&lt;/code&gt;网段下的地址，而&#xA;无法识别到客户端正确的外网地址，导致 Tailscale 客户端无法正确连接。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;volumes:&#xA;  - &#34;/var/run/tailscale/tailscaled.sock:/var/run/tailscale/tailscaled.sock&#34;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;前面提到所有通过 DERP 的数据都是端到端加密的，DERP 并不知道是谁在使用，这意味着如果不采取措施，任何知道你 DERP 服务器地址和端口号的人都可以使用它。这条配置的作用是挂载宿主机的 Tailscale 套接字文件到容器内，目的是允许 derper 服务通过 Tailscale 的 tailscaled 服务进行身份验证。配合&lt;code&gt;DERP_VERIFY_CLIENTS=true&lt;/code&gt;，可以防止你的 DERP 节点被他人白嫖。&lt;/p&gt;&#xA;&lt;p&gt;需要注意的是：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;宿主机必须已经安装并登陆 Tailscale 客户端（tailscaled），否则这个文件不存在，容器会报错；&lt;/li&gt;&#xA;&lt;li&gt;tailscaled 必须以 root 权限运行，才能创建这个 sock 文件。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;反向代理&#34;&gt;反向代理&#xA;&lt;/h3&gt;&lt;p&gt;以 Caddy 为例，需要反向代理 4433 端口，并为其部署 SSL 证书。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-caddyfile&#34;&gt;{&#xA;        email webmaster@l3zc.com&#xA;}&#xA;&#xA;*.l3zc.com {&#xA;        encode gzip&#xA;&#xA;        tls {&#xA;                dns dnspod APP_ID,APP_KEY&#xA;                resolvers 119.29.29.29 223.5.5.5&#xA;        }&#xA;&#xA;        @derp host derp.l3zc.com&#xA;        reverse_proxy @derp :4433&#xA;}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;需要注意的是，如果你和我一样使用 Caddy 配合 dnsproviders 申请泛域名证书，Tailscale 的 MagicDNS 可能会导致 Caddy 本地证书验证失败而报错，需要手动指定&lt;code&gt;resolvers&lt;/code&gt;参数解决。&lt;/p&gt;&#xA;&lt;p&gt;访问刚刚反代的节点，如果出现以下页面，说明配置正确。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/04/tailscale-setup-recap/image_hu_8846b1a6f5d79837.webp&#34; alt=&#34;DERP 搭建成功&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;h3 id=&#34;配置-acl-策略&#34;&gt;配置 ACL 策略&#xA;&lt;/h3&gt;&lt;p&gt;打开 Tailscale 控制台的「&lt;a class=&#34;link&#34; href=&#34;https://login.tailscale.com/admin/acls/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;Access Controls&lt;/a&gt;」页面配置 ACL 策略，将配置好的 DERP 加上。&lt;/p&gt;&#xA;&lt;p&gt;Tailscale 的 ACL 策略是用 HuJSON&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt; 写的，想要在 VSCode 中编辑，选择语言为「JSON with Comments（jsonc）」即可。以下是一个配置示例：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-jsonc&#34;&gt;{&#xA;  &#34;acls&#34;: [{ &#34;action&#34;: &#34;accept&#34;, &#34;src&#34;: [&#34;*&#34;], &#34;dst&#34;: [&#34;*:*&#34;] }],&#xA;  &#34;ssh&#34;: [&#xA;    {&#xA;      &#34;action&#34;: &#34;check&#34;,&#xA;      &#34;src&#34;: [&#34;autogroup:member&#34;],&#xA;      &#34;dst&#34;: [&#34;autogroup:self&#34;],&#xA;      &#34;users&#34;: [&#34;autogroup:nonroot&#34;, &#34;root&#34;]&#xA;    }&#xA;  ],&#xA;&#xA;  // 自建 DERP 配置&#xA;  &#34;derpMap&#34;: {&#xA;    &#34;OmitDefaultRegions&#34;: false, // 改为 true 以排除官方 DERP&#xA;    &#34;Regions&#34;: {&#xA;      &#34;900&#34;: {&#xA;        &#34;RegionID&#34;: 900,&#xA;        &#34;RegionCode&#34;: &#34;sha&#34;,&#xA;        &#34;RegionName&#34;: &#34;Shanghai&#34;,&#xA;        &#34;Nodes&#34;: [&#xA;          {&#xA;            &#34;Name&#34;: &#34;myderp&#34;,&#xA;            &#34;RegionID&#34;: 900,&#xA;            &#34;HostName&#34;: &#34;derp.l3zc.com&#34;&#xA;          }&#xA;        ]&#xA;      }&#xA;    }&#xA;  }&#xA;}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Tailscale 保留&lt;code&gt;RegionID&lt;/code&gt;中的 1-899 作为官方节点，自建节点的 RegionID 必须大于等于 900。&lt;/p&gt;&#xA;&lt;h3 id=&#34;测试连接&#34;&gt;测试连接&#xA;&lt;/h3&gt;&lt;p&gt;配置好 ACL 并保存，Tailscale 会自动为所有客户端同步配置，稍等片刻在客户端用&lt;code&gt;tailscale netcheck&lt;/code&gt;测试连接。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/04/tailscale-setup-recap/image-1_hu_25ccc2304c1a66fd.webp&#34; alt=&#34;用 tailscale netcheck 测试连接&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;需要注意返回的 IP 是否是自己真实的公网 IP，若返回了&lt;code&gt;172.17.0.0/16&lt;/code&gt;网段的地址，说明你 Docker 部分配置错了。&lt;/p&gt;&#xA;&lt;h2 id=&#34;与科学上网并存在-exit-node-劫持流量到-clash-内核&#34;&gt;与科学上网并存：在 Exit Node 劫持流量到 Clash 内核&#xA;&lt;/h2&gt;&lt;h3 id=&#34;声明-exit-node&#34;&gt;声明 Exit Node&#xA;&lt;/h3&gt;&lt;p&gt;在家中准备一个 24 小时开机的设备，可以是树莓派，可以是 MacMini。在上面安装 Tailscale 并将其声明为 Exit Node，并根据需要在 Tailscale 内网声明家庭内网网段，随后在控制台启用这个设备作为 Exit Node，你就获得了一个免费的 VPN，可以让你在陌生的网络环境中保持安全。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;sudo tailscale up --advertise-exit-node --advertise-routes 192.168.1.0/24&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/04/tailscale-setup-recap/image-2_hu_bac8de7f1a5258d0.webp&#34; alt=&#34;找到 Route Settings&#34; /&gt;&#xA; &#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/04/tailscale-setup-recap/Snipaste_2025-04-13_21-20-09_hu_d8313f59e1940d09.webp&#34; alt=&#34;启用相关设置&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;广播完成后，无论身处何地，只要能连上 Tailscale 网络，就能访问家中所有的内网设备。&lt;/p&gt;&#xA;&lt;h3 id=&#34;启用-ip-转发和禁用-udp-gro&#34;&gt;启用 IP 转发和禁用 UDP GRO&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;&#xA;&lt;/h3&gt;&lt;p&gt;启用 IP 转发是树莓派等设备作为 Exit Node 所必须的配置，这里以树莓派为例，如果你使用其他设备，请自行查阅 Tailscale 官网教程。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;echo &#39;net.ipv4.ip_forward = 1&#39; | sudo tee -a /etc/sysctl.d/99-tailscale.conf&#xA;echo &#39;net.ipv6.conf.all.forwarding = 1&#39; | sudo tee -a /etc/sysctl.d/99-tailscale.conf&#xA;sudo sysctl -p /etc/sysctl.d/99-tailscale.conf&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;根据 Tailscale 官方的说法&lt;sup id=&#34;fnref:5&#34;&gt;&lt;a href=&#34;#fn:5&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt;，禁用 UDP GRO&lt;sup id=&#34;fnref:6&#34;&gt;&lt;a href=&#34;#fn:6&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;6&lt;/a&gt;&lt;/sup&gt; 可以提升转发性能，但官方的持久化教程似乎在树莓派上无效，好在我们可以手动配置。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;# 安装 ethtool&#xA;sudo apt update &amp;&amp; sudo apt install ethtool -y&#xA;&#xA;# 关闭 UDP GRO&#xA;sudo ethtool -K eth0 gro off&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;针对持久化的问题手动编写 systemd 配置文件：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;# 创建服务文件&#xA;sudo nano /etc/systemd/system/ethtool.service&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;文件内容如下：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-systemd&#34;&gt;[Unit]&#xA;Description=Configure eth0 GRO&#xA;After=network.target&#xA;&#xA;[Service]&#xA;Type=oneshot&#xA;ExecStart=/sbin/ethtool -K eth0 gro off&#xA;&#xA;[Install]&#xA;WantedBy=multi-user.target&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;随后启动服务：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;sudo systemctl daemon-reload&#xA;sudo systemctl enable ethtool&#xA;sudo systemctl start ethtool&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;在-exit-node-劫持流量&#34;&gt;在 Exit Node 劫持流量&#xA;&lt;/h3&gt;&lt;p&gt;我的 Exit Node 是一台树莓派，在树莓派上配置好 Exit Node 之后就来到了最后一步，也就是劫持手机发送到 Exit Node 上的流量，实现科学上网。出于稳定性原因，我不希望在家庭主路由上直接运行代理软件，为了实现这一点，直接在树莓派上劫持流量是唯一的选择。&lt;/p&gt;&#xA;&lt;p&gt;首先安装 ShellCrash，请自行根据你的需要导入配置文件、配置自动任务等：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;export url=&#39;https://fastly.jsdelivr.net/gh/juewuy/ShellCrash@master&#39; &amp;&amp; wget -q --no-check-certificate -O /tmp/install.sh $url/install.sh  &amp;&amp; bash /tmp/install.sh &amp;&amp; source /etc/profile &amp;&gt; /dev/null&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;随后启动服务，修改修改防火墙运行模式为纯净模式，我个人建议将 SNI 嗅探打开，并将 DNS 模式从&lt;code&gt;fake-ip&lt;/code&gt;切换为&lt;code&gt;redir-host&lt;/code&gt;&lt;sup id=&#34;fnref:7&#34;&gt;&lt;a href=&#34;#fn:7&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;7&lt;/a&gt;&lt;/sup&gt;，同时启用 IPv6 透明代理。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/04/tailscale-setup-recap/image-5_hu_9e9d06f0578b4304.webp&#34; alt=&#34;修改防火墙劫持范围&#34; /&gt;&#xA; &#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/04/tailscale-setup-recap/image-3_hu_efcbb45f4cd30701.webp&#34; alt=&#34;启用域名嗅探&#34; /&gt;&#xA; &#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/04/tailscale-setup-recap/image-6_hu_d681ee087b8dd14a.webp&#34; alt=&#34;修改端口设置&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;设置纯净模式的目的是手动配置 iptables 以实现更精准的流量劫持。我们直接用 iptables 劫持所有 tailscale 网卡作为 Exit Node 转发的流量，首先用&lt;code&gt;ifconfig&lt;/code&gt;查看 tailscale 网卡的名称，默认情况下一般为 Tailscale 0：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;root@raspberrypi:~# ifconfig&#xA;eth0: ......&#xA;&#xA;tailscale0: flags=4305&lt;UP,POINTOPOINT,RUNNING,NOARP,MULTICAST&gt;  mtu 1280&#xA;        inet 100.111.19.50  netmask 255.255.255.255  destination 100.111.19.50&#xA;        inet6 fd7a:115c:a1e0::3d01:1332  prefixlen 128  scopeid 0x0&lt;global&gt;&#xA;        inet6 fe80::c0d5:1a1b:2005:48eb  prefixlen 64  scopeid 0x20&lt;link&gt;&#xA;        unspec 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00  txqueuelen 500  (UNSPEC)&#xA;        RX packets 6780155  bytes 958732442 (914.3 MiB)&#xA;        RX errors 0  dropped 0  overruns 0  frame 0&#xA;        TX packets 4811113  bytes 10858316472 (10.1 GiB)&#xA;        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;劫持&lt;code&gt;tailscale0&lt;/code&gt;网卡的所有流量到本地 Clash 内核监听端口&lt;code&gt;7892&lt;/code&gt;，这个设置在 ShellCrash 中叫做「静态路由端口」。以及，如果你不想和我一样遇到莫名其妙的网络问题，就一定不要忘记劫持 IPv6。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;# IPv4 劫持 tailscale0&#xA;iptables -t nat -A PREROUTING -i tailscale0 -p tcp -j REDIRECT --to-ports 7892&#xA;&#xA;# IPv6 劫持 tailscale0&#xA;ip6tables -t nat -A PREROUTING -i tailscale0 -p tcp -j REDIRECT --to-ports 7892&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;进阶用-tproxy-劫持-udp-流量&#34;&gt;进阶：用 TProxy 劫持 UDP 流量&#xA;&lt;/h3&gt;&lt;p&gt;iptables 的 REDIRECT 只能重定向 TCP 流量，UDP 没有连接状态（无连接协议），所以 REDIRECT 无法保留目标地址，导致透明代理无法知道原始目标地址。&lt;/p&gt;&#xA;&lt;p&gt;所以，如果你用如下方式劫持 UDP 流量：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;iptables -t nat -A PREROUTING -i tailscale0 -p udp -j REDIRECT --to-ports 7892&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;则这个规则不会生效或代理行为不正常。&lt;/p&gt;&#xA;&lt;p&gt;那么，有办法代理 UDP 流量吗？有的兄弟，有的。但前提是：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;核心支持 UDP 透明代理（Clash Premium 和 Mihomo 都支持）；&lt;/li&gt;&#xA;&lt;li&gt;使用 TProxy 模式，而不是 REDIRECT；&lt;/li&gt;&#xA;&lt;li&gt;正确配置了 iptables mangle 表和 policy routing；&lt;/li&gt;&#xA;&lt;li&gt;代理配置文件中启用了 UDP 代理（如 mode: rule + udp: true）。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;假设你已经满足第一条、第二条和最后一条，则以下是一个示例&lt;sup id=&#34;fnref:8&#34;&gt;&lt;a href=&#34;#fn:8&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;8&lt;/a&gt;&lt;/sup&gt;：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;# 创建自定义链&#xA;sudo iptables -t mangle -N SHELLCRASH&#xA;&#xA;# 根据自己的需要忽略本地流量&#xA;sudo iptables -t mangle -A SHELLCRASH -d 127.0.0.1/8 -j RETURN&#xA;sudo iptables -t mangle -A SHELLCRASH -d 100.64.0.0/10 -j RETURN&#xA;sudo iptables -t mangle -A SHELLCRASH -d 192.168.1.0/24 -j RETURN&#xA;sudo iptables -t mangle -A SHELLCRASH -d 172.17.0.0/16 -j RETURN&#xA;&#xA;# mark UDP 和 TCP 到代理&#xA;sudo iptables -t mangle -A SHELLCRASH -p tcp -j TPROXY --on-port 7893 --tproxy-mark 233&#xA;sudo iptables -t mangle -A SHELLCRASH -p udp -j TPROXY --on-port 7893 --tproxy-mark 233&#xA;&#xA;# 接口跳转&#xA;sudo iptables -t mangle -A PREROUTING -i tailscale0 -j SHELLCRASH&#xA;&#xA;# 路由表配置&#xA;echo &#34;233 shellcrash&#34; | sudo tee -a /etc/iproute2/rt_tables&#xA;sudo ip rule add fwmark 233 lookup shellcrash&#xA;sudo ip route add local 0.0.0.0/0 dev lo table shellcrash&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;当然，不要忘记 IPv6：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;# 创建链&#xA;sudo ip6tables -t mangle -N SHELLCRASH6&#xA;&#xA;# 跳过本地地址&#xA;sudo ip6tables -t mangle -A SHELLCRASH6 -d ::1/128 -j RETURN&#xA;sudo ip6tables -t mangle -A SHELLCRASH6 -d fd7a:115c:a1e0::/48 -j RETURN&#xA;&#xA;# 标记 TCP/UDP&#xA;ip6tables -t mangle -A SHELLCRASH6 -i tailscale0 -p tcp -j TPROXY --on-port 7893 --tproxy-mark 233&#xA;ip6tables -t mangle -A SHELLCRASH6 -i tailscale0 -p udp -j TPROXY --on-port 7893 --tproxy-mark 233&#xA;&#xA;# 接口跳转&#xA;sudo ip6tables -t mangle -A PREROUTING -i tailscale0 -j SHELLCRASH6&#xA;&#xA;# 路由表配置&#xA;echo &#34;233 shellcrash&#34; | sudo tee -a /etc/iproute2/rt_tables&#xA;sudo ip -6 rule add fwmark 233 lookup shellcrash&#xA;sudo ip -6 route add local ::/0 dev lo table shellcrash&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;路由规则持久化&#34;&gt;路由规则持久化&#xA;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;ip rule&lt;/code&gt;和&lt;code&gt;ip route&lt;/code&gt;创建的规则在重启后会丢失，所以需要我们手动持久化，最简单直接的方法就是创建一个脚本，并将其添加至 crontab。&lt;/p&gt;&#xA;&lt;p&gt;创建一个脚本：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;sudo nano /usr/local/bin/policy-route.sh&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;编辑为如下内容：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;#!/bin/bash&#xA;&#xA;# IPv4 策略路由&#xA;ip rule add fwmark 233 lookup 233&#xA;ip route add local 0.0.0.0/0 dev lo table 233&#xA;&#xA;# IPv6 策略路由&#xA;ip -6 rule add fwmark 233 lookup 233&#xA;ip -6 route add local ::/0 dev lo table 233&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;授予执行权限后编辑 Crontab：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;sudo chmod +x /usr/local/bin/policy-route.sh&#xA;sudo crontab -e&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;在 crontab 文件底部加上如下内容：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-crontab&#34;&gt;@reboot /usr/local/bin/policy-route.sh&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;原理解释tproxy-到底是怎么转发-udp-流量的&#34;&gt;原理解释：TProxy 到底是怎么转发 UDP 流量的？&#xA;&lt;/h3&gt;&lt;p&gt;如果你看到这里，也许会产生疑惑：为什么整个流程中，我们没有在 iptables 里写&lt;code&gt;--to-ports&lt;/code&gt;，也没看到目标地址被改写，UDP 流量就莫名其妙地被代理了？这是怎么做到的？&lt;/p&gt;&#xA;&lt;p&gt;要解释这个问题，我们先来看 TProxy 和 REDIRECT 的根本区别：&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;REDIRECT 模式：&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;使用 iptables nat 表；&lt;/li&gt;&#xA;&lt;li&gt;将目标地址改写为本地地址（比如 127.0.0.1:7892）；&lt;/li&gt;&#xA;&lt;li&gt;通常用于 TCP 流量；&lt;/li&gt;&#xA;&lt;li&gt;不能保留真实目标地址；&lt;/li&gt;&#xA;&lt;li&gt;需要指定&lt;code&gt;--to-ports&lt;/code&gt;。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-mermaid&#34;&gt;flowchart TB&#xA;    A[客户端设备&lt;br&gt;通过 Tailscale 发起 TCP 请求]&#xA;    B[tailscale0 接口接收流量]&#xA;    C[iptables NAT PREROUTING&lt;br&gt;REDIRECT --to-ports 7892]&#xA;    D[ShellCrash 本地监听&lt;br&gt;127.0.0.1:7892]&#xA;    E[ShellCrash 发起新 TCP 请求&lt;br&gt;→ 目标服务器]&#xA;    F[响应从网络返回&lt;br&gt;ShellCrash 转发响应]&#xA;    G[响应回到客户端设备]&#xA;&#xA;    A --&gt; B --&gt; C --&gt; D --&gt; E --&gt; F --&gt; G&#xA;&#xA;    style C fill:#f9f,stroke:#aaa,stroke-width:1px,color:#000&#xA;    style D fill:#bbf,stroke:#aaa,stroke-width:1px,color:#000&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;TPROXY 模式：&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;使用 iptables mangle 表；&lt;/li&gt;&#xA;&lt;li&gt;不修改目标 IP，而是保留原始目标地址；&lt;/li&gt;&#xA;&lt;li&gt;通过 fwmark 和 policy routing 将报文路由到 &lt;code&gt;lo&lt;/code&gt;；&lt;/li&gt;&#xA;&lt;li&gt;代理程序监听一个特殊端口（例如 7893），并启用 &lt;code&gt;IP_TRANSPARENT&lt;/code&gt;；&lt;/li&gt;&#xA;&lt;li&gt;支持 UDP 和 TCP；&lt;/li&gt;&#xA;&lt;li&gt;不需要 iptables 内指定 &lt;code&gt;--to-ports&lt;/code&gt;，因为不是 NAT，而是标记 + 路由。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-mermaid&#34;&gt;flowchart TB&#xA;    A[客户端设备&lt;br&gt;通过 Tailscale 发起 TCP/UDP 请求]&#xA;    B[tailscale0 接口接收流量]&#xA;    C[iptables MANGLE PREROUTING&lt;br&gt;打上 fwmark 233]&#xA;    D[ip rule: fwmark 233&lt;br&gt;使用 routing table shellcrash]&#xA;    E[ip route: local 0.0.0.0/0&lt;br&gt;dev lo table shellcrash]&#xA;    F[ShellCrash 在 lo:7893 监听&lt;br&gt;IP_TRANSPARENT 模式]&#xA;    G[ShellCrash 获取原始目标地址&lt;br&gt;发起代理连接]&#xA;    H[响应从网络返回&lt;br&gt;ShellCrash 转发响应]&#xA;    I[响应回到客户端设备]&#xA;&#xA;    A --&gt; B --&gt; C --&gt; D --&gt; E --&gt; F --&gt; G --&gt; H --&gt; I&#xA;&#xA;    style C fill:#f9f,stroke:#aaa,stroke-width:1px,color:#000&#xA;    style F fill:#bbf,stroke:#aaa,stroke-width:1px,color:#000&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;TProxy 不使用 DNAT/REDIRECT，而是通过 mangle 表给数据包打上 mark，然后通过 policy routing（&lt;code&gt;ip rule&lt;/code&gt; + &lt;code&gt;ip route&lt;/code&gt;）将这些数据包送到 &lt;code&gt;lo&lt;/code&gt; 接口。代理程序（如 Clash / ShellCrash）监听在 &lt;code&gt;lo&lt;/code&gt;&lt;sup id=&#34;fnref:9&#34;&gt;&lt;a href=&#34;#fn:9&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;9&lt;/a&gt;&lt;/sup&gt; 上的端口（例如 7893），通过启用 &lt;code&gt;IP_TRANSPARENT&lt;/code&gt; 选项，可以读取数据包的原始目标 IP 和端口并进行代理转发。&lt;/p&gt;&#xA;&lt;p&gt;简而言之，TProxy 模式只需要：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;iptables&lt;/code&gt; 给数据包打上 mark；&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;ip rule&lt;/code&gt; + &lt;code&gt;ip route&lt;/code&gt; 将这些包送到 &lt;code&gt;lo&lt;/code&gt;；&lt;/li&gt;&#xA;&lt;li&gt;程序监听 &lt;code&gt;lo&lt;/code&gt; 上的端口并开启 &lt;code&gt;IP_TRANSPARENT&lt;/code&gt;。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;所以不需要在 iptables 中指定 &lt;code&gt;--to-ports&lt;/code&gt;，因为目标 IP 和端口保持不变，代理程序自己可以感知并处理。&lt;/p&gt;&#xA;&lt;h3 id=&#34;iptables-规则持久化&#34;&gt;iptables 规则持久化&#xA;&lt;/h3&gt;&lt;p&gt;安装&lt;code&gt;iptables-persistent&lt;/code&gt;：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;sudo apt update&#xA;sudo apt install iptables-persistent&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;安装过程中会提示你是否保存当前的 IPv4 和 IPv6 配置，选择「是」即可。之后如果你添加了新的规则，记得执行保存命令：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;# 保存当前 IPv4/IPv6 规则&#xA;sudo netfilter-persistent save&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;保存后的规则文件路径：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;IPv4：&lt;code&gt;/etc/iptables/rules.v4&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;IPv6：&lt;code&gt;/etc/iptables/rules.v6&lt;/code&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;你也可以直接编辑上面的&lt;code&gt;rules.v4&lt;/code&gt;/&lt;code&gt;rules.v6&lt;/code&gt;文件，按需修改。&lt;/p&gt;&#xA;&lt;h2 id=&#34;最终效果&#34;&gt;最终效果&#xA;&lt;/h2&gt;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/04/tailscale-setup-recap/Screenshot_2025-04-14-18-05-18-259_com.tailscale._hu_783d6158c36336e1.webp&#34; alt=&#34;基本都能打洞成功&#34; /&gt;&#xA; &#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/04/tailscale-setup-recap/Screenshot_2025-04-14-18-04-45-053_com.cnspeedtes_hu_a47b6440dc775101.webp&#34; alt=&#34;速度尚可接受&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;这套方案的使用体验取决于你家的上行带宽，我家的网络是下行 500M 上行 60M，目前没有遇到一次打洞失败的情况，所以基本都能跑满，延迟也尚可接受，并且可以在外地随时随地端到端加密访问家中的 Immich 和 OpenWRT 路由器等设备以及实现科学上网，总体来看，我还算比较满意。&lt;/p&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;参考: &lt;a class=&#34;link&#34; href=&#34;https://icloudnative.io/posts/custom-derp-servers/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;https://icloudnative.io/posts/custom-derp-servers/&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:2&#34;&gt;&#xA;&lt;p&gt;参考: &lt;a class=&#34;link&#34; href=&#34;https://tailscale.com/kb/1232/derp-servers&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;https://tailscale.com/kb/1232/derp-servers&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:3&#34;&gt;&#xA;&lt;p&gt;有关 HuJSON: &lt;a class=&#34;link&#34; href=&#34;https://github.com/tailscale/hujson&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;https://github.com/tailscale/hujson&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:4&#34;&gt;&#xA;&lt;p&gt;参考： &lt;a class=&#34;link&#34; href=&#34;https://tailscale.com/kb/1408/quick-guide-exit-nodes&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;https://tailscale.com/kb/1408/quick-guide-exit-nodes&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:5&#34;&gt;&#xA;&lt;p&gt;参考: &lt;a class=&#34;link&#34; href=&#34;https://tailscale.com/kb/1320/performance-best-practices#linux-optimizations-for-subnet-routers-and-exit-nodes&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;https://tailscale.com/kb/1320/performance-best-practices#linux-optimizations-for-subnet-routers-and-exit-nodes&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:5&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:6&#34;&gt;&#xA;&lt;p&gt;UDP GRO（Generic Receive Offload）是 Linux 内核中的一种网络优化技术，主要用于合并多个小数据包以提高处理效率。但在设备作为网络转发节点的使用场景下，这可能会导致转发延迟增加和高丢包率环境下的吞吐量下降。&amp;#160;&lt;a href=&#34;#fnref:6&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:7&#34;&gt;&#xA;&lt;p&gt;相较于&lt;code&gt;fake-ip&lt;/code&gt;，&lt;code&gt;redir-host&lt;/code&gt;兼容性更好，出现问题的概率更低，也不会出现开关代理之后短时间内因为&lt;code&gt;fake-ip&lt;/code&gt;残留而断网的情况，所以一般情况下我建议使用&lt;code&gt;redir-host&lt;/code&gt;搭配 GeoSite 分流规则使用。我这台树莓派的 DNS 上游是&lt;a class=&#34;link&#34; href=&#34;https://blog.l3zc.com/2025/02/what-i-have-done-on-my-dns/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;已经配置好的 SmartDNS&lt;/a&gt;，不存在 DNS 污染的问题，体验良好。&amp;#160;&lt;a href=&#34;#fnref:7&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:8&#34;&gt;&#xA;&lt;p&gt;参考: &lt;a class=&#34;link&#34; href=&#34;https://blog.zonowry.com/posts/clash_iptables_tproxy/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;https://blog.zonowry.com/posts/clash_iptables_tproxy/&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:8&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:9&#34;&gt;&#xA;&lt;p&gt;&lt;code&gt;lo&lt;/code&gt; 是 Linux 系统中默认的「回环接口（Loopback Interface）」，在透明代理中，它不仅处理 localhost 流量，还被用来接收原本属于外部世界的网络连接，实现对外部流量的本地劫持和转发。&amp;#160;&lt;a href=&#34;#fnref:9&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/div&gt;&#xA;</description>
        </item><item>
            <title>Google Play 商店的国内 CDN：从密码学入门到分流策略优化</title>
            <link>https://blog.l3zc.com/2025/03/chinese-cdn-used-by-playstore/</link>
            <pubDate>Sat, 15 Mar 2025 21:15:01 +0800</pubDate>
            <guid>https://blog.l3zc.com/2025/03/chinese-cdn-used-by-playstore/</guid>
            <description>&lt;img src=&#34;https://blog.l3zc.com/2025/03/chinese-cdn-used-by-playstore/cover_hu_8e92ca92eba5064b.webp&#34; alt=&#34;Featured image of post Google Play 商店的国内 CDN：从密码学入门到分流策略优化&#34; /&gt;&lt;p&gt;改用自己的 Mihomo 覆写规则后，手机从 Google Play 上下载应用就会一直转圈圈，而换用机场的规则就没问题，非常奇怪。遂调取 Mihomo 内核日志查看。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-log&#34;&gt;play-lh.googleusercontent.com:443 match RuleSet(cdn_domainset) using 静态资源[🇭🇰 香港 07]&#xA;play.googleapis.com:443 match GeoSite(GFW) using 节点选择[🇭🇰 香港 07]&#xA;play-lh.googleusercontent.com:443 match RuleSet(cdn_domainset) using 静态资源[🇭🇰 香港 07]&#xA;play-fe.googleapis.com:443 match GeoSite(GFW) using 节点选择[🇭🇰 香港 07]&#xA;services.googleapis.cn:443 match GeoSite(CN) using 全球直连[DIRECT]&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;（以上日志已精简）&lt;/p&gt;&#xA;&lt;p&gt;国行手机内置的 Google Play 服务使用&lt;code&gt;services.googleapis.cn&lt;/code&gt;而非&lt;code&gt;services.googleapis.com&lt;/code&gt;域名提供服务，而这个域名在大多数分流规则中都是直连，对，问题就出在这里，修改规则把这个域名分流到代理就万事大吉了！&lt;/p&gt;&#xA;&lt;p&gt;……万事大吉了，吗？&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;rr4---sn-j5o7dn7s.xn--ngstr-lra8j.com:443 match Match using 漏网之鱼[🇭🇰 香港 07]&#xA;rr2---sn-j5o7dn7s.xn--ngstr-lra8j.com:443 match Match using 漏网之鱼[🇭🇰 香港 07]&#xA;rr1---sn-j5o76n7z.xn--ngstr-lra8j.com:443 match Match using 漏网之鱼[🇭🇰 香港 07]&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;等等，为什么每次从 Play 商店下载应用都会出现这些奇怪的域名？在网上查找一番资料后，新世界的大门就此打开。&lt;/p&gt;&#xA;&lt;h2 id=&#34;与-google-截然相反却又异曲同工的-ångströ&#34;&gt;与 Google 截然相反却又异曲同工的 Ångströ&#xA;&lt;/h2&gt;&lt;p&gt;看到 xn&amp;ndash;ngstr-lra8j.com，熟悉域名的同学肯定知道，这是 PunyCode 编码，这串字符解码后就是 ångströ.com。Anders Jonas Ångström 是一位瑞典物理学家，他是光谱学的奠基人。&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;埃斯特朗（Ångström）是为纪念他而以他的名字命名的长度单位，$ 1 Å = 10^{-10} m = \frac{1}{10} nm $，通常用于描述非常短的距离，比如原子和分子尺寸或光的波长。其代表极端小的量级，尤其是在物理学和化学中用于精细测量。&lt;/p&gt;&#xA;&lt;p&gt;虽然 Google 的名字并不是直接取自「Googol」，但是它的灵感来源于该词。「Googol」是一个数学术语，表达$10^{100}$，即1后面跟着100个零，是一个极其庞大的数字，常用来表示计算机科学中涉及的巨大数字或信息量。&lt;/p&gt;&#xA;&lt;p&gt;在命名哲学上，Google 与Ångströ 这对看似分处光谱两端的科技概念，却展现出耐人寻味的对称美学。前者源于数学概念「Googol」的创造性改写，后者则脱胎于物理单位「Ångström」的意象重构。这两个经过艺术化变奏的称谓，恰似科技文明的双螺旋：Google 之称承载着驾驭浩如星海的数字宇宙的雄心，Ångströ 之谓则暗喻着雕琢精微的原子级技术图景。当这两个经过创造性变形的专业术语在硅谷相遇，恰好构成了一组完美的认知坐标——既指向人类处理信息的尺度极限，也昭示着科技文明同时向宏观与微观进军的壮阔征程。&lt;/p&gt;&#xA;&lt;h2 id=&#34;入门密码学google-基础设施域名的规律&#34;&gt;入门密码学：Google 基础设施域名的规律&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;&#xA;&lt;/h2&gt;&lt;p&gt;OK，enough。现在让我们把视角转回 Google 的基础设施。先来看一些完整的连接域名：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;rr4---sn-j5o7dn7s.xn--ngstr-lra8j.com&#xA;rr1---sn-j5o76n7z.xn--ngstr-lra8j.com&#xA;rr5---sn-i3b7knzs.xn--ngstr-lra8j.com&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这些看似随机的字符串，实际上是经过一些简单的密码学加密后的结果。让我们拆解其中的核心部件。&lt;/p&gt;&#xA;&lt;h3 id=&#34;城市名称的转换规则&#34;&gt;城市名称的转换规则&#xA;&lt;/h3&gt;&lt;p&gt;以&lt;code&gt;rr1---sn-j5o76n7z&lt;/code&gt;为例，关键的信息段在&lt;code&gt;sn-&lt;/code&gt;后面的 8 位：&lt;code&gt;r1---sn-[123][45][6][78]&lt;/code&gt;。前三位，也就是本例中的&lt;code&gt;j5o&lt;/code&gt;是城市名称，由该城市主要机场的 IATA 码通过一定的密码学变换转换而来。&lt;/p&gt;&#xA;&lt;p&gt;首先构建一张 5 * 7 的数字字母表：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;行0: 1 0 2 3 4&#xA;行1: 5 6 7 8 9&#xA;行2: a b c d e&#xA;行3: f g h i j&#xA;行4: k l m n o&#xA;行5: p q r s t&#xA;行6: u v w x y&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;逆时针旋转这张表，即从新表格的左下角开始，依次按照原表格「从左到右，从上到下」的顺序，把原表的数据在新表上按照「从下到上，从左到右」的顺序誊抄。&lt;/p&gt;&#xA;&lt;p&gt;旋转完成后，可以得到下表：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;| 6 d k r y |&#xA;| --------- |&#xA;| 5 c j q x |&#xA;| 4 b i p w |&#xA;| 3 a h o v |&#xA;| 2 9 g n u |&#xA;| 0 8 f m t |&#xA;| --------- |&#xA;| 1 7 e l s |&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;表格中间用线条框出来的 5*5 的部分就是最终的密码表，一共有 25 个字符，「从左到右，从上到下」依次代表字母&lt;code&gt;a&lt;/code&gt;到字母&lt;code&gt;y&lt;/code&gt;。例如，上海虹桥国际机场的 IATA 代码为&lt;code&gt;sha&lt;/code&gt;,我们可以通过这张表得到加密后的密文：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;s&lt;/code&gt;在字母表中是第 19 个字母，「从左到右，从上到下」依次在密码表中数到第 19 个字符，也就是&lt;code&gt;n&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;h&lt;/code&gt;在字母表中是第 8 个字母，「从左到右，从上到下」依次在密码表中数到第 8 个字符，也就是&lt;code&gt;i&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;a&lt;/code&gt;在字母表中是第 1 个字母，「从左到右，从上到下」依次在密码表中数到第 1 个字符，也就是&lt;code&gt;5&lt;/code&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;加密后的密文就是&lt;code&gt;ni5&lt;/code&gt;。&lt;/p&gt;&#xA;&lt;p&gt;反之亦然，回到一开始的城市名称&lt;code&gt;j5o&lt;/code&gt;，从我们刚刚得到的密码表中反推出原文：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;j&lt;/code&gt;按照「从左到右，从上到下」的顺序是第 3 个字符，也就是&lt;code&gt;c&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;5&lt;/code&gt;按照「从左到右，从上到下」的顺序是第 1 个字符，也就是&lt;code&gt;a&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;o&lt;/code&gt;按照「从左到右，从上到下」的顺序是第 14 个字母，也就是&lt;code&gt;n&lt;/code&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;翻译完成的明文是&lt;code&gt;can&lt;/code&gt;，也就是广州白云国际机场的 IATA 码，显然，我们连接到的服务器位于广州。&lt;/p&gt;&#xA;&lt;p&gt;要是谷歌有某个服务器位于苏黎世，而苏黎世机场的 IATA 码是&lt;code&gt;zrh&lt;/code&gt;，有一个字母&lt;code&gt;z&lt;/code&gt;，可是我们的密码表只有&lt;code&gt;a&lt;/code&gt;到&lt;code&gt;y&lt;/code&gt;啊？别担心，Google 当然也考虑到了这个问题，&lt;code&gt;z&lt;/code&gt;对应密码表中的&lt;code&gt;1&lt;/code&gt;。&lt;/p&gt;&#xA;&lt;p&gt;将这套规则用代码表示如下：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;table = &#34;5cjqx4bipw3ahov29gnu08fmt1&#34;&#xA;def iata2cipher(iata):&#xA;    global table&#xA;    iata = iata.upper()&#xA;    cipher = &#34;&#34;&#xA;    for element in iata:&#xA;        index = ord(element) - 65&#xA;        cipher += table[index]&#xA;    return cipher&#xA;&#xA;def cipher2iata(cipher):&#xA;    global table&#xA;    cipher = cipher.lower()&#xA;    iata = &#34;&#34;&#xA;    for element in cipher:&#xA;        index = table.find(element)&#xA;        iata += chr(65 + index)&#xA;    return iata&#xA;&#xA;# print(iata2cipher(input(&#34;IATA:&#34;)))&#xA;# print(cipher2iata(input(&#34;Cipher:&#34;)))&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;服务器组编号&#34;&gt;服务器组编号&#xA;&lt;/h3&gt;&lt;p&gt;[45]和[78]位，如&lt;code&gt;76&lt;/code&gt;和&lt;code&gt;7z&lt;/code&gt;，表示服务器组（接入点）编号，由下表第一列（已经用分隔线隔开）包含的字符组成，&lt;code&gt;7elsz6dkry&lt;/code&gt;分别代表&lt;code&gt;0123456789&lt;/code&gt;。所以，&lt;code&gt;76&lt;/code&gt;的明文是&lt;code&gt;05&lt;/code&gt;，&lt;code&gt;7z&lt;/code&gt;的明文是&lt;code&gt;04&lt;/code&gt;。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;  | 1 2 3 4 5 6&#xA;7 | 8 9 a b c d&#xA;e | f g h i j k&#xA;l | m n o p q r&#xA;s | t u v w x y&#xA;z | 0 1 2 3 4 5&#xA;6 | 7 8 9 a b c&#xA;d | e f g h i j&#xA;k | l m n o p q&#xA;r | s t u v w x&#xA;y | z&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;以 Python 表示如下：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;codeTable = &#34;7elsz6dkry&#34;&#xA;def cipher2code(cipher):&#xA;    global codeTable&#xA;    cipher = cipher.lower()&#xA;    codeString = &#34;&#34;&#xA;    for element in cipher:&#xA;        index = codeTable.find(element)&#xA;        codeString += chr(index + 48)&#xA;    return codeString&#xA;&#xA;# print(cipher2code(input(&#34;Cipher:&#34;)))&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;同样要将数字加密为密文同理，这里不再赘述。&lt;/p&gt;&#xA;&lt;h3 id=&#34;支持协议&#34;&gt;支持协议&#xA;&lt;/h3&gt;&lt;p&gt;[6]位表示网络协议信息：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;n&lt;/code&gt;：IPv6（地址段 0x000-0x3FF）&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;u&lt;/code&gt;：IPv6（地址段 0x400-0x7FF）&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;m&lt;/code&gt;：仅支持 IPv4&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;例如：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;a5mekn7r&lt;/code&gt;，IPv6 前缀：&lt;code&gt;2607:f8b0:4007:a::/64&lt;/code&gt;，IPv4 前缀：&lt;code&gt;74.125.103.0/24&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;a5m7zu7r&lt;/code&gt;，IPv6 前缀：&lt;code&gt;2607:f8b0:4007:407::/64&lt;/code&gt;，IPv4 前缀：&lt;code&gt;74.125.215.0/24&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;a5mekm76&lt;/code&gt;，仅支持 IPv4，前缀：&lt;code&gt;208.117.242.0/24&lt;/code&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;正确的分流处理&#34;&gt;正确的分流处理&#xA;&lt;/h2&gt;&lt;blockquote class=&#34;alert alert-warning&#34;&gt;&#xA;        &lt;div class=&#34;alert-header&#34;&gt;&#xA;            &lt;span class=&#34;alert-icon&#34;&gt;⚠️&lt;/span&gt;&#xA;            &lt;span class=&#34;alert-title&#34;&gt;警告&lt;/span&gt;&#xA;        &lt;/div&gt;&#xA;        &lt;div class=&#34;alert-body&#34;&gt;&#xA;            &lt;p&gt;Google 显然不会为这些位于中国大陆的，未经 ICP 备案的 CDN 的稳定性和速度背书。如果流量足够，那还是直接将&lt;code&gt;services.googleapis.cn&lt;/code&gt;加入代理列表吧。&lt;/p&gt;&#xA;        &lt;/div&gt;&#xA;    &lt;/blockquote&gt;&#xA;&lt;p&gt;了解了 Google 基础设施域名的加密规律后，我们可以根据这些信息实现更精准的分流策略。目前已知 Google 在中国大陆的 CDN 节点主要分布在北京（&lt;code&gt;2x3&lt;/code&gt;）、上海（&lt;code&gt;ni5&lt;/code&gt;）和广州（&lt;code&gt;j5o&lt;/code&gt;），对应的域名特征可以通过正则表达式识别：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;rules:&#xA;  - DOMAIN-REGEX,^r+[0-9]+(---|\.)sn-(2x3|ni5|j5o)\w{5}\.xn--ngstr-lra8j\.com$,DIRECT&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这条规则会匹配所有形如 &lt;code&gt;rr1---sn-j5o76n7z.xn--ngstr-lra8j.com&lt;/code&gt; 的国内 CDN 域名，并将其标记为直连。而对于其他未被覆盖的 Google 域名（如 &lt;code&gt;play.googleapis.com&lt;/code&gt; 等），仍遵循原有的代理规则。&lt;/p&gt;&#xA;&lt;p&gt;事实上，GeoSite 数据库的上游 &lt;a class=&#34;link&#34; href=&#34;https://github.com/v2fly/domain-list-community&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;v2fly/domain-list-community&lt;/a&gt; 已针对 Google Play 的国内 CDN 节点进行了优化&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;。只需在 Mihomo 配置中启用 &lt;code&gt;GEOSITE,GOOGLE-PLAY@CN&lt;/code&gt; 规则，即可自动实现国内 CDN 直连与海外域名代理的智能分流：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;rules:&#xA;  - GEOSITE,GOOGLE-PLAY@CN,直连&#xA;  - GEOSITE,GOOGLE,代理&lt;/code&gt;&lt;/pre&gt;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;参考: &lt;a class=&#34;link&#34; href=&#34;https://en.wikipedia.org/wiki/Anders_Jonas_%C3%85ngstr%C3%B6m&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;https://en.wikipedia.org/wiki/Anders_Jonas_%C3%85ngstr%C3%B6m&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:2&#34;&gt;&#xA;&lt;p&gt;参考: &lt;a class=&#34;link&#34; href=&#34;https://github.com/lennylxx/ipv6-hosts/wiki/sn-domains&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;https://github.com/lennylxx/ipv6-hosts/wiki/sn-domains&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:3&#34;&gt;&#xA;&lt;p&gt;具体的commit: &lt;a class=&#34;link&#34; href=&#34;https://github.com/v2fly/domain-list-community/pull/2436/commits/a86c1bf3d9bf577869180874d87c76ddf6282fc1&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;https://github.com/v2fly/domain-list-community/pull/2436/commits/a86c1bf3d9bf577869180874d87c76ddf6282fc1&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/div&gt;&#xA;</description>
        </item><item>
            <title>最速 Substore 订阅管理指南</title>
            <link>https://blog.l3zc.com/2025/03/clash-subscription-convert/</link>
            <pubDate>Fri, 07 Mar 2025 18:54:29 +0800</pubDate>
            <guid>https://blog.l3zc.com/2025/03/clash-subscription-convert/</guid>
            <description>&lt;img src=&#34;https://blog.l3zc.com/2025/03/clash-subscription-convert/image-8_hu_92098803a4bf150d.webp&#34; alt=&#34;Featured image of post 最速 Substore 订阅管理指南&#34; /&gt;&lt;p&gt;当我趁着春节各家机场的促销订阅多个机场之后，如何充分利用每个节点就变成了说难不难说简单也不简单的问题，我当然可以订阅各家机场提供的配置文件，然后在他们之间切换，但这样未免也太麻烦了。更何况我还有自建节点，我可不想为了这一个节点专门去开一个新的配置。&lt;/p&gt;&#xA;&lt;p&gt;Sub-Store 很好的解决了这个问题，它可以从多个订阅中抽取节点信息，通过正则表达式或者 JS 整理它们，最后输出一个整合了所有节点信息的订阅。&lt;/p&gt;&#xA;&lt;p&gt;部署它可以直接使用 xream 打包好的镜像，这个镜像整合了前后端，如果在公网部署，记得更改一下后端路径，否则你的配置文件很可能会被盗用。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;services:&#xA;  sub-store:&#xA;    image: xream/sub-store&#xA;    container_name: substore&#xA;    restart: always&#xA;    environment:&#xA;      - SUB_STORE_CRON=55 23 * * *&#xA;      - SUB_STORE_FRONTEND_BACKEND_PATH=/super-random-path&#xA;    ports:&#xA;      - &#34;3001:3001&#34;&#xA;    volumes:&#xA;      - ./data:/opt/app/data&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;反代 3001 端口即可访问 Substore 的前端，这里以 Caddy 为例：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-caddyfile&#34;&gt;sub-domain.example.com {&#xA;        reverse_proxy :3001&#xA;}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;当然，初次进入前端别忘了新增后端地址，这时的后端地址取决于之前 compose file 里的设置，在本文的例子中，后端地址为&lt;code&gt;https://sub-domain.example.com/super-random-path&lt;/code&gt;。&lt;/p&gt;&#xA;&lt;h2 id=&#34;组合订阅的管理&#34;&gt;组合订阅的管理&#xA;&lt;/h2&gt;&lt;p&gt;添加所有机场上游和自建节点之后，就可以开始把它们全都加进单个组合订阅，但各个机场对节点的命名五花八门，默认情况下看起来非常杂乱，甚至不同机场之间的节点还有重名的可能。好在 Sub-Store 有通过脚本对节点进行批量重命名操作的功能，这里推荐&lt;a class=&#34;link&#34; href=&#34;https://github.com/Keywos/rule/blob/main/rename.js&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;一个脚本&lt;/a&gt;，能够帮我们为所有的机场节点重命名。&lt;/p&gt;&#xA;&lt;p&gt;欲使用这个脚本，只需在编辑订阅时将以下地址粘贴到脚本操作处即可。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;https://raw.githubusercontent.com/Keywos/rule/main/rename.js&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/03/clash-subscription-convert/image_hu_8310153db045f240.webp&#34; alt=&#34;节点操作&#34; /&gt;&#xA; &#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/03/clash-subscription-convert/image-1_hu_333de5a5c288737.webp&#34; alt=&#34;整理完成的节点列表&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;最后再进行一些你喜欢的节点操作，可以整理出一个统一规范的节点列表。&lt;/p&gt;&#xA;&lt;h2 id=&#34;生成-clash-配置&#34;&gt;生成 Clash 配置&#xA;&lt;/h2&gt;&lt;p&gt;现在虽然已经有了节点列表，但现在生成的配置文件并不包含任何规则，需要自行编写或者拉取第三方规则。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/03/clash-subscription-convert/image-2_hu_755a17a55a5da685.webp&#34; alt=&#34;选择 Mihomo 配置&#34; /&gt;&#xA; &#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/03/clash-subscription-convert/image-3_hu_244966c33439c095.webp&#34; alt=&#34;编辑信息&#34; /&gt;&#xA; &#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/03/clash-subscription-convert/image-4_hu_c92302b1defa984b.webp&#34; alt=&#34;生成分享链接&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/03/clash-subscription-convert/image-6_hu_c37bf30e1b9005a7.webp&#34; alt=&#34;脚本操作&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;转到 Substore 的文件管理，创建一份新的 Mihomo 配置：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;「来源」选择组合订阅，并在订阅名称上选择你的订阅组&lt;/li&gt;&#xA;&lt;li&gt;在脚本操作中填入自己的覆写配置&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;这是我自己的覆写规则 &lt;a class=&#34;link&#34; href=&#34;https://github.com/powerfullz/override-rules&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;powerfullz/override-rules&lt;/a&gt;，为 Mihomo/Substore 设计，核心特色如下：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;集成 &lt;a class=&#34;link&#34; href=&#34;https://github.com/SukkaW/Surge&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;SukkaW/Surge&lt;/a&gt; 与 &lt;a class=&#34;link&#34; href=&#34;https://github.com/217heidai/adblockfilters&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;217heidai/adblockfilters&lt;/a&gt; 等优质规则，兼容性强，覆盖面广。&lt;/li&gt;&#xA;&lt;li&gt;针对 Truth Social、E-Hentai、TikTok、加密货币等场景，新增专用分流规则，满足多样化需求。&lt;/li&gt;&#xA;&lt;li&gt;精简冗余，结构清晰，维护便捷。&lt;/li&gt;&#xA;&lt;li&gt;深度融合 &lt;a class=&#34;link&#34; href=&#34;https://github.com/Loyalsoldier/v2ray-rules-dat&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;Loyalsoldier/v2ray-rules-dat&lt;/a&gt; GeoSite/GeoIP，分流更精准。&lt;/li&gt;&#xA;&lt;li&gt;IP 规则默认添加 &lt;code&gt;no-resolve&lt;/code&gt;，有效减少本地 DNS 解析，提升速度与隐私。&lt;/li&gt;&#xA;&lt;li&gt;动态覆写：自动识别节点国家/地区，仅生成实际存在的分组，节点名称实时枚举，配置更智能。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;javascript-格式覆写&#34;&gt;JavaScript 格式覆写&#xA;&lt;/h3&gt;&lt;p&gt;复制 JavaScript 格式覆写文件的链接&lt;code&gt;https://cdn.jsdelivr.net/gh/powerfullz/override-rules@1/convert.min.js&lt;/code&gt;，并根据需要在后面附加参数，格式如下：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;https://cdn.jsdelivr.net/gh/powerfullz/override-rules@1/convert.min.js#参数1=true&amp;参数2=true&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;目前支持如下参数：&lt;/p&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;参数&lt;/th&gt;&#xA;          &lt;th&gt;功能&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;loadbalance&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;启用国家/地区节点组的负载均衡&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;landing&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;启用落地节点功能&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;ipv6&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;启用 IPv6 支持&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;full&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;生成针对纯内核使用场景的完整配置&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;keepalive&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;启用 TCP KeepAlive&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;quic&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;允许 QUIC 流量&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;fakeip&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;DNS 增强模式使用 fake-ip&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;regex&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;各国家代理组改用 include-all + 正则过滤模式&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;threshold&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;国家/地区节点数量小于该值时不显示分组（默认 0）&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;例如，若有负载均衡和 IPv6 需求，最终的覆写脚本链接为：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;https://cdn.jsdelivr.net/gh/powerfullz/override-rules@1/convert.min.js#loadbalance=true&amp;ipv6=true&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;将最终的覆写脚本链接粘贴到脚本操作处，使用 Substore 的生成预览功能确认没有问题后即可保存。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/03/clash-subscription-convert/image-7_hu_a9aea54492dfed6c.webp&#34; alt=&#34;新建一个脚本操作并粘贴最终的订阅链接&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;h3 id=&#34;yaml-格式覆写&#34;&gt;YAML 格式覆写&#xA;&lt;/h3&gt;&lt;p&gt;&lt;del&gt;YAML 格式覆写我自己已经不用了，随缘维护，但欢迎 PR&lt;/del&gt;&lt;/p&gt;&#xA;&lt;p&gt;写了个 Github Actions 自动根据 JS 格式覆写生成 YAML 格式覆写，所以现在 YAML 格式又开始维护了。&lt;/p&gt;&#xA;&lt;p&gt;除了直接引用 convert.js 动态覆写，你也可以使用仓库中预先生成好的 32 份 YAML 格式覆写——它们都放在 yamls/ 目录里，由 GitHub Actions 在每次推送后自动重新生成、覆盖。适用于诸如 Clash Verge 等不支持 JS 覆写的客户端和转换服务。&lt;/p&gt;&#xA;&lt;p&gt;文件命名规则：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;config_lb-{0|1}_landing-{0|1}_ipv6-{0|1}_full-{0|1}_keepalive-{0|1}_fakeip-{0|1}_quic-{0|1}.yaml&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;示例（开启 full，其余关闭）：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;https://raw.githubusercontent.com/powerfullz/override-rules/refs/heads/main/yamls/config_lb-0_landing-0_ipv6-0_full-1_keepalive-0_fakeip-0_quic-0.yaml&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;CI 只是套用一份假的&lt;code&gt;fake_proxies.json&lt;/code&gt;来生成覆写，所以不可能实现 JS 覆写自动根据节点匹配生成对应代理组的功能，只能把所有地区节点组都放进去。如果你已经搭建 Substore，并且想要「动态识别国家 + 传参」的灵活性，还是推荐使用 JS 覆写。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/03/clash-subscription-convert/image-6_hu_c37bf30e1b9005a7.webp&#34; alt=&#34;填入对应的 RAW 链接&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;h2 id=&#34;生成下载链接&#34;&gt;生成下载链接&#xA;&lt;/h2&gt;&lt;p&gt;保存成功后点击分享按钮生成分享链接，设置分享有效期后点击「创建分享」，生成的链接即最终成型的 Mihomo 配置文件，将其作为订阅链接在你的代理软件内订阅就大功告成了。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/03/clash-subscription-convert/image-8_hu_92098803a4bf150d.webp&#34; alt=&#34;最终效果&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;部分机场 UDP 性能差，开启后可能出现体验降级。&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:2&#34;&gt;&#xA;&lt;p&gt;开启后可能有助于解决 TUN 模式无法上网的问题。&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:3&#34;&gt;&#xA;&lt;p&gt;各国家代理组改用 &lt;code&gt;include-all&lt;/code&gt; + 正则过滤模式，由 Mihomo 内核在运行时按正则动态筛选节点，而非在脚本执行时枚举节点名称（默认 false）&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/div&gt;&#xA;</description>
        </item><item>
            <title>我的家庭网络域名解析方案</title>
            <link>https://blog.l3zc.com/2025/02/what-i-have-done-on-my-dns/</link>
            <pubDate>Thu, 06 Feb 2025 17:01:51 +0800</pubDate>
            <guid>https://blog.l3zc.com/2025/02/what-i-have-done-on-my-dns/</guid>
            <description>&lt;img src=&#34;https://blog.l3zc.com/2025/02/what-i-have-done-on-my-dns/cover_hu_1bda1c3daad4bc1b.webp&#34; alt=&#34;Featured image of post 我的家庭网络域名解析方案&#34; /&gt;&lt;p&gt;人在中国，用起 DNS 会碰到两个问题，一个是 DNS 污染，另一个是 DNS 泄露。想要上网上得痛快，就必须要先解决 DNS 的问题。&lt;/p&gt;&#xA;&lt;h2 id=&#34;什么是-dns-泄露&#34;&gt;什么是 DNS 泄露&#xA;&lt;/h2&gt;&lt;p&gt;如果你现在正在开着代理看这篇文章，可以先打开&lt;a class=&#34;link&#34; href=&#34;https://browserleaks.com/dns&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;这个网站&lt;/a&gt;检测一下自己的 DNS 请求是否被发送到了国内的 DNS 服务器上。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/02/what-i-have-done-on-my-dns/image-5_hu_76c2844c94b4a181.webp&#34; alt=&#34;典型的 DNS 泄露&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;简单来说，当你在使用 VPN 等代理工具时，你认为你的请求不会被除了你和 VPN 提供商以外的第三方看到，然而主机的 DNS 请求却被发送送给了运营商的 DNS 服务器或者是公共 DNS 服务器(阿里、腾讯等)进行解析，而你的真实 IP 地址和请求的域名都会被记录下来。如果 DNS 请求解析一些敏感网站的域名(比如电报、维基解密等)，就会被监管者注意到，从而被警告或者请去喝茶。&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;&#xA;&lt;p&gt;因此，正确地配置代理工具的分流策略来避免 DNS 泄露就显得尤为重要。&lt;/p&gt;&#xA;&lt;h2 id=&#34;smartdns-的配置&#34;&gt;SmartDNS 的配置&#xA;&lt;/h2&gt;&lt;h3 id=&#34;防止-dns-泄露&#34;&gt;防止 DNS 泄露&#xA;&lt;/h3&gt;&lt;p&gt;DNS 作为上网的指路人，排在第一位的要求就是必须要快，在这样的要求下本地部署 SmartDNS 充当家庭网络的 DNS 服务器真的再合适不过了。关于 SmartDNS 的作用和配置技巧，以及其如何优选 CDN 加快网络访问速度，Sukka 的「&lt;a class=&#34;link&#34; href=&#34;https://blog.skk.moe/post/i-have-my-unique-dns-setup/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;我有特别的 DNS 配置和使用技巧&lt;/a&gt;」已经讲的很好，推荐在继续阅读本文之前先去看看这篇文章，其中的内容我在这里便不再赘述，我的叙述重心将会放在如何在 Luci 界面上实现这些配置。&lt;/p&gt;&#xA;&lt;p&gt;如果你和我一样使用 OpenWRT，SmartDNS 的 Luci App 会在每次启动时根据面板上保存的配置自动生成新的配置文件，所以，不要直接修改&lt;code&gt;/etc/smartdns&lt;/code&gt;目录下原有的配置文件，所有的更改都应该在 Luci App 里操作。&lt;/p&gt;&#xA;&lt;p&gt;基本上，SmartDNS 上游数量越多越好，因为较多的上游并不会影响 SmartDNS 的性能&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;，并且多个上游可以增加冗余。为了防止 DNS 泄露，在配置上游时，应当在默认分组添加国外 DNS 上游（当然，最好是加密 DNS），并将国内的服务器归入一个单独的分组（例如&lt;code&gt;domestic&lt;/code&gt;），并加上&lt;code&gt;-exclude-default-group&lt;/code&gt;的额外参数以将它们从默认分组中排除。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/02/what-i-have-done-on-my-dns/image_hu_bd77370d9a6d2fcd.webp&#34; alt=&#34;国内上游的分组设置&#34; /&gt;&#xA; &#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/02/what-i-have-done-on-my-dns/image-1_hu_1ba4737b13c17f98.webp&#34; alt=&#34;一些可供参考的上游&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;h3 id=&#34;加速国内域名解析&#34;&gt;加速国内域名解析&#xA;&lt;/h3&gt;&lt;p&gt;设置好国外上游，DNS 泄露的问题就解决了，但因为我们在默认分组中排除了国内上游，访问新的域名时需要等待国外上游返回结果，这显然会拖慢国内网站的访问速度，并且还会劣化 CDN。&lt;/p&gt;&#xA;&lt;p&gt;&lt;a class=&#34;link&#34; href=&#34;https://github.com/felixonmars/dnsmasq-china-list&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;dnsmasq-china-list&lt;/a&gt; 是 Felix Yan 的项目，目前支持通过 Makefile 生成 SmartDNS 格式的配置文件，目前已经有人做好了现成的 Github Action 每天自动更新，直接下载就可以了，注意这些配置文件只会在国内 DNS 上游分组名称为&lt;code&gt;domestic&lt;/code&gt;时生效。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;cd /etc/smartdns&#xA;wget https://gcore.jsdelivr.net/gh/Olixn/china_list_for_smartdns@main/accelerated-domains.china.domain.smartdns.conf&#xA;wget https://gcore.jsdelivr.net/gh/Olixn/china_list_for_smartdns@main/apple.china.domain.smartdns.conf&#xA;wget https://gcore.jsdelivr.net/gh/Olixn/china_list_for_smartdns@main/chinalist.domain.smartdns.conf&#xA;wget https://gcore.jsdelivr.net/gh/Olixn/china_list_for_smartdns@main/google.china.domain.smartdns.conf&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;随后在 SmartDNS 中引入这些配置文件，将下面的内容粘贴到「自定义设置」栏的末尾。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;conf-file /etc/smartdns/accelerated-domains.china.domain.smartdns.conf&#xA;conf-file /etc/smartdns/apple.china.domain.smartdns.conf&#xA;conf-file /etc/smartdns/chinalist.domain.smartdns.conf&#xA;conf-file /etc/smartdns/google.china.domain.smartdns.conf&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/02/what-i-have-done-on-my-dns/image-2_hu_92b3727e74c74e1a.webp&#34; alt=&#34;引入配置文件&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;这样配置之后，国内域名的访问速度便不再会受到影响，访问国外域名时，也不会再有 DNS 泄露的问题了。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/02/what-i-have-done-on-my-dns/image-6_hu_d5c1130c9fef193d.webp&#34; alt=&#34;在无代理的情况下防止 DNS 泄露&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;h3 id=&#34;去广告&#34;&gt;去广告&#xA;&lt;/h3&gt;&lt;p&gt;DNS 层面的去广告效果有限，重点应该放在防止误杀上，所以不要用那臭名昭著&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;的号称自己是「致力于成为中文区命中率最高的广告过滤列表」的 Anti AD，而是使用一些别的规则。个人使用的规则是 &lt;a class=&#34;link&#34; href=&#34;https://github.com/217heidai/adblockfilters&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;217heidai/adblockfilters&lt;/a&gt;，这是一个已经整理好的聚合规则，直接拉取即可。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;wget -O /etc/smartdns/adblock.conf https://gcore.jsdelivr.net/gh/217heidai/adblockfilters@main/rules/adblocksmartdns.conf&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;最后引入配置文件：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;conf-file /etc/smartdns/adblock.conf&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;自动更新规则&#34;&gt;自动更新规则&#xA;&lt;/h3&gt;&lt;p&gt;设置如下 Crontab 即可在每天凌晨三点自动更新所有规则。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-crontab&#34;&gt;0 0 3 * * ?  wget -O /etc/smartdns/adblock.conf https://gcore.jsdelivr.net/gh/217heidai/adblockfilters@main/rules/adblocksmartdns.conf&#xA;0 0 3 * * ? wget -O /etc/smartdns/accelerated-domains.china.domain.smartdns.conf https://gcore.jsdelivr.net/gh/Olixn/china_list_for_smartdns@main/accelerated-domains.china.domain.smartdns.conf&#xA;0 0 3 * * ? wget -O /etc/smartdns/apple.china.domain.smartdns.conf https://gcore.jsdelivr.net/gh/Olixn/china_list_for_smartdns@main/apple.china.domain.smartdns.conf&#xA;0 0 3 * * ? wget -O /etc/smartdns/chinalist.domain.smartdns.conf https://gcore.jsdelivr.net/gh/Olixn/china_list_for_smartdns@main/chinalist.domain.smartdns.conf&#xA;0 0 3 * * ? wget -O /etc/smartdns/google.china.domain.smartdns.conf https://gcore.jsdelivr.net/gh/Olixn/china_list_for_smartdns@main/google.china.domain.smartdns.conf&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;旁路由可以用吗&#34;&gt;旁路由可以用吗&#xA;&lt;/h3&gt;&lt;p&gt;除非不使用 IPv6，否则最好还是将 OpenWRT 路由器作为主路由。在作为旁路由的情况下，某些系统（说的就是你，Windows）会不遵守旁路由广播的 IPv4 DNS，而执意使用运营商光猫广播的 IPv6 DNS 进行解析，我尝试修改过一些相关的注册表键值，但毫无作用。无奈只好让运营商上门将光猫改为桥接模式，并将 OpenWRT 用作主路由。&lt;/p&gt;&#xA;&lt;h2 id=&#34;覆写-clash-规则&#34;&gt;覆写 Clash 规则&#xA;&lt;/h2&gt;&lt;p&gt;这时如果你开着 Clash 去跑 DNS 泄露测试，大概率还是会看到你的 DNS 请求被发到了中国的 DNS 服务器上——几乎所有机场的默认配置都会使用腾讯、阿里等国内大厂的加密 DNS，虽然不至于明文泄露到运营商 DNS 而导致你接到反诈中心的电话，但总让人膈应 &lt;del&gt;（强迫症犯了）&lt;/del&gt;，更有甚者甚至完全没有相关配置。&lt;/p&gt;&#xA;&lt;p&gt;好在如果使用的是 Clash Verge Rev，那么可以很方便的覆写机场的配置，这里分享我的覆写配置，将其粘贴到全局扩展配置中即可覆写机场的配置。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;dns:&#xA;  enable: true&#xA;  ipv6: false&#xA;  prefer-h3: true&#xA;  use-system-hosts: true&#xA;  nameserver:&#xA;    - &#34;https://dns.cloudflare.com/dns-query&#34;&#xA;    - &#34;https://dns.sb/dns-query&#34;&#xA;    - &#34;https://dns.google/dns-query&#34;&#xA;    - &#34;https://public.dns.iij.jp/dns-query&#34;&#xA;    - &#34;https://jp.tiar.app/dns-query&#34;&#xA;    - &#34;https://jp.tiarap.org/dns-query&#34;&#xA;  nameserver-policy:&#xA;    &#34;geosite:cn&#34;:&#xA;      - system&#xA;  fallback:&#xA;    - &#34;tls://8.8.4.4&#34;&#xA;    - &#34;tls://1.1.1.1&#34;&#xA;  proxy-server-nameserver:&#xA;    - &#34;https://doh.pub/dns-query&#34;&#xA;    - &#34;https://223.5.5.5/dns-query&#34;&#xA;  direct-nameserver:&#xA;    - system&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/02/what-i-have-done-on-my-dns/image-3_hu_92a7537b0aa409b3.webp&#34; alt=&#34;如何修改全局扩展配置&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;h3 id=&#34;配置解析&#34;&gt;配置解析&#xA;&lt;/h3&gt;&lt;p&gt;根据 Mihomo 文档&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;，其 DNS 请求的流程如下：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-mermaid&#34;&gt;flowchart TD&#xA;  Start[客户端发起请求] --&gt; rule[匹配规则]&#xA;  rule --&gt;  Domain[匹配到基于域名的规则]&#xA;  rule --&gt; IP[匹配到基于 IP 的规则]&#xA;&#xA;  Domain --&gt; |域名匹配到直连规则|DNS&#xA;  IP --&gt; DNS[通过 Clash DNS 解析域名]&#xA;&#xA;&#xA;  Domain --&gt; |域名匹配到代理规则|Remote[通过代理服务器解析域名并建立连接]&#xA;&#xA;  Cache --&gt; |Redir-host/FakeIP-Direct 未命中|NS[匹配 nameserver-policy 并查询 ]&#xA;  Cache --&gt; |Cache 命中|Get&#xA;  Cache --&gt; |FakeIP 未命中,代理域名|Remote&#xA;&#xA;  NS --&gt; |匹配成功| Get[将查询到的 IP 用于匹配 IP 规则]&#xA;  NS --&gt; |没匹配到| NF[nameserver/fallback 并发查询]&#xA;&#xA;  NF --&gt; Get[查询得到 IP]&#xA;  Get --&gt; |缓存 DNS 结果|Cache[(Cache)]&#xA;  Get --&gt; S[通过 IP 直接/通过代理建立连接]&#xA;&#xA;  DNS --&gt; Redir-host/FakeIP&#xA;  Redir-host/FakeIP --&gt; |查询 DNS 缓存|Cache&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;其中&lt;code&gt;nameserver-policy&lt;/code&gt;的优先级高于&lt;code&gt;nameserver&lt;/code&gt;和&lt;code&gt;fallback&lt;/code&gt;，指定&lt;code&gt;geosite:cn&lt;/code&gt;中的网站以系统 DNS 查询，也就是使用之前配置好的 SmartDNS 查询，可以享受到局域网级别的响应速度和自带测速的 CDN 选择。而不在&lt;code&gt;geosite:cn&lt;/code&gt;中的网站则使用&lt;code&gt;nameserver/fallback&lt;/code&gt;查询，使用国外加密上游就可以确保解析结果的可信并防止 DNS 泄露。&lt;/p&gt;&#xA;&lt;h3 id=&#34;替换-geoip-和-geosite-数据库&#34;&gt;替换 geoip 和 geosite 数据库&#xA;&lt;/h3&gt;&lt;p&gt;Mihomo 默认使用 V2Ray 官方的 geoip 和 geosite 数据库，这个数据库其实不太全，而且更新的频率也很慢，所以我还是用 &lt;a class=&#34;link&#34; href=&#34;https://github.com/Loyalsoldier/v2ray-rules-dat&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;Loyalsoldier/v2ray-rules-dat&lt;/a&gt; 这个「加强版」数据库替换了原来的数据库，只需下载&lt;code&gt;geoip.dat&lt;/code&gt;和&lt;code&gt;geosite.dat&lt;/code&gt;以后丢进内核目录即可。当然，也可以新增几行覆写配置以实现自动/一键更新：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;geodata-mode: true&#xA;geox-url:&#xA;  geoip: &#34;https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat&#34;&#xA;  geosite: &#34;https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat&#34;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;clash-meta-for-android&#34;&gt;Clash Meta For Android&#xA;&lt;/h3&gt;&lt;p&gt;对于手机上的 Clash Meta For Android（以下简称 Clash），覆写设置时需要更加考虑手机的实际情况。&lt;/p&gt;&#xA;&lt;p&gt;首先手机上的 Clash 覆写设置选项就不如桌面端的 Clash Verge Rev 这么灵活，很多键值都不能覆写。另外，手机不像电脑，需要经常切换网络环境，这就导致系统 DNS 也会换得很频繁，经测试，在这种情况下，每次更换网络环境都需要开关一次 Clash 才能让其更新&lt;code&gt;system&lt;/code&gt;所代表的 DNS。于是我们只能退而求其次，固定一个国内加密 DNS 作为国内查询。&lt;/p&gt;&#xA;&lt;p&gt;进入 Clash -&amp;gt;「设置」-&amp;gt;「覆写」以下是所有覆写设置：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;「DNS」-&amp;gt;「策略」：强制启用&lt;/li&gt;&#xA;&lt;li&gt;「DNS」-&amp;gt;「Prefer h3」：已启用&lt;/li&gt;&#xA;&lt;li&gt;「DNS」-&amp;gt;「Name Server」：添加两个国外加密 DNS&lt;/li&gt;&#xA;&lt;li&gt;「DNS」-&amp;gt;「Name Server 策略」：在「键」一栏中填写&lt;code&gt;geosite:cn&lt;/code&gt;，值一栏填写&lt;code&gt;https://doh.pub/dns-query&lt;/code&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;提前从 Github 上下载&lt;code&gt;geoip.dat&lt;/code&gt;和&lt;code&gt;geosite.dat&lt;/code&gt;，最后进入 Clash -&amp;gt;「设置」-&amp;gt;「Meta Features」，分别用「导入 GeoIP 数据库」和「导入 GeoSite 数据库」功能导入它们。&lt;/p&gt;&#xA;&lt;h2 id=&#34;完成&#34;&gt;完成&#xA;&lt;/h2&gt;&lt;p&gt;最后再打开一个 &lt;a class=&#34;link&#34; href=&#34;https://browserleaks.com/dns&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;DNS 泄露测试&lt;/a&gt;，看一看问题是否解决，如果不再出现国内的 DNS，说明配置正确，好好享受 SmartDNS 和 Clash 带来的网络优化和防泄漏吧~&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/02/what-i-have-done-on-my-dns/image-4_hu_fb37d196f4006eaa.webp&#34; alt=&#34;DNS 不再泄露&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;参考自: &lt;a class=&#34;link&#34; href=&#34;https://blog.lololowe.com/posts/8f1e/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;https://blog.lololowe.com/posts/8f1e/&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:2&#34;&gt;&#xA;&lt;p&gt;SmartDNS 在设计时就考虑到了延迟问题，具体机制可以参考「&lt;a class=&#34;link&#34; href=&#34;https://blog.skk.moe/post/i-have-my-unique-dns-setup/#%E4%B8%AD%E5%9C%BA%E4%BC%91%E6%81%AF%EF%BC%9ASmartDNS-%E6%98%AF%E5%A6%82%E4%BD%95%E9%81%BF%E5%85%8D%E5%9B%A0%E6%B5%8B%E9%80%9F%E5%AF%BC%E8%87%B4-DNS-%E8%A7%A3%E6%9E%90%E8%BF%87%E6%85%A2%E7%9A%84&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;中场休息：SmartDNS 是如何避免因测速导致 DNS 解析过慢的&lt;/a&gt;」。&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:3&#34;&gt;&#xA;&lt;p&gt;有人整理了 &lt;a class=&#34;link&#34; href=&#34;https://github.com/Mosney/anti-anti-AD&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;Anti AD 的光荣事迹&lt;/a&gt;。&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:4&#34;&gt;&#xA;&lt;p&gt;引用自: &lt;a class=&#34;link&#34; href=&#34;https://wiki.metacubex.one/config/dns/diagram/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;https://wiki.metacubex.one/config/dns/diagram/&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/div&gt;&#xA;</description>
        </item><item>
            <title>Windows Hiccup 小日常：处理时间同步服务的问题</title>
            <link>https://blog.l3zc.com/2024/11/windows-ntp-hiccup/</link>
            <pubDate>Mon, 18 Nov 2024 18:03:47 +0800</pubDate>
            <guid>https://blog.l3zc.com/2024/11/windows-ntp-hiccup/</guid>
            <description>&lt;img src=&#34;https://blog.l3zc.com/2024/11/windows-ntp-hiccup/cover_hu_e196169a0c972a34.webp&#34; alt=&#34;Featured image of post Windows Hiccup 小日常：处理时间同步服务的问题&#34; /&gt;&lt;p&gt;不知出了什么问题，我的电脑的时间从来不会自动和 NTP 服务器同步，虽然短期内并不会对使用造成什么影响，但倘若放任不管，时钟的偏移量太多，则可能会造成 SSL 和 TOTP 等依赖时间戳的应用出现问题，再者，手动按同步键也很不方便，今天我决定必须要解决这个问题。&lt;/p&gt;&#xA;&lt;h2 id=&#34;specialpollinterval&#34;&gt;SpecialPollInterval&#xA;&lt;/h2&gt;&lt;p&gt;在网上搜索一番之后，我发现有人提到&lt;code&gt;SpecialPollInterval&lt;/code&gt;这个键值，其具体位置是&lt;code&gt;HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W32Time\TimeProviders\NtpClient&lt;/code&gt;，微软官方文档对其的描述如下：&lt;/p&gt;&#xA;&#xA;    &lt;blockquote&gt;&#xA;        &lt;p&gt;This entry specifies the special poll interval in seconds for manual peers. When the SpecialInterval 0x1 flag is enabled, W32Time uses this poll interval instead of a poll interval determine by the operating system. The default value on domain members is 3,600. The default value on stand-alone clients and servers is 604,800.&lt;/p&gt;&#xA;&#xA;    &lt;/blockquote&gt;&#xA;&lt;p&gt;对于一般客户端，这个键值的默认值是&lt;code&gt;604800&lt;/code&gt;，也就是一个星期同步一次，这个间隔显然太久，于是我将其设置成了&lt;code&gt;3600&lt;/code&gt;令其一小时同步一次。&lt;/p&gt;&#xA;&lt;h2 id=&#34;w32tm&#34;&gt;w32tm&#xA;&lt;/h2&gt;&lt;p&gt;几天后，当我再次检查时间时，却发现时间仍然没有自动同步，这一次的偏移量已经达到了 15.8 秒。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2024/11/windows-ntp-hiccup/Screenshot20241117-1915_hu_13e6b8a28a875d07.webp&#34; alt=&#34;系统时间快了15.8秒&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;于是查询「SpecialPollInterval」想要知道其具体的作用和未及预期目的的原因，却发现了微软的一篇&lt;a class=&#34;link&#34; href=&#34;https://learn.microsoft.com/zh-cn/troubleshoot/windows-server/active-directory/specialpollinterval-polling-interval-time-service-not-correct&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;疑难解答&lt;/a&gt;。这篇疑难解答指出：「每次客户端轮询时间样本到 NTP 服务器时，NTP 客户端都会进入 SPIKE 状态。 时间服务管理其内部状态，如果客户端进入 SPIKE 状态，则客户端不会同步其时间。」&lt;/p&gt;&#xA;&lt;p&gt;为了解决这个问题，需要将 Windows 时间配置为使用 MinPollInterval/MaxPollInterval 作为轮询间隔。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-powershell&#34;&gt;w32tm /config /update /manualpeerlist:cn.pool.ntp.org /syncfromflags:MANUAL&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;服务尚未启动&#34;&gt;服务尚未启动&#xA;&lt;/h2&gt;&lt;p&gt;执行这一行配置时出现了「服务尚未启动」的提示，这说明用于时间同步的服务根本没有启动。如此基础的服务竟然不会自动开机启动，我也不知道这是 Windows 的 Bug 还是我后期使用无意间制造的问题，不管怎样，首先启动这个服务：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-powershell&#34;&gt;net start w32time&#xA;w32tm /register   # 注册一些基本的注册表键值和相关服务&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;随后 Win + R 运行&lt;code&gt;services.msc&lt;/code&gt;打开服务管理面板，找到「Windows Time」服务，将其启动类型设置为「自动」，即可让其开机自动启动。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2024/11/windows-ntp-hiccup/image_hu_521a92b1a7f11576.webp&#34; alt=&#34;将 W32Time 的启动类型设置为自动&#34; /&gt;&#xA; &#xA;&lt;img src=&#34;https://blog.l3zc.com/2024/11/windows-ntp-hiccup/image-1_hu_36c2133f76139721.webp&#34; alt=&#34;出现「下次同步」字样即说明成功&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;再次重新启动电脑，这次打开「设置」&amp;ndash;&amp;gt;「时间和语言」&amp;ndash;&amp;gt;「日期和时间」&amp;ndash;&amp;gt;「附加时钟」，切换到「Internet 时间」选项卡，如果出现了「下次同步」的字样，说明这套系统终于是正常运作了。&lt;/p&gt;&#xA;</description>
        </item><item>
            <title>OBS 多路推流折腾记：那些本不必要的麻烦</title>
            <link>https://blog.l3zc.com/2024/09/my-multi-streaming-setup/</link>
            <pubDate>Wed, 18 Sep 2024 14:16:51 +0800</pubDate>
            <guid>https://blog.l3zc.com/2024/09/my-multi-streaming-setup/</guid>
            <description>&lt;img src=&#34;https://blog.l3zc.com/2024/09/my-multi-streaming-setup/cover_hu_891493d684eb454d.webp&#34; alt=&#34;Featured image of post OBS 多路推流折腾记：那些本不必要的麻烦&#34; /&gt;&lt;p&gt;长话短说，最近我开始直播我的游戏实况，我并不指望有多少人看，Just, Why not? 再者每个直播平台都会有直播回放。相当于免费保存我每一次游戏的录像，何乐而不为？&lt;/p&gt;&#xA;&lt;p&gt;扯远了，如你所见，近期我有了多平台同时直播的需求，然而 OBS 本身显然不支持同时推流，同时也因为不支持设置代理的特点和众所周知的原因，无法推流到 twitch.tv，于是我开始寻找解决方案。&lt;/p&gt;&#xA;&lt;h2 id=&#34;明确需求&#34;&gt;明确需求&#xA;&lt;/h2&gt;&lt;ul&gt;&#xA;&lt;li&gt;多路平台同时直播&lt;/li&gt;&#xA;&lt;li&gt;最好对性能影响小&lt;/li&gt;&#xA;&lt;li&gt;其中一路需要推流需要代理&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;多路推流&#34;&gt;多路推流&#xA;&lt;/h2&gt;&lt;p&gt;多路推流有现成的插件可用：&lt;a class=&#34;link&#34; href=&#34;https://github.com/sorayuki/obs-multi-rtmp&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;Github&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;在 Release 页面下载&lt;code&gt;.exe&lt;/code&gt;安装包，安装完成后记得在「停靠窗口」菜单里勾选「多路推流」。这时候默认会弹出一个独立窗口，可以趁这个机会顺便调整一下 OBS 的布局。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2024/09/my-multi-streaming-setup/image_hu_b8b674a93b9f86bc.webp&#34; alt=&#34;启用多路推流面板&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;插件的设置也没什么好说的，用在原版 OBS 的填法在这个插件里如法炮制即可。&lt;/p&gt;&#xA;&lt;h3 id=&#34;bilibili-直播姬&#34;&gt;Bilibili 直播姬&#xA;&lt;/h3&gt;&lt;p&gt;莫名其妙，B 站小于 50 粉丝的 Up 主只能使用直播姬开播，其他国内平台做出类似规定者大概也不在少数，启用第三方推流模式之后，直播面板会显示推流的配置。此时的不需要按照直播姬给出的推流地址进行配置，因为直播姬莫名其妙的给了一个每次都会变的内网 IP，直接以&lt;code&gt;127.0.0.1&lt;/code&gt;或者&lt;code&gt;localhost&lt;/code&gt;替换即可。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2024/09/my-multi-streaming-setup/image-2_hu_71c45766ee560a9.webp&#34; alt=&#34;Bilibili 直播姬的第三方推流模式&#34; /&gt;&#xA; &#xA;&lt;img src=&#34;https://blog.l3zc.com/2024/09/my-multi-streaming-setup/image-1_hu_17dc4c09eb0ae0af.webp&#34; alt=&#34;插件的设置&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;h2 id=&#34;推流到-twitch&#34;&gt;推流到 Twitch&#xA;&lt;/h2&gt;&lt;p&gt;推流到 Twitch 则会因为 GFW 多出很多不必要的麻烦。&lt;/p&gt;&#xA;&lt;p&gt;OBS 本身并不支持设置代理，起初我打算用 Clash 的 TAP 模式强制代理 OBS 的流量，而 Clash 的 TAP 并不监听 rtmp 流量，宣告失败。我决定直接架设一个转发服务器，这个服务器需要既可以与 Twitch 通信，又可以不受 GFW 的干扰。&lt;/p&gt;&#xA;&lt;p&gt;在网上查到一些信息，Caddy 似乎不支持 RTMP 协议，而 &lt;code&gt;nginx-rtmp&lt;/code&gt; 插件则专门为 RTMP 协议设计。这几乎是唯一的选择。&lt;/p&gt;&#xA;&lt;p&gt;部署的前提：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;熟悉基本的 Linux 命令行操作。&lt;/li&gt;&#xA;&lt;li&gt;服务器已经安装好&lt;code&gt;docker&lt;/code&gt;和&lt;code&gt;docker compose&lt;/code&gt;插件。&lt;/li&gt;&#xA;&lt;li&gt;有合适的命令行文本编辑器，比如&lt;code&gt;nvim&lt;/code&gt;或者绝大部分 distro 自带的&lt;code&gt;nano&lt;/code&gt;。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;新建目录：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;mkdir nginx-rtmp &amp;&amp; cd nginx-rtmp&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;创建&lt;code&gt;docker-compose.yml&lt;/code&gt;：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;services:&#xA;  nginx-rtmp:&#xA;    image: tiangolo/nginx-rtmp:latest&#xA;    container_name: nginx_rtmp&#xA;    ports:&#xA;      - &#34;1935:1935&#34; # RTMP port&#xA;    volumes:&#xA;      - ./nginx.conf:/etc/nginx/nginx.conf&#xA;    restart: unless-stopped&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;创建&lt;code&gt;nginx.conf&lt;/code&gt;：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-nginx&#34;&gt;worker_processes  auto;&#xA;&#xA;events {&#xA;    worker_connections  1024;&#xA;}&#xA;&#xA;rtmp {&#xA;    server {&#xA;        listen 1935;&#xA;        chunk_size 4096;&#xA;&#xA;        application live {&#xA;            live on;&#xA;            push rtmp://live.twitch.tv/app/{Twitch 主直播密钥};&#xA;        }&#xA;    }&#xA;}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;启动编排：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;docker compose up -d&#xA;# 老版本 compose 插件则是 docker-compose up&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;当然，别忘了开放防火墙端口。我的防火墙参考了 &lt;a class=&#34;link&#34; href=&#34;https://github.com/chaifeng/ufw-docker#solving-ufw-and-docker-issues&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;To Fix The Docker and UFW Security Flaw Without Disabling Iptables&lt;/a&gt; 一文中提到的配置，相应地，开放端口的命令如下：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;ufw route allow proto tcp from any to any port 1935&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;配置完成后，OBS 内的推流目标地址填入&lt;code&gt;rtmp://服务器ip/live&lt;/code&gt;，推流码填写任意值均可。设置完成后即可开始推流。&lt;/p&gt;&#xA;&lt;h3 id=&#34;netch&#34;&gt;Netch&#xA;&lt;/h3&gt;&lt;p&gt;如果没有服务器，亦可以使用 Netch 代理 OBS 进程进行推流。&lt;/p&gt;&#xA;&lt;p&gt;首先下载 Netch 1.9.2 版本，注意一定要是 1.9.2 版本。添加一个进程模式，将 OBS 文件夹加入，随后启用该进程模式的代理即可。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2024/09/my-multi-streaming-setup/image-3_hu_fab77139607785da.webp&#34; alt=&#34;创建一个进程模式&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;h2 id=&#34;尾声&#34;&gt;尾声&#xA;&lt;/h2&gt;&lt;p&gt;这次为了在同时在 Twitch 和 B 站上推流，遇到了很多原本不必要的麻烦。我可以怪 OBS 没有内置代理功能，也可以怪 Clash Verge 等代理工具不支持 RTMP，转来转去，最终要怪的，还得是 GFW。虽然最后的结果还算令人满意，仅仅为了推流而折腾这些东西的体验实在是称不上良好。也许我今后我应该去研究修改 Mihomo 内核，让其支持 RTMP 协议，亦或是自己写一套独立的实现单独转发 OBS 的流量，不论如何，在这片土地上，想要畅快的直播，道阻且长。&lt;/p&gt;&#xA;&lt;p&gt;最后，欢迎关注我的 Twitch 频道: &lt;a class=&#34;link&#34; href=&#34;https://www.twitch.tv/powerfullz233&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;https://www.twitch.tv/powerfullz233&lt;/a&gt;&lt;/p&gt;&#xA;</description>
        </item><item>
            <title>docker-mailserver 邮件服务器搭建记录</title>
            <link>https://blog.l3zc.com/2024/09/docker-mailserver-deployment-recap/</link>
            <pubDate>Fri, 06 Sep 2024 11:57:18 +0800</pubDate>
            <guid>https://blog.l3zc.com/2024/09/docker-mailserver-deployment-recap/</guid>
            <description>&lt;img src=&#34;https://blog.l3zc.com/2024/09/docker-mailserver-deployment-recap/docker-mailserver_hu_a649605529f354c4.webp&#34; alt=&#34;Featured image of post docker-mailserver 邮件服务器搭建记录&#34; /&gt;&lt;p&gt;某天早上醒来，发现收件箱里多了封邮件，通知我的邮箱在韩国登录，当时认为只是偶尔的 IP 误判，也就没有多想。可接下来几天，时不时又有新的邮件进来，而且每次 IP 都不一样，这下可就有些问题了，再不采取行动，怕不是邮箱真的要被拿去群发邮件。于是赶紧删掉了域名的 MX、SPF、DMARC 记录，寻找新的解决方案。&lt;/p&gt;&#xA;&lt;p&gt;我原本只是想改改密码，结果呢，腾讯企业邮箱改密码的入口真的巨能藏，到处都找不到。算了，迁移，何必受这气。&lt;/p&gt;&#xA;&lt;p&gt;于是就有了这篇文章，从最开始的开放防火墙端口开始，到最后的优化送达率，我将分享我搭建 docker-mailserver 邮件服务器的经验，希望能帮你少踩些坑。&lt;/p&gt;&#xA;&lt;h2 id=&#34;搭建-docker-mailserver-邮件服务器前的准备&#34;&gt;搭建 docker-mailserver 邮件服务器前的准备&#xA;&lt;/h2&gt;&lt;h3 id=&#34;防火墙的处理&#34;&gt;防火墙的处理&#xA;&lt;/h3&gt;&lt;p&gt;首先先开放防火墙的邮件服务常用端口，分别是&lt;code&gt;25&lt;/code&gt;、&lt;code&gt;143&lt;/code&gt;、&lt;code&gt;465&lt;/code&gt;、&lt;code&gt;587&lt;/code&gt;、&lt;code&gt;993&lt;/code&gt;，各分支开放端口的操作不同，请自行谷歌或询问 AI，以 Ubuntu 使用的&lt;code&gt;ufw&lt;/code&gt;为例：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;ufw allow 25&#xA;ufw allow 143&#xA;ufw allow 465&#xA;ufw allow 587&#xA;ufw allow 993&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;我的防火墙比较特殊，用到了&lt;code&gt;ufw&lt;/code&gt;和 &lt;a class=&#34;link&#34; href=&#34;https://github.com/chaifeng/ufw-docker#solving-ufw-and-docker-issues&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;To Fix The Docker and UFW Security Flaw Without Disabling Iptables&lt;/a&gt; 一文中提到的配置。所以当我想要把 Docker 容器的端口暴露在外时需要做出额外的配置。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;# 开放邮件服务器所需的端口&#xA;ufw route allow proto tcp from any to any port 25&#xA;ufw route allow proto tcp from any to any port 143&#xA;ufw route allow proto tcp from any to any port 465&#xA;ufw route allow proto tcp from any to any port 587&#xA;ufw route allow proto tcp from any to any port 993&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;验证你的服务器是否具备搭建条件&#34;&gt;验证你的服务器是否具备搭建条件&#xA;&lt;/h3&gt;&lt;p&gt;很多服务器，尤其是各种廉价的 VPS，并不适合搭建 docker-mailserver，各大邮件服务商为了过滤垃圾邮件，索性将所有曾经有过 Spam 行为的 IP 全部列入黑名单。如果你的 IP 被某个 Spammer 使用过，而恰好又被你的服务器提供商分配给了你，而你折腾了几个小时的邮件服务器却愣是收不到邮件，最后发现竟是 IP 被列入黑名单的原因，我不知道你会作何感想。&lt;/p&gt;&#xA;&lt;p&gt;有的服务器提供商的 IP 乍一看确实不在邮件黑名单中，但实际上搭建好之后还是无法发送邮件，为什么？因为服务器提供商担心 IP 被滥用，索性将邮件服务的端口全都封锁了。&lt;/p&gt;&#xA;&lt;p&gt;使用如下脚本检测你的服务器是否具备搭建邮件服务器的条件：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;bash &lt;(curl -sL IP.Check.Place)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2024/09/docker-mailserver-deployment-recap/image-3_hu_6dacc5e9abb4b1ca.webp&#34; alt=&#34;不能搭建邮件服务器&#34; /&gt;&#xA; &#xA;&lt;img src=&#34;https://blog.l3zc.com/2024/09/docker-mailserver-deployment-recap/image-4_hu_fb1b1c9dd78cde41.webp&#34; alt=&#34;可以搭建邮件服务器&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;如果在这一步发现你的服务器不适合搭建甚至不能搭建&lt;code&gt;docker-mailserver&lt;/code&gt;，那就趁早打住吧，想办法换一个更干净、限制更少的 IP 再说。&lt;/p&gt;&#xA;&lt;h2 id=&#34;搭建过程&#34;&gt;搭建过程&#xA;&lt;/h2&gt;&lt;h3 id=&#34;拉取配置文件&#34;&gt;拉取配置文件&#xA;&lt;/h3&gt;&lt;p&gt;docker-mailserver 需要配置的配置文件有两个，&lt;code&gt;compose.yaml&lt;/code&gt;和&lt;code&gt;mailserver.env&lt;/code&gt;，分别拉取配置文件：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;mkdir mailserver &amp;&amp; cd mailserver&#xA;sudo apt install wget&#xA;wget -O compose.yaml &#34;https://raw.githubusercontent.com/docker-mailserver/docker-mailserver/master/compose.yaml&#34;&#xA;wget -O mailserver.env &#34;https://github.com/docker-mailserver/docker-mailserver/blob/master/mailserver.env&#34;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;根据需求，可以修改不同的配置，下面是我修改的部分：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;POSTMASTER_ADDRESS=webmaster@l3zc.com&#xA;TZ=Asia/Shanghai&#xA;SSL_TYPE=manual&#xA;SSL_CERT_PATH=/certs/cert.crt&#xA;SSL_KEY_PATH=/certs/cert.key&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;用-caddy-准备和自动维护-tls-证书&#34;&gt;用 Caddy 准备和自动维护 TLS 证书&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;&#xA;&lt;/h3&gt;&lt;p&gt;因为我已经在使用 Caddy，所以我这次就打算直接使用 Caddy 来自动维护证书。根据项目的官方文档，我只需要在 Caddyfile 里新增一个子域名，Caddy 就可以自动帮我维护证书。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-caddyfile&#34;&gt;mail.example.com {&#xA;  tls internal {&#xA;    # ?? 这不是直接生成的内部证书么&#xA;    key_type rsa2048&#xA;  }&#xA;&#xA;  # Optional, can be useful for troubleshooting&#xA;  # connection to Caddy with correct certificate:&#xA;  respond &#34;Hello DMS&#34;&#xA;}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;当我更新完 DNS 记录，实际操作起来发现，我的情况比较特殊：我这台服务器上并没有用到 Caddy 的自动申请证书功能，而是使用了15年有效期的泛域名 Cloudflare Origin Certificate。即使我不为这个子域名指定证书文件，Caddy 也会在加载这张泛域名证书后认为无需再申请新的证书。这就很尴尬了，用 Caddy 的时候 Cloudflare Origin Certificate 和邮件服务器只能二选一。只好安装了&lt;code&gt;dns.providers.cloudflare&lt;/code&gt;，并放弃使用 Cloudflare Origin Certificate，转而让 Caddy 自动为每个子域名申请和维护 TLS/SSL 证书，反正都是自动的，眼不见心不烦。&lt;/p&gt;&#xA;&lt;p&gt;安装&lt;code&gt;dns.providers.cloudflare&lt;/code&gt;后的全局配置：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-caddyfile&#34;&gt;{&#xA;        acme_dns cloudflare {API_KEY}&#xA;}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;最后把 Caddy 的证书存储位置映射到 docker-mailserver 容器内。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;volumes:&#xA;  - /home/user/.local/share/caddy/certificates/acme-v02.api.letsencrypt.org-directory/yourdomain.tld/:/certs/&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;如果 Caddy 以 root 用户运行，则证书的位置应当为&lt;code&gt;/root/.local/share/caddy/certificates/acme-v02.api.letsencrypt.org-directory/yourdomain.tld/&lt;/code&gt;，做出相应的修改即可。&lt;/p&gt;&#xA;&lt;p&gt;然后就是之前提到过的配置，我将证书存储位置映射到了容器内的&lt;code&gt;/certs/&lt;/code&gt;目录，所以配置为：&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;SSL_CERT_PATH=/certs/cert.crt&#xA;SSL_KEY_PATH=/certs/cert.key&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;首次启动容器&#34;&gt;首次启动容器&#xA;&lt;/h3&gt;&lt;p&gt;首次启动容器需要立即（120 秒内）创建新的 Postmaster 账号。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;docker compose up -d&#xA;docker exec -it &lt;CONTAINER NAME&gt; setup email add &lt;postmaster@yourdomain.tld&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;优化送达率&#34;&gt;优化送达率&#xA;&lt;/h2&gt;&lt;h3 id=&#34;dns-记录&#34;&gt;DNS 记录&#xA;&lt;/h3&gt;&lt;ul&gt;&#xA;&lt;li&gt;一条 A 记录，名称随意（假设为&lt;code&gt;mail&lt;/code&gt;），指向服务器 IP（Cloudflare 需使用「仅 DNS」）&lt;/li&gt;&#xA;&lt;li&gt;一条 MX 记录，Apex 域（@），指向&lt;code&gt;mail.yourdomain.tld&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;一条 TXT 记录（SPF），Apex 域（@），可以在&lt;a class=&#34;link&#34; href=&#34;https://mxtoolbox.com/SPFRecordGenerator.aspx&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;这里&lt;/a&gt;生成(可选)。&lt;/li&gt;&#xA;&lt;li&gt;一条 TXT 记录（DMARC），名称&lt;code&gt;_dmarc&lt;/code&gt;，可以在&lt;a class=&#34;link&#34; href=&#34;https://mxtoolbox.com/DMARCRecordGenerator.aspx&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;这里&lt;/a&gt;生成（可选）。&lt;/li&gt;&#xA;&lt;li&gt;一条 TXT 记录（DKIM），名称&lt;code&gt;mail._domainkey&lt;/code&gt;，具体配置在后文（可选）。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;dkim-spf-和-dmarc&#34;&gt;DKIM, SPF 和 DMARC&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;&#xA;&lt;/h3&gt;&lt;p&gt;什么是 DKIM，SPF 和 DMARC？简单来说，DKIM 是一个数字签名机制；SPF 类似于「员工名单」，记录所有合法的发件服务器；DMARC 则用于指示收件方的服务器如何处理未通过 DKIM 和 SPF 验证的邮件。&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;&#xA;&lt;p&gt;首先设置 DKIM，在服务器上生成公私钥对。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;docker exec -it &lt;CONTAINER NAME&gt; setup config dkim&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;生成后的公私钥对可以在映射的容器目录&lt;code&gt;./docker-data/dms/config/opendkim/keys/&lt;/code&gt;下找到。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2024/09/docker-mailserver-deployment-recap/image_hu_1f64fffc01f8db13.webp&#34; alt=&#34;mail.txt 便是公钥的 DNS Zone file&#34; /&gt;&#xA; &#xA;&lt;img src=&#34;https://blog.l3zc.com/2024/09/docker-mailserver-deployment-recap/image-1_hu_d6c86630e3947c78.webp&#34; alt=&#34;Cloudflare 的导入入口&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;下载 mail.txt（复制其内所有内容粘贴到本地文件也可以），将其导入到 Cloudflare Dashboard 即可完成 DKIM 设置。需要注意配置完成 DKIM 后需要重启容器才能生效。&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;docker compose up -d --force-recreate&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;前面提到 SPF 相当于一个「员工名单」，既然我合法的发件服务器只有一台，那么 DNS 填写如下设置即可。&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;&#34;v=spf1 mx ~all&#34;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;再就是 DMARC，可以使用前面提到的模板生成，这里不再赘述。&lt;/p&gt;&#xA;&lt;p&gt;如果你的域名服务商拥有自己的设置向导（例如 Cloudflare），亦可以使用它们简化设置流程。&lt;/p&gt;&#xA;&lt;h2 id=&#34;客户端设置&#34;&gt;客户端设置&#xA;&lt;/h2&gt;&lt;p&gt;客户端设置相对就很简单了，IMAP 和 SMTP 服务器都是&lt;code&gt;mail.yourdomain.ltd&lt;/code&gt;，开启 SSL 后端口分别是 465 和 993，登录即可。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2024/09/docker-mailserver-deployment-recap/image-2_hu_51756253bf099359.webp&#34; alt=&#34;设置示例&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;h2 id=&#34;善用-mailtester&#34;&gt;善用 MailTester&#xA;&lt;/h2&gt;&lt;p&gt;至此我们已经完成了搭建 docker-mailserver 邮件服务器的整个过程，在开始发送邮件之前，我们可以使用 &lt;a class=&#34;link&#34; href=&#34;https://www.mail-tester.com/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;MailTester&lt;/a&gt; 测试我们的配置是否正确。如果测试结果是 10/10，那么我们的 docker-mailserver 邮件服务器应该会拥有漂亮的送达率，好好享受吧。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2024/09/docker-mailserver-deployment-recap/image-5_hu_6de856f3e3b52ca0.webp&#34; alt=&#34;完美的评分&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;如果不是 10/10，也不要着急，认真查看扣分的原因，对症下药，一项一项解决即可。&lt;/p&gt;&#xA;&lt;h2 id=&#34;todo-list&#34;&gt;TODO-List&#xA;&lt;/h2&gt;&lt;ul&gt;&#xA;&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; 配套 WebUI&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;参考: &lt;a class=&#34;link&#34; href=&#34;https://docker-mailserver.github.io/docker-mailserver/latest/config/security/ssl/#caddy&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;https://docker-mailserver.github.io/docker-mailserver/latest/config/security/ssl/#caddy&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:2&#34;&gt;&#xA;&lt;p&gt;参考: &lt;a class=&#34;link&#34; href=&#34;https://docker-mailserver.github.io/docker-mailserver/latest/config/best-practices/dkim_dmarc_spf/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;https://docker-mailserver.github.io/docker-mailserver/latest/config/best-practices/dkim_dmarc_spf/&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:3&#34;&gt;&#xA;&lt;p&gt;更加具体的介绍: &lt;a class=&#34;link&#34; href=&#34;https://www.cloudflare.com/learning/email-security/dmarc-dkim-spf/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;https://www.cloudflare.com/learning/email-security/dmarc-dkim-spf/&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:4&#34;&gt;&#xA;&lt;p&gt;在&lt;a class=&#34;link&#34; href=&#34;https://docker-mailserver.github.io/docker-mailserver/latest/config/best-practices/dkim_dmarc_spf/#generating-keys&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;官方文档&lt;/a&gt;中有提及。&amp;#160;&lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/div&gt;&#xA;</description>
        </item></channel>
</rss>
