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