Featured image of post 「zz.ac」公益主机代理地址浅探

「zz.ac」公益主机代理地址浅探

每日一言:

运伟大之思者,必行伟大之迷途。—— 海德格尔


涛叔在ZZ.NIC 公益 Linux 主机空间这篇文章中说:

注意它已经设置了 fe80::2/64 本地链接地址。登录之后无法直接访问外部网络环境。但你用 curl 访问 github.com 就会发现是通的。这是因为系统会自动设置 HTTP 代理环境变量:

1
2
3
$ export|grep http
declare -x http_proxy="http://[fe80::1%eth0]:8888"
declare -x https_proxy="http://[fe80::1%eth0]:8888"

这个代理究竟是什么意思呢?今天通过Gemini探究一下:

1
2
declare -x http_proxy="http://[fe80::1%eth0]:8888"
declare -x https_proxy="http://[fe80::1%eth0]:8888"
  1. 语法拆解:declare -x
  • 在 Bash 中,declare 是一个用于声明变量及其属性的内置命令: declare: 告知 Shell 我要定义一个变量。

  • -x: 代表 export(导出)。它的作用是将该变量变成“环境变量”。

区别:如果只写 http_proxy="…",这个变量只在当前终端窗口有效;使用 -x 后,从这个终端启动的所有程序(如 curl、wget、git、apt 等)都能读取到这个变量。

  1. 地址拆解:IPv6 链路本地地址:
  • 这是这条命令中最复杂的部分:http://[fe80::1%eth0]:8888

  • http://: 即使是代理 IPv6 流量,代理服务器本身运行的协议通常仍然是 HTTP。

  • fe80::1: 这是一个 IPv6 Link-Local Address(链路本地地址)。这类地址仅在当前的物理网段内有效(可以理解为局域网?)。

  • fe80::1 通常是网关(如路由器或宿主机)在 IPv6 环境下的简写形式。

    完整(非压缩)形式为:fe80:0000:0000:0000:0000:0000:0000:0001

  • %eth0: 这是 Zone Index(区域索引/范围标识)。

    关键点:由于所有的网卡(eth0, eth1, wlan0)都可能拥有以 fe80 开头的地址,系统无法仅凭地址知道该往哪个网卡发包。

  • %eth0 明确告诉系统:通过名为 eth0 的网卡去寻找这个代理服务器。

    在 IPv6 的语法中,这个 % 符号被称为 Zone Index(区域索引) 或 Scope ID(作用域标识符) 的分隔符。 在网络中,fe80::/10 开头的地址是链路本地地址(Link-Local Address)。这种地址在每个网络接口(网卡)上都会自动生成一个,且只在当前物理网段内有效。 由于一个操作系统可能插了多张网卡(如 eth0 是有线,wlan0 是无线),每张网卡对应的物理网段里可能都有一个 fe80::1:

    如果不加 %,电脑不知道应该从哪张网卡把数据包发出去。 加上 %eth0,就是明确告诉电脑:“请通过 eth0 这张网卡,去找那个叫 fe80::1 的目标”。

  • [ ]: 在 URL 中,如果使用 IPv6 地址,必须用方括号包围,以区分地址中的冒号和端口号的冒号。

  • :8888: 代理服务器监听的端口号。

  1. 变量名拆解:
  • http_proxy 与 https_proxy这两个是 Linux 系统中约定俗成的环境变量名:
  • http_proxy拦截所有不加密的 HTTP 请求,并转发给代理服务器。
  • https_proxy拦截所有加密的 HTTPS 请求(通常通过 HTTP CONNECT 隧道)。

我的公益主机上的代理:

1
2
3
export |grep http
declare -x http_proxy="http://[fe80::1%25eth0]:8888"
declare -x https_proxy="http://[fe80::1%25eth0]:8888"

而涛叔的文章中是:

1
2
declare -x http_proxy="http://[fe80::1%eth0]:8888"
declare -x https_proxy="http://[fe80::1%eth0]:8888"

显而易见:

区别在于我的代理的%25eth0和默认的%eth0的区别:我网卡名前多了25

这个区别是我为了运行node js项目的时候,Gemini让我修改的。 有个困惑:为啥需要改成这样呢?区别在那里呢?

关于这里的25,这涉及到了 URL 编码(URL Encoding) 的底层机制:

简单的说:%25 其实就是百分号 % 本身经过“翻译”后的样子

  1. 为什么会有 %25?

    • 在计算机网络标准中,% 符号是一个特殊保留字符。它在 URL(统一资源定位符)中被用作“转义字符”的前缀。例如:空格会被编码为 %20,斜杠 / 会被编码为 %2F
    • 当直接写 %eth0 时,某些底层解析器(比如 npm 使用的 Node.js 网络库或某些版本的 curl)会尝试去寻找 % 后面跟随的两个十六进制数字。因为它发现后面跟着的是 et而不是数字,解析器就会报错,或者无法正确识别这个地址,导致代理失效。
    • 为了在 URL 中表示一个“纯粹的百分号”,你必须对百分号本身进行编码。在 ASCII 码表中,百分号 % 的十六进制值是 25
  2. 两组代理的区别特性:

    特性第一组 (%eth0)第二组 (%25eth0)
    形式原始/未编码形式URL 编码形式
    解析行为Shell 脚本或简单的系统工具能识别,但高级编程语言(Node.js, Go)往往会报错。符合标准规范。所有的现代网络库都能正确识别。
    稳定性不稳定,取决于具体软件对 URL 的解析严谨程度。非常稳定,是跨平台、跨工具的标准写法。
    npm 表现失败。Node.js 的 URL 解析器会认为这是一个非法的 URL 格式。成功。Node.js 将 %25 还原为 %,从而正确找到网卡。

实测:在这个系统curl 进行下载正常,使用wget 下载却报错:

1
2
3
4
5
6
bosh@h1:~$ wget www.google.com
Error parsing proxy URL http://[fe80::1%25eth0]:8888: Invalid IPv6 numeric address.
bosh@h1:~$ crule www.google.com
-bash: crule: command not found
bosh@h1:~$ curl www.google.com
<!doctype html><html itemscope="" itemtype="http://schema.org/WebPage" lang="en"><head><meta content="Search the world's information, including webpages, images, videos and more. Google has many special features to help you find exactly what you're looking for." name="description"><meta content="noodp, " name="robots"><meta content="text/html; charset=UTF-8" http-equiv="Content-Type"><meta content="/images/branding/googleg/1x/googleg_standard_color_128dp.png" itemprop="image"><title>Google</title><script nonce="qOaPXbwdBh-h

这是为何那么呢?

  • Linux 中两个最常用的下载工具 curl 和 wget 在代码实现严谨程度上的差异。
  • 核心原因:wget 的一个已知“顽疾”
  • wget 的报错信息:Error parsing proxy URL … Invalid IPv6 numeric address 已经给出了答案。
  • 原因:wget 的 URL 解析器(Parser)写得比较陈旧。它无法正确处理 IPv6 地址中的 Scope ID(作用域标识符,即 %eth0 部分)。
  • 现象:
  • 即使使用了 %25 转义,wget 依然会尝试去验证这个 IP 地址是否符合它心目中的“标准 IPv6 格式”。
  • 在 wget 的逻辑里,它可能只认 [fe80::1] 这种纯地址。一旦看到里面夹杂了 % 这种符号,它的解析逻辑就会“崩溃”,认为这是一个无效的数字地址。
  • 为什么 curl 可以?
  • curl 被称为网络界的“瑞士军刀”,它的背后是强大的 libcurl 库。
  • 更现代的解析器:curl 完美支持 RFC 4007(定义 IPv6 作用域地址的标准)。它不仅能识别 % 符号,还知道如何在建立连接时提取 % 后的网卡名称,并调用底层系统函数(如 getaddrinfo)去绑定正确的网卡。
  • 对转义的宽容度:当你使用 %25 时,curl 的解析器会先执行一层 URL 解码,将其还原为 %,然后再正常处理。

  1. 为什么 npm 需要这个? npm 是基于 Node.js 运行的。Node.js 在处理网络请求时,对环境变量中的 URL 格式要求非常严格。
  • 当 Node.js 读取到 http://[fe80::1%eth0]:8888 时,它会尝试解析这个字符串。

  • 由于 % 后面不是有效的十六进制数,解析器会抛出一个内部错误,或者直接忽略这个代理配置,导致 npm install 尝试直接连接网络,进而因为连不上墙外的服务器而超时。

而使用 %25eth0 时,Node.js 会执行以下逻辑:

  • 检测到 %25。

  • 将其解码(Decode)为 %。

  • 得到最终地址 fe80::1%eth0。

  • 交给操作系统,操作系统通过 eth0 网卡连接到 fe80::1。

总结:

这个坑其实是 “标准之争”。虽然在终端手动 ping 的时候可以直接用 %eth0,但在程序代码、配置文件和 URL 中,永远建议使用 %25 来代替 %,以确保最大的兼容性。


😄 😄 😄 😄 😄

热爱生活 学无止境
使用 Hugo 构建
主题 StackJimmy 设计