Deno deno_net 扩展深度解析:TCP、UDP、TLS、QUIC 与 Unix 套接字的 op 实现全貌
本文以 ext/net/README.md 为核心,系统讲解 Deno 运行时中 deno_net crate 的定位、装配方式与全部 op 清单,并结合 ext/net 源码深入剖析 TCP 连接中的 Happy Eyeballs 算法、双层权限检查、SO_REUSEPORT 负载均衡监听以及基于 hickory 的 DNS 解析等关键实现,帮助读者从 JS 侧的 Deno.connect/Deno.listen 一路读懂底层 Rust 调用链。
deno_net 是什么
ext/net/README.md 开篇即明确:deno_net 这个 crate 实现了 Deno 的网络相关 API。它是 Deno 扩展体系(deno_core extension)中的一员,以 lib.rs 中的 deno_core::extension! 宏声明自身,对外暴露一组 op_* 操作,由 JS 侧脚本(01_net.js、02_tls.js、03_quic.js)封装成用户可见的 Deno.connect、Deno.listen、Deno.listenTls、Deno.resolveDns 等 API。
从 Cargo.toml 的依赖表可以看出该扩展的技术底座:
| 依赖 | 作用 |
|---|---|
quinn(features: runtime-tokio, rustls-aws-lc-rs) |
QUIC 协议栈,支撑 QUIC/WebTransport ops |
rustls-tokio-stream + deno_tls |
TLS 连接与根证书信任逻辑 |
hickory-resolver / hickory-proto |
纯 Rust 的 DNS 解析(op_dns_resolve) |
socket2 |
底层 socket 选项(SO_REUSEPORT、广播、多播等) |
tokio-vsock(仅 Android/Linux/macOS) |
VSOCK 通信(虚拟机↔宿主机) |
web-transport-proto |
WebTransport 协议映射 |
README 还声明了扩展依赖:deno_web 由 deno_web crate 提供,deno_fetch 由 deno_fetch crate 提供;在 lib.rs 中 extension! 宏也确实标注了 deps = [ deno_web ]。
装配方式:JS 加载脚本 + Rust 初始化参数
README 给出的用法示例是理解该扩展入口的最佳材料。JS 侧需按顺序加载扩展脚本(README 示例):
import { core } from "ext:core/mod.js";
const webidl = core.loadExtScript("ext:deno_webidl/00_webidl.js");
const net = core.loadExtScript("ext:deno_net/01_net.js");
const tls = core.loadExtScript("ext:deno_net/02_tls.js");
const loadQuic = core.createLazyLoader("ext:deno_net/03_quic.js");
const quic = loadQuic();
注意 03_quic.js 走的是 createLazyLoader 懒加载路径,与 lib.rs 中 lazy_loaded_esm = [ "03_quic.js" ]、lazy_loaded_js = [ "01_net.js", "02_tls.js" ] 的声明一致——QUIC 脚本不随快照立即执行,只有真正用到 QUIC/WebTransport 时才加载。
Rust 侧则在 RuntimeOptions.extensions 字段中提供初始化函数:
deno_net::deno_net::init(root_cert_store_provider, unsafely_ignore_certificate_errors)
两个参数在 lib.rs 的 options 块中有完整签名:
root_cert_store_provider: Option<Arc<dyn RootCertStoreProvider>>—— 延迟提供根证书信任库的 trait 对象,存为DefaultTlsOptions(lib.rs L95-L109),调用root_cert_store()时才真正解析;unsafely_ignore_certificate_errors: Option<Vec<String>>—— 按 hostname 忽略证书错误的白名单,用专门的结构体UnsafelyIgnoreCertificateErrors包装存入GothamState(lib.rs L111-L115,注释解释了不用类型别名是为了避免同名类型在 state 中互相覆盖)。
仓库内的真实调用点可验证这一契约:runtime/snapshot_info.rs 在构建运行时快照时使用 deno_net::deno_net::init(None, None),runtime/web_worker.rs 在创建 Web Worker 时同样以 deno_net::deno_net::init(...) 装配该扩展。
提供的 ops 完整清单
README 按协议域列出了该扩展提供的全部 ops,可经 Deno.ops 访问。以下清单与 lib.rs 中 extension! 宏的 ops = [...] 注册列表逐一吻合:
Net
op_net_accept_tcpop_net_get_ips_from_perm_tokenop_net_connect_tcpop_net_listen_tcpop_net_listen_udpop_net_recv_udpop_net_send_udpop_net_join_multi_v4_udpop_net_join_multi_v6_udpop_net_leave_multi_v4_udpop_net_leave_multi_v6_udpop_net_set_multi_loopback_udpop_net_set_multi_ttl_udpop_net_set_broadcast_udpop_net_validate_multicastop_net_get_system_dns_serversop_net_listen_vsockop_net_accept_vsockop_net_connect_vsockop_net_listen_tunnelop_net_accept_tunnel
TLS
op_tls_key_nullop_tls_key_staticop_tls_cert_resolver_createop_tls_cert_resolver_pollop_tls_cert_resolver_resolveop_tls_cert_resolver_resolve_errorop_tls_startop_tls_handshake
QUIC
op_quic_connecting_0rttop_quic_connecting_1rttop_quic_connection_accept_biop_quic_connection_accept_uniop_quic_connection_closeop_quic_connection_closedop_quic_connection_get_protocolop_quic_connection_get_remote_addrop_quic_connection_get_server_nameop_quic_connection_handshakeop_quic_connection_open_biop_quic_connection_open_uniop_quic_connection_get_max_datagram_sizeop_quic_connection_read_datagramop_quic_connection_send_datagramop_quic_endpoint_closeop_quic_endpoint_connectop_quic_endpoint_createop_quic_endpoint_get_addrop_quic_endpoint_listenop_quic_incoming_acceptop_quic_incoming_accept_0rttop_quic_incoming_ignoreop_quic_incoming_local_ipop_quic_incoming_refuseop_quic_incoming_remote_addrop_quic_incoming_remote_addr_validatedop_quic_listener_acceptop_quic_listener_stopop_quic_recv_stream_get_idop_quic_send_stream_get_idop_quic_send_stream_get_priorityop_quic_send_stream_set_priority
WebTransport
op_webtransport_acceptop_webtransport_connect
Other
op_node_unstable_net_listen_udpop_dns_resolveop_set_nodelayop_set_keepaliveop_node_unstable_net_listen_unixpacket
另有 6 个 Unix 域套接字 ops(op_net_accept_unix、op_net_connect_unix、op_net_listen_unix、op_net_listen_unixpacket、op_net_recv_unixpacket、op_net_send_unixpacket,README 将其归入 Net 列表)只在 unix 平台注册,实现在 ops_unix.rs;在非 unix 平台上 lib.rs L218-L246 用 stub_op! 宏生成同名桩函数,直接返回 Unsupported 错误——这是该扩展保证跨平台 op 表一致的常见手法。
TCP 连接:权限令牌、Happy Eyeballs 与取消语义
op_net_connect_tcp 是理解 deno_net 设计思路的枢纽,实现在 ops.rs L588-L725。它体现了三层工程细节:
1. 基于 NetPermToken 的权限归因。 连接前先做 check_net(检查原始 hostname),而 JS 层在域名解析阶段就会生成一个 NetPermToken(ops.rs L547-L579),保存「原始 hostname + 已解析的 IP 列表」。当后续按解析出的某个 IP 发起连接时,token.check_host() 会把它映射回原始 hostname 再做权限判定——op_net_get_ips_from_perm_token 正是 JS 侧获取该列表用的 op。
2. 解析后二次校验(deny 规则不可绕过)。 解析完成后,对所有可能实际连接的候选地址再执行 check_net_resolved(ops.rs L657-L677)。源码注释点明了防御目标:防止用数字主机名别名(如 2130706433 → 127.0.0.1)绕过针对 IP 字面量的 deny 规则。
3. Happy Eyeballs(RFC 8305)。 当 auto_select_family 开启且解析出多个地址时走 connect_happy_eyeballs(happy_eyeballs.rs),否则只连接第一个地址。相关参数由 TcpConnectOptions 反序列化(ops.rs L92-L111):
| 字段 | 默认值 | 说明 |
|---|---|---|
autoSelectFamily |
true |
是否自动选择地址族(Happy Eyeballs) |
autoSelectFamilyAttemptDelay |
250(ms) |
两次连接尝试之间的间隔,常量见 DEFAULT_ATTEMPT_DELAY_MS |
connect_happy_eyeballs 的行为从源码(happy_eyeballs.rs L34-L117)可以完整读出:
interleave_addresses按 RFC 8305 第 4 节交错 IPv6/IPv4:输入[v6_1, v6_2, v4_1, v4_2]输出[v6_1, v4_1, v6_2, v4_2](v6 优先);- 第一个地址立即发起连接,之后每隔
attempt_delay启动下一个并行尝试,先成功的胜出,其余尝试被取消; - 若某个快速失败后没有 pending 尝试,则立即启动下一个地址而非空等
attempt_delay; - 全部失败时返回最后一个失败的错误,与 Node.js
net.connect()的报错习惯保持一致; - 每个尝试都挂
CancelHandle,JS 侧 abort 会级联取消所有在途连接。
单元测试(happy_eyeballs.rs L169-L463)覆盖了交错排序、空列表、单一地址成功/失败、回退到第二个地址、并行第二者胜出等场景,可以直接在仓库中运行验证。
地址解析本身由 resolve_addr.rs 完成:异步路径用 tokio::net::lookup_host,同步路径(如 listen)用 to_socket_addrs;空 hostname 归一为 0.0.0.0,带方括号的 IPv6([2001:db8::1])会被剥掉方括号。
TCP 监听:SO_REUSEPORT 与进程内负载均衡
op_net_listen_tcp(ops.rs L742-L773)接收 reuse_port、load_balanced、tcp_backlog 三个参数,其中 reusePort: true 属于不稳定特性——触发 check_unstable(特性名 "net",见 lib.rs L25 的 UNSTABLE_FEATURE_NAME),即需要 --unstable-net 之类的开关才能使用。
监听器由 tcp.rs 的 TcpListener 封装,其平台策略值得注意:
- Linux/Android:内核的
SO_REUSEPORT自带负载均衡语义,TcpListener::bind走bind_load_balanced; - 其他平台:macOS(BSD) 的
SO_REUSEPORT是「后绑定者独占」而非负载均衡(tcp.rs L98-L112 注释详述了这一差异),因此源码用进程内Connections表(OnceLock<Mutex<Connections>>)+ 克隆 FD 的方式自行模拟:同一地址的多个监听者共享一个「原始」socket,各自持有一个克隆 FD 竞争accept; - 所有绑定路径都设置
SO_REUSEADDR与 nonblocking,并允许自定义backlog。
tcp.rs L222-L249 还包含一个回归测试 concurrent_load_balanced_listener_drops_remove_connection,专门验证两个负载均衡监听器并发 drop 时注册表能正确清理,避免连接表泄漏。
UDP 与多播:unstable API 的完整参数
op_net_listen_udp(对应 Deno.listenDatagram)整体处于 unstable 特性门禁之下(ops.rs L842-L851),内部 net_listen_udp 函数根据地址族创建 socket,并对 reuse_address 做了平台区分(注释说明逻辑取自 libuv):Windows/Android/Linux 上设置 SO_REUSEADDR,其他 BSD 系平台设置 SO_REUSEPORT(ops.rs L796-L815);同时默认开启广播(set_broadcast(true))并应用 loopback 多播环回参数。
多播一族 ops(join/leave/loopback/ttl/broadcast/validate)全部通过 UdpSocketResource 操作底层 tokio::net::UdpSocket。以 op_net_join_multi_v4_udp 为例(ops.rs L378-L409),它先取 socket 的绑定端口,调用 lib.rs L63-L72 的 check_multicast_membership_permission 做双重检查:一次针对「组地址:端口」的 host-and-port 授权,一次针对解析后 IP 的 deny 规则,然后才调用 socket.join_multicast_v4。op_net_validate_multicast 则是纯校验 op(#[op2(fast)] 同步执行),确保组地址与接口地址都是合法的多播 IPv4。
op_net_send_udp 展示了数据面上的完整权限流程:先 check_net(按传入地址)→ resolve_addr 异步解析 → check_net_resolved(按解析出的 IP)→ send_to(ops.rs L317-L357)。
DNS 解析:hickory 引擎与 Node 兼容的错误码
op_dns_resolve(ops.rs L1123-L1235)是 Deno.resolveDns 的后端,几个实现要点:
- 查询名校验前置:先
Name::from_utf8(&query)解析,失败立即报DnsInvalidName。注释解释了原因——hickory 把格式错误与传输层故障归为同一种ProtoErrorKind,提前解析才能精确分类出 c-ares 风格的EBADNAME; - 名称服务器权限:若指定了自定义
nameServer(默认端口 53),则只查该服务器;否则读系统配置(system_conf::read_system_conf),并对每个实际将被查询的名称服务器逐一执行check_net——DNS 请求本质上是发往外部 UDP 端口的网络访问; - 可取消且低残留:提供
cancel_rid时把 resolver 的timeout收紧为 1 秒、attempts = 1,因为 hickory 的后台连接任务不受 cancel handle 直接中止,短超时能让资源尽快清理; - 记录类型:
DnsRecordData枚举支持 A、AAAA、ANAME、CAA、CNAME、MX、NAPTR、NS、PTR、SOA、SRV、TXT 十二类(ops.rs L1048-L1090);ANY 查询会为每条记录附带record_type字段以区分类型,未知类型在 ANY 场景被静默跳过,显式查询则报UnsupportedRecordType; - Node 兼容错误码:
NetError::ares_code(ops.rs L210-L245)把 hickory 错误映射为 c-ares 风格的ENOTFOUND/ENODATA/EBADNAME,使node:dns的解析错误暴露与 Node.js 相同的code。
ops.rs 尾部测试模块 对这些做了系统覆盖:每种记录类型的序列化(含非法 UTF-8 的有损转换、ANY 查询跳过 RRSIG 等边界),以及 tcp_set_no_delay/tcp_set_keepalive 两个 sockopt 集成测试(先真实 op_net_connect_tcp_inner 建立连接,再通过 socket2::SockRef 断言 nodelay()/keepalive() 确实生效)。
TLS:密钥资源、证书解析器与握手 ops
README 的 TLS ops 列表对应 ops_tls.rs(约 686 行)。从命名结构可以梳理出 Deno 的 TLS 编程模型:
- 密钥抽象:
op_tls_key_null/op_tls_key_static提供「无密钥」与「静态密钥」两种资源,后者对应Deno.listenTls所需的 key/cert 材料(缺 key 时报NetError::ListenTlsRequiresKey,ops.rs L193-L195); - 动态证书解析:
op_tls_cert_resolver_create/poll/resolve/resolve_error四件套构成一个「按需回调」机制——服务端握手需要证书时挂起,JS 侧证书解析器通过poll/resolve异步取回,支持按 SNI 动态选择证书; - 握手:
op_tls_start/op_tls_handshake驱动 rustls 状态机,op_net_connect_tls、op_net_listen_tls、op_net_accept_tls负责在 TLS 之上建立/接受双向流。
信任库一侧则由初始化参数落地:RootCertStoreProvider 通过 DefaultTlsOptions::root_cert_store() 惰性求值(lib.rs L95-L109),rustls-tokio-stream 与 quinn(rustls-aws-lc-rs feature)共同使用同一信任模型。
QUIC 与 WebTransport:懒加载的协议扩展
QUIC 侧共 32 个 ops,围绕 quinn 的三张抽象表展开:
- endpoint:
op_quic_endpoint_create/listen/connect/get_addr/close——端点的生命周期; - connection:
op_quic_connecting_0rtt/1rtt、op_quic_connection_handshake、双向流(open_bi/accept_bi)与单向流(open_uni/accept_uni)、datagram(send_datagram/read_datagram/get_max_datagram_size)、以及get_server_name/get_remote_addr/get_protocol等元信息读取; - incoming:
op_quic_incoming_accept/accept_0rtt/refuse/ignore与local_ip/remote_addr/remote_addr_validated——入站连接的验证与裁决(remote_addr_validated区分「经过地址验证」的远端,是防放大攻击的关键状态)。
WebTransport 只有两个 op(op_webtransport_accept/op_webtransport_connect),实现在 quic.rs 的 webtransport 子模块,协议映射依赖 web-transport-proto crate,把 QUIC 流/数据报语义翻译成 WHATWG WebTransport 接口。
Unix 域套接字、VSOCK 与 Tunnel:权限边界的三种形态
Unix 套接字是权限设计上最讲究的一类。lib.rs L27-L56 的 check_unix_socket_path 注释讲得很清楚:unix socket 既是文件系统条目又是出站网络原语,因此必须同时满足 --allow-net=unix:<path> 规则和对 socket 路径的文件系统读权限——否则只给了 --allow-read=/var/run/docker.sock 的脚本就能无 --allow-net 地连上 Docker/dbus/podman 等本地 IPC 服务。Linux 抽象命名空间路径(以 NUL 开头,is_unix_socket_abstract_path 判定)没有文件系统条目,只做网络侧检查。该函数还被 ext/node 的 PipeWrap 路径复用(Windows 命名管道也走同一入口)。
VSOCK 三个 ops 仅编译进 Android/Linux/macOS(cfg 门禁,依赖 tokio-vsock),需要独立的 "vsock" 特性检查与 check_net_vsock 权限;其余平台注册为返回 VsockUnsupported 的桩(ops.rs L906-L1003)。
Tunnel 两件套(op_net_listen_tunnel/op_net_accept_tunnel,ops.rs L1005-L1046)从全局 tunnel::get_tunnel() 取出当前进程已建立的 tun 设备连接,无连接时报 TunnelMissing——它服务于 Deno 的隧道/代理场景,而非通用 socket。
小结
deno_net 虽然只是一份 README + 十余个源码文件的 crate,却完整承载了 Deno 的网络能力版图:TCP/UDP/Unix/VSOCK 的同步 listen op 与异步 accept/connect op、rustls 之上的 TLS 握手与动态证书、quinn 驱动的 QUIC/WebTransport、hickory 支撑的 DNS。其源码中反复出现的三个模式——双重权限检查(hostname 级 + 解析后 IP 级)、资源表 + CancelHandle 的可取消异步、平台差异的 cfg 桩函数——是阅读 Deno 其他 ext 扩展时的通用范式。如需继续深入,建议按 ext/net/README.md → ext/net/lib.rs → ext/net/ops.rs → ext/net/happy_eyeballs.rs 的顺序阅读,并配合 ops.rs 内的单元测试 验证每一处行为。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00