1 /*
2  * Jicofo, the Jitsi Conference Focus.
3  *
4  * Copyright @ 2018 - present 8x8, Inc.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18 package org.jitsi.jicofo;
19 
20 import net.java.sip.communicator.impl.protocol.jabber.*;
21 import org.jitsi.jicofo.bridge.*;
22 import org.jitsi.xmpp.extensions.colibri.*;
23 
24 import org.jitsi.eventadmin.*;
25 import org.jitsi.jicofo.discovery.*;
26 import org.jitsi.jicofo.discovery.Version;
27 import org.jitsi.jicofo.event.*;
28 import org.jitsi.jicofo.jigasi.*;
29 import org.jitsi.jicofo.recording.jibri.*;
30 import org.jitsi.jicofo.xmpp.*;
31 import org.jitsi.osgi.*;
32 import org.jitsi.protocol.xmpp.*;
33 import org.jitsi.service.configuration.*;
34 import org.jitsi.utils.*;
35 import org.jitsi.utils.logging.*;
36 
37 import org.json.simple.*;
38 import org.jxmpp.jid.*;
39 import org.osgi.framework.*;
40 
41 import java.util.*;
42 
43 import static org.jitsi.jicofo.bridge.BridgeSelector.*;
44 
45 /**
46  * Class manages discovery of Jitsi Meet application services like
47  * jitsi-videobridge, recording, SIP gateway and so on...
48  *
49  * @author Pawel Domas
50  */
51 public class JitsiMeetServices
52     extends EventHandlerActivator
53 {
54     /**
55      * The logger
56      */
57     private final static Logger logger
58         = Logger.getLogger(JitsiMeetServices.class);
59 
60     /**
61      * The set of features sufficient for a node to be recognized as a
62      * jitsi-videobridge.
63      */
64     public static final String[] VIDEOBRIDGE_FEATURES = new String[]
65         {
66             ColibriConferenceIQ.NAMESPACE,
67             ProtocolProviderServiceJabberImpl
68                 .URN_XMPP_JINGLE_DTLS_SRTP,
69             ProtocolProviderServiceJabberImpl
70                 .URN_XMPP_JINGLE_ICE_UDP_1
71         };
72 
73     /**
74      * The XMPP Service Discovery features of MUC service provided by the XMPP
75      * server.
76      */
77     private static final String[] MUC_FEATURES
78         = { "http://jabber.org/protocol/muc" };
79 
80     /**
81      * Features advertised by SIP gateway component.
82      */
83     private static final String[] SIP_GW_FEATURES = new String[]
84         {
85             "http://jitsi.org/protocol/jigasi",
86             "urn:xmpp:rayo:0"
87         };
88 
89     /**
90      * Features used to recognize pub-sub service.
91      */
92     /*private static final String[] PUBSUB_FEATURES = new String[]
93         {
94             "http://jabber.org/protocol/pubsub",
95             "http://jabber.org/protocol/pubsub#subscribe"
96         };*/
97 
98     /**
99      * Manages Jitsi Videobridge component XMPP addresses.
100      */
101     private final BridgeSelector bridgeSelector;
102 
103     private Set<BaseBrewery> breweryDetectors = new HashSet<>();
104 
105     /**
106      * The name of XMPP domain to which Jicofo user logs in.
107      */
108     private final DomainBareJid jicofoUserDomain;
109 
110     /**
111      * The {@link ProtocolProviderHandler} for JVB XMPP connection.
112      */
113     private final ProtocolProviderHandler jvbBreweryProtocolProvider;
114 
115     /**
116      * The {@link ProtocolProviderHandler} for Jicofo XMPP connection.
117      */
118     private final ProtocolProviderHandler protocolProvider;
119 
120     /**
121      * SIP gateway component XMPP address.
122      */
123     private Jid sipGateway;
124 
125     /**
126      * The address of MUC component served by our XMPP domain.
127      */
128     private Jid mucService;
129 
130     /**
131      * <tt>Version</tt> IQ instance holding detected XMPP server's version
132      * (if any).
133      */
134     private Version XMPPServerVersion;
135 
136     /**
137      * Returns <tt>true</tt> if given list of features complies with JVB feature
138      * list.
139      * @param features the list of feature to be checked.
140      */
isJitsiVideobridge(List<String> features)141     static public boolean isJitsiVideobridge(List<String> features)
142     {
143         return DiscoveryUtil.checkFeatureSupport(
144                 VIDEOBRIDGE_FEATURES, features);
145     }
146 
147     /**
148      * Creates new instance of <tt>JitsiMeetServices</tt>
149      *  @param protocolProviderHandler {@link ProtocolProviderHandler} for Jicofo
150      *        XMPP connection.
151      * @param jvbMucProtocolProvider {@link ProtocolProviderHandler} for JVB
152      *        XMPP connection.
153      * @param jicofoUserDomain the name of the XMPP domain to which Jicofo user
154      */
JitsiMeetServices(ProtocolProviderHandler protocolProviderHandler, ProtocolProviderHandler jvbMucProtocolProvider, DomainBareJid jicofoUserDomain)155     public JitsiMeetServices(ProtocolProviderHandler protocolProviderHandler,
156                              ProtocolProviderHandler jvbMucProtocolProvider,
157                              DomainBareJid jicofoUserDomain)
158     {
159         super(new String[] { BridgeEvent.HEALTH_CHECK_FAILED });
160 
161         Objects.requireNonNull(
162             protocolProviderHandler, "protocolProviderHandler");
163         Objects.requireNonNull(
164             jvbMucProtocolProvider, "jvbMucProtocolProvider");
165 
166         OperationSetSubscription subscriptionOpSet
167             = protocolProviderHandler.getOperationSet(
168                     OperationSetSubscription.class);
169 
170         Objects.requireNonNull(subscriptionOpSet, "subscriptionOpSet");
171 
172         this.jicofoUserDomain = jicofoUserDomain;
173         this.protocolProvider = protocolProviderHandler;
174         this.jvbBreweryProtocolProvider = jvbMucProtocolProvider;
175         this.bridgeSelector = new BridgeSelector(subscriptionOpSet);
176     }
177 
178     /**
179      * Called by other classes when they detect JVB instance.
180      * @param bridgeJid the JID of discovered JVB component.
181      */
newBridgeDiscovered(Jid bridgeJid, Version version)182     void newBridgeDiscovered(Jid bridgeJid, Version version)
183     {
184         bridgeSelector.addJvbAddress(bridgeJid, version);
185     }
186 
187     /**
188      * Call when new component becomes available.
189      *
190      * @param node component XMPP address
191      * @param features list of features supported by <tt>node</tt>
192      * @param version the <tt>Version</tt> IQ which carries the info about
193      *                <tt>node</tt> version(if any).
194      */
newNodeDiscovered(Jid node, List<String> features, Version version)195     void newNodeDiscovered(Jid node,
196                            List<String> features,
197                            Version version)
198     {
199         if (isJitsiVideobridge(features))
200         {
201             newBridgeDiscovered(node, version);
202         }
203         else if (sipGateway == null
204             && DiscoveryUtil.checkFeatureSupport(SIP_GW_FEATURES, features))
205         {
206             logger.info("Discovered SIP gateway: " + node);
207 
208             setSipGateway(node);
209         }
210         else if (mucService == null
211             && DiscoveryUtil.checkFeatureSupport(MUC_FEATURES, features))
212         {
213             logger.info("MUC component discovered: " + node);
214 
215             setMucService(node);
216         }
217         else if (jicofoUserDomain != null && jicofoUserDomain.equals(node) && version != null)
218         {
219             this.XMPPServerVersion = version;
220 
221             logger.info("Detected XMPP server version: " + version.getNameVersionOsString());
222         }
223         /*
224         FIXME: pub-sub service auto-detect ?
225         else if (capsOpSet.hasFeatureSupport(item, PUBSUB_FEATURES))
226         {
227             // Potential PUBSUB service
228             logger.info("Potential PUBSUB service:" + item);
229             List<String> subItems = capsOpSet.getItems(item);
230             for (String subItem: subItems)
231             {
232                 logger.info("Subnode " + subItem + " of " + item);
233                 capsOpSet.hasFeatureSupport(
234                     item, subItem, VIDEOBRIDGE_FEATURES);
235             }
236         }*/
237     }
238 
239     /**
240      * Call when a component goes offline.
241      *
242      * @param node XMPP address of disconnected XMPP component.
243      */
nodeNoLongerAvailable(Jid node)244     void nodeNoLongerAvailable(Jid node)
245     {
246         if (bridgeSelector.isJvbOnTheList(node))
247         {
248             bridgeSelector.removeJvbAddress(node);
249         }
250         else if (node.equals(sipGateway))
251         {
252             logger.warn("SIP gateway went offline: " + node);
253 
254             sipGateway = null;
255         }
256         else if (node.equals(mucService))
257         {
258             logger.warn("MUC component went offline: " + node);
259 
260             mucService = null;
261         }
262     }
263 
264     /**
265      * Sets new XMPP address of the SIP gateway component.
266      * @param sipGateway the XMPP address to be set as SIP gateway component
267      *                   address.
268      */
setSipGateway(Jid sipGateway)269     void setSipGateway(Jid sipGateway)
270     {
271         this.sipGateway = sipGateway;
272     }
273 
274     /**
275      * Returns XMPP address of SIP gateway component.
276      */
getSipGateway()277     public Jid getSipGateway()
278     {
279         return sipGateway;
280     }
281 
282     /**
283      * Returns Jibri SIP detector if available.
284      * @return {@link JibriDetector} or <tt>null</tt> if not configured.
285      */
getSipJibriDetector()286     public JibriDetector getSipJibriDetector()
287     {
288         return breweryDetectors.stream()
289             .filter(d -> d instanceof JibriDetector)
290             .map(d -> ((JibriDetector) d))
291             .filter(JibriDetector::isSip)
292             .findFirst().orElse(null);
293     }
294 
295     /**
296      * Returns {@link JibriDetector} instance that manages Jibri pool used by
297      * this Jicofo process or <tt>null</tt> if unavailable in the current
298      * session.
299      */
getJibriDetector()300     public JibriDetector getJibriDetector()
301     {
302         return breweryDetectors.stream()
303             .filter(d -> d instanceof JibriDetector)
304             .map(d -> ((JibriDetector) d))
305             .filter(d -> !d.isSip())
306             .findFirst().orElse(null);
307     }
308 
309     /**
310      * Returns {@link JigasiDetector} instance that manages Jigasi pool used by
311      * this Jicofo process or <tt>null</tt> if unavailable in the current
312      * session.
313      */
getJigasiDetector()314     public JigasiDetector getJigasiDetector()
315     {
316         return breweryDetectors.stream()
317             .filter(d -> d instanceof JigasiDetector)
318             .map(d -> ((JigasiDetector) d))
319             .findFirst().orElse(null);
320     }
321 
322     /**
323      * Returns {@link BridgeSelector} bound to this instance that can be used to
324      * select the videobridge on the xmppDomain handled by this instance.
325      */
getBridgeSelector()326     public BridgeSelector getBridgeSelector()
327     {
328         return bridgeSelector;
329     }
330 
331     /**
332      * Returns the address of MUC component for our XMPP domain.
333      */
getMucService()334     public Jid getMucService()
335     {
336         return mucService;
337     }
338 
339     /**
340      * Sets the address of MUC component.
341      * @param mucService component sub domain that refers to MUC
342      */
setMucService(Jid mucService)343     public void setMucService(Jid mucService)
344     {
345         this.mucService = mucService;
346     }
347 
348     @Override
start(BundleContext bundleContext)349     public void start(BundleContext bundleContext)
350         throws Exception
351     {
352         bridgeSelector.init();
353 
354         super.start(bundleContext);
355 
356         ConfigurationService config = FocusBundleActivator.getConfigService();
357         String jibriBreweryName
358             = config.getString(JibriDetector.JIBRI_ROOM_PNAME);
359 
360         if (!StringUtils.isNullOrEmpty(jibriBreweryName))
361         {
362             JibriDetector jibriDetector
363                 = new JibriDetector(protocolProvider, jibriBreweryName, false);
364             logger.info("Using a Jibri detector with MUC: " + jibriBreweryName);
365 
366             jibriDetector.init();
367             breweryDetectors.add(jibriDetector);
368         }
369 
370         String jigasiBreweryName
371             = config.getString(JigasiDetector.JIGASI_ROOM_PNAME);
372         if (!StringUtils.isNullOrEmpty(jigasiBreweryName))
373         {
374             JigasiDetector jigasiDetector
375                 = new JigasiDetector(
376                     protocolProvider,
377                     jigasiBreweryName,
378                     config.getString(LOCAL_REGION_PNAME, null));
379             logger.info("Using a Jigasi detector with MUC: " + jigasiBreweryName);
380 
381             jigasiDetector.init();
382             breweryDetectors.add(jigasiDetector);
383         }
384 
385         String jibriSipBreweryName
386             = config.getString(JibriDetector.JIBRI_SIP_ROOM_PNAME);
387         if (!StringUtils.isNullOrEmpty(jibriSipBreweryName))
388         {
389             JibriDetector sipJibriDetector
390                 = new JibriDetector(
391                         protocolProvider, jibriSipBreweryName, true);
392             logger.info(
393                 "Using a SIP Jibri detector with MUC: " + jibriSipBreweryName);
394 
395             sipJibriDetector.init();
396             breweryDetectors.add(sipJibriDetector);
397         }
398 
399         String bridgeBreweryName
400             = config.getString(BridgeMucDetector.BRIDGE_MUC_PNAME);
401         if (!StringUtils.isNullOrEmpty(bridgeBreweryName))
402         {
403             BridgeMucDetector bridgeMucDetector
404                 = new BridgeMucDetector(
405                     jvbBreweryProtocolProvider,
406                     bridgeBreweryName,
407                     bridgeSelector);
408             logger.info(
409                 "Using a Bridge MUC detector with MUC: " + bridgeBreweryName);
410 
411             bridgeMucDetector.init();
412             breweryDetectors.add(bridgeMucDetector);
413         }
414     }
415 
416     @Override
stop(BundleContext bundleContext)417     public void stop(BundleContext bundleContext)
418         throws Exception
419     {
420         breweryDetectors.forEach(BaseBrewery::dispose);
421         breweryDetectors.clear();
422 
423         bridgeSelector.dispose();
424 
425         super.stop(bundleContext);
426     }
427 
428     @Override
handleEvent(Event event)429     public void handleEvent(Event event)
430     {
431         if (BridgeEvent.HEALTH_CHECK_FAILED.equals(event.getTopic()))
432         {
433             BridgeEvent bridgeEvent = (BridgeEvent) event;
434 
435             bridgeSelector.removeJvbAddress(bridgeEvent.getBridgeJid());
436         }
437     }
438 
439     /**
440      * The version of XMPP server to which Jicofo user is connecting to.
441      *
442      * @return {@link Version} instance which holds the version details. Can be
443      *         <tt>null</tt> if not discovered yet.
444      */
getXMPPServerVersion()445     public Version getXMPPServerVersion()
446     {
447         return XMPPServerVersion;
448     }
449 
450     /**
451      * Finds the version of the videobridge identified by given
452      * <tt>bridgeJid</tt>.
453      *
454      * @param bridgeJid the XMPP address of the videobridge for which we want to
455      *        obtain the version.
456      *
457      * @return JVB version or <tt>null</tt> if unknown.
458      */
getBridgeVersion(Jid bridgeJid)459     public String getBridgeVersion(Jid bridgeJid)
460     {
461         return bridgeSelector.getBridgeVersion(bridgeJid);
462     }
463 
getStats()464     public JSONObject getStats()
465     {
466         JSONObject json = new JSONObject();
467 
468         json.put("bridge_selector", bridgeSelector.getStats());
469         JigasiDetector jigasiDetector = getJigasiDetector();
470         if (jigasiDetector != null)
471         {
472             json.put("jigasi_detector", jigasiDetector.getStats());
473         }
474 
475         JibriDetector jibriDetector = getJibriDetector();
476         if (jibriDetector != null)
477         {
478             json.put("jibri_detector", jibriDetector.getStats());
479         }
480 
481         JibriDetector sipJibriDetector = getSipJibriDetector();
482         if (sipJibriDetector != null)
483         {
484             json.put("sip_jibri_detector", sipJibriDetector.getStats());
485         }
486 
487         return json;
488     }
489 }
490