4. 通过 HTTP 隧道化 IP
为了允许协商通过 HTTP 承载 IP 的隧道, 本文档定义 "connect-ip" HTTP upgrade token. 由此产生的 IP 隧道使用 Capsule Protocol (见 HTTP-DGRAM 第 3.2 节), 并结合采用第 6 节所定义格式的 HTTP Datagrams.
为了发起与单个 HTTP 流关联的 IP 隧道, 客户端发出一个包含 "connect-ip" upgrade token 的请求.
发送 IP 代理请求时, 客户端 SHALL 执行 URI Template 展开, 以确定其请求的 path 和 query; 见第 3 节.
根据 Capsule Protocol 的定义 (见 HTTP-DGRAM 第 3.2 节), IP 代理请求不携带任何 message content. 类似地, 成功的 IP 代理响应也不携带任何 message content.
HTTP 上的 IP 代理 MUST 运行在 TLS 或 QUIC 加密之上, 或运行在另一种等效加密协议之上, 以提供机密性, 完整性和认证.
4.1. IP 代理处理
收到 IP 代理请求后:
-
如果接收方被配置为使用另一个 HTTP 服务器, 它将作为中介把请求转发给另一个 HTTP 服务器. 注意, 如果这类中介使用的 HTTP 版本与接收请求时使用的版本不同, 它可能需要重新编码请求, 因为请求编码会随版本而异 (见下文).
-
否则, 接收方将作为 IP 代理. IP 代理可以选择拒绝该 IP 代理请求. 否则, 它从根据请求头部重建出的 URI 中提取可选的 "target" 和 "ipproto" 变量, 解码其百分号编码, 并建立 IP 隧道.
IP 代理 MUST 验证解码后的 "target" 和 "ipproto" 变量是否满足第 4.6 节中的要求. 如果不满足, IP 代理 MUST 将该请求视为格式错误; 见 HTTP/2 第 8.1.1 节和 HTTP/3 第 4.1.2 节. 如果 "target" 变量是 DNS 名称, IP 代理 MUST 在回复 HTTP 请求之前执行 DNS 解析 (通过 A 和/或 AAAA 记录获得对应的 IPv4 和/或 IPv6 地址). 如果此过程中发生错误, IP 代理 MUST 拒绝请求, 并 SHOULD 使用适当的 Proxy-Status 头字段 [PROXY-STATUS] 发送详细信息. 例如, 如果 DNS 解析返回错误, 代理可以使用 PROXY-STATUS 第 2.3.2 节中的 dns_error proxy error type.
IP 转发隧道的生命周期与 IP 代理请求流绑定. 在请求流保持打开期间, IP 代理 MUST 维护与 IP 转发隧道关联的所有 IP 地址和路由分配. IP 代理 MAY 因一段时间不活动而选择拆除隧道, 但这样做时 MUST 关闭请求流.
成功的 IP 代理响应 (定义见第 4.3 节和第 4.5 节) 表明 IP 代理已经建立 IP 隧道, 并愿意代理 IP payload. 任何不是成功 IP 代理响应的响应都表示请求失败; 因此, 客户端 MUST 中止该请求.
在成功 IP 代理响应的同时, IP 代理可以向客户端发送 capsule 以分配地址并通告路由 (第 4.7 节). 客户端也可以向 IP 代理分配地址并通告路由, 用于网络到网络路由.
4.2. HTTP/1.1 请求
使用 HTTP/1.1 [HTTP/1.1] 时, IP 代理请求将满足以下要求:
-
method SHALL 为 "GET".
-
请求 SHALL 包含单个 Host 头字段, 其中包含 IP 代理的主机和可选端口.
-
请求 SHALL 包含值为 "Upgrade" 的 Connection 头字段 (注意, 根据 HTTP 第 7.6.1 节, 此要求不区分大小写).
-
请求 SHALL 包含值为 "connect-ip" 的 Upgrade 头字段.
不符合这些限制的 IP 代理请求是格式错误的. 这类格式错误请求的接收方 MUST 以错误响应, 并 SHOULD 使用 400 (Bad Request) 状态码.
例如, 如果客户端配置了 URI Template "https://example.org/.well-known/masque/ip/{target}/{ipproto}/", 并希望打开一个没有 target 或协议限制的 IP 转发隧道, 它可以发送以下请求:
GET https://example.org/.well-known/masque/ip/*/*/ HTTP/1.1
Host: example.org
Connection: Upgrade
Upgrade: connect-ip
Capsule-Protocol: ?1
图 2: HTTP/1.1 请求示例
4.3. HTTP/1.1 响应
服务器通过满足以下要求的回复来表示成功的 IP 代理响应:
-
响应中的 HTTP 状态码 SHALL 为 101 (Switching Protocols).
-
响应 SHALL 包含值为 "Upgrade" 的 Connection 头字段 (注意, 根据 HTTP 第 7.6.1 节, 此要求不区分大小写).
-
响应 SHALL 包含单个值为 "connect-ip" 的 Upgrade 头字段.
-
响应 SHALL 满足启动 Capsule Protocol 的 HTTP 响应要求; 见 HTTP-DGRAM 第 3.2 节.
如果任何这些要求未被满足, 客户端 MUST 将此次代理尝试视为失败并关闭连接.
例如, 服务器可以如下响应:
HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Upgrade: connect-ip
Capsule-Protocol: ?1
图 3: HTTP/1.1 响应示例
4.4. HTTP/2 和 HTTP/3 请求
使用 HTTP/2 [HTTP/2] 或 HTTP/3 [HTTP/3] 时, IP 代理请求使用 HTTP Extended CONNECT. 这要求服务器按 [EXT-CONNECT2] 和 [EXT-CONNECT3] 的规定发送 HTTP Setting, 并要求请求使用满足以下要求的 HTTP pseudo-header 字段:
-
:method pseudo-header 字段 SHALL 为 "CONNECT".
-
:protocol pseudo-header 字段 SHALL 为 "connect-ip".
-
:authority pseudo-header 字段 SHALL 包含 IP 代理的 authority.
-
:path 和 :scheme pseudo-header 字段 SHALL NOT 为空. 它们的值 SHALL 包含 URI Template 展开过程完成后来自 URI Template 的 scheme 和 path; 见第 3 节. URI Template 中的变量可以确定请求的范围, 例如请求全隧道 IP 分组转发或某个特定的被代理流; 见第 4.6 节.
不符合这些限制的 IP 代理请求是格式错误的; 见 HTTP/2 第 8.1.1 节和 HTTP/3 第 4.1.2 节.
例如, 如果客户端配置了 URI Template "https://example.org/.well-known/masque/ip/{target}/{ipproto}/", 并希望打开一个没有 target 或协议限制的 IP 转发隧道, 它可以发送以下请求:
HEADERS
:method = CONNECT
:protocol = connect-ip
:scheme = https
:path = /.well-known/masque/ip/*/*/
:authority = example.org
capsule-protocol = ?1
图 4: HTTP/2 或 HTTP/3 请求示例
4.5. HTTP/2 和 HTTP/3 响应
服务器通过满足以下要求的回复来表示成功的 IP 代理响应:
-
响应中的 HTTP 状态码 SHALL 位于 2xx (Successful) 范围内.
-
响应 SHALL 满足启动 Capsule Protocol 的 HTTP 响应要求; 见 HTTP-DGRAM 第 3.2 节.
如果任何这些要求未被满足, 客户端 MUST 将此次代理尝试视为失败并中止请求. 例如, 3xx 范围内的任何状态码都将被视为失败, 并导致客户端中止请求.
例如, 服务器可以如下响应:
HEADERS
:status = 200
capsule-protocol = ?1
图 5: HTTP/2 或 HTTP/3 响应示例
4.6. 限制请求范围
与要求指定目标主机的 UDP 代理请求不同, IP 代理请求可以允许端点向任意主机发送任意 IP 分组. 客户端可以通过向请求添加参数, 选择将给定请求限制到特定 IP 前缀或 IP 协议. 当 IP 代理知道某个请求限定于目标前缀或协议时, 它可以利用这些信息优化其资源分配; 例如, IP 代理可以把同一个公共 IP 地址分配给限定于不同前缀和/或不同协议的两个 IP 代理请求.
请求范围由客户端通过 URI Template 的 "target" 和 "ipproto" 变量向 IP 代理指示; 见第 3 节. "target" 和 "ipproto" 变量都是可选的; 如果未包含它们, 则认为它们携带通配值 "*".
target: "target" 变量包含客户端想要向其代理分组的特定主机的主机名或 IP 前缀. 如果未指定 "target" 变量, 或其值为 "*", 则客户端请求与任何允许的主机通信. "target" 支持使用 DNS 名称, IPv6 前缀和 IPv4 前缀. 注意, 不支持 IPv6 scoped addressing zone identifiers [IPv6-ZONE-ID]. 如果 target 是 IP 前缀 (IP 地址后面可选地跟随百分号编码的斜杠和以比特计的前缀长度), 该请求将只支持单个 IP 版本. 如果 target 是主机名, IP 代理预期执行 DNS 解析, 以确定要向客户端通告哪些路由. IP 代理 SHOULD 发送 ROUTE_ADVERTISEMENT capsule, 其中包含针对所请求主机名解析出的, IP 代理可访问且属于 IP 代理也发送 Assigned Address 的地址族的所有地址的路由.
ipproto: "ipproto" 变量包含 Internet Protocol Number; 见 "Assigned Internet Protocol Numbers" IANA 注册表 [IANA-PN] 中定义的列表. 如果该变量存在, 它指定客户端只希望为此请求代理某个特定 IP 协议. 如果值为 "*", 或未包含该变量, 则客户端请求使用任何 IP 协议. "ipproto" 变量中指示的 IP 协议表示直接在 HTTP Datagrams 中发送的 IP 头部 (最外层 IP 头部) 所携带的可允许 next header 值. 无论该字段值如何, ICMP 流量始终被允许.
使用 [URI] 中的 IPv6address, IPv4address 和 reg-name 术语, "target" 和 "ipproto" 变量 MUST 遵循图 6 中的格式, 并使用 [ABNF] 中的记法. 此外:
-
如果 "target" 包含 IPv6 字面量或前缀, 冒号 (":") MUST 被百分号编码. 例如, 如果目标主机为 "2001:db8::42", 它将在 URI 中编码为 "2001%3Adb8%3A%3A42".
-
如果 "target" 中存在 IP 前缀长度, 它前面 SHALL 带有百分号编码的斜杠 ("/"): "%2F". IP 前缀长度 MUST 表示介于 0 和 IP 地址比特长度之间的十进制整数, 含端点.
-
如果 "target" 包含 IP 前缀, 且前缀长度严格小于 IP 地址的比特长度, 则 IP 地址中未被前缀长度覆盖的低位比特 MUST 全部设置为 0.
-
"ipproto" MUST 表示介于 0 和 255 之间的十进制整数 (含端点), 或通配值 "*".
target = IPv6prefix / IPv4prefix / reg-name / "*"
IPv6prefix = IPv6address ["%2F" 1*3DIGIT]
IPv4prefix = IPv4address ["%2F" 1*2DIGIT]
ipproto = 1*3DIGIT / "*"
图 6: URI Template 变量格式
IP 代理 MAY 使用客户端提供的范围限定信息执行访问控制, 即如果客户端未被授权访问该范围中包含的任何目的地, IP 代理可以立即拒绝请求.
4.7. Capsules
本文档定义了多个新的 capsule 类型, 允许端点交换 IP 配置信息. 两个端点都 MAY 发送任意数量的这些新 capsule.
4.7.1. ADDRESS_ASSIGN Capsule
ADDRESS_ASSIGN capsule (capsule type 0x01) 允许端点向其对等方分配一个 IP 地址或前缀列表. 每个 capsule 都包含当前分配给接收方的完整 IP 前缀列表. 这些地址中的任意一个都可用作由该 capsule 接收方发起的 IP 分组的源地址.
ADDRESS_ASSIGN Capsule {
Type (i) = 0x01,
Length (i),
Assigned Address (..) ...,
}
图 7: ADDRESS_ASSIGN Capsule 格式
ADDRESS_ASSIGN capsule 包含由零个或多个 Assigned Addresses 构成的序列.
Assigned Address {
Request ID (i),
IP Version (8),
IP Address (32..128),
IP Prefix Length (8),
}
图 8: Assigned Address 格式
每个 Assigned Address 包含以下字段:
Request ID: 请求标识符, 编码为可变长度整数. 如果此地址分配是对 Address Request (见第 4.7.2 节) 的响应, 则此字段 SHALL 包含请求中对应字段的值. 否则, 此字段 SHALL 为零.
IP Version: 此地址分配的 IP Version, 编码为无符号 8 位整数. 它 MUST 为 4 或 6.
IP Address: 分配的 IP 地址. 如果 IP Version 字段的值为 4, IP Address 字段 SHALL 长度为 32 位. 如果 IP Version 字段的值为 6, IP Address 字段 SHALL 长度为 128 位.
IP Prefix Length: IP 地址中用于定义所分配前缀的比特数, 编码为无符号 8 位整数. 该值 MUST 小于或等于 IP Address 字段的比特长度. 如果前缀长度等于 IP 地址长度, 则该 capsule 的接收方被允许从单个源地址发送分组. 如果前缀长度小于 IP 地址长度, 则该 capsule 的接收方被允许从该前缀范围内的任意源地址发送分组. 如果前缀长度严格小于 IP 地址的比特长度, 则 IP Address 字段中未被前缀长度覆盖的低位比特 MUST 全部设置为 0.
如果接收时发现任何 capsule 字段格式错误, capsule 接收方 MUST 遵循 HTTP-DGRAM 第 3.3 节中定义的错误处理过程.
如果某个 ADDRESS_ASSIGN capsule 不包含先前在另一个 ADDRESS_ASSIGN capsule 中传输过的地址, 则表示该地址已被移除. ADDRESS_ASSIGN capsule 也可以为空, 表示所有地址都已被移除.
在 HTTP 中 IP 代理的某些部署中, 端点需要先由其对等方分配地址, 才能知道应在自身分组上设置什么源地址. 例如, 在远程访问 VPN 场景 (第 8.1 节) 中, 客户端在知道要使用哪个地址之前无法发送 IP 分组. 在这些部署中, 期望获得地址分配的端点 MUST 发送 ADDRESS_REQUEST capsule. 如果端点不需要任何地址分配, 则不要求这样做, 例如端点已通过带外方式配置静态地址.
虽然 ADDRESS_ASSIGN capsule 通常作为对 ADDRESS_REQUEST capsule 的响应发送, 但端点 MAY 主动发送 ADDRESS_ASSIGN capsule.
4.7.2. ADDRESS_REQUEST Capsule
ADDRESS_REQUEST capsule (capsule type 0x02) 允许端点向其对等方请求分配 IP 地址. 该 capsule 允许端点可选地指示其希望被分配哪个地址的偏好.
ADDRESS_REQUEST Capsule {
Type (i) = 0x02,
Length (i),
Requested Address (..) ...,
}
图 9: ADDRESS_REQUEST Capsule 格式
ADDRESS_REQUEST capsule 包含由一个或多个 Requested Addresses 构成的序列.
Requested Address {
Request ID (i),
IP Version (8),
IP Address (32..128),
IP Prefix Length (8),
}
图 10: Requested Address 格式
每个 Requested Address 包含以下字段:
Request ID: 请求标识符, 编码为可变长度整数. 这是此特定地址请求的标识符. 来自某个给定端点的每个请求都携带不同的标识符. Request ID MUST NOT 被某个端点重复使用, 并且 MUST NOT 为零.
IP Version: 此地址请求的 IP Version, 编码为无符号 8 位整数. 它 MUST 为 4 或 6.
IP Address: 请求的 IP 地址. 如果 IP Version 字段的值为 4, IP Address 字段 SHALL 长度为 32 位. 如果 IP Version 字段的值为 6, IP Address 字段 SHALL 长度为 128 位.
IP Prefix Length: 所请求 IP Prefix 的长度, 以比特计, 编码为无符号 8 位整数. 它 MUST 小于或等于 IP Address 字段的比特长度. 如果前缀长度严格小于 IP 地址的比特长度, 则 IP Address 字段中未被前缀长度覆盖的低位比特 MUST 全部设置为 0.
如果 IP 地址全为零 (0.0.0.0 或 ::), 这表示发送方正在请求该地址族的地址, 但对具体地址没有偏好. 在这种场景下, 前缀长度仍表示发送方对所请求前缀长度的偏好.
如果接收时发现任何 capsule 字段格式错误, capsule 接收方 MUST 遵循 HTTP-DGRAM 第 3.3 节中定义的错误处理过程.
收到 ADDRESS_REQUEST capsule 后, 端点 SHOULD 向其对等方分配一个或多个 IP 地址, 然后以 ADDRESS_ASSIGN capsule 响应以通知对等方该分配. 对于每个 Requested Address, ADDRESS_REQUEST capsule 的接收方 SHALL 以具有匹配 Request ID 的 Assigned Address 响应. 如果请求的地址已被分配, Assigned Address 响应中的 IP Address 和 IP Prefix Length 字段 SHALL 设置为分配值. 如果请求的地址未被分配, IP 地址 SHALL 全为零, IP Prefix Length SHALL 为最大长度 (0.0.0.0/32 或 ::/128), 以表示未分配地址. 这些地址拒绝 SHOULD NOT 包含在后续 ADDRESS_ASSIGN capsule 中. 注意, 同一个 ADDRESS_ASSIGN 响应中也可以包含不对应任何 Request ID 的其他 Assigned Address 条目.
如果端点收到包含零个 Requested Addresses 的 ADDRESS_REQUEST capsule, 它 MUST 中止 IP 代理请求流.
注意, Requested Addresses 的顺序不携带任何语义. 类似地, Request ID 仅用作唯一标识符; 它不传达任何优先级或重要性.
4.7.3. ROUTE_ADVERTISEMENT Capsule
ROUTE_ADVERTISEMENT capsule (capsule type 0x03) 允许端点告知其对等方, 它愿意把流量路由到一组 IP 地址范围. 这表示发送方已经拥有到每个地址范围的现有路由, 并通知其对等方: 如果 ROUTE_ADVERTISEMENT capsule 的接收方在 HTTP Datagrams 中发送面向这些范围之一的 IP 分组, capsule 的发送方会沿其既有路由转发这些分组. 位于这些地址范围之一中的任意地址都可用作该 capsule 接收方发起的 IP 分组的目的地址.
ROUTE_ADVERTISEMENT Capsule {
Type (i) = 0x03,
Length (i),
IP Address Range (..) ...,
}
图 11: ROUTE_ADVERTISEMENT Capsule 格式
ROUTE_ADVERTISEMENT capsule 包含由零个或多个 IP Address Ranges 构成的序列.
IP Address Range {
IP Version (8),
Start IP Address (32..128),
End IP Address (32..128),
IP Protocol (8),
}
图 12: IP Address Range 格式
每个 IP Address Range 包含以下字段:
IP Version: 此范围的 IP Version, 编码为无符号 8 位整数. 它 MUST 为 4 或 6.
Start IP Address and End IP Address: 所通告范围的起始和结束 IP 地址, 二者均包含在范围内. 如果 IP Version 字段的值为 4, 这些字段 SHALL 长度为 32 位. 如果 IP Version 字段的值为 6, 这些字段 SHALL 长度为 128 位. Start IP Address MUST 小于或等于 End IP Address.
IP Protocol: 可发送到此范围的流量的 Internet Protocol Number, 编码为无符号 8 位整数. 如果值为 0, 则允许所有协议. 如果值不为 0, 它表示直接在 HTTP Datagrams 中发送的 IP 头部 (最外层 IP 头部) 所携带的可允许 next header 值. 无论该字段值如何, ICMP 流量始终被允许.
如果接收时发现任何 capsule 字段格式错误, capsule 接收方 MUST 遵循 HTTP-DGRAM 第 3.3 节中定义的错误处理过程.
收到 ROUTE_ADVERTISEMENT capsule 后, 端点 MAY 更新其关于对等方愿意路由哪些内容的本地状态 (受本地策略约束), 例如在路由表中安装条目.
每个 ROUTE_ADVERTISEMENT 都包含地址范围的完整列表. 如果在一个方向上发送了多个 ROUTE_ADVERTISEMENT capsule, 则每个 ROUTE_ADVERTISEMENT capsule 都取代此前的 capsule. 换言之, 如果某个给定地址范围存在于先前 capsule 中, 但最近收到的 ROUTE_ADVERTISEMENT capsule 中不包含它, 接收方会认为该范围已被撤销.
如果使用相同 IP 协议的多个范围发生重叠, 某些路由表实现可能会拒绝它们. 为防止重叠, 这些范围是有序的; 这把责任放在发送方, 并使接收方验证起来简单得多. 如果同一 ROUTE_ADVERTISEMENT capsule 中的 IP Address Range A 位于 IP Address Range B 之前, 它们 MUST 遵循以下要求:
-
A 的 IP Version MUST 小于或等于 B 的 IP Version.
-
如果 A 和 B 的 IP Version 相等, A 的 IP Protocol MUST 小于或等于 B 的 IP Protocol.
-
如果 A 和 B 的 IP Version 与 IP Protocol 都相等, A 的 End IP Address MUST 严格小于 B 的 Start IP Address.
如果端点收到不满足这些要求的 ROUTE_ADVERTISEMENT capsule, 它 MUST 中止 IP 代理请求流.
由于将 IP protocol 设置为零表示允许所有协议, 上述要求使得当一条路由的 IP protocol 设置为零而另一条路由设置为非零时, 两条路由可能发生重叠. 端点 MUST NOT 发送带有这种方式重叠路由的 ROUTE_ADVERTISEMENT capsule. 验证此要求是 OPTIONAL 的, 但如果端点检测到该违规, 它 MUST 中止 IP 代理请求流.
4.8. IPv6 扩展头
请求范围限定 (见第 4.6 节) 和 ROUTE_ADVERTISEMENT capsule (见第 4.7.3 节) 都使用 Internet Protocol Numbers. 这些编号既表示上层协议 (如 IPv6 第 2 节所定义, 示例包括 TCP 和 UDP), 也表示 IPv6 扩展头 (如 IPv6 第 4 节所定义, 示例包括 Fragment 和 Options 头). IP 代理 MAY 拒绝范围限定到扩展头所用协议编号的请求. 收到分组时, 支持按 Internet Protocol Number 进行范围限定或路由的实现 MUST 遍历扩展链, 找到最外层的非扩展 Internet Protocol Number, 以便与范围限定规则匹配. 注意, ROUTE_ADVERTISEMENT capsule 使用 Internet Protocol Number 0 表示允许所有协议; 它并不把路由限制到 IPv6 Hop-by-Hop Options 头 (IPv6 第 4.3 节).