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 String elementName; 345 346 if (!parser.getNamespace().equals(RTPBridge.NAMESPACE)) 347 // TODO: Should be SmackParseException. 348 throw new IOException("Not a RTP Bridge packet"); 349 350 RTPBridge iq = new RTPBridge(); 351 352 for (int i = 0; i < parser.getAttributeCount(); i++) { 353 if (parser.getAttributeName(i).equals("sid")) 354 iq.setSid(parser.getAttributeValue(i)); 355 } 356 357 // Start processing sub-elements 358 while (!done) { 359 eventType = parser.next(); 360 elementName = parser.getName(); 361 362 if (eventType == XmlPullParser.Event.START_ELEMENT) { 363 if (elementName.equals("candidate")) { 364 for (int i = 0; i < parser.getAttributeCount(); i++) { 365 if (parser.getAttributeName(i).equals("ip")) 366 iq.setIp(parser.getAttributeValue(i)); 367 else if (parser.getAttributeName(i).equals("pass")) 368 iq.setPass(parser.getAttributeValue(i)); 369 else if (parser.getAttributeName(i).equals("name")) 370 iq.setName(parser.getAttributeValue(i)); 371 else if (parser.getAttributeName(i).equals("porta")) 372 iq.setPortA(Integer.parseInt(parser.getAttributeValue(i))); 373 else if (parser.getAttributeName(i).equals("portb")) 374 iq.setPortB(Integer.parseInt(parser.getAttributeValue(i))); 375 } 376 } 377 else if (elementName.equals("publicip")) { 378 for (int i = 0; i < parser.getAttributeCount(); i++) { 379 if (parser.getAttributeName(i).equals("ip")) 380 iq.setIp(parser.getAttributeValue(i)); 381 } 382 } 383 } 384 else if (eventType == XmlPullParser.Event.END_ELEMENT) { 385 if (parser.getName().equals(RTPBridge.ELEMENT_NAME)) { 386 done = true; 387 } 388 } 389 } 390 return iq; 391 } 392 } 393 394 /** 395 * Get a new RTPBridge Candidate from the server. 396 * If a error occurs or the server don't support RTPBridge Service, null is returned. 397 * 398 * @param connection TODO javadoc me please 399 * @param sessionID TODO javadoc me please 400 * @return the new RTPBridge 401 * @throws NotConnectedException if the XMPP connection is not connected. 402 * @throws InterruptedException if the calling thread was interrupted. 403 */ 404 public static RTPBridge getRTPBridge(XMPPConnection connection, String sessionID) throws NotConnectedException, InterruptedException { 405 406 if (!connection.isConnected()) { 407 return null; 408 } 409 410 RTPBridge rtpPacket = new RTPBridge(sessionID); 411 DomainBareJid jid; 412 try { 413 jid = JidCreate.domainBareFrom(RTPBridge.NAME + "." + connection.getXMPPServiceDomain()); 414 } catch (XmppStringprepException e) { 415 throw new AssertionError(e); 416 } 417 rtpPacket.setTo(jid); 418 419 StanzaCollector collector = connection.createStanzaCollectorAndSend(rtpPacket); 420 421 RTPBridge response = collector.nextResult(); 422 423 // Cancel the collector. 424 collector.cancel(); 425 426 return response; 427 } 428 429 /** 430 * Check if the server support RTPBridge Service. 431 * 432 * @param connection TODO javadoc me please 433 * @return true if the server supports the RTPBridge service 434 * @throws XMPPErrorException if there was an XMPP error returned. 435 * @throws NoResponseException if there was no response from the remote entity. 436 * @throws NotConnectedException if the XMPP connection is not connected. 437 * @throws InterruptedException if the calling thread was interrupted. 438 */ 439 public static boolean serviceAvailable(XMPPConnection connection) throws NoResponseException, 440 XMPPErrorException, NotConnectedException, InterruptedException { 441 442 if (!connection.isConnected()) { 443 return false; 444 } 445 446 LOGGER.fine("Service listing"); 447 448 ServiceDiscoveryManager disco = ServiceDiscoveryManager 449 .getInstanceFor(connection); 450// DiscoverItems items = disco.discoverItems(connection.getXMPPServiceDomain()); 451// Iterator iter = items.getItems(); 452// while (iter.hasNext()) { 453// DiscoverItems.Item item = (DiscoverItems.Item) iter.next(); 454// if (item.getEntityID().startsWith("rtpbridge.")) { 455// return true; 456// } 457// } 458 459 DiscoverInfo discoInfo = disco.discoverInfo(connection.getXMPPServiceDomain()); 460 for (DiscoverInfo.Identity identity : discoInfo.getIdentities()) { 461 if (identity.getName() != null && identity.getName().startsWith("rtpbridge")) { 462 return true; 463 } 464 } 465 466 return false; 467 } 468 469 /** 470 * Check if the server support RTPBridge Service. 471 * 472 * @param connection TODO javadoc me please 473 * @param sessionID the session id. 474 * @param pass the password. 475 * @param proxyCandidate the proxy candidate. 476 * @param localCandidate the local candidate. 477 * @return the RTPBridge 478 * @throws NotConnectedException if the XMPP connection is not connected. 479 * @throws InterruptedException if the calling thread was interrupted. 480 */ 481 public static RTPBridge relaySession(XMPPConnection connection, String sessionID, String pass, TransportCandidate proxyCandidate, TransportCandidate localCandidate) throws NotConnectedException, InterruptedException { 482 483 if (!connection.isConnected()) { 484 return null; 485 } 486 487 RTPBridge rtpPacket = new RTPBridge(sessionID, RTPBridge.BridgeAction.change); 488 DomainBareJid jid; 489 try { 490 jid = JidCreate.domainBareFrom(RTPBridge.NAME + "." + connection.getXMPPServiceDomain()); 491 } catch (XmppStringprepException e) { 492 throw new AssertionError(e); 493 } 494 rtpPacket.setTo(jid); 495 rtpPacket.setType(Type.set); 496 497 rtpPacket.setPass(pass); 498 rtpPacket.setPortA(localCandidate.getPort()); 499 rtpPacket.setPortB(proxyCandidate.getPort()); 500 rtpPacket.setHostA(localCandidate.getIp()); 501 rtpPacket.setHostB(proxyCandidate.getIp()); 502 503 // LOGGER.debug("Relayed to: " + candidate.getIp() + ":" + candidate.getPort()); 504 505 StanzaCollector collector = connection.createStanzaCollectorAndSend(rtpPacket); 506 507 RTPBridge response = collector.nextResult(); 508 509 // Cancel the collector. 510 collector.cancel(); 511 512 return response; 513 } 514 515 /** 516 * Get Public Address from the Server. 517 * 518 * @param xmppConnection TODO javadoc me please 519 * @return public IP String or null if not found 520 * @throws NotConnectedException if the XMPP connection is not connected. 521 * @throws InterruptedException if the calling thread was interrupted. 522 */ 523 public static String getPublicIP(XMPPConnection xmppConnection) throws NotConnectedException, InterruptedException { 524 525 if (!xmppConnection.isConnected()) { 526 return null; 527 } 528 529 RTPBridge rtpPacket = new RTPBridge(RTPBridge.BridgeAction.publicip); 530 DomainBareJid jid; 531 try { 532 jid = JidCreate.domainBareFrom(RTPBridge.NAME + "." + xmppConnection.getXMPPServiceDomain()); 533 } catch (XmppStringprepException e) { 534 throw new AssertionError(e); 535 } 536 rtpPacket.setTo(jid); 537 rtpPacket.setType(Type.set); 538 539 // LOGGER.debug("Relayed to: " + candidate.getIp() + ":" + candidate.getPort()); 540 541 StanzaCollector collector = xmppConnection.createStanzaCollectorAndSend(rtpPacket); 542 543 RTPBridge response = collector.nextResult(); 544 545 // Cancel the collector. 546 collector.cancel(); 547 548 if (response == null) return null; 549 550 if (response.getIp() == null || response.getIp().equals("")) return null; 551 552 Enumeration<NetworkInterface> ifaces = null; 553 try { 554 ifaces = NetworkInterface.getNetworkInterfaces(); 555 } 556 catch (SocketException e) { 557 LOGGER.log(Level.WARNING, "exception", e); 558 } 559 while (ifaces != null && ifaces.hasMoreElements()) { 560 561 NetworkInterface iface = ifaces.nextElement(); 562 Enumeration<InetAddress> iaddresses = iface.getInetAddresses(); 563 564 while (iaddresses.hasMoreElements()) { 565 InetAddress iaddress = iaddresses.nextElement(); 566 if (!iaddress.isLoopbackAddress()) 567 if (iaddress.getHostAddress().indexOf(response.getIp()) >= 0) 568 return null; 569 570 } 571 } 572 573 return response.getIp(); 574 } 575 576}