소스 검색

Try to connect using IPv6 before IPv4, and falling back if there are errors.

Change-Id: I56e7ab284abb91e22bf5c35b73035302a85b45b4
Reviewed-on: http://gerrit.dmdirc.com/2513
Automatic-Compile: DMDirc Build Manager
Reviewed-by: Greg Holmes <greg@dmdirc.com>
tags/0.7rc1
Shane Mc Cormack 12 년 전
부모
커밋
3ce9380cf5
1개의 변경된 파일117개의 추가작업 그리고 30개의 파일을 삭제
  1. 117
    30
      src/com/dmdirc/parser/irc/IRCParser.java

+ 117
- 30
src/com/dmdirc/parser/irc/IRCParser.java 파일 보기

@@ -38,6 +38,8 @@ import com.dmdirc.parser.irc.IRCReader.ReadLine;
38 38
 import com.dmdirc.parser.irc.outputqueue.OutputQueue;
39 39
 
40 40
 import java.io.IOException;
41
+import java.net.Inet4Address;
42
+import java.net.Inet6Address;
41 43
 import java.net.InetAddress;
42 44
 import java.net.InetSocketAddress;
43 45
 import java.net.Proxy;
@@ -730,40 +732,26 @@ public class IRCParser extends BaseParser implements SecureParser, EncodingParse
730 732
         disconnectOnFatal = newValue;
731 733
     }
732 734
 
