2023-10-17 01:16:08 +00:00
|
|
|
# TURN/STUN NAT traversal service
|
|
|
|
# commonly used to establish realtime calls with prosody, or possibly matrix/synapse
|
2023-10-17 01:16:59 +00:00
|
|
|
#
|
2023-10-20 03:14:36 +00:00
|
|
|
# - <https://github.com/coturn/coturn/>
|
|
|
|
# - `man turnserver`
|
|
|
|
# - config docs: <https://github.com/coturn/coturn/blob/master/examples/etc/turnserver.conf>
|
|
|
|
#
|
2023-10-17 09:43:55 +00:00
|
|
|
# N.B. during operation it's NORMAL to see "error 401".
|
|
|
|
# during session creation:
|
|
|
|
# - client sends Allocate request
|
|
|
|
# - server replies error 401, providing a realm and nonce
|
|
|
|
# - client uses realm + nonce + shared secret to construct an auth key & call Allocate again
|
|
|
|
# - server replies Allocate Success Response
|
|
|
|
# - source: <https://stackoverflow.com/a/66643135>
|
2023-10-20 23:22:34 +00:00
|
|
|
#
|
|
|
|
# N.B. this safest implementation routes all traffic THROUGH A VPN
|
|
|
|
# - that adds a lot of latency, but in practice turns out to be inconsequential.
|
|
|
|
# i guess ICE allows clients to prefer the other party's lower-latency server, in practice?
|
|
|
|
# - still, this is the "safe" implementation because STUN works with IP addresses instead of domain names:
|
|
|
|
# 1. client A queries the STUN server to determine its own IP address/port.
|
|
|
|
# 2. client A tells client B which IP address/port client A is visible on.
|
|
|
|
# 3. client B contacts that IP address/port
|
|
|
|
# this only works so long as the IP address/port which STUN server sees client A on is publicly routable.
|
|
|
|
# that is NOT the case when the STUN server and client A are on the same LAN
|
|
|
|
# even if client A contacts the STUN server via its WAN address with port reflection enabled.
|
|
|
|
# hence, there's no obvious way to put the STUN server on the same LAN as either client and expect the rest to work.
|
2023-10-17 01:16:08 +00:00
|
|
|
{ lib, ... }:
|
|
|
|
let
|
|
|
|
# TODO: this range could be larger, but right now that's costly because each element is its own UPnP forward
|
|
|
|
# TURN port range (inclusive)
|
|
|
|
turnPortLow = 49152;
|
|
|
|
turnPortHigh = 49167;
|
|
|
|
turnPortRange = lib.range turnPortLow turnPortHigh;
|
|
|
|
in
|
|
|
|
{
|
|
|
|
sane.ports.ports = lib.mkMerge ([
|
|
|
|
{
|
|
|
|
"3478" = {
|
|
|
|
# this is the "control" port.
|
|
|
|
# i.e. no client data is forwarded through it, but it's where clients request tunnels.
|
|
|
|
protocol = [ "tcp" "udp" ];
|
2023-10-20 03:14:36 +00:00
|
|
|
# visibleTo.lan = true;
|
|
|
|
# visibleTo.wan = true;
|
|
|
|
visibleTo.ovpn = true;
|
2023-10-17 01:16:08 +00:00
|
|
|
description = "colin-stun-turn";
|
|
|
|
};
|
|
|
|
"5349" = {
|
|
|
|
# the other port 3478 also supports TLS/DTLS, but presumably clients wanting TLS will default 5349
|
|
|
|
protocol = [ "tcp" ];
|
2023-10-20 03:14:36 +00:00
|
|
|
# visibleTo.lan = true;
|
|
|
|
# visibleTo.wan = true;
|
|
|
|
visibleTo.ovpn = true;
|
2023-10-17 01:16:08 +00:00
|
|
|
description = "colin-stun-turn-over-tls";
|
|
|
|
};
|
|
|
|
}
|
|
|
|
] ++ (builtins.map
|
|
|
|
(port: {
|
|
|
|
"${builtins.toString port}" = let
|
|
|
|
count = port - turnPortLow + 1;
|
|
|
|
numPorts = turnPortHigh - turnPortLow + 1;
|
|
|
|
in {
|
|
|
|
protocol = [ "tcp" "udp" ];
|
2023-10-20 03:14:36 +00:00
|
|
|
# visibleTo.lan = true;
|
|
|
|
# visibleTo.wan = true;
|
2023-10-17 09:43:55 +00:00
|
|
|
visibleTo.ovpn = true;
|
2023-10-17 01:16:08 +00:00
|
|
|
description = "colin-turn-${builtins.toString count}-of-${builtins.toString numPorts}";
|
|
|
|
};
|
|
|
|
})
|
|
|
|
turnPortRange
|
|
|
|
));
|
|
|
|
|
|
|
|
services.nginx.virtualHosts."turn.uninsane.org" = {
|
|
|
|
# allow ACME to procure a cert via nginx for this domain
|
|
|
|
enableACME = true;
|
|
|
|
};
|
2023-10-17 01:16:59 +00:00
|
|
|
sane.dns.zones."uninsane.org".inet = {
|
2023-10-17 09:43:55 +00:00
|
|
|
# CNAME."turn" = "servo.wan";
|
|
|
|
# CNAME."turn" = "ovpns";
|
|
|
|
# CNAME."turn" = "native";
|
|
|
|
# XXX: SRV records have to point to something with a A/AAAA record; no CNAMEs
|
|
|
|
A."turn" = "%AOVPNS%";
|
2023-10-20 03:14:36 +00:00
|
|
|
# A."turn" = "%AWAN%";
|
2023-10-17 09:43:55 +00:00
|
|
|
|
|
|
|
SRV."_stun._udp" = "5 50 3478 turn";
|
|
|
|
SRV."_stun._tcp" = "5 50 3478 turn";
|
|
|
|
SRV."_stuns._tcp" = "5 50 5349 turn";
|
|
|
|
SRV."_turn._udp" = "5 50 3478 turn";
|
|
|
|
SRV."_turn._tcp" = "5 50 3478 turn";
|
|
|
|
SRV."_turns._tcp" = "5 50 5349 turn";
|
2023-10-17 01:16:59 +00:00
|
|
|
};
|
2023-10-17 01:16:08 +00:00
|
|
|
|
|
|
|
sane.derived-secrets."/var/lib/coturn/shared_secret.bin" = {
|
|
|
|
encoding = "base64";
|
|
|
|
# TODO: make this not globally readable
|
|
|
|
acl.mode = "0644";
|
|
|
|
};
|
2023-10-17 09:43:55 +00:00
|
|
|
sane.fs."/var/lib/coturn/shared_secret.bin".wantedBeforeBy = [ "coturn.service" ];
|
2023-10-17 01:16:08 +00:00
|
|
|
|
2023-10-17 01:16:59 +00:00
|
|
|
# provide access to certs
|
|
|
|
users.users.turnserver.extraGroups = [ "nginx" ];
|
|
|
|
|
2023-10-17 01:16:08 +00:00
|
|
|
services.coturn.enable = true;
|
|
|
|
services.coturn.realm = "turn.uninsane.org";
|
|
|
|
services.coturn.cert = "/var/lib/acme/turn.uninsane.org/fullchain.pem";
|
|
|
|
services.coturn.pkey = "/var/lib/acme/turn.uninsane.org/key.pem";
|
2023-10-17 01:16:59 +00:00
|
|
|
services.coturn.use-auth-secret = true;
|
2023-10-17 01:16:08 +00:00
|
|
|
services.coturn.static-auth-secret-file = "/var/lib/coturn/shared_secret.bin";
|
2023-10-17 09:43:55 +00:00
|
|
|
services.coturn.lt-cred-mech = true;
|
2023-10-17 01:16:08 +00:00
|
|
|
services.coturn.min-port = turnPortLow;
|
|
|
|
services.coturn.max-port = turnPortHigh;
|
2023-10-17 09:43:55 +00:00
|
|
|
# services.coturn.secure-stun = true;
|
2023-10-20 03:14:36 +00:00
|
|
|
services.coturn.extraConfig = lib.concatStringsSep "\n" [
|
|
|
|
"verbose"
|
|
|
|
# "Verbose" #< even MORE verbosity than "verbose"
|
|
|
|
# "no-multicast-peers" # disables sending to IPv4 broadcast addresses (e.g. 224.0.0.0/3)
|
|
|
|
"listening-ip=10.0.1.5"
|
|
|
|
# "external-ip=185.157.162.178/10.0.1.5"
|
|
|
|
"external-ip=185.157.162.178"
|
|
|
|
# "listening-ip=10.78.79.51" # can be specified multiple times; omit for *
|
|
|
|
# "external-ip=97.113.128.229/10.78.79.51"
|
|
|
|
# "external-ip=97.113.128.229"
|
|
|
|
# "mobility" # "mobility with ICE (MICE) specs support" (?)
|
|
|
|
];
|
2023-10-17 01:16:08 +00:00
|
|
|
}
|