<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Sarv&#39;s Blog</title>
    <link>https://sarv.blog/</link>
    <description>Recent content on Sarv&#39;s Blog</description>
    <generator>Hugo -- 0.144.2</generator>
    <language>zh-CN</language>
    <lastBuildDate>Tue, 19 Aug 2025 00:00:00 +0800</lastBuildDate>
    <atom:link href="https://sarv.blog/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>WXAM（WXGF）图片格式解析</title>
      <link>https://sarv.blog/posts/wxam/</link>
      <pubDate>Tue, 19 Aug 2025 00:00:00 +0800</pubDate>
      <guid>https://sarv.blog/posts/wxam/</guid>
      <description>&lt;h3 id=&#34;概述&#34;&gt;概述&lt;/h3&gt;
&lt;p&gt;微信 4 的正式版本更新了图片加密算法，从测试版的通用 AES 密钥调整为每台设备不同的  AES 密钥。&lt;br&gt;
老样子，从内存数据中将 AES 密钥扒拉出来，然后就遇到了 WXAM 图片数据。&lt;br&gt;
经过不少时间的分析和处理，chatlog 目前已经能够初步解析 WXAM 图片了，写篇博客记录一下。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h3 id="概述">概述</h3>
<p>微信 4 的正式版本更新了图片加密算法，从测试版的通用 AES 密钥调整为每台设备不同的  AES 密钥。<br>
老样子，从内存数据中将 AES 密钥扒拉出来，然后就遇到了 WXAM 图片数据。<br>
经过不少时间的分析和处理，chatlog 目前已经能够初步解析 WXAM 图片了，写篇博客记录一下。</p>
<p>相关代码：<a href="https://github.com/sjzar/chatlog/blob/a16b689/pkg/util/dat2img/wxgf.go">https://github.com/sjzar/chatlog/blob/a16b689/pkg/util/dat2img/wxgf.go</a></p>
<h3 id="相关资料">相关资料</h3>
<p>由于是内部格式，网上能找到的资料少的可怜，唯一的官方信息是18 年腾讯工程团队发布的文章：<a href="https://cloud.tencent.com/developer/article/1028362">如何节省 1TB 图片带宽？解密极致图像压缩-腾讯云开发者社区-腾讯云</a><br>
明确了几点信息，WXAM 格式的目标是极致压缩比，并且应该是没有加密逻辑。</p>
<p>其余资料：</p>
<ul>
<li><a href="https://ppwwyyxx.com/blog/2025/wechat-dump-10-years/#WXGF-%E8%A7%A3%E7%A0%81">写在 wechat-dump 项目的第十年 - Yuxin&rsquo;s Blog</a>  Yuxin Wu 大佬尝试直接使用 Android 微信的 <code>libwechatcommon.so</code> 进行解码。</li>
<li><a href="https://github.com/recarto404/WxDatDecrypt">GitHub - recarto404/WxDatDecrypt: 解密与查看微信 4.1 的图片，将微信缓存的 dat 文件解密为原始图片格式</a>
recarto404 大佬发布了支持 wxgf 的微信图片查看器，使用的是 Windows 微信的 <code>VoipEngine.dll</code> 进行解码。</li>
<li><a href="https://signal-labs.com/fuzzing-wechats-wxam-parser/">Fuzzing WeChat’s Wxam Parser | Advanced Offensive Cybersecurity Training</a>  Christopher Vella 写了一篇模 wxam 糊测试案例，同样是分析了<code>VoipEngine.dll</code></li>
</ul>
<p>以上就是全网能找到的有价值的 WXAM 图片格式信息了，还有例如微信官方在一次更新时，错误的将 wxgf 图片分发给了小程序，导致图片无法解析（哈哈哈哈哈：<a href="https://developers.weixin.qq.com/community/develop/doc/0000c84a7080f8cb5322246ff6b800?jumpto=comment&amp;commentid=00082af34d4eb01b5a225787966c">微信开放社区</a><br>
虽然资料较少，但是目标还挺清晰的，直接利用官方的 dll 进行分析，只要分析出编码机制，就能编写转换代码了，计划通！直到我遇到了&hellip;哈哈先卖个关子。</p>
<h3 id="分析过程">分析过程</h3>
<p>使用 IDA 打开 <code>VoipEngine.dll</code> 文件，直接按照关键词查询，就能看到. 晰的函数名称。<br>
<img loading="lazy" src="https://qupfile.cloudvdn.com/IMG-20250819000537497.png"></p>
<p>从 <code>wxam_dec_wxam2pic_5</code> 函数入口开始分析，写点注释，做点笔记，稍微耐心一点，一般都能梳理出整体的处理框架。 <br>
现在做逆向分析比之前要轻松不少，可读性较差的 pseudocode 可以直接丢给 LLM，能够得到比较容易理解的结果，甚至 IDA Pro 还有 MCP（还没试过）。<br>
猜测是因为有较重的历史包袱，所以 WXAM 的代码中充满各种分支判断和参数配置，所以在分析时尽量按照层级处理，不要追着 call 一路 F7，会迷路的。</p>
<p>第一轮函数分析，感觉良好，这是一个结构清晰的函数，感觉马上就能完成解析。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>sub_7FFD9B5D2540            // 入口
</span></span><span style="display:flex;"><span>├─ wxam_dec_isWXGF_5        // 判断 Header
</span></span><span style="display:flex;"><span>├─ wxam_dec_init_5          // 初始内存分配
</span></span><span style="display:flex;"><span>├─ wxam_dec_decode_buffer_5 // 第一次调用 decode
</span></span><span style="display:flex;"><span>├─ wxam_dec_get_option_5    // 第一次调用 get option
</span></span><span style="display:flex;"><span>├─ wxam_dec_get_option_5    // 第二次调用 get option
</span></span><span style="display:flex;"><span>├─ wxam_dec_decode_buffer_5 // 第二次调用 decode
</span></span><span style="display:flex;"><span>├─ wxam_dec_get_option_5    // 第二次调用 get option
</span></span><span style="display:flex;"><span>├─ sub_7FFD9B5D1B50         // 图像处理（核心？）
</span></span><span style="display:flex;"><span>└─ wxam_dec_uninit_5        // 收尾
</span></span></code></pre></div><p>第二轮函数分析，感觉良好，专注在 <code>sub_7FFD9B5D1B50</code> ，学习如何将 RGBA32 转为 JPG，alpha 通道如何处理，直到我看到了 <code>lea rcx, String2; &quot;JPEGMEM&quot;</code>，原来看了半天是 <code>libjpeg</code>。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>sub_7FFD9B5D1B50    // 逐行图像处理，4 转 <span style="color:#ae81ff">3</span>
</span></span><span style="display:flex;"><span>├─ sub_7FFD9B5913C8 // 内存分配，分配值为宽度*3
</span></span><span style="display:flex;"><span>├─ sub_7FFEA5A8FC10 // 初始化错误/消息管理器结构体 类似 libjpeg jpeg_std_error（？？？）
</span></span><span style="display:flex;"><span>├─ sub_7FFEA5A8FCE0 // 创建解码器对象
</span></span><span style="display:flex;"><span>├─ sub_7FFEA5A900C0 // 类似 jpeg_source_mgr
</span></span><span style="display:flex;"><span>├─ sub_7FFEA5A90E80 // 类似 jpeg_read_header
</span></span><span style="display:flex;"><span>├─ sub_7FFEA5A90FE0 // 设置自定义解压选项 rdx 接收 1-100 的值，猜测是质量
</span></span><span style="display:flex;"><span>├─ sub_7FFEA5A914B0 // 类似 jpeg_start_decompress
</span></span><span style="display:flex;"><span>├─ sub_7FFEA5A91570 // 循环调用 jpeg_read_scanlines，看到 lea rcx, String2; <span style="color:#e6db74">&#34;JPEGMEM&#34;</span> （WTF）
</span></span><span style="display:flex;"><span>├─ sub_7FFEA5A8FE10 // jpeg_finish_decompress
</span></span><span style="display:flex;"><span>└─ sub_7FFEA5A8FE00 // jpeg_destroy_decompress 清理函数 避免内存泄露
</span></span></code></pre></div><p>第三轮函数分析，感觉良好，回过头开始分析 <code>wxam_dec_decode_buffer_5</code>，了解了 WXAM 的 Header 处理逻辑。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>wxam_dec_decode_buffer_5 // rcx handle; rdx 数据指针; r8 数据大小
</span></span><span style="display:flex;"><span>└─ sub_7FFD9B5CB1E0         // 解析核心函数 rdx 数据指针 r8; r8 数据大小
</span></span><span style="display:flex;"><span>   ├─ sub_7FFEA5A5F2B0      // 数据预处理
</span></span><span style="display:flex;"><span>      ├─ sub_7FFEA5A5DD40   // 解析 Header
</span></span><span style="display:flex;"><span>      └─ sub_7FFEA5A5C900   // 解析额外参数（version &gt;<span style="color:#f92672">=</span><span style="color:#ae81ff">2</span> 且 <span style="color:#f92672">[</span>rbx+59h<span style="color:#f92672">]</span> 为 0）
</span></span><span style="display:flex;"><span>   ├─ sub_7FFEA5A5B990      // 数据区块解析
</span></span><span style="display:flex;"><span>&lt;...&gt;
</span></span></code></pre></div><p>第四轮函数分析，分析到一半看到 <code>A110</code> 函数天都塌了，好好好，<code>A110</code> 指向了内部的 Vcodec2 视频解码库，微信你用 HEVC 存静态图片！怪不得你压缩率高！</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>sub_7FFD9B5CB1E0     // 解析核心函数
</span></span><span style="display:flex;"><span>   ├─ sub_7FFEA5A5F2B0  // 数据预处理
</span></span><span style="display:flex;"><span>   ├─ WxAMFrame_new     // 创建帧
</span></span><span style="display:flex;"><span>   └─ sub_7FFEA5A5C1D0  // 解码流程控制
</span></span><span style="display:flex;"><span>      ├─ sub_7FFCC4C4A110 // 解析一个原始的、包含视频元数据和/或图像数据的 NALU <span style="color:#f92672">(</span>Network Abstraction Layer Unit<span style="color:#f92672">)</span> 码流
</span></span><span style="display:flex;"><span>      └─ sub_7FFCC4BFFB60 // 图像转换或处理
</span></span><span style="display:flex;"><span>         └─ sub_7FFCC4C1BEE0 // <span style="color:#66d9ef">case</span> <span style="color:#ae81ff">3</span> 图像格式转换调度器
</span></span><span style="display:flex;"><span>            └─ sub_7FFCC4C1D390 // mode <span style="color:#ae81ff">0</span> 通用处理函数 YUV420P 到 BGRA32 的转换函数
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>sub_7FFCC4C4A110 <span style="color:#f92672">(</span>解码器主循环/NAL流处理器<span style="color:#f92672">)</span> - 顶层控制器，负责从输入比特流中读取数据，识别并分离出独立的NAL单元，然后将其送往NAL分发器进行处理。
</span></span><span style="display:flex;"><span>└─ sub_7FFCC4C4BE80 <span style="color:#f92672">(</span>NAL单元分发器<span style="color:#f92672">)</span> - 接收单个NAL单元，判断其类型（如SPS, PPS, VPS, 或 VCL Slice），并将包含图像编码数据的VCL单元送入核心解码流水线。
</span></span><span style="display:flex;"><span>   └─ <span style="color:#f92672">[</span>VCL Slice NAL 单元处理分支<span style="color:#f92672">]</span>
</span></span><span style="display:flex;"><span>      ├─ sub_7FFCC4C51050 <span style="color:#f92672">(</span>Slice头解析器<span style="color:#f92672">)</span> - 从比特流中读取并解析Slice Header，提取解码当前画面切片所需的全部语法元素（如切片类型、参考帧信息等）。
</span></span><span style="display:flex;"><span>      ├─ sub_7FFCC4CB5550 <span style="color:#f92672">(</span>参考帧列表构建器<span style="color:#f92672">)</span> - 根据Slice Header中的信息，构建解码器进行帧间预测所依赖的参考图像列表（List <span style="color:#ae81ff">0</span> 和 List 1）。
</span></span><span style="display:flex;"><span>      └─ sub_7FFCC4C4DC00 <span style="color:#f92672">(</span>Slice解码与图像重建循环<span style="color:#f92672">)</span> - 作为核心驱动引擎，按编码树单元<span style="color:#f92672">(</span>CTU<span style="color:#f92672">)</span>的顺序，循环处理整个Slice，调用具体模块完成像素重建。
</span></span><span style="display:flex;"><span>         ├─ sub_7FFCC4C4CC40 <span style="color:#f92672">(</span>单个CTU解码器<span style="color:#f92672">)</span> - 解码流程的核心，负责对一个编码树单元<span style="color:#f92672">(</span>CTU<span style="color:#f92672">)</span>完成所有关键转换：熵解码<span style="color:#f92672">(</span>CABAC<span style="color:#f92672">)</span>、反量化、反变换、预测（帧内/帧间）以及最终的像素块重建。
</span></span><span style="display:flex;"><span>         └─ sub__7FFCC4C4EA20 <span style="color:#f92672">(</span>环路滤波器<span style="color:#f92672">)</span> - 对重建后的像素块执行去块效应（Deblocking）和样本自适应偏移（SAO）滤波，以减少编码失真、提升最终图像质量。
</span></span></code></pre></div><p>至此，从 IDA 的分析告一段落，<code>wxam2pic</code> 函数的逻辑是，首先调用 <code>Vcodec2</code> 将 HEVC NALU 处理为 YUV420 数据，然后转为 RGBA32 数据，最后使用 <code>libjpeg</code> 处理为 JPG 图像输出；处理逻辑中存在大量分支，支持不同的编解码器，没有再一一分析。<br>
使用 HEVC 保存数据确实在压缩率上非常有优势，但是这就让我们无法简单实现图片转换了，使用 Golang 处理音视频数据转封装比较简单，而处理复杂运算的转码就有些力不从心，手搓一个 HEVC 解码器也不现实，所以只能考虑其他方案。<br>
最简单的方案就是额外调用 <code>ffmpeg</code> 进行转码处理，为了兼容部分没有安装 ffmpeg 的用户，考虑给一个兜底方案，用转封装的形式提供 MP4 格式的图片（认真脸）。</p>
<h3 id="wxam-数据结构">WXAM 数据结构</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    +-----------------------------------------------------------------+
</span></span><span style="display:flex;"><span>    |                        WXGF Header Chunk                        |
</span></span><span style="display:flex;"><span>    +--------------------------------+--------------------------------+
</span></span><span style="display:flex;"><span>    |         Magic <span style="color:#e6db74">&#39;wxgf&#39;</span> <span style="color:#f92672">(</span>4B<span style="color:#f92672">)</span>      |       Header Length <span style="color:#f92672">(</span>1B<span style="color:#f92672">)</span>       |
</span></span><span style="display:flex;"><span>    +--------------------------------+--------------------------------+
</span></span><span style="display:flex;"><span>    |          Version <span style="color:#f92672">(</span>2B<span style="color:#f92672">)</span>          |            Width <span style="color:#f92672">(</span>2B<span style="color:#f92672">)</span>          |
</span></span><span style="display:flex;"><span>    +--------------------------------+--------------------------------+
</span></span><span style="display:flex;"><span>    |           Height <span style="color:#f92672">(</span>2B<span style="color:#f92672">)</span>          |     Other Args <span style="color:#f92672">(</span>Variable<span style="color:#f92672">)</span>      |
</span></span><span style="display:flex;"><span>    +-----------------------------------------------------------------+
</span></span><span style="display:flex;"><span>    |                  -- End of Main Header --                       |
</span></span><span style="display:flex;"><span>    +-----------------------------------------------------------------+
</span></span><span style="display:flex;"><span>    |                  WXGF Extra Header Chunk<span style="color:#f92672">(</span>s<span style="color:#f92672">)</span>                     |
</span></span><span style="display:flex;"><span>    |          <span style="color:#f92672">(</span>Repeats <span style="color:#66d9ef">until</span> a 0x00 flag is encountered<span style="color:#f92672">)</span>             |
</span></span><span style="display:flex;"><span>    +--------------------------------+--------------------------------+
</span></span><span style="display:flex;"><span>    |   Extra Header Flag <span style="color:#f92672">(</span>1B, !<span style="color:#f92672">=</span>0<span style="color:#f92672">)</span>  |  Extra Header Sub Flag <span style="color:#f92672">(</span>1B<span style="color:#f92672">)</span>    |
</span></span><span style="display:flex;"><span>    +--------------------------------+--------------------------------+
</span></span><span style="display:flex;"><span>    |  Extra Header Case Length <span style="color:#f92672">(</span>1B<span style="color:#f92672">)</span> |  Extra Header Case Data <span style="color:#f92672">(</span>Var<span style="color:#f92672">)</span>  |
</span></span><span style="display:flex;"><span>    +-----------------------------------------------------------------+
</span></span><span style="display:flex;"><span>    |                  -- End of Extra Header<span style="color:#f92672">(</span>s<span style="color:#f92672">)</span> --                   |
</span></span><span style="display:flex;"><span>    +-----------------------------------------------------------------+
</span></span><span style="display:flex;"><span>    |                    WXGF Partition Header Chunk                  |
</span></span><span style="display:flex;"><span>    |              <span style="color:#f92672">(</span>Starts with the 0x00 byte from above<span style="color:#f92672">)</span>             |
</span></span><span style="display:flex;"><span>    +-----------------------------------------------------------------+
</span></span><span style="display:flex;"><span>    |                    Partition Header <span style="color:#f92672">(</span>Variable<span style="color:#f92672">)</span>                  |
</span></span><span style="display:flex;"><span>    +-----------------------------------------------------------------+
</span></span><span style="display:flex;"><span>    
</span></span></code></pre></div><p>WXAM 数据结构由 3 部分组成，分别是 Header、Extra Header、Data Partition。<br>
Header 部分比较清晰，其数据长度由 0x04 位置的值控制，Other Args 部分的数据是非 byte 对齐的，按 bit 控制，参数包括帧率、视频质量、压缩选项、高级选项等，宽高信息是大端序；<br>
Extra Header 仅在 Version 为 2 时存在，通过 offset 首位是否为 0x00 判断是否结束，目前有 18 个参数分支，支持缓冲区设置、量化参数、编码器设置等；<br>
Data Partition 由多个数据分片组成，每个分片头部有类型、格式、长度信息，会应用从 Header 中读取的压缩相关参数信息；一般静态图片只有一个 Partition 保存大量数据，动态图片每一帧会作为一个 Partition。</p>
<h3 id="数据处理">数据处理</h3>
<p>刚开始，确实是硬着头皮在写完整的 Header 解析，反复在 IDA 中调试确认参数。后面考虑到完整解析 Header 的复杂性与实际收益不成正比（我们最终只需要裸流数据），索性放弃解析具体参数了。我决定采用一个更直接高效的方案，直接找 HEVC NALU 的 Header (<code>0x00000001</code> 或 <code>0x000001</code>)。因为在 HEVC 编码规范中，会通过在数据中插入 <code>0x03</code> 以避免和起始码冲突，所以干扰项只有 WXAM 的各个 Header，反复查询几次就能准确定位到所有 Partition 的数据了。<br>
然后是如何使用这些数据块，正常的单帧图片，一般是某个 Partition 的数据量占比特别大，那就直接使用这个 Partition 做解析即可。从 Partition 中获取的数据是 Annex B 格式的 HEVC NALU 裸流，ffmpeg 可以直接识别，非常方便；兜底方案采用 <code>github.com/Eyevinn/mp4ff</code> 库，调试一会也能正常出数据了，设置为 1 秒的 mp4 文件，想象大家打开图片时看到个 1 秒的视频一脸懵逼的样子就想笑（嘿嘿。<br>
多帧动画方面，WXAM 的方案还挺优雅的，多个 Partition 组成了两路交替的视频流，其中一路流是遮罩，另一路流是正常动画，在解码时利用遮罩视频处理出透明度。 <br>
遮罩流的类型甚至已经处理为 hevc (Rext), gray(tv)，可惜目前绝大部分播放器无法支持多路流遮罩的场景，只能由内部解码器处理。 <br>
在当前我们的方案中，如果使用 <code>ffmpeg</code> 处理，会将多帧动画处理为 gif，遮罩流的透明度也是正常处理；兜底方案的话，虽然使用了多路 MP4 的方案，但是遮罩流大概率是不工作的。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>Input <span style="color:#75715e">#0, hevc, from &#39;anime.mp4&#39;:</span>
</span></span><span style="display:flex;"><span>  Stream <span style="color:#75715e">#0:0: Video: hevc (Main), yuv420p(tv), 128x128, 25 fps, 30 tbr, 1200k tbn</span>
</span></span><span style="display:flex;"><span>Input <span style="color:#75715e">#1, hevc, from &#39;mask.mp4&#39;:</span>
</span></span><span style="display:flex;"><span>  Stream <span style="color:#75715e">#1:0: Video: hevc (Rext), gray(tv), 128x128, 25 fps, 30 tbr, 1200k tbn</span>
</span></span></code></pre></div><h3 id="使用方式">使用方式</h3>
<p>使用新版本 <code>chatlog</code> ，重新执行获取密钥功能（为了获取图片 AES 密钥），就能支持新版本的图片解析。如果本地没有安装 <code>ffmpeg</code> 的话，WXGF 文件会被转封装为 MP4 文件，这样做的目的是让浏览器可以直接解析。<br>
更推荐的使用方式是安装 <code>ffmpeg</code> 命令行工具，这样能够正常将 WXGF 文件转码为 JPG 图片，多帧动画将被转码为 GIF 动画。只需要 PATH 路径中有 <code>ffmpeg</code> 命令行工具，<code>chatlog</code> 就会自动检测并使用 <code>ffmpeg</code>。<br>
Windows 用户可以直接在 <code>ffmpeg</code> 官网下载 <a href="https://github.com/BtbN/FFmpeg-Builds/releases">BtbN</a> / <a href="https://www.gyan.dev/ffmpeg/builds/">gyan.dev</a> 提供的预编译版本，下载后需要将 <code>ffmpeg.exe</code> 路径加入系统 PATH 中，稍微搜索就能找到很多教程。macOS 用户可以使用 <a href="https://formulae.brew.sh/formula/ffmpeg"><code>brew install ffmpeg</code></a> 命令进行安装，非常方便。<br>
如果需要在自己的项目中集成，可以直接使用 <code>github.com/sjzar/chatlog/pkg/util/dat2img</code> 这个 package，代码比较简单，直接 vibe coding 一下一般都能集成。</p>
<h3 id="总结">总结</h3>
<p>好的，总结一下。本次对 WXAM (WXGF) 格式的分析，我们了解到微信为了实现极致的图像压缩，采用了 HEVC 视频编码来存储静态甚至动态图片，并通过自定义的 <code>Vcodec2</code> 库进行解码。其解码流程大致为：<code>HEVC NALU -&gt; YUV420P -&gt; RGBA32 -&gt; JPG/原始像素</code>。<br>
在无法手搓 HEVC 解码器的情况下， 我们通过特征码扫描绕过复杂的私有 Header，直接提取 HEVC 裸流，并使用 <code>ffmpeg</code> 进行解码和格式转换，成功实现了对单帧、多帧动画 WXAM 图片的解析。<br>
以上就是本次 WXAM（WXGF）图片格式的分析和处理过程，希望对你有所帮助，我也被动看了不少 <code>libjpeg</code> 和 HEVC 解码的逻辑，感觉要长脑子了。</p>
<p>最后，给咱的另一个小项目 <a href="https://imgmcp.com">ImgMCP</a> 打个广告，有生图生视频需求的老板们来看看，Veo3、GPT-Image-1、Midjourney 统统清仓价清仓价了喂。</p>
]]></content:encoded>
    </item>
    <item>
      <title>微信聊天记录解密</title>
      <link>https://sarv.blog/posts/chatlog/</link>
      <pubDate>Tue, 25 Mar 2025 00:00:00 +0800</pubDate>
      <guid>https://sarv.blog/posts/chatlog/</guid>
      <description>&lt;h3 id=&#34;概述&#34;&gt;概述&lt;/h3&gt;
