首页
/ Deno deno_net 扩展深度解析:TCP、UDP、TLS、QUIC 与 Unix 套接字的 op 实现全貌

Deno deno_net 扩展深度解析:TCP、UDP、TLS、QUIC 与 Unix 套接字的 op 实现全貌

2026-09-04 23:06:55作者:何将鹤

本文以 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.js02_tls.js03_quic.js)封装成用户可见的 Deno.connectDeno.listenDeno.listenTlsDeno.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_webdeno_web crate 提供,deno_fetchdeno_fetch crate 提供;在 lib.rsextension! 宏也确实标注了 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.rslazy_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.rsoptions 块中有完整签名:

  • root_cert_store_provider: Option<Arc<dyn RootCertStoreProvider>> —— 延迟提供根证书信任库的 trait 对象,存为 DefaultTlsOptionslib.rs L95-L109),调用 root_cert_store() 时才真正解析;
  • unsafely_ignore_certificate_errors: Option<Vec<String>> —— 按 hostname 忽略证书错误的白名单,用专门的结构体 UnsafelyIgnoreCertificateErrors 包装存入 GothamStatelib.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.rsextension! 宏的 ops = [...] 注册列表逐一吻合:

Net

  • op_net_accept_tcp
  • op_net_get_ips_from_perm_token
  • op_net_connect_tcp
  • op_net_listen_tcp
  • op_net_listen_udp
  • op_net_recv_udp
  • op_net_send_udp
  • op_net_join_multi_v4_udp
  • op_net_join_multi_v6_udp
  • op_net_leave_multi_v4_udp
  • op_net_leave_multi_v6_udp
  • op_net_set_multi_loopback_udp
  • op_net_set_multi_ttl_udp
  • op_net_set_broadcast_udp
  • op_net_validate_multicast
  • op_net_get_system_dns_servers
  • op_net_listen_vsock
  • op_net_accept_vsock
  • op_net_connect_vsock
  • op_net_listen_tunnel
  • op_net_accept_tunnel

TLS

  • op_tls_key_null
  • op_tls_key_static
  • op_tls_cert_resolver_create
  • op_tls_cert_resolver_poll
  • op_tls_cert_resolver_resolve
  • op_tls_cert_resolver_resolve_error
  • op_tls_start
  • op_tls_handshake

QUIC

  • op_quic_connecting_0rtt
  • op_quic_connecting_1rtt
  • op_quic_connection_accept_bi
  • op_quic_connection_accept_uni
  • op_quic_connection_close
  • op_quic_connection_closed
  • op_quic_connection_get_protocol
  • op_quic_connection_get_remote_addr
  • op_quic_connection_get_server_name
  • op_quic_connection_handshake
  • op_quic_connection_open_bi
  • op_quic_connection_open_uni
  • op_quic_connection_get_max_datagram_size
  • op_quic_connection_read_datagram
  • op_quic_connection_send_datagram
  • op_quic_endpoint_close
  • op_quic_endpoint_connect
  • op_quic_endpoint_create
  • op_quic_endpoint_get_addr
  • op_quic_endpoint_listen
  • op_quic_incoming_accept
  • op_quic_incoming_accept_0rtt
  • op_quic_incoming_ignore
  • op_quic_incoming_local_ip
  • op_quic_incoming_refuse
  • op_quic_incoming_remote_addr
  • op_quic_incoming_remote_addr_validated
  • op_quic_listener_accept
  • op_quic_listener_stop
  • op_quic_recv_stream_get_id
  • op_quic_send_stream_get_id
  • op_quic_send_stream_get_priority
  • op_quic_send_stream_set_priority

WebTransport

  • op_webtransport_accept
  • op_webtransport_connect

Other

  • op_node_unstable_net_listen_udp
  • op_dns_resolve
  • op_set_nodelay
  • op_set_keepalive
  • op_node_unstable_net_listen_unixpacket

另有 6 个 Unix 域套接字 ops(op_net_accept_unixop_net_connect_unixop_net_listen_unixop_net_listen_unixpacketop_net_recv_unixpacketop_net_send_unixpacket,README 将其归入 Net 列表)只在 unix 平台注册,实现在 ops_unix.rs;在非 unix 平台上 lib.rs L218-L246stub_op! 宏生成同名桩函数,直接返回 Unsupported 错误——这是该扩展保证跨平台 op 表一致的常见手法。

