Planet IM

September 15, 2020

Using STUN & TURN server with Tigase XMPP Server with XEP-0215 (External Service Discovery)

Communication with your family and friends is not only about instant chats. Audio and Video calls are quite important and sometimes, under unfavourable network configurations establishing a call may prove difficult. Luckily, with the help of STUN (Session Traversal Utilities for NAT) and TURN (Traversal Using Relays around NAT ) servers it’s no longer a problem

In the following guide we will show how to setup TURN and STUN servers with Tigase XMPP Server, so that compatible XMPP clients will be able to use them. Our xmpp.cloud installation supports not only them, but also XMPP MIX

Assumptions

We are assuming that you have installed your preferred TURN server and created an account on the TURN server for use by your XMPP server users and that you have installed and configured Tigase XMPP Server.

At the end of the article there is a short guide hot to quickly setup CoTURN server.

Enabling external service discovery (required only for Tigase XMPP Server 8.1.0 and earlier)

First you need to edit etc/config.tdsl file and:

  1. Add following line in the main section of the file:

    'ext-disco' () {}
  2. Add following line in the sess-man section of the file:

    'urn:xmpp:extdisco:2' () {}

so that your config file would look like this:

'ext-disco' () {}
'sess-man' () {
    'urn:xmpp:extdisco:2' () {}
    …
}
…

Start Tigase XMPP Server

After applying changes mentioned above, you need to start Tigase XMPP Server or, in case if it was running, restart it.

Open Admin UI

Open web browser and head to http://<your-xmpp-server-and-port>/admin/ (for example: https://localhost:8080). When promped, log in by providing admin user credentials: bare JID (i.e.: user@domain) as the user and related password. Afterwards you’ll see main Web AdminUI screen:

web admin main page

and on that screen open Configuration group on the left by clicking on it.

Add external TURN service

After opening Configuration group (1) click on Add New Item (2) position which has ext-disco@… in its subtitle.

In the opened form you need to provide following detail: web admin add new turn item

  • Service - ID of the service which will be used for identification by Tigase XMPP Server (ie. turn@example.com)
  • Service name - name of the service which may be presented to the user (ie. TURN server)
  • Host - fully qualified domain name of the TURN server or its IP address (ie. turn.example.com)
  • Port - port at which TURN server listens (ie. 3478)
  • Type - type of the server, enter turn
  • Transport - type of transport used for communication with the server udp or tcp (usually udp)
  • Requires username and password - for notifying XMPP client that this service requires its username and password for XMPP service (leave unchecked)
  • Username - username required for authentication for TURN server (ie. turn-user)
  • Password - password required for authentication for TURN server (ie. turn-password)

After filling out the form, press Submit button (3) to send form and add a TURN server to external services for your server. Admin UI will confirm that service was added with the following result web admin add new item confirmation

Add external STUN service

While adding a TURN server is usually all what you need, in some cases you may want to allow your users to use also STUN. Steps are quite similar like on TURN server - after opening Configuration group (1) click on Add New Item (2) position which has ext-disco@… in its subtitle and in the opened form you need to provide following detail: web admin add new stun item

  • Service - ID of the service which will be used for identification by Tigase XMPP Server (ie. stun@example.com)
  • Service name - name of the service which may be presented to the user (ie. STUN server)
  • Host - fully qualified domain name of the STUN server or its IP address (ie. stun.example.com)
  • Port - port at which TURN server listens (ie. 3478)
  • Type - type of the server, enter stun
  • Transport - type of transport used for communication with the server udp or tcp (usually udp)
  • Requires username and password - for notifying XMPP client that this service requires its username and password for XMPP service (leave unchecked)
  • Username - username required for authentication for STUN server (if required)
  • Password - password required for authentication for STUN server (if required)

Note

If you are using the same server for STUN and TURN (you usually will as TURN servers usually contain STUN functionality) you will fill the following form with almost the same details *(only use different Service field value, Type will be stun and most likely you will skip passing Username and Password - leaving them empty, the rest of the field values will be the same).

