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