diff --git a/config.js b/config.js index 3237579a69dc0..da1c05d2b7365 100644 --- a/config.js +++ b/config.js @@ -329,6 +329,8 @@ var config = { // The STUN servers that will be used in the peer to peer connections stunServers: [ + + // { urls: 'stun:jitsi-meet.example.com:443' }, { urls: 'stun:stun.l.google.com:19302' }, { urls: 'stun:stun1.l.google.com:19302' }, { urls: 'stun:stun2.l.google.com:19302' } diff --git a/debian/control b/debian/control index 061a78d339f91..94cb4094f11db 100644 --- a/debian/control +++ b/debian/control @@ -21,7 +21,7 @@ Description: WebRTC JavaScript video conferences Package: jitsi-meet-web-config Architecture: all -Depends: openssl, nginx | nginx-extras | apache2 +Depends: openssl, nginx | nginx-full | nginx-extras | apache2 Description: Configuration for web serving of Jitsi Meet Jitsi Meet is a WebRTC JavaScript application that uses Jitsi Videobridge to provide high quality, scalable video conferences. @@ -54,3 +54,7 @@ Architecture: all Depends: ${misc:Depends}, prosody-trunk (>= 1nightly747) | prosody-0.11 | prosody (>= 0.11.2), libssl-dev, luarocks, jitsi-meet-prosody Description: Prosody token authentication plugin for Jitsi Meet +Package: jitsi-meet-turnserver +Architecture: all +Depends: ${misc:Depends}, nginx (>= 1.13.10) | nginx-full (>= 1.13.10) | nginx-extras (>= 1.13.10), jitsi-meet-prosody, coturn, dnsutils +Description: Configures coturn to be used with Jitsi Meet diff --git a/debian/jitsi-meet-prosody.postinst b/debian/jitsi-meet-prosody.postinst index bb7951f974135..83cf23f660ea4 100644 --- a/debian/jitsi-meet-prosody.postinst +++ b/debian/jitsi-meet-prosody.postinst @@ -80,6 +80,15 @@ case "$1" in # stores the hostname so we will reuse it later, like in purge db_set jitsi-meet-prosody/jvb-hostname "$JVB_HOSTNAME" + db_get jitsi-meet-prosody/turn-secret + if [ -z "$RET" ] ; then + # 8-chars random secret used for the turnserver + TURN_SECRET=`cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 8 | head -n 1` + db_set jitsi-meet-prosody/turn-secret "$TURN_SECRET" + else + TURN_SECRET="$RET" + fi + # and we're done with debconf db_stop @@ -97,6 +106,7 @@ case "$1" in sed -i "s/jitmeet.example.com/$JVB_HOSTNAME/g" $PROSODY_HOST_CONFIG sed -i "s/focusSecret/$JICOFO_SECRET/g" $PROSODY_HOST_CONFIG sed -i "s/focusUser/$JICOFO_AUTH_USER/g" $PROSODY_HOST_CONFIG + sed -i "s/__turnSecret__/$TURN_SECRET/g" $PROSODY_HOST_CONFIG if [ ! -f /etc/prosody/conf.d/$JVB_HOSTNAME.cfg.lua ]; then ln -s $PROSODY_HOST_CONFIG /etc/prosody/conf.d/$JVB_HOSTNAME.cfg.lua fi @@ -115,12 +125,14 @@ case "$1" in PROSODY_CONFIG_PRESENT="false" fi - # we always try to create the user 'jvb' and not fail configure if user exists - prosodyctl register jvb $JICOFO_AUTH_DOMAIN $JVB_SECRET || true + USER_EXISTS_CHECK=`prosodyctl adduser jvb@$JICOFO_AUTH_DOMAIN < /dev/null || true` + if [ ! "$USER_EXISTS_CHECK" = "That user already exists" ]; then + prosodyctl register jvb $JICOFO_AUTH_DOMAIN $JVB_SECRET || true + fi # Check whether prosody config has the internal muc, if not add it, # as we are migrating configs - if ! grep -q "internal.auth.$JVB_HOSTNAME" $PROSODY_HOST_CONFIG; then + if [ -f $PROSODY_HOST_CONFIG ] && ! grep -q "internal.auth.$JVB_HOSTNAME" $PROSODY_HOST_CONFIG; then echo -e "\nComponent \"internal.auth.$JVB_HOSTNAME\" \"muc\"" >> $PROSODY_HOST_CONFIG echo -e " storage = \"null\"" >> $PROSODY_HOST_CONFIG echo -e " modules_enabled = { \"ping\"; }" >> $PROSODY_HOST_CONFIG diff --git a/debian/jitsi-meet-prosody.templates b/debian/jitsi-meet-prosody.templates index 18d634b306f1f..fc51949d4c346 100644 --- a/debian/jitsi-meet-prosody.templates +++ b/debian/jitsi-meet-prosody.templates @@ -28,3 +28,8 @@ Template: jicofo/jicofosecret Type: password _Description: Jicofo Component secret: The secret used to connect to xmpp server as component + +Template: jitsi-meet-prosody/turn-secret +Type: string +_Description: The turn server secret + The secret used to connect to turnserver server. diff --git a/debian/jitsi-meet-turnserver.install b/debian/jitsi-meet-turnserver.install new file mode 100644 index 0000000000000..ba71b7a979db2 --- /dev/null +++ b/debian/jitsi-meet-turnserver.install @@ -0,0 +1,2 @@ +doc/debian/jitsi-meet-turn/turnserver.conf /usr/share/jitsi-meet-turnserver/ +doc/debian/jitsi-meet/jitsi-meet.conf /usr/share/jitsi-meet-turnserver/ diff --git a/debian/jitsi-meet-turnserver.links b/debian/jitsi-meet-turnserver.links new file mode 100644 index 0000000000000..9518f96ac1e72 --- /dev/null +++ b/debian/jitsi-meet-turnserver.links @@ -0,0 +1 @@ +/usr/share/jitsi-meet-turnserver/jitsi-meet.conf /etc/nginx/modules-enabled/60-jitsi-meet.conf diff --git a/debian/jitsi-meet-turnserver.postinst b/debian/jitsi-meet-turnserver.postinst new file mode 100644 index 0000000000000..305b1d66aa037 --- /dev/null +++ b/debian/jitsi-meet-turnserver.postinst @@ -0,0 +1,127 @@ +#!/bin/bash +# postinst script for jitsi-meet-turnserver +# +# see: dh_installdeb(1) + +set -e + +# summary of how this script can be called: +# * `configure' +# * `abort-upgrade' +# * `abort-remove' `in-favour' +# +# * `abort-remove' +# * `abort-deconfigure' `in-favour' +# `removing' +# +# for details, see http://www.debian.org/doc/debian-policy/ or +# the debian-policy package + +case "$1" in + configure) + # loading debconf + . /usr/share/debconf/confmodule + + # try to get host from jitsi-videobridge + db_get jitsi-videobridge/jvb-hostname + if [ -z "$RET" ] ; then + # server hostname + db_set jitsi-videobridge/jvb-hostname "localhost" + db_input critical jitsi-videobridge/jvb-hostname || true + db_go + fi + JVB_HOSTNAME="$RET" + + TURN_CONFIG="/etc/turnserver.conf" + NGINX_CONFIG="/etc/nginx/sites-available/$JVB_HOSTNAME.conf" + JITSI_MEET_CONFIG="/etc/jitsi/meet/$JVB_HOSTNAME-config.js" + + # detect dpkg-reconfigure, just delete old links + db_get jitsi-meet-turnserver/jvb-hostname + JVB_HOSTNAME_OLD=$RET + if [ -n "$RET" ] && [ ! "$JVB_HOSTNAME_OLD" = "$JVB_HOSTNAME" ] ; then + rm -f $TURN_CONFIG + fi + + # this detect only old installations with no nginx + db_get jitsi-meet/jvb-serve || true + if [ ! -f $NGINX_CONFIG -o "$RET" = "true" ] ; then + # nothing to do + echo "" + echo "turnserver not configured as no nginx found to multiplex traffic" + echo "" + db_stop + exit 0 + fi + + # stores the hostname so we will reuse it later, like in purge + db_set jitsi-meet-turnserver/jvb-hostname "$JVB_HOSTNAME" + + # try to get turnserver password + db_get jitsi-meet-prosody/turn-secret + if [ -z "$RET" ] ; then + db_input critical jitsi-meet-prosody/turn-secret || true + db_go + fi + TURN_SECRET="$RET" + + if [[ -f $TURN_CONFIG ]] && ! grep -q "jitsi-meet coturn config" "$TURN_CONFIG" ; then + PUBLIC_IP=$(dig +short myip.opendns.com @resolver1.opendns.com) + cp /usr/share/jitsi-meet-turnserver/turnserver.conf $TURN_CONFIG + sed -i "s/jitsi-meet.example.com/$JVB_HOSTNAME/g" $TURN_CONFIG + sed -i "s/__turnSecret__/$TURN_SECRET/g" $TURN_CONFIG + sed -i "s/__external_ip_address__/$JVB_HOSTNAME/g" $TURN_CONFIG + + # SSL for nginx + db_get jitsi-meet/cert-choice + CERT_CHOICE="$RET" + + if [ "$CERT_CHOICE" = "I want to use my own certificate" ] ; then + db_get jitsi-meet/cert-path-key + CERT_KEY="$RET" + db_get jitsi-meet/cert-path-crt + CERT_CRT="$RET" + + # replace self-signed certificate paths with user provided ones + CERT_KEY_ESC=$(echo $CERT_KEY | sed 's/\./\\\./g') + CERT_KEY_ESC=$(echo $CERT_KEY_ESC | sed 's/\//\\\//g') + sed -i "s/pkey=\/etc\/jitsi\/meet\/.*key/pkey=$CERT_KEY_ESC/g" $TURN_CONFIG + CERT_CRT_ESC=$(echo $CERT_CRT | sed 's/\./\\\./g') + CERT_CRT_ESC=$(echo $CERT_CRT_ESC | sed 's/\//\\\//g') + sed -i "s/cert=\/etc\/jitsi\/meet\/.*crt/cert=$CERT_CRT_ESC/g" $TURN_CONFIG + fi + + sed -i "s/#TURNSERVER_ENABLED/TURNSERVER_ENABLED/g" /etc/default/coturn + invoke-rc.d coturn restart || true + + NGINX_STREAM_CONFIG="/etc/nginx/modules-enabled/60-jitsi-meet.conf" + if [ -f $NGINX_STREAM_CONFIG ] && [ -f $NGINX_CONFIG ] ; then + sed -i "s/listen 443 ssl/listen 4444 ssl http2/g" $NGINX_CONFIG + invoke-rc.d nginx reload || true + fi + + # Enable turn server in config.js + if [ -f $JITSI_MEET_CONFIG ] ; then + sed -i "s/\/\/ useStunTurn: true/useStunTurn: true/g" $JITSI_MEET_CONFIG + fi + fi + + # and we're done with debconf + db_stop + ;; + + abort-upgrade|abort-remove|abort-deconfigure) + ;; + + *) + echo "postinst called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 diff --git a/debian/jitsi-meet-turnserver.templates b/debian/jitsi-meet-turnserver.templates new file mode 100644 index 0000000000000..9d95394423b83 --- /dev/null +++ b/debian/jitsi-meet-turnserver.templates @@ -0,0 +1,9 @@ +Template: jitsi-meet-turnserver/jvb-hostname +Type: string +_Description: The hostname of the current installation: + The value for the hostname that is set in Jitsi Videobridge installation. + +Template: jitsi-videobridge/jvb-hostname +Type: string +_Description: The hostname of the current installation: + The value for the hostname that is set in Jitsi Videobridge installation. diff --git a/debian/jitsi-meet-web-config.postinst b/debian/jitsi-meet-web-config.postinst index d756858f1c911..4ce11e50d17f0 100644 --- a/debian/jitsi-meet-web-config.postinst +++ b/debian/jitsi-meet-web-config.postinst @@ -53,9 +53,12 @@ case "$1" in db_set jitsi-meet/jvb-hostname $JVB_HOSTNAME NGINX_INSTALL_CHECK="$(dpkg-query -f '${Status}' -W 'nginx' 2>/dev/null | awk '{print $3}' || true)" + NGINX_FULL_INSTALL_CHECK="$(dpkg-query -f '${Status}' -W 'nginx-full' 2>/dev/null | awk '{print $3}' || true)" NGINX_EXTRAS_INSTALL_CHECK="$(dpkg-query -f '${Status}' -W 'nginx-extras' 2>/dev/null | awk '{print $3}' || true)" if [ "$NGINX_INSTALL_CHECK" = "installed" ] \ || [ "$NGINX_INSTALL_CHECK" = "unpacked" ] \ + || [ "$NGINX_FULL_INSTALL_CHECK" = "installed" ] \ + || [ "$NGINX_FULL_INSTALL_CHECK" = "unpacked" ] \ || [ "$NGINX_EXTRAS_INSTALL_CHECK" = "installed" ] \ || [ "$NGINX_EXTRAS_INSTALL_CHECK" = "unpacked" ] ; then FORCE_NGINX="true" @@ -105,8 +108,6 @@ case "$1" in sed -i "s/jitsi-meet.example.com/$JVB_HOSTNAME/g" $JITSI_MEET_CONFIG fi - JVB_CONFIG="/etc/jitsi/videobridge/sip-communicator.properties" - # this is new install let's configure jvb to serve meet # no-nginx, no-apache installed on machine, this is new install or reconfiguring old one which have jvb_serve set if [[ "$JVB_SERVE" = "true" ]] ; then @@ -121,11 +122,6 @@ case "$1" in echo "------------------------------------------------" echo "" elif [[ "$FORCE_NGINX" = "true" && ( -z "$JVB_HOSTNAME_OLD" || "$RECONFIGURING" = "true" ) ]] ; then - # disables tcp harvester to make sure jvb will not take port 443 - if [[ -f $JVB_CONFIG ]] && ! grep -q "org.jitsi.videobridge.DISABLE_TCP_HARVESTER" "$JVB_CONFIG" ;then - echo "org.jitsi.videobridge.DISABLE_TCP_HARVESTER=true" >> $JVB_CONFIG - invoke-rc.d jvb restart || true - fi # this is a reconfigure, lets just delete old links if [ "$RECONFIGURING" = "true" ] ; then @@ -156,11 +152,6 @@ case "$1" in invoke-rc.d nginx reload || true elif [[ "$FORCE_APACHE" = "true" && ( -z "$JVB_HOSTNAME_OLD" || "$RECONFIGURING" = "true" ) ]] ; then - # disables tcp harvester to make sure jvb will not take port 443 - if [[ -f $JVB_CONFIG ]] && ! grep -q "org.jitsi.videobridge.DISABLE_TCP_HARVESTER" "$JVB_CONFIG" ;then - echo "org.jitsi.videobridge.DISABLE_TCP_HARVESTER=true" >> $JVB_CONFIG - invoke-rc.d jvb restart || true - fi # this is a reconfigure, lets just delete old links if [ "$RECONFIGURING" = "true" ] ; then diff --git a/doc/debian/jitsi-meet-prosody/prosody.cfg.lua-jvb.example b/doc/debian/jitsi-meet-prosody/prosody.cfg.lua-jvb.example index e4af1c5bf6ae4..b7f0db42e0acc 100644 --- a/doc/debian/jitsi-meet-prosody/prosody.cfg.lua-jvb.example +++ b/doc/debian/jitsi-meet-prosody/prosody.cfg.lua-jvb.example @@ -3,6 +3,17 @@ plugin_paths = { "/usr/share/jitsi-meet/prosody-plugins/" } -- domain mapper options, must at least have domain base set to use the mapper muc_mapper_domain_base = "jitmeet.example.com"; +turncredentials_secret = "__turnSecret__"; + +turncredentials = { + { type = "stun", host = "jitmeet.example.com", port = "443" }, + { type = "turn", host = "jitmeet.example.com", port = "443", transport = "udp" }, + { type = "turns", host = "jitmeet.example.com", port = "443", transport = "tcp" } +}; + +cross_domain_bosh = false; +consider_bosh_secure = true; + VirtualHost "jitmeet.example.com" -- enabled = false -- Remove this line to enable this host authentication = "anonymous" @@ -25,6 +36,7 @@ VirtualHost "jitmeet.example.com" "pubsub"; "ping"; -- Enable mod_ping "speakerstats"; + "turncredentials"; } c2s_require_encryption = false diff --git a/doc/debian/jitsi-meet-turn/RREADME b/doc/debian/jitsi-meet-turn/RREADME new file mode 100644 index 0000000000000..c64c0f43cd5eb --- /dev/null +++ b/doc/debian/jitsi-meet-turn/RREADME @@ -0,0 +1 @@ +Coturn configuration for Jitsi Meet diff --git a/doc/debian/jitsi-meet-turn/turnserver.conf b/doc/debian/jitsi-meet-turn/turnserver.conf new file mode 100644 index 0000000000000..c25dfd6f2e0a6 --- /dev/null +++ b/doc/debian/jitsi-meet-turn/turnserver.conf @@ -0,0 +1,13 @@ +# jitsi-meet coturn config. Do not modify this line +lt-cred-mech +use-auth-secret +keep-address-family +static-auth-secret=__turnSecret__ +realm=jitsi-meet.example.com +cert=/etc/jitsi/meet/jitsi-meet.example.com.crt +pkey=/etc/jitsi/meet/jitsi-meet.example.com.key + +no-tcp +listening-port=443 +tls-listening-port=4445 +external-ip=__external_ip_address__ diff --git a/doc/debian/jitsi-meet/jitsi-meet.conf b/doc/debian/jitsi-meet/jitsi-meet.conf new file mode 100644 index 0000000000000..5d2a589f2405a --- /dev/null +++ b/doc/debian/jitsi-meet/jitsi-meet.conf @@ -0,0 +1,30 @@ +# this is jitsi-meet nginx module configuration +# this forward all http traffic to the nginx virtual host port +# and the rest to the turn server + +stream { + upstream web { + server 127.0.0.1:4444; + } + upstream turn { + server 127.0.0.1:4445; + } + # since 1.13.10 + map $ssl_preread_alpn_protocols $upstream { + "h2" web; + "http/1.1" web; + "h2,http/1.1" web; + default turn; + } + + server { + listen 443; + + # since 1.11.5 + ssl_preread on; + proxy_pass $upstream; + + # Increase buffer to serve video + proxy_buffer_size 10m; + } +} diff --git a/doc/debian/jitsi-meet/jitsi-meet.example b/doc/debian/jitsi-meet/jitsi-meet.example index bd743d5882eb4..2900834079f58 100644 --- a/doc/debian/jitsi-meet/jitsi-meet.example +++ b/doc/debian/jitsi-meet/jitsi-meet.example @@ -3,7 +3,17 @@ server_names_hash_bucket_size 64; server { listen 80; server_name jitsi-meet.example.com; - return 301 https://$host$request_uri; + + location ^~ /.well-known/acme-challenge/ { + default_type "text/plain"; + root /usr/share/jitsi-meet; + } + location = /.well-known/acme-challenge/ { + return 404; + } + location / { + return 301 https://$host$request_uri; + } } server { listen 443 ssl; diff --git a/doc/quick-install.md b/doc/quick-install.md index 936c8e11c5455..ebd74e836c606 100644 --- a/doc/quick-install.md +++ b/doc/quick-install.md @@ -6,7 +6,7 @@ Debian Wheezy and other older systems may require additional things to be done. Also note that a recent default Ubuntu installation has only the `main` repository enabled, and Jitsi Meet needs packages from `universe`. Check your `/etc/apt/sources.list` file, and if `universe` is not present refer to [Ubuntu's documentation](https://help.ubuntu.com/community/Repositories/Ubuntu) on how to enable it. (Usually it amounts to copying the `main` lines and changing to `universe`.) -N.B.: +N.B.: a.) All commands are supposed to be run by root. If you are logged in as a regular user with sudo rights, please prepend ___sudo___ to each of the commands. @@ -46,7 +46,7 @@ During the installation, you will be asked to enter the hostname of the Jitsi Me This hostname (or IP address) will be used for virtualhost configuration inside the Jitsi Meet and also, you and your correspondents will be using it to access the web conferences. -### Generate a Let's Encrypt certificate +### Generate a Let's Encrypt certificate Simply run the following in your shell @@ -109,7 +109,7 @@ Enjoy! ## Uninstall ```sh -apt-get purge jigasi jitsi-meet jitsi-meet-web-config jitsi-meet-prosody jitsi-meet-web jicofo jitsi-videobridge +apt-get purge jigasi jitsi-meet jitsi-meet-web-config jitsi-meet-prosody jitsi-meet-turnserver jitsi-meet-web jicofo jitsi-videobridge ``` Sometimes the following packages will fail to uninstall properly: diff --git a/resources/install-letsencrypt-cert.sh b/resources/install-letsencrypt-cert.sh index 267eceff4f571..1163cf402279d 100755 --- a/resources/install-letsencrypt-cert.sh +++ b/resources/install-letsencrypt-cert.sh @@ -57,6 +57,15 @@ if [ -f /etc/nginx/sites-enabled/$DOMAIN.conf ] ; then echo "service nginx reload" >> $CRON_FILE service nginx reload + TURN_CONFIG="/etc/turnserver.conf" + if [ -f $TURN_CONFIG ] && grep -q "jitsi-meet coturn config" "$TURN_CONFIG" ; then + echo "Configuring turnserver" + sed -i "s/cert=\/etc\/jitsi\/meet\/.*crt/cert=$CERT_CRT_ESC/g" $TURN_CONFIG + sed -i "s/pkey=\/etc\/jitsi\/meet\/.*key/pkey=$CERT_KEY_ESC/g" $TURN_CONFIG + + echo "service coturn restart" >> $CRON_FILE + service coturn restart + fi elif [ -f /etc/apache2/sites-enabled/$DOMAIN.conf ] ; then ./certbot-auto certonly --noninteractive \ diff --git a/resources/prosody-plugins/mod_turncredentials.lua b/resources/prosody-plugins/mod_turncredentials.lua new file mode 100644 index 0000000000000..9648385b79b69 --- /dev/null +++ b/resources/prosody-plugins/mod_turncredentials.lua @@ -0,0 +1,80 @@ +-- XEP-0215 implementation for time-limited turn credentials +-- Copyright (C) 2012-2014 Philipp Hancke +-- This file is MIT/X11 licensed. + +--turncredentials_secret = "keepthissecret"; +--turncredentials = { +-- { type = "stun", host = "8.8.8.8" }, +-- { type = "turn", host = "8.8.8.8", port = "3478" }, +-- { type = "turn", host = "8.8.8.8", port = "80", transport = "tcp" } +--} +-- for stun servers, host is required, port defaults to 3478 +-- for turn servers, host is required, port defaults to tcp, +-- transport defaults to udp +-- hosts can be a list of server names / ips for random +-- choice loadbalancing + +local st = require "util.stanza"; +local hmac_sha1 = require "util.hashes".hmac_sha1; +local base64 = require "util.encodings".base64; +local os_time = os.time; +local secret = module:get_option_string("turncredentials_secret"); +local ttl = module:get_option_number("turncredentials_ttl", 86400); +local hosts = module:get_option("turncredentials") or {}; +if not (secret) then + module:log("error", "turncredentials not configured"); + return; +end + +module:add_feature("urn:xmpp:extdisco:1"); + +function random(arr) + local index = math.random(1, #arr); + return arr[index]; +end + + +module:hook_global("config-reloaded", function() + module:log("debug", "config-reloaded") + secret = module:get_option_string("turncredentials_secret"); + ttl = module:get_option_number("turncredentials_ttl", 86400); + hosts = module:get_option("turncredentials") or {}; +end); + +module:hook("iq-get/host/urn:xmpp:extdisco:1:services", function(event) + local origin, stanza = event.origin, event.stanza; + if origin.type ~= "c2s" then + return; + end + local now = os_time() + ttl; + local userpart = tostring(now); + local nonce = base64.encode(hmac_sha1(secret, tostring(userpart), false)); + local reply = st.reply(stanza):tag("services", {xmlns = "urn:xmpp:extdisco:1"}) + for idx, item in pairs(hosts) do + if item.type == "stun" or item.type == "stuns" then + -- stun items need host and port (defaults to 3478) + reply:tag("service", + { type = item.type, host = item.host, port = tostring(item.port) or "3478" } + ):up(); + elseif item.type == "turn" or item.type == "turns" then + local turn = {} + -- turn items need host, port (defaults to 3478), + -- transport (defaults to udp) + -- username, password, ttl + turn.type = item.type; + turn.port = tostring(item.port); + turn.transport = item.transport; + turn.username = userpart; + turn.password = nonce; + turn.ttl = tostring(ttl); + if item.hosts then + turn.host = random(item.hosts) + else + turn.host = item.host + end + reply:tag("service", turn):up(); + end + end + origin.send(reply); + return true; +end);