kin · rendezvous + directory

会合与目录服务

它做两件事:让两个都在 NAT 后面的朋友互相知道对方当前的外部地址; 以及让你能按对方的 ID 找到他、给他发一条好友请求。 之后它就无事可做了 —— 隧道是两台设备之间直接建立的,一个字节都不经过这里。

为什么需要它

kin 是朋友之间互相提供网络出口的 VPN。要让两台设备直接相连,双方必须 同时 知道对方此刻的外部地址,才能同时朝对方发包、把 NAT 上的洞打开。

麻烦在于:双方都在 NAT 后面,谁也不可达 —— 没有人能把这个地址直接递给对方。 所以需要一个在公网上的、双方都能连上的撮合方,把两张名片放进同一个抽屉里。 这就是这个服务,也仅此而已。

按 ID 找人

原来加朋友要复制一串 200 字符的邀请码。现在还有一条路:你可以把自己登记进来 —— 一个公钥指纹、一把公钥、一个显示名 —— 对方知道你的指纹 (形如 4F0J-3Q4Y-NC8H-QBPQ-0SGG)就能查到你,并发来一条好友请求。

只能按指纹精确查。没有按名字搜索,没有列出所有人。 这不是还没做,是故意不做:指纹是 100 bit,不可枚举,所以 ID 本身就是凭据 —— 像电话号码,没有号码就找不到人,也翻不出号码簿。

好友请求带一个 MAC,是发送方用「自己的私钥 + 你的公钥」算出来的。 这个服务验证不了它(它没有任何人的私钥),验证在你自己的设备上做 —— 所以它也没有能力伪造一条「某人想加你」。

代价要说清楚:用了目录,这个服务就知道了「谁存在、谁的显示名、谁在找谁」, 这是纯邀请码方案没有的元数据。所以登记是可选的、24 小时就过期、 随时可以删掉;邀请码那条路也一直留着,给最在意的人。 它也不认识任何人 —— 知道你指纹的人能看到发给你的好友请求, 所以那里只放「谁想加你」这种东西。真正要紧的那道门在隧道上: 没有你亲手批准的公钥,谁也进不来。

它看得到什么,看不到什么

✓ 它看得到

  • 一个随机的一次性 token(配对码里带的,不含任何身份信息)
  • 一个公钥指纹和一个 base64 公钥
  • 最多 16 个 ip:port 候选地址
  • 一个你自己填的显示名,比如「哥哥的台式机」
  • 如果你登记了目录:你的指纹、公钥、显示名,和谁在查谁
  • 发给你的好友请求:对方的指纹、公钥、显示名和一句留言

✕ 它看不到

  • 你的流量内容 —— 它不在数据路径上
  • 你访问了什么网站、流量去了哪里
  • 你是谁:没有账号、没有注册、没有用户表
  • 这次握手最后成没成 —— 打洞是两端之间的事

它不留什么

会合记录的 TTL 是 300 秒,到点自己消失 —— 会合是短暂的,不该留痕。 目录登记 24 小时(停了就自动消失,所以它永远不会留着你早已不用的 那台机器),好友请求 7 天(够对方开机看一眼),处理完立刻删除。

它不记录客户端 IP,不写访问日志,没有分析脚本 —— 这一页连 JavaScript 都没有。 服务端的全部状态就是一个 KV namespace:没有数据库,没有队列,没有一个 secret, 因为它不需要知道任何需要保密的东西。

它也不能偷偷变成中继:这个服务只收发几条短 JSON, 不具备转发 UDP 的能力。流量要么直连,要么根本不通。

接口

就这些。超额或畸形的输入一律拒绝。

POST /api/rv
  → {"token":"…","endpoint":{"peer_id":…,"pub_key":…,
                             "candidates":["203.0.113.7:51820"],"name":…}}
  ← 200 {"ok":true}

GET  /api/rv?token=…&self=…
  ← 200 {"ready":true,"endpoint":{…}}   // 该 token 下不是你自己的那条
  ← 200 {"ready":false}                 // 对端还没来,等一会儿再问

POST   /api/dir       {"record":{…}}            // 登记,24 小时后过期
DELETE /api/dir?fp=…                            // 注销
GET    /api/dir?fp=…  → {"record":{…}} / 404    // 按指纹精确查,没有别的查法

POST /api/req         {"to_fp":…,"request":{…}} // 发一条好友请求,7 天后过期
GET  /api/req?fp=…    → {"requests":[…]}        // 收件箱,每人最多 32 条
POST /api/req/ack     {"fp":…,"id":…}           // 处理完就删

自己跑一个

这个服务是开源的,也刻意做得很小 —— 小到你可以读完全部代码, 然后部署成你自己的那一个:只需要一个 Cloudflare 账号和一条 wrangler pages deploy。 不用相信别人的会合服务,是这件事里最重要的一部分。