&lt;p&gt;为了整理历史聊天记录，翻阅了不少网络资料和代码库，很多项目的目标都是如何 1:1 还原微信聊天界面，这和我的需求有些出入，我更希望能够有：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Local First 且查询自由，支持任意时段任意对象的全文检索。&lt;/li&gt;
&lt;li&gt;能够非常简单的和大模型工具配合，帮助我从聊天记录中获取相关信息。&lt;br&gt;
既然没有合适的工具，那就考虑自己实现一个，于是就有了 chatlog 这个项目。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;项目地址：&lt;a href=&#34;https://github.com/sjzar/chatlog&#34;&gt;https://github.com/sjzar/chatlog&lt;/a&gt;&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h3 id="概述">概述</h3>
<p>为了整理历史聊天记录，翻阅了不少网络资料和代码库，很多项目的目标都是如何 1:1 还原微信聊天界面，这和我的需求有些出入，我更希望能够有：</p>
<ul>
<li>Local First 且查询自由，支持任意时段任意对象的全文检索。</li>
<li>能够非常简单的和大模型工具配合，帮助我从聊天记录中获取相关信息。<br>
既然没有合适的工具，那就考虑自己实现一个，于是就有了 chatlog 这个项目。</li>
</ul>
<p>项目地址：<a href="https://github.com/sjzar/chatlog">https://github.com/sjzar/chatlog</a></p>
<h3 id="微信本地数据库">微信本地数据库</h3>
<p>微信的聊天数据都保存在本地的数据库文件中，这就是我们要聊的第一部分——微信本地数据库。<br>
微信本地数据库文件是一组加密后的 SQLCipher 文件，由腾讯的<a href="https://github.com/Tencent/wcdb"> WCDB 项目</a> 支持。<br>
SQLCipher 的机制是以增加 10-15% 的开销，100% 加密本地的 SQLite 文件，并提供与 SQLite 相同的 API。<br>
首次在设备登录微信时，会计算出一组密钥用于加密本地的数据库文件，这组密钥在每个账户、每台设备上都是不一样的。<br>
在同账号同设备上更换密钥的频率不高，更换密钥意味着需要对全部数据重新加密，重度使用的微信账号数据量不小，所以一般是微信大版本更新时更换密钥。<br>
虽然微信在各个平台不同大版本中均使用 WCDB 项目，但是加密方式却有所区别。下面是总结的信息：</p>
<table>
  <thead>
      <tr>
          <th>平台</th>
          <th>版本</th>
          <th>加密方式</th>
          <th>加密参数</th>
          <th>备注</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Windows</td>
          <td>3.x</td>
          <td>SQLCipher v3</td>
          <td>Page Size 4096 / KDF 迭代 64000 / HMAC 算法 SHA-1 / KDF 算法 SHA-1</td>
          <td></td>
      </tr>
      <tr>
          <td>Windows</td>
          <td>4.0</td>
          <td>SQLCipher v4</td>
          <td>Page Size 4096 / KDF 迭代 256000 / HMAC 算法 SHA-256 / KDF 算法 SHA-256</td>
          <td>SQLCipher v4 默认配置</td>
      </tr>
      <tr>
          <td>macOS</td>
          <td>3.x</td>
          <td>SQLCipher v3</td>
          <td>Page Size 1024 / HMAC 算法 SHA-1</td>
          <td>比较特殊，直接使用原始密钥</td>
      </tr>
      <tr>
          <td>macOS</td>
          <td>4.0</td>
          <td>SQLCipher v4</td>
          <td>Page Size 4096 / KDF 迭代 256000 / HMAC 算法 SHA-256 / KDF 算法 SHA-256</td>
          <td>与 Windows 4.x 一致</td>
      </tr>
  </tbody>
</table>
<p>了解完数据库加密方式后，简单聊下数据结构。<br>
不同版本的数据结构差异较大，例如 Windows 3.x 版本的数据库，聊天记录是保存在多个 db 文件中，并按时间分库，最新的聊天记录都在同一个文件里，在同一个 db 文件中，所有聊天记录都在一张大表里；macOS 3.x 版本的数据库，聊天记录是按照聊天对象分表的，每个聊天对象有单独的表，再将这些表分配到多个 db 文件中，这意味着如果部分聊天对象记录较多，那么对应的 db 文件就比较大；4.0 版本的微信，数据结构又发生了变化，结合了上述两个方案的特点，先按照时间划分 db 文件，再为每个聊天对象设置单独的表，并且这回 Windows 和 macOS 采用的是相同的方案。</p>
<p>4.0 版本的数据结构其实是更像 Windows 3.x 版本，这也愈发让人觉得 macOS 3.x 版本的数据结构特别奇怪，甚至不像是同一个团队出的产品。<br>
例如在其他版本中，群聊叫做 ChatRoom，而在 macOS 3.x 中叫做 Group；在其他版本中，群成员的信息是保存在 Contact 表中的，而在 macOS 3.x 版本中群成员是独立维护的。（吐槽的原因是由于 chatlog 项目希望支持所有主流版本的微信，所以为了 macOS 3.x 做了不少兼容工作）</p>
<h3 id="本地数据库解密">本地数据库解密</h3>
<p>目前网上的解密方式，一般是从微信进程中获取密钥，再通过密钥解密数据库后查询数据，这也是为什么 Windows 下工具较多，而 macOS 下没有特别方便的工具，因为 Windows 下获取微信内存数据比较简单，可以通过 Windows API 直接读取内存数据，而在 macOS 下有安全机制限制访问其他进程的内存数据，需要先关闭 SIP 才能操作。</p>
<p>获取密钥有几种方式，一种是通过内存基址计算偏移来获取，获取密钥速度快，缺点就是需要手动维护每个小版本的内存基址，代表项目是 <a href="https://github.com/xaoyaoo/PyWxDump/blob/master/pywxdump/WX_OFFS.json">xaoyaoo/PyWxDump</a> 。<br>
另一种方式就是通过内存数据暴力搜索，微信在运行时会打开数据库文件句柄，那么通常在内存数据中会有密钥信息，完整扫描整块内存区域不太可行，但是通过一些数据特征缩小扫描范围，就让这个方案变得可行了，代表项目是 <a href="https://github.com/0xlane/wechat-dump-rs">0xlane/wechat-dump-rs</a>。<br>
第三种方案是 Hook 方案，向微信进程注入动态库后直接调用相关函数获取密钥数据，通常使用 Hook 方案的项目重点都不在获取密钥，而是对微信本身的功能做魔改，例如防撤回、找单删好友、抢红包等，这个方案也是封号风险最大的。</p>
<p>chatlog 项目采用的是方案二，也就是通过读取微信内存，暴力搜索查找数据密钥。<br>
经过测试，Windows 系统下一般 20~30ms 可以找到密钥，macOS 系统下需要约 20 秒的时间，其中大部分时间是在用于 dump 内存数据。<br>
这个方案的封号风险我认为不大，项目并不会操作微信的 API 做额外的请求，获取密钥后的所有操作都和微信进程不相关了。</p>
<h3 id="macos-版本的解密方案">macOS 版本的解密方案</h3>
<p>Windows 平台下有不少成熟的解密项目，但是 macOS 这边相关项目较少，这也给我研究解密带来不少困难。<br>
网上流传最广的 <a href="https://github.com/xaoyaoo/PyWxDump/blob/master/doc/MAC%E6%95%B0%E6%8D%AE%E5%BA%93%E8%A7%A3%E5%AF%86.md">macOS 微信解密方案</a>，是利用 lldb 的断点功能，在设置 sqlite3_key 的地方下断，然后登录微信触发断点后，从寄存器中获取密钥。<br>
这个方案只在 3.8.5 以下版本的微信中可用，更高版本的微信就无法通过这个方案获取密钥了。<br>
我的思路还是先把内存 dump 出来，然后分析内容，既然选择内存暴力破解，那么只要选好特征，避免全量扫描即可。<br>
本地有加密的数据库文件和 dump 出来的内存二进制文件，反复测试后获得密钥数据，再通过密钥数据归纳特征，就有了现在的解密方案。</p>
<h3 id="数据处理">数据处理</h3>
<p>有了密钥后就需要对数据库文件进行处理，刚开始想的是利用密钥对数据库文件做一个即时解密的转换层，这样不需要额外保存一份数据文件。但是由于对 sqlite 不太熟悉，这个方案难度较大，索性还是先将数据解密成标准的 sqlite 文件再进行读取。<br>
由于不同版本的微信数据库结构还有差异，还简单做了 wrap 处理，多媒体数据还没来得及处理，不过这个难度不大，总会处理完的。<br>
关于多媒体数据，我的想法是仍然放在微信数据目录内，通过 chatlog 的 HTTP 服务做文件代理。例如聊天记录中引用某个文件，就转换为 chatlog 提供的 localhost  HTTP 链接，访问链接就读取到了微信数据目录内的相关文件。优点是无需额外保存一份多媒体数据，缺点就是部分数据可能需要即时转码。不过方案肯定还能优化，后续再聊了。</p>
<h3 id="mcp">MCP</h3>
<p>好的，到了最关键的时刻，辛辛苦苦把聊天记录弄出来，就是要把自己的数据用起来。<br>
基于 MCP 的 SSE 方案写了服务，目前能够支持 ChatWise、Claude Desktop、Monica Code、Cline 等工具，但是只有 ChatWise 是原生支持 SSE，其他方案都是通过 mcp-proxy 转发。<br>
先看下效果：</p>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.4.1/dist/jquery.min.js"></script>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.css" />
<script src="https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.js"></script>