733
-    /**
734
-     * Connect to IRC.
735
-     *
736
-     * @throws IOException if the socket can not be connected
737
-     * @throws NoSuchAlgorithmException if SSL is not available
738
-     * @throws KeyManagementException if the trustManager is invalid
739
-     */
740
-    private void doConnect() throws IOException, NoSuchAlgorithmException, KeyManagementException {
741
-        if (getURI() == null || getURI().getHost() == null) {
742
-            throw new UnknownHostException("Unspecified host.");
735
+    private Socket newSocket(final URI target, final URI proxy) throws IOException {
736
+        if (target.getPort() > 65535 || target.getPort() <= 0) {
737
+            throw new IOException("Server port (" + target.getPort() + ") is invalid.");
743 738
         }
744 739
 
745
-        resetState();
746
-        callDebugInfo(DEBUG_SOCKET, "Connecting to " + getURI().getHost() + ":" + getURI().getPort());
747
-
748
-        if (getURI().getPort() > 65535 || getURI().getPort() <= 0) {
749
-            throw new IOException("Server port (" + getURI().getPort() + ") is invalid.");
750
-        }
751
-
752
-        if (getProxy() == null) {
740
+        Socket mySocket = null;
741
+        if (proxy == null) {
753 742
             callDebugInfo(DEBUG_SOCKET, "Not using Proxy");
754
-            socket = new Socket();
743
+            mySocket = new Socket();
755 744
 
756 745
             if (getBindIP() != null && !getBindIP().isEmpty()) {
757 746
                 callDebugInfo(DEBUG_SOCKET, "Binding to IP: " + getBindIP());
758 747
                 try {
759
-                    socket.bind(new InetSocketAddress(InetAddress.getByName(getBindIP()), 0));
748
+                    mySocket.bind(new InetSocketAddress(InetAddress.getByName(getBindIP()), 0));
760 749
                 } catch (IOException e) {
761 750
                     callDebugInfo(DEBUG_SOCKET, "Binding failed: " + e.getMessage());
762 751
                 }
763 752
             }
764 753
 
765
-            currentSocketState = SocketState.OPENING;
766
-            socket.connect(new InetSocketAddress(getURI().getHost(), getURI().getPort()), connectTimeout);
754
+            mySocket.connect(new InetSocketAddress(target.getHost(), target.getPort()), connectTimeout);
767 755
         } else {
768 756
             callDebugInfo(DEBUG_SOCKET, "Using Proxy");
769 757
 
@@ -771,16 +759,15 @@ public class IRCParser extends BaseParser implements SecureParser, EncodingParse
771 759
                 callDebugInfo(DEBUG_SOCKET, "IP Binding is not possible when using a proxy.");
772 760
             }
773 761
 
774
-            final String proxyHost = getProxy().getHost();
775
-            final int proxyPort = getProxy().getPort();
762
+            final String proxyHost = proxy.getHost();
763
+            final int proxyPort = proxy.getPort();
776 764
 
777 765
             if (proxyPort > 65535 || proxyPort <= 0) {
778 766
                 throw new IOException("Proxy port (" + proxyPort + ") is invalid.");
779 767
             }
780 768
 
781
-            final Proxy.Type proxyType = Proxy.Type.valueOf(getProxy().getScheme().toUpperCase());
782
-            socket = new Socket(new Proxy(proxyType, new InetSocketAddress(proxyHost, proxyPort)));
783
-            currentSocketState = SocketState.OPENING;
769
+            final Proxy.Type proxyType = Proxy.Type.valueOf(proxy.getScheme().toUpperCase());
770
+            mySocket = new Socket(new Proxy(proxyType, new InetSocketAddress(proxyHost, proxyPort)));
784 771
 
785 772
             final IRCAuthenticator ia = IRCAuthenticator.getIRCAuthenticator();
786 773
 
@@ -790,14 +777,114 @@ public class IRCParser extends BaseParser implements SecureParser, EncodingParse
790 777
                 } catch (InterruptedException ex) {
791 778
                 }
792 779
 
793
-                ia.addAuthentication(getURI(), getProxy());
794
-                socket.connect(new InetSocketAddress(getURI().getHost(), getURI().getPort()), connectTimeout);
780
+                ia.addAuthentication(target, proxy);
781
+                mySocket.connect(new InetSocketAddress(target.getHost(), target.getPort()), connectTimeout);
795 782
             } finally {
796
-                ia.removeAuthentication(getURI(), getProxy());
783
+                ia.removeAuthentication(target, proxy);
797 784
                 ia.getSemaphore().release();
798 785
             }
799 786
         }
800 787
 
788
+        return mySocket;
789
+    }
790
+
791
+    /**
792
+     * Find a socket to connect using, this will where possible attempt IPv6
793
+     * first, before falling back to IPv4.
794
+     *
795
+     * This will:
796
+     *   - Try using v6 first before v4.
797
+     *   - If there are any failures at all with v6 (including, not having a
798
+     *     v6 address...) then fall through to v4.
799
+     *   - If there is no v4 address, throw the v6 exception
800
+     *   - Otherwise, try v4
801
+     *   - If there are failures with v4, throw the exception.
802
+     *
803
+     * If we have both IPv6 and IPv4, and a target host has both IPv6 and IPv4
804
+     * addresses, but does not listen on the requested port on either, this
805
+     * will cause a double-length connection timeout.
806
+     *
807
+     * @param target Target URI to connect to.
808
+     * @param proxy Proxy URI to use
809
+     * @return Socket to use in future connections.
810
+     * @throws IOException if there is an error.
811
+     */
812
+    private Socket findSocket(final URI target, final URI proxy) throws IOException {
813
+        URI target6 = null;
814
+        URI target4 = null;
815
+
816
+        // Get the v6 and v4 addresses if appropriate..
817
+        try {
818
+            for (InetAddress i : InetAddress.getAllByName(target.getHost())) {
819
+                if (target6 == null && i instanceof Inet6Address) {
820
+                    target6 = new URI(target.getScheme(), target.getUserInfo(), i.getHostAddress(), target.getPort(), target.getPath(), target.getQuery(), target.getFragment());
821
+                } else if (target4 == null && i instanceof Inet4Address) {
822
+                    target4 = new URI(target.getScheme(), target.getUserInfo(), i.getHostAddress(), target.getPort(), target.getPath(), target.getQuery(), target.getFragment());
823
+                }
824
+            }
825
+        } catch (final URISyntaxException use) { /* Won't happen. */ }
826
+
827
+        // Now try and connect.
828
+        // Try using v6 first before v4.
829
+        // If there are any failures at all with v6 (including, not having a
830
+        // v6 address...) then fall through to v4.
831
+        // If there is no v4 address, throw the v6 exception
832
+        // Otherwise, try v4
833
+        // If there are failures with v4, throw the exception.
834
+        //
835
+        // If we have both IPv6 and IPv4, and a target host has both IPv6 and
836
+        // IPv4 addresses, but does not listen on the requested port on either,
837
+        // this will cause a double-length connection timeout.
838
+        //
839
+        // In future this may want to be rewritten to perform a happy-eyeballs
840
+        // race rather than trying one then the other.
841
+        Exception v6Exception = null;
842
+        if (target6 != null) {
843
+            try {
844
+                return newSocket(target6, proxy);
845
+            } catch (final IOException ioe) {
846
+                if (target4 == null) {
847
+                    throw ioe;
848
+                }
849
+            } catch (final Exception e) {
850
+                v6Exception = e;
851
+                callDebugInfo(DEBUG_SOCKET, "Exception trying to use IPv6: " + e);
852
+            }
853
+        }
854
+        if (target4 != null) {
855
+            try {
856
+                return newSocket(target4, proxy);
857
+            } catch (final IOException e2) {
858
+                callDebugInfo(DEBUG_SOCKET, "Exception trying to use IPv4: " + e2);
859
+                throw e2;
860
+            }
861
+        } else if (v6Exception != null) {
862
+            throw new IOException("Error connecting to: " + target + " (IPv6 failure, with no IPv4 fallback)", v6Exception);
863
+        } else {
864
+            // We should never get here as if both target4 and target6 were null
865
+            // getAllByName would have thrown an exception instead.
866
+            throw new IOException("Error connecting to: " + target + " (General connectivity failure)");
867
+        }
868
+    }
869
+
870
+    /**
871
+     * Connect to IRC.
872
+     *
873
+     * @throws IOException if the socket can not be connected
874
+     * @throws NoSuchAlgorithmException if SSL is not available
875
+     * @throws KeyManagementException if the trustManager is invalid
876
+     */
877
+    private void doConnect() throws IOException, NoSuchAlgorithmException, KeyManagementException {
878
+        if (getURI() == null || getURI().getHost() == null) {
879
+            throw new UnknownHostException("Unspecified host.");
880
+        }
881
+
882
+        resetState();
883
+        callDebugInfo(DEBUG_SOCKET, "Connecting to " + getURI().getHost() + ":" + getURI().getPort());
884
+
885
+        currentSocketState = SocketState.OPENING;
886
+        socket = findSocket(getURI(), getProxy());
887
+
801 888
         rawSocket = socket;
802 889
 
803 890
         if (getURI().getScheme().endsWith("s")) {

Loading…
취소
저장