PageRenderTime 50ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 1ms

/src/net/java/sip/communicator/impl/netaddr/NetworkAddressManagerServiceImpl.java

https://gitlab.com/yorty.ruiz/jitsi
Java | 749 lines | 433 code | 73 blank | 243 comment | 64 complexity | 513391ffe2164768a5f153f9ac3a3461 MD5 | raw file
  1. /*
  2. * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
  3. *
  4. * Copyright @ 2015 Atlassian Pty Ltd
  5. *
  6. * Licensed under the Apache License, Version 2.0 (the "License");
  7. * you may not use this file except in compliance with the License.
  8. * You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing, software
  13. * distributed under the License is distributed on an "AS IS" BASIS,
  14. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. * See the License for the specific language governing permissions and
  16. * limitations under the License.
  17. */
  18. package net.java.sip.communicator.impl.netaddr;
  19. import java.beans.*;
  20. import java.io.*;
  21. import java.lang.reflect.*;
  22. import java.net.*;
  23. import java.text.*;
  24. import java.util.*;
  25. import net.java.sip.communicator.service.dns.*;
  26. import net.java.sip.communicator.service.netaddr.*;
  27. import net.java.sip.communicator.service.netaddr.event.*;
  28. import net.java.sip.communicator.util.*;
  29. import net.java.sip.communicator.util.Logger;
  30. import net.java.sip.communicator.util.NetworkUtils;
  31. import org.ice4j.*;
  32. import org.ice4j.ice.*;
  33. import org.ice4j.ice.harvest.*;
  34. import org.ice4j.security.*;
  35. import org.ice4j.stack.*;
  36. import org.jitsi.service.configuration.*;
  37. import org.jitsi.util.*;
  38. /**
  39. * This implementation of the Network Address Manager allows you to
  40. * intelligently retrieve the address of your localhost according to the
  41. * destinations that you will be trying to reach. It also provides an interface
  42. * to the ICE implementation in ice4j.
  43. *
  44. * @author Emil Ivov
  45. */
  46. public class NetworkAddressManagerServiceImpl
  47. implements NetworkAddressManagerService
  48. {
  49. /**
  50. * Our class logger.
  51. */
  52. private static Logger logger =
  53. Logger.getLogger(NetworkAddressManagerServiceImpl.class);
  54. /**
  55. * The socket that we use for dummy connections during selection of a local
  56. * address that has to be used when communicating with a specific location.
  57. */
  58. DatagramSocket localHostFinderSocket = null;
  59. /**
  60. * A random (unused)local port to use when trying to select a local host
  61. * address to use when sending messages to a specific destination.
  62. */
  63. private static final int RANDOM_ADDR_DISC_PORT = 55721;
  64. /**
  65. * The name of the property containing the number of binds that we should
  66. * should execute in case a port is already bound to (each retry would be on
  67. * a new random port).
  68. */
  69. public static final String BIND_RETRIES_PROPERTY_NAME
  70. = "net.java.sip.communicator.service.netaddr.BIND_RETRIES";
  71. /**
  72. * Default STUN server port.
  73. */
  74. public static final int DEFAULT_STUN_SERVER_PORT = 3478;
  75. /**
  76. * A thread which periodically scans network interfaces and reports
  77. * changes in network configuration.
  78. */
  79. private NetworkConfigurationWatcher networkConfigurationWatcher = null;
  80. /**
  81. * The service name to use when discovering TURN servers through DNS using
  82. * SRV requests as per RFC 5766.
  83. */
  84. public static final String TURN_SRV_NAME = "turn";
  85. /**
  86. * The service name to use when discovering STUN servers through DNS using
  87. * SRV requests as per RFC 5389.
  88. */
  89. public static final String STUN_SRV_NAME = "stun";
  90. /**
  91. * Initializes this network address manager service implementation.
  92. */
  93. public void start()
  94. {
  95. this.localHostFinderSocket = initRandomPortSocket();
  96. // set packet logging to ice4j stack
  97. StunStack.setPacketLogger(new Ice4jPacketLogger());
  98. }
  99. /**
  100. * Kills all threads/processes launched by this thread (if any) and
  101. * prepares it for shutdown. You may use this method as a reinitialization
  102. * technique (you'll have to call start afterwards)
  103. */
  104. public void stop()
  105. {
  106. try
  107. {
  108. if(networkConfigurationWatcher != null)
  109. networkConfigurationWatcher.stop();
  110. }
  111. finally
  112. {
  113. logger.logExit();
  114. }
  115. }
  116. /**
  117. * Returns an InetAddress instance that represents the localhost, and that
  118. * a socket can bind upon or distribute to peers as a contact address.
  119. *
  120. * @param intendedDestination the destination that we'd like to use the
  121. * localhost address with.
  122. *
  123. * @return an InetAddress instance representing the local host, and that
  124. * a socket can bind upon or distribute to peers as a contact address.
  125. */
  126. public synchronized InetAddress getLocalHost(
  127. InetAddress intendedDestination)
  128. {
  129. InetAddress localHost = null;
  130. if(logger.isTraceEnabled())
  131. {
  132. logger.trace(
  133. "Querying for a localhost address"
  134. + " for intended destination '"
  135. + intendedDestination
  136. + "'");
  137. }
  138. /* use native code (JNI) to find source address for a specific
  139. * destination address on Windows XP SP1 and over.
  140. *
  141. * For other systems, we used method based on DatagramSocket.connect
  142. * which will returns us source address. The reason why we cannot use it
  143. * on Windows is because its socket implementation returns the any
  144. * address...
  145. */
  146. String osVersion;
  147. if (OSUtils.IS_WINDOWS
  148. && !(osVersion = System.getProperty("os.version")).startsWith(
  149. "4") /* 95/98/Me/NT */
  150. && !osVersion.startsWith("5.0")) /* 2000 */
  151. {
  152. byte[] src
  153. = Win32LocalhostRetriever.getSourceForDestination(
  154. intendedDestination.getAddress());
  155. if (src == null)
  156. {
  157. logger.warn("Failed to get localhost ");
  158. }
  159. else
  160. {
  161. try
  162. {
  163. localHost = InetAddress.getByAddress(src);
  164. }
  165. catch(UnknownHostException uhe)
  166. {
  167. logger.warn("Failed to get localhost", uhe);
  168. }
  169. }
  170. }
  171. else if (OSUtils.IS_MAC)
  172. {
  173. try
  174. {
  175. localHost = BsdLocalhostRetriever
  176. .getLocalSocketAddress(new InetSocketAddress(
  177. intendedDestination, RANDOM_ADDR_DISC_PORT));
  178. }
  179. catch (IOException e)
  180. {
  181. logger.warn("Failed to get localhost", e);
  182. }
  183. }
  184. else
  185. {
  186. //no point in making sure that the localHostFinderSocket is
  187. //initialized.
  188. //better let it through a NullPointerException.
  189. localHostFinderSocket.connect(intendedDestination,
  190. RANDOM_ADDR_DISC_PORT);
  191. localHost = localHostFinderSocket.getLocalAddress();
  192. localHostFinderSocket.disconnect();
  193. }
  194. //windows socket implementations return the any address so we need to
  195. //find something else here ... InetAddress.getLocalHost seems to work
  196. //better on windows so let's hope it'll do the trick.
  197. if (localHost == null)
  198. {
  199. try
  200. {
  201. localHost = InetAddress.getLocalHost();
  202. }
  203. catch (UnknownHostException e)
  204. {
  205. logger.warn("Failed to get localhost ", e);
  206. }
  207. }
  208. if (localHost.isAnyLocalAddress())
  209. {
  210. if (logger.isTraceEnabled())
  211. {
  212. logger.trace(
  213. "Socket returned the ANY local address."
  214. + " Trying a workaround.");
  215. }
  216. try
  217. {
  218. //all that's inside the if is an ugly IPv6 hack
  219. //(good ol' IPv6 - always causing more problems than it solves.)
  220. if (intendedDestination instanceof Inet6Address)
  221. {
  222. //return the first globally routable ipv6 address we find
  223. //on the machine (and hope it's a good one)
  224. boolean done = false;
  225. Enumeration<NetworkInterface> ifaces
  226. = NetworkInterface.getNetworkInterfaces();
  227. while (!done && ifaces.hasMoreElements())
  228. {
  229. Enumeration<InetAddress> addresses
  230. = ifaces.nextElement().getInetAddresses();
  231. while (addresses.hasMoreElements())
  232. {
  233. InetAddress address = addresses.nextElement();
  234. if ((address instanceof Inet6Address)
  235. && !address.isAnyLocalAddress()
  236. && !address.isLinkLocalAddress()
  237. && !address.isLoopbackAddress()
  238. && !address.isSiteLocalAddress())
  239. {
  240. localHost = address;
  241. done = true;
  242. break;
  243. }
  244. }
  245. }
  246. }
  247. else
  248. // an IPv4 destination
  249. {
  250. // Make sure we got an IPv4 address.
  251. if (intendedDestination instanceof Inet4Address)
  252. {
  253. // return the first non-loopback interface we find.
  254. boolean done = false;
  255. Enumeration<NetworkInterface> ifaces
  256. = NetworkInterface.getNetworkInterfaces();
  257. while (!done && ifaces.hasMoreElements())
  258. {
  259. Enumeration<InetAddress> addresses
  260. = ifaces.nextElement().getInetAddresses();
  261. while (addresses.hasMoreElements())
  262. {
  263. InetAddress address = addresses.nextElement();
  264. if ((address instanceof Inet4Address)
  265. && !address.isLoopbackAddress())
  266. {
  267. localHost = address;
  268. done = true;
  269. break;
  270. }
  271. }
  272. }
  273. }
  274. }
  275. }
  276. catch (Exception e)
  277. {
  278. //sigh ... ok return 0.0.0.0
  279. logger.warn("Failed to get localhost", e);
  280. }
  281. }
  282. if (logger.isTraceEnabled())
  283. logger.trace("Returning the localhost address '" + localHost + "'");
  284. return localHost;
  285. }
  286. /**
  287. * Returns the hardware address (i.e. MAC address) of the specified
  288. * interface name.
  289. *
  290. * @param iface the <tt>NetworkInterface</tt>
  291. * @return array of bytes representing the layer 2 address or null if
  292. * interface does not exist
  293. */
  294. public byte[] getHardwareAddress(NetworkInterface iface)
  295. {
  296. String ifName = null;
  297. byte hwAddress[] = null;
  298. /* try reflection */
  299. try
  300. {
  301. Method method = iface.getClass().
  302. getMethod("getHardwareAddress");
  303. if(method != null)
  304. {
  305. hwAddress = (byte[])method.invoke(iface, new Object[]{});
  306. return hwAddress;
  307. }
  308. }
  309. catch(Exception e)
  310. {
  311. }
  312. /* maybe getHardwareAddress not available on this JVM try
  313. * with our JNI
  314. */
  315. if(OSUtils.IS_WINDOWS)
  316. {
  317. ifName = iface.getDisplayName();
  318. }
  319. else
  320. {
  321. ifName = iface.getName();
  322. }
  323. hwAddress = HardwareAddressRetriever.getHardwareAddress(ifName);
  324. return hwAddress;
  325. }
  326. /**
  327. * Tries to obtain an for the specified port.
  328. *
  329. * @param dst the destination that we'd like to use this address with.
  330. * @param port the port whose mapping we are interested in.
  331. * @return a public address corresponding to the specified port or null
  332. * if all attempts to retrieve such an address have failed.
  333. *
  334. * @throws IOException if an error occurs while creating the socket.
  335. * @throws BindException if the port is already in use.
  336. */
  337. public InetSocketAddress getPublicAddressFor(InetAddress dst, int port)
  338. throws IOException, BindException
  339. {
  340. //we'll try to bind so that we could notify the caller
  341. //if the port has been taken already.
  342. DatagramSocket bindTestSocket = new DatagramSocket(port);
  343. bindTestSocket.close();
  344. //if we're here then the port was free.
  345. return new InetSocketAddress(getLocalHost(dst), port);
  346. }
  347. /**
  348. * This method gets called when a bound property is changed.
  349. * @param evt A PropertyChangeEvent object describing the event source
  350. * and the property that has changed.
  351. */
  352. public void propertyChange(PropertyChangeEvent evt)
  353. {
  354. //there's no point in implementing this method as we have no way of
  355. //knowing whether the current property change event is the only event
  356. //we're going to get or whether another one is going to follow..
  357. //in the case of a STUN_SERVER_ADDRESS property change for example
  358. //there's no way of knowing whether a STUN_SERVER_PORT property change
  359. //will follow or not.
  360. //Reinitializaion will therefore only happen if the reinitialize()
  361. //method is called.
  362. }
  363. /**
  364. * Initializes and binds a socket that on a random port number. The method
  365. * would try to bind on a random port and retry 5 times until a free port
  366. * is found.
  367. *
  368. * @return the socket that we have initialized on a randomport number.
  369. */
  370. private DatagramSocket initRandomPortSocket()
  371. {
  372. DatagramSocket resultSocket = null;
  373. String bindRetriesStr
  374. = NetaddrActivator.getConfigurationService().getString(
  375. BIND_RETRIES_PROPERTY_NAME);
  376. int bindRetries = 5;
  377. if (bindRetriesStr != null)
  378. {
  379. try
  380. {
  381. bindRetries = Integer.parseInt(bindRetriesStr);
  382. }
  383. catch (NumberFormatException ex)
  384. {
  385. logger.error(bindRetriesStr
  386. + " does not appear to be an integer. "
  387. + "Defaulting port bind retries to " + bindRetries
  388. , ex);
  389. }
  390. }
  391. int currentlyTriedPort = NetworkUtils.getRandomPortNumber();
  392. //we'll first try to bind to a random port. if this fails we'll try
  393. //again (bindRetries times in all) until we find a free local port.
  394. for (int i = 0; i < bindRetries; i++)
  395. {
  396. try
  397. {
  398. resultSocket = new DatagramSocket(currentlyTriedPort);
  399. //we succeeded - break so that we don't try to bind again
  400. break;
  401. }
  402. catch (SocketException exc)
  403. {
  404. if (exc.getMessage().indexOf("Address already in use") == -1)
  405. {
  406. logger.fatal("An exception occurred while trying to create"
  407. + "a local host discovery socket.", exc);
  408. return null;
  409. }
  410. //port seems to be taken. try another one.
  411. if (logger.isDebugEnabled())
  412. logger.debug("Port " + currentlyTriedPort
  413. + " seems in use.");
  414. currentlyTriedPort
  415. = NetworkUtils.getRandomPortNumber();
  416. if (logger.isDebugEnabled())
  417. logger.debug("Retrying bind on port "
  418. + currentlyTriedPort);
  419. }
  420. }
  421. return resultSocket;
  422. }
  423. /**
  424. * Creates a <tt>DatagramSocket</tt> and binds it to the specified
  425. * <tt>localAddress</tt> and a port in the range specified by the
  426. * <tt>minPort</tt> and <tt>maxPort</tt> parameters. We first try to bind
  427. * the newly created socket on the <tt>preferredPort</tt> port number
  428. * (unless it is outside the <tt>[minPort, maxPort]</tt> range in which case
  429. * we first try the <tt>minPort</tt>) and then proceed incrementally upwards
  430. * until we succeed or reach the bind retries limit. If we reach the
  431. * <tt>maxPort</tt> port number before the bind retries limit, we will then
  432. * start over again at <tt>minPort</tt> and keep going until we run out of
  433. * retries.
  434. *
  435. * @param laddr the address that we'd like to bind the socket on.
  436. * @param preferredPort the port number that we should try to bind to first.
  437. * @param minPort the port number where we should first try to bind before
  438. * moving to the next one (i.e. <tt>minPort + 1</tt>)
  439. * @param maxPort the maximum port number where we should try binding
  440. * before giving up and throwinG an exception.
  441. *
  442. * @return the newly created <tt>DatagramSocket</tt>.
  443. *
  444. * @throws IllegalArgumentException if either <tt>minPort</tt> or
  445. * <tt>maxPort</tt> is not a valid port number or if <tt>minPort >
  446. * maxPort</tt>.
  447. * @throws IOException if an error occurs while the underlying resolver lib
  448. * is using sockets.
  449. * @throws BindException if we couldn't find a free port between
  450. * <tt>minPort</tt> and <tt>maxPort</tt> before reaching the maximum allowed
  451. * number of retries.
  452. */
  453. public DatagramSocket createDatagramSocket(InetAddress laddr,
  454. int preferredPort,
  455. int minPort,
  456. int maxPort)
  457. throws IllegalArgumentException,
  458. IOException,
  459. BindException
  460. {
  461. // make sure port numbers are valid
  462. if (!NetworkUtils.isValidPortNumber(minPort)
  463. || !NetworkUtils.isValidPortNumber(maxPort))
  464. {
  465. throw new IllegalArgumentException("minPort (" + minPort
  466. + ") and maxPort (" + maxPort + ") "
  467. + "should be integers between 1024 and 65535.");
  468. }
  469. // make sure minPort comes before maxPort.
  470. if (minPort > maxPort)
  471. {
  472. throw new IllegalArgumentException("minPort (" + minPort
  473. + ") should be less than or "
  474. + "equal to maxPort (" + maxPort + ")");
  475. }
  476. // if preferredPort is not in the allowed range, place it at min.
  477. if (minPort > preferredPort || preferredPort > maxPort)
  478. {
  479. throw new IllegalArgumentException("preferredPort ("+preferredPort
  480. +") must be between minPort (" + minPort
  481. + ") and maxPort (" + maxPort + ")");
  482. }
  483. ConfigurationService config = NetaddrActivator
  484. .getConfigurationService();
  485. int bindRetries = config.getInt(BIND_RETRIES_PROPERTY_NAME,
  486. BIND_RETRIES_DEFAULT_VALUE);
  487. int port = preferredPort;
  488. for (int i = 0; i < bindRetries; i++)
  489. {
  490. try
  491. {
  492. return new DatagramSocket(port, laddr);
  493. }
  494. catch (SocketException se)
  495. {
  496. if (logger.isInfoEnabled())
  497. {
  498. logger.info(
  499. "Retrying a bind because of a failure to bind to address "
  500. + laddr + " and port " + port);
  501. if (logger.isTraceEnabled())
  502. logger.trace("Since you seem, here's a stack:", se);
  503. }
  504. }
  505. port ++;
  506. if (port > maxPort)
  507. port = minPort;
  508. }
  509. throw new BindException("Could not bind to any port between "
  510. + minPort + " and " + (port -1));
  511. }
  512. /**
  513. * Adds new <tt>NetworkConfigurationChangeListener</tt> which will
  514. * be informed for network configuration changes.
  515. *
  516. * @param listener the listener.
  517. */
  518. public synchronized void addNetworkConfigurationChangeListener(
  519. NetworkConfigurationChangeListener listener)
  520. {
  521. if(networkConfigurationWatcher == null)
  522. networkConfigurationWatcher = new NetworkConfigurationWatcher();
  523. networkConfigurationWatcher
  524. .addNetworkConfigurationChangeListener(listener);
  525. }
  526. /**
  527. * Remove <tt>NetworkConfigurationChangeListener</tt>.
  528. *
  529. * @param listener the listener.
  530. */
  531. public synchronized void removeNetworkConfigurationChangeListener(
  532. NetworkConfigurationChangeListener listener)
  533. {
  534. if(networkConfigurationWatcher != null)
  535. networkConfigurationWatcher
  536. .removeNetworkConfigurationChangeListener(listener);
  537. }
  538. /**
  539. * Creates and returns an ICE agent that a protocol could use for the
  540. * negotiation of media transport addresses. One ICE agent should only be
  541. * used for a single session negotiation.
  542. *
  543. * @return the newly created ICE Agent.
  544. */
  545. public Agent createIceAgent()
  546. {
  547. return new Agent();
  548. }
  549. /**
  550. * Tries to discover a TURN or a STUN server for the specified
  551. * <tt>domainName</tt>. The method would first try to discover a TURN
  552. * server and then fall back to STUN only. In both cases we would only care
  553. * about a UDP transport.
  554. *
  555. * @param domainName the domain name that we are trying to discover a
  556. * TURN server for.
  557. * @param userName the name of the user we'd like to use when connecting to
  558. * a TURN server (we won't be using credentials in case we only have a STUN
  559. * server).
  560. * @param password the password that we'd like to try when connecting to
  561. * a TURN server (we won't be using credentials in case we only have a STUN
  562. * server).
  563. *
  564. * @return A {@link StunCandidateHarvester} corresponding to the TURN or
  565. * STUN server we discovered or <tt>null</tt> if there were no such records
  566. * for the specified <tt>domainName</tt>
  567. */
  568. public StunCandidateHarvester discoverStunServer(String domainName,
  569. byte[] userName,
  570. byte[] password)
  571. {
  572. String srvrAddress = null;
  573. int port = 0;
  574. try
  575. {
  576. SRVRecord srvRecord = NetworkUtils.getSRVRecord(
  577. TURN_SRV_NAME, Transport.UDP.toString(), domainName);
  578. if(srvRecord != null)
  579. {
  580. srvrAddress = srvRecord.getTarget();
  581. }
  582. if(srvrAddress != null)
  583. {
  584. //yay! we seem to have a TURN server, so we'll be using it for
  585. //both TURN and STUN harvesting.
  586. return new TurnCandidateHarvester(
  587. new TransportAddress(srvrAddress,
  588. srvRecord.getPort(),
  589. Transport.UDP),
  590. new LongTermCredential(userName, password));
  591. }
  592. //srvrAddres was null. try for a STUN only server.
  593. srvRecord = NetworkUtils.getSRVRecord(
  594. STUN_SRV_NAME, Transport.UDP.toString(), domainName);
  595. if(srvRecord != null)
  596. {
  597. srvrAddress = srvRecord.getTarget();
  598. port = srvRecord.getPort();
  599. }
  600. }
  601. catch (ParseException e)
  602. {
  603. logger.info(domainName + " seems to be causing parse problems", e);
  604. srvrAddress = null;
  605. }
  606. catch (DnssecException e)
  607. {
  608. logger.warn("DNSSEC validation for " + domainName
  609. + " STUN/TURN failed.", e);
  610. }
  611. if(srvrAddress != null)
  612. {
  613. return new StunCandidateHarvester(
  614. new TransportAddress(
  615. srvrAddress,
  616. port,
  617. Transport.UDP));
  618. }
  619. //srvrAddress was still null. sigh ...
  620. return null;
  621. }
  622. /**
  623. * Creates an <tt>IceMediaStrean</tt> and adds to it an RTP and and RTCP
  624. * component, which also implies running the currently installed
  625. * harvesters so that they would.
  626. *
  627. * @param rtpPort the port that we should try to bind the RTP component on
  628. * (the RTCP one would automatically go to rtpPort + 1)
  629. * @param streamName the name of the stream to create
  630. * @param agent the <tt>Agent</tt> that should create the stream.
  631. *
  632. *@return the newly created <tt>IceMediaStream</tt>.
  633. *
  634. * @throws IllegalArgumentException if <tt>rtpPort</tt> is not a valid port
  635. * number.
  636. * @throws IOException if an error occurs while the underlying resolver
  637. * is using sockets.
  638. * @throws BindException if we couldn't find a free port between within the
  639. * default number of retries.
  640. */
  641. public IceMediaStream createIceStream( int rtpPort,
  642. String streamName,
  643. Agent agent)
  644. throws IllegalArgumentException,
  645. IOException,
  646. BindException
  647. {
  648. return createIceStream(2, rtpPort, streamName, agent);
  649. }
  650. /**
  651. * {@inheritDoc}
  652. */
  653. public IceMediaStream createIceStream( int numComponents,
  654. int portBase,
  655. String streamName,
  656. Agent agent)
  657. throws IllegalArgumentException,
  658. IOException,
  659. BindException
  660. {
  661. if(numComponents < 1 || numComponents > 2)
  662. throw new IllegalArgumentException(
  663. "Invalid numComponents value: " + numComponents);
  664. IceMediaStream stream = agent.createMediaStream(streamName);
  665. agent.createComponent(
  666. stream, Transport.UDP,
  667. portBase, portBase, portBase + 100);
  668. if(numComponents > 1)
  669. {
  670. agent.createComponent(
  671. stream, Transport.UDP,
  672. portBase + 1, portBase + 1, portBase + 101);
  673. }
  674. return stream;
  675. }
  676. }