<a data-fancybox="gallery" href="https://qupfile.cloudvdn.com/IMG-20250325003314415.png">
<figure class="align-center">
    <img loading="lazy" src="https://qupfile.cloudvdn.com/IMG-20250325003314415.png" width="400"/> <figcaption>
            <p>ChatWise</p>
        </figcaption>
</figure>
</a>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.4.1/dist/jquery.min.js"></script>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.css" />
<script src="https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.js"></script>

<a data-fancybox="gallery" href="https://qupfile.cloudvdn.com/IMG-20250325003352392.png">
<figure class="align-center">
    <img loading="lazy" src="https://qupfile.cloudvdn.com/IMG-20250325003352392.png" width="400"/> <figcaption>
            <p>Claude Desktop</p>
        </figcaption>
</figure>
</a>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.4.1/dist/jquery.min.js"></script>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.css" />
<script src="https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.js"></script>

<a data-fancybox="gallery" href="https://qupfile.cloudvdn.com/IMG-20250325003612045.png">
<figure class="align-center">
    <img loading="lazy" src="https://qupfile.cloudvdn.com/IMG-20250325003612045.png" width="400"/> <figcaption>
            <p>Monica Code</p>
        </figcaption>
</figure>
</a>
<p>MCP 服务支持 stdio 和 SSE 两种传输方式，但是由于项目不完善，只有 SPEC 没落地，连官方的 Claude  Desktop 都不支持 SSE 服务器。<br>
stdio 方案是由本地聊天工具起子进程，通过 stdin 和 stdout 和子进程传输数据。<br>
如果有外部服务想作为 MCP，在现在的 Claude Desktop 上只能想办法弄个子进程服务，把相关请求代理出来。<br>
SSE 方案，是基于 HTTP 协议做的，用长+短两个连接来连 Client 和 Server。<br>
长连接就是 SSE，Client 发给 Server，Server 返回一个 sessionid，然后连接保持住不断开，后续服务端向客户端发数据都用这个 SSE 连接。<br>
客户端向服务端请求就再发起一个 HTTP 请求给服务端，叫做 /messages，然后服务端给这个 /messages 请求返回一个202，再用 SSE 连接返回需要的数据，这样实现双向通信。<br>
但是这个方案也有坑，就是没约定 SSE 连接多久发心跳，断了咋重连，所以在 SSE 断开后，客户端再发 /messages，服务端就无法响应了。<br>
好消息是官方也注意到这个问题了，支持了新的流式 HTTP 模式：<a href="https://github.com/modelcontextprotocol/specification/pull/206">[RFC] Replace HTTP+SSE with new &ldquo;Streamable HTTP&rdquo; transport by jspahrsummers · Pull Request #206 · modelcontextprotocol/specification · GitHub</a>。<br>
还想吐槽的是，官方将服务器提供的内容归类为 Resource、Prompt、Tool，但是目前官方的 Claude Desktop 还不支持 Resource。<br>
本来想将 chatlog 作为 Resource 的，写完代码发现没法用，只能作为 Tool 调用，希望接下来尽快完善～</p>
<h3 id="terminal-ui">Terminal UI</h3>
<p>临近结尾，回过头聊一下项目的 TUI 吧。<br>
这次用的是 <a href="https://github.com/rivo/tview">rivo/tview</a> 项目，第一次使用这个项目，效果看起来还不错。<br>
想到前两年做的命令行项目，还是读取 readline 后自己维护页面，现在的 TUI 效果真是好太多了。</p>
<h3 id="总结">总结</h3>
<p>以上就是本次项目的一些碎碎念，总结下来就是写了个微信聊天记录解密项目，希望这个工具能够帮到大家。</p>
]]></content:encoded>
    </item>
    <item>
      <title>家庭网络规划指南</title>
      <link>https://sarv.blog/posts/home-network/</link>
      <pubDate>Sat, 01 Mar 2025 00:00:00 +0800</pubDate>
      <guid>https://sarv.blog/posts/home-network/</guid>
      <description>&lt;h2 id=&#34;概述&#34;&gt;概述&lt;/h2&gt;
&lt;p&gt;前段时间新家装修，重新规划了家庭网络，分享一下家庭网络规划的思路。&lt;/p&gt;
&lt;h2 id=&#34;家庭网络功能分析&#34;&gt;家庭网络功能分析&lt;/h2&gt;
&lt;p&gt;首先，我们从最简单的网络拓扑结构开始说起，就是只用一台光猫承载全屋的网络功能。&lt;br&gt;
在这个结构中，光猫提供了光电转换、拨号、路由、交换、无线 AP 等功能，大部分对网络要求不是很高的家庭都是这样的拓扑结构。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h2 id="概述">概述</h2>
<p>前段时间新家装修，重新规划了家庭网络，分享一下家庭网络规划的思路。</p>
<h2 id="家庭网络功能分析">家庭网络功能分析</h2>
<p>首先，我们从最简单的网络拓扑结构开始说起，就是只用一台光猫承载全屋的网络功能。<br>
在这个结构中，光猫提供了光电转换、拨号、路由、交换、无线 AP 等功能，大部分对网络要求不是很高的家庭都是这样的拓扑结构。</p>
<p>在此基础上，如果我们觉得光猫的性能不足，会在光猫后方接一台路由器，这样的话，路由、交换、无线 AP 等功能将从光猫转移到路由器，光猫专门负责光电转换和拨号。<br>
部分小伙伴还会要求 ISP 改桥接，也就是将拨号能力也交给路由器。</p>
<p>更进一步，如果我们对家庭网络有更高的要求，想尝试万兆内网，例如需要高速存取 NAS 上的文件；或是家庭面积太大了，一台路由器无法覆盖，我们就需要对家庭网络的拓扑结构进行调整。</p>
<p>实际上，这都是对家庭网络功能的再拆分。
下面我们就看看家庭网络中都用到了哪些功能：</p>
<h3 id="光电转换">光电转换</h3>
<p>入户光纤是家庭网络的起点，通常由光猫完成光电转换的功能，将光信号变成数字信号。<br>
入户光纤通常采用 SC 接口，除了连接光猫以外，还可以用于接入猫棒。
猫棒的外形与光模块类似，是集成度比较高的设备，一头是 SC 光纤接口，另一头是 SFP+ 接口，可以替代光猫完成光电转换与网络接入功能。</p>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.4.1/dist/jquery.min.js"></script>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.css" />
<script src="https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.js"></script>

<a data-fancybox="gallery" href="https://qupfile.cloudvdn.com/IMG-20250303144846059.png">
<figure class="align-center">
    <img loading="lazy" src="https://qupfile.cloudvdn.com/IMG-20250303144846059.png" width="600"/> <figcaption>
            <p>SC 接口 &amp; 猫棒</p>
        </figcaption>
</figure>
</a>
<h3 id="网络接入拨号">网络接入（拨号）</h3>
<p>家庭网络中的宽带接入通常通过 PPPoE 拨号实现，由光猫向互联网服务提供商（ISP）发起拨号请求，经过认证后，取得上网所需的IP地址和网关地址。
为了降低用户使用网络的门槛，ISP 通常会默认使用下发配置的形式帮助用户完成网络配置，用户无需手动拨号即可上网。<br>
部分用户为了更好的控制网络，会要求 ISP 将下发到光猫的配置改为桥接模式，这时光猫只负责光电装换与转发数据包，将网络接入（拨号）的功能交给后续设备。</p>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.4.1/dist/jquery.min.js"></script>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.css" />
<script src="https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.js"></script>

<a data-fancybox="gallery" href="https://qupfile.cloudvdn.com/IMG-20250303145036891.png">
<figure class="align-center">
    <img loading="lazy" src="https://qupfile.cloudvdn.com/IMG-20250303145036891.png" width="600"/> <figcaption>
            <p>PPPoE 流程</p>
        </figcaption>