After filling out the form, press Submit button (3) to send form and add a STUN server to external services for your server. Admin UI will confirm that service was added with the following result web admin add new item confirmation

And now what?

Now you have fully configured your STUN/TURN server for usage with Tigase XMPP Server allowing XMPP clients connected to your server and compatible with XEP-0215: External Service Discovery to take full advantage of your STUN/TURN server ie. by providing better VoIP experience.

CoTURN installation

You can quickly setup CoTURN server using Docker. Please follow Docker installation on your operating system and then install CoTURN using Docker Hub (instrumentisto/coturn). The bare minimum required to run it looks like that (please update realm with your domain and external-ip with IP on which server should be accessible):

sudo docker run --name coturn -d --network=host --restart always  instrumentisto/coturn -n --log-file=stdout --min-port=49160 --max-port=49200 --realm=awesomexmpp.net --external-ip=<external_ip> -a'

Subsequently, add user to CoTURN with password and domain:

sudo docker exec -i -t coturn turnadmin -a -u tigase -r awesomexmpp.net -p Ajbk7Ck38nIobLVl

September 04, 2020

BeagleIM 4.0 and SiskinIM 6.0 released

New versions of XMPP clients for Apple’s mobile and desktop platforms have been released. The biggest change is introduction of XMPP MIX - the modern way of chatting in groups (if you are looking for a server where you can use this new feature be sure to check our xmpp.cloud installation). It also significantly improves on audio/video calls.

Common changes

New XEPs:

Major changes

  • Redesigned channel joining UI
  • Optimized resource usage during the establishment of VoIP calls and improved quality of VoIP calls
  • Improved Markdown support
  • Improved message synchronization speed, reliability and add support for MAM version 2 (XEP-0313: Message Archive Management)
  • Started using MAM for MUC history synchronization on rejoin (if possible)
  • Improved support for CAPS (feature discovery)
  • New chats list view with a single section for 1-1 chats and group chats
  • Added a way to join hidden MUC rooms

Fixes

  • Fixed issue with avatars in group chats not being properly displayed.
  • Added workaround for possible issue with message archive synchronization if server does not send <complete/> attribute
  • Fixed issue with channel info refresh

BeagleIM 4.0

UI adjustments:

  • MIX / group chat creation streamlined

    mix-create

  • Better VoIP connectivity

    beagle-call

  • Message Correction, Retraction and quick replies

    beagle-edit-message

Major changes

  • Adjusted font size in chat history (message text is slightly bigger)
  • Improved support for XMPP URI (requires beagle-im-set-as-default helper app)
  • Improved behaviour of service discovery window (indentation of items, scrolling)
  • Improved handling of responses for <iq/> requests which should fix some OMEMO key publication issues.
  • Improved message text selection
  • Remembers size of chats/roster windows
  • Improved auto-completion of nicknames.
  • Stopped downloading link previews if message with that link was received from someone outside of roster.

Fixes

  • Fixed issue with respecting global settings for Allow subscription and Request subscription
  • Fixed Typo in Authorization menu
  • Fixed issue with not showing notifications for incoming groupchat messages containing keywords
  • Improved scrolling behavior (scrolling to found or first unread message, scrolling on the opening of a chat and concurrency issue)
  • Fixed an issue with a chat message not being resized properly when window size was changed.
  • Fixed issue with text color in chat history
  • Fixed issue with invisible selection in the search dialog
  • Added possible workaround for crashes when preview is for a movie
  • Fixed crash when the server is not sending Content-Length for uploaded files.

Development

  • Fixed issues with compilation on XCode 12
  • Improved layout in chat logs (internal change)

