|
@@ -25,6 +25,7 @@ package com.dmdirc.parser.common;
|
25
|
25
|
import com.dmdirc.parser.irc.IRCAuthenticator;
|
26
|
26
|
|
27
|
27
|
import java.io.IOException;
|
|
28
|
+import java.net.Inet4Address;
|
28
|
29
|
import java.net.Inet6Address;
|
29
|
30
|
import java.net.InetAddress;
|
30
|
31
|
import java.net.InetSocketAddress;
|
|
@@ -116,97 +117,142 @@ public abstract class BaseSocketAwareParser extends BaseParser {
|
116
|
117
|
// Do nothing by default
|
117
|
118
|
}
|
118
|
119
|
|
119
|
|
- private class BindingSocketFactory extends SocketFactory {
|
|
120
|
+ /**
|
|
121
|
+ * Creates and binds a new socket to the IP address specified by the parser. If the target
|
|
122
|
+ * address is an IPv6 address, the parser's {@link #getBindIPv6()} value will be used;
|
|
123
|
+ * otherwise, the standard {@link #getBindIP()} will be used.
|
|
124
|
+ *
|
|
125
|
+ * @param host The host to connect to.
|
|
126
|
+ * @param port The port to connect on.
|
|
127
|
+ * @return A new socket bound appropriately and connected.
|
|
128
|
+ */
|
|
129
|
+ @SuppressWarnings({"resource", "SocketOpenedButNotSafelyClosed"})
|
|
130
|
+ private Socket boundSocket(final InetAddress host, final int port) throws IOException {
|
|
131
|
+ final Socket socket = new Socket();
|
|
132
|
+ final String bindIp = host instanceof Inet6Address ? getBindIPv6() : getBindIP();
|
120
|
133
|
|
121
|
|
- @Override
|
122
|
|
- public Socket createSocket(final String host, final int port) throws IOException {
|
123
|
|
- return createSocket(InetAddress.getByName(host), port);
|
|
134
|
+ if (bindIp != null && !bindIp.isEmpty()) {
|
|
135
|
+ try {
|
|
136
|
+ socket.bind(new InetSocketAddress(InetAddress.getByName(bindIp), 0));
|
|
137
|
+ } catch (IOException ex) {
|
|
138
|
+ // Bind failed; continue trying to connect anyway.
|
|
139
|
+ handleSocketDebug("Binding failed: " + ex.getMessage());
|
|
140
|
+ }
|
124
|
141
|
}
|
|
142
|
+ setSocket(socket);
|
|
143
|
+ socket.connect(new InetSocketAddress(host, port), connectTimeout);
|
|
144
|
+ return socket;
|
|
145
|
+ }
|
125
|
146
|
|
126
|
|
- @Override
|
127
|
|
- @SuppressWarnings("resource")
|
128
|
|
- public Socket createSocket(final InetAddress host, final int port) throws IOException {
|
129
|
|
- checkPort(port, "server");
|
130
|
|
- return getProxy() == null ? boundSocket(host, port) : proxiedSocket(host, port);
|
|
147
|
+ /**
|
|
148
|
+ * Creates a new socket via a proxy.
|
|
149
|
+ *
|
|
150
|
+ * @param host The host to connect to.
|
|
151
|
+ * @param port The port to connect on.
|
|
152
|
+ * @return A new proxy-using socket.
|
|
153
|
+ */
|
|
154
|
+ @SuppressWarnings({"resource", "SocketOpenedButNotSafelyClosed"})
|
|
155
|
+ private Socket proxiedSocket(final InetAddress host, final int port) throws IOException {
|
|
156
|
+ final URI proxy = getProxy();
|
|
157
|
+ final Proxy.Type proxyType = Proxy.Type.valueOf(proxy.getScheme().toUpperCase());
|
|
158
|
+ final String proxyHost = proxy.getHost();
|
|
159
|
+ final int proxyPort = checkPort(proxy.getPort(), "Proxy");
|
|
160
|
+
|
|
161
|
+ final Socket socket = new Socket(
|
|
162
|
+ new Proxy(proxyType, new InetSocketAddress(proxyHost, proxyPort)));
|
|
163
|
+
|
|
164
|
+ try {
|
|
165
|
+ final IRCAuthenticator ia = IRCAuthenticator.getIRCAuthenticator();
|
|
166
|
+ final URI serverUri = new URI(null, null, host.getHostName(), port,
|
|
167
|
+ null, null, null);
|
|
168
|
+ try {
|
|
169
|
+ ia.getSemaphore().acquireUninterruptibly();
|
|
170
|
+ ia.addAuthentication(serverUri, proxy);
|
|
171
|
+ socket.connect(new InetSocketAddress(host, port), connectTimeout);
|
|
172
|
+ } finally {
|
|
173
|
+ ia.removeAuthentication(serverUri, proxy);
|
|
174
|
+ ia.getSemaphore().release();
|
|
175
|
+ }
|
|
176
|
+ } catch (URISyntaxException ex) {
|
|
177
|
+ // Won't happen.
|
131
|
178
|
}
|
132
|
179
|
|
133
|
|
- /**
|
134
|
|
- * Creates and binds a new socket to the IP address specified by the parser. If the target
|
135
|
|
- * address is an IPv6 address, the parser's {@link #getBindIPv6()} value will be used;
|
136
|
|
- * otherwise, the standard {@link #getBindIP()} will be used.
|
137
|
|
- *
|
138
|
|
- * @param host The host to connect to.
|
139
|
|
- * @param port The port to connect on.
|
140
|
|
- * @return A new socket bound appropriately and connected.
|
141
|
|
- */
|
142
|
|
- @SuppressWarnings({"resource", "SocketOpenedButNotSafelyClosed"})
|
143
|
|
- private Socket boundSocket(final InetAddress host, final int port) throws IOException {
|
144
|
|
- final Socket socket = new Socket();
|
145
|
|
- final String bindIp = host instanceof Inet6Address ? getBindIPv6() : getBindIP();
|
146
|
|
-
|
147
|
|
- if (bindIp != null && !bindIp.isEmpty()) {
|
148
|
|
- try {
|
149
|
|
- socket.bind(new InetSocketAddress(InetAddress.getByName(bindIp), 0));
|
150
|
|
- } catch (IOException ex) {
|
151
|
|
- // Bind failed; continue trying to connect anyway.
|
152
|
|
- handleSocketDebug("Binding failed: " + ex.getMessage());
|
153
|
|
- }
|
|
180
|
+ return socket;
|
|
181
|
+ }
|
|
182
|
+
|
|
183
|
+ /**
|
|
184
|
+ * Utility method to ensure a port is in the correct range. This stops networking classes
|
|
185
|
+ * throwing obscure exceptions.
|
|
186
|
+ *
|
|
187
|
+ * @param port The port to test.
|
|
188
|
+ * @param description Description of the port for error messages.
|
|
189
|
+ * @return The given port.
|
|
190
|
+ * @throws IOException If the port is out of range.
|
|
191
|
+ */
|
|
192
|
+ private int checkPort(final int port, final String description) throws IOException {
|
|
193
|
+ if (port > 65535 || port <= 0) {
|
|
194
|
+ throw new IOException(description + " port (" + port + ") is invalid.");
|
|
195
|
+ }
|
|
196
|
+ return port;
|
|
197
|
+ }
|
|
198
|
+
|
|
199
|
+ /**
|
|
200
|
+ * Finds an address to connect to of the specified type.
|
|
201
|
+ *
|
|
202
|
+ * @param host The host to resolve.
|
|
203
|
+ * @param type The type of address to look for
|
|
204
|
+ * @return An address of the specified type, or {@code null} if none exists.
|
|
205
|
+ * @throws IOException if there is an error.
|
|
206
|
+ */
|
|
207
|
+ private InetAddress getAddressForHost(final String host,
|
|
208
|
+ final Class<? extends InetAddress> type) throws IOException {
|
|
209
|
+ if (getProxy() != null) {
|
|
210
|
+ // If we have a proxy, let it worry about all this instead.
|
|
211
|
+ //
|
|
212
|
+ // 1) We have no idea what sort of connectivity the proxy has
|
|
213
|
+ // 2) If we do this here, then any DNS-based geo-balancing is
|
|
214
|
+ // going to be based on our location, not the proxy.
|
|
215
|
+ return InetAddress.getByName(host);
|
|
216
|
+ }
|
|
217
|
+
|
|
218
|
+ for (InetAddress i : InetAddress.getAllByName(host)) {
|
|
219
|
+ if (type.isAssignableFrom(i.getClass())) {
|
|
220
|
+ return i;
|
154
|
221
|
}
|
155
|
|
- setSocket(socket);
|
156
|
|
- socket.connect(new InetSocketAddress(host, port), connectTimeout);
|
157
|
|
- return socket;
|
158
|
222
|
}
|
159
|
223
|
|
160
|
|
- /**
|
161
|
|
- * Creates a new socket via a proxy.
|
162
|
|
- *
|
163
|
|
- * @param host The host to connect to.
|
164
|
|
- * @param port The port to connect on.
|
165
|
|
- * @return A new proxy-using socket.
|
166
|
|
- */
|
167
|
|
- @SuppressWarnings({"resource", "SocketOpenedButNotSafelyClosed"})
|
168
|
|
- private Socket proxiedSocket(final InetAddress host, final int port) throws IOException {
|
169
|
|
- final URI proxy = getProxy();
|
170
|
|
- final Proxy.Type proxyType = Proxy.Type.valueOf(proxy.getScheme().toUpperCase());
|
171
|
|
- final String proxyHost = proxy.getHost();
|
172
|
|
- final int proxyPort = checkPort(proxy.getPort(), "Proxy");
|
173
|
|
-
|
174
|
|
- final Socket socket = new Socket(
|
175
|
|
- new Proxy(proxyType, new InetSocketAddress(proxyHost, proxyPort)));
|
|
224
|
+ return null;
|
|
225
|
+ }
|
176
|
226
|
|
|
227
|
+ private class BindingSocketFactory extends SocketFactory {
|
|
228
|
+
|
|
229
|
+ @Override
|
|
230
|
+ public Socket createSocket(final String host, final int port) throws IOException {
|
|
231
|
+ // Attempt to connect to IPv6 addresses first.
|
|
232
|
+ IOException sixException = null;
|
177
|
233
|
try {
|
178
|
|
- final IRCAuthenticator ia = IRCAuthenticator.getIRCAuthenticator();
|
179
|
|
- final URI serverUri = new URI(null, null, host.getHostName(), port,
|
180
|
|
- null, null, null);
|
181
|
|
- try {
|
182
|
|
- ia.getSemaphore().acquireUninterruptibly();
|
183
|
|
- ia.addAuthentication(serverUri, proxy);
|
184
|
|
- socket.connect(new InetSocketAddress(host, port), connectTimeout);
|
185
|
|
- } finally {
|
186
|
|
- ia.removeAuthentication(serverUri, proxy);
|
187
|
|
- ia.getSemaphore().release();
|
|
234
|
+ final InetAddress sixAddress = getAddressForHost(host, Inet6Address.class);
|
|
235
|
+ if (sixAddress != null) {
|
|
236
|
+ return createSocket(sixAddress, port);
|
188
|
237
|
}
|
189
|
|
- } catch (URISyntaxException ex) {
|
190
|
|
- // Won't happen.
|
|
238
|
+ } catch (IOException ex) {
|
|
239
|
+ handleSocketDebug("Unable to use IPv6: " + ex.getMessage());
|
|
240
|
+ sixException = ex;
|
191
|
241
|
}
|
192
|
242
|
|
193
|
|
- return socket;
|
|
243
|
+ // If there isn't an IPv6 address, or it has failed, fall back to IPv4.
|
|
244
|
+ final InetAddress fourAddress = getAddressForHost(host, Inet4Address.class);
|
|
245
|
+ if (fourAddress == null) {
|
|
246
|
+ throw new IOException("No IPv4 address and IPv6 failed", sixException);
|
|
247
|
+ }
|
|
248
|
+ return createSocket(fourAddress, port);
|
194
|
249
|
}
|
195
|
250
|
|
196
|
|
- /**
|
197
|
|
- * Utility method to ensure a port is in the correct range. This stops networking classes
|
198
|
|
- * throwing obscure exceptions.
|
199
|
|
- *
|
200
|
|
- * @param port The port to test.
|
201
|
|
- * @param description Description of the port for error messages.
|
202
|
|
- * @return The given port.
|
203
|
|
- * @throws IOException If the port is out of range.
|
204
|
|
- */
|
205
|
|
- private int checkPort(final int port, final String description) throws IOException {
|
206
|
|
- if (port > 65535 || port <= 0) {
|
207
|
|
- throw new IOException(description + " port (" + port + ") is invalid.");
|
208
|
|
- }
|
209
|
|
- return port;
|
|
251
|
+ @Override
|
|
252
|
+ @SuppressWarnings("resource")
|
|
253
|
+ public Socket createSocket(final InetAddress host, final int port) throws IOException {
|
|
254
|
+ checkPort(port, "server");
|
|
255
|
+ return getProxy() == null ? boundSocket(host, port) : proxiedSocket(host, port);
|
210
|
256
|
}
|
211
|
257
|
|
212
|
258
|
@Override
|