Bridges

Forward topics between this broker and others: from an edge site to a central broker, from a cloud broker back to devices, or between data centres.

A first bridge

A site broker forwarding its sensor readings to a central broker, and receiving commands from it:

[[bridges]]
name = "central"
host = "central.example.com"
port = 1883
username = "site1"
password = "secret"

# Readings go out
[[bridges.topics]]
pattern = "sensors/#"
direction = "out"
qos = 1

# Commands come in
[[bridges.topics]]
pattern = "commands/#"
direction = "in"
qos = 1

The bridge connects to the central broker as an MQTT client with the id mercurio-bridge-central, or client_id when set. Each bridge needs its own name and client id, or the two would keep taking over each other's session; with notifications on, the client id is a level of the state topic, so it can't hold /, + or #. Messages published here on sensors/# are published there, and messages published there on commands/# are published here.

Delivery

A bridge forwards with up to 100 messages in flight in each direction, within the Receive Maximum of the broker receiving them, so a long link doesn't cost a round trip per message. Messages keep their order in each direction. QoS 1 and 2 messages are delivered at least once: see below for what happens when a connection drops.

Topics and prefixes

Each [[bridges.topics]] entry forwards pattern out (the default), in or both ways. The prefixes place the pattern in each broker's topic tree, so topics are renamed on the way:

[[bridges.topics]]
pattern = "sensors/#"
direction = "out"
local_prefix = "site/"
remote_prefix = "plants/plant1/"
This brokerRemote broker
site/sensors/t1plants/plant1/sensors/t1

With MQTT 5, retained messages keep their retain flag across the bridge, and properties such as user properties and response topics travel with the message. MQTT 3.1.1 carries neither; there, only the retained messages a broker sends when the bridge subscribes arrive as retained.

Both directions without loops

With direction = "both", a message forwarded one way could come back the other. With MQTT 5, the bridge subscribes with No Local, so the brokers never send it its own messages. A message crosses once, or twice when a connection drops while it is under way.

MQTT 3.1.1 brokers

[[bridges]]
name = "legacy"
host = "old-broker.example.com"
protocol_version = "mqttv311"

MQTT 3.1.1 has no No Local, so a remote broker sends back the messages the bridge forwarded to it. The bridge remembers the last 1,000 messages it forwarded and drops those copies. An identical message published on the remote broker just after one was forwarded is dropped too.

Failover

[[bridges]]
name = "cloud"
addresses = ["broker1.example.com:8883", "broker2.example.com:8883"]

When a broker can't be reached or the connection is lost, the bridge waits and tries the next address, round and round. The first wait is about a second; each failed attempt doubles it, up to a minute, and a randomised share keeps bridges that lost the same broker from all coming back at once. A successful connection starts the waits over.

TLS and client certificates

[[bridges]]
name = "cloud"
host = "iot.example.com"
port = 8883
[bridges.tls]
enabled = true
# Verify the remote broker with this CA instead of the system's roots
ca_path = "/etc/mercurio/cloud-ca.crt"
# Authenticate with a client certificate
cert_path = "/etc/mercurio/site1.crt"
key_path = "/etc/mercurio/site1.key"

enabled = true alone verifies the remote broker with the system's root certificates. A ca_path or a client certificate turns TLS on even without enabled, as --cafile does for recado and mercurioctl, so the password never crosses the network in the clear by mistake. cert_path and key_path go together; the broker refuses to start with only one. The files are loaded at startup and by mercuriod --check, and a file that can't be read or holds no certificate or key stops the broker from starting; each connection to the remote broker reads them again.

Monitoring

With notifications (on by default), the bridge publishes its state, retained, on this broker: 1 while forwarding, 0 at startup and whenever either connection is lost. It goes to $SYS/broker/connection/<client id>/state, or to notification_topic when set.

recado sub -t '$SYS/broker/connection/+/state' -T
# $SYS/broker/connection/mercurio-bridge-cloud/state: 1

With notifications_local_only = false, the remote broker gets the state on the same topic: the bridge publishes 1, retained, once connected, and connects with a retained will of 0, which the remote broker publishes when the link drops. Brokers that refuse client publishes to $SYS, Mercurio among them, need a notification_topic outside it:

[[bridges]]
name = "cloud"
host = "iot.example.com"
notification_topic = "bridges/site1/state"
notifications_local_only = false

mercurioctl bridge list, or the listBridges admin command, shows each bridge's link, the messages queued for it, the messages forwarded each way since the broker started, and those dropped because the other broker refused them or no route covered them.

When the remote broker is down

Access control

The bridge connects to this broker in-process, so this broker's authentication and ACLs don't apply to it, and it may publish anywhere. On the remote broker, it is an ordinary client with the given credentials.