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.net.DatagramPacket; 021import java.net.DatagramSocket; 022import java.net.InetAddress; 023import java.net.SocketException; 024import java.net.UnknownHostException; 025import java.nio.ByteBuffer; 026import java.nio.charset.StandardCharsets; 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 if (session.getConnection().getUser().equals(session.getInitiator())) { 665 this.send = local.getBytes(StandardCharsets.UTF_8); 666 this.receive = remote.getBytes(StandardCharsets.UTF_8); 667 } else { 668 this.receive = local.getBytes(StandardCharsets.UTF_8); 669 this.send = remote.getBytes(StandardCharsets.UTF_8); 670 } 671 } 672 673 @SuppressWarnings("UnusedVariable") 674 @Override 675 public void run() { 676 try { 677 LOGGER.fine("Listening for ECHO: " + socket.getLocalAddress().getHostAddress() + ":" + socket.getLocalPort()); 678 while (true) { 679 680 DatagramPacket packet = new DatagramPacket(new byte[150], 150); 681 682 socket.receive(packet); 683 684 // LOGGER.fine("ECHO Packet Received in: " + socket.getLocalAddress().getHostAddress() + ":" + socket.getLocalPort() + " From: " + packet.getAddress().getHostAddress() + ":" + packet.getPort()); 685 686 boolean accept = false; 687 688 ByteBuffer buf = ByteBuffer.wrap(packet.getData()); 689 byte[] content = new byte[packet.getLength()]; 690 buf = buf.get(content, 0, packet.getLength()); 691 692 packet.setData(content); 693 694 for (DatagramListener listener : listeners) { 695 accept = listener.datagramReceived(packet); 696 if (accept) break; 697 } 698 699 long delay = 100 / replyTries; 700 701 String[] str = new String(packet.getData(), StandardCharsets.UTF_8).split(";"); 702 String pass = str[0]; 703 String[] address = str[1].split(":"); 704 String ip = address[0]; 705 String port = address[1]; 706 707 if (pass.equals(candidate.getPassword()) && !accept) { 708 709 byte[] cont = (password + ";" + candidate.getIp() + ":" + candidate.getPort()).getBytes(StandardCharsets.UTF_8); 710 711 packet.setData(cont); 712 packet.setLength(cont.length); 713 packet.setAddress(InetAddress.getByName(ip)); 714 packet.setPort(Integer.parseInt(port)); 715 716 for (int i = 0; i < replyTries; i++) { 717 socket.send(packet); 718 if (!enabled) break; 719 try { 720 Thread.sleep(delay); 721 } 722 catch (InterruptedException e) { 723 LOGGER.log(Level.WARNING, "exception", e); 724 } 725 } 726 } 727 } 728 } 729 catch (UnknownHostException uhe) { 730 if (enabled) { 731 } 732 } 733 catch (SocketException se) { 734 if (enabled) { 735 } 736 } 737 catch (IOException ioe) { 738 if (enabled) { 739 } 740 } 741 catch (Exception e) { 742 if (enabled) { 743 } 744 } 745 } 746 747 public void cancel() { 748 this.enabled = false; 749 socket.close(); 750 } 751 752 private void fireTestResult(TestResult testResult, TransportCandidate candidate) { 753 for (ResultListener resultListener : resultListeners) 754 resultListener.testFinished(testResult, candidate); 755 } 756 757 public void testASync(final TransportCandidate transportCandidate, final String password) { 758 759 Thread thread = new Thread(new Runnable() { 760 761 @Override 762 public void run() { 763 764 DatagramListener listener = new DatagramListener() { 765 @Override 766 public boolean datagramReceived(DatagramPacket datagramPacket) { 767 LOGGER.fine("ECHO Received to: " + candidate.getIp() + ":" + candidate.getPort() + " data: " + new String(datagramPacket.getData(), StandardCharsets.UTF_8)); 768 String[] str = new String(datagramPacket.getData(), StandardCharsets.UTF_8).split(";"); 769 String pass = str[0]; 770 String[] addr = str[1].split(":"); 771 String ip = addr[0]; 772 String pt = addr[1]; 773 774 // CHECKSTYLE:OFF 775 if (pass.equals(password) 776 && transportCandidate.getIp().indexOf(ip) != -1 777 && transportCandidate.getPort() == Integer.parseInt(pt)) { 778 // CHECKSTYLE:ON 779 LOGGER.fine("ECHO OK: " + candidate.getIp() + ":" + candidate.getPort() + " <-> " + transportCandidate.getIp() + ":" + transportCandidate.getPort()); 780 TestResult testResult = new TestResult(); 781 testResult.setResult(true); 782 ended = true; 783 fireTestResult(testResult, transportCandidate); 784 return true; 785 } 786 787 LOGGER.fine("ECHO Wrong Data: " + datagramPacket.getAddress().getHostAddress() + ":" + datagramPacket.getPort()); 788 return false; 789 } 790 }; 791 792 addListener(listener); 793 794 byte[] content = new String(password + ";" + getIp() + ":" + getPort()).getBytes(StandardCharsets.UTF_8); 795 796 DatagramPacket packet = new DatagramPacket(content, content.length); 797 798 try { 799 packet.setAddress(InetAddress.getByName(transportCandidate.getIp())); 800 } 801 catch (UnknownHostException e) { 802 LOGGER.log(Level.WARNING, "exception", e); 803 } 804 packet.setPort(transportCandidate.getPort()); 805 806 long delay = 200; 807 808 try { 809 for (int i = 0; i < tries; i++) { 810 socket.send(packet); 811 if (ended) break; 812 try { 813 Thread.sleep(delay); 814 } 815 catch (InterruptedException e) { 816 LOGGER.log(Level.WARNING, "exception", e); 817 } 818 } 819 } 820 catch (IOException e) { 821 // Do Nothing 822 } 823 824 try { 825 Thread.sleep(2000); 826 } 827 catch (InterruptedException e) { 828 LOGGER.log(Level.WARNING, "exception", e); 829 } 830 831 removeListener(listener); 832 } 833 }); 834 thread.start(); 835 } 836 837 public void addListener(DatagramListener listener) { 838 listeners.add(listener); 839 } 840 841 public void removeListener(DatagramListener listener) { 842 listeners.remove(listener); 843 } 844 845 public void addResultListener(ResultListener resultListener) { 846 resultListeners.add(resultListener); 847 } 848 849 public void removeResultListener(ResultListener resultListener) { 850 resultListeners.remove(resultListener); 851 } 852 853 } 854 855}