Server Constellations

Throughout the discussion of components in this chapter, and how they're arranged to form a 'complete' Jabber server, we've only really considered a monolithic server, running in a single process. [1]

However, there may be good reasons (performance, administration and manageability) to run the Jabber server in different configurations, or 'constellations'. [2]

In this concluding section of Chapter 4 we take a look at some of the possible constellations, and how they're constructed.

Multiple Servers on one Host

Although it's unlikely that this constellation would be of much use, it is possible to run more than one Jabber server on one host simply by creating multiple installations, maintaining each server's jabber.xml configuration file separately, and starting them up to listen on different ports to each other. Note that some Jabber clients don't support connections to anything other than port 5222, however.

As we have seen from examining the instance configuration for the Client (to Server) Connections and the Server (to Server) Connections components, the 'standard' Jabber ports for client and server connectivity are 5222 and 5269 respectively. To run a second Jabber server on the same host, just ensure that its Connections component instances are configured to listen on different ports.

'Real' Virtual Jabber Servers

While looking at the section called Host filter we saw how to use multiple <host/> tags to allow connection to the Jabber server under multiple hostnames. Although this simple feature might be useful in some circumstances, a better distinction of Session Management functionality might be more appropriate.

Taking our a-domain.com and b-domain.com hostname examples again, we might want to offer different 'welcome' messages to new users; we may want to limit the authentication possibilities for the b-domain.com host to zero-knowledge only; and disable the message filtering service for the a-domain.com host; furthermore it's likely that we'd want to offer - in the <browse/> list - a different set of services for each of the hosts.

Let's have a look how this can be done. Using the <jabberd:include/> tag to organise our configuration XML by component instance definitions, we might have a jabber.xml configuration file that looks like Example 4-24.

Example 4-24. Virtual server jabber.xml configuration

<jabber>

  <!-- Common components -->

  <jabberd:include>./config/common/xdb.xml</jabberd:include>
  <jabberd:include>./config/common/c2s.xml</jabberd:include>
  <jabberd:include>./config/common/elogger.xml</jabberd:include>
  <jabberd:include>./config/common/rlogger.xml</jabberd:include>
  <jabberd:include>./config/common/dnsrv.xml</jabberd:include>
  <jabberd:include>./config/common/s2s.xml</jabberd:include>


  <!-- a-domain.com -->

  <jabberd:include>./config/a-domain/sessions.xml</jabberd:include>
  <jabberd:include>./config/a-domain/conference.xml</jabberd:include>


  <!-- b-domain.com -->

  <jabberd:include>./config/b-domain/sessions.xml</jabberd:include>
  <jabberd:include>./config/b-domain/conference.xml</jabberd:include>
  <jabberd:include>./config/b-domain/jud.xml</jabberd:include>


  <!-- IO, PIDfile -->

  <jabberd:include>./config/common/io.xml</jabberd:include>
  <jabberd:include>./config/common/pidfile.xml</jabberd:include>

</jabber>

What can we see here?

