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