</figure>
</a>
<h3 id="路由功能">路由功能</h3>
<p>路由功能决定网络请求的路径。<br>
在家庭网络中，路由功能需要判断请求是发往内部网络的其他设备还是外部的 ISP 网关。</p>
<h3 id="交换功能">交换功能</h3>
<p>交换功能负责数据包转发。<br>
与路由功能不同，交换功能不负责决策数据包的目的地，而是根据 MAC 地址将数据包转发到正确的端口。<br>
当然为了加速数据交换，也有工作在网络层或更上层的交换机（多层交换机），是交换功能和部分路由功能的组合。</p>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.4.1/dist/jquery.min.js"></script>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.css" />
<script src="https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.js"></script>

<a data-fancybox="gallery" href="https://qupfile.cloudvdn.com/IMG-20250303145243301.png">
<figure class="align-center">
    <img loading="lazy" src="https://qupfile.cloudvdn.com/IMG-20250303145243301.png" width="600"/> <figcaption>
            <p>OSI 七层模型</p>
        </figcaption>
</figure>
</a>
<h3 id="ip-地址分配dhcp-服务">IP 地址分配（DHCP 服务）</h3>
<p>DHCP 服务负责自动分配内网 IP 地址给家庭网络中的设备，以便它们能够访问网络。<br>
DHCP 协议分为服务端和客户端，服务端负责分配地址，客户端负责接收分配到的地址并配置到自身。</p>
<ul>
<li>与 PPPoE 在同一选框中的 DHCP 指的是 DHCP 客户端，用于从运营商网关获取分配的地址；</li>
<li>路由器配置中的 DHCP 服务指的是 DHCP 服务端，同时需要配置 IP 池、DNS、网关 IP、租期等参数；</li>
<li>PC/手机的网络设置中的 DHCP 指的是 DHCP 客户端，用于从局域网网关获取分配的地址，也常用&quot;自动&quot;字样表示。</li>
</ul>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.4.1/dist/jquery.min.js"></script>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.css" />
<script src="https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.js"></script>

<a data-fancybox="gallery" href="https://qupfile.cloudvdn.com/IMG-20250303145440783.png">
<figure class="align-center">
    <img loading="lazy" src="https://qupfile.cloudvdn.com/IMG-20250303145440783.png" width="600"/> <figcaption>
            <p>DHCP 服务端 &amp; 客户端</p>
        </figcaption>
</figure>
</a>
<h3 id="域名解析dns服务">域名解析（DNS服务）</h3>
<p>DNS 服务将域名转换为服务器的 IP 地址，以便用户可以访问互联网上的特定服务器。<br>
在 PPPoE 拨号时，ISP 会提供推荐的 DNS 服务器地址。</p>
<p>为了提高安全性和解析速度，用户可以选择使用公共 DNS 服务，如腾讯的 <code>119.29.29.29</code> 或阿里的 <code>223.5.5.5</code>。<br>
此外，路由器也可以提供本地 DNS 服务，进行 DNS 缓存或更强的控制能力。例如 <a href="https://github.com/AdguardTeam/AdGuardHome">AdGuardHome</a> 会限制广告域名的解析，<a href="https://github.com/pymumu/smartdns">SmartDNS</a> 可以通过 IP “严选“提高网络访问速度。</p>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.4.1/dist/jquery.min.js"></script>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.css" />
<script src="https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.js"></script>

<a data-fancybox="gallery" href="https://qupfile.cloudvdn.com/IMG-20250303150159175.png">
<figure class="align-center">
    <img loading="lazy" src="https://qupfile.cloudvdn.com/IMG-20250303150159175.png" width="600"/> <figcaption>
            <p>DNS 查询过程</p>
        </figcaption>
</figure>
</a>
<h3 id="地址转换nat">地址转换（NAT）</h3>
<p>家庭网络通常使用私有 IP 地址段，如 <code>10.0.0.0/8</code>、<code>172.16.0.0/12</code>、<code>192.168.0.0/16</code> 等。<br>
当内网设备需要访问公网时，NAT 功能会将这些私有地址转换为公网地址，确保服务器能够正确地将响应发回给请求的设备。</p>
<h3 id="网络安全防火墙">网络安全（防火墙）</h3>
<p>为了保护家庭网络不受外部恶意访问的影响，防火墙功能是必不可少的，它可以拦截和过滤不正常的网络访问请求。</p>
<h2 id="家庭网络拓扑方案">家庭网络拓扑方案</h2>
<p>了解了家庭网络相关功能后，就能看懂网络拓扑结构了。下面我们来看一些典型的家庭网络拓扑方案。</p>
<h3 id="方案一单光猫">方案一：单光猫</h3>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.4.1/dist/jquery.min.js"></script>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.css" />
<script src="https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.js"></script>

<a data-fancybox="gallery" href="https://qupfile.cloudvdn.com/IMG-20250303161920734.png">
<figure class="align-center">
    <img loading="lazy" src="https://qupfile.cloudvdn.com/IMG-20250303161920734.png" width="600"/> <figcaption>
            <p>单光猫</p>
        </figcaption>
</figure>
</a>
<p>最简单的家庭网络拓扑结构，由光猫承担所有的核心功能，不涉及其他网络设备。用户通过有线或无线方式直接连接到光猫。</p>
<p>优点是设置简单，容易理解，出现任何网络问题直接找运营商处理即可，并且没有额外的硬件成本。</p>
<p>缺点是网络性能和管理功能受限于光猫的性能，无线覆盖范围和信号强度可能不足，扩展性和网络稳定性相对较差。</p>
<p>如果运营商提供的光猫还带着百兆网口，建议更新换代。</p>
<h3 id="方案二光猫--路由器">方案二：光猫 + 路由器</h3>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.4.1/dist/jquery.min.js"></script>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.css" />
<script src="https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.js"></script>

<a data-fancybox="gallery" href="https://qupfile.cloudvdn.com/IMG-20250303162152291.png">
<figure class="align-center">
    <img loading="lazy" src="https://qupfile.cloudvdn.com/IMG-20250303162152291.png" width="600"/> <figcaption>
            <p>光猫 + 路由器</p>
        </figcaption>
</figure>
</a>
<p>在方案一的基础上，增加一台路由器。光猫作为主要的网络接入设备，路由器提供额外的网络管理和扩展功能。用户通过有线或无线方式连接到路由器。</p>
<p>这个方案中，路由器并没有独立的子网，网络的关键功能仍然是光猫承担，路由器仅作为无线 AP。用更好理解的话说，就是路由器的网线是插在了 LAN 口上。</p>
<p>优点是由路由器提供了更好的无线网络性能，并且运营商师傅非常熟悉这类配置，出现任何网络问题同样可以直接找运营商处理。</p>
<p>缺点是开始需要额外的硬件成本，网络的稳定性和性能受限于路由器的质量。</p>
<h3 id="方案三光猫--路由器double-nat">方案三：光猫 + 路由器（Double NAT）</h3>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.4.1/dist/jquery.min.js"></script>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.css" />
<script src="https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.js"></script>

<a data-fancybox="gallery" href="https://qupfile.cloudvdn.com/IMG-20250303164412124.png">
<figure class="align-center">
    <img loading="lazy" src="https://qupfile.cloudvdn.com/IMG-20250303164412124.png" width="600"/> <figcaption>
            <p>光猫 + 路由器（Double NAT）</p>
        </figcaption>
</figure>
</a>
<p>我们可以看到，这个网络拓扑结构与方案二对比，物理连线上没有区别，但是网络关键功能发生了较大变化。</p>
<p>光猫上无需变更配置，路由器隔离了 WAN/LAN 子网，由路由器对下挂设备提供完整的网络管理功能。</p>
<p>优点是网络控制能力增强，可以更好地控制网络流量和连接数。</p>
<p>缺点是流量做了两次 NAT 转换，如果需要设置端口映射的话，需要同时在光猫和路由器上配置。</p>
<h3 id="方案四光猫--路由器桥接">方案四：光猫 + 路由器（桥接）</h3>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.4.1/dist/jquery.min.js"></script>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.css" />
<script src="https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.js"></script>

<a data-fancybox="gallery" href="https://qupfile.cloudvdn.com/IMG-20250303165523131.png">
<figure class="align-center">
    <img loading="lazy" src="https://qupfile.cloudvdn.com/IMG-20250303165523131.png" width="600"/> <figcaption>
            <p>光猫 + 路由器（桥接）</p>
        </figcaption>
</figure>
</a>
<p>光猫工作在桥接模式，仅作为信号转换设备。路由器负责拨号上网，并管理整个家庭网络。</p>
<p>这种方案充分利用路由器的高级功能，可以更好地控制网络流量和连接数，提高了网络的灵活性和扩展性。</p>
<p>缺点是配置相对复杂，需要用户有一定的网络知识，而且需要沟通运营商改桥接。路由器的性能将直接影响整个网络的稳定性和速度。</p>
<p>新的万兆光猫（指万兆光口，下联 2.5G 电口的光猫）性能并不差，改完桥接后如果使用质量较差的路由器管理网络，可能还会得不偿失。</p>
<h3 id="方案五光猫--2-台路由器">方案五：光猫 + 2 台路由器</h3>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.4.1/dist/jquery.min.js"></script>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.css" />
<script src="https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.js"></script>

<a data-fancybox="gallery" href="https://qupfile.cloudvdn.com/IMG-20250303165807517.png">
<figure class="align-center">
    <img loading="lazy" src="https://qupfile.cloudvdn.com/IMG-20250303165807517.png" width="600"/> <figcaption>
            <p>光猫 + 2 台路由器</p>
        </figcaption>
</figure>
</a>
<p>通过两台或多台路由器组成 Mesh 网络，实现无缝覆盖和自动切换。</p>
<p>光猫桥接负责信号转换，主路由器负责网络管理，副路由器扩展无线覆盖。</p>
<p>优点是提供了最佳的无线覆盖和网络性能，网络扩展性强，适合大户型，用户在移动过程中可以无缝切换到信号最强的节点。</p>
<p>关于两台路由器是否需要同品牌，我的建议是没有必要。</p>
<p>近几年新出的路由器都支持 802.11kvr 协议，所以不同品牌的路由器之间，也能够实现无缝覆盖和自动切换功能。</p>
<p>每一家厂商的 Mesh 组网方案互不兼容，使用同品牌路由器组 Mesh 网络，在配置上会更简单；而使用不同品牌的路由器，需要单独对每一台路由器进行配置。</p>
<h3 id="方案六光猫--路由器--交换机">方案六：光猫 + 路由器 + 交换机</h3>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.4.1/dist/jquery.min.js"></script>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.css" />
<script src="https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.js"></script>

