43/ZRE

43/ZRE

ZeroMQ实时交换协议

  • 状态:草案
  • 编辑

ZeroMQ 实时交换协议 (ZRE) 规定了如何在网络上的一组对等方之间进行发现、组织成组以及相互发送事件。ZRE 运行在 ZeroMQ 消息传输协议 (ZMTP) 之上。

前言

版权所有 (c) 2009-2014 iMatix Corporation 版权所有 (c) 2017 Barely3am

本规范是自由软件;您可以根据自由软件基金会发布的 GNU 通用公共许可证的条款重新分发和/或修改它;该许可证的第3版,或者(由您选择)任何后续版本。分发本规范是希望它能够有用,但没有任何担保;甚至不包括适销性或特定用途适用性的隐含担保。有关更多详细信息,请参阅 GNU 通用公共许可证。您应该已经随本程序收到了 GNU 通用公共许可证的副本;如果没有,请参阅 https://gnu.ac.cn/licenses

本规范是一个自由开放标准,并受数字标准组织 共识导向规范系统 的管辖。

本文档中的关键词“MUST”、“MUST NOT”、“REQUIRED”、“SHALL”、“SHALL NOT”、“SHOULD”、“SHOULD NOT”、“RECOMMENDED”、“MAY”和“OPTIONAL”应按照 RFC 2119 中的描述进行解释。

目标

ZRE 协议提供了一种方式,使得本地网络上的一组节点能够相互发现、跟踪对等方的进出、向单个对等方发送消息(单播)以及向对等方组发送消息(多播)。其目标是:

  • 在网络上默认可用的服务和中介之外,无需任何集中式服务或中介即可工作。
  • 在低质量网络上也能健壮运行,尤其是无线网络。
  • 最大程度地缩短检测到新对等方加入网络所需的时间。
  • 能够从瞬时连接故障中恢复。
  • 对操作系统、编程语言和硬件保持中立。
  • 允许任意数量的节点在一个进程中运行,以便进行大规模仿真和测试。
  • 为希望在开放互联网上扩展 ZYRE 的实现者提供一个启用 CURVE 的传输框架。

相较于版本2的更改

这是 ZRE 的版本 3。相较于版本 2 的更改包括:

  • 版本号增加到 3。
  • 信标数据包支持广告一个 32 字节的 CURVE 公钥,以便为连接建立启用 CURVE 的安全性。
  • 通过 IPv6 多播进行 UDP 发现

实现

节点标识和生命周期

一个 ZRE 节点代表消息的来源或目标。节点通常映射到应用程序。ZRE 节点由一个 16 字节的通用唯一标识符 (UUID) 标识。ZRE 不定义节点的创建或销毁方式,但假定节点具有一定的持久性。

节点发现与存在

ZRE 使用 UDP IPv4/IPv6 信标广播来发现节点。每个 ZRE 节点应监听 ZRE 发现服务,该服务使用 UDP 端口 5670(IANA 分配的 ZRE-DISC 端口)。每个 ZRE 节点应在 UDP 端口 5670 上以固定间隔广播一个信标,以向网络上任何监听节点标识自己。

ZRE 信标由一个 54 字节的 UDP 消息组成,格式如下:

+---+---+---+------+  +------+------+------------------+
| Z | R | E | %x01 |  | UUID | port | CURVE PUBLIC KEY |
+---+---+---+------+  +------+------+------------------+

       Header                    Body

头部应包含字母 'Z'、'R' 和 'E',后跟信标版本号,该版本号应为 %x03。

主体应包含发送方的 16 字节 UUID,后跟一个按网络字节顺序排列的 2 字节邮箱端口号,再后跟一个可选的 32 字节 CURVE 公钥。如果端口号非零,这表明对等方将在该端口号上接受 ZeroMQ TCP 连接。如果端口号为零,这表明对等方正在断开与网络的连接。

有效的信标应使用已识别的头部和正确大小的主体。收到无效信标的节点应静默丢弃它。节点可以记录发送方 IP 地址用于调试目的。节点应丢弃从自身收到的信标。

当一个 ZRE 节点从一个它尚不认识的节点收到信标,且端口号非零时,它应将其视为一个新的对等方。

当一个 ZRE 节点从一个已知的节点收到信标,且端口号为零时,它应断开与该对等方的连接。

互连模型

每个节点应创建一个 ZeroMQ ROUTER 套接字并将其绑定到一个临时 TCP 端口(范围在 %C000x - %FFFFx)。节点应在所有发送的信标中广播此邮箱端口号。注意,节点不广播其 IP 地址,因为这是由 UDP recvfrom 函数提供的。

此 ROUTER 套接字应用于接收来自其他节点的所有 ZeroMQ 消息。节点不应通过此套接字向对等方发送消息。

当节点发现新的对等方时,它应创建一个 ZeroMQ DEALER 套接字,在该套接字上设置其身份(包含 1 的一个字节,后跟 16 字节 UUID),并将其连接到对等方的邮箱端口。节点可以在连接后立即通过此 DEALER 套接字开始向对等方发送消息。

