摘要

  • TCPMUX 让客户端先连接端口 1,再提交服务名;收到正应答后,选中的应用才在同一条连接上开始交谈。它不是同时承载多条应用流的机制。
  • 公共编号并未消失。共同入口、保留名称和旧服务端口仍需遵守统一约定;新增私有服务的程序映射,则由主机自己的配置决定。
  • inetd 可以替目标程序发出正应答。因此,“选中了服务”不能自动升级为“程序已健康运行”,更不能代替身份认证或业务完成凭据。

连接已经通了,却还不知道该找谁

设想一条 TCP 连接已经建立,接收端却没有立即送来邮件问候,也没有显示登录提示。客户端先发一行名字,以回车和换行结束。服务端回答一个加号,随后双方才开始真正的应用协议。这不是服务器犹豫,而是连接入口与具体服务之间刻意留下的一道选择程序。

RFC 1078 在 1988 年 11 月规定了这套 TCP Port Service Multiplexer,简称 TCPMUX。公共入口是 TCP 端口 1;服务名不区分大小写。服务端的答复以加号或减号开头,可以附带解释,也以回车换行结束。正应答以后继续所选协议,负应答以后关闭连接。

这里容易被“多路复用”四个字带偏。客户端不是请求一个新端口,再另开一条连接;也不是把几项业务交错写入带流编号的数据帧。一次选择确定这条连接接下来由哪个应用接手。名字只出现在入口阶段,随后同一条字节流继续使用。它解决的是接待与分派,而非多条并发会话的调度。

这一区别也决定了客户端不能毫无准备。一个原本直接连到应用端口、等待服务器先开口的老程序,不会因为服务器增加了 TCPMUX 就突然懂得先报服务名。共同入口节省了编号,却增加了一段双方都必须认识的开场白。

从公共号码簿退回本地配置

当年的问题有具体背景。RFC 1010 是 1987 年 5 月发布的 Assigned Numbers,也是 RFC 1078 明确引用的编号表。它记录各种协议编号,并提供申请协调的联系人。独立实现需要这样的公共参考,否则同一个端口可能被各自理解为不同服务。

RFC 1078 把当时熟知端口的范围写成 0 至 255。这是历史语境,不能据此说 TCP 总共只能容纳 256 个端口,或把这个范围当作今天的分类。TCP 的端口字段与某一时期被集中分配的熟知端口区间不是一回事。后来 RFC 6335 规定的体系区分系统端口、用户端口与动态端口,边界已经不同。

TCPMUX 的动作,是让一项私有协议不必先取得自己的正式 TCP 端口编号,就能使用现成入口。主机收到名字,在自己的服务表中找程序。全网共同约定的是如何到达入口、如何读取选择结果;某个私有名字在这台主机上要启动什么,则可以成为本地决定。

这不是取消协调,而是缩小必须共同协调的部分。公共编号表仍然有价值,主机管理员也仍然有权拒绝某项服务。改变的是决策边界:设计一种本地新服务,不必与为所有互不相识的客户端建立统一号码产生同样的程序负担。

老端口不能被共同入口悄悄撤走

规范并未把端口 1 描绘成可以取代所有旧入口的万能通道。已经拥有独立分配端口的服务,必须继续在那些端口提供服务;经由 TCPMUX 提供访问只是可选的补充。保留名称也必须保持 Assigned Numbers 里的原有含义。

这一条比一行服务名更能说明设计的约束。新机制可以让愿意采用的客户端走新入口,却不能要求不认识它的旧客户端突然改写开场方式。新增能力与撤销既有兼容性,被明确分成两件事。

私有名称同样不是随便起就够了。RFC 1078 建议采用较不易冲突的名字,例如加上组织名称前缀;不同版本可以加版本后缀。前缀是一种降低偶然重名的办法,不是组织身份证明,也没有自动核验域名所有权的效果。名字的可读性不能替代谁控制配置、谁实际运行程序的证据。

另一项保留名称是 HELP。请求它会得到逐行列出的受支持服务名,随后连接关闭。这是当前主机的一张菜单,而非全球服务目录。菜单可以帮助客户端发现选择项,却不能证明每个项目此刻都健康、当前用户都获授权,或者名字背后的软件从未被更换。

那个加号究竟是谁发的

实现手册把抽象的控制边界落到了具体配置上。NetBSD 的 inetd 手册 描述,TCPMUX 从 /etc/inetd.conf 提供的服务名表中查找目标。普通 tcpmux/ 形式下,被调用的服务器应当自己发出正应答;使用 tcpmux/+ 前缀时,则由复用器替它回答。

这项兼容安排使一些通过标准输入、标准输出工作的旧程序不必增加专门的服务端开场代码。对管理员来说,它降低了把本地程序接到网络入口的成本;对分析连接的人来说,它改变了加号的证据含义。同样一个正号,可能来自目标程序,也可能来自负责把连接交给程序的中间步骤。

因此,抓包看到加号,最多应先记录选择阶段的肯定结果。它不是一份独立健康证明,没有说明应用已读到后续请求、已核验用户,或已完成写入。若程序启动失败、随后退出,或者双方对谁该发加号理解不同,入口成功仍可能变成应用层失败。

这里还有一个常见的责任缝隙:双方都发加号,会让应用读到多余内容;双方都以为对方负责,客户端则可能一直等候选择应答。这个风险是协议交接关系的直接推论,不需要编造一次历史事故来使它成立。

FreeBSD 的 inetd 手册源码 进一步指出,启用具体 TCPMUX 服务之外,还要启用复用器本身。写好目标服务的配置,并不等于公共入口已经开放。手册把它用于本地开发的服务器,这证明存在明确的实现与配置路径;它并不告诉我们全网有多少台机器实际采用了这一功能。

号码保留下来,不能代替使用记录

今天的 IANA 服务名与端口注册表 仍能找到端口 1 的 tcpmux 条目。表里同时列有 TCP 和 UDP 行,但 RFC 1078 描述的是 TCP 连接上的交换,不能从并列登记推导出一套该文没有定义的 UDP 对话。

登记持续存在,也不能变成“该机制仍广泛部署”的证据。编号保留、手册保留、配置启用、客户端采用以及实际流量,是不同层次的材料。本篇依据足以解释设计和实现,却不支持为 TCPMUX 编出成功率、市场占有率或确定的衰落原因。

2015 年的 RFC 7605 讨论节约端口、在协议内部区分版本,以及使用带内信息进行分派。这说明“每增加一种功能,不一定增加一个公共端口”是一个持续存在的工程问题。它不等于证明后来的指导直接源自 TCPMUX。

TCPMUX 留下的清晰切面,是让统一入口止步于必要的互通约定,把具体程序选择留在主机。但本地决定也要对本地后果负责:服务名不能替代权限,加号不能替代完成,公开文档不能替代真实采用。端口 1 可以把连接送到门内;接下来发生什么,仍取决于双方实际运行的程序。