TCP 连接:权限令牌、Happy Eyeballs 与取消语义

op_net_connect_tcp 是理解 deno_net 设计思路的枢纽,实现在 ops.rs L588-L725。它体现了三层工程细节:

1. 基于 NetPermToken 的权限归因。 连接前先做 check_net(检查原始 hostname),而 JS 层在域名解析阶段就会生成一个 NetPermTokenops.rs L547-L579),保存「原始 hostname + 已解析的 IP 列表」。当后续按解析出的某个 IP 发起连接时,token.check_host() 会把它映射回原始 hostname 再做权限判定——op_net_get_ips_from_perm_token 正是 JS 侧获取该列表用的 op。

2. 解析后二次校验(deny 规则不可绕过)。 解析完成后,对所有可能实际连接的候选地址再执行 check_net_resolvedops.rs L657-L677)。源码注释点明了防御目标:防止用数字主机名别名(如 2130706433127.0.0.1)绕过针对 IP 字面量的 deny 规则。

3. Happy Eyeballs(RFC 8305)。auto_select_family 开启且解析出多个地址时走 connect_happy_eyeballshappy_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_tcpops.rs L742-L773)接收 reuse_portload_balancedtcp_backlog 三个参数,其中 reusePort: true 属于不稳定特性——触发 check_unstable(特性名 "net",见 lib.rs L25UNSTABLE_FEATURE_NAME),即需要 --unstable-net 之类的开关才能使用。

监听器由 tcp.rsTcpListener 封装,其平台策略值得注意:

  • Linux/Android:内核的 SO_REUSEPORT 自带负载均衡语义,TcpListener::bindbind_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_REUSEPORTops.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-L72check_multicast_membership_permission双重检查:一次针对「组地址:端口」的 host-and-port 授权,一次针对解析后 IP 的 deny 规则,然后才调用 socket.join_multicast_v4op_net_validate_multicast 则是纯校验 op(#[op2(fast)] 同步执行),确保组地址与接口地址都是合法的多播 IPv4。

op_net_send_udp 展示了数据面上的完整权限流程:先 check_net(按传入地址)→ resolve_addr 异步解析 → check_net_resolved(按解析出的 IP)→ send_toops.rs L317-L357)。

DNS 解析:hickory 引擎与 Node 兼容的错误码

op_dns_resolveops.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_codeops.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::ListenTlsRequiresKeyops.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_tlsop_net_listen_tlsop_net_accept_tls 负责在 TLS 之上建立/接受双向流。

信任库一侧则由初始化参数落地:RootCertStoreProvider 通过 DefaultTlsOptions::root_cert_store() 惰性求值(lib.rs L95-L109),rustls-tokio-streamquinnrustls-aws-lc-rs feature)共同使用同一信任模型。

QUIC 与 WebTransport:懒加载的协议扩展

QUIC 侧共 32 个 ops,围绕 quinn 的三张抽象表展开:

  • endpointop_quic_endpoint_create/listen/connect/get_addr/close——端点的生命周期;
  • connectionop_quic_connecting_0rtt/1rttop_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 等元信息读取;
  • incomingop_quic_incoming_accept/accept_0rtt/refuse/ignorelocal_ip/remote_addr/remote_addr_validated——入站连接的验证与裁决(remote_addr_validated 区分「经过地址验证」的远端,是防放大攻击的关键状态)。

WebTransport 只有两个 op(op_webtransport_accept/op_webtransport_connect),实现在 quic.rswebtransport 子模块,协议映射依赖 web-transport-proto crate,把 QUIC 流/数据报语义翻译成 WHATWG WebTransport 接口。

Unix 域套接字、VSOCK 与 Tunnel:权限边界的三种形态

Unix 套接字是权限设计上最讲究的一类。lib.rs L27-L56check_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/nodePipeWrap 路径复用(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_tunnelops.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.mdext/net/lib.rsext/net/ops.rsext/net/happy_eyeballs.rs 的顺序阅读,并配合 ops.rs 内的单元测试 验证每一处行为。

登录后查看全文
热门项目推荐
相关项目推荐