节点应将每个 DEALER 套接字连接到至多一个对等方。如果对等方在一段时间内未能响应(参见心跳机制),节点可以断开其 DEALER 套接字。

此 DEALER 套接字应用于发送给特定对等方的所有 ZeroMQ 消息。节点不应在此套接字上接收消息。发送方可以设置一个高水位标记 (HWM),例如每秒 100 条消息(如果超时时间为 30 秒,这意味着 HWM 为 3,000 条消息)。发送方宜将套接字上的发送超时设置为零,以便能够检测到发送缓冲区已满并将其视为“对等方无响应”。

注意,ROUTER 套接字为调用方提供在套接字上收到的任何消息的发送方 UUID,作为消息中位于其他帧之前的一个身份帧。因此,对等方可以使用收到消息中的身份来查找用于向该对等方发送消息的相应 DEALER 套接字。身份应是一个 17 字节的二进制 UUID 值,其中第一个字节始终为 1。

当节点在其 ROUTER 套接字上收到来自未知节点的有效消息时,它应将其视为一个新对等方,处理方式与收到来自未知节点的 UDP 信标完全相同。

注意:ZRE 使用的 ROUTER-到-DEALER 模式旨在确保消息不会因同步问题而丢失。向尚未连接到对等方的 ROUTER 套接字发送消息将导致消息被丢弃。

协议签名

每个通过 TCP 发送的 ZRE 消息应以 ZRE 协议签名 %xAA %xA1 开头。节点应静默丢弃收到的不以这两个字节开头的任何消息。

此机制专门为那些绑定到可能先前被其他协议使用过、且仍有节点尝试连接的临时端口的应用程序设计。它也是一种通用的快速失败机制,用于检测格式错误的消息。

版本控制

签名后应跟随一个版本号字节 %x03。节点应丢弃不包含有效版本号的消息。没有向后兼容的机制。

TCP协议语法

以下 ABNF 语法定义了 ZRE 协议:

zre             = greeting *traffic
greeting        = hello
traffic         = whisper / shout / join / leave / ping / ping-ok

;     Greet a peer so it can connect back to us
hello           = signature %d1 version sequence endpoint groups status name headers
signature       = %xAA %xA1             ; two octets
version         = number-1              ; Version number (3)
sequence        = number-2              ; Cyclic sequence number
endpoint        = string                ; Sender connect endpoint
groups          = strings               ; List of groups sender is in
status          = number-1              ; Sender groups status value
name            = string                ; Sender public name
headers         = dictionary            ; Sender header properties

;     Send a multi-part message to a peer
whisper         = signature %d2 version sequence content
version         = number-1              ; Version number (3)
sequence        = number-2              ; Cyclic sequence number
content         = msg                   ; Wrapped message content

;     Send a multi-part message to a group
shout           = signature %d3 version sequence group content
version         = number-1              ; Version number (3)
sequence        = number-2              ; Cyclic sequence number
group           = string                ; Group to send to
content         = msg                   ; Wrapped message content

;     Join a group
join            = signature %d4 version sequence group status
version         = number-1              ; Version number (3)
sequence        = number-2              ; Cyclic sequence number
group           = string                ; Name of group
status          = number-1              ; Sender groups status value

;     Leave a group
leave           = signature %d5 version sequence group status
version         = number-1              ; Version number (3)
sequence        = number-2              ; Cyclic sequence number
group           = string                ; Name of group
status          = number-1              ; Sender groups status value

;     Ping a peer that has gone silent
ping            = signature %d6 version sequence
version         = number-1              ; Version number (3)
sequence        = number-2              ; Cyclic sequence number

;     Reply to a peer's ping
ping_ok         = signature %d7 version sequence
version         = number-1              ; Version number (3)
sequence        = number-2              ; Cyclic sequence number

; A list of string values
strings         = strings-count *strings-value
strings-count   = number-4
strings-value   = longstr

; A list of name/value pairs
dictionary      = dict-count *( dict-name dict-value )
dict-count      = number-4
dict-value      = longstr
dict-name       = string

; A msg is zero or more distinct frames
msg             = *frame

; Strings are always length + text contents
string          = number-1 *VCHAR
longstr         = number-4 *VCHAR

; Numbers are unsigned integers in network byte order
number-1        = 1OCTET
number-2        = 2OCTET
number-4        = 4OCTET

ZRE命令

所有命令都以协议签名 (%xAA %xA1) 开头,然后是命令标识符,接着是协议版本号 (%d3),最后是序列号(按网络字节顺序排列的两个字节)。来自对等方的第一个消息 (HELLO) 必须具有序列号 1,且每条消息的序列号必须严格递增。当对等方检测到序列中的空隙或乱序消息时,它应将该对等方视为无效,并断开连接。

HELLO 命令

