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