You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

BaseSocketAwareParser.java 9.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  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. /**
  62. * Gets the current connection timeout.
  63. *
  64. * @return The connection timeout, in milliseconds.
  65. */
  66. public int getConnectTimeout() {
  67. return connectTimeout;
  68. }
  69. /**
  70. * Sets the connection timeout.
  71. *
  72. * @param connectTimeout The connection timeout, in milliseconds.
  73. */
  74. public void setConnectTimeout(final int connectTimeout) {
  75. this.connectTimeout = connectTimeout;
  76. }
  77. /**
  78. * Creates a socket factory that can be used by this parser.
  79. *
  80. * @return An appropriately configured socket factory
  81. */
  82. protected SocketFactory getSocketFactory() {
  83. return new BindingSocketFactory();
  84. }
  85. /**
  86. * Stores a reference to a recently created socket.
  87. *
  88. * @param socket The newly created socket.
  89. */
  90. private void setSocket(final Socket socket) {
  91. this.socket = socket;
  92. this.localPort = socket.getLocalPort();
  93. }
  94. /**
  95. * Allows subclasses to handle socket-related debug messages.
  96. *
  97. * @param message The debug message.
  98. */
  99. protected void handleSocketDebug(final String message) {
  100. // Do nothing by default
  101. }
  102. /**
  103. * Creates and binds a new socket to the IP address specified by the parser. If the target
  104. * address is an IPv6 address, the parser's {@link #getBindIPv6()} value will be used;
  105. * otherwise, the standard {@link #getBindIP()} will be used.
  106. *
  107. * @param host The host to connect to.
  108. * @param port The port to connect on.
  109. * @return A new socket bound appropriately and connected.
  110. */
  111. @SuppressWarnings({"resource", "SocketOpenedButNotSafelyClosed"})
  112. private Socket boundSocket(final InetAddress host, final int port) throws IOException {
  113. final Socket socket = new Socket();
  114. final String bindIp = host instanceof Inet6Address ? getBindIPv6() : getBindIP();
  115. if (bindIp != null && !bindIp.isEmpty()) {
  116. try {
  117. socket.bind(new InetSocketAddress(InetAddress.getByName(bindIp), 0));
  118. } catch (IOException ex) {
  119. // Bind failed; continue trying to connect anyway.
  120. handleSocketDebug("Binding failed: " + ex.getMessage());
  121. }
  122. }
  123. setSocket(socket);
  124. socket.connect(new InetSocketAddress(host, port), connectTimeout);
  125. return socket;
  126. }
  127. /**
  128. * Creates a new socket via a proxy.
  129. *
  130. * @param host The host to connect to.
  131. * @param port The port to connect on.
  132. * @return A new proxy-using socket.
  133. */
  134. @SuppressWarnings({"resource", "SocketOpenedButNotSafelyClosed"})
  135. private Socket proxiedSocket(final InetAddress host, final int port) throws IOException {
  136. final URI proxy = getProxy();
  137. final Proxy.Type proxyType = Proxy.Type.valueOf(proxy.getScheme().toUpperCase());
  138. final String proxyHost = proxy.getHost();
  139. final int proxyPort = checkPort(proxy.getPort(), "Proxy");
  140. final Socket socket = new Socket(
  141. new Proxy(proxyType, new InetSocketAddress(proxyHost, proxyPort)));
  142. try {
  143. final ProxyAuthenticator ia = ProxyAuthenticator.getProxyAuthenticator();
  144. final URI serverUri = new URI(null, null, host.getHostName(), port,
  145. null, null, null);
  146. try {
  147. ia.getSemaphore().acquireUninterruptibly();
  148. ia.addAuthentication(serverUri, proxy);
  149. socket.connect(new InetSocketAddress(host, port), connectTimeout);
  150. } finally {
  151. ia.removeAuthentication(serverUri, proxy);
  152. ia.getSemaphore().release();
  153. }
  154. } catch (URISyntaxException ex) {
  155. // Won't happen.
  156. }
  157. return socket;
  158. }
  159. /**
  160. * Utility method to ensure a port is in the correct range. This stops networking classes
  161. * throwing obscure exceptions.
  162. *
  163. * @param port The port to test.
  164. * @param description Description of the port for error messages.
  165. * @return The given port.
  166. * @throws IOException If the port is out of range.
  167. */
  168. private int checkPort(final int port, final String description) throws IOException {
  169. if (port > 65535 || port <= 0) {
  170. throw new IOException(description + " port (" + port + ") is invalid.");
  171. }
  172. return port;
  173. }
  174. /**
  175. * Finds an address to connect to of the specified type.
  176. *
  177. * @param host The host to resolve.
  178. * @param type The type of address to look for
  179. * @return An address of the specified type, or {@code null} if none exists.
  180. * @throws IOException if there is an error.
  181. */
  182. private InetAddress getAddressForHost(final String host,
  183. final Class<? extends InetAddress> type) throws IOException {
  184. if (getProxy() != null) {
  185. // If we have a proxy, let it worry about all this instead.
  186. //
  187. // 1) We have no idea what sort of connectivity the proxy has
  188. // 2) If we do this here, then any DNS-based geo-balancing is
  189. // going to be based on our location, not the proxy.
  190. return InetAddress.getByName(host);
  191. }
  192. for (InetAddress i : InetAddress.getAllByName(host)) {
  193. if (type.isAssignableFrom(i.getClass())) {
  194. return i;
  195. }
  196. }
  197. return null;
  198. }
  199. private class BindingSocketFactory extends SocketFactory {
  200. @Override
  201. public Socket createSocket(final String host, final int port) throws IOException {
  202. // Attempt to connect to IPv6 addresses first.
  203. IOException sixException = null;
  204. try {
  205. final InetAddress sixAddress = getAddressForHost(host, Inet6Address.class);
  206. if (sixAddress != null) {
  207. return createSocket(sixAddress, port);
  208. }
  209. } catch (IOException ex) {
  210. handleSocketDebug("Unable to use IPv6: " + ex.getMessage());
  211. sixException = ex;
  212. }
  213. // If there isn't an IPv6 address, or it has failed, fall back to IPv4.
  214. final InetAddress fourAddress = getAddressForHost(host, Inet4Address.class);
  215. if (fourAddress == null) {
  216. throw new IOException("No IPv4 address and IPv6 failed", sixException);
  217. }
  218. return createSocket(fourAddress, port);
  219. }
  220. @Override
  221. @SuppressWarnings("resource")
  222. public Socket createSocket(final InetAddress host, final int port) throws IOException {
  223. checkPort(port, "server");
  224. return getProxy() == null ? boundSocket(host, port) : proxiedSocket(host, port);
  225. }
  226. @Override
  227. public Socket createSocket(final String host, final int port,
  228. final InetAddress localHost, final int localPort) throws IOException {
  229. return createSocket(host, port);
  230. }
  231. @Override
  232. public Socket createSocket(final InetAddress address, final int port,
  233. final InetAddress localAddress, final int localPort) throws IOException {
  234. return createSocket(address, port);
  235. }
  236. }
  237. }