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.IqData; 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, IqData iqData, 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 * @throws XMPPErrorException if there was an XMPP error returned. 403 * @throws NoResponseException if there was no response from the remote entity. 404 */ 405 public static RTPBridge getRTPBridge(XMPPConnection connection, String sessionID) throws NotConnectedException, InterruptedException, NoResponseException, XMPPErrorException { 406 407 if (!connection.isConnected()) { 408 return null; 409 } 410 411 RTPBridge rtpPacket = new RTPBridge(sessionID); 412 DomainBareJid jid; 413 try { 414 jid = JidCreate.domainBareFrom(RTPBridge.NAME + "." + connection.getXMPPServiceDomain()); 415 } catch (XmppStringprepException e) { 416 throw new AssertionError(e); 417 } 418 rtpPacket.setTo(jid); 419 420 RTPBridge response = connection.sendIqRequestAndWaitForResponse(rtpPacket); 421 422 return response; 423 } 424 425 /** 426 * Check if the server support RTPBridge Service. 427 * 428 * @param connection TODO javadoc me please 429 * @return true if the server supports the RTPBridge service 430 * @throws XMPPErrorException if there was an XMPP error returned. 431 * @throws NoResponseException if there was no response from the remote entity. 432 * @throws NotConnectedException if the XMPP connection is not connected. 433 * @throws InterruptedException if the calling thread was interrupted. 434 */ 435 public static boolean serviceAvailable(XMPPConnection connection) throws NoResponseException, 436 XMPPErrorException, NotConnectedException, InterruptedException { 437 438 if (!connection.isConnected()) { 439 return false; 440 } 441 442 LOGGER.fine("Service listing"); 443 444 ServiceDiscoveryManager disco = ServiceDiscoveryManager 445 .getInstanceFor(connection); 446// DiscoverItems items = disco.discoverItems(connection.getXMPPServiceDomain()); 447// Iterator iter = items.getItems(); 448// while (iter.hasNext()) { 449// DiscoverItems.Item item = (DiscoverItems.Item) iter.next(); 450// if (item.getEntityID().startsWith("rtpbridge.")) { 451// return true; 452// } 453// } 454 455 DiscoverInfo discoInfo = disco.discoverInfo(connection.getXMPPServiceDomain()); 456 for (DiscoverInfo.Identity identity : discoInfo.getIdentities()) { 457 if (identity.getName() != null && identity.getName().startsWith("rtpbridge")) { 458 return true; 459 } 460 } 461 462 return false; 463 } 464 465 /** 466 * Check if the server support RTPBridge Service. 467 * 468 * @param connection TODO javadoc me please 469 * @param sessionID the session id. 470 * @param pass the password. 471 * @param proxyCandidate the proxy candidate. 472 * @param localCandidate the local candidate. 473 * @return the RTPBridge 474 * @throws NotConnectedException if the XMPP connection is not connected. 475 * @throws InterruptedException if the calling thread was interrupted. 476 * @throws XMPPErrorException if there was an XMPP error returned. 477 * @throws NoResponseException if there was no response from the remote entity. 478 */ 479 public static RTPBridge relaySession(XMPPConnection connection, String sessionID, String pass, TransportCandidate proxyCandidate, TransportCandidate localCandidate) throws NotConnectedException, InterruptedException, NoResponseException, XMPPErrorException { 480 481 if (!connection.isConnected()) { 482 return null; 483 } 484 485 RTPBridge rtpPacket = new RTPBridge(sessionID, RTPBridge.BridgeAction.change); 486 DomainBareJid jid; 487 try { 488 jid = JidCreate.domainBareFrom(RTPBridge.NAME + "." + connection.getXMPPServiceDomain()); 489 } catch (XmppStringprepException e) { 490 throw new AssertionError(e); 491 } 492 rtpPacket.setTo(jid); 493 rtpPacket.setType(Type.set); 494 495 rtpPacket.setPass(pass); 496 rtpPacket.setPortA(localCandidate.getPort()); 497 rtpPacket.setPortB(proxyCandidate.getPort()); 498 rtpPacket.setHostA(localCandidate.getIp()); 499 rtpPacket.setHostB(proxyCandidate.getIp()); 500 501 // LOGGER.debug("Relayed to: " + candidate.getIp() + ":" + candidate.getPort()); 502 503 RTPBridge response = connection.sendIqRequestAndWaitForResponse(rtpPacket); 504 505 return response; 506 } 507 508 /** 509 * Get Public Address from the Server. 510 * 511 * @param xmppConnection TODO javadoc me please 512 * @return public IP String or null if not found 513 * @throws NotConnectedException if the XMPP connection is not connected. 514 * @throws InterruptedException if the calling thread was interrupted. 515 * @throws XMPPErrorException if there was an XMPP error returned. 516 * @throws NoResponseException if there was no response from the remote entity. 517 */ 518 public static String getPublicIP(XMPPConnection xmppConnection) throws NotConnectedException, InterruptedException, NoResponseException, XMPPErrorException { 519 520 if (!xmppConnection.isConnected()) { 521 return null; 522 } 523 524 RTPBridge rtpPacket = new RTPBridge(RTPBridge.BridgeAction.publicip); 525 DomainBareJid jid; 526 try { 527 jid = JidCreate.domainBareFrom(RTPBridge.NAME + "." + xmppConnection.getXMPPServiceDomain()); 528 } catch (XmppStringprepException e) { 529 throw new AssertionError(e); 530 } 531 rtpPacket.setTo(jid); 532 rtpPacket.setType(Type.set); 533 534 // LOGGER.debug("Relayed to: " + candidate.getIp() + ":" + candidate.getPort()); 535 536 RTPBridge response = xmppConnection.sendIqRequestAndWaitForResponse(rtpPacket); 537 538 if (response == null) return null; 539 540 if (response.getIp() == null || response.getIp().equals("")) return null; 541 542 Enumeration<NetworkInterface> ifaces = null; 543 try { 544 ifaces = NetworkInterface.getNetworkInterfaces(); 545 } 546 catch (SocketException e) { 547 LOGGER.log(Level.WARNING, "exception", e); 548 } 549 while (ifaces != null && ifaces.hasMoreElements()) { 550 551 NetworkInterface iface = ifaces.nextElement(); 552 Enumeration<InetAddress> iaddresses = iface.getInetAddresses(); 553 554 while (iaddresses.hasMoreElements()) { 555 InetAddress iaddress = iaddresses.nextElement(); 556 if (!iaddress.isLoopbackAddress()) 557 if (iaddress.getHostAddress().indexOf(response.getIp()) >= 0) 558 return null; 559 560 } 561 } 562 563 return response.getIp(); 564 } 565 566}