<a data-fancybox="gallery" href="https://qupfile.cloudvdn.com/IMG-20250303171838226.png">
<figure class="align-center">
    <img loading="lazy" src="https://qupfile.cloudvdn.com/IMG-20250303171838226.png" width="600"/> <figcaption>
            <p>光猫 + 路由器 + 交换机</p>
        </figcaption>
</figure>
</a>
<p>通过交换机和无线 AP 分别扩展有线和无线网络。路由器作为网络的核心，负责拨号和管理。</p>
<p>将交换功能从路由器迁移到交换机，路由器的网络负载降低。当路由器故障时，不影响家庭内部设备间的访问，非常适合折腾软路由的方案。</p>
<p>优点是可以根据需要灵活添加更多的 AP 和交换机，扩展性强，增加交换机后有线网络性能稳定，适合带宽要求高的应用，无线 AP 可以提供更好的无线覆盖。</p>
<p>缺点是需要更多的网络设备，成本和布线复杂度增加，管理和维护相对繁琐。</p>
<h3 id="方案七旁路由">方案七：旁路由</h3>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.4.1/dist/jquery.min.js"></script>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.css" />
<script src="https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.js"></script>

<a data-fancybox="gallery" href="https://qupfile.cloudvdn.com/IMG-20250303173949647.png">
<figure class="align-center">
    <img loading="lazy" src="https://qupfile.cloudvdn.com/IMG-20250303173949647.png" width="600"/> <figcaption>
            <p>旁路由</p>
        </figcaption>
</figure>
</a>
<p>在交换机上连接一个旁路由，用于处理特定的网络流量或提供特殊服务。主路由器和旁路由可以协同工作，实现更复杂的网络功能。</p>
<p>旁路由可以提供额外的服务，如广告拦截、网络存储等，增强了网络的灵活性和定制性。</p>
<p>优点是可以实现网络流量的分流，提高特定应用的性能。</p>
<p>并且旁路由故障时，不影响家庭网络的连通性，对于家庭和睦有帮助。</p>
<p>但是配置更为复杂，需要用户具备较高的网络知识基础。</p>
<h3 id="方案八ac--ap">方案八：AC + AP</h3>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.4.1/dist/jquery.min.js"></script>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.css" />
<script src="https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.js"></script>

<a data-fancybox="gallery" href="https://qupfile.cloudvdn.com/IMG-20250303195609219.png">
<figure class="align-center">
    <img loading="lazy" src="https://qupfile.cloudvdn.com/IMG-20250303195609219.png" width="600"/> <figcaption>
            <p>AC + AP</p>
        </figcaption>
</figure>
</a>
<p>AC+AP 方案，在家庭网络中添加一台 AC 控制器，可以集中管理无线 AP 的配置，适用于需要多个无线 AP 的场景。</p>
<p>实际上，AC 控制器并不负责网络的关键功能，但可以简化无线网络的配置和维护工作。如果厂商愿意，是可以省略 AC 控制器这一设备，采用纯软件方案或整合到其他路由器设备中。</p>
<p>交换机和无线 AP 也可以按需扩展，适合多层住宅的场景。</p>
<h2 id="网络拓扑案例">网络拓扑案例</h2>
<p>讨论过几种典型的网络拓扑结构和关键功能的分配后，大家对于如何设计适合自己的家庭网络拓扑结构，是否已经有想法了？
接下来，我们来讨论几个具体案例，分别是全屋千兆与万兆的方案，并给出具体的设备型号参考。</p>
<h3 id="案例一全屋千兆">案例一：全屋千兆</h3>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.4.1/dist/jquery.min.js"></script>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.css" />
<script src="https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.js"></script>

<a data-fancybox="gallery" href="https://qupfile.cloudvdn.com/IMG-20250303210332205.png">
<figure class="align-center">
    <img loading="lazy" src="https://qupfile.cloudvdn.com/IMG-20250303210332205.png" width="600"/> <figcaption>
            <p>全屋千兆</p>
        </figcaption>
</figure>
</a>
<p>第一个案例，我们以光猫下串联接两个路由器为例：</p>
<ul>
<li>光猫到主路由器的速率为千兆。</li>
<li>主路由器与副路由器之间采用有线连接，并做 Mesh 组网。</li>
<li>关于路由器的购买选择，Wi-Fi 7(BE) &gt; Wi-Fi 6(AX) &gt; Wi-Fi 5(AC)，新协议的路由器有更高的速率和更低的延迟。</li>
<li>如果无线 AP 设备较少，我的建议是没必要上 AC+AP方案，会限制后续的 AP 产品更新；另外，避免选择 AP 面板类产品，由于这类产品的形态都是入墙的，无法有效散热，所以通常网络性能较低或是温度较高，需要注意。</li>
</ul>
<h3 id="案例二添加软路由和-nas">案例二：添加软路由和 NAS</h3>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.4.1/dist/jquery.min.js"></script>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.css" />
<script src="https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.js"></script>

<a data-fancybox="gallery" href="https://qupfile.cloudvdn.com/IMG-20250303210949912.png">
<figure class="align-center">
    <img loading="lazy" src="https://qupfile.cloudvdn.com/IMG-20250303210949912.png" width="600"/> <figcaption>
            <p>添加软路由和 NAS</p>
        </figcaption>
</figure>
</a>
<p>如果需要在案例一的网络拓扑上做扩展，可以考虑添置软路由和 NAS：</p>
<ul>
<li>NAS 即网络附加存储，本质是长时间运行的小型计算机，能够满足资料备份、影音中心、手机相册同步、远程下载等多方面的需求。对于是否需要 NAS，取决于个人的工作和生活习惯。一些人可能高度依赖 NAS，而另一些人购买 NAS 后并不经常使用。</li>
<li>软路由是指通过软件实现的路由解决方案，它同样是一台需要持续运行的小型计算机。软路由的主要优势在于其高度的配置灵活性，可以通过安装各种插件来扩展其功能，如广告屏蔽、远程管理等。在性能相似的情况下，软路由的成本通常低于传统的硬件路由器。</li>
</ul>
<p>大家可能会好奇，既然 NAS 和软路由都是小型计算机，为何要将它们分开使用。确实，存在将两者集成在一起的 All In One 方案。但是，考虑到设备的功能侧重点不同，路由设备通常需要多个网络接口，而存储设备则需要多个硬盘插槽，因此，我们仍然推荐将它们作为两个独立的设备来使用。</p>
<p>此外，在本案例中，并未将软路由作为主路由来使用，这是为了避免对家庭现有网络环境造成影响。对于新手用户来说，他们可能会频繁更换固件、安装新插件、进行网速测试或尝试其他虚拟机软件，如果将软路由设置为主路由，可能会对其他家庭成员的网络使用造成不便。</p>
<p>因此，建议在系统稳定运行后，再考虑将其更换为主路由，或者长期作为旁路由使用。这样既能满足个性化需求，又能确保家庭网络环境的稳定。</p>
<h3 id="案例三局部万兆升级">案例三：局部万兆升级</h3>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.4.1/dist/jquery.min.js"></script>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.css" />
<script src="https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.js"></script>

<a data-fancybox="gallery" href="https://qupfile.cloudvdn.com/IMG-20250303211512741.png">
<figure class="align-center">
    <img loading="lazy" src="https://qupfile.cloudvdn.com/IMG-20250303211512741.png" width="600"/> <figcaption>
            <p>局部万兆升级</p>
        </figcaption>
</figure>
</a>
<p>将光猫升级至带有 2.5Gbps 电口的万兆光猫。在千兆宽带套餐下，实际能带来约 1200Mbps 的最高公网下载速率。</p>
<p>路由器采用软路由方案，在 PVE 平台上运行 RouterOS 和 OpenWRT。其中 RouterOS 负责网关功能，而 OpenWRT 则作为旁路由提供额外的网络服务。</p>
<p>交换机选用双 10Gbps SFP+ 接口的型号，带简单管理能力，采用螃蟹 8373 + 8224 芯片方案，经济实惠。</p>
<p>无线 AP 则选择支持 Wi-Fi 7 标准并带有 10Gbps SFP+ 接口的路由器，例如小米万兆路由器。</p>
<p>这样，我们就拥有了 NAS 到任意设备的超千兆 &amp; 并行访问速率，并且成本非常低。</p>
<p>如果有 PC 到 NAS 的万兆需求，可以将无线 AP 这条链路降级至 2.5G，或是采用更多 10Gbps 接口的交换机。</p>
<h3 id="具体设备推荐">具体设备推荐</h3>
<p>直接推荐设备其实比较困难，大家的需求不太一样，以局部万兆的案例为例，要实现相关功能需求什么设备。</p>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.4.1/dist/jquery.min.js"></script>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.css" />
<script src="https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.js"></script>

<a data-fancybox="gallery" href="https://qupfile.cloudvdn.com/IMG-20250303212451025.png">
<figure class="align-center">
    <img loading="lazy" src="https://qupfile.cloudvdn.com/IMG-20250303212451025.png" width="600"/> <figcaption>
            <p>具体设备推荐</p>
        </figcaption>
