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