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