mirror of https://github.com/jitsi/jitsi-meet
feat: Adds docs, config and scripts around the visitor mode. (#12658)
* feat: Moves handle of vnode from conferenceIQ stanza error to result. * feat: Handles redirected to visitor node event. * feat: Adds README and configurations. * squash: Drop comment. * copy edits * image fix * fix background for dark mode * fix the background * feat: Update s2soutinjection. * Update README commands formatting. * Update doc/extra-large-conference/README.md Co-authored-by: Saúl Ibarra Corretgé <saghul@jitsi.org> * squash: Creates a generateVisitorConfig helper. * squash: Moves the folder from doc. * squash: Update example. * squash: Drop config. * squash: Rename var to look like template. * squash: Fix plugins path. * squash: Fix sort order of import. * squash: Fix lint errors. Co-authored-by: scott boone <scott.e.boone@gmail.com> Co-authored-by: Scott Boone <scott.boone@8x8.com> Co-authored-by: Saúl Ibarra Corretgé <saghul@jitsi.org>pull/12680/head jitsi-meet_8153
parent
3445c513ba
commit
9fbbe05d6c
@ -0,0 +1,88 @@ |
||||
WARNING: This is still a Work In Progress |
||||
|
||||
The final implementation may diverge. Currently, only the participants after a |
||||
configured threshold will be just viewers (visitors) and there is no promotion |
||||
mechanism to become a main participant yet. |
||||
|
||||
TODO: |
||||
* Merge messaging between visitor nodes and main conference |
||||
* Polls |
||||
* Raise hand to be promoted to enter the main conference |
||||
* Make sure it works with tenants. |
||||
|
||||
|
||||
# Low-latency conference streaming to very large audiences |
||||
|
||||
To have a low-latency conference with a very large audience, the media and |
||||
signaling load must be spread beyond what can be handled by a typical Jitsi |
||||
installation. A call with 10k participants requires around 50 bridges on decent |
||||
vms (8+ cores). The main participants of a conference with a very large |
||||
audience will share a main prosody, like with normal conferences, and |
||||
additional prosody vms are needed to support signaling to the audience. |
||||
|
||||
In the example configuration we use a 16 core machine. Eight of the cores are |
||||
used for the main prosody and other services (nginx, jicofo, etc) and the other |
||||
eight cores are used to run prosody services for visitors, i.e., "visitor |
||||
prosodies". |
||||
|
||||
We consider 2000 participants per visitor node a safe value. So eight visitor |
||||
prosodies will be enough for one 10k participants meeting. |
||||
|
||||
<img src="imgs/visitors-prosody.svg" alt="diagram of a central prosody connected to several visitor prosodies" width="500"/> |
||||
|
||||
# Configuration |
||||
Use the `pre-configure.sh` script to configure your system, passing it the |
||||
number of visitor prosodies to set up. |
||||
`./pre-configure.sh 8` |
||||
|
||||
The script will add for each visitor prosody: |
||||
- folders in `/etc/` |
||||
- a systemd unit file in `/lib/systemd/system/` |
||||
- a user for jicofo |
||||
- a config entry in jicofo.conf |
||||
|
||||
Setting up configuration for the main prosody is a manual process: |
||||
- Add to the enabled modules list in the general part (e.g. [here](https://github.com/bjc/prosody/blob/76bf6d511f851c7cde8a81257afaaae0fb7a4160/prosody.cfg.lua.dist#L33)): |
||||
``` |
||||
"s2s_bidi"; |
||||
"certs_s2soutinjection"; |
||||
"s2soutinjection"; |
||||
"s2s_whitelist"; |
||||
``` |
||||
|
||||
- Add the following config also in the general part (matching the number of prosodies you generated config for): |
||||
``` |
||||
-- targets must be IPs, not hostnames |
||||
s2s_connect_overrides = { |
||||
["conference.v1.meet.jitsi"] = { "127.0.0.1", 52691 }; |
||||
["conference.v2.meet.jitsi"] = { "127.0.0.1", 52692 }; |
||||
["conference.v3.meet.jitsi"] = { "127.0.0.1", 52693 }; |
||||
["conference.v4.meet.jitsi"] = { "127.0.0.1", 52694 }; |
||||
["conference.v5.meet.jitsi"] = { "127.0.0.1", 52695 }; |
||||
["conference.v6.meet.jitsi"] = { "127.0.0.1", 52696 }; |
||||
["conference.v7.meet.jitsi"] = { "127.0.0.1", 52697 }; |
||||
["conference.v8.meet.jitsi"] = { "127.0.0.1", 52698 }; |
||||
} |
||||
-- allowed list of server-2-server connections |
||||
s2s_whitelist = { |
||||
"conference.v1.meet.jitsi", "conference.v2.meet.jitsi", "conference.v3.meet.jitsi", "conference.v4.meet.jitsi", |
||||
"conference.v5.meet.jitsi", "conference.v6.meet.jitsi", "conference.v7.meet.jitsi", "conference.v8.meet.jitsi" |
||||
}; |
||||
``` |
||||
|
||||
- Make sure s2s is not in modules_disabled |
||||
- Enable `"xxl_conference";` module under the main virtual host (e.g. [here](https://github.com/jitsi/jitsi-meet/blob/f42772ec5bcc87ff6de17423d36df9bcad6e770d/doc/debian/jitsi-meet-prosody/prosody.cfg.lua-jvb.example#L57)) |
||||
|
||||
After configuring you can set the maximum number of main participants, before |
||||
redirecting to visitors. |
||||
``` |
||||
hocon -f /etc/jitsi/jicofo/jicofo.conf set "jicofo.visitors.max-participants" 30 |
||||
``` |
||||
Now restart prosody and jicofo |
||||
``` |
||||
service prosody restart |
||||
service jicofo restart |
||||
``` |
||||
|
||||
Now after the main 30 participants join, the rest will be visitors using the |
||||
visitor nodes. |
After Width: | Height: | Size: 198 KiB |
@ -0,0 +1,51 @@ |
||||
#!/bin/bash |
||||
|
||||
SCRIPT_DIR=`dirname "$0"` |
||||
cd $SCRIPT_DIR |
||||
|
||||
NUMBER_OF_INSTANCES=$1 |
||||
|
||||
if ! [[ $NUMBER_OF_INSTANCES =~ ^[0-9]+([.][0-9]+)?$ ]] ; then |
||||
echo "error: Not a number param" >&2; |
||||
exit 1 |
||||
fi |
||||
|
||||
echo "Will configure $NUMBER_OF_INSTANCES number of visitor prosodies" |
||||
set -e |
||||
set -x |
||||
|
||||
# Configure prosody instances |
||||
for (( i=1 ; i<=${NUMBER_OF_INSTANCES} ; i++ )); |
||||
do |
||||
cp prosody-v.service.template /lib/systemd/system/prosody-v${i}.service |
||||
sed -i "s/vX/v${i}/g" /lib/systemd/system/prosody-v${i}.service |
||||
mkdir /etc/prosody-v${i} |
||||
ln -s /etc/prosody/certs /etc/prosody-v${i}/certs |
||||
cp prosody.cfg.lua.visitor.template /etc/prosody-v${i}/prosody.cfg.lua |
||||
sed -i "s/vX/v${i}/g" /etc/prosody-v${i}/prosody.cfg.lua |
||||
done |
||||
|
||||
# Configure jicofo |
||||
HOCON_CONFIG="/etc/jitsi/jicofo/jicofo.conf" |
||||
hocon -f $HOCON_CONFIG set "jicofo.bridge.selection-strategy" "VisitorSelectionStrategy" |
||||
hocon -f $HOCON_CONFIG set "jicofo.bridge.visitor-selection-strategy" "RegionBasedBridgeSelectionStrategy" |
||||
hocon -f $HOCON_CONFIG set "jicofo.bridge.topology-strategy" "VisitorTopologyStrategy" |
||||
|
||||
PASS=$(hocon -f $HOCON_CONFIG get "jicofo.xmpp.client.password") |
||||
for (( i=1 ; i<=${NUMBER_OF_INSTANCES} ; i++ )); |
||||
do |
||||
prosodyctl --config /etc/prosody-v${i}/prosody.cfg.lua register focus auth.meet.jitsi $PASS |
||||
hocon -f $HOCON_CONFIG set "jicofo.xmpp.visitors.v${i}.enabled" true |
||||
hocon -f $HOCON_CONFIG set "jicofo.xmpp.visitors.v${i}.conference-service" "conference.v${i}.meet.jitsi" |
||||
hocon -f $HOCON_CONFIG set "jicofo.xmpp.visitors.v${i}.hostname" 127.0.0.1 |
||||
hocon -f $HOCON_CONFIG set "jicofo.xmpp.visitors.v${i}.port" 5222${i} |
||||
hocon -f $HOCON_CONFIG set "jicofo.xmpp.visitors.v${i}.domain" "auth.meet.jitsi" |
||||
hocon -f $HOCON_CONFIG set "jicofo.xmpp.visitors.v${i}.password" "${PASS}" |
||||
hocon -f $HOCON_CONFIG set "jicofo.xmpp.visitors.v${i}.disable-certificate-verification" true |
||||
done |
||||
|
||||
for (( i=1 ; i<=${NUMBER_OF_INSTANCES} ; i++ )); |
||||
do |
||||
service prosody-v${i} restart |
||||
done |
||||
service jicofo restart |
@ -0,0 +1,46 @@ |
||||
[Unit] |
||||
### see man systemd.unit |
||||
Description=Prosody vX (visitor vX) JVB XMPP Server |
||||
Documentation=https://prosody.im/doc |
||||
|
||||
Requires=network-online.target |
||||
After=network-online.target network.target mariadb.service mysql.service postgresql.service |
||||
Before=biboumi.service |
||||
|
||||
[Service] |
||||
### see man systemd.service |
||||
Type=simple |
||||
|
||||
# Start by executing the main executable |
||||
# Note: -F option requires Prosody 0.11.5 or later |
||||
ExecStart=/usr/bin/prosody --config /etc/prosody-vX/prosody.cfg.lua -F |
||||
ExecReload=/bin/kill -HUP $MAINPID |
||||
Restart=on-abnormal |
||||
|
||||
### see man systemd.exec |
||||
User=prosody |
||||
Group=prosody |
||||
UMask=0027 |
||||
|
||||
RuntimeDirectory=prosody-vX |
||||
ConfigurationDirectory=prosody-vX |
||||
StateDirectory=prosody-vX |
||||
StateDirectoryMode=0750 |
||||
LogsDirectory=prosody-vX |
||||
WorkingDirectory=~ |
||||
|
||||
# Set stdin to /dev/null since Prosody does not need it |
||||
StandardInput=null |
||||
|
||||
# Direct stdout/-err to journald for use with log = "*stdout" |
||||
StandardOutput=journal |
||||
StandardError=inherit |
||||
|
||||
# Allow binding low ports |
||||
AmbientCapabilities=CAP_NET_BIND_SERVICE |
||||
|
||||
[Install] |
||||
### see man systemd.unit |
||||
WantedBy=multi-user.target |
||||
|
||||
# vim: filetype=systemd |
@ -0,0 +1,121 @@ |
||||
---------- Server-wide settings ---------- |
||||
s2s_ports = { 52691 }; |
||||
c2s_ports = { 52221 } |
||||
http_ports = { 52801 } |
||||
https_ports = { 52811 } |
||||
|
||||
daemonize = true; |
||||
|
||||
-- we use a common jid for jicofo |
||||
admins = { |
||||
'focus@auth.meet.jitsi' |
||||
} |
||||
|
||||
-- Enable use of native prosody 0.11 support for epoll over select |
||||
network_backend = 'epoll'; |
||||
network_settings = { |
||||
tcp_backlog = 511; |
||||
} |
||||
|
||||
modules_enabled = { |
||||
'saslauth'; |
||||
'tls'; |
||||
'disco'; |
||||
'posix'; |
||||
|
||||
'secure_interfaces'; |
||||
|
||||
-- jitsi |
||||
'websocket'; |
||||
'bosh'; |
||||
's2s_bidi'; |
||||
's2s_whitelist'; |
||||
}; |
||||
|
||||
s2s_whitelist = {}; |
||||
|
||||
external_service_secret = '__turnSecret__'; |
||||
|
||||
external_services = { |
||||
{ type = 'stun', host = 'jitmeet.example.com', port = 3478 }, |
||||
{ type = 'turn', host = 'jitmeet.example.com', port = 3478, transport = 'udp', secret = true, ttl = 86400, algorithm = 'turn' }, |
||||
{ type = 'turns', host = 'jitmeet.example.com', port = 5349, transport = 'tcp', secret = true, ttl = 86400, algorithm = 'turn' } |
||||
}; |
||||
|
||||
muc_mapper_domain_base = 'vX.meet.jitsi'; |
||||
|
||||
-- https://prosody.im/doc/modules/mod_smacks |
||||
smacks_max_unacked_stanzas = 5; |
||||
smacks_hibernation_time = 60; |
||||
-- this is dropped in 0.12 |
||||
smacks_max_hibernated_sessions = 1; |
||||
smacks_max_old_sessions = 1; |
||||
|
||||
unlimited_jids = { 'focus@auth.meet.jitsi' } |
||||
limits = { |
||||
c2s = { |
||||
rate = '512kb/s'; |
||||
}; |
||||
} |
||||
|
||||
modules_disabled = { |
||||
'offline'; |
||||
'pubsub'; |
||||
'register'; |
||||
}; |
||||
|
||||
allow_registration = false; |
||||
authentication = 'internal_hashed' |
||||
storage = 'internal' |
||||
log = { |
||||
-- Log files (change 'info' to 'debug' for debug logs): |
||||
info = '/var/log/prosody-vX/prosody.log'; |
||||
error = '/var/log/prosody-vX/prosody.err'; |
||||
} |
||||
|
||||
consider_websocket_secure = true; |
||||
consider_bosh_secure = true; |
||||
bosh_max_inactivity = 60; |
||||
|
||||
plugin_paths = { '/usr/share/jitsi-meet/prosody-plugins/' } |
||||
|
||||
----------- Virtual hosts ----------- |
||||
VirtualHost 'vX.meet.jitsi' |
||||
authentication = 'jitsi-anonymous' |
||||
ssl = { |
||||
key = '/etc/prosody/certs/jitmeet.example.com.key'; |
||||
certificate = '/etc/prosody/certs/jitmeet.example.com.crt'; |
||||
} |
||||
modules_enabled = { |
||||
'bosh'; |
||||
'ping'; |
||||
'external_services'; |
||||
'smacks'; |
||||
'jiconop'; |
||||
} |
||||
main_muc = 'conference.vX.meet.jitsi'; |
||||
|
||||
VirtualHost 'auth.meet.jitsi' |
||||
modules_enabled = { |
||||
'limits_exception'; |
||||
'ping'; |
||||
} |
||||
authentication = 'internal_hashed' |
||||
|
||||
Component 'conference.vX.meet.jitsi' 'muc' |
||||
storage = 'memory' |
||||
muc_room_cache_size = 10000 |
||||
restrict_room_creation = true |
||||
modules_enabled = { |
||||
'muc_hide_all'; |
||||
'muc_domain_mapper'; |
||||
'muc_meeting_id'; |
||||
'fmuc'; |
||||
} |
||||
muc_room_default_presence_broadcast = { |
||||
visitor = false; |
||||
participant = true; |
||||
moderator = true; |
||||
}; |
||||
muc_room_locking = false |
||||
muc_room_default_public_jids = true |
@ -1,16 +0,0 @@ |
||||
-- validates all certificates, global module |
||||
-- Warning: use this only for testing purposes as it will accept all kind of certificates for s2s connections |
||||
-- you can use https://modules.prosody.im/mod_s2s_whitelist.html for whitelisting only certain destinations |
||||
module:set_global(); |
||||
|
||||
function attach(event) |
||||
local session = event.session; |
||||
|
||||
session.cert_chain_status = 'valid'; |
||||
session.cert_identity_status = 'valid'; |
||||
|
||||
return true; |
||||
end |
||||
module:wrap_event('s2s-check-certificate', function (handlers, event_name, event_data) |
||||
return attach(event_data); |
||||
end); |
@ -0,0 +1,19 @@ |
||||
-- global module |
||||
-- validates certificates for all hosts used for s2soutinjection |
||||
module:set_global(); |
||||
|
||||
local s2s_overrides = module:get_option("s2s_connect_overrides"); |
||||
|
||||
function attach(event) |
||||
local session = event.session; |
||||
|
||||
if s2s_overrides and s2s_overrides[event.host] then |
||||
session.cert_chain_status = 'valid'; |
||||
session.cert_identity_status = 'valid'; |
||||
|
||||
return true; |
||||
end |
||||
end |
||||
module:wrap_event('s2s-check-certificate', function (handlers, event_name, event_data) |
||||
return attach(event_data); |
||||
end); |
@ -0,0 +1,20 @@ |
||||
-- Using version https://hg.prosody.im/prosody-modules/file/c1a8ce147885/mod_s2s_whitelist/mod_s2s_whitelist.lua |
||||
local st = require "util.stanza"; |
||||
|
||||
local whitelist = module:get_option_inherited_set("s2s_whitelist", {}); |
||||
|
||||
module:hook("route/remote", function (event) |
||||
if not whitelist:contains(event.to_host) then |
||||
module:send(st.error_reply(event.stanza, "cancel", "not-allowed", "Communication with this domain is restricted")); |
||||
return true; |
||||
end |
||||
end, 100); |
||||
|
||||
module:hook("s2s-stream-features", function (event) |
||||
if not whitelist:contains(event.origin.from_host) then |
||||
event.origin:close({ |
||||
condition = "policy-violation"; |
||||
text = "Communication with this domain is restricted"; |
||||
}); |
||||
end |
||||
end, 1000); |
@ -0,0 +1,90 @@ |
||||
-- Using version https://hg.prosody.im/prosody-modules/file/4fb922aa0ace/mod_s2soutinjection/mod_s2soutinjection.lua |
||||
local st = require"util.stanza"; |
||||
local new_outgoing = require"core.s2smanager".new_outgoing; |
||||
local bounce_sendq = module:depends"s2s".route_to_new_session.bounce_sendq; |
||||
local initialize_filters = require "util.filters".initialize; |
||||
|
||||
local portmanager = require "core.portmanager"; |
||||
|
||||
local addclient = require "net.server".addclient; |
||||
|
||||
module:depends("s2s"); |
||||
|
||||
local sessions = module:shared("sessions"); |
||||
|
||||
local injected = module:get_option("s2s_connect_overrides"); |
||||
|
||||
-- The proxy_listener handles connection while still connecting to the proxy, |
||||
-- then it hands them over to the normal listener (in mod_s2s) |
||||
local proxy_listener = { default_port = nil, default_mode = "*a", default_interface = "*" }; |
||||
|
||||
function proxy_listener.onconnect(conn) |
||||
local session = sessions[conn]; |
||||
|
||||
-- Now the real s2s listener can take over the connection. |
||||
local listener = portmanager.get_service("s2s").listener; |
||||
|
||||
local log = session.log; |
||||
|
||||
local filter = initialize_filters(session); |
||||
|
||||
session.version = 1; |
||||
|
||||
session.sends2s = function (t) |
||||
log("debug", "sending (s2s over proxy): %s", (t.top_tag and t:top_tag()) or t:match("^[^>]*>?")); |
||||
if t.name then |
||||
t = filter("stanzas/out", t); |
||||
end |
||||
if t then |
||||
t = filter("bytes/out", tostring(t)); |
||||
if t then |
||||
return conn:write(tostring(t)); |
||||
end |
||||
end |
||||
end |
||||
|
||||
session.open_stream = function () |
||||
session.sends2s(st.stanza("stream:stream", { |
||||
xmlns='jabber:server', ["xmlns:db"]='jabber:server:dialback', |
||||
["xmlns:stream"]='http://etherx.jabber.org/streams', |
||||
from=session.from_host, to=session.to_host, version='1.0', ["xml:lang"]='en'}):top_tag()); |
||||
end |
||||
|
||||
conn.setlistener(conn, listener); |
||||
|
||||
listener.register_outgoing(conn, session); |
||||
|
||||
listener.onconnect(conn); |
||||
end |
||||
|
||||
function proxy_listener.register_outgoing(conn, session) |
||||
session.direction = "outgoing"; |
||||
sessions[conn] = session; |
||||
end |
||||
|
||||
function proxy_listener.ondisconnect(conn, err) |
||||
sessions[conn] = nil; |
||||
end |
||||
|
||||
module:hook("route/remote", function(event) |
||||
local from_host, to_host, stanza = event.from_host, event.to_host, event.stanza; |
||||
local inject = injected and injected[to_host]; |
||||
if not inject then return end |
||||
module:log("debug", "opening a new outgoing connection for this stanza"); |
||||
local host_session = new_outgoing(from_host, to_host); |
||||
|
||||
-- Store in buffer |
||||
host_session.bounce_sendq = bounce_sendq; |
||||
host_session.sendq = { {tostring(stanza), stanza.attr.type ~= "error" and stanza.attr.type ~= "result" and st.reply(stanza)} }; |
||||
host_session.log("debug", "stanza [%s] queued until connection complete", tostring(stanza.name)); |
||||
|
||||
local host, port = inject[1] or inject, tonumber(inject[2]) or 5269; |
||||
|
||||
local conn = addclient(host, port, proxy_listener, "*a"); |
||||
|
||||
proxy_listener.register_outgoing(conn, host_session); |
||||
|
||||
host_session.conn = conn; |
||||
return true; |
||||
end, -2); |
||||
|
@ -0,0 +1,20 @@ |
||||
-- Using version https://hg.prosody.im/prosody-modules/file/6c806a99f802/mod_secure_interfaces/mod_secure_interfaces.lua |
||||
local secure_interfaces = module:get_option_set("secure_interfaces", { "127.0.0.1", "::1" }); |
||||
|
||||
module:hook("stream-features", function (event) |
||||
local session = event.origin; |
||||
if session.type ~= "c2s_unauthed" then return; end |
||||
local socket = session.conn:socket(); |
||||
if not socket.getsockname then |
||||
module:log("debug", "Unable to determine local address of incoming connection"); |
||||
return; |
||||
end |
||||
local localip = socket:getsockname(); |
||||
if secure_interfaces:contains(localip) then |
||||
module:log("debug", "Marking session from %s to %s as secure", session.ip or "[?]", localip); |
||||
session.secure = true; |
||||
session.conn.starttls = false; |
||||
else |
||||
module:log("debug", "Not marking session from %s to %s as secure", session.ip or "[?]", localip); |
||||
end |
||||
end, 2500); |
Loading…
Reference in new issue