RabbitMQ gateway module for ejabberd

Copyright © 2008 Tony Garnock-Jones and Rabbit Technologies Ltd.; Copyright © 2008-2009 Tony Garnock-Jones and LShift Ltd.

Version: 0.0.1

Authors: Tony Garnock-Jones (tonyg@lshift.net), Rabbit Technologies Ltd. (info@rabbitmq.com), LShift Ltd. (query@lshift.net).

RabbitMQ gateway for ejabberd.

Introduction

Gateway in relation to ejabberd and RabbitMQ

The mod_rabbitmq module implements an ejabberd extension module which gateways AMQP (as implemented by RabbitMQ) to XMPP.

By bridging between the two systems, we benefit from:

The current implementation is a very simple mapping between the two systems. Its simplicity keeps the code short, but only exposes a subset of AMQP features to the XMPP network, and vice versa.

Basic Operation

The services provided by a mod_rabbitmq gateway are accessed by ordinary XMPP accounts registered with normal XMPP servers taking part in the global XMPP network, including

The AMQP resources managed by the gateway are also accessible as usual via the AMQP protocol.

Illustration of example AMQP/XMPP network

In the example illustration above,

JIDs Map to Exchanges

Each exchange in the AMQP virtual host is given a JID. For example, the JID "test@dev.rabbitmq.com" resolves to the exchange named "test" within the virtual host that the gateway represents.

Queues are not currently given JIDs; instead, they're created and deleted on demand by the action of either direct AMQP commands, or by XMPP subscription management (see below).

Messages sent via XMPP to an exchange's JID are routed and delivered as per normal AMQP rules.

Presence Subscription Controls Queue Creation and Binding

Whenever a JID in the global XMPP network subscribes to presence notifications of an exchange JID managed by the gateway, the exchange subscribes back. Once the subscription process is complete, a queue is created for the remote JID (if one does not already exist), and the queue is then bound to the exchange. For example:

If romeo later subscribes to another exchange managed by the same gateway, the same queue is reused. Deliveries for each exchange subscribed to are all placed on the single queue responsible for the remote JID "romeo@montague.net".

If romeo later unsubscribes from all exchanges managed by the gateway, so that no subscriptions/bindings are left for his queue, the queue is deleted.

Presence Controls Consumers and Deliveries

Because the gateway has subscribed to romeo's presence status, it will hear about his presence changes. When he comes online, an AMQP consumer is started, which reads from his queue, delivering each queued-up message as an instant message. When he goes offline, the consumer is stopped, and messages are buffered in the AMQP queue as usual for later consumption.

JID Resources are used as Routing Keys and Binding Patterns

XMPP's JIDs have three parts: name@dns.host/resource.

In the current AMQP-to-XMPP binding, the resource part of the JID is used as the AMQP routing key when delivering messages, and the AMQP binding pattern when subscribing and unsubscribing.

This means that:

Support for XMPP Discovery

The gateway implements the XMPP Service Discovery protocol to permit discovery of exposed exchanges.

Interacting with the RabbitMQ-XMPP Bot

Since the "name" part of a JID is technically optional, the current implementation makes use of this by reserving bare, domain-only JIDs for use in addressing a built-in XMPP bot that controls exchange creation and deletion.

The bot speaks a high-level command-line based language for creating, listing and deleting exchanges. If, for example, a mod_rabbitmq instance is configured to be responsible for the JIDs within the domain dev.rabbitmq.com, then the associated bot will be addressable at the bare JID dev.rabbitmq.com itself.

The reason we took the unusual step of giving a chat-capable actor a bare JID address was that it neatly avoids the need to reserve special names that would otherwise arise. For example, we might have named the bot "bot@dev.rabbitmq.com", but what would then have happened if a user created an AMQP exchange named "bot"?

Creating, Listing and Deleting Exchanges

Given a mod_rabbitmq instance responsible for dev.rabbitmq.com, the following interaction, between the two JIDs romeo@montague.net and dev.rabbitmq.com (note again the bare JID, without a "name@" part!) shows the instance's bot in action:

[18:12:05] <romeo> help
[18:12:06] <dev.rabbitmq.com> Here is a list of commands. Use 'help (command)' to get details on any one.
["help","exchange.declare","exchange.delete","bind","unbind","list"]
[18:12:12] <romeo> list
[18:12:12] <dev.rabbitmq.com> Exchanges available:
[{"mandatoryTestExchange",fanout,transient,[]},
 {"canvaspainter",fanout,durable,[]},
 {"sam",fanout,transient,[]},
 {"amq.rabbitmq.log",topic,durable,[]},
 {"rabbit",fanout,durable,[]},
 {"test",fanout,durable,[]},
 {"test completion",fanout,transient,[]},
 {"amq.topic",topic,durable,[]},
 {"amq.direct",direct,durable,[]},
 {"amq.fanout",fanout,durable,[]}]
[18:13:05] <romeo> help exchange.declare
[18:13:05] <dev.rabbitmq.com> 'exchange.declare (name) [-type (type)] [-transient]'. Creates a new AMQP exchange.
[18:13:14] <romeo> exchange.declare foo -type fanout
[18:13:14] <dev.rabbitmq.com> Exchange "foo" of type fanout declared. Now you can subscribe to it.
[18:13:19] <romeo> help exchange.delete
[18:13:19] <dev.rabbitmq.com> 'exchange.delete (name)'. Deletes an AMQP exchange.
[18:13:23] <romeo> exchange.delete foo
[18:13:23] <dev.rabbitmq.com> Exchange "foo" deleted.

Compiling the gateway

To compile the gateway,

  1. Download, install, and start the RabbitMQ server.
  2. Download (or check out from subversion) ejabberd.
  3. Copy (or symlink) src/mod_rabbitmq.erl and src/rabbit.hrl into your ejabberd/src directory, so that it exists alongside such ejabberd source files as mod_echo.erl, mod_offline.erl etc.
  4. Compile and install the ejabberd server from the source tree.

Once ejabberd is compiled and installed, configure it as you usually would, and proceed with the next section.

Configuring a mod_rabbitmq Instance

Each mod_rabbitmq instance in a running ejabberd maps to a single AMQP "virtual host" running within a RabbitMQ instance: a collection of exchanges, queues, and the bindings between them. The instance is given responsibility for a single DNS domain: for example, JIDs ending with @dev.rabbitmq.com map to a mod_rabbitmq instance.

Currently, only a single mod_rabbitmq can run within each ejabberd node.

To enable the gateway, decide on the DNS name that is to map to the AMQP virtual host, and edit /etc/ejabberd/ejabberd.cfg to include a mod_rabbitmq stanza in its modules section:

{modules,
 [
  {mod_adhoc,    []},
  ...
  {mod_rabbitmq, [{rabbitmq_node, rabbit@yourhostname}]},
  ...
  {mod_version,  []}
 ]}.

Set the rabbitmq_node setting to the Erlang node name of your RabbitMQ server: for most normal installations, this will be rabbit@yourhostname, where yourhostname is the short form of your machine's node name. As an example, for the machine devbox.example.com, the usual RabbitMQ server node name would be rabbit@devbox.

Limitations

The current implementation is largely a proof-of-concept.

There is no support for the XMPP Multi-User Chat protocol.

There is no support for the XMPP Pub-Sub protocol.

A more sophisticated mapping would give JIDs to queues as well as exchanges.

Only plain-text IM-style messages and chats are routed across the gateway at present. Other media types are not supported.

There is no support for message-receipt acknowledgement (AMQP's "basic.ack" operation).

The command protocol (understood by the XMPP bot built in to the gateway) is ridiculous. It should be mechanically generated from the AMQP specification itself (possibly augmented with extension methods).

Generated by EDoc