Documentation
¶
Overview ¶
Package natsmqtt5 is an MQTT version 5.0 broker that uses an existing NATS server as its messaging core.
The broker is a NATS client, not a fork of nats-server. Point it at a NATS URL you already run and it speaks MQTT v5 on a TCP port, translating topics to NATS subjects, fan-out to NATS subscriptions, shared subscriptions to NATS queue groups and retained messages to a JetStream stream. Two brokers against the same NATS server see each other's traffic, so an MQTT client on one reaches a subscriber on the other.
The subject mapping is byte-for-byte the one nats-server uses for its own MQTT 3.1.1 support, so topics land where a NATS-native subscriber expects.
Minimal use:
b, err := natsmqtt5.New(natsmqtt5.Options{NATSURL: "nats://localhost:4222"})
if err != nil {
return err
}
defer b.Close()
return b.Serve(ctx)
Section numbers in the documentation refer to the OASIS MQTT v5.0 Standard of 07 March 2019.
Example ¶
Run a broker against an existing NATS server.
package main
import (
"context"
"log"
"github.com/taumatix/natsmqtt5"
)
func main() {
broker, err := natsmqtt5.New(natsmqtt5.Options{
NATSURL: "nats://localhost:4222",
Listen: ":1883",
})
if err != nil {
log.Fatal(err)
}
defer broker.Close()
if err := broker.Serve(context.Background()); err != nil {
log.Fatal(err)
}
}
Output:
Example (Authentication) ¶
Reject unknown credentials with the CONNACK Reason Code of your choosing, and confine each user to its own topic subtree.
package main
import (
"context"
"errors"
"log"
"strings"
"github.com/nats-io/nuid"
"github.com/taumatix/natsmqtt5"
"github.com/taumatix/natsmqtt5/packet"
)
func main() {
passwords := map[string]string{"alice": "s3cret"}
opts := natsmqtt5.Options{
NATSURL: "nats://localhost:4222",
Authenticator: natsmqtt5.AuthenticatorFunc(
func(_ context.Context, req *natsmqtt5.AuthRequest) (*natsmqtt5.AuthResult, error) {
if want, ok := passwords[req.Username]; !ok || want != string(req.Password) {
return nil, &natsmqtt5.ConnectError{Code: packet.BadUserNameOrPassword}
}
// A Session is keyed by its Client Identifier alone, so an
// authenticated user must not be free to choose any: without
// this, alice connecting as "bob-sensor" inherits bob's
// session and everything it is subscribed to.
//
// A zero-length identifier is the client asking the server to
// assign one [MQTT-3.1.3-6], so it is answered rather than
// refused.
if req.ClientID == "" {
return &natsmqtt5.AuthResult{
Identity: req.Username,
AssignClientID: req.Username + "/" + nuid.Next(),
}, nil
}
// Equality on the first segment, not a prefix test: a user
// named "a" passes HasPrefix("a/b/sensor", "a/") and would
// inherit the session of the user named "a/b".
if owner, _, found := strings.Cut(req.ClientID, "/"); !found || owner != req.Username {
return nil, &natsmqtt5.ConnectError{Code: packet.ClientIdentifierNotValid}
}
return &natsmqtt5.AuthResult{Identity: req.Username}, nil
}),
Authorizer: natsmqtt5.AuthorizerFunc(
func(_ context.Context, req *natsmqtt5.AuthzRequest) error {
// A denied publish comes back as 0x87 in the PUBACK; a denied
// subscription as 0x87 in the SUBACK. The connection stays up.
if !strings.HasPrefix(req.Topic, "users/"+req.Identity+"/") {
return errors.New("outside the user's subtree")
}
return nil
}),
}
broker, err := natsmqtt5.New(opts)
if err != nil {
log.Fatal(err)
}
defer broker.Close()
log.Fatal(broker.Serve(context.Background()))
}
Output:
Example (Embedded) ¶
Share one NATS connection between the broker and the rest of an application, and keep MQTT traffic in its own subject namespace.
package main
import (
"context"
"log"
"github.com/nats-io/nats.go"
"github.com/taumatix/natsmqtt5"
)
func main() {
// An existing connection the application already uses. The broker does not
// close a connection it did not open.
nc, err := nats.Connect("nats://localhost:4222")
if err != nil {
log.Fatal(err)
}
defer nc.Close()
broker, err := natsmqtt5.New(natsmqtt5.Options{
Conn: nc,
Listen: "127.0.0.1:1883",
SubjectPrefix: "iot",
StreamPrefix: "IOT",
})
if err != nil {
log.Fatal(err)
}
defer broker.Close()
// An MQTT client publishing to "sensors/7/temp" now reaches any NATS
// subscriber on "iot.sensors.7.temp", and the other way round.
log.Fatal(broker.Serve(context.Background()))
}
Output:
Index ¶
Examples ¶
Constants ¶
const ( DefaultListen = ":1883" DefaultSubjectPrefix = "mqtt" DefaultStreamPrefix = "MQTT5" // DefaultReceiveMaximum bounds how many QoS 1 and QoS 2 publications a // client may have in flight towards the broker (MQTT-5.0 §3.2.2.3.3). DefaultReceiveMaximum = 1024 // DefaultTopicAliasMaximum is the highest Topic Alias the broker accepts // from a client (MQTT-5.0 §3.1.2.11.5). DefaultTopicAliasMaximum = 64 // DefaultMaximumPacketSize caps an inbound packet at 64 MiB // (MQTT-5.0 §3.2.2.3.6). DefaultMaximumPacketSize = 64 << 20 // DefaultConnectTimeout bounds how long a new connection may stay silent // before sending its CONNECT (MQTT-5.0 §3.1.4, step 1). DefaultConnectTimeout = 10 * time.Second // DefaultMaxSessionExpiry caps the Session Expiry Interval a client may // request. The broker returns the capped value in CONNACK, as // MQTT-5.0 §3.2.2.3.2 provides for. DefaultMaxSessionExpiry = time.Hour // DefaultSessionCheckpointInterval is how often a broker with // PersistentSessions writes where a connected client stands to the session // record, when something has changed. DefaultSessionCheckpointInterval = time.Second )
Defaults applied to the zero value of the corresponding Options field.
const DefaultRetainedTombstoneTTL = 24 * time.Hour
DefaultRetainedTombstoneTTL is how long a cleared retained topic's marker stays in the stream; see Options.RetainedTombstoneTTL.
const DefaultSessionSweepInterval = time.Minute
DefaultSessionSweepInterval is how often a broker looks for detached sessions whose Session Expiry Interval has passed; see Options.SessionSweepInterval.
const DroppedFilterProperty = "natsmqtt5-dropped-filter"
DroppedFilterProperty is the name of the User Property a CONNACK carries, once per filter, for each stored subscription a resumed session lost because the Authorizer refused it on resume. The CONNACK has no per-filter Reason Code, so a client that trusts Session Present would otherwise wait for messages that will not come. It is this broker's own and not part of MQTT v5; a client that does not read it loses nothing. It is left out when the CONNACK would not fit the client's Maximum Packet Size.
const MaxPersistentClientIDLen = 128
MaxPersistentClientIDLen caps the Client Identifier a broker with Options.PersistentSessions accepts. The identifier is encoded into a JetStream key-value key, and a key is part of a NATS subject, so it cannot be the 65535 bytes MQTT permits. A longer one is refused with 0x85 (Client Identifier not valid), which MQTT-5.0 §3.1.3.1 provides for: a server states the identifiers it accepts, and 128 bytes is more than five times the 23 that every MQTT server must accept.
ReauthorizeForwardWait is how long Broker.Reauthorize waits for another broker to say it holds a session this one does not. Every broker answers at once, so the wait is spent only when no broker holds it.
Variables ¶
ErrAuthorizerUnavailable is what an Authorizer returns (bare or wrapped, it is matched with errors.Is) when it could not reach its policy and so cannot say either way. The broker then does not treat it as a denial where a denial would be wrong:
- a SUBSCRIBE gets Reason Code 0x83 in its SUBACK, a PUBLISH 0x83 in its PUBACK or PUBREC, and a CONNECT whose Will cannot be checked 0x83 in the CONNACK, instead of 0x87 Not authorized, so the client knows to retry;
- Broker.Reauthorize leaves the subscriptions and the Will it could not check as they are, and returns this error once the sweep is over;
- a CONNECT that resumes a session is refused with 0x83 before the session is claimed or the connection holding it displaced, so the stored filters and the live connection are untouched and the client can retry.
The resume check runs before the takeover, over the filters the session holds in memory and in the store. A filter that appears in between is checked again at resume, and an unavailable answer there keeps it: the filter was authorised when it was subscribed and no CONNECT is left to refuse.
var ErrInvalidOptions = errors.New("natsmqtt5: invalid options")
ErrInvalidOptions reports an Options value the broker cannot run with.
Functions ¶
Types ¶
type AuthRequest ¶
type AuthRequest struct {
// ClientID is the Client Identifier from CONNECT, before the broker
// assigns one for an empty value.
ClientID string
// Username is empty when the User Name Flag was not set.
Username string
// Password is nil when the Password Flag was not set. MQTT v5 permits a
// password with no username (MQTT-5.0 §3.1.2.9).
Password []byte
// RemoteAddr is the client's network address.
RemoteAddr net.Addr
// TLS is the connection's TLS state, or nil for a plaintext connection.
// Use it for client-certificate authentication.
TLS *tls.ConnectionState
// Properties are the CONNECT properties, including any User Properties.
Properties *packet.Properties
}
AuthRequest describes a connection attempt for an Authenticator.
type AuthResult ¶
type AuthResult struct {
// Identity, when non-empty, names the authenticated principal. It is
// passed to the Authorizer and included in log records; it does not change
// the Client Identifier.
Identity string
// AssignClientID, when non-empty, overrides the Client Identifier for the
// session and is returned to the client as the Assigned Client Identifier
// [MQTT-3.1.3-7].
AssignClientID string
}
AuthResult is an Authenticator's verdict.
type Authenticator ¶
type Authenticator interface {
Authenticate(ctx context.Context, req *AuthRequest) (*AuthResult, error)
}
Authenticator decides whether a CONNECT is accepted.
Returning a nil error accepts the connection. Returning an error rejects it; wrap or return a *ConnectError to choose the CONNACK Reason Code, otherwise the broker answers 0x87 (Not authorized) and does not disclose the reason.
The Client Identifier is the session key ¶
A Session is identified by its Client Identifier alone (MQTT-5.0 §4.1), so every connection an implementation accepts under a given identifier inherits that session: its subscriptions, its unacknowledged messages and its QoS 2 receive state. An implementation that authenticates a principal but lets it choose any ClientID therefore lets it read another principal's session.
So an implementation must either refuse a CONNECT whose AuthRequest.ClientID is not one the authenticated principal owns, or ignore it and return an AuthResult.AssignClientID derived from the principal. Nothing downstream can make up for this: the Authorizer decides topics, not sessions.
The broker re-runs the Authorizer over a resumed session's Topic Filters, so a permission narrowed between two connections takes effect on the second one rather than when the session expires. That bounds the damage; it does not replace binding the identifier, because the filters a principal is allowed are usually not what distinguishes it from the session's rightful owner.
type AuthenticatorFunc ¶
type AuthenticatorFunc func(ctx context.Context, req *AuthRequest) (*AuthResult, error)
AuthenticatorFunc adapts a function to the Authenticator interface.
func (AuthenticatorFunc) Authenticate ¶
func (f AuthenticatorFunc) Authenticate(ctx context.Context, req *AuthRequest) (*AuthResult, error)
Authenticate calls f.
type Authorizer ¶
type Authorizer interface {
Authorize(ctx context.Context, req *AuthzRequest) error
}
Authorizer decides whether a session may publish to or subscribe to a topic. A nil error permits the operation.
A denied publish is answered with 0x87 (Not authorized) in the PUBACK or PUBREC, or dropped silently at QoS 0, as MQTT-5.0 §3.3.4 requires. A denied SUBSCRIBE is answered with 0x87 in the SUBACK for that filter. A denied Will Message, asked about with AuthzRequest.Will set, refuses the CONNECT with 0x87 in the CONNACK.
It is also called while a session is being resumed ¶
A session is keyed by its Client Identifier alone, so the broker puts every filter of a resumed session back past the Authorizer before answering the CONNECT — once per live filter, with AuthzRequest.Resume set. That is what makes a permission narrowed between two connections take effect on the second one rather than when the session expires.
Two consequences an implementation has to plan for:
A slow Authorizer delays the CONNACK in proportion to how many filters the session holds. The ctx carries no deadline of the broker's making.
A denial there is silent and it is final. There is no per-filter Reason Code in a CONNACK, so the filter is torn down, logged at warn level, and the client is told SessionPresent: true with no indication that anything is missing. With Options.PersistentSessions the reduced set is written straight back to the durable record. An error returned because the policy store could not be reached is therefore indistinguishable from a decision to deny, and costs the client its subscription permanently — an implementation that cannot decide should return ErrAuthorizerUnavailable, which refuses the CONNECT with 0x83 and leaves the session as it was, rather than any other error.
type AuthorizerFunc ¶
type AuthorizerFunc func(ctx context.Context, req *AuthzRequest) error
AuthorizerFunc adapts a function to the Authorizer interface.
func (AuthorizerFunc) Authorize ¶
func (f AuthorizerFunc) Authorize(ctx context.Context, req *AuthzRequest) error
Authorize calls f.
type AuthzRequest ¶
type AuthzRequest struct {
Action Action
// Resume distinguishes a filter being re-checked because a session is
// being resumed from one the client has just asked for in a SUBSCRIBE. It
// is only ever set with ActionSubscribe.
//
// The two are otherwise identical, and an implementation that audit-logs,
// meters or rate-limits subscriptions needs to tell them apart: the client
// performed no action, and the same filters come back on every reconnect.
// It is also the cheap way out of the latency this check adds to a CONNACK
// — an implementation may answer a resume from a cache it would not trust
// for a fresh SUBSCRIBE.
Resume bool
// Will marks the Will Message of a CONNECT, asked about as an
// ActionPublish before the connection is accepted. It is only ever set with
// ActionPublish.
//
// The broker publishes a Will on the client's behalf after the client has
// gone, so this is the only chance to refuse it: a denial refuses the
// CONNECT with 0x87 (Not authorized), and the Will is not re-checked when it
// fires. An implementation that audit-logs publishes can use the flag to
// tell a Will — which may never be sent — from a PUBLISH the client made.
Will bool
// ClientID is the session's Client Identifier, after any assignment.
ClientID string
// Identity is the AuthResult.Identity from authentication, if any.
Identity string
// Username is the User Name from CONNECT, if any.
Username string
// Topic is the Topic Name for ActionPublish or the Topic Filter, shared
// prefix included, for ActionSubscribe.
Topic string
// QoS is the requested QoS.
QoS packet.QoS
// Retain is set for a publish with the RETAIN flag.
Retain bool
}
AuthzRequest describes an operation for an Authorizer.
type Broker ¶
type Broker struct {
// contains filtered or unexported fields
}
Broker is an MQTT v5 broker backed by a NATS server. Create one with New, run it with Serve, and release its resources with Close.
A Broker is safe for concurrent use.
func New ¶
New connects to NATS, provisions the JetStream assets the broker needs and binds the MQTT listener. It does not accept connections until Serve is called, so a caller can read ListenAddr first.
The returned Broker must be closed with Close.
func NewWithContext ¶
NewWithContext is New with a context bounding the NATS connection and JetStream setup.
func (*Broker) Close ¶
Close stops the broker: it closes the listener, disconnects every client with 0x8B (Server shutting down) and releases the NATS resources it owns. A NATS connection passed in through Options.Conn is left open.
func (*Broker) ListenAddr ¶
ListenAddr reports the address the broker accepts MQTT connections on. It is valid as soon as New returns, which lets a test bind :0 and discover the port.
func (*Broker) NATS ¶
NATS returns the NATS connection the broker uses, so an embedding application can share it rather than opening a second one.
func (*Broker) OfflineQueueActive ¶ added in v0.12.0
OfflineQueueActive reports whether the broker is keeping QoS 1 and 2 messages for disconnected sessions. It is false when the queue is off by option, and when the queue is on only by default and could not be created at start-up (the broker keeps trying, and the value turns true once it succeeds).
func (*Broker) Reauthorize ¶ added in v0.5.0
Reauthorize re-runs the Authorizer over everything the session for clientID holds: each subscription, as at a resume, and its Will Message, as at CONNECT. Call it when a principal's permissions change; the broker does not poll a policy store it knows nothing about, and does not ask the Authorizer on every delivery.
A denied subscription is removed. It stops delivering at once, including messages already queued for the connection, and the messages it had in flight are withdrawn, so a later resume does not resend them; their acknowledgements are still accepted. A denied Will is discarded, including one already waiting out its Will Delay Interval. The connection itself is left alone: it is still allowed, and a DISCONNECT would publish the very Will being revoked. The client is not told which filters it lost; MQTT has no packet for that.
The Authorizer sees a subscription with AuthzRequest.Resume set and a Will with Will set, as it does for those checks elsewhere. As there, any error it returns is a denial, so an Authorizer that cannot reach its policy should not be asked to re-check a fleet during an outage.
Any broker will do. One that does not hold the session asks the others sharing its NATS connection and SubjectPrefix, and the one holding it runs the sweep and answers. When none holds it after ReauthorizeForwardWait (or when ctx ends first), it returns nil: a session held nowhere, only in the session store, is re-checked when it is next resumed. It returns ctx's error, having changed nothing further, if ctx ends during this broker's own sweep, and the holder's error if its sweep failed.
type ConnectError ¶
type ConnectError struct {
Code packet.ReasonCode
Reason string
}
ConnectError carries the CONNACK Reason Code an Authenticator wants the broker to send. Any refusal code MQTT-5.0 Table 3-1 lists for a CONNACK is sent as given; common choices are BadUserNameOrPassword (0x86), NotAuthorized (0x87), Banned (0x8A) and ClientIdentifierNotValid (0x85). A code the table does not list, success codes included, is logged and sent as UnspecifiedError (0x80) [MQTT-3.2.2-8].
func (*ConnectError) Error ¶
func (e *ConnectError) Error() string
type Options ¶
type Options struct {
// NATSURL is the NATS server to use. Defaults to nats.DefaultURL. Ignored
// when Conn is set.
NATSURL string
// NATSOptions are passed to nats.Connect for credentials, TLS and so on.
// Ignored when Conn is set.
NATSOptions []nats.Option
// Conn is an already-established NATS connection to use instead of
// dialling. The broker does not close a connection it did not open.
Conn *nats.Conn
// Listen is the TCP address to accept MQTT connections on. Defaults to
// DefaultListen. Ignored when Listener is set.
Listen string
// Listener is an already-open listener to accept on instead of binding
// Listen. The broker closes it on shutdown.
Listener net.Listener
// TLSConfig, when non-nil, wraps the listener in TLS.
TLSConfig *tls.Config
// SubjectPrefix namespaces every NATS subject the broker publishes MQTT
// traffic on, keeping it clear of NATS system subjects. Defaults to
// DefaultSubjectPrefix. Set it to the same value on every broker that
// should share traffic.
SubjectPrefix string
// StreamPrefix names the JetStream assets the broker creates. Defaults to
// DefaultStreamPrefix. Brokers sharing a SubjectPrefix must share this.
StreamPrefix string
// DisableRetained turns off retained-message support, so no JetStream
// assets are created and CONNACK advertises Retain Available = 0
// (MQTT-5.0 §3.2.2.3.5). Use it against a NATS server without JetStream.
DisableRetained bool
// RetainedStorage selects file or memory storage for the retained-message
// stream. Defaults to file storage.
RetainedStorage jetstream.StorageType
// RetainedReplicas is the JetStream replica count for the retained-message
// stream. Defaults to 1.
RetainedReplicas int
// RetainedTombstoneTTL is how long the marker left in the retained stream
// by clearing a retained message (an empty-payload retained PUBLISH) is
// kept before the periodic sweep deletes it. Without the purge a workload
// that retains and clears many distinct topics grows the stream without
// bound. Defaults to DefaultRetainedTombstoneTTL; a negative value keeps
// tombstones forever. A broker cut off from NATS for longer than this can
// miss a clear and keep the cleared message until it restarts.
RetainedTombstoneTTL time.Duration
// QoS 1 message without acknowledging it. Past it the message is handed
// back to the group, so a member that is connected but never sends PUBACK
// does not keep it from the others, and the group may deliver it twice:
// the late PUBACK is still accepted and completes the exchange with that
// client. Zero, the default, holds for as long as the session lives. The
// check runs every third of the group's acknowledgement wait (10 s), so the
// limit is met to within that.
SharedHoldLimit time.Duration
// PersistentSessions keeps session state in a JetStream key-value bucket
// instead of in the broker process, so a client reconnecting with Clean
// Start 0 resumes its subscriptions after a broker restart and on a broker
// that has never seen it. Brokers sharing a StreamPrefix share the bucket
// and arbitrate ownership between themselves, so exactly one of them serves
// a given Client Identifier [MQTT-3.1.4-3].
//
// It needs JetStream, and it caps the Client Identifier at
// MaxPersistentClientIDLen bytes.
//
// A resumed session carries its subscription set, and the offline queue
// delivers what was published while it was away. Messages that were in
// flight to the client when its broker went down are not stored, so they
// are not resent. The Will Message is not part of the session record, because
// it belongs to the network connection rather than to the session
// (MQTT-5.0 §3.1.2.5); DurableWills stores it separately. ROADMAP.md has
// the first.
PersistentSessions bool
// SessionStorage selects file or memory storage for the session bucket.
// Defaults to file storage. Memory storage makes sessions survive a broker
// restart but not a NATS restart.
SessionStorage jetstream.StorageType
// SessionReplicas is the JetStream replica count for the session bucket.
// Defaults to 1.
SessionReplicas int
// RestrictDollarTopics follows MQTT-5.0 §4.7.2's "The Server SHOULD
// prevent Clients from using such Topic Names to exchange messages with
// other Clients" for every Topic Name and Topic Filter that starts with
// "$": a PUBLISH or Will to one is refused with Topic Name invalid (0x90),
// and a subscription to one with Topic Filter invalid (0x8F). A Shared
// Subscription ($share/…) is allowed unless the Topic Filter inside it
// starts with "$". Off by default, which leaves "$app/…" and the like usable
// as before; "$retained" and "$queue", where the broker keeps its own data,
// are refused either way. It does not apply to a subscription a persistent
// session already stored.
RestrictDollarTopics bool
// DurableWills keeps the Will Message of every open connection in a
// JetStream key-value bucket, so that the Will of a broker that is killed
// outright is still published: a surviving broker notices the owner has
// stopped answering its liveness subject, adopts the Will, waits out
// whatever is left of the Will Delay Interval and publishes it
// [MQTT-3.1.2-7], [MQTT-3.1.2-8], [MQTT-3.1.3-9]. Exactly one broker publishes a given Will,
// by compare-and-swap on the record.
//
// Off by default, because it changes where Wills live: the Will's payload
// is written to the "<StreamPrefix>_wills" bucket in clear text, so that
// bucket needs the access control of the session bucket. It needs JetStream
// and works with or without PersistentSessions; without them a client that
// reconnects to a different broker starts a new session, so a Will waiting
// out its delay is published when it does, rather than cancelled.
//
// The delay is counted from the moment a surviving broker notices, which is
// up to WillCheckInterval after the connection was lost.
DurableWills bool
// WillCheckInterval is how often a broker looks for Wills whose owner has
// gone, with DurableWills. It bounds how long after a broker's death its
// clients' Wills wait to be noticed. Defaults to 5 seconds.
//
// It also sets the patience of a broker that loses NATS: one that cannot
// reach it for twice this interval closes its client connections, and a
// survivor waits about 4.5 intervals (plus the liveness ping timeout) before
// adopting a silent broker's Wills, so a Will is never published while its
// client's connection is open.
WillCheckInterval time.Duration
// OfflineQueue requires the offline queue, so a broker that cannot set it
// up fails to start.
//
// The queue keeps QoS 1 and QoS 2 messages for a session while its client
// is disconnected, and delivers them when the session resumes, in publish
// order and before anything published after the resume. MQTT-5.0 counts
// those messages as session state that MUST outlive the connection
// [MQTT-3.1.2-23], [MQTT-4.5.0-1], so the queue is on by default whenever
// the broker uses JetStream (retained messages, the default, or
// PersistentSessions); then a stream that cannot be created is logged at
// warn level and the broker runs without it. Without the queue, messages
// are dropped for an absent session, as core NATS keeps nothing.
//
// Each such message is published a second time, under
// <SubjectPrefix>.$queue.<subject>, into a JetStream stream that keeps it
// for OfflineQueueMaxAge, and the PUBACK or PUBREC waits for JetStream to
// store it. It covers sessions held in this broker's memory and, with
// PersistentSessions, sessions restored from the session store after a
// restart or on another broker. Messages published straight onto NATS
// rather than through a broker are not queued (ROADMAP.md).
//
// The queue also backs shared subscriptions: each gets a durable consumer
// on the queue stream that only members with a connected client pull
// from, so QoS 1 and 2 work goes to members that can take it and waits
// when none can. Brokers sharing a SubjectPrefix must agree on whether the
// queue is on, or a message can reach a group twice.
OfflineQueue bool
// DisableOfflineQueue turns the default offline queue off, for a
// deployment that would rather not store every QoS 1 and 2 message and
// accepts that a resumed session misses what was published while it was
// away. Setting it together with OfflineQueue is an error.
DisableOfflineQueue bool
// DurablePublish makes the PUBACK of a QoS 1 PUBLISH and the PUBREC of a QoS 2
// one mean that the message is safe, not only that the broker has it. It
// implies OfflineQueue (the broker fails to start without the queue) and is
// an error together with DisableOfflineQueue.
//
// Without it the broker already waits for JetStream to store the queue's
// copy, but then hands the live copy to its NATS connection and
// acknowledges at once; nats.go writes that from another goroutine, so a
// broker killed in the window loses a live copy it has told the client
// about. With it the broker also waits for the NATS server to confirm it
// has processed the live publish (a flush: a PING answered by a PONG) before
// it acknowledges. So an acknowledged message is (1) in the queue stream,
// to the stream's replication and storage, for OfflineQueueMaxAge, and
// (2) has reached the NATS server's routing. If either step fails or takes
// longer than five seconds the client gets PUBACK / PUBREC 0x83
// Implementation specific error and may send the message again.
//
// It does not make delivery to a subscriber exactly-once or guaranteed:
// live delivery is core NATS, at most once per hop, and a session whose
// client is away gets the queued copy on resume. It covers only QoS 1 and
// 2 publishes from clients, not Will Messages. Costs a NATS round trip per
// such publish on top of the queue's; see the README for measurements.
DurablePublish bool
// OfflineQueueMaxAge is how long a queued message is kept. Defaults to 24
// hours. A session away for longer loses what is older.
OfflineQueueMaxAge time.Duration
// OfflineQueueRewind is how far before a session's connection ended its
// replay reaches for a queued message whose live copy never arrived (the
// publisher stopped between storing the queue copy and publishing the live
// one), and for one still on its way. The session keeps the ids of what it
// delivered in that span so that nothing is sent twice, so a longer rewind
// costs memory per session and a read of that much of the queue stream on
// every resume. Defaults to 10 seconds.
OfflineQueueRewind time.Duration
// OfflineQueueStorage selects file or memory storage for the queue
// stream. Defaults to file storage.
OfflineQueueStorage jetstream.StorageType
// OfflineQueueReplicas is the JetStream replica count for the queue
// stream. Defaults to 1.
OfflineQueueReplicas int
// Authenticator, when non-nil, decides whether a CONNECT is accepted. When
// nil every connection is accepted, which is only appropriate on a trusted
// network.
Authenticator Authenticator
// Authorizer, when non-nil, decides whether a session may publish to or
// subscribe to a topic. When nil everything is permitted.
Authorizer Authorizer
// ReceiveMaximum is advertised in CONNACK and bounds the client's in-flight
// QoS 1 and QoS 2 publications. A client that has more QoS 2 PUBLISH packets
// unacknowledged than this is sent DISCONNECT 0x93 and disconnected
// (MQTT-5.0 §3.3.4). Defaults to DefaultReceiveMaximum.
ReceiveMaximum uint16
// MaximumQoS is the highest QoS the broker accepts in a PUBLISH and grants
// on a SUBSCRIBE, advertised in CONNACK [MQTT-3.2.2-9]. Values above 2 are
// clamped. Leave it nil for the default of 2; it is a pointer so that
// Ptr(uint8(0)) can express a broker that only does QoS 0, which the zero
// value would otherwise make unreachable.
MaximumQoS *uint8
// TopicAliasMaximum is advertised in CONNACK. Zero disables inbound topic
// aliases; leave it unset for DefaultTopicAliasMaximum.
TopicAliasMaximum *uint16
// MaximumPacketSize caps an inbound packet. Defaults to
// DefaultMaximumPacketSize.
MaximumPacketSize uint32
// ServerKeepAlive, when non-zero, overrides the client's requested Keep
// Alive and is returned in CONNACK [MQTT-3.1.2-21].
ServerKeepAlive uint16
// ConnectTimeout bounds the wait for a CONNECT on a new connection.
// Defaults to DefaultConnectTimeout.
ConnectTimeout time.Duration
// MaxSessionExpiry caps a requested Session Expiry Interval. Defaults to
// DefaultMaxSessionExpiry.
MaxSessionExpiry time.Duration
// SessionSweepInterval is how often the broker discards detached sessions
// whose Session Expiry Interval has passed, tearing down their NATS
// subscriptions, and retained messages whose Message Expiry Interval has
// passed. A session therefore outlives its interval by up to this
// long, never less [MQTT-3.1.2-23]; a client that returns after the interval
// but before the sweep still gets Session Present 0. Defaults to
// DefaultSessionSweepInterval.
SessionSweepInterval time.Duration
// SessionCheckpointInterval is the longest a broker with PersistentSessions
// goes between writing where a connected client stands (the offline-queue
// position its replay would start from) to the session record, while that
// changes. It bounds what a broker killed outright costs its sessions: the
// broker that claims the record next replays from the last position written,
// so a client is sent again at most what it was sent in this long, instead
// of everything the queue holds (OfflineQueueMaxAge). The cost is one
// key-value write per interval for each connection whose position moved.
// Defaults to DefaultSessionCheckpointInterval; a negative value turns the
// checkpoint off, and a killed broker's sessions then replay the whole queue.
SessionCheckpointInterval time.Duration
// Logger receives broker events. Defaults to slog.Default().
Logger *slog.Logger
}
Options configures a Broker. The zero value is usable: it connects to nats.DefaultURL and listens on :1883.
Fields are only ever added to this struct, never removed or repurposed, so a composite literal with field names keeps compiling across versions.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
natsmqtt5
command
Command natsmqtt5 runs an MQTT v5 broker against an existing NATS server.
|
Command natsmqtt5 runs an MQTT v5 broker against an existing NATS server. |
|
Package packet implements the MQTT version 5.0 wire format as specified by the OASIS Standard of 07 March 2019.
|
Package packet implements the MQTT version 5.0 wire format as specified by the OASIS Standard of 07 March 2019. |
|
Package topic implements MQTT Topic Name and Topic Filter handling, and the mapping between MQTT topics and NATS subjects.
|
Package topic implements MQTT Topic Name and Topic Filter handling, and the mapping between MQTT topics and NATS subjects. |