Kegan Dougal

7 posts tagged with "Kegan Dougal" (See all Author)

Low Bandwidth Matrix: An implementation guide

10.06.2021 17:08 — Tutorials Kegan Dougal
Last update: 10.06.2021 11:28

Disclaimer: Low bandwidth Matrix is experimental, not yet standardised, and subject to change without notice.

This guide is for Matrix developers who want to support MSC3079: Low Bandwidth CS API in their clients/servers. Please read the experimental MSC if you want to learn more about what is happening at a protocol level. If you want a high level overview of low bandwidth Matrix and why you should care, watch the 12 minute demo on Matrix Live.

Matrix currently uses HTTP APIs with JSON data to communicate from the client to the server. This is widely supported but is not very bandwidth efficient. This means that the protocol is slower, more costly and less able to be used on low bandwidth links (e.g 2G networks) which are common in certain parts of the world. MSC3079 defines a low bandwidth protocol using CoAP and CBOR instead of HTTP and JSON respectively. In the future homeservers will natively support some form of low bandwidth protocol. However, at present, no homeserver natively supports MSC3079. Therefore, this guide will set up a low bandwidth proxy server which can be put in front of any Matrix homeserver (Synapse, Dendrite, Conduit, etc) to make it MSC3079-compatible. This guide will also configure an Android device to speak MSC3079.

Low bandwidth Matrix currently does not support web browsers due to their inability to send UDP traffic. You do not need to be running a homeserver to follow this tutorial.

🔗Setting up a low bandwidth proxy for your homeserver

Prerequisites:

  • Go 1.13+
  • openssl to generate a self-signed DTLS certificate, or an existing certificate you want to use.
  • Linux or Mac user

Steps:

  • Clone the repo: git clone https://github.com/matrix-org/lb.git
  • Build the low bandwidth proxy: go build ./cmd/proxy
  • Generate a elliptic curve DTLS key/certificate: (we use curve keys as they are smaller than RSA keys, but both work.)
    openssl ecparam -name prime256v1 -genkey -noout -out private-key.pem
    openssl req -new -x509 -key private-key.pem -out cert.pem -days 365
    # you now have cert.pem and private-key.pem
    
  • Run it pointing at matrix.org:
    ./proxy -local 'https://matrix-client.matrix.org' \
    --tls-cert cert.pem --tls-key private-key.pem \
    --advertise "http://127.0.0.1:8008" \
    --dtls-bind-addr :8008
    
  • You should see something like this:
    INFO[0000] Listening on :8008/tcp to reverse proxy from http://127.0.0.1:8008 to https://matrix-client.matrix.org - HTTPS enabled: false 
    INFO[0000] Listening for DTLS on :8008 - ACK piggyback period: 5s
    

Mac users: If you are having trouble generating EC certificates, make sure you are using OpenSSL and not LibreSSL which comes by default: openssl version. To use OpenSSL, brew install openssl which then dumps the binary to /usr/local/opt/openssl/bin/openssl.

To test it is working correctly:

# build command line tools we can use to act as a low bandwidth client
go build ./cmd/jc
go build ./cmd/coap

# do a CoAP GET request to matrix.org via the proxy
./coap -X GET -k 'http://localhost:8008/_matrix/client/versions' | ./jc -c2j '-'

{"unstable_features":{"io.element.e2ee_forced.private":false,"io.element.e2ee_forced.public":false,"io.element.e2ee_forced.trusted_private":false,"org.matrix.e2e_cross_signing":true,"org.matrix.label_based_filtering":true,"org.matrix.msc2432":true,"org.matrix.msc3026.busy_presence":false,"uk.half-shot.msc2666":true},"versions":["r0.0.1","r0.1.0","r0.2.0","r0.3.0","r0.4.0","r0.5.0","r0.6.0"]}

If this doesn't work:

  • Check the proxy logs for errors (e.g bad hostname)
  • Try adding -v to ./coap (e.g bad method or path)
  • Run the proxy with SSLKEYLOGFILE=ssl.log and inspect the decrypted traffic using Wireshark.