First, a-domain.com and b-domain.com Jabber users will share the common facilities such as Data Storage, remembering that data will be stored by hostname within the spool area (and while you're remembering, remember also that you can use two xdb component instances, specifying a different host filter in each, to store data in separate places - see the section called Component instance: xdb earlier in this Chapter), Client (to Server) Connections, Logging and so on. They also share the same IO settings and PIDfile definition - after all, there is still only one Jabber server that is hosting these two virtual servers, so we only need one PIDfile.

But we also see that there are two sessions.xml files included - one for the a-domain.com host and another for the b-domain.com host. And with each of the sessions.xml files included, we have one or two other components - for Conferencing and JUD services.

Configuration for a-domain.com

The layout in the jabber.xml file indicates that there are separate definitions for each of the two hosts. Let's examine the contents of ./config/a-domain/sessions.xml:

<service id="sessions.a-domain">

  <host>a-domain.com</host>

  <jsm xmlns="jabber:config:jsm">

    <!-- no filter config necessary -->

    <vCard>
      <FN>a-domain.com Jabber Services</FN>
      <DESC>Jabber 1.4.1 on a-domain.com</DESC>
      <URL>http://www.a-domain.com</URL>
    </vCard>

    <browse>
      <conference type="public" jid="conference.a-domain.com"
                  name="a-domain Conferencing"/>
    </browse>

    <!-- 

    a-domain.com not open for self-service new user accounts 

    <register notify="yes">
      <instructions/>
      <name/>
      <email/>
    </register>

    -->

    <welcome>
      <subject>Welcome!</subject>
      <body>Welcome to the Jabber server at a-domain.com</body>
    </welcome>

    <admin>
      <write>admin@a-domain.com</write>
      <reply>
        <subject>Auto Reply</subject>
        <body>This is a special administrative address.</body>
      </reply>
    </admin>

  </jsm>

  <load main="jsm">
    <jsm>./jsm/jsm.so</jsm>
    <mod_echo>./jsm/jsm.so</mod_echo>
    <mod_roster>./jsm/jsm.so</mod_roster>
    <mod_time>./jsm/jsm.so</mod_time>
    <mod_vcard>./jsm/jsm.so</mod_vcard>
    <mod_last>./jsm/jsm.so</mod_last>
    <mod_version>./jsm/jsm.so</mod_version>
    <mod_announce>./jsm/jsm.so</mod_announce>
    <mod_agents>./jsm/jsm.so</mod_agents>
    <mod_browse>./jsm/jsm.so</mod_browse>
    <mod_admin>./jsm/jsm.so</mod_admin>

    <!-- No filter service for a-domain.com
    <mod_filter>./jsm/jsm.so</mod_filter>
    -->

    <mod_offline>./jsm/jsm.so</mod_offline>
    <mod_presence>./jsm/jsm.so</mod_presence>
    <mod_auth_plain>./jsm/jsm.so</mod_auth_plain>
    <mod_auth_digest>./jsm/jsm.so</mod_auth_digest>
    <mod_auth_0k>./jsm/jsm.so</mod_auth_0k>
    <mod_log>./jsm/jsm.so</mod_log>
    <mod_register>./jsm/jsm.so</mod_register>
    <mod_xml>./jsm/jsm.so</mod_xml>
  </load>

</service>

We can see that this configuration file contains the definition of a jsm component instance. The instance is identified with the name sessions.a-domain and the host a-domain.com has been registered as what the jsm listens for - its 'external identification'.

We can also see that the literal texts in the descriptions and welcome message are specific to a-domain.com, the administration section in the configuration describes a local user at a-domain.com as the administrator, the new user registration facility has been disabled, and that the mod_filter service has been commented out from the list of loaded modules in the component connection definition.

There is one service listed in the browse section - the Conferencing service, with the JID conference.a-domain.com; this is the service that's defined in the file ./config/a-domain/conference.xml, which itself is specified in a <jabberd:include/> tag in the main jabber.xml alongside this sessions.xml file.

Taking a look at this Conferencing service definition for a-domain.com in the ./config/a-domain/conference.xml file, we see:

<service id='conf.a-domain'>
  <host>conference.a-domain.com</host>
  <load><conference>./conference-0.4.1/conference.so</conference></load>
  <conference xmlns="jabber:config:conference">
    <public/>
    <vCard>
      <FN>a-domain Chatrooms</FN>
      <DESC>This service is for public chatrooms.</DESC>
      <URL>http://www.a-domain.com/chatrooms</URL>
    </vCard>
    <history>10</history>
    <notice>
      <join> is here</join>
      <leave> has left</leave>
      <rename> is now known as </rename>
    </notice>
    <room jid="bar@conference.a-domain.com">
      <name>The Bar</name>
    </room>
  </conference>
</service>

Similar to the ./config/a-domain/sessions.xml content, here we see a-domain.com specific definitions - crucially the service identification as conf.a-domain and the <host/> tag declaring the hostname that this service serves under.

Configuration for b-domain.com

Now that we've seen the a-domain specific XML, let's have a look at the b-domain specific XML:

<service id="sessions.b-domain">
  <host>b-domain.com</host>
  <jsm xmlns="jabber:config:jsm">

    ... 

    <vCard>
      <FN>b-domain Jabber Server</FN>
      <DESC>Jabber 1.4.1 on b-domain.com</DESC>
      <URL>http://www.b-domain.com/</URL>
    </vCard>

    <browse>
      <conference type="public" jid="conference.b-domain"
                  name="b-domain Conferencing"/>
      <service type="jud" jid="jud.b-domain" name="b-domain JUD">
        <ns>jabber:iq:search</ns>
        <ns>jabber:iq:register</ns>
      </service>
    </browse>

    <register notify="yes">
      <instructions>
         Choose a username and password to register with this server.
      </instructions>
      <name/>
    </register>

    <welcome>
      <subject>Welcome!</subject>
      <body>Welcome to the Jabber server at b-domain</body>
    </welcome>

    <admin>
      <read>info@b-domain</read>
      <write>service@b-domain</write>
      <reply>
        <subject>Auto Reply</subject>
        <body>This is a special administrative address.</body>
      </reply>
    </admin>

  </jsm>

  <load main="jsm">
    <jsm>./jsm/jsm.so</jsm>
    <mod_echo>./jsm/jsm.so</mod_echo>
    <mod_roster>./jsm/jsm.so</mod_roster>
    <mod_time>./jsm/jsm.so</mod_time>
    <mod_vcard>./jsm/jsm.so</mod_vcard>
    <mod_last>./jsm/jsm.so</mod_last>
    <mod_version>./jsm/jsm.so</mod_version>
    <mod_announce>./jsm/jsm.so</mod_announce>
    <mod_agents>./jsm/jsm.so</mod_agents>
    <mod_browse>./jsm/jsm.so</mod_browse>
    <mod_admin>./jsm/jsm.so</mod_admin>
    <mod_filter>./jsm/jsm.so</mod_filter>
    <mod_offline>./jsm/jsm.so</mod_offline>
    <mod_presence>./jsm/jsm.so</mod_presence>
    <!-- 

    zero-knowledge authentication only

    <mod_auth_plain>./jsm/jsm.so</mod_auth_plain>
    <mod_auth_digest>./jsm/jsm.so</mod_auth_digest>
    -->

    <mod_auth_0k>./jsm/jsm.so</mod_auth_0k>

    <mod_log>./jsm/jsm.so</mod_log>
    <mod_register>./jsm/jsm.so</mod_register>
    <mod_xml>./jsm/jsm.so</mod_xml>
  </load>

</service>


We can see here that we've fulfilled our requirements of the virtual server for b-domain - registration is open but authentication is limited to zero-knowledge, and the services offered in the <browse/> list are unique to b-domain. [3]

The Conferencing and JUD services associated with the b-domain.com hostname will be configured in a similar way to how the Conferencing service was configured in ./config/a-domain/conference.xml for a-domain - crucially again the service ids will be unique and the <host/> tags will be specific to b-domain.com.

As long as each component instance is uniquely identified and you have used separate hostname definitions, 'real' virtual Jabber servers all listening to the same Jabber standard client port of 5222 on a single host can be, er, a reality.

Splitting up the Jabber Server Processes

As well as being possible to lump multiple Jabber server identities in the form of virtual hosting onto a single Jabber server and its corresponding monolithic process, it is also possible to go in the opposite direction and split up a single Jabber server into multiple processes. These processes interact through TCP socket connections and so it's possible for them to run on the same or different physical hosts.

How is this achieved? Well, revisiting the ideas from the start of this chapter, we consider that a Jabber server is a daemon (jabberd) and a set of components that provide the services. Taking one step away from the 'classic' Jabber server model which contains components such as the ones described in the section called An Overview of the Server Architecture we can imagine a Jabber server where jabberd controls just one component, say the Conferencing component.

How much use is a Jabber server with a single Conferencing component? Not much. But linked together with another Jabber server, we can see that this is a way to split off components and run them independently.

Taking the Conferencing component as an example candidate for ostracism, let's have a look at what we need to do.

Define the Configuration for the Satellite Server

This is very straightforward - we've seen Conferencing configuration before so we'll shorten it a bit here:

<jabber>

  <service id='conf.yak'>
    <host>conference.yak</host>
    <load><conference>./conference-0.4.1/conference.so</conference></load>
    <conference xmlns="jabber:config:conference">

      ...

    </conference>
  </service>

</jabber>

This is the entirety of the configuration file so far for the satellite server - there's only one component instance - with an id of "conf.yak". Notice that the only other tag pair is the file-wide <jabber> ... </jabber>. Let's call it jabber_conf.xml.

Open a Connection Point in the Main Server

We've already seen a mechanism earlier in this chapter in the section called Component Connection Methods that allows external components to connect into the Jabber server backbone by exchanging XML streams in the jabber:component:accept namespace. This is the TCP socket connection method.

We can prepare a connection point to the main Jabber server by specifying a component connection like this:

<service id="conflinker">
  <host>conference.merlix</host>
  <accept>
    <ip>127.0.0.1</ip>
    <port>9001</port>
    <secret>confsecret</secret>
  </accept>
</service>

in the configuration for the main Jabber server.

There's no real difference between this XML and the XML shown in the <accept/> example earlier in this Chapter. The clue lies in the service id, which has been defined as "conflinker". There's nothing special about the name; it simply gives the administrator a hint that there's some sort of link to a conference service from this point.

We're specifying acceptance of connections on IP address 127.0.0.1 - the same host as this main server, but it could just as easily be the IP address assigned to a network card, so that the connection could be made from a satellite server on a separate host.

List the Service Definition in <browse/>

While we're editing the main server's XML, we should add an entry for our satellite conference service:

<browse>

  ...

    <conference type="public" jid="conference.yak" name="yak Conferencing"/>
  
  ...

</browse>

The jid defined here must match the host defined in the Conferencing component instance definition in the satellite server configuration.

Add a Connector Mechanism to the Satellite Server

Now we've opened up a connection point in the main server, we need to add some corresponding configuration to the satellite server's XML - the 'plug' that will attach to the connection point on the main server:

<jabber>

  <service id="conflinker">
    <uplink/>
    <connect>
      <ip>127.0.0.1</ip>
      <port>9001</port>
      <secret>confsecret</secret>
    </connect>
  </service>

  <service id='conf.yak'>
    <host>conference.yak</host>
    <load><conference>./conference-0.4.1/conference.so</conference></load>
    <conference xmlns="jabber:config:conference">

      ...

    </conference>
  </service>

</jabber>

This new service - the 'plug' - with an id of "conflinker" (to match the id of the corresponding 'socket' in the main server) contains two elements - the <connect/> tag which corresponds to the <accept/> tag in the main server's configuration, and the <uplink/> tag, which serves as a conduit for all types of packets - those handled by each of the three delivery trees log, xdb and service. [4]

While we're looking at the satellite server's configuration again, it's worth pointing out that even in a situation where the satellite server process would be running on a separate host (we're running it here on the same host - hence the localhost IP address of 127.0.0.1 in the <accept/> tag), the value of the conference service's host filter is still conference.yak - that is the name of the host where the satellite server runs is actually irrelevant. This is because the conference service is still seen by the main Jabber server's jabberd as 'local', through the accept/connect binding.

Specify a Different PIDfile location

If the satellite server is going to be running on the same host as the main server, and from the same directory (indeed, in this example, we've named the satellite server's configuration file 'jabber_conf.xml' to distinguish it from the main server's 'jabber.xml'), make sure a different location for storing the PIDfile is specified:

<jabber>

  <service id="conflinker">

    ...

  </service>

  <service id='conf.yak'>

    ...

  </service>

  <pidfile>jabber_conf.pid</pidfile>

</jabber>

Start up the Main Server

Once everything is configured, start up the main server:

yak:~/jabber-1.4.1$ ./jabberd/jabberd -c jabber.xml &

The <accept/> section should start listening on port 9001 for a connection.

yak:~/jabber-1.4.1$ netstat -an | grep 9001
tcp        0      0 127.0.0.1:9001          0.0.0.0:*               LISTEN

Start up the Satellite Server

Now it's time to start up the satellite server. From the same directory in this example:

yak:~/jabber-1.4.1$ ./jabberd/jabberd -c jabber_conf.xml &

The satellite server should make a connection to the socket listening on 127.0.0.1:9001.

At this stage, you should have Jabber server services split between a main process and a separate process that runs a Conferencing component.

At the risk of stating the obvious, it is worth pointing out that this example shows that simply starting jabberd does not mean that any process will bind to and start listening on port 5222. It is the jsm component that makes this happen. So starting a second jabberd on the same host did not cause any socket listening problems because this second jabberd doesn't have a jsm component and so doesn't try to bind to port 5222.

Using Services on Other Jabber Servers

This section describes a technique that we've already seen used implicitly in the section called Splitting up the Jabber Server Processes. That is the use of services on other Jabber servers. In reality, the example of running a Conferencing module in a satellite Jabber server showed the technique in the context of local administrative control - we control the main and and satellite servers, and the module in the satellite server may rely on services in the main server for support.

But consider the <browse/> section in the jabber.xml configuration file that comes out of the box with Jabber server 1.4.1:

<browse>

  ...

  <service type="jud" jid="users.jabber.org" name="Jabber User Directory">
    <ns>jabber:iq:search</ns>
    <ns>jabber:iq:register</ns>
  </service>

  ...

</browse>

What's this? A JID of users.jabber.org? How many Jabber server installations will be running with the jabber.org domain name? Yes, just one. So what this means is that this <browse/> section is pointing to a JUD component running at jabber.org as users.jabber.org. And if our Jabber server is running the Server (to Server) Connections and Hostname Resolution components, clients connecting to our server can transparently jump across the wire and avail themselves of the JUD services at users.jabber.org.

Indeed, the entry doesn't even have to be in the <browse/> section - this is more for convenience, so that the clients can build a dynamic list of services from which the user may choose. The client may of course offer a facility for the user to directly enter the name (hostname, address) of the service they require.

How does this procedure compare to the 'satellite server' procedure? In this case, the packets that originate from a Jabber client connected to our Jabber server make their way to the JUD service on users.jabber.org by means of the s2s service. That is, they travel through a connection described by the jabber:server namespace. On the other hand, packets on our server destined for a satellite conference service travel through a connection described by the jabber:component:accept namespace.

Notes

[1]

If the Hostname Resolution component is configured to run, then you will see two processes at startup, as the component - dnsrv - forks to have the resolver functionality run in a separate child process.

[2]

'Constellation' is used in German for the word 'configuration' when referring to system arrangement in IT. It's such a wonderfully evocative and appropriate word - much more interesting than 'configuration' - that I've decided to use it here.

[3]

That said, it is possible for someone registered to b-domain to connect to and use, say, the Conferencing service listening on conferencing.a-domain.com - see the section called Using Services on Other Jabber Servers later in this Chapter.

[4]

Although the service ids do not need to match.