</figure>
</a>
<p>具体设备参考
光猫：中兴 F4607P  ~400 元
路由器（软路由）：畅网 N100 ~1000 元
交换机：<a href="https://u.jd.com/GrNzmtY">兮克 SKS3200M</a> ~300 元
无线 AP：<a href="https://u.jd.com/GDNKKfg">小米 万兆路由器</a> ~1600 元
合计：~3300 元</p>
<p>这里未计算 NAS 的价格，并且 AP 的选择也不是性价比最高的型号，选择小米万兆路由器主要是看中有双万兆口。如果需要更具性价比的 AP，可以考虑 <a href="https://u.jd.com/GGNsIF4">中兴 BE7200Pro+</a>，500+ 的价格，4x4 MIMO，2.5G 电口。其他的路由器、交换机设备性能测试，可以看 <a href="https://www.acwifi.net/">ACWIFI-路由器技术分享</a> 博客。</p>
<h2 id="软路由硬件选择">软路由硬件选择</h2>
<p>软路由是一种通过软件来实现的路由功能解决方案。实际上，我们日常使用的电脑、笔记本和电视盒子等设备，都具备充当软路由的潜力。然而为了获得更佳的使用体验，我们通常会倾向于选择配备了多个网络接口的专用硬件设备。</p>
<p>根据硬件架构的不同，软路由硬件主要分为两大类：ARM 架构的软路由和 x86 架构的软路由。</p>
<h3 id="arm-架构软路由">ARM 架构软路由</h3>
<p>ARM 架构的设备特点是低功耗和高度集成的系统，例如苹果的 Silicon 芯片、手机上的骁龙芯片、以及市面上的绝大多数传统硬件路由器的芯片，都采用了 ARM 架构。</p>
<p>在软路由方面，S905、RK3399、RK3568 等芯片型号都有不错的产品。极具代表性的是 N1 盒子，使用的是 S905 芯片，70 元左右的价格，能够满足 500Mbps 的网络需求，flippy 大佬还在持续为 N1 更新固件。</p>
<p>可惜的是，这个&quot;性价比&quot;优势，没有延续到新一代的产品。例如友善的 NanoPi 推出的新品 R6S，使用了 RK3588S 芯片，双 2.5 Gbps 网口加一个千兆网口，售价 1000 元左右，而带四个 2.5Gbps 网口的 N100 设备，也是同样的价格。综合对比价格、功耗、性能、可用的软件，大部分人会选择 x86 架构的 N100。</p>
<p>ARM 芯片的高度定制化特点意味着每推出一款新硬件都需要为其量身打造固件，这无疑增加了研发的难度和成本。并且从开箱即用的角度上来看，现阶段 x86 架构无疑比 ARM 架构更好，毕竟不是每一款硬件都配了一位 flippy 大佬。总之，期待后续 ARM 架构下有更加优秀的新产品。</p>
<h3 id="x86-架构软路由">x86 架构软路由</h3>
<p>相比之下，x86 架构的设备拥有广泛的系统和软件支持。这些设备提供了从入门级到高端的多样化产品线，已经成为软路由硬件的首选。</p>
<ul>
<li>
<p><strong>入门级</strong>：可以考虑购买 J1900 或同等级别的处理器，价格在 100 多元。J1900 处理器在市场上的占有率较高，且有大量的使用教程可供参考。需要注意的是，由于 J1900 处理器推出的时间较久，虽然其可玩性较高，但并不建议将其作为主路由设备使用。</p>
</li>
<li>
<p><strong>主流方案</strong>：J4125、N5105 或者新一代的 N100 处理器是不错的选择，价格在 500~1000 元之间。对于软路由而言，这些处理器的性能已经完全足够，同时它们还具备不错的视频处理能力，甚至可以尝试将其作为媒体播放器使用。</p>
</li>
<li>
<p><strong>高性能方案</strong>：对于不太关心功耗或追求更高性能的用户，可以考虑选择 N305、8505 或是 AMD Ryzen 的 5600U 等处理器，价格来到千元以上。这些处理器的功耗达到 15w，性能比主流方案更高，适合作为 All In One 来折腾了。</p>
</li>
<li>
<p><strong>专业服务器</strong>：如果用户有特殊需求，甚至可以选择 EPYC 系列的处理器来构建家庭服务器。</p>
</li>
</ul>
<p>在硬件品牌上，可以选的余地比较多，例如做工控机的畅网、倍控；做迷你电脑的铭凡、零刻，还能选择 Intel NUC 或 Mac mini。</p>
<p>总而言之，大家可以根据自己的实际需求和预算，挑选最适合自己的产品。</p>
<h2 id="软路由系统选择">软路由系统选择</h2>
<p>接下来的步骤是为设备安装相应的路由系统。市面上存在众多路由系统，包括但不限于 OpenWrt、RouterOS、爱快、pfSense &amp; OPNsense、VyOS 等。</p>
<p>这些路由系统主要分为商业和开源两大类。商业系统通常通过 License 销售模式进行收费，其优势在于提供稳定的官方支持，如 RouterOS 便是一个典型的例子。另一方面，开源系统则允许用户免费使用，它们依赖于活跃的开源社区进行持续的更新和迭代，OpenWrt 便是其中的佼佼者。值得一提的是，某些产品如 pfSense 和 VyOS，同时提供了开源社区版本和闭源商业版本，以满足不同用户的需求。更有甚者，如爱快，不仅提供免费和付费版本的软件，还积极探索硬件销售和云服务领域。</p>
<p>各种路由系统的上手门槛存在显著差异。一些路由系统用户友好度高，提供与传统路由器类似的图形化网页配置界面，使得用户能够轻松上手。然而，也存在一些系统学习曲线较为陡峭，这些系统往往要求用户具备一定的网络知识和 Linux 操作系统基础。因此，在选择软路由系统时，用户应根据自己的技术背景和需求进行权衡。</p>
<h3 id="openwrt">OpenWrt</h3>
<p>OpenWrt 是一款专为网络设备打造的开源操作系统，其核心优势在于对系统组件的深度优化，能够运行在许多嵌入式设备上，为原本功能受限的家用路由器带来更多可能性。</p>
<p>该系统的起源可追溯至 2004 年，Linksys 基于 GNU 协议开放了 WRT54G 设备固件代码，OpenWrt 项目即以这份代码为基础，基于 Linux 内核开发，最初旨在取代一些商业路由器上的限制性固件。随着时间的推移，OpenWrt 不断发展，现已支持广泛的硬件设备，并具备丰富的功能，广泛应用于个人和商业领域。</p>
<p>OpenWrt 以其极高的开放性著称，拥有一个非常活跃的路由系统社区。社区中提供了大量的第三方软件包和定制固件，例如 Lean 的 Lede、小米路由器固件、KoolShare 小宝等人开发的 iStoreOS 等，都是 OpenWrt 的衍生版本。</p>
<p>值得一提的是 Lean 的 Lede，它源自 2016 至 2018 年间的 OpenWrt Lede 项目的分支。尽管 2018 年 Lede 项目已经合并回 OpenWrt 主分支，但 Lean 的 Lede 分支依然持续存在并得到维护。Lean 大佬对该项目做出了许多改进，使其成为国内软路由用户群体中最流行的 OpenWrt 分支。</p>
<p>在国内，恩山是讨论 OpenWrt / Lede 最为热闹的平台。由于 Lede 项目开源，编译步骤简单，恩山论坛里有大量的改版固件发布，给人一种早期大量安卓 ROM 层出不穷的既视感。</p>
<p>综上所述，OpenWrt 是软路由爱好者不可或缺的一个系统。它不仅拥有美观的 LuCI Web 界面，而且学习门槛较低。对于初学者，建议从 Lean 的 Lede 或 iStoreOS 版本固件入手，尝试学习软路由。</p>
<h3 id="routeros">RouterOS</h3>
<p>RouterOS 是由 MikroTik 公司开发的闭源操作系统，专为路由器设计，基于 Debian GNU/Linux 内核。这款操作系统不仅能够安装在 MikroTik 自家的硬件上，还能部署在标准的 PC 或虚拟机中，为用户提供全面而强大的网络管理和配置能力。</p>
<p>自 1997 年推出首个版本以来，RouterOS 一直致力于为用户提供一个既简单又可靠、功能全面的路由解决方案。尽管它源自 Linux 内核，但 MikroTik 对其进行了深度定制和优化，以满足专业级别的网络管理需求。</p>
<p>RouterOS 特别适合对稳定性和控制性要求较高的商业环境。它支持众多网络协议和功能，能够应对复杂的网络配置挑战，如动态路由、防火墙策略、带宽管理和无线接入点配置等。RouterOS 还具备强大的脚本编写功能，使得用户能够自动化执行许多常规网络任务，大幅提升工作效率和精确度。</p>
<p>尽管 RouterOS 的学习曲线相对较陡，需要投入时间来掌握网络相关知识，并且使用它需要购买相应的授权，但对于对网络技术感兴趣的用户来说，尝试学习和使用 RouterOS 是非常值得的。通过配置 RouterOS 的过程，用户将能够全面而深入地理解家庭网络的多个方面，为未来的网络管理和优化打下坚实的基础。</p>
<h3 id="爱快">爱快</h3>
<p>爱快（iKuai）是国内开发的操作系统，它不仅集成了路由、交换、防火墙等多种网络功能，而且通过提供简单易用且高效的网络管理解决方案，旨在满足企业网络管理的复杂需求。</p>
<p>爱快系统通过提供一个集中的管理界面，极大地简化了网络配置和监控的过程，使得网络管理工作直观而便捷。系统内置了丰富的网络管理工具。</p>
<p>爱快路由有一些特色功能，例如简易的多播叠加，批量配置下发等，在部分玩宽带的群体中较受欢迎。相比国际知名的网络系统，爱快主要在中国市场运营，可能在国际化支持方面存在局限。</p>
<p>总的来说，爱快是一个非常易于上手的网络系统，并且功能相对全面，对没有网络基础的用户非常友好。</p>
<h3 id="pfsense--opnsense">pfSense &amp; OPNsense</h3>
<p>pfSense 是一款建立在 FreeBSD 基础之上的开源系统，专门设计用于实现防火墙和路由器功能。该系统提供了一个用户友好的 Web 界面，便于用户进行网络管理，并且支持安装在多种 x86 设备或官方提供的 ARM 设备上。</p>
<p>pfSense 的开发始于 2004 年，作为当时流行的防火墙项目 m0n0wall 的分支，其初衷在于扩展更多的功能和对更多硬件设备的支持。</p>
<p>OPNsense 作为 pfSense 系统的一个衍生分支，自 2015 年首次亮相以来，便以提供一个更加现代化的用户界面、提升代码透明度以及实施更频繁的安全更新为设计宗旨。</p>
<p>pfSense 和 OPNsense 均适用于各种规模的网络设置，从小型家庭网络到中型企业级别。它们具备处理复杂网络安全任务的能力，包括入侵检测与防御、网络流量监控与过滤等，展现出强大的网络安全防护功能。</p>
<h2 id="虚拟化平台选择">虚拟化平台选择</h2>
<p>如果直接将上述的路由系统安装在软路由设备上，有可能出现性能浪费的问题。当前的软路由设备，只要是主流配置的硬件，单独跑路由系统肯定是性能过剩的。而且近几年推出的 CPU 普遍支持硬件虚拟化技术，所以考虑使用虚拟化技术来提升设备使用效率，成为了顺理成章的决定。</p>
<p>对于软路由设备而言，一种常见的做法是先部署一个虚拟化平台（Hypervisors），然后在该平台上启动路由系统的虚拟机。这种方法的优势在于：首先，它允许多个系统在单一设备上同时运行，实现一机多用，从而显著提升资源的利用效率。其次，虚拟化的路由系统拥有更高的灵活性，无论是在系统故障还是设备迁移的情况下，都能迅速恢复服务。</p>
<p>在虚拟化平台（Hypervisors）的选择上，我们有多种常见选项，如 ESXi、PVE、unRaid、Hyper-V 等。与传统的虚拟机软件相比，这些虚拟化平台（Type 1 Hypervisors）无需依赖宿主操作系统，能够更充分地发挥硬件资源的潜力。</p>
<p>此外，它们通常配备有网页控制台，使得用户能够通过网页端便捷地进行系统管理和虚拟机操作，极大地方便了远程使用的需求。</p>
<h3 id="esxi">ESXi</h3>
<p>ESXi 是由 VMware 公司开发的一款企业级服务器虚拟化平台，它构成了 VMware vSphere 解决方案的核心。</p>
<p>ESXi 以其卓越的稳定性和安全性而闻名，配备了一套完善的管理监控系统，其 Web 控制台界面直观流畅，易于使用。此外，ESXi 还提供了一系列强大的管理工具和API，使其成为大规模集群化部署的理想选择。</p>
<p>然而，由于 ESXi 是一个闭源的商业系统，它的开放性和定制化能力相对较低。尽管 ESXi 的最新版本已经发展到 8.0，但许多用户仍然倾向于使用 6.7 或 7.0 版本。这是因为 ESXi 主要针对服务器硬件进行设计，对于家用硬件的兼容性不够好，需要用户尝试自己注入相关驱动。</p>
<p>在 ESXi 的较新版本中，一些旧硬件的驱动支持已被移除，这进一步降低了用户升级到最新版本的动力。</p>
<h3 id="proxmox-ve">Proxmox VE</h3>
<p>Proxmox VE 是一个开源服务器虚拟化管理平台，基于 Debian GNU/Linux 并使用 KVM 和 LXC 作为其核心虚拟化技术。</p>
<p>该平台由 Proxmox Server Solutions GmbH 开发，自 2008 年首次亮相以来，一直致力于提供功能全面且成本效益高的虚拟化解决方案，以满足企业级用户的需求。</p>
<p>Proxmox VE 在开源许可下发布，这意味着用户可以免费下载和使用该平台。同时，还可以选择购买额外的商业支持服务，以获得更专业的帮助和保障。</p>
<p>Proxmox VE 在硬件兼容性方面表现出色，同时，由于系统是基于 Debian 进行开发的，有 Linux 基础的用户可以轻松地进行系统优化，这使得它在虚拟化技术领域中成为了一个受欢迎的选择。</p>
<h3 id="unraid">unRaid</h3>
<p>unRaid 实际上是一款由 Lime Technology, Inc. 基于 Linux 开发的 NAS 管理系统。它被设计用于在家庭服务器上运行，担当多种角色，包括作为网络存储设备、应用程序服务器、媒体服务器以及虚拟化主机的运行平台。</p>
<p>与 Proxmox VE 相似，unRaid 同样采用了 KVM 作为其虚拟化技术的基础。unRaid 之所以受到众多用户的青睐，不仅因为其出色的易用性，还因为其在磁盘管理和 Docker 容器管理方面的优秀表现。</p>
<p>这些特性使得 unRaid 成为了家庭服务器应用场景中的一个受欢迎的选择。</p>
<h2 id="总结">总结</h2>
<p>在整理文章的过程中，不禁感慨家庭网络的发展速度真是飞快。</p>
<p>最早接触网络大概是在 2004 年，当时是在父母的单位用 163 拨号上网，并且上网的时候电话线会被占用。</p>
<p>随后家里装上了 ADSL 宽带，这一用就是好多好多年。那时候，家里的电脑就是唯一可以上网的设备，Wi-Fi 还没开始流行。</p>
<p>2008 年，第一次接触到诺基亚 S60 系统的手机，可以连接 WLAN 了。于是购买了人生中第一台路由器，150Mbps 的塑料壳 TP-Link（没记错的话）。</p>
<p>直到 2015 年以后，我才开始折腾路由器。从淘来的洋垃圾网件刷 OpenWrt 开始，到后来 ASUS 一统天下（能科学上网），再到软路由开始逐步流行。</p>
<p>这些年来，家庭网络设备和软件也在不断更新，TP-Link 不再是便宜大碗的代名词，玩金融的斐讯昙花一现，UniFi 这苹果味是真精致、爱快多播牛逼、RouterOS 真稳定&hellip;越来越多的软件、硬件产品涌现，真有些百花齐放的感觉。</p>
<p>与此同时，大家对于家庭网络的要求也在不断提高，家中需要联网的智能设备越来越多，看流媒体也从 1080P 提升到 4K 高码率，对折腾家庭网络感兴趣的小伙伴也越来越多。</p>
<p>这篇文章我想尝试聊的全面且基础一些，让没有折腾过家庭网络的小伙伴也能够看懂，希望能对大家有所帮助，瑞思拜～</p>
<p>（最后强调，请不要为了折腾创造需求，研究软路由需要较多的时间和精力，并且不会让你的生活更加幸福，建议以兴趣爱好的心态看待并入坑。）</p>
]]></content:encoded>
    </item>
    <item>
      <title>Hello Hugo</title>
      <link>https://sarv.blog/posts/hello-hugo/</link>
      <pubDate>Tue, 25 Feb 2025 14:00:00 +0800</pubDate>
      <guid>https://sarv.blog/posts/hello-hugo/</guid>
      <description>&lt;p&gt;好的，又有博客了。&lt;br&gt;
