|
@@ -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")) {
|