Building/configuring a TURN server

So, ideally again, maybe freedombox could provide a local url for the router to request with a parameter like ?new_ip=x.x.x.x to get coturn updated.

This is one of the pending items for the Coturn setup. The current plan is to implement as follows:

Regularly ping an external server to find the external IP address of FreedomBox (or somehow use Dynamic DNS module to since it has do that anyway). The process can be done every 30 minutes or even sooner since it is not expensive. Once the IP is found if it is different from the one configured in Coturn, reconfigure Coturn with external-ip= and reload the configuration. To discover the external IP, public STUN servers could be used. stun-client command can be invoked to communicate with a public STUN server. We may allow the user to choose a public STUN server for privacy reasons or pick from a list of servers that we know respect privacy. Perform this entire process only if the user has selected that they are behind a router (and not directly connected to the Internet). Warn the user about the privacy consequences.

1 Like

Finding out the IP of my domain turns out to be quite straightforward on linux. One can just use host, dig or nslookup like
host mydomain.net <ip of my dns e.g. local ip of my router>

But unfortunately I see no way to only receive the ip as a single value but only complex output in shell by this.

So a better way might be python’s

import socket
print(socket.getaddressbyname('mydomain.net'))

which returns exactly what I need to alter the config and reload coturn as @sunil described.

P.S.
Just to mention: The coturn service does not support the reload action, only restart.

P.P.S.
YES! YES! It works! :purple_heart:
Adding external-ip = 93.158.178.34 (just made this IP up of course) and restarting coturn and voilĂĄ: The video call between my cell with VPN and my laptop immediately and astonishing stable just works!
So now it’s only the automation of the dynamic IP change that’s left to do :stuck_out_tongue_winking_eye:

1 Like

DNS approach assumes that the mapping is up-to-date (which is very reasonable assumption). It may be better for privacy than using STUN. This an approach to consider.

The router seems the best (and only) point to trigger reliable dns updates upon IP renewals. Conseqently the dns IP info should be most up to date, within the limits of the dns TTL value.

However, if freedombox relies on polling to detect changes, there will always be times were the ICE service does not work, is not reachable and points to a foreign IP. :frowning:
So, it might only be a fallback and make things work in the beginnig and be a bad idea at the same time. I think it really isn’t a proper “production” setup.

The mechanism to implement should make the domain much more continuously reachable (instantly reachable, and the IP renewal interruptions as short as possible).
So maybe, either the router supports to update the freedombox as a second configured dyndns (ddns) provider, or only the freedombox needs to be configured as ddns service in the router, and freedombox will then have to forward the update to the real external dyndns/ddns service whenever getting an update from the router.

Is the ability to get an update about IP address from the router a common feature found in routers?

Yes, all I know, you may also search, for example, for how-to-configure-ddns-in-router

BTW, did somebody also try the new audio and video call support of Conversations.im (XMPP)?

I scripted a quick and dirty fix for the dynamic IP problem in my case.
That might help others until there will be an official solution.

#!/usr/bin/python3                                                              

import socket, shutil

# Read the current coturn config                                                
#                                                                               
config = {}
with open('/etc/coturn/freedombox.conf') as f:
    for line in f:
        key, value = line.partition("=")[::2]
        config[key.strip()] = value.strip()


# Get current IP of TLD                                                         
#                                                                               
current_ip = socket.gethostbyname(config['realm'])

print('Old IP: %s' % config['external-ip'])
print('Current IP: %s' % current_ip)

# If the IP hasn't change, simply do nothing                                    
#                                                                               
if current_ip == config['external-ip']:                                         
    print("Still the same IP (%s)." % config['external-ip'])                    
    print("Doing nothing.")                                                     
                                                                                
