Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

BaseSocketAwareParser.java 9.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. /*
  2. * Copyright (c) 2006-2017 DMDirc Developers
  3. *
  4. * Permission is hereby granted, free of charge, to any person obtaining a copy
  5. * of this software and associated documentation files (the "Software"), to deal
  6. * in the Software without restriction, including without limitation the rights
  7. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. * copies of the Software, and to permit persons to whom the Software is
  9. * furnished to do so, subject to the following conditions:
  10. *
  11. * The above copyright notice and this permission notice shall be included in
  12. * all copies or substantial portions of the Software.
  13. *
  14. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  20. * SOFTWARE.
  21. */
  22. package com.dmdirc.parser.common;
  23. import java.io.IOException;
  24. import java.net.Inet4Address;
  25. import java.net.Inet6Address;
  26. import java.net.InetAddress;
  27. import java.net.InetSocketAddress;
  28. import java.net.Proxy;
  29. import java.net.Socket;
  30. import java.net.URI;
  31. import java.net.URISyntaxException;
  32. import javax.net.SocketFactory;
  33. /**
  34. * A base parser which can construct a SocketFactory given socket-related
  35. * options.
  36. */
  37. public abstract class BaseSocketAwareParser extends BaseParser {
  38. /** The socket that was most recently created by this parser. */
  39. private Socket socket;
  40. /** The local port that this parser's *most recently created* socket bound to. */
  41. private int localPort = -1;
  42. /** The connection timeout, in milliseconds. */
  43. private int connectTimeout = 5000;
  44. /**
  45. * Creates a new base parser for the specified URI.
  46. *
  47. * @param uri The URI this parser will connect to.
  48. */
  49. public BaseSocketAwareParser(final URI uri) {
  50. super(uri);
  51. }
  52. @Override
  53. public int getLocalPort() {
  54. if (localPort == -1 && socket != null) {
  55. // Try to update the local port from the socket, as it may have
  56. // bound since the last time we tried
  57. localPort = socket.getLocalPort();
  58. }
  59. return localPort;
  60. }
  61. @Override
  62. public void shutdown() {
  63. try {
  64. if (socket != null) {
  65. socket.close();
  66. }
  67. } catch (IOException e) {
  68. }
  69. super.shutdown();
  70. }
  71. /**
  72. * Gets the current connection timeout.
  73. *
  74. * @return The connection timeout, in milliseconds.
  75. */
  76. public int getConnectTimeout() {
  77. return connectTimeout;
  78. }
  79. /**
  80. * Sets the connection timeout.
  81. *
  82. * @param connectTimeout The connection timeout, in milliseconds.
  83. */
  84. public void setConnectTimeout(final int connectTimeout) {
  85. this.connectTimeout = connectTimeout;
  86. }
  87. /**
  88. * Creates a socket factory that can be used by this parser.
  89. *
  90. * @return An appropriately configured socket factory
  91. */
  92. protected SocketFactory getSocketFactory() {
  93. return new BindingSocketFactory();
  94. }
  95. /**
  96. * Stores a reference to a recently created socket.
  97. *
  98. * @param socket The newly created socket.
  99. */
  100. private void setSocket(final Socket socket) {
  101. this.socket = socket;
  102. this.localPort = socket.getLocalPort();
  103. }
  104. /**
  105. * Allows subclasses to handle socket-related debug messages.
  106. *
  107. * @param message The debug message.
  108. */
  109. protected void handleSocketDebug(final String message) {
  110. // Do nothing by default
  111. }
  112. /**
  113. * Creates and binds a new socket to the IP address specified by the parser. If the target
  114. * address is an IPv6 address, the parser's {@link #getBindIPv6()} value will be used;
  115. * otherwise, the standard {@link #getBindIP()} will be used.
  116. *
  117. * @param host The host to connect to.
  118. * @param port The port to connect on.
  119. * @return A new socket bound appropriately and connected.
  120. */
  121. @SuppressWarnings({"resource", "SocketOpenedButNotSafelyClosed"})
  122. private Socket boundSocket(final InetAddress host, final int port) throws IOException {
  123. final Socket socket = new Socket();
  124. final String bindIp = host instanceof Inet6Address ? getBindIPv6() : getBindIP();
  125. if (bindIp != null && !bindIp.isEmpty()) {
  126. try {
  127. socket.bind(new InetSocketAddress(InetAddress.getByName(bindIp), 0));
  128. } catch (IOException ex) {
  129. // Bind failed; continue trying to connect anyway.
  130. handleSocketDebug("Binding failed: " + ex.getMessage());
  131. }
  132. }
  133. setSocket(socket);
  134. socket.connect(new InetSocketAddress(host, port), connectTimeout);
  135. return socket;
  136. }
  137. /**
  138. * Creates a new socket via a proxy.
  139. *
  140. * @param host The host to connect to.
  141. * @param port The port to connect on.
  142. * @return A new proxy-using socket.
  143. */
  144. @SuppressWarnings({"resource", "SocketOpenedButNotSafelyClosed"})
  145. private Socket proxiedSocket(final InetAddress host, final int port) throws IOException {
  146. final URI proxy = getProxy();
  147. final Proxy.Type proxyType = Proxy.Type.valueOf(proxy.getScheme().toUpperCase());
  148. final String proxyHost = proxy.getHost();
  149. final int proxyPort = checkPort(proxy.getPort(), "Proxy");
  150. final Socket socket = new Socket(
  151. new Proxy(proxyType, new InetSocketAddress(proxyHost, proxyPort)));
  152. try {
  153. final ProxyAuthenticator ia = ProxyAuthenticator.getProxyAuthenticator();
  154. final URI serverUri = new URI(null, null, host.getHostName(), port,
  155. null, null, null);
  156. try {
  157. ia.getSemaphore().acquireUninterruptibly();
  158. ia.addAuthentication(serverUri, proxy);
  159. socket.connect(new InetSocketAddress(host, port), connectTimeout);
  160. } finally {
  161. ia.removeAuthentication(serverUri, proxy);
  162. ia.getSemaphore().release();
  163. }
  164. } catch (URISyntaxException ex) {
  165. // Won't happen.
  166. }
  167. return socket;
  168. }
  169. /**
  170. * Utility method to ensure a port is in the correct range. This stops networking classes
  171. * throwing obscure exceptions.
  172. *
  173. * @param port The port to test.
  174. * @param description Description of the port for error messages.
  175. * @return The given port.
  176. * @throws IOException If the port is out of range.
  177. */
  178. private int checkPort(final int port, final String description) throws IOException {
  179. if (port > 65535 || port <= 0) {
  180. throw new IOException(description + " port (" + port + ") is invalid.");
  181. }
  182. return port;
  183. }
  184. /**
  185. * Finds an address to connect to of the specified type.
  186. *
  187. * @param host The host to resolve.
  188. * @param type The type of address to look for
  189. * @return An address of the specified type, or {@code null} if none exists.
  190. * @throws IOException if there is an error.
  191. */
  192. private InetAddress getAddressForHost(final String host,
  193. final Class<? extends InetAddress> type) throws IOException {
  194. if (getProxy() != null) {
  195. // If we have a proxy, let it worry about all this instead.
  196. //
  197. // 1) We have no idea what sort of connectivity the proxy has
  198. // 2) If we do this here, then any DNS-based geo-balancing is
  199. // going to be based on our location, not the proxy.
  200. return InetAddress.getByName(host);
  201. }
  202. for (InetAddress i : InetAddress.getAllByName(host)) {
  203. if (type.isAssignableFrom(i.getClass())) {
  204. return i;
  205. }
  206. }
  207. return null;
  208. }
  209. private class BindingSocketFactory extends SocketFactory {
  210. @Override
  211. public Socket createSocket(final String host, final int port) throws IOException {
  212. // Attempt to connect to IPv6 addresses first.
  213. IOException sixException = null;
  214. try {
  215. final InetAddress sixAddress = getAddressForHost(host, Inet6Address.class);
  216. if (sixAddress != null) {
  217. return createSocket(sixAddress, port);
  218. }
  219. } catch (IOException ex) {
  220. handleSocketDebug("Unable to use IPv6: " + ex.getMessage());
  221. sixException = ex;
  222. }
  223. // If there isn't an IPv6 address, or it has failed, fall back to IPv4.
  224. final InetAddress fourAddress = getAddressForHost(host, Inet4Address.class);
  225. if (fourAddress == null) {
  226. throw new IOException("No IPv4 address and IPv6 failed", sixException);
  227. }
  228. return createSocket(fourAddress, port);
  229. }
  230. @Override
  231. @SuppressWarnings("resource")
  232. public Socket createSocket(final InetAddress host, final int port) throws IOException {
  233. checkPort(port, "server");
  234. return getProxy() == null ? boundSocket(host, port) : proxiedSocket(host, port);
  235. }
  236. @Override
  237. public Socket createSocket(final String host, final int port,
  238. final InetAddress localHost, final int localPort) throws IOException {
  239. return createSocket(host, port);
  240. }
  241. @Override
  242. public Socket createSocket(final InetAddress address, final int port,
  243. final InetAddress localAddress, final int localPort) throws IOException {
  244. return createSocket(address, port);
  245. }
  246. }
  247. }