每个节点应通过在连接到对等方时发送 HELLO 作为第一个命令来启动对话。

当节点收到来自新对等方的消息时,它应静默忽略任何在 HELLO 命令之前的命令。HELLO 的序列号应为 1。

HELLO 命令包含以下字段:

  • 端点- 发送方将接受连接的 ZeroMQ 端点。
  • - 发送方所在的组列表,表示为字符串列表。
  • 状态- 发送方的组状态序列号。
  • 头部- 发送方设置的零个或多个属性。

如果接收方尚未连接到此对等方,它应创建一个 ZeroMQ DEALER 套接字并将其连接到指定为“tcp://ipaddress:mailbox”的端点。

“组状态序列号”是一个单字节数字,每当对等方加入或离开组时都会递增。每个对等方可以使用它来断言其自身组管理信息的准确性。

WHISPER 命令

当节点希望向单个对等方发送消息时,它应使用 WHISPER 命令。WHISPER 命令包含一个字段,即定义为 0MQ 多部分消息的消息内容。

SHOUT 命令

当节点希望向参与组的一组节点发送消息时,它应使用 SHOUT 命令。SHOUT 命令包含两个字段:组的名称,以及定义为 0MQ 多部分消息的消息内容。

请注意,消息通过 ZeroMQ 经 TCP 发送,因此 SHOUT 命令会单播到每个应该接收它的对等方。ZRE 不提供任何 UDP 多播功能。

节点可以向其自身尚未加入的组发送消息。

JOIN 命令

当节点加入组时,它应向所有对等方广播 JOIN 命令。JOIN 命令有两个字段:要加入的组的名称,以及加入组的组状态序列号。组名区分大小写。

LEAVE 命令

当节点离开组时,它应向所有对等方广播 LEAVE 命令。LEAVE 命令有两个字段:要离开的组的名称,以及离开组的组状态序列号。

PING 命令

节点宜向在一定时间内(通常为五秒)未收到其 UDP 信标的任何对等方发送 PING 命令。注意,在网络严重拥塞时,UDP 流量可能会被丢弃。如果节点在 PING 命令发出后以及稍长一段时间(通常为 30 秒)内未收到来自对等方的任何回复或其他流量,它宜将该对等方视为已死亡。

请注意,PING 命令宜仅在对等方在其他方面保持静默的特定情况下使用。否则,PING 命令的开销将随连接的对等方数量呈指数级增长,并可能降低网络性能。

PING-OK 命令

当节点收到 PING 命令时,它应回复一个 PING-OK 命令。

节点发现与存在

ZRE 使用 UDP IPv4/IPv6 信标广播来发现节点并跟踪它们的存在。其工作原理如下:

  • 一个 ZRE 节点应监听 ZRE 发现服务,该服务是 UDP 端口 5670(IANA 分配的 ZRE-DISC 端口),通过 IPv4 广播地址或 IPv6 多播地址进行。
  • 每个 ZRE 节点应通过 IPv4 广播地址或 IPv6 多播地址,以固定间隔广播一个 UDP 信标,以向网络上任何监听节点标识自己。
  • 当一个 ZRE 节点收到来自它尚不认识的节点的信标时,它应将其视为一个新的对等方。
  • 当一个 ZRE 节点停止收到来自已知对等方的信标时,经过一定间隔后,它应将该对等方视为已断开连接或已死亡。
  • 配置了 CURVE 安全的 ZRE 节点应忽略不包含有效的 32 字节 CURVE 密钥的信标。

ZRE 信标由一个 54 字节的 UDP 消息组成,格式如下:

+---+---+---+------+  +------+------+------------------+
| Z | R | E | %x03 |  | UUID | port | CURVE PUBLIC KEY |
+---+---+---+------+  +------+------+------------------+

       Header                    Body

给实现者的说明

  • 头部应包含字母 'Z'、'R' 和 'E',后跟信标版本号,该版本号应为 %x03。
  • 主体应包含发送方的 16 字节 UUID,后跟一个按网络字节顺序排列的 2 字节邮箱端口号,再后跟一个可选的 CURVE 32 字节公钥。
  • 一个有效的信标应:使用已识别的头部;使用正确大小的主体;并提供一个非零的邮箱端口号。
  • 收到无效信标的节点应静默丢弃它。节点可以记录发送方 IP 地址用于调试目的。
  • 节点应丢弃从自身收到的信标。
  • 节点在 CURVE 模式下运行时,应忽略 v2 信标以避免降级攻击。

安全方面

  • ZRE 的安全性由底层传输处理。
  • 对于 ZMTP v2 和 v1,没有安全模型,所有信息都以明文交换。
  • 对于 ZMTP v3,可以使用任何已定义的安全性机制。
  • 需要某种形式授权(CURVE 或其他)的应用程序,应通过使用 ZAP 或基于应用程序中 ZYRE 事件的自定义钩子来处理此问题。
  • 所有对等方在可能的情况下宜具有唯一的密钥,但这并非强制要求。