# If the IP has actually changed                                                
#                                                                               
else:                                                                           
    print("The IP has changed.")

    # Set external-ip to new value and write it to a new file                   
    #                                                                           
    config['external-ip'] = current_ip
    with open('/etc/coturn/freedombox.conf.new','w') as f:
        for key, value in config.items():
            if value is not "":
                f.write('%s = %s\n' % (key, value))
            else:
                f.write('%s' % key)
                continue
    print("Changed coturn IP to %s." % current_ip)

    try:
        shutil.copyfile('/etc/coturn/freedombox.conf', '/etc/coturn/freedombox.\
conf.bak')
        print("Backuped old config.")
    except:
	print("Could not backup old config!")

    try:
        shutil.copyfile('/etc/coturn/freedombox.conf.new', '/etc/coturn/freedom\
box.conf')
        print("Substituted old config with the new.")
    except:
	print("Could not substitute config file!")

# Restart the coturn service                                                
    #                                                                           
    try:
        os.system("systemctl restart coturn")
        print("Restarted coturn service.")
    except:
        print("Could not restart coturn service!")

With sudo crontab -e I let this script fire every 8 hours.

0 4,12,20 * * *	     python3 /root/update_coturn_external_ip.py

Until now looks like it works smoothely.

1 Like

@homer77, fantastic work, do you have test case or test tool that can distinguish a proper coturn server with external-ip address set and one without?

I tested the coturn server before both with

  • my old setup and with
  • your config of the app.

In both cases I could not connect between my smartphone while it is in cell mode or in a VPN to my Laptop in the same NAT as my FreedomBox.

Since I added external-ip to the config it works flawlessly.

This corresponds to difference in the log I observed. Before the external-ip only the client inside the NAT showed up with the name of account. The one outside didn’t or showed up as some number. Also there were a lot of 401 errors like this:

session 002000000000000010: realm <mydomain.net> user <>: incoming packet message processed, error 401: Unauthorized
IPv4. Local relay addr: 192.168.1.19:49394

As you can see the user tag is empty there. Only the local users in the NAT are shown in the log, the ones from outside cannot be authorized.

But now - after defining external-ip - both clients’ accounts show up, and no obvious error messages!

One problem is stil left:
The log shows that coturn doesn’t find the certs from the config file. So that TLS connections are impossible.

WARNING: cannot find private key file: /etc/coturn/certs/pkey.pem (1)
WARNING: cannot start TLS and DTLS listeners because private key file is not set properly

Checking my log and config I found out that you named the key pkey.pem while I named it privkey.pem. After correcting this in config there’s a working TLS setup.

Still I receive:

set_ctx: ERROR: cannot set DH
ERROR: set_ctx: ERROR: cannot set DH

which vanishes if I add

dh-file=/etc/apache2/ssl/dhparams.pem

in my config.
Now TLS and DTLS are flawlessly working according to the log.

After setting /etc/matrix-synapse/homeserver.yaml up with

turn_uris:
  - "stun:ismus.net:3478?transport=udp"
  - "stun:ismus.net:3478?transport=tcp"
  - "turn:ismus.net:3478?transport=udp"
  - "turn:ismus.net:3478?transport=tcp"
  - "stun:ismus.net:5349?transport=udp"
  - "stun:ismus.net:5349?transport=tcp"
  - "turn:ismus.net:5349?transport=udp"
  - "turn:ismus.net:5349?transport=tcp"

the log tells me also that the 5349 port is used by matrix-synapse.

I guess now I’m ready :grimacing:

Checking only every 8 hours will leave your server unreachable for many hours every day on average.

Another idea to detect IP changes could be to try much more frequent (10 seconds?) pings or connection attempts, but only to a known to work local port of the router using the own public IP. Assuming that the port becomes unreachable if the IP is not the own anymore, that could trigger a DNS check for the new IP.

However, such polling will probably prevent that the freedombox and router enter a power saving state.

@homer77 does your router support entering a custom url for a DDNS service? You could then enter your local freedombox IP there and just let your script wait on a port (http server) to catch the router’s request.

If there is no way to configure a custom DDNS URL, you could try configuring a host or route entry to override a preconfigured DDNS provider, routing the request to your freedombox IP instead.

You’re right regarding the interval. I only thought about my special case that I know that

  • usually my IP changes exclusively once in 24h
  • There are exactly two accounts on my matrix instance - one of them me - so that I exactly know when the server’s needed :wink:

Since the mere IP check isn’t very expensive it wasn’t an actual problem to make the cronjob far more regular.
10 seconds on the other hand seems to me like a heavy overhead. Let’s assume 10 minutes. That would mean that the maximum downtime of the coturn server would be 10 minutes. For most FB users this should be a marginal loss…

This could of course also be adjustable in the VoIP-Helper app’s interface.

Regarding the power saving state:
Are you sure? I mean … aren’t there lots of such regular jobs already running? E.g. if the matrix instance is running and my Android Client is on - aren’t there continous connections between server and client?
I’m not an expert for the power management but I actually doubt that the coturn IP check has to impede that more than any instant messenger service running on your box yet :thinking:

Router/DDNS:
I could use the Fritzbox for DDNS. But as I have a running ddclient config since years I stick to that.
I am not sure if I understand exactly what you mean by catching the router’s request. Can you give an example how that request would look?

Thanks for your elaborating reply.

Ok, I see the polling interval could be shortened, but it’s so avoidable to create constant traffic, might even lead to problems by block/filter/usage listing or analytics, and is noise if you want to monitor your own connections. (at least for a fredombox supported setup)

Concerning the power saving, I think listening on an open connection can be delegated into a low power state of the network adapter, but cron + making nework requests causes CPU activity.

What I first had in mind was starting a webserver with python…

But it could be much better as a test setup if maybe a freedombox developer could provide a hint about how to best configure a new port or subdirectory? served by the webserver that is already running on the freedombox.

Then your script could in the simplest form just loop over an inotifywait that watches that port/directory’s log file, to trigger the update after the router makes the http request.

Your router seems to support entering a custom URL in its DynDNS configuration, at least there seems to be a help page about it. So it should be possible to enter the local freedombox URL there.

EDIT:
(In case your are wondering, I’m myself still looking for updating my filesystem setup, and not there yet.)

When your script just inotifywaits for the router making a request, without extracting any info from it, I think it would first have to trigger your ddclient, to actually update the DNS, and then do your lookup+coturn update.

And your script would already be fully usable in freedombox if it could fallback to polling as long as the logfile does not exist (no DDNS configured in the router), which should also render a warning to the user in the webfrontend/email notifications.

EDIT:

@sunil What could allow the easiest and most versatile configuration of routers might be: To configure an additional internal IP on the freedom box.

(And letting any type of request to that IP trigger a dyndns update (updating the real provider as configured in the freedombox) and then the STUN/TURN update, as suggested above.)

That could allow any type of router with an arbitrary DDNS implementation to trigger an update, no matter the URL or even protocol. Either by directly configuring the custom IP as custom DDNS provider with arbitrary protocol, or by configuring a hosts or route entry on the router, that redirects an available provider to the special freedombox DDNS update IP.

ejabberd and Coturn URLs and shared authentication secret?

Hey, great to have Coturn installed.

Noobie questions… for XMPP/eJabberd do I have to add the Coturn URLs and shared authentication secret to: sudo nano /etc/ejabberd/ejabberd.yml

Or are the URLs and secret only for Matrix Synapse?

In reference to the docs I haven’t found a way to use an external stun/turn config in ejabberd yet. It seems mandatory for ejabberd to use its internal stun/turn server which above doesn’t work with ldap auth afai read.

Besides it seems overhead to use two seperate stun/turn servers for two im services on one box.

So I’m also not sure how to deal with this.

1 Like

Thanks for the clarification.

doesn’t work with ldap auth

This is the bit that I had not quite understood. So as it stands, there is no workable TURN solution for eJabberd with Freedombox?

I’m afraid that I don’t find the eJabberd documentation/configuration very easy to understand!

On another note, last night I tested Conversation.im (2.8.2+fcr - free from F-Droid repos) on two Android devices using my Freedombox/eJabberd XMPP server. Over the local network Conversation works great - nice snappy connections and great quality audio and video chat.

When I tried it with both Android devices going through a VPN then they rang but the connection was never established - what you’d expect I guess!

1 Like

Don’t give up hope! Guess you will like that:

https://blog.cubieserver.de/2020/ejabberd-announce-external-stun/turn-over-xmpp-xep-0215/

Points exactly to your question I would say. :grinning: