S5B optimizing extensions for Jabber

Email: justin@affinix.com
JID: justin@andbit.net


Fast Mode

Fast-mode allows the initiator and target to connect to each other simultaneously, improving the chance of success between the two peers, and doing it quickly. It addresses some scenarios not addressed by plain S5B:

Scenario Plain S5B S5B w/ Fast mode
Sender is behind NAT with proxy Must use proxy Try direct before resorting to proxy
Sender is behind NAT without proxy Can't connect Try direct
Sender and receiver both behind NAT, receiver has proxy Can't connect Use receiver's proxy

Indicate support for this by including the 'fast' element. You'll notice also that the initiator explicitly indicates which streamhosts are proxies by using the 'proxy' element. This is not required, but it allows the target to perform a clever optimization (see the 'Recommended Usage' section).

  <iq from="alice" to="bob" type="set" id="s1">
    <query xmlns="http://jabber.org/protocol/bytestreams" sid="mySID">
      <streamhost jid="alice" host="private.alice" port="8000"/>
      <streamhost jid="alice" host="public.alice" port="8000"/>
      <streamhost jid="proxy.alice" host="proxy.alice" port="8000">
        <proxy xmlns="http://affinix.com/jabber/stream"/>
      </streamhost>
      <fast xmlns="http://affinix.com/jabber/stream"/>
    </query>
  </iq>

Fast-mode is an offer. The target can choose to not use it, and it doesn't break the protocol at all. If the target does wish to take advantage of it, then it simply initiates a new S5B connection using the same 'sid' as in the original request. The target must not offer any streamhosts that the initiator is already offering. This prevents both sides from offering to use the same proxy, which would result in proxy failure, and also makes loopback (send to self) work.

  <iq from="bob" to="alice" type="set" id="s2">
    <query xmlns="http://jabber.org/protocol/bytestreams" sid="mySID">
      <streamhost jid="bob" host="private.bob" port="8000"/>
      <streamhost jid="bob" host="public.bob" port="8000"/>
    </query>
  </iq>

(Note that the 'fast' element MUST NOT be included in this request, as it would make no sense in this context).

At this point, both sides should be trying to reach a working streamhost. To avoid a race condition, the stream to use is to be decided by the original initiator (Alice, in this example). This is done by sending an additional [CR] character across the bytestream to indicate its selection. This must only be performed if the target requested fast mode. Once a stream has been selected, any other stream attempts must be considered to have failed.

If Alice fails to reach any of Bob's streamhosts, then she MUST return a normal iq-error to indicate failure. She must do this even if Bob is able to reach one of her streamhosts.

  <iq from="alice" to="bob" type="error" id="s2">
    <error code="500">Couldn't connect to any streamhosts</error>
  </iq>

If Bob is unable to reach any of Alice's streamhosts, then he must return an iq-error.

  <iq from="bob" to="alice" type="error" id="s1">
    <error code="500">Couldn't connect to any streamhosts</error>
  </iq>

When either side has success reaching the other's streamhost, then iq-result be sent, such as:

  <iq from="alice" to="bob" type="result" id="s2">
    <query xmlns="http://jabber.org/protocol/bytestreams">
      <streamhost-used jid="bob"/>
    </query>
  </iq>

Note that it is possible for both sides to indicate success, for instance if both sides offered proxies. However, after stream selection (by sending the [CR] character mentioned earlier), only one TCP connection should remain on either side.


Multiplex Mode (Experimental)

For collaboration applications, where quick responses are desired, it would be useful to share a single S5B connection to deliver different data streams, simultaneously.

Indicate support for this by including the "multiplex" element. Normally, an S5B request is sent in response to a File Transfer request or some other high-level activity. For multiplexing, the connection can be established at any time.

  <iq from="alice" to="bob" type="set" id="s1">
    <query xmlns="http://jabber.org/protocol/bytestreams" sid="mySID">
      <streamhost jid="alice" host="private.alice" port="8000"/>
      <multiplex xmlns="http://affinix.com/jabber/stream"/>
    </query>
  </iq>

The target will reply, and the connection will be formed:

  <iq from="bob" to="alice" type="result" id="s1">
    <query xmlns="http://jabber.org/protocol/bytestreams">
      <streamhost-used jid="alice"/>
      <multiplex xmlns="http://affinix.com/jabber/stream"/>
    </query>
  </iq>

The reply MUST contain the multiplex element, to indicate that the peer is aware of what has just been done. More likely though, if the peer didn't support multiplex then it would probably reject the S5B request (since it has no associated higher-level action). If the peer accepts, but leaves out the multiplex element, the initiator MUST close the connection.

Now, when a higher-level action is invoked by either party, it specifies a new sid. Then a follow-up S5B request occurs, which allows the merging of the higher-level sid with the multiplex sid:

  <iq from="alice" to="bob" type="set" id="s2">
    <query xmlns="http://jabber.org/protocol/bytestreams" sid="ftSID">
      <multiplex xmlns="http://affinix.com/jabber/stream" sid="mySID"/>
    </query>
  </iq>

Instead of doing the usual TCP and SOCKS5 procedure, the target simply replies with a channel id:

  <iq from="bob" to="alice" type="result" id="s2">
    <query xmlns="http://jabber.org/protocol/bytestreams">
      <multiplex xmlns="http://affinix.com/jabber/stream" channel="0"/>
    </query>
  </iq>

Now the initiator can send data over the original S5B connection using the channel chosen by the target. The channel is an 8-bit unsigned integer, and thus in the range of 0-255. Data sent over the connection is sent in packets with the following format: [channel (unsigned 8-bit)], [payload size (unsigned 16-bit)], [data bytes ...].


Recommended Usage

For the most optimal data connection possible, the following recommendation is made:

  1. Alice initiates, marking any proxy streamhosts as such, and indicates fast mode support.
  2. Bob initiates (taking advantage of fast-mode). If Alice offered a proxy, then Bob should offer only non-proxy streamhosts.
  3. Alice tries to connect to Bob's streamhosts and Bob tries to connect to Alice's non-proxy streamhosts.
  4. If Bob and Alice both find themselves unsuccessful (Bob will know Alice is unsuccessful by receipt of iq-error), then Bob should try to connect to Alice's proxy streamhosts.

How to Implement

Implementing S5B is tricky enough, and fast-mode makes it particularly difficult. Below are some hints to how you might want to design your S5B implementation: