kin · rendezvous + directory + message relay
会合、目录与消息中继
它做三件事:让两个都在 NAT 后面的朋友互相知道对方当前的外部地址; 让你能按对方的 ID 找到他、给他发一条好友请求; 以及在你们还没连上的时候,替你们搬一个个它打不开的信封。 隧道流量一个字节都不经过这里 —— 那是两台设备之间直接建立的。
为什么需要它
kin 是朋友之间互相提供网络出口的 VPN。要让两台设备直接相连,双方必须 同时 知道对方此刻的外部地址,才能同时朝对方发包、把 NAT 上的洞打开。
麻烦在于:双方都在 NAT 后面,谁也不可达 —— 没有人能把这个地址直接递给对方。 所以需要一个在公网上的、双方都能连上的撮合方,把两张名片放进同一个抽屉里。 这就是这个服务,也仅此而已。
按 ID 找人
原来加朋友要复制一串 200 字符的邀请码。现在还有一条路:你可以把自己登记进来
—— 一个公钥指纹、一把公钥、一个显示名 —— 对方知道你的指纹
(形如 4F0J-3Q4Y-NC8H-QBPQ-0SGG)就能查到你,并发来一条好友请求。
只能按指纹精确查。没有按名字搜索,没有列出所有人。 这不是还没做,是故意不做:指纹是 100 bit,不可枚举,所以 ID 本身就是凭据 —— 像电话号码,没有号码就找不到人,也翻不出号码簿。
好友请求带一个 MAC,是发送方用「自己的私钥 + 你的公钥」算出来的。 这个服务验证不了它(它没有任何人的私钥),验证在你自己的设备上做 —— 所以它也没有能力伪造一条「某人想加你」。
代价要说清楚:用了目录,这个服务就知道了「谁存在、谁的显示名、谁在找谁」, 这是纯邀请码方案没有的元数据。所以登记是可选的、24 小时就过期、 随时可以删掉;邀请码那条路也一直留着,给最在意的人。 它也不认识任何人 —— 知道你指纹的人能看到发给你的好友请求, 所以那里只放「谁想加你」这种东西。真正要紧的那道门在隧道上: 没有你亲手批准的公钥,谁也进不来。
还没连上的时候,消息走这里
聊天不该依赖隧道。两个都在 NAT 后面的人并不总能连通 (一边只有 IPv4、另一边只有 IPv6 的蜂窝网就打不通), 而恰恰是那种时候最需要能说一句「我连不上你」。 所以消息默认走这里:投进来、躺最多 7 天、被对方取走、立刻删掉。
消息在哪条路上都是加密的。 信封是用「发送方的私钥 + 收件方的公钥」算出来的密钥封好的, 而这个服务不持有任何人的私钥 —— 它算不出那把密钥。 这不是「我们承诺不看」,是看不了。它能看到的只有信封外面那两个指纹和投递时间。
所以两条路的区别是元数据,不是加密强度: 走中继,这个服务知道「这两个指纹在通信」;走直连,它连这件事都不知道。 客户端里那个开关切的是这个,不是「安全 / 不安全」。
代价一样要说清楚:它不认识任何人,也没有任何可以用来鉴权的东西。 于是知道你指纹的人能往你的信箱里投信封、能看到谁在给你发(读不懂内容), 也能把你还没取走的信封删掉。 每人信箱最多 256 个信封,满了就拒收 —— 别让它变成垃圾桶。
它看得到什么,看不到什么
✓ 它看得到
- 一个随机的一次性
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":…} // 处理完就删
POST /api/msg {"envelope":{…}} // 投一个加密信封,7 天后过期
GET /api/msg?fp=…&since=…
→ {"envelopes":[…],"more":false} // 取发给你的,一页最多 32 个
POST /api/msg/ack {"fp":…,"ids":[…]} // 取到了就删
自己跑一个
这个服务是开源的,也刻意做得很小 —— 小到你可以读完全部代码,
然后部署成你自己的那一个:只需要一个 Cloudflare 账号和一条
wrangler pages deploy。
不用相信别人的会合服务,是这件事里最重要的一部分。