Browse Source

Move IPv4/6 selection down into BSAP.

Change-Id: I6563502da4e5ab76553ad413ea8eeb2d5afd6d29
Reviewed-on: http://gerrit.dmdirc.com/3974
Reviewed-by: Greg Holmes <greg@dmdirc.com>
Automatic-Compile: DMDirc Build Manager
changes/74/3974/3
Chris Smith 9 years ago
parent
commit
aae0eebc0e

+ 122
- 76
src/com/dmdirc/parser/common/BaseSocketAwareParser.java View File

@@ -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

+ 3
- 90
src/com/dmdirc/parser/irc/IRCParser.java View File

@@ -50,8 +50,6 @@ import com.dmdirc.parser.irc.IRCReader.ReadLine;
50 50
 import com.dmdirc.parser.irc.outputqueue.OutputQueue;
51 51
 
52 52
 import java.io.IOException;
53
-import java.net.Inet4Address;
54
-import java.net.Inet6Address;
55 53
 import java.net.InetAddress;
56 54
 import java.net.Socket;
57 55
 import java.net.URI;
@@ -731,93 +729,6 @@ public class IRCParser extends BaseSocketAwareParser implements SecureParser, En
731 729
         disconnectOnFatal = newValue;
732 730
     }
733 731
 
734
-    /**
735
-     * Find a socket to connect using, this will where possible attempt IPv6
736
-     * first, before falling back to IPv4.
737
-     *
738
-     * This will:
739
-     *   - Try using v6 first before v4.
740
-     *   - If there are any failures at all with v6 (including, not having a
741
-     *     v6 address...) then fall through to v4.
742
-     *   - If there is no v4 address, throw the v6 exception
743
-     *   - Otherwise, try v4
744
-     *   - If there are failures with v4, throw the exception.
745
-     *
746
-     * If we have both IPv6 and IPv4, and a target host has both IPv6 and IPv4
747
-     * addresses, but does not listen on the requested port on either, this
748
-     * will cause a double-length connection timeout.
749
-     *
750
-     * @param target Target URI to connect to.
751
-     * @return Socket to use in future connections.
752
-     * @throws IOException if there is an error.
753
-     */
754
-    private Socket findSocket(final URI target) throws IOException {
755
-        if (getProxy() != null) {
756
-            // If we have a proxy, let it worry about all this instead.
757
-            //
758
-            // 1) We have no idea what sort of connectivity the proxy has
759
-            // 2) If we do this here, then any DNS-based geo-balancing is
760
-            //    going to be based on our location, not the proxy.
761
-            return getSocketFactory().createSocket(target.getHost(), target.getPort());
762
-        }
763
-
764
-        URI target6 = null;
765
-        URI target4 = null;
766
-
767
-        // Get the v6 and v4 addresses if appropriate..
768
-        try {
769
-            for (InetAddress i : InetAddress.getAllByName(target.getHost())) {
770
-                if (target6 == null && i instanceof Inet6Address) {
771
-                    target6 = new URI(target.getScheme(), target.getUserInfo(), i.getHostAddress(), target.getPort(), target.getPath(), target.getQuery(), target.getFragment());
772
-                } else if (target4 == null && i instanceof Inet4Address) {
773
-                    target4 = new URI(target.getScheme(), target.getUserInfo(), i.getHostAddress(), target.getPort(), target.getPath(), target.getQuery(), target.getFragment());
774
-                }
775
-            }
776
-        } catch (final URISyntaxException use) { /* Won't happen. */ }
777
-
778
-        // Now try and connect.
779
-        // Try using v6 first before v4.
780
-        // If there are any failures at all with v6 (including, not having a
781
-        // v6 address...) then fall through to v4.
782
-        // If there is no v4 address, throw the v6 exception
783
-        // Otherwise, try v4
784
-        // If there are failures with v4, throw the exception.
785
-        //
786
-        // If we have both IPv6 and IPv4, and a target host has both IPv6 and
787
-        // IPv4 addresses, but does not listen on the requested port on either,
788
-        // this will cause a double-length connection timeout.
789
-        //
790
-        // In future this may want to be rewritten to perform a happy-eyeballs
791
-        // race rather than trying one then the other.
792
-        Exception v6Exception = null;
793
-        if (target6 != null) {
794
-            try {
795
-                return getSocketFactory().createSocket(target6.getHost(), target6.getPort());
796
-            } catch (final IOException ioe) {
797
-                if (target4 == null) {
798
-                    throw ioe;
799
-                }
800
-            } catch (final Exception e) {
801
-                v6Exception = e;
802
-                callDebugInfo(DEBUG_SOCKET, "Exception trying to use IPv6: " + e);
803
-            }
804
-        }
805
-        if (target4 != null) {
806
-            try {
807
-                return getSocketFactory().createSocket(target4.getHost(), target4.getPort());
808
-            } catch (final IOException e2) {
809
-                callDebugInfo(DEBUG_SOCKET, "Exception trying to use IPv4: " + e2);
810
-                throw e2;
811
-            }
812
-        } else if (v6Exception != null) {
813
-            throw new IOException("Error connecting to: " + target + " (IPv6 failure, with no IPv4 fallback)", v6Exception);
814
-        } else {
815
-            // We should never get here as if both target4 and target6 were null
816
-            // getAllByName would have thrown an exception instead.
817
-            throw new IOException("Error connecting to: " + target + " (General connectivity failure)");
818
-        }
819
-    }
820
-
821 732
     /**
822 733
      * Connect to IRC.
823 734
      *
@@ -834,7 +745,9 @@ public class IRCParser extends BaseSocketAwareParser implements SecureParser, En
834 745
         callDebugInfo(DEBUG_SOCKET, "Connecting to " + getURI().getHost() + ':' + getURI().getPort());
835 746
 
836 747
         currentSocketState = SocketState.OPENING;
837
-        socket = findSocket(getConnectURI(getURI()));
748
+
749
+        final URI connectUri = getConnectURI(getURI());
750
+        socket = getSocketFactory().createSocket(connectUri.getHost(), connectUri.getPort());
838 751
 
839 752
         rawSocket = socket;
840 753
 

Loading…
Cancel
Save