Otherwise, congratulations! You now have a low bandwidth proxy! You can connect to your proxy just like you would to matrix.org or any other homeserver.

🔗Security considerations

  • The proxy acts as a man in the middle and can read all non-E2EE traffic, including login credentials. DO NOT USE UNTRUSTED LOW BANDWIDTH PROXY SERVERS. Only use proxy servers run by yourself or the homeserver admins.

🔗Further reading

🔗Setting up a custom Element Android

We'll add low bandwidth matrix to Element Android and iOS by default once it's standardised - but while things are still experimental, here's a guide for how to build Element Android to do it yourself if you feel the urge. This can be used as inspiration for other Matrix clients too.

Prerequisites:

  • Android Studio

Steps:

  • Clone the repo: git clone https://github.com/vector-im/element-android.git
  • Checkout kegan/lb: git checkout kegan/lb. This branch replaces all HTTP traffic going to /_matrix/client/* with LB traffic. /_matrix/media traffic is left untouched. This branch also disables TLS checks entirely so self-signed certificates will work.
  • Clone the low bandwidth repo if you haven't already: git clone https://github.com/matrix-org/lb.git
  • In the low bandwidth repo, build the mobile bindings:
    go get golang.org/x/mobile/cmd/gomobile
    cd mobile
    # if gomobile isn't on your path, then ~/go/bin/gomobile
    gomobile bind -target=android
    
  • Copy the output files to a directory in the Element Android repo which Gradle will pick up:
    mkdir $PATH_TO_ELEMENT_ANDROID_REPO/matrix-sdk-android/libs
    cp mobile-sources.jar $PATH_TO_ELEMENT_ANDROID_REPO/matrix-sdk-android/libs
    cp mobile.aar $PATH_TO_ELEMENT_ANDROID_REPO/matrix-sdk-android/libs
    
  • Open the project in Android Studio.
  • Build and run on a device/emulator.
  • Configure the proxy's --advertise address. If you are running on a local device, restart the proxy with an --advertise of your machines LAN IP e.g 192.168.1.2 instead of 127.0.0.1. If you are running on an emulator, restart the proxy with an --advertise of the host IP: 10.0.2.2. The URL scheme should be https not http, else image loading won't work as Element Android won't download media over http.
  • Login to your matrix.org account via the proxy with the --advertise address as the HS URL e.g https://192.168.1.2:8008 or https://10.0.2.2:8008. The port is important.

To verify it is running via low bandwidth:

  • Install Wireshark.
  • Restart the proxy with the environment variable SSLKEYLOGFILE=ssl.log.
  • Run tcpdump on the right interface e.g: sudo tcpdump -i en0 -s 0 -v port 8008 -w lb.pcap
  • Force stop the android app to forcibly close any existing DTLS connections.
  • Re-open the app.
  • Open lb.pcap in Wireshark and set ssl.log as the Pre-Master Secret log filename via Preferences -> Protocols -> TLS -> Pre-Master Secret log filename.
  • Check there is DTLS/CoAP traffic.

🔗Performance

To send a single 'Hello World' message to /room/$room_id/send/m.room.message/$txn_id and receive the response, including connection setup:

ProtocolNum packetsTotal bytes
HTTP2+JSON436533
CoAP+CBOR61440

🔗Limitations

  • CoAP OBSERVE is not enabled by default. This extension allows the server to push data to the client so the client doesn't need to long-poll. It is not yet enabled because of the risk of state synchronisation issues between the proxy and the client. If the proxy gets restarted, the client will not receive sync updates until it refreshes its subscription, which happens infrequently. During this time the client is not aware that anything is wrong.
  • CoAP uses Blockwise Transfer to download large responses. Each block must be ACKed before the next block can be sent. This is less efficient than TCP which has a Receive Window which allows multiple in-flight packets at once. This means CoAP is worse at downloading large responses, requiring more round trips to completely send the data.
  • The current version of /sync sends back much more data than is strictly necessary. This means the initial sync can be slower than expected. On a low kbps link this can flood the network with so much data that the sync stream begins to fall behind. Future work will look to optimise the sync API.
  • The proxy currently doesn't implement the low bandwidth response in /versions.

An Adventure in IRC-Land

14.03.2017 00:00 — General Kegan Dougal

Hi everyone. I'm Kegan, one of the core developers at matrix.org. This is the first in a series on the matrix.org IRC bridge. The aim of this series is to try to give a behind the scenes look at how the IRC bridge works, what kinds of problems we encountered, and how we plan to scale in the future. This post looks at how the IRC bridge actually works.

Firstly, what is "bridging"? The simple answer is that it is a program which maps between different messaging protocols so that users on different protocols can communicate with each other. Some protocols may have features which are not supported in the other (typing notifications in Matrix, DCC - direct file transfers - in IRC). This means that bridging will always be "inferior" to just using the respective protocol. That being said, where there is common ground a bridge can work well; all messaging protocols support sending and receiving text messages for example. As we'll see however, the devil is in the detail...

A lot of existing IRC bridges for different protocols share one thing in common: they use a single global bot to bridge traffic. This bot listens to all messages from IRC, and sends them to the other network. The bot also listens for messages from users on the other network, and sends messages on their behalf to IRC. This is a lot easier than having to maintain dedicated TCP connections for each user. However, it isn't a great experience for IRC users as they:

  • Don't know who is reading messages on a channel as there is just 1 bot in the membership list.
  • Cannot PM users on the other network.
  • Cannot kick/ban users on the other network without affecting everyone else.
  • Cannot bing/mention users on the other network easily (tab completion).
We made the decision very early on that we would keep dedicated TCP connections for each Matrix user. This means every Matrix user has their own tiny IRC client. This has its own problems:
  • It involves multiple connections to the IRCd so you need special permission to set up an i:line.
  • You need to be able to support identification of individual users (via ident or unique IPv6 addresses).
  • With all these connections to the same IRC channels, you need to have some way to identify which incoming messages have already been handled and which have not.

🔗Mapping Rooms

So now that we have a way to send and receive messages, how do we map the rooms/channels between protocols? This isn't as easy as you may think. We can have a single static one-to-one mapping:

  • All messages to #channel go to !abcdef:matrix.org.
  • All messages from !abcdef:matrix.org go to #channel.
  • All PMs between @alice:matrix.org and Bob go to !wxyz:matrix.org and the respective PM on IRC.
In order to make PMs secure, we need to limit who can access the room. This is done by making the Matrix PM room "invite-only". This can cause problems though if the Matrix user ever leaves that room: they won't be able to ever re-join! The IRC bridges get around this by allowing Matrix users to replace their dedicated PM room with a new room, and by checking to make sure that the Matrix user is inside the room before sending messages.

Then you have problem of "ownership" of rooms. Who should be able to kick users in a bridged room? There are two main scenarios to consider:

  • The IRC channel has existed for a while and there are existing IRC channel operators.
  • The IRC channel does not exist, but there are existing Matrix moderators.
In the first case, we want to defer ownership to the channel operators. This is what happens by default for all bridged IRC channels on matrix.org. The Matrix users have no power in the room, and are at the mercy of the IRC channel operators. The channel operators are represented by virtual Matrix users in the room. However, they do not have any power level: they are at the same level as real Matrix users. Why? The bridge does this because, unlike IRC, it's not possible in Matrix to bring a user to the same level as yourself (e.g +o), and then downgrade them back to a regular user (e.g. -o). Instead, the bridge bot itself acts as a custodian for the room, and performs privileged IRC operations (topic changing, kickbans, etc) on the IRC channel operator's behalf.

In the second case, we want to defer ownership to the Matrix moderators. This is what happens when you "provision a room" in Matrix. The bridge will PM a currently online channel operator and ask for their permission to bridge to Matrix. If they accept, the bridge is made and the power levels in the pre-existing Matrix room are left untouched, giving moderators in Matrix control over the room. However, this power doesn't extend completely to IRC. If a Matrix moderator grants moderator powers to another Matrix user, this will not be mapped to IRC. Why? It's not possible for the bridge to give chanops to any random user on any random IRC channel, so it cannot always honour the request. This relies on the humans on either side of the bridge to communicate and map power accordingly. This is done on purpose as there is no 100% perfect mapping between IRC powers and Matrix powers: it's always going to need to compromise which only a human can make.

Finally, there is the problem of one-to-many mappings. It is possible to have two Matrix rooms bridged to the same IRC channel. The problem occurs when a Matrix user in one room speaks. The bridge can easily map that to IRC, but unless it also maps it back to Matrix, the message will never make it to the 2nd Matrix room. The bridge cannot control/puppet the Matrix user who spoke, so instead it creates a virtual Matrix user to represent that real Matrix user and then sends the message into the 2nd Matrix room. Needless to say, this can be quite confusing and we strongly discourage one-to-many mappings for this reason.

🔗Mapping Messages

Mapping Matrix messages to IRC is rather easy for the most part. Messages are passed from the Homeserver to the bridge via the AS API, and the bridge sends a textual representation of the message to IRC using the IRC connection for that Matrix user. The exact form of the text for images, videos and long text can be quite subjective, and there is inevitably some data loss along the way. For example, you can send big text headings, tables and lists in Matrix, but there is no equivalent on IRC. Thankfully, most Matrix users are sending the corresponding markdown and so the formatting can be reasonably preserved by just sending the plaintext (markdown) body.

Mapping IRC messages to Matrix is more difficult: not because it's hard to represent the message in Matrix, but because of the architecture of the bridge. The bridge maintains separate connections for each Matrix user. This means the bridge might have, for example, 5 users (and hence connections) on the same channel. When an IRC user sends a message, the bridge gets 5 copies of the message. How does the bridge know:

  • If the message has already been sent?
  • If the message is an intentional duplicate?
The IRC protocol does not have message IDs, so the bridge cannot de-duplicate messages as they arrive. Instead, it "nominates" a single user's connection to be responsible for delivering messages from that channel. This introduces another problem though. Long-lived TCP connections are fickle things, and can fail without any kind of visible warning until you try to send bytes down it. If a user's connection drops, another user needs to take over responsibility for delivering messages. This is what the "IRC Event Broker" class does. It allows users to "steal" messages if the bridge has any indication that the connection in charge has dropped. This technique has worked well for us, and gives us the ability to have more robust connections to the channel than with one TCP connection alone.

🔗Admin Rooms

Admin rooms are private Matrix rooms between a real Matrix user and the bridge bot. It allows the Matrix user to control their connection to IRC. It allows:

  • The IRC nick to be changed.
  • The ability to issue /whois commands.
  • The ability to bypass the bridge and send raw IRC commands directly down the TCP connection (e.g. MODE commands).
  • The ability to save a NickServ password for use when the bridge reconnects you.
  • The ability to disconnect from the network entirely.
To perform these actions, Matrix users send a text message which starts with a command name, e.g !whois $ARG. Like all commands, you expect to get a reply once you've issued it. However, IRC makes this extremely difficult to do. There is no request/response pair like there is with HTTP requests. Instead, the IRC server may:
  • Ignore the request entirely.
  • Send an error you're aware of (in the RFC/most servers)
  • Send some information which can be assumed to indicate success.
  • Send an error you're unaware of.
  • Send some information which sometimes indicates success.
This makes it very difficult to know if a request succeeded or failed, and I'll go into more detail in the next post which focuses on problems we've encountered when developing the IRC bridge. This room is also used to inform the Matrix user about general information about their IRC connection, such as when their connection has been lost, or if there are any errors (e.g. "requires chanops to do this action"). The bridge makes no effort to parse these errors, because it doesn't always know what caused the error to happen.

🔗Wrapup

Developing a comprehensive IRC bridge is a very difficult task. This post has outlined a few of the ways in which we've designed our bridge, and some of the general problems in this field. The bridge is constantly improving as we discover new edge cases with the plethora of IRCd implementations out there. The next post will look at some of these edge cases and look back at some previous outages and examine why they occurred.

New bridged IRC network: GIMPNet

06.03.2017 00:00 — General Kegan Dougal

Hey everyone! As of last week, we are now bridging irc.gimp.org (GIMPNet) for all your GTK+/GNOME needs! It's running a bleeding-edge version of the IRC bridge which supports basic chanops syncing from IRC to Matrix. This means that if an IRC user gives chanops to a Matrix connection, the bridge will give that Matrix user moderator privileges in the room, allowing them to set the room topic/avatar/alias/etc! We hope this will make customising Matrix-bridged rooms a lot easier.

For a more complete list of current and future bridged IRC networks, see the official wishlist.

Matrix-IRC Bridge reaches v0.4.0

15.08.2016 00:00 — Tech Kegan Dougal

A new version of the IRC bridge has been released onto NPM and the matrix.org bridges!

The IRC bridge has undergone quite a number of modifications since its original inception over a year ago. Version 0.4 introduces a number of additional features and improvements, which can be found in the changelog. These include automatically linkifying large blocks of text and mirroring kicks/bans to and from Matrix.

With a plethora of protocol gotchas and non-standard features on well-known IRC networks, IRC is a challenging protocol to work with. It's inevitable that some corner cases are not handled well by the bridge. Over time, the bridge has been hardened by edge cases which we have encountered and patched. These releases signify the continual improvement in the robustness of the bridge, which we aim to continue with into the foreseeable future.

Performance wise, our busiest bridge which we host is the bridge to Freenode. We now have over 1300 active connections to it and have a steady rate of about 240 messages per minute going through to Matrix. We expect to see this number increase significantly over the next few months. Let's see what the next year will bring!

Micropub support as an Application Service!

29.07.2015 00:00 — Tech Kegan Dougal

I was at IndieWebCamp Edinburgh last week and during the hack day I created a Matrix Application Service (AS) which could act as an IndieWeb Micropub client. Any Matrix message sent to the AS (@micropub:domain) would be converted to a request to a Micropub endpoint.

This required the AS to support IndieAuth - which it does by sending !indieauth http://yourdomain.dom to @micropub:domain which then returns an OAuth2 URL to login via. Currently, the AS just supports a 1:1 mapping from m.text to h:entry but in the future, it can be expanded to include categories and potentially the reverse mapping (where Micropub clients can act as Matrix users!).

Overall, it was a great weekend and I look forward to adding more support for IndieWeb protocols in the future.

The All New Matrix-IRC Application Service

22.04.2015 00:00 — Tech Kegan Dougal

This post has now been edited into a guide - you can find the source in github, and the formatted guide on the matrix.org website!


Like a lot of open source projects, we use IRC a lot. Naturally, we also use Matrix to communicate with each other. For some time now we've had an IRC bot sitting on specific channels to "bridge" together IRC and Matrix. This bot simply sent IRC messages when it received Matrix messages and vice versa. As we started to rely on it more and more though, we realised that there were things that were impossible for simple client-side bots to do by themselves. This spurred the development of Application Services which I introduced in my previous post. In this blog post, I want to outline some of the features and techniques of the IRC application service which we've been working on over the past few weeks.

Features:

  • Specific channel-to-matrix room bridging : This is what the original IRC bot did. You can specify specific channels and specific room IDs, and messages will be bridged.
  • Dynamic channel-to-matrix room bridging : This allows Matrix users to join any channel on an IRC network, rather than being forced to use one of the specific channels configured.
  • Two-way PM support : IRC users can PM the virtual "M-" users and private Matrix rooms will be created. Likewise, Matrix users can invite the virtual "@irc_Nick:domain" user IDs to a room and a PM to the IRC nick will be made.
  • IRC nick changing support : Matrix users are no longer forced to use "M-" nicks and can change them by sending "!nick" messages directly to the bridge.
  • Ident support : This allows usernames to be authenticated for virtual IRC clients, which means IRC bans can be targeted at the Matrix user rather than the entire application service.
The use of the Application Services API means:
  • The bot can reserve user IDs. This prevents humans from registering for @irc_... user IDs which would then clash with the operation of the bot.
  • The bot can reserve room aliases. This prevents humans from register for #irc_... aliases which would then clash with the operation of the bot.
  • The bot can trivially manage hundreds of users. Events are pushed to the application service directly. If you tried to do this as a client-side bot, you would need one event stream connection per virtual user.
  • The bot can lazily create rooms on demand. This means Matrix users can join non-existent room aliases and have the application service quickly track an IRC channel and create a room with that alias, allowing the join request to succeed.
Implementation details:
  • Written in Node.js, designed to be run using forever.
  • Built on the generic matrix-appservice-node framework.
  • Supports sending metrics in statsd format.
  • Uses matrix-appservice-node to provide a standardised interface when writing application services, rather than an explicit web framework (though under the hood matrix-appservice-node is using Express).
At present, the IRC application service is in beta, and is being run on #matrix and #matrix-dev. If you want to give it a go, check it out on Github - it is not currently released on npm. N.B. it requires features from the develop branch of synapse; either run your own synapse off the develop branch or wait a few days for us to release Synapse 0.9.0.

Needless to say, we look forward to this being the first of many full network<->network bridges into Matrix - come chat on #matrix:matrix.org if you'd like to write or run your own! Next up is Lync and XMPP...

Introduction to Application Services

02.03.2015 00:00 — Tutorials Kegan Dougal

This post has now been edited into a guide - you can find the source in github, and the formatted guide on the matrix.org website!


Hi everyone. I'm Kegan, one of the core developers on Matrix. I'd like to explain a bit more about one of the upcoming features in Matrix: Application Services. This is an entirely new component in the Matrix architecture which gives great power (along with great responsibility!) to the wielder.

Application services are distinct modules which which sit alongside a home server providing arbitrary extensible functionality decoupled from the home server implementation. Just like the rest of Matrix, they communicate via HTTP using JSON. Application services function in a very similar way to traditional clients, but they are given much more power than a normal client. They can reserve entire namespaces of room aliases and user IDs for their own purposes. They can silently monitor events in rooms, or any events directed at any user ID. This power allows application services to have extremely useful abilities which overall enhance the end user experience.

One of the main use cases for application services is for protocol bridges. As you may know, we have an IRC bridge bot on matrix.org which resides as a user on #matrix, #matrix-dev, #openwebrtc and #vuc which bridges into freenode. There is nothing special about this bot; it is just treated as a client. However, this limits the things the bot can do. For example, the bot cannot reserve the virtual user IDs it creates, and cannot lazily bridge arbitrary IRC rooms on-the-fly. By using application services, the IRC bot can do both of these things. This would allow any Matrix user to join a room alias which doesn't exist: say #irc.freenode.python:matrix.org, which would then tell the application service to create a new Matrix room, make the alias for it, join #python on freenode and bridge into it. Any IRC user on #python would then be provisioned as a virtual user e.g. @irc.freenode.alice:matrix.org. This also allows PMs to be sent directly to @irc.freenode.alice:matrix.org, no matter what channel Alice is on.

Application services have enormous potential for creating new and exciting ways to transform and enhance the core Matrix protocol. For example, you could aggregate information from multiple rooms into a summary room, or create throwaway virtual user accounts to proxy messages for a fixed user ID on-the-fly. As you may expect, all of this power assumes a high degree of trust between application services and home servers. Only home server admins can allow an application service to link up with their home server, and the application service is in no way federated to other home servers. You can think of application services as additional logic on the home server itself, without messing around with the book-keeping that home servers have to do. This makes adding useful functionality very easy.

Example

The application service (AS) API itself uses webhooks to communicate from the home server to the AS:

  • Room Alias Query API : The home server hits a URL on your application server to see if a room alias exists.
  • User Query API : The home server hits a URL on your application server to see if a user ID exists.
  • Push API : The home server hits a URL on your application server to notify you of new events for your users and rooms.

A very basic application service may want to log all messages in rooms which have an alias starting with "#logged_" (side note: logging won't work if these rooms are using end-to-end encryption).

Here's an example of a very basic application service using Python (with Flask and Requests) which logs room activity:

# app_service.py:

import json, requests  # we will use this later
from flask import Flask, jsonify, request
app = Flask(__name__)

@app.route("/transactions/<transaction>", methods=["PUT"])
def on_receive_events(transaction):
    events = request.get_json()["events"]
    for event in events:
        print "User: %s Room: %s" % (event["user_id"], event["room_id"])
        print "Event Type: %s" % event["type"]
        print "Content: %s" % event["content"]
    return jsonify({'{'}{'}'})

if __name__ == "__main__":
    app.run()

Set your new application service running on port 5000 with:

python app_service.py

The home server needs to know that the application service exists before it will send requests to it. This is done via a registration YAML file which is specified in Synapse's main config file e.g. homeserver.yaml. The server admin needs to add the application service registration configuration file as an entry to this file.

# homeserver.yaml
app_service_config_files:
  - "/path/to/appservice/registration.yaml"

NB: Note the "-" at the start; this indicates a list element. The registration file registration.yaml should look like:

# registration.yaml

# this is the base URL of the application service
url: "http://localhost:5000"

# This is the token that the AS should use as its access_token when using the Client-Server API
# This can be anything you want.
as_token: wfghWEGh3wgWHEf3478sHFWE

# This is the token that the HS will use when sending requests to the AS.
# This can be anything you want.
hs_token: ugw8243igya57aaABGFfgeyu

# this is the local part of the desired user ID for this AS (in this case @logging:localhost)
sender_localpart: logging
namespaces:
  users: []
  rooms: []
  aliases:
    - exclusive: false
      regex: "#logged_.*"

You will need to restart the home server after editing the config file before it will take effect.

To test everything is working correctly, go ahead and explicitly create a room with the alias "#logged_test:localhost" and send a message into the room: the HS will relay the message to the AS by PUTing to /transactions/<tid> and you should see your AS print the event on the terminal. This will monitor any room which has an alias prefix of "#logged_", but it won't lazily create room aliases if they don't already exist. This means it will only log messages in the room you created before: #logged_test:localhost. Try joining the room "#logged_test2:localhost" without creating it, and it will fail. Let's fix that and add in lazy room creation:

@app.route("/rooms/<alias>")
def query_alias(alias):
    alias_localpart = alias.split(":")[0][1:]
    requests.post(
        # NB: "TOKEN" is the as_token referred to in registration.yaml
        "http://localhost:8008/_matrix/client/api/v1/createRoom?access_token=TOKEN",
        json.dumps({'{'}
            "room_alias_name": alias_localpart
        {'}'}),
        headers={'{'}"Content-Type":"application/json"{'}'}
    )
    return jsonify({'{'}{'}'})

This makes the application service lazily create a room with the requested alias whenever the HS queries the AS for the existence of that alias (when users try to join that room), allowing any room with the alias prefix #logged_ to be sent to the AS. Now try joining the room "#logged_test2:localhost" and it will work as you'd expect.  You can see that if this were a real bridge, the AS would have checked for the existence of #logged_test2 in the remote network, and then lazily-created it in Matrix as required.

Application services are powerful components which extend the functionality of home servers, but they are limited. They can only ever function in a "passive" way. For example, you cannot implement an application service which censors swear words in rooms, because there is no way to prevent the event from being sent. Aside from the fact that censoring will not work when using end-to-end encryption, all federated home servers would also need to reject the event in order to stop developing an inconsistent event graph. To "actively" monitor events, another component called a "Policy Server" is required, which is beyond the scope of this post.  Also, Application Services can result in a performance bottleneck, as all events on the homeserver must be ordered and sent to the registered application services.  If you are bridging huge amounts of traffic, you may be better off having your bridge directly talk the Server-Server federation API rather than the simpler Application Service API.

I hope this post demonstrates how easy it is to create an application service, along with a few ideas of the kinds of things you can do with them. Obvious uses include build protocol bridges, search engines, invisible bots, etc. For more information on the AS HTTP API, check out the new Application Service API section in the spec, or the raw drafts and spec in https://github.com/matrix-org/matrix-doc/.  The AS API is not yet frozen, so feedback is very welcome!