1 /* 2 * Copyright (c) 2010, 2018, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 /* 25 * @test 26 * @bug 6370908 27 * @summary Add support for HTTP_CONNECT proxy in Socket class 28 * @modules java.base/sun.net.www 29 * @run main HttpProxy 30 * @run main/othervm -Djava.net.preferIPv4Stack=true HttpProxy 31 */ 32 33 import java.io.IOException; 34 import java.io.InputStream; 35 import java.io.OutputStream; 36 import java.io.PrintWriter; 37 import static java.lang.System.out; 38 import java.net.InetAddress; 39 import java.net.InetSocketAddress; 40 import java.net.Proxy; 41 import java.net.ServerSocket; 42 import java.net.Socket; 43 import sun.net.www.MessageHeader; 44 45 public class HttpProxy { 46 final String proxyHost; 47 final int proxyPort; 48 static final int SO_TIMEOUT = 15000; 49 50 public static void main(String[] args) throws Exception { 51 String host; 52 int port; 53 if (args.length == 0) { 54 // Start internal proxy 55 ConnectProxyTunnelServer proxy = new ConnectProxyTunnelServer(); 56 proxy.start(); 57 host = "localhost"; 58 port = proxy.getLocalPort(); 59 out.println("Running with internal proxy: " + host + ":" + port); 60 } else if (args.length == 2) { 61 host = args[0]; 62 port = Integer.valueOf(args[1]); 63 out.println("Running against specified proxy server: " + host + ":" + port); 64 } else { 65 System.err.println("Usage: java HttpProxy [<proxy host> <proxy port>]"); 66 return; 67 } 68 69 HttpProxy p = new HttpProxy(host, port); 70 p.test(); 71 } 72 73 public HttpProxy(String proxyHost, int proxyPort) { 74 this.proxyHost = proxyHost; 75 this.proxyPort = proxyPort; 76 } 77 78 void test() throws Exception { 79 InetSocketAddress proxyAddress = new InetSocketAddress(proxyHost, proxyPort); 80 Proxy httpProxy = new Proxy(Proxy.Type.HTTP, proxyAddress); 81 82 try (ServerSocket ss = new ServerSocket(0); 83 Socket sock = new Socket(httpProxy)) { 84 sock.setSoTimeout(SO_TIMEOUT); 85 sock.setTcpNoDelay(false); 86 87 InetSocketAddress externalAddress = 88 new InetSocketAddress(InetAddress.getLocalHost(), ss.getLocalPort()); 89 90 out.println("Trying to connect to server socket on " + externalAddress); 91 sock.connect(externalAddress); 92 try (Socket externalSock = ss.accept()) { 93 // perform some simple checks 94 check(sock.isBound(), "Socket is not bound"); 95 check(sock.isConnected(), "Socket is not connected"); 96 check(!sock.isClosed(), "Socket should not be closed"); 97 check(sock.getSoTimeout() == SO_TIMEOUT, 98 "Socket should have a previously set timeout"); 99 check(sock.getTcpNoDelay() == false, "NODELAY should be false"); 100 101 simpleDataExchange(sock, externalSock); 102 } 103 } 104 } 105 106 static void check(boolean condition, String message) { 107 if (!condition) out.println(message); 108 } 109 110 static Exception unexpected(Exception e) { 111 out.println("Unexcepted Exception: " + e); 112 e.printStackTrace(); 113 return e; 114 } 115 116 // performs a simple exchange of data between the two sockets 117 // and throws an exception if there is any problem. 118 void simpleDataExchange(Socket s1, Socket s2) throws Exception { 119 try (final InputStream i1 = s1.getInputStream(); 120 final InputStream i2 = s2.getInputStream(); 121 final OutputStream o1 = s1.getOutputStream(); 122 final OutputStream o2 = s2.getOutputStream()) { 123 startSimpleWriter("simpleWriter1", o1, 100); 124 startSimpleWriter("simpleWriter2", o2, 200); 125 simpleRead(i2, 100); 126 simpleRead(i1, 200); 127 } 128 } 129 130 void startSimpleWriter(String threadName, final OutputStream os, final int start) { 131 (new Thread(new Runnable() { 147 void simpleRead(InputStream is, int start) throws Exception { 148 byte b[] = new byte [2]; 149 for (int i=start; i<start+100; i++) { 150 int x = is.read(b); 151 if (x == 1) 152 x += is.read(b,1,1); 153 if (x!=2) 154 throw new Exception("read error"); 155 int r = bytes(b[0], b[1]); 156 if (r != i) 157 throw new Exception("read " + r + " expected " +i); 158 } 159 } 160 161 int bytes(byte b1, byte b2) { 162 int i1 = (int)b1 & 0xFF; 163 int i2 = (int)b2 & 0xFF; 164 return i1 * 256 + i2; 165 } 166 167 static class ConnectProxyTunnelServer extends Thread { 168 169 private final ServerSocket ss; 170 171 public ConnectProxyTunnelServer() throws IOException { 172 ss = new ServerSocket(0); 173 } 174 175 @Override 176 public void run() { 177 try (Socket clientSocket = ss.accept()) { 178 processRequest(clientSocket); 179 } catch (Exception e) { 180 out.println("Proxy Failed: " + e); 181 e.printStackTrace(); 182 } finally { 183 try { ss.close(); } catch (IOException x) { unexpected(x); } 184 } 185 } 186 187 /** 188 * Returns the port on which the proxy is accepting connections. 189 */ 190 public int getLocalPort() { 191 return ss.getLocalPort(); 192 } 193 194 /* 195 * Processes the CONNECT request 196 */ 197 private void processRequest(Socket clientSocket) throws Exception { 198 MessageHeader mheader = new MessageHeader(clientSocket.getInputStream()); 199 String statusLine = mheader.getValue(0); 200 201 if (!statusLine.startsWith("CONNECT")) { 202 out.println("proxy server: processes only " 203 + "CONNECT method requests, recieved: " 204 + statusLine); 205 return; 206 } 207 208 // retrieve the host and port info from the status-line 209 InetSocketAddress serverAddr = getConnectInfo(statusLine); 210 211 //open socket to the server 212 try (Socket serverSocket = new Socket(serverAddr.getAddress(), 213 serverAddr.getPort())) { 214 Forwarder clientFW = new Forwarder(clientSocket.getInputStream(), 215 serverSocket.getOutputStream()); 216 Thread clientForwarderThread = new Thread(clientFW, "ClientForwarder"); 217 clientForwarderThread.start(); 218 send200(clientSocket); 219 Forwarder serverFW = new Forwarder(serverSocket.getInputStream(), 220 clientSocket.getOutputStream()); 221 serverFW.run(); 222 clientForwarderThread.join(); 223 } 229 230 pout.println("HTTP/1.1 200 OK"); 231 pout.println(); 232 pout.flush(); 233 } 234 235 /* 236 * This method retrieves the hostname and port of the tunnel destination 237 * from the request line. 238 * @param connectStr 239 * of the form: <i>CONNECT server-name:server-port HTTP/1.x</i> 240 */ 241 static InetSocketAddress getConnectInfo(String connectStr) 242 throws Exception 243 { 244 try { 245 int starti = connectStr.indexOf(' '); 246 int endi = connectStr.lastIndexOf(' '); 247 String connectInfo = connectStr.substring(starti+1, endi).trim(); 248 // retrieve server name and port 249 endi = connectInfo.indexOf(':'); 250 String name = connectInfo.substring(0, endi); 251 int port = Integer.parseInt(connectInfo.substring(endi+1)); 252 return new InetSocketAddress(name, port); 253 } catch (Exception e) { 254 out.println("Proxy recieved a request: " + connectStr); 255 throw unexpected(e); 256 } 257 } 258 } 259 260 /* Reads from the given InputStream and writes to the given OutputStream */ 261 static class Forwarder implements Runnable 262 { 263 private final InputStream in; 264 private final OutputStream os; 265 266 Forwarder(InputStream in, OutputStream os) { 267 this.in = in; 268 this.os = os; 269 } 270 271 @Override 272 public void run() { 273 try { 274 byte[] ba = new byte[1024]; | 1 /* 2 * Copyright (c) 2010, 2019, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 /* 25 * @test 26 * @bug 6370908 8220663 27 * @summary Add support for HTTP_CONNECT proxy in Socket class 28 * @modules java.base/sun.net.www 29 * @run main HttpProxy 30 * @run main/othervm -Djava.net.preferIPv4Stack=true HttpProxy 31 * @run main/othervm -Djava.net.preferIPv6Addresses=true HttpProxy 32 */ 33 34 import java.io.IOException; 35 import java.io.InputStream; 36 import java.io.OutputStream; 37 import java.io.PrintWriter; 38 import static java.lang.System.out; 39 import java.net.InetAddress; 40 import java.net.InetSocketAddress; 41 import java.net.Proxy; 42 import java.net.ServerSocket; 43 import java.net.Socket; 44 import java.net.SocketAddress; 45 import java.util.ArrayList; 46 import java.util.List; 47 import sun.net.www.MessageHeader; 48 49 public class HttpProxy { 50 final String proxyHost; 51 final int proxyPort; 52 static final int SO_TIMEOUT = 15000; 53 54 public static void main(String[] args) throws Exception { 55 String host; 56 int port; 57 ConnectProxyTunnelServer proxy = null; 58 if (args.length == 0) { 59 // Start internal proxy 60 proxy = new ConnectProxyTunnelServer(); 61 proxy.start(); 62 host = "localhost"; 63 port = proxy.getLocalPort(); 64 out.println("Running with internal proxy: " + host + ":" + port); 65 } else if (args.length == 2) { 66 host = args[0]; 67 port = Integer.valueOf(args[1]); 68 out.println("Running against specified proxy server: " + host + ":" + port); 69 } else { 70 System.err.println("Usage: java HttpProxy [<proxy host> <proxy port>]"); 71 return; 72 } 73 74 try { 75 HttpProxy p = new HttpProxy(host, port); 76 p.test(); 77 } finally { 78 if (proxy != null) 79 proxy.close(); 80 } 81 } 82 83 public HttpProxy(String proxyHost, int proxyPort) { 84 this.proxyHost = proxyHost; 85 this.proxyPort = proxyPort; 86 } 87 88 void test() throws Exception { 89 InetSocketAddress proxyAddress = new InetSocketAddress(proxyHost, proxyPort); 90 Proxy httpProxy = new Proxy(Proxy.Type.HTTP, proxyAddress); 91 92 try (ServerSocket ss = new ServerSocket(0)) { 93 List<InetSocketAddress> externalAddresses = new ArrayList<>(); 94 externalAddresses.add( 95 new InetSocketAddress(InetAddress.getLocalHost(), ss.getLocalPort())); 96 97 if (!"true".equals(System.getProperty("java.net.preferIPv4Stack"))) { 98 byte[] bytes = new byte[] {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}; 99 var address = InetAddress.getByAddress(bytes); 100 externalAddresses.add( 101 new InetSocketAddress(address, ss.getLocalPort())); 102 } 103 104 for (SocketAddress externalAddress : externalAddresses) { 105 try (Socket sock = new Socket(httpProxy)) { 106 sock.setSoTimeout(SO_TIMEOUT); 107 sock.setTcpNoDelay(false); 108 109 out.println("Trying to connect to server socket on " + externalAddress); 110 sock.connect(externalAddress); 111 try (Socket externalSock = ss.accept()) { 112 // perform some simple checks 113 check(sock.isBound(), "Socket is not bound"); 114 check(sock.isConnected(), "Socket is not connected"); 115 check(!sock.isClosed(), "Socket should not be closed"); 116 check(sock.getSoTimeout() == SO_TIMEOUT, 117 "Socket should have a previously set timeout"); 118 check(sock.getTcpNoDelay() == false, "NODELAY should be false"); 119 120 simpleDataExchange(sock, externalSock); 121 } 122 } 123 } 124 } 125 } 126 127 static void check(boolean condition, String message) { 128 if (!condition) out.println(message); 129 } 130 131 static Exception unexpected(Exception e) { 132 out.println("Unexpected Exception: " + e); 133 e.printStackTrace(); 134 return e; 135 } 136 137 // performs a simple exchange of data between the two sockets 138 // and throws an exception if there is any problem. 139 void simpleDataExchange(Socket s1, Socket s2) throws Exception { 140 try (final InputStream i1 = s1.getInputStream(); 141 final InputStream i2 = s2.getInputStream(); 142 final OutputStream o1 = s1.getOutputStream(); 143 final OutputStream o2 = s2.getOutputStream()) { 144 startSimpleWriter("simpleWriter1", o1, 100); 145 startSimpleWriter("simpleWriter2", o2, 200); 146 simpleRead(i2, 100); 147 simpleRead(i1, 200); 148 } 149 } 150 151 void startSimpleWriter(String threadName, final OutputStream os, final int start) { 152 (new Thread(new Runnable() { 168 void simpleRead(InputStream is, int start) throws Exception { 169 byte b[] = new byte [2]; 170 for (int i=start; i<start+100; i++) { 171 int x = is.read(b); 172 if (x == 1) 173 x += is.read(b,1,1); 174 if (x!=2) 175 throw new Exception("read error"); 176 int r = bytes(b[0], b[1]); 177 if (r != i) 178 throw new Exception("read " + r + " expected " +i); 179 } 180 } 181 182 int bytes(byte b1, byte b2) { 183 int i1 = (int)b1 & 0xFF; 184 int i2 = (int)b2 & 0xFF; 185 return i1 * 256 + i2; 186 } 187 188 static class ConnectProxyTunnelServer extends Thread implements AutoCloseable { 189 190 private final ServerSocket ss; 191 private volatile boolean closed; 192 193 public ConnectProxyTunnelServer() throws IOException { 194 ss = new ServerSocket(0); 195 } 196 197 @Override 198 public void run() { 199 try { 200 while (!closed) { 201 try (Socket clientSocket = ss.accept()) { 202 processRequest(clientSocket); 203 } 204 } 205 } catch (Exception e) { 206 if (!closed) { 207 out.println("Proxy Failed: " + e); 208 e.printStackTrace(); 209 } 210 } finally { 211 if (!closed) 212 try { ss.close(); } catch (IOException x) { unexpected(x); } 213 } 214 } 215 216 /** 217 * Returns the port on which the proxy is accepting connections. 218 */ 219 public int getLocalPort() { 220 return ss.getLocalPort(); 221 } 222 223 @Override 224 public void close() throws Exception { 225 closed = true; 226 ss.close(); 227 } 228 229 /* 230 * Processes the CONNECT request 231 */ 232 private void processRequest(Socket clientSocket) throws Exception { 233 MessageHeader mheader = new MessageHeader(clientSocket.getInputStream()); 234 String statusLine = mheader.getValue(0); 235 236 if (!statusLine.startsWith("CONNECT")) { 237 out.println("proxy server: processes only " 238 + "CONNECT method requests, received: " 239 + statusLine); 240 return; 241 } 242 243 // retrieve the host and port info from the status-line 244 InetSocketAddress serverAddr = getConnectInfo(statusLine); 245 246 //open socket to the server 247 try (Socket serverSocket = new Socket(serverAddr.getAddress(), 248 serverAddr.getPort())) { 249 Forwarder clientFW = new Forwarder(clientSocket.getInputStream(), 250 serverSocket.getOutputStream()); 251 Thread clientForwarderThread = new Thread(clientFW, "ClientForwarder"); 252 clientForwarderThread.start(); 253 send200(clientSocket); 254 Forwarder serverFW = new Forwarder(serverSocket.getInputStream(), 255 clientSocket.getOutputStream()); 256 serverFW.run(); 257 clientForwarderThread.join(); 258 } 264 265 pout.println("HTTP/1.1 200 OK"); 266 pout.println(); 267 pout.flush(); 268 } 269 270 /* 271 * This method retrieves the hostname and port of the tunnel destination 272 * from the request line. 273 * @param connectStr 274 * of the form: <i>CONNECT server-name:server-port HTTP/1.x</i> 275 */ 276 static InetSocketAddress getConnectInfo(String connectStr) 277 throws Exception 278 { 279 try { 280 int starti = connectStr.indexOf(' '); 281 int endi = connectStr.lastIndexOf(' '); 282 String connectInfo = connectStr.substring(starti+1, endi).trim(); 283 // retrieve server name and port 284 endi = connectInfo.lastIndexOf(':'); 285 String name = connectInfo.substring(0, endi); 286 287 if (name.contains(":")) { 288 if (!(name.startsWith("[") && name.endsWith("]"))) { 289 throw new IOException("Invalid host:" + name); 290 } 291 name = name.substring(1, name.length() - 1); 292 } 293 int port = Integer.parseInt(connectInfo.substring(endi+1)); 294 return new InetSocketAddress(name, port); 295 } catch (Exception e) { 296 out.println("Proxy received a request: " + connectStr); 297 throw unexpected(e); 298 } 299 } 300 } 301 302 /* Reads from the given InputStream and writes to the given OutputStream */ 303 static class Forwarder implements Runnable 304 { 305 private final InputStream in; 306 private final OutputStream os; 307 308 Forwarder(InputStream in, OutputStream os) { 309 this.in = in; 310 this.os = os; 311 } 312 313 @Override 314 public void run() { 315 try { 316 byte[] ba = new byte[1024]; |