diff --git a/src/main/java/com/trilead/ssh2/Connection.java b/src/main/java/com/trilead/ssh2/Connection.java index 64e3fe0d..f3b527c9 100644 --- a/src/main/java/com/trilead/ssh2/Connection.java +++ b/src/main/java/com/trilead/ssh2/Connection.java @@ -46,6 +46,16 @@ public class Connection implements AutoCloseable { + /** + * Allow the caller to restrict the IP version of the connection to + * be established. + */ + public enum IpVersion { + IPV4_AND_IPV6, ///< Allow both IPV4 and IPv6, the default. + IPV4_ONLY, ///< Require that the connection be over IPv4 only. + IPV6_ONLY ///< Require that the connection be over IPv6 only. + } + /** * The identifier presented to the SSH-2 server. */ @@ -564,30 +574,74 @@ private void close(Throwable t, boolean hard) /** * Same as - * {@link #connect(ServerHostKeyVerifier, int, int) connect(null, 0, 0)}. + * {@link #connect(ServerHostKeyVerifier, int, int, IpVersion) connect(null, 0, 0, IpVersion.IPV4_AND_IPV6)}. * * @return see comments for the - * {@link #connect(ServerHostKeyVerifier, int, int) connect(ServerHostKeyVerifier, int, int)} + * {@link #connect(ServerHostKeyVerifier, int, int, IpVersion) connect(ServerHostKeyVerifier, int, int, IpVersion)} * method. * @throws IOException */ public synchronized ConnectionInfo connect() throws IOException { - return connect(null, 0, 0); + return connect(null, 0, 0, IpVersion.IPV4_AND_IPV6); + } + + /** + * Same as + * {@link #connect(ServerHostKeyVerifier, int, int, IpVersion) connect(null, 0, 0, ipVersion)}. + * + * @return see comments for the + * {@link #connect(ServerHostKeyVerifier, int, int, IpVersion) connect(ServerHostKeyVerifier, int, int, IpVersion)} + * method. + * @throws IOException + */ + public synchronized ConnectionInfo connect(IpVersion ipVersion) throws IOException + { + return connect(null, 0, 0, ipVersion); } + /** * Same as - * {@link #connect(ServerHostKeyVerifier, int, int) connect(verifier, 0, 0)}. + * {@link #connect(ServerHostKeyVerifier, int, int, IpVersion) connect(verifier, 0, 0, IpVersion.IPV4_AND_IPV6)}. * * @return see comments for the - * {@link #connect(ServerHostKeyVerifier, int, int) connect(ServerHostKeyVerifier, int, int)} + * {@link #connect(ServerHostKeyVerifier, int, int, IpVersion) connect(ServerHostKeyVerifier, int, int, IpVersion)} * method. * @throws IOException */ public synchronized ConnectionInfo connect(ServerHostKeyVerifier verifier) throws IOException { - return connect(verifier, 0, 0); + return connect(verifier, 0, 0, IpVersion.IPV4_AND_IPV6); + } + + /** + * Same as + * {@link #connect(ServerHostKeyVerifier, int, int, IpVersion) connect(verifier, 0, 0, ipVersion)}. + * + * @return see comments for the + * {@link #connect(ServerHostKeyVerifier, int, int, IpVersion) connect(ServerHostKeyVerifier, int, int, IpVersion)} + * method. + * @throws IOException + */ + public synchronized ConnectionInfo connect(ServerHostKeyVerifier verifier, IpVersion ipVersion) throws IOException + { + return connect(verifier, 0, 0, ipVersion); + } + + /** + * Same as + * {@link #connect(ServerHostKeyVerifier, int, int, IpVersion) connect(verifier, connectTimeout, kexTimeout, IpVersion.IPV4_AND_IPV6)}. + * + * @return see comments for the + * {@link #connect(ServerHostKeyVerifier, int, int, IpVersion) connect(ServerHostKeyVerifier, int, int, IpVersion)} + * method. + * @throws IOException + */ + public synchronized ConnectionInfo connect(ServerHostKeyVerifier verifier, int connectTimeout, int kexTimeout) + throws IOException + { + return connect(verifier, connectTimeout, kexTimeout, IpVersion.IPV4_AND_IPV6); } /** @@ -649,6 +703,11 @@ public synchronized ConnectionInfo connect(ServerHostKeyVerifier verifier) throw * but it will only have an effect after the * verifier returns. * + * @param ipVersion + * Specify whether the connection should be restricted to one of + * IPv4 or IPv6, with a default of allowing both. See + * {@link IpVersion}. + * * @return A {@link ConnectionInfo} object containing the details of the * established connection. * @@ -672,7 +731,7 @@ public synchronized ConnectionInfo connect(ServerHostKeyVerifier verifier) throw * proxy is buggy and does not return a proper HTTP response, * then a normal IOException is thrown instead. */ - public synchronized ConnectionInfo connect(ServerHostKeyVerifier verifier, int connectTimeout, int kexTimeout) + public synchronized ConnectionInfo connect(ServerHostKeyVerifier verifier, int connectTimeout, int kexTimeout, IpVersion ipVersion) throws IOException { final class TimeoutState @@ -745,9 +804,22 @@ public void run() token = TimeoutService.addTimeoutHandler(timeoutHorizont, timeoutHandler); } + TransportManager.IpVersion tmIpVersion; + if (ipVersion == IpVersion.IPV4_ONLY) + { + tmIpVersion = TransportManager.IpVersion.IPV4_ONLY; + } + else if (ipVersion == IpVersion.IPV6_ONLY) { + tmIpVersion = TransportManager.IpVersion.IPV6_ONLY; + } + else // Assume (ipVersion == IpVersion.IPV4_AND_IPV6), the default. + { + tmIpVersion = TransportManager.IpVersion.IPV4_AND_IPV6; + } + try { - tm.initialize(cryptoWishList, verifier, dhgexpara, connectTimeout, getOrCreateSecureRND(), proxyData); + tm.initialize(cryptoWishList, verifier, dhgexpara, connectTimeout, tmIpVersion, getOrCreateSecureRND(), proxyData); } catch (SocketTimeoutException se) { diff --git a/src/main/java/com/trilead/ssh2/transport/TransportManager.java b/src/main/java/com/trilead/ssh2/transport/TransportManager.java index 4bdb4f6c..e06a2658 100644 --- a/src/main/java/com/trilead/ssh2/transport/TransportManager.java +++ b/src/main/java/com/trilead/ssh2/transport/TransportManager.java @@ -4,6 +4,7 @@ import com.trilead.ssh2.ExtensionInfo; import com.trilead.ssh2.packets.PacketExtInfo; import java.io.IOException; +import java.net.Inet6Address; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; @@ -49,6 +50,16 @@ */ public class TransportManager { + /** + * Allow the caller to restrict the IP version of the connection to + * be established. + */ + public enum IpVersion { + IPV4_AND_IPV6, ///< Allow both IPV4 and IPv6, the default. + IPV4_ONLY, ///< Require that the connection be over IPv4 only. + IPV6_ONLY ///< Require that the connection be over IPv6 only. + } + private static final Logger log = Logger.getLogger(TransportManager.class); class HandlerEntry @@ -265,30 +276,59 @@ public void close(Throwable cause, boolean useDisconnectPacket) } } - private void establishConnection(ProxyData proxyData, int connectTimeout) throws IOException + private static InetAddress getIPv4Address(InetAddress[] addresses) { + for (InetAddress address : addresses) { + if (! (address instanceof Inet6Address)) { + return address; + } + } + return null; + } + private static Inet6Address getIPv6Address(InetAddress[] addresses) { + for (InetAddress address : addresses) { + if (address instanceof Inet6Address) { + return (Inet6Address) address; + } + } + return null; + } + + private void establishConnection(ProxyData proxyData, int connectTimeout, IpVersion ipVersion) throws IOException { if (proxyData == null) - sock = connectDirect(hostname, port, connectTimeout); + sock = connectDirect(hostname, port, connectTimeout, ipVersion); else sock = proxyData.openConnection(hostname, port, connectTimeout); } - private static Socket connectDirect(String hostname, int port, int connectTimeout) + private static Socket connectDirect(String hostname, int port, int connectTimeout, IpVersion ipVersion) throws IOException { Socket sock = new Socket(); - InetAddress addr = InetAddress.getByName(hostname); + InetAddress addr; + if (ipVersion == IpVersion.IPV4_ONLY) + { + addr = getIPv4Address(InetAddress.getAllByName(hostname)); + } + else if (ipVersion == IpVersion.IPV6_ONLY) + { + addr = getIPv6Address(InetAddress.getAllByName(hostname)); + } + else // Assume (ipVersion == IpVersion.IPV4_AND_IPV6), the default. + { + addr = InetAddress.getByName(hostname); + } sock.connect(new InetSocketAddress(addr, port), connectTimeout); sock.setSoTimeout(0); return sock; } public void initialize(CryptoWishList cwl, ServerHostKeyVerifier verifier, DHGexParameters dhgex, - int connectTimeout, SecureRandom rnd, ProxyData proxyData) throws IOException + int connectTimeout, IpVersion ipVersion, SecureRandom rnd, ProxyData proxyData) throws IOException { /* First, establish the TCP connection to the SSH-2 server */ - establishConnection(proxyData, connectTimeout); + establishConnection(proxyData, connectTimeout, ipVersion); /* Parse the server line and say hello - important: this information is later needed for the * key exchange (to stop man-in-the-middle attacks) - that is why we wrap it into an object