001/** 002 * 003 * Copyright the original author or authors 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 */ 017package org.jivesoftware.smackx.jingleold.nat; 018 019import java.io.IOException; 020import java.io.UnsupportedEncodingException; 021import java.net.DatagramPacket; 022import java.net.DatagramSocket; 023import java.net.InetAddress; 024import java.net.SocketException; 025import java.net.UnknownHostException; 026import java.nio.ByteBuffer; 027import java.util.ArrayList; 028import java.util.List; 029import java.util.Locale; 030import java.util.logging.Level; 031import java.util.logging.Logger; 032 033import org.jivesoftware.smack.XMPPConnection; 034 035import org.jivesoftware.smackx.jingleold.JingleSession; 036 037import org.jxmpp.jid.Jid; 038 039/** 040 * Transport candidate. 041 * 042 * A candidate represents the possible transport for data interchange between 043 * the two endpoints. 044 * 045 * @author Thiago Camargo 046 * @author Alvaro Saurin 047 */ 048@SuppressWarnings("EqualsHashCode") 049public abstract class TransportCandidate { 050 051 private static final Logger LOGGER = Logger.getLogger(TransportCandidate.class.getName()); 052 053 private String name; 054 055 private String ip; // IP address 056 057 private int port; // Port to use, or 0 for any port 058 059 private String localIp; 060 061 private int generation; 062 063 protected String password; 064 065 private String sessionId; 066 067 private XMPPConnection connection; 068 069 private TransportCandidate symmetric; 070 071 private CandidateEcho candidateEcho = null; 072 073 private Thread echoThread = null; 074 075 // Listeners for events 076 private final List<TransportResolverListener.Checker> listeners = new ArrayList<>(); 077 078 public void addCandidateEcho(JingleSession session) throws SocketException, UnknownHostException { 079 candidateEcho = new CandidateEcho(this, session); 080 echoThread = new Thread(candidateEcho); 081 echoThread.start(); 082 } 083 084 public void removeCandidateEcho() { 085 if (candidateEcho != null) 086 candidateEcho.cancel(); 087 candidateEcho = null; 088 echoThread = null; 089 } 090 091 public CandidateEcho getCandidateEcho() { 092 return candidateEcho; 093 } 094 095 public String getIp() { 096 return ip; 097 } 098 099 /** 100 * Set the IP address. 101 * 102 * @param ip the IP address 103 */ 104 public void setIp(String ip) { 105 this.ip = ip; 106 } 107 108 /** 109 * Get local IP to bind to this candidate. 110 * 111 * @return the local IP 112 */ 113 public String getLocalIp() { 114 return localIp == null ? ip : localIp; 115 } 116 117 /** 118 * Set local IP to bind to this candidate. 119 * 120 * @param localIp TODO javadoc me please 121 */ 122 public void setLocalIp(String localIp) { 123 this.localIp = localIp; 124 } 125 126 /** 127 * Get the symmetric candidate for this candidate if it exists. 128 * 129 * @return the symmetric candidate 130 */ 131 public TransportCandidate getSymmetric() { 132 return symmetric; 133 } 134 135 /** 136 * Set the symmetric candidate for this candidate. 137 * 138 * @param symmetric TODO javadoc me please 139 */ 140 public void setSymmetric(TransportCandidate symmetric) { 141 this.symmetric = symmetric; 142 } 143 144 /** 145 * Get the password used by ICE or relayed candidate. 146 * 147 * @return a password 148 */ 149 public String getPassword() { 150 return password; 151 } 152 153 /** 154 * Set the password used by ICE or relayed candidate. 155 * 156 * @param password a password 157 */ 158 public void setPassword(String password) { 159 this.password = password; 160 } 161 162 /** 163 * Get the XMPPConnection use to send or receive this candidate. 164 * 165 * @return the connection 166 */ 167 public XMPPConnection getConnection() { 168 return connection; 169 } 170 171 /** 172 * Set the XMPPConnection use to send or receive this candidate. 173 * 174 * @param connection TODO javadoc me please 175 */ 176 public void setConnection(XMPPConnection connection) { 177 this.connection = connection; 178 } 179 180 /** 181 * Get the jingle's sessionId that is using this candidate. 182 * 183 * @return the session ID 184 */ 185 public String getSessionId() { 186 return sessionId; 187 } 188 189 /** 190 * Set the jingle's sessionId that is using this candidate. 191 * 192 * @param sessionId TODO javadoc me please 193 */ 194 public void setSessionId(String sessionId) { 195 this.sessionId = sessionId; 196 } 197 198 /** 199 * Empty constructor. 200 */ 201 public TransportCandidate() { 202 this(null, 0, 0); 203 } 204 205 /** 206 * Constructor with IP address and port. 207 * 208 * @param ip The IP address. 209 * @param port The port number. 210 */ 211 public TransportCandidate(String ip, int port) { 212 this(ip, port, 0); 213 } 214 215 /** 216 * Constructor with IP address and port. 217 * 218 * @param ip The IP address. 219 * @param port The port number. 220 * @param generation The generation 221 */ 222 public TransportCandidate(String ip, int port, int generation) { 223 this.ip = ip; 224 this.port = port; 225 this.generation = generation; 226 } 227 228 /** 229 * Return true if the candidate is not valid. 230 * 231 * @return true if the candidate is null. 232 */ 233 public boolean isNull() { 234 if (ip == null) { 235 return true; 236 } else if (ip.length() == 0) { 237 return true; 238 } else if (port < 0) { 239 return true; 240 } else { 241 return false; 242 } 243 } 244 245 /** 246 * Get the port, or 0 for any port. 247 * 248 * @return the port or 0 249 */ 250 public int getPort() { 251 return port; 252 } 253 254 /** 255 * Set the port, using 0 for any port. 256 * 257 * @param port the port 258 */ 259 public void setPort(int port) { 260 this.port = port; 261 } 262 263 /** 264 * Get the generation for a transportElement definition. 265 * 266 * @return the generation 267 */ 268 public int getGeneration() { 269 return generation; 270 } 271 272 /** 273 * Set the generation for a transportElement definition. 274 * 275 * @param generation the generation number 276 */ 277 public void setGeneration(int generation) { 278 this.generation = generation; 279 } 280 281 /** 282 * Get the name used for identifying this transportElement method (optional). 283 * 284 * @return a name used for identifying this transportElement (ie, 285 * "myrtpvoice1") 286 */ 287 public String getName() { 288 return name; 289 } 290 291 /** 292 * Set a name for identifying this transportElement. 293 * 294 * @param name the name used for the transportElement 295 */ 296 public void setName(String name) { 297 this.name = name; 298 } 299 300 /* 301 * (non-Javadoc) 302 * 303 * @see java.lang.Object#equals(java.lang.Object) 304 */ 305 @Override 306 public boolean equals(Object obj) { 307 if (this == obj) { 308 return true; 309 } 310 if (obj == null) { 311 return false; 312 } 313 if (!(obj instanceof TransportCandidate)) { 314 return false; 315 } 316 final TransportCandidate other = (TransportCandidate) obj; 317 if (generation != other.generation) { 318 return false; 319 } 320 if (getIp() == null) { 321 if (other.getIp() != null) { 322 return false; 323 } 324 } else if (!getIp().equals(other.getIp())) { 325 return false; 326 } 327 328 if (getPort() != other.getPort()) { 329 return false; 330 } 331 332 if (getName() == null) { 333 if (other.getName() != null) { 334 return false; 335 } 336 } else if (!getName().equals(other.getName())) { 337 return false; 338 } 339 if (getPort() != other.getPort()) { 340 return false; 341 } 342 return true; 343 } 344 345 346 /** 347 * Check if a transport candidate is usable. The transport resolver should 348 * check if the transport candidate the other endpoint has provided is 349 * usable. 350 * 351 * Subclasses should provide better methods if they can... 352 * 353 * @param localCandidates a list of local candidates. 354 */ 355 @SuppressWarnings("UnusedVariable") 356 public void check(final List<TransportCandidate> localCandidates) { 357 // TODO candidate is being checked trigger 358 // candidatesChecking.add(cand); 359 360 Thread checkThread = new Thread(new Runnable() { 361 @Override 362 public void run() { 363 boolean isUsable; 364 365 366 try { 367 // CHECKSTYLE:OFF 368 InetAddress candAddress = InetAddress.getByName(getIp()); 369 // CHECKSTYLE:ON 370 isUsable = true;//candAddress.isReachable(TransportResolver.CHECK_TIMEOUT); 371 } 372 catch (Exception e) { 373 isUsable = false; 374 } 375 triggerCandidateChecked(isUsable); 376 377 // TODO candidate is being checked trigger 378 // candidatesChecking.remove(cand); 379 } 380 }, "Transport candidate check"); 381 382 checkThread.setName("Transport candidate test"); 383 checkThread.start(); 384 } 385 386 /** 387 * Trigger a new candidate checked event. 388 * 389 * @param result The result. 390 */ 391 void triggerCandidateChecked(boolean result) { 392 393 for (TransportResolverListener.Checker trl : getListenersList()) { 394 trl.candidateChecked(this, result); 395 } 396 } 397 398 /** 399 * Get the list of listeners. 400 * 401 * @return the list of listeners 402 */ 403 public List<TransportResolverListener.Checker> getListenersList() { 404 synchronized (listeners) { 405 return new ArrayList<>(listeners); 406 } 407 } 408 409 /** 410 * Add a transport resolver listener. 411 * 412 * @param li The transport resolver listener to be added. 413 */ 414 public void addListener(TransportResolverListener.Checker li) { 415 synchronized (listeners) { 416 listeners.add(li); 417 } 418 } 419 420 /** 421 * Fixed transport candidate. 422 */ 423 public static class Fixed extends TransportCandidate { 424 425 public Fixed() { 426 super(); 427 } 428 429 /** 430 * Constructor with IP address and port. 431 * 432 * @param ip The IP address. 433 * @param port The port number. 434 */ 435 public Fixed(String ip, int port) { 436 super(ip, port); 437 } 438 439 /** 440 * Constructor with IP address and port. 441 * 442 * @param ip The IP address. 443 * @param port The port number. 444 * @param generation The generation 445 */ 446 public Fixed(String ip, int port, int generation) { 447 super(ip, port, generation); 448 } 449 } 450 451 /** 452 * Type-safe enum for the transportElement protocol. 453 */ 454 public static final class Protocol { 455 456 public static final Protocol UDP = new Protocol("udp"); 457 458 public static final Protocol TCP = new Protocol("tcp"); 459 460 public static final Protocol TCPACT = new Protocol("tcp-act"); 461 462 public static final Protocol TCPPASS = new Protocol("tcp-pass"); 463 464 public static final Protocol SSLTCP = new Protocol("ssltcp"); 465 466 private String value; 467 468 public Protocol(String value) { 469 this.value = value; 470 } 471 472 @Override 473 public String toString() { 474 return value; 475 } 476 477 /** 478 * Returns the Protocol constant associated with the String value. 479 * 480 * @param value the input String. 481 * @return the protocol. 482 */ 483 public static Protocol fromString(String value) { 484 if (value == null) { 485 return UDP; 486 } 487 value = value.toLowerCase(Locale.US); 488 if (value.equals("udp")) { 489 return UDP; 490 } else if (value.equals("tcp")) { 491 return TCP; 492 } else if (value.equals("tcp-act")) { 493 return TCPACT; 494 } else if (value.equals("tcp-pass")) { 495 return TCPPASS; 496 } else if (value.equals("ssltcp")) { 497 return SSLTCP; 498 } else { 499 return UDP; 500 } 501 } 502 503 @Override 504 public boolean equals(Object obj) { 505 if (this == obj) { 506 return true; 507 } 508 if (obj == null) { 509 return false; 510 } 511 if (getClass() != obj.getClass()) { 512 return false; 513 } 514 final Protocol other = (Protocol) obj; 515 if (value == null) { 516 if (other.value != null) { 517 return false; 518 } 519 } else if (!value.equals(other.value)) { 520 return false; 521 } 522 return true; 523 } 524 525 @Override 526 public int hashCode() { 527 if (value == null) { 528 return -1; 529 } 530 return value.hashCode(); 531 } 532 533 /** 534 * Return true if the protocol is not valid. 535 * 536 * @return true if the protocol is null 537 */ 538 public boolean isNull() { 539 if (value == null) { 540 return true; 541 } else if (value.length() == 0) { 542 return true; 543 } else { 544 return false; 545 } 546 } 547 } 548 549 /** 550 * Type-safe enum for the transportElement channel. 551 */ 552 public static class Channel { 553 554 public static final Channel MYRTPVOICE = new Channel("myrtpvoice"); 555 556 public static final Channel MYRTCPVOICE = new Channel("myrtcpvoice"); 557 558 private String value; 559 560 public Channel(String value) { 561 this.value = value; 562 } 563 564 @Override 565 public String toString() { 566 return value; 567 } 568 569 /** 570 * Returns the MediaChannel constant associated with the String value. 571 * 572 * @param value the input String. 573 * @return the channel. 574 */ 575 public static Channel fromString(String value) { 576 if (value == null) { 577 return MYRTPVOICE; 578 } 579 value = value.toLowerCase(Locale.US); 580 if (value.equals("myrtpvoice")) { 581 return MYRTPVOICE; 582 } else if (value.equals("tcp")) { 583 return MYRTCPVOICE; 584 } else { 585 return MYRTPVOICE; 586 } 587 } 588 589 @Override 590 public boolean equals(Object obj) { 591 if (this == obj) { 592 return true; 593 } 594 if (obj == null) { 595 return false; 596 } 597 if (!(obj instanceof Channel)) { 598 return false; 599 } 600 final Channel other = (Channel) obj; 601 if (value == null) { 602 if (other.value != null) { 603 return false; 604 } 605 } else if (!value.equals(other.value)) { 606 return false; 607 } 608 return true; 609 } 610 611 @Override 612 public int hashCode() { 613 if (value == null) { 614 return -1; 615 } 616 return value.hashCode(); 617 } 618 619 /** 620 * Return true if the channel is not valid. 621 * 622 * @return true if the channel is null 623 */ 624 public boolean isNull() { 625 if (value == null) { 626 return true; 627 } else if (value.length() == 0) { 628 return true; 629 } else { 630 return false; 631 } 632 } 633 } 634 635 public class CandidateEcho implements Runnable { 636 637 DatagramSocket socket = null; 638 Jid localUser; 639 Jid remoteUser; 640 String id = null; 641 byte[] send = null; 642 byte[] receive = null; 643 DatagramPacket sendStanza = null; 644 List<DatagramListener> listeners = new ArrayList<>(); 645 List<ResultListener> resultListeners = new ArrayList<>(); 646 boolean enabled = true; 647 boolean ended = false; 648 long replyTries = 2; 649 long tries = 10; 650 TransportCandidate candidate = null; 651 652 public CandidateEcho(TransportCandidate candidate, JingleSession session) throws UnknownHostException, SocketException { 653 this.socket = new DatagramSocket(candidate.getPort(), InetAddress.getByName(candidate.getLocalIp())); 654 this.localUser = session.getInitiator(); 655 this.remoteUser = session.getResponder(); 656 this.id = session.getSid(); 657 this.candidate = candidate; 658 659 int keySplitIndex = (int) Math.ceil(((float) id.length()) / 2); 660 661 String local = id.substring(0, keySplitIndex) + ";" + localUser; 662 String remote = id.substring(keySplitIndex) + ";" + remoteUser; 663 664 try { 665 if (session.getConnection().getUser().equals(session.getInitiator())) { 666 667 this.send = local.getBytes("UTF-8"); 668 this.receive = remote.getBytes("UTF-8"); 669 } else { 670 this.receive = local.getBytes("UTF-8"); 671 this.send = remote.getBytes("UTF-8"); 672 } 673 } 674 catch (UnsupportedEncodingException e) { 675 LOGGER.log(Level.WARNING, "exception", e); 676 } 677 678 679 } 680 681 @SuppressWarnings("UnusedVariable") 682 @Override 683 public void run() { 684 try { 685 LOGGER.fine("Listening for ECHO: " + socket.getLocalAddress().getHostAddress() + ":" + socket.getLocalPort()); 686 while (true) { 687 688 DatagramPacket packet = new DatagramPacket(new byte[150], 150); 689 690 socket.receive(packet); 691 692 // LOGGER.fine("ECHO Packet Received in: " + socket.getLocalAddress().getHostAddress() + ":" + socket.getLocalPort() + " From: " + packet.getAddress().getHostAddress() + ":" + packet.getPort()); 693 694 boolean accept = false; 695 696 ByteBuffer buf = ByteBuffer.wrap(packet.getData()); 697 byte[] content = new byte[packet.getLength()]; 698 buf = buf.get(content, 0, packet.getLength()); 699 700 packet.setData(content); 701 702 for (DatagramListener listener : listeners) { 703 accept = listener.datagramReceived(packet); 704 if (accept) break; 705 } 706 707 long delay = 100 / replyTries; 708 709 String[] str = new String(packet.getData(), "UTF-8").split(";"); 710 String pass = str[0]; 711 String[] address = str[1].split(":"); 712 String ip = address[0]; 713 String port = address[1]; 714 715 if (pass.equals(candidate.getPassword()) && !accept) { 716 717 byte[] cont = null; 718 try { 719 cont = (password + ";" + candidate.getIp() + ":" + candidate.getPort()).getBytes("UTF-8"); 720 } 721 catch (UnsupportedEncodingException e) { 722 LOGGER.log(Level.WARNING, "exception", e); 723 } 724 725 packet.setData(cont); 726 packet.setLength(cont.length); 727 packet.setAddress(InetAddress.getByName(ip)); 728 packet.setPort(Integer.parseInt(port)); 729 730 for (int i = 0; i < replyTries; i++) { 731 socket.send(packet); 732 if (!enabled) break; 733 try { 734 Thread.sleep(delay); 735 } 736 catch (InterruptedException e) { 737 LOGGER.log(Level.WARNING, "exception", e); 738 } 739 } 740 } 741 } 742 } 743 catch (UnknownHostException uhe) { 744 if (enabled) { 745 } 746 } 747 catch (SocketException se) { 748 if (enabled) { 749 } 750 } 751 catch (IOException ioe) { 752 if (enabled) { 753 } 754 } 755 catch (Exception e) { 756 if (enabled) { 757 } 758 } 759 } 760 761 public void cancel() { 762 this.enabled = false; 763 socket.close(); 764 } 765 766 private void fireTestResult(TestResult testResult, TransportCandidate candidate) { 767 for (ResultListener resultListener : resultListeners) 768 resultListener.testFinished(testResult, candidate); 769 } 770 771 public void testASync(final TransportCandidate transportCandidate, final String password) { 772 773 Thread thread = new Thread(new Runnable() { 774 775 @Override 776 public void run() { 777 778 DatagramListener listener = new DatagramListener() { 779 @Override 780 public boolean datagramReceived(DatagramPacket datagramPacket) { 781 782 try { 783 LOGGER.fine("ECHO Received to: " + candidate.getIp() + ":" + candidate.getPort() + " data: " + new String(datagramPacket.getData(), "UTF-8")); 784 String[] str = new String(datagramPacket.getData(), "UTF-8").split(";"); 785 String pass = str[0]; 786 String[] addr = str[1].split(":"); 787 String ip = addr[0]; 788 String pt = addr[1]; 789 790 // CHECKSTYLE:OFF 791 if (pass.equals(password) 792 && transportCandidate.getIp().indexOf(ip) != -1 793 && transportCandidate.getPort() == Integer.parseInt(pt)) { 794 // CHECKSTYLE:ON 795 LOGGER.fine("ECHO OK: " + candidate.getIp() + ":" + candidate.getPort() + " <-> " + transportCandidate.getIp() + ":" + transportCandidate.getPort()); 796 TestResult testResult = new TestResult(); 797 testResult.setResult(true); 798 ended = true; 799 fireTestResult(testResult, transportCandidate); 800 return true; 801 } 802 803 } 804 catch (UnsupportedEncodingException e) { 805 LOGGER.log(Level.WARNING, "exception", e); 806 } 807 808 LOGGER.fine("ECHO Wrong Data: " + datagramPacket.getAddress().getHostAddress() + ":" + datagramPacket.getPort()); 809 return false; 810 } 811 }; 812 813 addListener(listener); 814 815 byte[] content = null; 816 try { 817 content = new String(password + ";" + getIp() + ":" + getPort()).getBytes("UTF-8"); 818 } 819 catch (UnsupportedEncodingException e) { 820 LOGGER.log(Level.WARNING, "exception", e); 821 } 822 823 DatagramPacket packet = new DatagramPacket(content, content.length); 824 825 try { 826 packet.setAddress(InetAddress.getByName(transportCandidate.getIp())); 827 } 828 catch (UnknownHostException e) { 829 LOGGER.log(Level.WARNING, "exception", e); 830 } 831 packet.setPort(transportCandidate.getPort()); 832 833 long delay = 200; 834 835 try { 836 for (int i = 0; i < tries; i++) { 837 socket.send(packet); 838 if (ended) break; 839 try { 840 Thread.sleep(delay); 841 } 842 catch (InterruptedException e) { 843 LOGGER.log(Level.WARNING, "exception", e); 844 } 845 } 846 } 847 catch (IOException e) { 848 // Do Nothing 849 } 850 851 try { 852 Thread.sleep(2000); 853 } 854 catch (InterruptedException e) { 855 LOGGER.log(Level.WARNING, "exception", e); 856 } 857 858 removeListener(listener); 859 } 860 }); 861 thread.start(); 862 } 863 864 public void addListener(DatagramListener listener) { 865 listeners.add(listener); 866 } 867 868 public void removeListener(DatagramListener listener) { 869 listeners.remove(listener); 870 } 871 872 public void addResultListener(ResultListener resultListener) { 873 resultListeners.add(resultListener); 874 } 875 876 public void removeResultListener(ResultListener resultListener) { 877 resultListeners.remove(resultListener); 878 } 879 880 } 881 882}