SiskinIM 6.0

  • Improved detection of server features and prompting to enable them in SiskinIM
  • Added request for background fetch on push notification and fixed crashes when push notification is tapped just after it is displayed (#siskinim-205, #siskinim-206)
  • Fixed issue with using wrong speaker during the VoIP call (#siskinim-211)
  • Added warning when access to microphone or camera is not granted during initialization of a video call (#siskinim-213)
  • Improved account removal mechanism (#siskinim-199)
  • Improved look of the QR codes (#siskinim-195)
  • Fixed an issue with not dismissing room creation view after room is created (#siskinim-230)
  • Fixed an issue with encrypting files with AESGCM send to MUC rooms when default encryption is set to OMEMO (#siskinim-237)

    siskin-chat-list siskin-chat

Downloads

You can download both application from their respective app-stores: Beagle IM from macOS appstore and Siskin IM from iOS appstore and star them on GitHub: Siskin IM on GitHub and Beagle IM on GitHub

Join the discussion

You can discuss all-things-Tigase (including our client apps) on our groupchat: tigase@muc.tigase.org

join-discussion-qr-code

September 02, 2020

A look at Halcyon

A look at Halcyon

Some time ago, we started developing multiplatform XMPP Library called Halcyon based on Kotlin Multiplatform by Jetbrains. Our plan is to allow using the same library in different target environments: JVM, Android, JavaScript and Native. Currently we are focused on JVM and JavaScript targets.

In this post we will try to show library design and example of usage.

halcyon-idea

Before you start

Because Halcyon isn’t published in any Maven repository (yet), you need to compile it yourself. We believe, it will not be a problem. The only two things you need to do is to clone repository and compile library:

git clone https://github.com/tigase/halcyon.git
cd halcyon
./gradlew publishToMavenLocal

Thats all. Now Halcyon is in your local Maven repository.

Let’s do something

We recommend using Gradle to build everything (except for towers and bridges maybe). You can also use Maven, it doesn’t matter. Just use one of them, to prevent problems with dependencies. Here is sample build.gradle.kts file, the most important this is to enable kotlin plugin and include Hayclon in the list of dependencies:

plugins {
    java
    kotlin("jvm") version "1.3.61"
}

repositories {
    mavenLocal()
    jcenter()
    mavenCentral()
}

dependencies {
    implementation(kotlin("stdlib-jdk8"))
    implementation("tigase.halcyon:halcyon-core-jvm:0.0.1")
    testCompile("junit", "junit", "4.12")
}

configure<JavaPluginConvention> {
    sourceCompatibility = JavaVersion.VERSION_1_8
}
tasks {
    compileKotlin {
        kotlinOptions.jvmTarget = "1.8"
    }
    compileTestKotlin {
        kotlinOptions.jvmTarget = "1.8"
    }
}

Let’s add some Kotlin code:

fun main(args: Array<String>) {
    val client = Halcyon()
    client.configure {
        userJID = "user@sampleserver.org".toBareJID()
        password = "secret"
    }
    client.connectAndWait()
    client.disconnect()
}

This simple code creates XMPP client, connects to XMPP server and then disconnects.

To show how to work with Halcyon library, we will by adding code to this small code base.

Events

Halcyon is events-driven library. It means, that each part of library may publish event to event bus and all registered listeners will receive it.

Lets add some code to see what is being send and received over XMPP stream:

client.eventBus.register<ReceivedXMLElementEvent>(ReceivedXMLElementEvent.TYPE) { event ->
    println(">> ${event.element.getAsString()}")
}
client.eventBus.register<SentXMLElementEvent>(SentXMLElementEvent.TYPE) { event ->
    println("<< ${event.element.getAsString()}")
}

To listen for all events since the connection is started, we have to add this code before client.connectAndWait().

All events extend class tigase.halcyon.core.eventbus.Event, so you can easily find them all in your favourite IDE.

Each module may have it’s own set of events, so please check documentation or source code of modules of interest.

Request

Now we will look at one of the most interesting things in XMPP: requests and responses.

XMPP protocol allows sending request to another entity and receive response. Why is it so exciting? Because we can ping other clients, or ask for their local time! Ok, stop joking. Of course above examples are true, but with request-response we can do much more than simple sending messages: we can manage our contacts list, we can manage Multi User Chatrooms, we can execute remote methods on server or other clients.

As an example we will ping other XMPP entity (it may be server or other client). First we need to get PingModule to be able to use its request builder:

val pingModule = client.getModule<PingModule>(PingModule.TYPE)!!

Ping module has method ping() which creates a request builder (note, that it doesn’t send anything yet!) configured to understand response and return it as object. Method ping() has optional parameter jid. If is not provided, then ping will be send to the server to which the client is connected.

val request = pingModule.ping()
request.response { result ->
    when (result) {
        is IQResult.Success -> println("Pong in ${result.get()!!.time} ms")
        is IQResult.Error -> println("Oops! Error ${result.error}")
    }
}
request.send()

There is also a different way to add response handler to the request:

request.handle {
    success { request, iq, result -> println("Pong in ${result!!.time} ms") }
    error { request, iq, errorCondition, message -> println("Oops! Error $errorCondition") }
}

Use the one that you prefer.

One more example: we will check list of features of our server:

val discoveryModule = client.getModule<DiscoveryModule>(DiscoveryModule.TYPE)!!
discoveryModule.info("sampleserver.org".toJID()).handle {
    error { request, iq, errorCondition, message -> println("Oops! Error $errorCondition") }
    success { request, iq, result ->
        println("Server JID: ${result!!.jid}")
        println("Features:")
        result!!.features.forEach { println(" - $it") }
    }
}.send()

Messages

This chapter will be very hard, mostly because MessageModule isn’t finished yet. We haven’t made a design decision yet - how this module should work. It is good for you though, because we can create message stanza from scratch! And it’s cool!

This is how message stanza look like:

<message
      from='juliet@example.com/balcony'
      id='ktx72v49'
      to='romeo@example.net'>
    <body>Art thou not Romeo, and a Montague?</body>
</message>

Let’s try to create this stanza in Kotlin and send it.

var messageRequest = client.request.message {
    to = "romeo@example.net".toJID()
    body = "Art thou not Romeo, and a Montague?"
}
messageRequest.send()

The only thing currently implemented in MessageModule is MessageReceivedEvent, useful to handle all incoming message stanzas:

client.eventBus.register<MessageReceivedEvent>(MessageReceivedEvent.TYPE) { event ->
    println("Message from ${event.fromJID}: ${event.stanza.body}")
}

Roster and presence

Ok, we can send a message to anybody, but most of the time we want to send them to our friends. We need a list of our friends. Luckily such list is available out-of-box in XMPP protocol: it is called Roster.

It shouldn’t be a surprise, but to manage your roster you need RosterModule:

var rosterModule = client.getModule<RosterModule>(RosterModule.TYPE)!!

We can add (or update, with the same method) roster items, remove and list them.

val allRosterItems = rosterModule.store.getAllItems()

RosterItem contains JabberID of the contact, list of groups being assigned to, status of subscription (if contact is allowed to see our presence or not, and if we are allowed to see it’s presence).

Presence is “status of contact”. You can see if your contacts are online, offline or maybe you shouldn’t send any message to someone because he has “Do Not Disturb” status.

As an example, we will list all contacts from the roster and their presence:

rosterModule.store.getAllItems().forEach { rosterItem ->
    val presenceStanza = presenceModule.getBestPresenceOf(rosterItem.jid)
    println("${rosterItem.name} <${rosterItem.jid}> : ${presenceStanza?.show ?: "Offline"}")
}

Thanks…

…for being here up to this point. We hope you enjoyed reading about Halcyon library, and you liked it even though it is not finished yet.

Please share you thoughts and ideas at our group chat tigase@muc.tigase.org or on library GitHub page.

Last updated: September 22, 2020 03:20 AM