Route of a message through the system
Let's examine the flow of a message sent from Alice to Bob, both of whom are served by the same domain and connected to the server.
Note that hooks are called at various stages of routing - they perform many tasks, and many MongooseIM functionalities are implemented through hooks & handlers. For a general introduction to hooks, see Hooks and Handlers; to get a closer look at a core few, see the hooks description.
1. Sender's C2S process receives the message
Alice's C2S (client-to-server) process, which is a state machine implemented in the mongoose_c2s
module, receives data from the TCP socket, and parses each incoming XML element with exml
to an internal representation of the stanza, which is then processed by the C2S as a subsequent event.
2. Call to user_send_*
hooks
Upon some minimal validation of the stanza, the hook user_send_packet
is called.
Next, depending on the type of the stanza, one of the following hooks is called:
user_send_message
for messages,user_send_presence
for presences,user_send_iq
for IQ (info/query) stanzas,user_send_xmlel
for other XML elements.
Each hook can be handled by multiple modules subscribed to it. Those modules do various complementary tasks, like storing the message in an archive, sending carbon copies, checking the stanza against privacy lists etc. It is possible for a handler to immediately stop routing at this point, preventing execution of any subsequent handlers or hooks. See hooks description for more information.
3. Message routing
The stanza is routed by ejabberd_router:route/3
, which passes it through a chain of routing modules implementing the xmpp_router
behaviour and applies the following functions for each of them:
Mod:filter/3
, which either drops the stanza, stopping the routing chain, or returns it for further processing, modifying it if necessary.Mod:route/3
, which either handles the stanza, stopping the routing chain, or returns it for further processing, modifying it if necessary.
A list of routing modules can be set in the routing_modules
option.
The default behaviour is the following:
mongoose_router_global
: runs a globalfilter_packet
hook.mongoose_router_localdomain
: if there is a local route registered for the destination domain (i.e. there is an entry in themongoose_router
ETS table), routes the stanza to it. When the recipient's domain is checked for the first time, the corresponding route is not registered yet, because the routes are added lazily - seemongoose_router_dynamic_domains
.mongoose_router_external_localnode
: if there is an external component registered for the destination domain on the current node, routes the stanza to it. Such components are stored in the Mnesia tableexternal_component
, which is not replicated in the cluster.mongoose_router_external
: if there is an external component registered for the destination domain on any node in the cluster, routes the stanza to it. Such components are stored in the Mnesia tableexternal_component_global
, which is replicated among all cluster nodes.mongoose_router_dynamic_domains
: if the recipient's domain is hosted by the local server, a route is added for it, and the stanza is routed locally.ejabberd_s2s
: tries to find or establish a connection to another server and send the stanza there.
Assuming that the message from Alice to Bob is not the first stanza addressed to their domain, the routing chain will stop at mongoose_router_localdomain
, which will deliver the message locally.
4. mongoose_local_delivery
When an external component or a local route is found, the packet is delivered locally by mongoose_local_delivery:do_route/5
. Firstly, the filter_local_packet
hook is run to check if the stanza should be delivered or dropped. This hook is also a place where modules can add their own functionality evaluated for each locally delivered stanza.
If the check passes, the next step is to call the handler associated with the component or the local route. Handlers are modules implementing the mongoose_packet_handler
behaviour, and stanzas to local users (like Alice and Bob) are handled by the ejabberd_local
module.
5. ejabberd_local
to ejabberd_sm
ejabberd_local:process_packet/5
checks if the stanza is addressed to a user or to the server itself. For local users like Bob, ejabberd_sm:route/4
is called.
6. ejabberd_sm
ejabberd_sm
determines the available resources of the recipient, takes into account their priorities and whether the message is addressed to a particular resource or a bare JID.
It appropriately replicates (or not) the message and sends it to the recipient's C2S process(es) by calling mongoose_c2s:route/2
.
In case no resources are available for delivery (hence no C2S processes to pass the message to), offline_message_hook
is run.
As Bob has one online session, the message is sent to the C2S process associated with that session.
7. Recipient's C2S process delivers the message
The user_receive_packet
hook is run to notify the rest of the system about the stanza delivery.
Next, depending on the type of the stanza, one of the following hooks is called:
user_receive_message
for messages,user_receive_presence
for presences,user_receive_iq
for IQ (info/query),user_receive_xmlel
for other XML elements.
Each hook can be handled by multiple modules subscribed to it. These hooks' handlers can stop the routing, e.g. when the stanza is blocked by mod_privacy
. Finally, the xmpp_presend_element
hook is called, which is used mod_csi
and mod_stream_management
. This is the last hook that can stop the routing - otherwise, the stanza is converted to binary data and sent to the recipient's TCP socket.