feat: Modules for implementing visitor nodes. (#12593)
* feat: Modules for implementing visitor nodes. Still WIP, uses visitor nodes prosodies where we create the main participants and forward the visitors to watch. Used for huge conferences. * squash: Fix comments.
This commit is contained in:
parent
0ba033e07d
commit
76471a0ea9
|
@ -0,0 +1,16 @@
|
||||||
|
-- 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,138 @@
|
||||||
|
--- activate under main muc component
|
||||||
|
--- Add the following config under the main muc component
|
||||||
|
--- muc_room_default_presence_broadcast = {
|
||||||
|
--- visitor = false;
|
||||||
|
--- participant = true;
|
||||||
|
--- moderator = true;
|
||||||
|
--- };
|
||||||
|
--- Enable in global modules: 's2s_bidi'
|
||||||
|
--- Make sure 's2s' is not in modules_disabled
|
||||||
|
--- TODO: Do we need the /etc/hosts changes? We can drop it for https://modules.prosody.im/mod_s2soutinjection.html
|
||||||
|
--- In /etc/hosts add:
|
||||||
|
--- vmmain-ip-address focus.domain.com
|
||||||
|
--- vmmain-ip-address conference.domain.com
|
||||||
|
--- vmmain-ip-address domain.com
|
||||||
|
--- Open port 5269 on the provider side and on the firewall of the machine, so the core node can access this visitor one
|
||||||
|
local jid = require 'util.jid';
|
||||||
|
local st = require 'util.stanza';
|
||||||
|
|
||||||
|
local muc_domain_prefix = module:get_option_string('muc_mapper_domain_prefix', 'conference');
|
||||||
|
local main_domain = string.gsub(module.host, muc_domain_prefix..'.', '');
|
||||||
|
|
||||||
|
local function get_focus_occupant(room)
|
||||||
|
local focus_occupant = room._data.focus_occupant;
|
||||||
|
|
||||||
|
if focus_occupant then
|
||||||
|
return focus_occupant;
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, n_occupant in room:each_occupant() do
|
||||||
|
if jid.node(n_occupant.jid) == 'focus' then
|
||||||
|
room._data.focus_occupant = n_occupant;
|
||||||
|
return n_occupant;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return nil;
|
||||||
|
end
|
||||||
|
|
||||||
|
-- mark all occupants as visitors
|
||||||
|
module:hook('muc-occupant-pre-join', function (event)
|
||||||
|
local occupant = event.occupant;
|
||||||
|
|
||||||
|
if jid.host(occupant.bare_jid) == main_domain then
|
||||||
|
occupant.role = 'visitor';
|
||||||
|
end
|
||||||
|
end, 3);
|
||||||
|
|
||||||
|
-- when occupant is leaving forward presences to jicofo for visitors
|
||||||
|
-- do not check occupant.role as it maybe already reset
|
||||||
|
-- if there are no main occupants or no visitors, destroy the room (give 15 seconds of grace period for reconnections)
|
||||||
|
module:hook('muc-occupant-left', function (event)
|
||||||
|
local room, occupant = event.room, event.occupant;
|
||||||
|
local occupant_domain = jid.host(occupant.bare_jid);
|
||||||
|
|
||||||
|
if occupant_domain == main_domain then
|
||||||
|
local focus_occupant = get_focus_occupant(room);
|
||||||
|
if not focus_occupant then
|
||||||
|
module:log('warn', 'No focus found for %s', room.jid);
|
||||||
|
return;
|
||||||
|
end
|
||||||
|
-- Let's forward unavailable presence to the special jicofo
|
||||||
|
local pr = st.presence({ to = focus_occupant.jid, from = occupant.nick, type = 'unavailable' })
|
||||||
|
:tag('x', { xmlns = 'http://jabber.org/protocol/muc#user' })
|
||||||
|
:tag('item', {
|
||||||
|
affiliation = room:get_affiliation(occupant.bare_jid) or 'none';
|
||||||
|
role = 'none';
|
||||||
|
nick = event.nick;
|
||||||
|
jid = occupant.bare_jid }):up()
|
||||||
|
:up();
|
||||||
|
room:route_stanza(pr);
|
||||||
|
end
|
||||||
|
|
||||||
|
if not room.destroying then
|
||||||
|
if room.xxl_destroy_timer then
|
||||||
|
room.xxl_destroy_timer:stop();
|
||||||
|
end
|
||||||
|
|
||||||
|
room.xxl_destroy_timer = module:add_timer(15, function()
|
||||||
|
-- let's check are all visitors in the room, if all a visitors - destroy it
|
||||||
|
-- if all are main-participants also destroy it
|
||||||
|
local main_count = 0;
|
||||||
|
local visitors_count = 0;
|
||||||
|
|
||||||
|
for _, o in room:each_occupant() do
|
||||||
|
-- if there are visitor and main participant there is no point continue
|
||||||
|
if main_count > 0 and visitors_count > 0 then
|
||||||
|
return;
|
||||||
|
end
|
||||||
|
|
||||||
|
if o.role == 'visitor' then
|
||||||
|
visitors_count = visitors_count + 1;
|
||||||
|
else
|
||||||
|
main_count = main_count + 1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if main_count == 0 then
|
||||||
|
module:log('info', 'Will destroy:%s main_occupants:%s visitors:%s', room.jid, main_count, visitors_count);
|
||||||
|
room:destroy(nil, 'No main participants.');
|
||||||
|
elseif visitors_count == 0 then
|
||||||
|
module:log('info', 'Will destroy:%s main_occupants:%s visitors:%s', room.jid, main_count, visitors_count);
|
||||||
|
room:destroy(nil, 'No visitors.');
|
||||||
|
end
|
||||||
|
end);
|
||||||
|
end
|
||||||
|
end);
|
||||||
|
|
||||||
|
-- forward visitor presences to jicofo
|
||||||
|
module:hook('muc-broadcast-presence', function (event)
|
||||||
|
local occupant = event.occupant;
|
||||||
|
|
||||||
|
---- we are interested only of visitors presence to send it to jicofo
|
||||||
|
if occupant.role ~= 'visitor' then
|
||||||
|
return;
|
||||||
|
end
|
||||||
|
|
||||||
|
local room = event.room;
|
||||||
|
local focus_occupant = get_focus_occupant(room);
|
||||||
|
|
||||||
|
if not focus_occupant then
|
||||||
|
return;
|
||||||
|
end
|
||||||
|
|
||||||
|
local actor, base_presence, nick, reason, x = event.actor, event.stanza, event.nick, event.reason, event.x;
|
||||||
|
local actor_nick;
|
||||||
|
if actor then
|
||||||
|
actor_nick = jid.resource(room:get_occupant_jid(actor));
|
||||||
|
end
|
||||||
|
|
||||||
|
-- create a presence to send it to jicofo, as jicofo is special :)
|
||||||
|
local full_x = st.clone(x.full or x);
|
||||||
|
|
||||||
|
room:build_item_list(occupant, full_x, false, nick, actor_nick, actor, reason);
|
||||||
|
local full_p = st.clone(base_presence):add_child(full_x);
|
||||||
|
full_p.attr.to = focus_occupant.jid;
|
||||||
|
room:route_to_occupant(focus_occupant, full_p);
|
||||||
|
return;
|
||||||
|
end);
|
|
@ -38,3 +38,11 @@ end);
|
||||||
module:hook("muc-config-form", function(event)
|
module:hook("muc-config-form", function(event)
|
||||||
table.insert(event.form, getMeetingIdConfig(event.room));
|
table.insert(event.form, getMeetingIdConfig(event.room));
|
||||||
end, 90-3);
|
end, 90-3);
|
||||||
|
|
||||||
|
-- disabled few options for room config, to not mess with visitor logic
|
||||||
|
module:hook("muc-config-submitted/muc#roomconfig_moderatedroom", function()
|
||||||
|
return true;
|
||||||
|
end, 99);
|
||||||
|
module:hook("muc-config-submitted/muc#roomconfig_presencebroadcast", function()
|
||||||
|
return true;
|
||||||
|
end, 99);
|
||||||
|
|
|
@ -0,0 +1,216 @@
|
||||||
|
--- activate under main vhost
|
||||||
|
--- In /etc/hosts add:
|
||||||
|
--- vm1-ip-address visitors1.domain.com
|
||||||
|
--- vm1-ip-address conference.visitors1.domain.com
|
||||||
|
--- vm2-ip-address visitors2.domain.com
|
||||||
|
--- vm2-ip-address conference.visitors2.domain.com
|
||||||
|
--- TODO: drop the /etc/hosts changes for https://modules.prosody.im/mod_s2soutinjection.html
|
||||||
|
--- Enable in global modules: 's2s_bidi' and 'certs_all'
|
||||||
|
--- Make sure 's2s' is not in modules_disabled
|
||||||
|
--- Open port 5269 on the provider side and on the firewall on the machine (iptables -I INPUT 4 -p tcp -m tcp --dport 5269 -j ACCEPT)
|
||||||
|
--- TODO: make it work with tenants
|
||||||
|
local st = require 'util.stanza';
|
||||||
|
local jid = require 'util.jid';
|
||||||
|
local util = module:require 'util';
|
||||||
|
local presence_check_status = util.presence_check_status;
|
||||||
|
|
||||||
|
local um_is_admin = require 'core.usermanager'.is_admin;
|
||||||
|
local function is_admin(jid)
|
||||||
|
return um_is_admin(jid, module.host);
|
||||||
|
end
|
||||||
|
|
||||||
|
local MUC_NS = 'http://jabber.org/protocol/muc';
|
||||||
|
|
||||||
|
-- get/infer focus component hostname so we can intercept IQ bound for it
|
||||||
|
local focus_component_host = module:get_option_string('focus_component');
|
||||||
|
if not focus_component_host then
|
||||||
|
local muc_domain_base = module:get_option_string('muc_mapper_domain_base');
|
||||||
|
if not muc_domain_base then
|
||||||
|
module:log('error', 'Could not infer focus domain. Disabling %s', module:get_name());
|
||||||
|
return;
|
||||||
|
end
|
||||||
|
focus_component_host = 'focus.'..muc_domain_base;
|
||||||
|
end
|
||||||
|
|
||||||
|
-- required parameter for custom muc component prefix, defaults to 'conference'
|
||||||
|
local muc_domain_prefix = module:get_option_string('muc_mapper_domain_prefix', 'conference');
|
||||||
|
|
||||||
|
local main_muc_component_config = module:get_option_string('main_muc');
|
||||||
|
if main_muc_component_config == nil then
|
||||||
|
module:log('error', 'xxl rooms not enabled missing main_muc config');
|
||||||
|
return ;
|
||||||
|
end
|
||||||
|
|
||||||
|
-- visitors_nodes = {
|
||||||
|
-- roomjid1 = {
|
||||||
|
-- nodes = {
|
||||||
|
-- ['conference.visitors1.jid'] = 2, // number of main participants, on 0 we clean it
|
||||||
|
-- ['conference.visitors2.jid'] = 3
|
||||||
|
-- }
|
||||||
|
-- },
|
||||||
|
-- roomjid2 = {}
|
||||||
|
--}
|
||||||
|
local visitors_nodes = {};
|
||||||
|
|
||||||
|
--- Intercept conference IQ error from Jicofo. Sends the main participants to the visitor node.
|
||||||
|
--- Jicofo is connected to the room when sending this error
|
||||||
|
module:log('info', 'Hook to iq/host');
|
||||||
|
module:hook('iq/full', function(event)
|
||||||
|
local session, stanza = event.origin, event.stanza;
|
||||||
|
|
||||||
|
if stanza.name ~= 'iq' or stanza.attr.type ~= 'error' or stanza.attr.from ~= focus_component_host then
|
||||||
|
return; -- not IQ from jicofo. Ignore this event.
|
||||||
|
end
|
||||||
|
|
||||||
|
local error = stanza:get_child('error');
|
||||||
|
if error == nil then
|
||||||
|
return; -- not Conference IQ error. Ignore.
|
||||||
|
end
|
||||||
|
|
||||||
|
local redirect = error:get_child('redirect', 'urn:ietf:params:xml:ns:xmpp-stanzas');
|
||||||
|
local redirect_host = error:get_child_text('url', 'http://jitsi.org/jitmeet');
|
||||||
|
|
||||||
|
if not redirect or not redirect_host then
|
||||||
|
return;
|
||||||
|
end
|
||||||
|
|
||||||
|
-- let's send participants if any from the room to the visitors room
|
||||||
|
-- TODO fix room name extract, make sure it works wit tenants
|
||||||
|
local main_room = error:get_child_text('main-room', 'http://jitsi.org/jitmeet');
|
||||||
|
|
||||||
|
if not main_room then
|
||||||
|
return;
|
||||||
|
end
|
||||||
|
|
||||||
|
local room = get_room_from_jid(main_room);
|
||||||
|
|
||||||
|
if room == nil then
|
||||||
|
return; -- room does not exists. Continue with normal flow
|
||||||
|
end
|
||||||
|
|
||||||
|
local conference_service = muc_domain_prefix..'.'..redirect_host;
|
||||||
|
|
||||||
|
if visitors_nodes[room.jid] and
|
||||||
|
visitors_nodes[room.jid].nodes and
|
||||||
|
visitors_nodes[room.jid].nodes[conference_service] then
|
||||||
|
-- nothing to do
|
||||||
|
return;
|
||||||
|
end
|
||||||
|
|
||||||
|
if visitors_nodes[room.jid] == nil then
|
||||||
|
visitors_nodes[room.jid] = {};
|
||||||
|
end
|
||||||
|
if visitors_nodes[room.jid].nodes == nil then
|
||||||
|
visitors_nodes[room.jid].nodes = {};
|
||||||
|
end
|
||||||
|
|
||||||
|
local sent_main_participants = 0;
|
||||||
|
|
||||||
|
for _, o in room:each_occupant() do
|
||||||
|
if not is_admin(o.bare_jid) then
|
||||||
|
local fmuc_pr = st.clone(o:get_presence());
|
||||||
|
local user, _, res = jid.split(o.nick);
|
||||||
|
fmuc_pr.attr.to = jid.join(user, conference_service , res);
|
||||||
|
fmuc_pr.attr.from = o.jid;
|
||||||
|
-- add <x>
|
||||||
|
fmuc_pr:tag('x', { xmlns = MUC_NS }):up();
|
||||||
|
|
||||||
|
module:send(fmuc_pr);
|
||||||
|
|
||||||
|
sent_main_participants = sent_main_participants + 1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
visitors_nodes[room.jid].nodes[conference_service] = sent_main_participants;
|
||||||
|
end, 900);
|
||||||
|
|
||||||
|
-- takes care when the visitor nodes destroys the room to count the leaving participants from there, and if its really destroyed
|
||||||
|
-- we clean up, so if we establish again the connection to the same visitor node to send the main participants
|
||||||
|
module:hook('presence/full', function(event)
|
||||||
|
local stanza = event.stanza;
|
||||||
|
local room_name, from_host = jid.split(stanza.attr.from);
|
||||||
|
if stanza.attr.type == 'unavailable' and from_host ~= main_muc_component_config then
|
||||||
|
-- TODO tenants???
|
||||||
|
local room_jid = jid.join(room_name, main_muc_component_config); -- converts from visitor to main room jid
|
||||||
|
|
||||||
|
local x = stanza:get_child('x', 'http://jabber.org/protocol/muc#user');
|
||||||
|
if not presence_check_status(x, '110') then
|
||||||
|
return;
|
||||||
|
end
|
||||||
|
|
||||||
|
if visitors_nodes[room_jid] and visitors_nodes[room_jid].nodes
|
||||||
|
and visitors_nodes[room_jid].nodes[from_host] then
|
||||||
|
visitors_nodes[room_jid].nodes[from_host] = visitors_nodes[room_jid].nodes[from_host] - 1;
|
||||||
|
if visitors_nodes[room_jid].nodes[from_host] == 0 then
|
||||||
|
visitors_nodes[room_jid].nodes[from_host] = nil;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end, 900);
|
||||||
|
|
||||||
|
-- process a host module directly if loaded or hooks to wait for its load
|
||||||
|
function process_host_module(name, callback)
|
||||||
|
local function process_host(host)
|
||||||
|
if host == name then
|
||||||
|
callback(module:context(host), host);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if prosody.hosts[name] == nil then
|
||||||
|
module:log('debug', 'No host/component found, will wait for it: %s', name)
|
||||||
|
|
||||||
|
-- when a host or component is added
|
||||||
|
prosody.events.add_handler('host-activated', process_host);
|
||||||
|
else
|
||||||
|
process_host(name);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
process_host_module(main_muc_component_config, function(host_module, host)
|
||||||
|
-- detects presence change in a main participant and propagate it to the used visitor nodes
|
||||||
|
host_module:hook('muc-occupant-pre-change', function (event)
|
||||||
|
local room, stanza, occupant = event.room, event.stanza, event.dest_occupant;
|
||||||
|
|
||||||
|
-- filter focus
|
||||||
|
if is_admin(stanza.attr.from) or visitors_nodes[room.jid] == nil then
|
||||||
|
return;
|
||||||
|
end
|
||||||
|
|
||||||
|
local vnodes = visitors_nodes[room.jid].nodes;
|
||||||
|
-- a change in the presence of a main participant we need to update all active visitor nodes
|
||||||
|
for k in pairs(vnodes) do
|
||||||
|
local fmuc_pr = st.clone(stanza);
|
||||||
|
local user, _, res = jid.split(occupant.nick);
|
||||||
|
fmuc_pr.attr.to = jid.join(user, k, res);
|
||||||
|
fmuc_pr.attr.from = occupant.jid;
|
||||||
|
module:send(fmuc_pr);
|
||||||
|
end
|
||||||
|
end);
|
||||||
|
|
||||||
|
-- when a main participant leaves inform the visitor nodes
|
||||||
|
host_module:hook('muc-occupant-left', function (event)
|
||||||
|
local room, stanza, occupant = event.room, event.stanza, event.occupant;
|
||||||
|
|
||||||
|
if is_admin(occupant.bare_jid) or visitors_nodes[room.jid] == nil or visitors_nodes[room.jid].nodes == nil then
|
||||||
|
return;
|
||||||
|
end
|
||||||
|
|
||||||
|
-- we want to update visitor node that a main participant left
|
||||||
|
if stanza then
|
||||||
|
local vnodes = visitors_nodes[room.jid].nodes;
|
||||||
|
for k in pairs(vnodes) do
|
||||||
|
local fmuc_pr = st.clone(stanza);
|
||||||
|
local user, _, res = jid.split(occupant.nick);
|
||||||
|
fmuc_pr.attr.to = jid.join(user, k, res);
|
||||||
|
fmuc_pr.attr.from = occupant.jid;
|
||||||
|
module:send(fmuc_pr);
|
||||||
|
end
|
||||||
|
else
|
||||||
|
module:log('warn', 'No unavailable stanza found ... leak participant on visitor');
|
||||||
|
end
|
||||||
|
end);
|
||||||
|
|
||||||
|
-- cleanup cache
|
||||||
|
host_module:hook('muc-room-destroyed',function(event)
|
||||||
|
visitors_nodes[event.room.jid] = nil;
|
||||||
|
end);
|
||||||
|
end);
|
Loading…
Reference in New Issue