这次使用的是 &lt;a href=&#34;https://gohugo.io/&#34;&gt;Hugo&lt;/a&gt; 和 &lt;a href=&#34;https://github.com/adityatelange/hugo-PaperMod&#34;&gt;PaperMod&lt;/a&gt; 主题，做下记录。&lt;/p&gt;
&lt;h3 id=&#34;文章目录toc&#34;&gt;文章目录（TOC）&lt;/h3&gt;
&lt;p&gt;PaperMod 主题的目录只能固定在页面顶部，使用上有些不便，并且主题作者坚持单列布局，拒绝合并侧边目录的功能&lt;span class=&#34;sidenote-number&#34;&gt;&lt;small class=&#34;sidenote&#34;&gt;&lt;a href=&#34;https://github.com/adityatelange/hugo-PaperMod/pull/675#issuecomment-1416736188&#34;&gt;Github Issue&lt;/a&gt;&lt;/small&gt;&lt;/span&gt;。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>好的，又有博客了。<br>
这次使用的是 <a href="https://gohugo.io/">Hugo</a> 和 <a href="https://github.com/adityatelange/hugo-PaperMod">PaperMod</a> 主题，做下记录。</p>
<h3 id="文章目录toc">文章目录（TOC）</h3>
<p>PaperMod 主题的目录只能固定在页面顶部，使用上有些不便，并且主题作者坚持单列布局，拒绝合并侧边目录的功能<span class="sidenote-number"><small class="sidenote"><a href="https://github.com/adityatelange/hugo-PaperMod/pull/675#issuecomment-1416736188">Github Issue</a></small></span>。</p>
<p>所以采用了第三方方案，通过自定义 <code>css</code> 和 <code>layout</code> 实现侧边目录。这里参考了 <a href="https://yunpengtai.top/posts/hugo-journey/#%e4%be%a7%e8%be%b9%e6%82%ac%e6%b5%ae%e7%9b%ae%e5%bd%95">Yunpeng Tai</a> 大佬的方案。</p>
<p>另外还有 <a href="https://github.com/JannikArndt/jannikarndt.github.io/commit/8b99f6cbc61c6b4f0238c592d5315cefe07c0599">JannikArndt</a> 和 <a href="https://www.sulvblog.cn/posts/blog/hugo_toc_side/">Sulv&rsquo;s Blog</a> 等方案可选。</p>
<h3 id="样式">样式</h3>
<p>基于 <a href="https://dvel.me/posts/hugo-papermod-config/#markdown-%E6%B8%B2%E6%9F%93%E9%A3%8E%E6%A0%BC">Dvel</a> 大佬的方案做了一些调整，例如文章宽度从 <code>720px</code> 调整为 <code>896px</code> 等。</p>
<p>代码块</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span><span style="color:#75715e"># 最小配置，可连接 ssh</span>
</span></span><span style="display:flex;"><span>/interface bridge
</span></span><span style="display:flex;"><span>add name<span style="color:#f92672">=</span>bridge1 auto-mac<span style="color:#f92672">=</span>yes
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>/interface bridge port
</span></span><span style="display:flex;"><span>add bridge<span style="color:#f92672">=</span>bridge1 interface<span style="color:#f92672">=</span>ether2
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>/ip address
</span></span><span style="display:flex;"><span>add interface<span style="color:#f92672">=</span>bridge1 address<span style="color:#f92672">=</span>10.0.0.1/22 network<span style="color:#f92672">=</span>10.0.0.0
</span></span></code></pre></div><p>表格</p>
<table>
  <thead>
      <tr>
          <th>版本</th>
          <th>限制</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>License L4/P1</td>
          <td>1 Gbps</td>
      </tr>
      <tr>
          <td>License L5/P10</td>
          <td>10 Gbps</td>
      </tr>
      <tr>
          <td>License L6/PU</td>
          <td>-</td>
      </tr>
  </tbody>
</table>
<h3 id="评论组件">评论组件</h3>
<p>使用了 <a href="https://github.com/walinejs/waline">waline</a> 组件。<br>
按照官方指示一路操作即可。</p>
<h3 id="图片放大">图片放大</h3>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.4.1/dist/jquery.min.js"></script>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.css" />
<script src="https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.js"></script>

<a data-fancybox="gallery" href="https://qupfile.cloudvdn.com/WX20250227-011142@2x.png">
<figure class="align-center">
    <img loading="lazy" src="https://qupfile.cloudvdn.com/WX20250227-011142@2x.png" width="400"/> <figcaption>
            <p>第一个博客（2014 年）</p>
        </figcaption>
</figure>
</a>
<p>参考了 <a href="https://yunpengtai.top/posts/hugo-journey/#%e4%be%a7%e8%be%b9%e6%82%ac%e6%b5%ae%e7%9b%ae%e5%bd%95">Yunpeng Tai</a> 大佬的方案，通过修改 <code>figure</code> 实现了图片放大。</p>
<h3 id="侧边笔记">侧边笔记</h3>
<p>应该称呼 <code>Marginal notes</code><span class="sidenote-number"><small class="sidenote"><a href="https://kennethfriedman.org/thoughts/2019/marginal-notes/">KF: Marginal Notes</a></small></span>，指的是在右侧直接显示注释信息。<br>
同样是参考了 <a href="https://yunpengtai.top/posts/hugo-journey/#marginnote">Yunpeng Tai</a> 大佬的方案。</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
