001/** 002 * 003 * Copyright 2003-2005 Jive Software. 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.jivesoftware.smackx.jingleold.nat; 019 020import java.io.IOException; 021import java.net.InetAddress; 022import java.net.NetworkInterface; 023import java.net.SocketException; 024import java.util.Enumeration; 025import java.util.logging.Level; 026import java.util.logging.Logger; 027 028import org.jivesoftware.smack.SmackException.NoResponseException; 029import org.jivesoftware.smack.SmackException.NotConnectedException; 030import org.jivesoftware.smack.StanzaCollector; 031import org.jivesoftware.smack.XMPPConnection; 032import org.jivesoftware.smack.XMPPException.XMPPErrorException; 033import org.jivesoftware.smack.packet.IQ; 034import org.jivesoftware.smack.packet.XmlEnvironment; 035import org.jivesoftware.smack.provider.IQProvider; 036import org.jivesoftware.smack.provider.ProviderManager; 037import org.jivesoftware.smack.xml.XmlPullParser; 038import org.jivesoftware.smack.xml.XmlPullParserException; 039 040import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; 041import org.jivesoftware.smackx.disco.packet.DiscoverInfo; 042 043import org.jxmpp.jid.DomainBareJid; 044import org.jxmpp.jid.impl.JidCreate; 045import org.jxmpp.stringprep.XmppStringprepException; 046 047/** 048 * RTPBridge IQ Stanza used to request and retrieve a RTPBridge Candidates that can be used for a Jingle Media Transmission between two parties that are behind NAT. 049 * This Jingle Bridge has all the needed information to establish a full UDP Channel (Send and Receive) between two parties. 050 * <i>This transport method should be used only if other transport methods are not allowed. Or if you want a more reliable transport.</i> 051 * 052 * High Level Usage Example: 053 * 054 * RTPBridge rtpBridge = RTPBridge.getRTPBridge(connection, sessionID); 055 * 056 * @author Thiago Camargo 057 */ 058public class RTPBridge extends IQ { 059 060 private static final Logger LOGGER = Logger.getLogger(RTPBridge.class.getName()); 061 062 private String sid; 063 private String pass; 064 private String ip; 065 private String name; 066 private int portA = -1; 067 private int portB = -1; 068 private String hostA; 069 private String hostB; 070 private BridgeAction bridgeAction = BridgeAction.create; 071 072 private enum BridgeAction { 073 074 create, change, publicip 075 } 076 077 /** 078 * Element name of the stanza extension. 079 */ 080 public static final String NAME = "rtpbridge"; 081 082 /** 083 * Element name of the stanza extension. 084 */ 085 public static final String ELEMENT_NAME = "rtpbridge"; 086 087 /** 088 * Namespace of the stanza extension. 089 */ 090 public static final String NAMESPACE = "http://www.jivesoftware.com/protocol/rtpbridge"; 091 092 static { 093 ProviderManager.addIQProvider(NAME, NAMESPACE, new Provider()); 094 } 095 096 /** 097 * Creates a RTPBridge Instance with defined Session ID. 098 * 099 * @param sid TODO javadoc me please 100 */ 101 public RTPBridge(String sid) { 102 this(); 103 this.sid = sid; 104 } 105 106 /** 107 * Creates a RTPBridge Instance with defined Session ID. 108 * 109 * @param action TODO javadoc me please 110 */ 111 public RTPBridge(BridgeAction action) { 112 this(); 113 this.bridgeAction = action; 114 } 115 116 /** 117 * Creates a RTPBridge Instance with defined Session ID. 118 * 119 * @param sid TODO javadoc me please 120 * @param bridgeAction TODO javadoc me please 121 */ 122 public RTPBridge(String sid, BridgeAction bridgeAction) { 123 this(); 124 this.sid = sid; 125 this.bridgeAction = bridgeAction; 126 } 127 128 /** 129 * Creates a RTPBridge Stanza without Session ID. 130 */ 131 public RTPBridge() { 132 super(ELEMENT_NAME, NAMESPACE); 133 } 134 135 /** 136 * Get the attributes string. 137 * 138 * @return the attributes. 139 */ 140 public String getAttributes() { 141 StringBuilder str = new StringBuilder(); 142 143 if (getSid() != null) 144 str.append(" sid='").append(getSid()).append('\''); 145 146 if (getPass() != null) 147 str.append(" pass='").append(getPass()).append('\''); 148 149 if (getPortA() != -1) 150 str.append(" porta='").append(getPortA()).append('\''); 151 152 if (getPortB() != -1) 153 str.append(" portb='").append(getPortB()).append('\''); 154 155 if (getHostA() != null) 156 str.append(" hosta='").append(getHostA()).append('\''); 157 158 if (getHostB() != null) 159 str.append(" hostb='").append(getHostB()).append('\''); 160 161 return str.toString(); 162 } 163 164 /** 165 * Get the Session ID of the Stanza (usually same as Jingle Session ID). 166 * 167 * @return the session ID 168 */ 169 public String getSid() { 170 return sid; 171 } 172 173 /** 174 * Set the Session ID of the Stanza (usually same as Jingle Session ID). 175 * 176 * @param sid TODO javadoc me please 177 */ 178 public void setSid(String sid) { 179 this.sid = sid; 180 } 181 182 /** 183 * Get the Host A IP Address. 184 * 185 * @return the Host A IP Address 186 */ 187 public String getHostA() { 188 return hostA; 189 } 190 191 /** 192 * Set the Host A IP Address. 193 * 194 * @param hostA TODO javadoc me please 195 */ 196 public void setHostA(String hostA) { 197 this.hostA = hostA; 198 } 199 200 /** 201 * Get the Host B IP Address. 202 * 203 * @return the Host B IP Address 204 */ 205 public String getHostB() { 206 return hostB; 207 } 208 209 /** 210 * Set the Host B IP Address. 211 * 212 * @param hostB TODO javadoc me please 213 */ 214 public void setHostB(String hostB) { 215 this.hostB = hostB; 216 } 217 218 /** 219 * Get Side A receive port. 220 * 221 * @return the side A receive port 222 */ 223 public int getPortA() { 224 return portA; 225 } 226 227 /** 228 * Set Side A receive port. 229 * 230 * @param portA TODO javadoc me please 231 */ 232 public void setPortA(int portA) { 233 this.portA = portA; 234 } 235 236 /** 237 * Get Side B receive port. 238 * 239 * @return the side B receive port 240 */ 241 public int getPortB() { 242 return portB; 243 } 244 245 /** 246 * Set Side B receive port. 247 * 248 * @param portB TODO javadoc me please 249 */ 250 public void setPortB(int portB) { 251 this.portB = portB; 252 } 253 254 /** 255 * Get the RTP Bridge IP. 256 * 257 * @return the RTP Bridge IP 258 */ 259 public String getIp() { 260 return ip; 261 } 262 263 /** 264 * Set the RTP Bridge IP. 265 * 266 * @param ip TODO javadoc me please 267 */ 268 public void setIp(String ip) { 269 this.ip = ip; 270 } 271 272 /** 273 * Get the RTP Agent Pass. 274 * 275 * @return the RTP Agent Pass 276 */ 277 public String getPass() { 278 return pass; 279 } 280 281 /** 282 * Set the RTP Agent Pass. 283 * 284 * @param pass TODO javadoc me please 285 */ 286 public void setPass(String pass) { 287 this.pass = pass; 288 } 289 290 /** 291 * Get the name of the Candidate. 292 * 293 * @return the name of the Candidate 294 */ 295 public String getName() { 296 return name; 297 } 298 299 /** 300 * Set the name of the Candidate. 301 * 302 * @param name TODO javadoc me please 303 */ 304 public void setName(String name) { 305 this.name = name; 306 } 307 308 /** 309 * Get the Child Element XML of the Packet 310 * 311 * @return the Child Element XML of the Packet 312 */ 313 @Override 314 protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder str) { 315 str.attribute("sid", sid); 316 str.rightAngleBracket(); 317 318 if (bridgeAction.equals(BridgeAction.create)) 319 str.append("<candidate/>"); 320 else if (bridgeAction.equals(BridgeAction.change)) 321 str.append("<relay ").append(getAttributes()).append(" />"); 322 else 323 str.append("<publicip ").append(getAttributes()).append(" />"); 324 325 return str; 326 } 327 328 /** 329 * IQProvider for RTP Bridge packets. 330 * Parse receive RTPBridge stanza to a RTPBridge instance 331 * 332 * @author Thiago Rocha 333 */ 334 public static class Provider extends IQProvider<RTPBridge> { 335 336 @Override 337 public RTPBridge parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment) 338 throws XmlPullParserException, 339 IOException { 340 341 boolean done = false; 342 343 XmlPullParser.Event eventType; 344 345 if (!parser.getNamespace().equals(RTPBridge.NAMESPACE)) 346 // TODO: Should be SmackParseException. 347 throw new IOException("Not a RTP Bridge packet"); 348 349 RTPBridge iq = new RTPBridge(); 350 351 for (int i = 0; i < parser.getAttributeCount(); i++) { 352 if (parser.getAttributeName(i).equals("sid")) 353 iq.setSid(parser.getAttributeValue(i)); 354 } 355 356 // Start processing sub-elements 357 while (!done) { 358 eventType = parser.next(); 359 360 if (eventType == XmlPullParser.Event.START_ELEMENT) { 361 String elementName = parser.getName(); 362 if (elementName.equals("candidate")) { 363 for (int i = 0; i < parser.getAttributeCount(); i++) { 364 if (parser.getAttributeName(i).equals("ip")) 365 iq.setIp(parser.getAttributeValue(i)); 366 else if (parser.getAttributeName(i).equals("pass")) 367 iq.setPass(parser.getAttributeValue(i)); 368 else if (parser.getAttributeName(i).equals("name")) 369 iq.setName(parser.getAttributeValue(i)); 370 else if (parser.getAttributeName(i).equals("porta")) 371 iq.setPortA(Integer.parseInt(parser.getAttributeValue(i))); 372 else if (parser.getAttributeName(i).equals("portb")) 373 iq.setPortB(Integer.parseInt(parser.getAttributeValue(i))); 374 } 375 } 376 else if (elementName.equals("publicip")) { 377 for (int i = 0; i < parser.getAttributeCount(); i++) { 378 if (parser.getAttributeName(i).equals("ip")) 379 iq.setIp(parser.getAttributeValue(i)); 380 } 381 } 382 } 383 else if (eventType == XmlPullParser.Event.END_ELEMENT) { 384 if (parser.getName().equals(RTPBridge.ELEMENT_NAME)) { 385 done = true; 386 } 387 } 388 } 389 return iq; 390 } 391 } 392 393 /** 394 * Get a new RTPBridge Candidate from the server. 395 * If a error occurs or the server don't support RTPBridge Service, null is returned. 396 * 397 * @param connection TODO javadoc me please 398 * @param sessionID TODO javadoc me please 399 * @return the new RTPBridge 400 * @throws NotConnectedException if the XMPP connection is not connected. 401 * @throws InterruptedException if the calling thread was interrupted. 402 */ 403 public static RTPBridge getRTPBridge(XMPPConnection connection, String sessionID) throws NotConnectedException, InterruptedException { 404 405 if (!connection.isConnected()) { 406 return null; 407 } 408 409 RTPBridge rtpPacket = new RTPBridge(sessionID); 410 DomainBareJid jid; 411 try { 412 jid = JidCreate.domainBareFrom(RTPBridge.NAME + "." + connection.getXMPPServiceDomain()); 413 } catch (XmppStringprepException e) { 414 throw new AssertionError(e); 415 } 416 rtpPacket.setTo(jid); 417 418 StanzaCollector collector = connection.createStanzaCollectorAndSend(rtpPacket); 419 420 RTPBridge response = collector.nextResult(); 421 422 // Cancel the collector. 423 collector.cancel(); 424 425 return response; 426 } 427 428 /** 429 * Check if the server support RTPBridge Service. 430 * 431 * @param connection TODO javadoc me please 432 * @return true if the server supports the RTPBridge service 433 * @throws XMPPErrorException if there was an XMPP error returned. 434 * @throws NoResponseException if there was no response from the remote entity. 435 * @throws NotConnectedException if the XMPP connection is not connected. 436 * @throws InterruptedException if the calling thread was interrupted. 437 */ 438 public static boolean serviceAvailable(XMPPConnection connection) throws NoResponseException, 439 XMPPErrorException, NotConnectedException, InterruptedException { 440 441 if (!connection.isConnected()) { 442 return false; 443 } 444 445 LOGGER.fine("Service listing"); 446 447 ServiceDiscoveryManager disco = ServiceDiscoveryManager 448 .getInstanceFor(connection); 449// DiscoverItems items = disco.discoverItems(connection.getXMPPServiceDomain()); 450// Iterator iter = items.getItems(); 451// while (iter.hasNext()) { 452// DiscoverItems.Item item = (DiscoverItems.Item) iter.next(); 453// if (item.getEntityID().startsWith("rtpbridge.")) { 454// return true; 455// } 456// } 457 458 DiscoverInfo discoInfo = disco.discoverInfo(connection.getXMPPServiceDomain()); 459 for (DiscoverInfo.Identity identity : discoInfo.getIdentities()) { 460 if (identity.getName() != null && identity.getName().startsWith("rtpbridge")) { 461 return true; 462 } 463 } 464 465 return false; 466 } 467 468 /** 469 * Check if the server support RTPBridge Service. 470 * 471 * @param connection TODO javadoc me please 472 * @param sessionID the session id. 473 * @param pass the password. 474 * @param proxyCandidate the proxy candidate. 475 * @param localCandidate the local candidate. 476 * @return the RTPBridge 477 * @throws NotConnectedException if the XMPP connection is not connected. 478 * @throws InterruptedException if the calling thread was interrupted. 479 */ 480 public static RTPBridge relaySession(XMPPConnection connection, String sessionID, String pass, TransportCandidate proxyCandidate, TransportCandidate localCandidate) throws NotConnectedException, InterruptedException { 481 482 if (!connection.isConnected()) { 483 return null; 484 } 485 486 RTPBridge rtpPacket = new RTPBridge(sessionID, RTPBridge.BridgeAction.change); 487 DomainBareJid jid; 488 try { 489 jid = JidCreate.domainBareFrom(RTPBridge.NAME + "." + connection.getXMPPServiceDomain()); 490 } catch (XmppStringprepException e) { 491 throw new AssertionError(e); 492 } 493 rtpPacket.setTo(jid); 494 rtpPacket.setType(Type.set); 495 496 rtpPacket.setPass(pass); 497 rtpPacket.setPortA(localCandidate.getPort()); 498 rtpPacket.setPortB(proxyCandidate.getPort()); 499 rtpPacket.setHostA(localCandidate.getIp()); 500 rtpPacket.setHostB(proxyCandidate.getIp()); 501 502 // LOGGER.debug("Relayed to: " + candidate.getIp() + ":" + candidate.getPort()); 503 504 StanzaCollector collector = connection.createStanzaCollectorAndSend(rtpPacket); 505 506 RTPBridge response = collector.nextResult(); 507 508 // Cancel the collector. 509 collector.cancel(); 510 511 return response; 512 } 513 514 /** 515 * Get Public Address from the Server. 516 * 517 * @param xmppConnection TODO javadoc me please 518 * @return public IP String or null if not found 519 * @throws NotConnectedException if the XMPP connection is not connected. 520 * @throws InterruptedException if the calling thread was interrupted. 521 */ 522 public static String getPublicIP(XMPPConnection xmppConnection) throws NotConnectedException, InterruptedException { 523 524 if (!xmppConnection.isConnected()) { 525 return null; 526 } 527 528 RTPBridge rtpPacket = new RTPBridge(RTPBridge.BridgeAction.publicip); 529 DomainBareJid jid; 530 try { 531 jid = JidCreate.domainBareFrom(RTPBridge.NAME + "." + xmppConnection.getXMPPServiceDomain()); 532 } catch (XmppStringprepException e) { 533 throw new AssertionError(e); 534 } 535 rtpPacket.setTo(jid); 536 rtpPacket.setType(Type.set); 537 538 // LOGGER.debug("Relayed to: " + candidate.getIp() + ":" + candidate.getPort()); 539 540 StanzaCollector collector = xmppConnection.createStanzaCollectorAndSend(rtpPacket); 541 542 RTPBridge response = collector.nextResult(); 543 544 // Cancel the collector. 545 collector.cancel(); 546 547 if (response == null) return null; 548 549 if (response.getIp() == null || response.getIp().equals("")) return null; 550 551 Enumeration<NetworkInterface> ifaces = null; 552 try { 553 ifaces = NetworkInterface.getNetworkInterfaces(); 554 } 555 catch (SocketException e) { 556 LOGGER.log(Level.WARNING, "exception", e); 557 } 558 while (ifaces != null && ifaces.hasMoreElements()) { 559 560 NetworkInterface iface = ifaces.nextElement(); 561 Enumeration<InetAddress> iaddresses = iface.getInetAddresses(); 562 563 while (iaddresses.hasMoreElements()) { 564 InetAddress iaddress = iaddresses.nextElement(); 565 if (!iaddress.isLoopbackAddress()) 566 if (iaddress.getHostAddress().indexOf(response.getIp()) >= 0) 567 return null; 568 569 } 570 } 571 572 return response.getIp(); 573 } 574 575}