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