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 @Override 301 public boolean equals(Object obj) { 302 if (this == obj) { 303 return true; 304 } 305 if (obj == null) { 306 return false; 307 } 308 if (!(obj instanceof TransportCandidate)) { 309 return false; 310 } 311 final TransportCandidate other = (TransportCandidate) obj; 312 if (generation != other.generation) { 313 return false; 314 } 315 if (getIp() == null) { 316 if (other.getIp() != null) { 317 return false; 318 } 319 } else if (!getIp().equals(other.getIp())) { 320 return false; 321 } 322 323 if (getPort() != other.getPort()) { 324 return false; 325 } 326 327 if (getName() == null) { 328 if (other.getName() != null) { 329 return false; 330 } 331 } else if (!getName().equals(other.getName())) { 332 return false; 333 } 334 if (getPort() != other.getPort()) { 335 return false; 336 } 337 return true; 338 } 339 340 341 /** 342 * Check if a transport candidate is usable. The transport resolver should 343 * check if the transport candidate the other endpoint has provided is 344 * usable. 345 * 346 * Subclasses should provide better methods if they can... 347 * 348 * @param localCandidates a list of local candidates. 349 */ 350 @SuppressWarnings("UnusedVariable") 351 public void check(final List<TransportCandidate> localCandidates) { 352 // TODO candidate is being checked trigger 353 // candidatesChecking.add(cand); 354 355 Thread checkThread = new Thread(new Runnable() { 356 @Override 357 public void run() { 358 boolean isUsable; 359 360 361 try { 362 // CHECKSTYLE:OFF 363 InetAddress candAddress = InetAddress.getByName(getIp()); 364 // CHECKSTYLE:ON 365 isUsable = true;//candAddress.isReachable(TransportResolver.CHECK_TIMEOUT); 366 } 367 catch (Exception e) { 368 isUsable = false; 369 } 370 triggerCandidateChecked(isUsable); 371 372 // TODO candidate is being checked trigger 373 // candidatesChecking.remove(cand); 374 } 375 }, "Transport candidate check"); 376 377 checkThread.setName("Transport candidate test"); 378 checkThread.start(); 379 } 380 381 /** 382 * Trigger a new candidate checked event. 383 * 384 * @param result The result. 385 */ 386 void triggerCandidateChecked(boolean result) { 387 388 for (TransportResolverListener.Checker trl : getListenersList()) { 389 trl.candidateChecked(this, result); 390 } 391 } 392 393 /** 394 * Get the list of listeners. 395 * 396 * @return the list of listeners 397 */ 398 public List<TransportResolverListener.Checker> getListenersList() { 399 synchronized (listeners) { 400 return new ArrayList<>(listeners); 401 } 402 } 403 404 /** 405 * Add a transport resolver listener. 406 * 407 * @param li The transport resolver listener to be added. 408 */ 409 public void addListener(TransportResolverListener.Checker li) { 410 synchronized (listeners) { 411 listeners.add(li); 412 } 413 } 414 415 /** 416 * Fixed transport candidate. 417 */ 418 public static class Fixed extends TransportCandidate { 419 420 public Fixed() { 421 super(); 422 } 423 424 /** 425 * Constructor with IP address and port. 426 * 427 * @param ip The IP address. 428 * @param port The port number. 429 */ 430 public Fixed(String ip, int port) { 431 super(ip, port); 432 } 433 434 /** 435 * Constructor with IP address and port. 436 * 437 * @param ip The IP address. 438 * @param port The port number. 439 * @param generation The generation 440 */ 441 public Fixed(String ip, int port, int generation) { 442 super(ip, port, generation); 443 } 444 } 445 446 /** 447 * Type-safe enum for the transportElement protocol. 448 */ 449 public static final class Protocol { 450 451 public static final Protocol UDP = new Protocol("udp"); 452 453 public static final Protocol TCP = new Protocol("tcp"); 454 455 public static final Protocol TCPACT = new Protocol("tcp-act"); 456 457 public static final Protocol TCPPASS = new Protocol("tcp-pass"); 458 459 public static final Protocol SSLTCP = new Protocol("ssltcp"); 460 461 private String value; 462 463 public Protocol(String value) { 464 this.value = value; 465 } 466 467 @Override 468 public String toString() { 469 return value; 470 } 471 472 /** 473 * Returns the Protocol constant associated with the String value. 474 * 475 * @param value the input String. 476 * @return the protocol. 477 */ 478 public static Protocol fromString(String value) { 479 if (value == null) { 480 return UDP; 481 } 482 value = value.toLowerCase(Locale.US); 483 if (value.equals("udp")) { 484 return UDP; 485 } else if (value.equals("tcp")) { 486 return TCP; 487 } else if (value.equals("tcp-act")) { 488 return TCPACT; 489 } else if (value.equals("tcp-pass")) { 490 return TCPPASS; 491 } else if (value.equals("ssltcp")) { 492 return SSLTCP; 493 } else { 494 return UDP; 495 } 496 } 497 498 @Override 499 public boolean equals(Object obj) { 500 if (this == obj) { 501 return true; 502 } 503 if (obj == null) { 504 return false; 505 } 506 if (getClass() != obj.getClass()) { 507 return false; 508 } 509 final Protocol other = (Protocol) obj; 510 if (value == null) { 511 if (other.value != null) { 512 return false; 513 } 514 } else if (!value.equals(other.value)) { 515 return false; 516 } 517 return true; 518 } 519 520 @Override 521 public int hashCode() { 522 if (value == null) { 523 return -1; 524 } 525 return value.hashCode(); 526 } 527 528 /** 529 * Return true if the protocol is not valid. 530 * 531 * @return true if the protocol is null 532 */ 533 public boolean isNull() { 534 if (value == null) { 535 return true; 536 } else if (value.length() == 0) { 537 return true; 538 } else { 539 return false; 540 } 541 } 542 } 543 544 /** 545 * Type-safe enum for the transportElement channel. 546 */ 547 public static class Channel { 548 549 public static final Channel MYRTPVOICE = new Channel("myrtpvoice"); 550 551 public static final Channel MYRTCPVOICE = new Channel("myrtcpvoice"); 552 553 private String value; 554 555 public Channel(String value) { 556 this.value = value; 557 } 558 559 @Override 560 public String toString() { 561 return value; 562 } 563 564 /** 565 * Returns the MediaChannel constant associated with the String value. 566 * 567 * @param value the input String. 568 * @return the channel. 569 */ 570 public static Channel fromString(String value) { 571 if (value == null) { 572 return MYRTPVOICE; 573 } 574 value = value.toLowerCase(Locale.US); 575 if (value.equals("myrtpvoice")) { 576 return MYRTPVOICE; 577 } else if (value.equals("tcp")) { 578 return MYRTCPVOICE; 579 } else { 580 return MYRTPVOICE; 581 } 582 } 583 584 @Override 585 public boolean equals(Object obj) { 586 if (this == obj) { 587 return true; 588 } 589 if (obj == null) { 590 return false; 591 } 592 if (!(obj instanceof Channel)) { 593 return false; 594 } 595 final Channel other = (Channel) obj; 596 if (value == null) { 597 if (other.value != null) { 598 return false; 599 } 600 } else if (!value.equals(other.value)) { 601 return false; 602 } 603 return true; 604 } 605 606 @Override 607 public int hashCode() { 608 if (value == null) { 609 return -1; 610 } 611 return value.hashCode(); 612 } 613 614 /** 615 * Return true if the channel is not valid. 616 * 617 * @return true if the channel is null 618 */ 619 public boolean isNull() { 620 if (value == null) { 621 return true; 622 } else if (value.length() == 0) { 623 return true; 624 } else { 625 return false; 626 } 627 } 628 } 629 630 public class CandidateEcho implements Runnable { 631 632 DatagramSocket socket = null; 633 Jid localUser; 634 Jid remoteUser; 635 String id = null; 636 byte[] send = null; 637 byte[] receive = null; 638 DatagramPacket sendStanza = null; 639 List<DatagramListener> listeners = new ArrayList<>(); 640 List<ResultListener> resultListeners = new ArrayList<>(); 641 boolean enabled = true; 642 boolean ended = false; 643 long replyTries = 2; 644 long tries = 10; 645 TransportCandidate candidate = null; 646 647 public CandidateEcho(TransportCandidate candidate, JingleSession session) throws UnknownHostException, SocketException { 648 this.socket = new DatagramSocket(candidate.getPort(), InetAddress.getByName(candidate.getLocalIp())); 649 this.localUser = session.getInitiator(); 650 this.remoteUser = session.getResponder(); 651 this.id = session.getSid(); 652 this.candidate = candidate; 653 654 int keySplitIndex = (int) Math.ceil(((float) id.length()) / 2); 655 656 String local = id.substring(0, keySplitIndex) + ";" + localUser; 657 String remote = id.substring(keySplitIndex) + ";" + remoteUser; 658 659 if (session.getConnection().getUser().equals(session.getInitiator())) { 660 this.send = local.getBytes(StandardCharsets.UTF_8); 661 this.receive = remote.getBytes(StandardCharsets.UTF_8); 662 } else { 663 this.receive = local.getBytes(StandardCharsets.UTF_8); 664 this.send = remote.getBytes(StandardCharsets.UTF_8); 665 } 666 } 667 668 @SuppressWarnings("UnusedVariable") 669 @Override 670 public void run() { 671 try { 672 LOGGER.fine("Listening for ECHO: " + socket.getLocalAddress().getHostAddress() + ":" + socket.getLocalPort()); 673 while (true) { 674 675 DatagramPacket packet = new DatagramPacket(new byte[150], 150); 676 677 socket.receive(packet); 678 679 // LOGGER.fine("ECHO Packet Received in: " + socket.getLocalAddress().getHostAddress() + ":" + socket.getLocalPort() + " From: " + packet.getAddress().getHostAddress() + ":" + packet.getPort()); 680 681 boolean accept = false; 682 683 ByteBuffer buf = ByteBuffer.wrap(packet.getData()); 684 byte[] content = new byte[packet.getLength()]; 685 buf = buf.get(content, 0, packet.getLength()); 686 687 packet.setData(content); 688 689 for (DatagramListener listener : listeners) { 690 accept = listener.datagramReceived(packet); 691 if (accept) break; 692 } 693 694 long delay = 100 / replyTries; 695 696 String[] str = new String(packet.getData(), StandardCharsets.UTF_8).split(";"); 697 String pass = str[0]; 698 String[] address = str[1].split(":"); 699 String ip = address[0]; 700 String port = address[1]; 701 702 if (pass.equals(candidate.getPassword()) && !accept) { 703 704 byte[] cont = (password + ";" + candidate.getIp() + ":" + candidate.getPort()).getBytes(StandardCharsets.UTF_8); 705 706 packet.setData(cont); 707 packet.setLength(cont.length); 708 packet.setAddress(InetAddress.getByName(ip)); 709 packet.setPort(Integer.parseInt(port)); 710 711 for (int i = 0; i < replyTries; i++) { 712 socket.send(packet); 713 if (!enabled) break; 714 try { 715 Thread.sleep(delay); 716 } 717 catch (InterruptedException e) { 718 LOGGER.log(Level.WARNING, "exception", e); 719 } 720 } 721 } 722 } 723 } 724 catch (UnknownHostException uhe) { 725 if (enabled) { 726 } 727 } 728 catch (SocketException se) { 729 if (enabled) { 730 } 731 } 732 catch (IOException ioe) { 733 if (enabled) { 734 } 735 } 736 catch (Exception e) { 737 if (enabled) { 738 } 739 } 740 } 741 742 public void cancel() { 743 this.enabled = false; 744 socket.close(); 745 } 746 747 private void fireTestResult(TestResult testResult, TransportCandidate candidate) { 748 for (ResultListener resultListener : resultListeners) 749 resultListener.testFinished(testResult, candidate); 750 } 751 752 public void testASync(final TransportCandidate transportCandidate, final String password) { 753 754 Thread thread = new Thread(new Runnable() { 755 756 @Override 757 public void run() { 758 759 DatagramListener listener = new DatagramListener() { 760 @Override 761 public boolean datagramReceived(DatagramPacket datagramPacket) { 762 LOGGER.fine("ECHO Received to: " + candidate.getIp() + ":" + candidate.getPort() + " data: " + new String(datagramPacket.getData(), StandardCharsets.UTF_8)); 763 String[] str = new String(datagramPacket.getData(), StandardCharsets.UTF_8).split(";"); 764 String pass = str[0]; 765 String[] addr = str[1].split(":"); 766 String ip = addr[0]; 767 String pt = addr[1]; 768 769 // CHECKSTYLE:OFF 770 if (pass.equals(password) 771 && transportCandidate.getIp().indexOf(ip) != -1 772 && transportCandidate.getPort() == Integer.parseInt(pt)) { 773 // CHECKSTYLE:ON 774 LOGGER.fine("ECHO OK: " + candidate.getIp() + ":" + candidate.getPort() + " <-> " + transportCandidate.getIp() + ":" + transportCandidate.getPort()); 775 TestResult testResult = new TestResult(); 776 testResult.setResult(true); 777 ended = true; 778 fireTestResult(testResult, transportCandidate); 779 return true; 780 } 781 782 LOGGER.fine("ECHO Wrong Data: " + datagramPacket.getAddress().getHostAddress() + ":" + datagramPacket.getPort()); 783 return false; 784 } 785 }; 786 787 addListener(listener); 788 789 byte[] content = new String(password + ";" + getIp() + ":" + getPort()).getBytes(StandardCharsets.UTF_8); 790 791 DatagramPacket packet = new DatagramPacket(content, content.length); 792 793 try { 794 packet.setAddress(InetAddress.getByName(transportCandidate.getIp())); 795 } 796 catch (UnknownHostException e) { 797 LOGGER.log(Level.WARNING, "exception", e); 798 } 799 packet.setPort(transportCandidate.getPort()); 800 801 long delay = 200; 802 803 try { 804 for (int i = 0; i < tries; i++) { 805 socket.send(packet); 806 if (ended) break; 807 try { 808 Thread.sleep(delay); 809 } 810 catch (InterruptedException e) { 811 LOGGER.log(Level.WARNING, "exception", e); 812 } 813 } 814 } 815 catch (IOException e) { 816 // Do Nothing 817 } 818 819 try { 820 Thread.sleep(2000); 821 } 822 catch (InterruptedException e) { 823 LOGGER.log(Level.WARNING, "exception", e); 824 } 825 826 removeListener(listener); 827 } 828 }); 829 thread.start(); 830 } 831 832 public void addListener(DatagramListener listener) { 833 listeners.add(listener); 834 } 835 836 public void removeListener(DatagramListener listener) { 837 listeners.remove(listener); 838 } 839 840 public void addResultListener(ResultListener resultListener) { 841 resultListeners.add(resultListener); 842 } 843 844 public void removeResultListener(ResultListener resultListener) { 845 resultListeners.remove(resultListener); 846 } 847 848 } 849 850}