From 9cf2895e7e7cb7baf089887339afa268f0bb9d71 Mon Sep 17 00:00:00 2001 From: chegar Date: Tue, 14 Jan 2014 16:23:49 +0000 Subject: [PATCH] 7100957: SOCKS proxying does not work with IPv6 connections Reviewed-by: chegar, alanb Contributed-by: Dimitar Mavrodiev --- .../classes/java/net/SocksSocketImpl.java | 10 +- test/java/net/Socks/SocksIPv6Test.java | 178 ++++++++++++++++++ test/java/net/Socks/SocksServer.java | 34 ++-- 3 files changed, 197 insertions(+), 25 deletions(-) create mode 100644 test/java/net/Socks/SocksIPv6Test.java diff --git a/src/share/classes/java/net/SocksSocketImpl.java b/src/share/classes/java/net/SocksSocketImpl.java index dd89283fb..3d162ec82 100644 --- a/src/share/classes/java/net/SocksSocketImpl.java +++ b/src/share/classes/java/net/SocksSocketImpl.java @@ -118,7 +118,7 @@ class SocksSocketImpl extends PlainSocketImpl implements SocksConsts { private int readSocksReply(InputStream in, byte[] data, long deadlineMillis) throws IOException { int len = data.length; int received = 0; - for (int attempts = 0; received < len && attempts < 3; attempts++) { + while (received < len) { int count; try { count = ((SocketInputStream)in).read(data, received, len - received, remainingMillis(deadlineMillis)); @@ -520,7 +520,11 @@ class SocksSocketImpl extends PlainSocketImpl implements SocksConsts { throw new SocketException("Reply from SOCKS server badly formatted"); break; case DOMAIN_NAME: - len = data[1]; + byte[] lenByte = new byte[1]; + i = readSocksReply(in, lenByte, deadlineMillis); + if (i != 1) + throw new SocketException("Reply from SOCKS server badly formatted"); + len = lenByte[0] & 0xFF; byte[] host = new byte[len]; i = readSocksReply(in, host, deadlineMillis); if (i != len) @@ -531,7 +535,7 @@ class SocksSocketImpl extends PlainSocketImpl implements SocksConsts { throw new SocketException("Reply from SOCKS server badly formatted"); break; case IPV6: - len = data[1]; + len = 16; addr = new byte[len]; i = readSocksReply(in, addr, deadlineMillis); if (i != len) diff --git a/test/java/net/Socks/SocksIPv6Test.java b/test/java/net/Socks/SocksIPv6Test.java new file mode 100644 index 000000000..1b4dc82ff --- /dev/null +++ b/test/java/net/Socks/SocksIPv6Test.java @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2013, 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* @test + * @bug 7100957 + * @summary Java doesn't correctly handle the SOCKS protocol when used over IPv6. + * @run testng SocksIPv6Test + */ + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.net.Authenticator; +import java.net.InetSocketAddress; +import java.net.URL; +import java.net.Proxy; +import java.lang.Override; +import java.net.InetAddress; +import java.net.Inet6Address; +import java.net.ServerSocket; +import java.net.SocketException; +import java.net.NetworkInterface; +import java.net.UnknownHostException; +import java.util.Collections; +import java.util.List; +import com.sun.net.httpserver.*; +import java.io.BufferedWriter; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import static org.testng.Assert.*; + +public class SocksIPv6Test { + + private HttpServer server; + private SocksServer socks; + private String response = "Hello."; + private static boolean shouldRun = false; + + @BeforeClass + public void setUp() throws Exception { + shouldRun = ensureInet6AddressFamily() && ensureIPv6OnLoopback(); + + server = HttpServer.create(new InetSocketAddress(0), 0); + server.createContext("/", ex -> { + ex.sendResponseHeaders(200, response.length()); + try (BufferedWriter writer = new BufferedWriter( + new OutputStreamWriter(ex.getResponseBody(), "UTF-8"))) { + writer.write(response); + } + ex.close(); + }); + server.start(); + + socks = new SocksServer(0, false); + socks.addUser("user", "pass"); + socks.start(); + + Authenticator.setDefault(new Authenticator() { + @Override + protected java.net.PasswordAuthentication getPasswordAuthentication() { + return new java.net.PasswordAuthentication( + "user", "pass".toCharArray()); + } + }); + } + + private boolean ensureIPv6OnLoopback() throws Exception { + boolean ipv6 = false; + + List nics = Collections.list(NetworkInterface.getNetworkInterfaces()); + for (NetworkInterface nic : nics) { + if (!nic.isLoopback()) { + continue; + } + List addrs = Collections.list(nic.getInetAddresses()); + for (InetAddress addr : addrs) { + if (addr instanceof Inet6Address) { + ipv6 = true; + break; + } + } + } + if (!ipv6) + System.out.println("IPv6 is not enabled on loopback. Skipping test suite."); + return ipv6; + } + + private boolean ensureInet6AddressFamily() throws IOException { + try (ServerSocket s = new ServerSocket()) { + s.bind(new InetSocketAddress("::1", 0)); + return true; + } catch (SocketException e) { + System.out.println("Inet 6 address family is not available. Skipping test suite."); + } + return false; + } + + @Test(groups = "unit") + public void testSocksOverIPv6() throws Exception { + if (!shouldRun) return; + + Proxy proxy = new Proxy(Proxy.Type.SOCKS, new InetSocketAddress("::1", + socks.getPort())); + URL url = new URL("http://[::1]:" + server.getAddress().getPort()); + java.net.URLConnection conn = url.openConnection(proxy); + String actual = ""; + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(conn.getInputStream()))) { + actual = reader.readLine(); + } + assertEquals(actual, response); + } + + @Test(groups = "unit") + public void testSocksOverIPv6Hostname() throws Exception { + if (!shouldRun) return; + + String ipv6Hostname = InetAddress.getByName("::1").getHostName(); + String ipv4Hostname = InetAddress.getByName("127.0.0.1").getHostName(); + + if (ipv6Hostname.equals(InetAddress.getByName("::1").getHostAddress())) { + System.out.println("Unable to get the hostname of the IPv6 loopback " + + "address. Skipping test case."); + return; + } + + if (ipv6Hostname.equals(ipv4Hostname)) { + System.out.println("IPv6 and IPv4 loopback addresses map to the" + + " same hostname. Skipping test case."); + return; + } + + Proxy proxy = new Proxy(Proxy.Type.SOCKS, new InetSocketAddress(ipv6Hostname, + socks.getPort())); + URL url = new URL("http://" + ipv6Hostname + ":" + server.getAddress().getPort()); + java.net.URLConnection conn = url.openConnection(proxy); + String actual = ""; + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(conn.getInputStream()))) { + actual = reader.readLine(); + } + assertEquals(actual, response); + } + + @AfterClass + public void tearDown() { + if (server != null) { + server.stop(1); + } + if (socks != null) { + socks.terminate(); + } + } +} diff --git a/test/java/net/Socks/SocksServer.java b/test/java/net/Socks/SocksServer.java index 6e27e342e..f2d6ef82b 100644 --- a/test/java/net/Socks/SocksServer.java +++ b/test/java/net/Socks/SocksServer.java @@ -89,6 +89,7 @@ public class SocksServer extends Thread { return; } tout.write(b); + tout.flush(); } catch (IOException e) { // actually exit from the thread return; @@ -99,8 +100,8 @@ public class SocksServer extends Thread { ClientHandler(Socket s) throws IOException { client = s; - in = client.getInputStream(); - out = client.getOutputStream(); + in = new BufferedInputStream(client.getInputStream()); + out = new BufferedOutputStream(client.getOutputStream()); } private void readBuf(InputStream is, byte[] buf) throws IOException { @@ -230,8 +231,8 @@ public class SocksServer extends Thread { out.write(port & 0xff); out.write(buf); out.flush(); - InputStream in2 = dest.getInputStream(); - OutputStream out2 = dest.getOutputStream(); + InputStream in2 = new BufferedInputStream(dest.getInputStream()); + OutputStream out2 = new BufferedOutputStream(dest.getOutputStream()); Tunnel tunnel = new Tunnel(in2, out); tunnel.start(); @@ -246,6 +247,7 @@ public class SocksServer extends Thread { return; } out2.write(b); + out2.flush(); } catch (IOException ex) { } } while (!client.isClosed()); @@ -323,8 +325,8 @@ public class SocksServer extends Thread { out.write((addr.getPort() >> 0) & 0xff); out.flush(); - InputStream in2 = dest.getInputStream(); - OutputStream out2 = dest.getOutputStream(); + InputStream in2 = new BufferedInputStream(dest.getInputStream()); + OutputStream out2 = new BufferedOutputStream(dest.getOutputStream()); Tunnel tunnel = new Tunnel(in2, out); tunnel.start(); @@ -340,6 +342,7 @@ public class SocksServer extends Thread { return; } out2.write(b); + out2.flush(); } catch(IOException ioe) { } } while (!client.isClosed()); @@ -384,6 +387,7 @@ public class SocksServer extends Thread { return; } out2.write(b); + out2.flush(); } catch(IOException ioe) { } } while (!client.isClosed()); @@ -410,14 +414,7 @@ public class SocksServer extends Thread { { byte[] buf = new byte[4]; readBuf(in, buf); - int i = 0; - StringBuffer sb = new StringBuffer(); - for (i = 0; i < 4; i++) { - sb.append(buf[i]&0xff); - if (i < 3) - sb.append('.'); - } - addr = sb.toString(); + addr = InetAddress.getByAddress(buf).getHostAddress(); } break; case DOMAIN_NAME: @@ -432,14 +429,7 @@ public class SocksServer extends Thread { { byte[] buf = new byte[16]; readBuf(in, buf); - int i = 0; - StringBuffer sb = new StringBuffer(); - for (i = 0; i<16; i++) { - sb.append(Integer.toHexString(buf[i]&0xff)); - if (i < 15) - sb.append(':'); - } - addr = sb.toString(); + addr = InetAddress.getByAddress(buf).getHostAddress(); } break; } -- GitLab