proxy: make it the caller's reponsibility to close the socket

This makes the code shorter as there is now a single place where the
socket should be closed.
This commit is contained in:
Florian Schmaus 2019-11-18 17:44:08 +01:00
parent 9d626bf787
commit 7afd1fdf46
4 changed files with 123 additions and 151 deletions

View File

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2015-2016 Florian Schmaus. * Copyright 2015-2019 Florian Schmaus.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -21,6 +21,16 @@ import java.net.Socket;
public interface ProxySocketConnection { public interface ProxySocketConnection {
/**
* Initiate a connection to the given host on the given port. Note that the caller is responsible for closing the
* socket in case this method throws.
*
* @param socket the socket to use to initiate the connection to the proxy.
* @param host the host to connect to.
* @param port the port to connect to.
* @param timeout the timeout in milliseconds.
* @throws IOException in case an I/O error occurs.
*/
void connect(Socket socket, String host, int port, int timeout) void connect(Socket socket, String host, int port, int timeout)
throws IOException; throws IOException;

View File

@ -39,20 +39,17 @@ public class Socks4ProxySocketConnection implements ProxySocketConnection {
@Override @Override
public void connect(Socket socket, String host, int port, int timeout) public void connect(Socket socket, String host, int port, int timeout)
throws IOException { throws IOException {
InputStream in = null;
OutputStream out = null;
String proxy_host = proxy.getProxyAddress(); String proxy_host = proxy.getProxyAddress();
int proxy_port = proxy.getProxyPort(); int proxy_port = proxy.getProxyPort();
String user = proxy.getProxyUsername(); String user = proxy.getProxyUsername();
try { socket.connect(new InetSocketAddress(proxy_host, proxy_port), timeout);
socket.connect(new InetSocketAddress(proxy_host, proxy_port), timeout); InputStream in = socket.getInputStream();
in = socket.getInputStream(); OutputStream out = socket.getOutputStream();
out = socket.getOutputStream(); socket.setTcpNoDelay(true);
socket.setTcpNoDelay(true);
byte[] buf = new byte[1024]; byte[] buf = new byte[1024];
int index = 0; int index = 0;
/* /*
1) CONNECT 1) CONNECT
@ -72,25 +69,25 @@ public class Socks4ProxySocketConnection implements ProxySocketConnection {
of all zero bits. of all zero bits.
*/ */
buf[index++] = 4; buf[index++] = 4;
buf[index++] = 1; buf[index++] = 1;
buf[index++] = (byte) (port >>> 8); buf[index++] = (byte) (port >>> 8);
buf[index++] = (byte) (port & 0xff); buf[index++] = (byte) (port & 0xff);
InetAddress inetAddress = InetAddress.getByName(proxy_host); InetAddress inetAddress = InetAddress.getByName(proxy_host);
byte[] byteAddress = inetAddress.getAddress(); byte[] byteAddress = inetAddress.getAddress();
for (int i = 0; i < byteAddress.length; i++) { for (int i = 0; i < byteAddress.length; i++) {
buf[index++] = byteAddress[i]; buf[index++] = byteAddress[i];
} }
if (user != null) { if (user != null) {
byte[] userBytes = user.getBytes(StandardCharsets.UTF_8); byte[] userBytes = user.getBytes(StandardCharsets.UTF_8);
System.arraycopy(userBytes, 0, buf, index, user.length()); System.arraycopy(userBytes, 0, buf, index, user.length());
index += user.length(); index += user.length();
} }
buf[index++] = 0; buf[index++] = 0;
out.write(buf, 0, index); out.write(buf, 0, index);
/* /*
The SOCKS server checks to see whether such a request should be granted The SOCKS server checks to see whether such a request should be granted
@ -119,43 +116,26 @@ public class Socks4ProxySocketConnection implements ProxySocketConnection {
The remaining fields are ignored. The remaining fields are ignored.
*/ */
int len = 6; int len = 6;
int s = 0; int s = 0;
while (s < len) { while (s < len) {
int i = in.read(buf, s, len - s); int i = in.read(buf, s, len - s);
if (i <= 0) { if (i <= 0) {
throw new ProxyException(ProxyInfo.ProxyType.SOCKS4,
"stream is closed");
}
s += i;
}
if (buf[0] != 0) {
throw new ProxyException(ProxyInfo.ProxyType.SOCKS4, throw new ProxyException(ProxyInfo.ProxyType.SOCKS4,
"server returns VN " + buf[0]); "stream is closed");
} }
if (buf[1] != 90) { s += i;
try {
socket.close();
}
catch (Exception eee) {
}
String message = "ProxySOCKS4: server returns CD " + buf[1];
throw new ProxyException(ProxyInfo.ProxyType.SOCKS4, message);
}
byte[] temp = new byte[2];
in.read(temp, 0, 2);
} }
catch (RuntimeException e) { if (buf[0] != 0) {
throw e; throw new ProxyException(ProxyInfo.ProxyType.SOCKS4,
"server returns VN " + buf[0]);
} }
catch (Exception e) { if (buf[1] != 90) {
try { String message = "ProxySOCKS4: server returns CD " + buf[1];
socket.close(); throw new ProxyException(ProxyInfo.ProxyType.SOCKS4, message);
}
catch (Exception eee) {
}
throw new ProxyException(ProxyInfo.ProxyType.SOCKS4, e.toString());
} }
byte[] temp = new byte[2];
in.read(temp, 0, 2);
} }
} }

View File

@ -22,9 +22,6 @@ import java.io.OutputStream;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.Socket; import java.net.Socket;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.logging.Logger;
import org.jivesoftware.smack.util.CloseableUtil;
/** /**
* Socket factory for Socks5 proxy. * Socket factory for Socks5 proxy.
@ -32,7 +29,6 @@ import org.jivesoftware.smack.util.CloseableUtil;
* @author Atul Aggarwal * @author Atul Aggarwal
*/ */
public class Socks5ProxySocketConnection implements ProxySocketConnection { public class Socks5ProxySocketConnection implements ProxySocketConnection {
private static final Logger LOGGER = Logger.getLogger(Socks5ProxySocketConnection.class.getName());
private final ProxyInfo proxy; private final ProxyInfo proxy;
@ -43,22 +39,19 @@ public class Socks5ProxySocketConnection implements ProxySocketConnection {
@Override @Override
public void connect(Socket socket, String host, int port, int timeout) public void connect(Socket socket, String host, int port, int timeout)
throws IOException { throws IOException {
InputStream in = null;
OutputStream out = null;
String proxy_host = proxy.getProxyAddress(); String proxy_host = proxy.getProxyAddress();
int proxy_port = proxy.getProxyPort(); int proxy_port = proxy.getProxyPort();
String user = proxy.getProxyUsername(); String user = proxy.getProxyUsername();
String passwd = proxy.getProxyPassword(); String passwd = proxy.getProxyPassword();
try { socket.connect(new InetSocketAddress(proxy_host, proxy_port), timeout);
socket.connect(new InetSocketAddress(proxy_host, proxy_port), timeout); InputStream in = socket.getInputStream();
in = socket.getInputStream(); OutputStream out = socket.getOutputStream();
out = socket.getOutputStream();
socket.setTcpNoDelay(true); socket.setTcpNoDelay(true);
byte[] buf = new byte[1024]; byte[] buf = new byte[1024];
int index = 0; int index = 0;
/* /*
+----+----------+----------+ +----+----------+----------+
@ -81,13 +74,13 @@ public class Socks5ProxySocketConnection implements ProxySocketConnection {
o X'FF' NO ACCEPTABLE METHODS o X'FF' NO ACCEPTABLE METHODS
*/ */
buf[index++] = 5; buf[index++] = 5;
buf[index++] = 2; buf[index++] = 2;
buf[index++] = 0; // NO AUTHENTICATION REQUIRED buf[index++] = 0; // NO AUTHENTICATION REQUIRED
buf[index++] = 2; // USERNAME/PASSWORD buf[index++] = 2; // USERNAME/PASSWORD
out.write(buf, 0, index); out.write(buf, 0, index);
/* /*
The server selects from one of the methods given in METHODS, and The server selects from one of the methods given in METHODS, and
@ -99,17 +92,17 @@ public class Socks5ProxySocketConnection implements ProxySocketConnection {
| 1 | 1 | | 1 | 1 |
+----+--------+ +----+--------+
*/ */
fill(in, buf, 2); fill(in, buf, 2);
boolean check = false; boolean check = false;
switch (buf[1] & 0xff) { switch (buf[1] & 0xff) {
case 0: // NO AUTHENTICATION REQUIRED case 0: // NO AUTHENTICATION REQUIRED
check = true; check = true;
break;
case 2: // USERNAME/PASSWORD
if (user == null || passwd == null) {
break; break;
case 2: // USERNAME/PASSWORD }
if (user == null || passwd == null) {
break;
}
/* /*
Once the SOCKS V5 server has started, and the client has selected the Once the SOCKS V5 server has started, and the client has selected the
@ -130,20 +123,19 @@ public class Socks5ProxySocketConnection implements ProxySocketConnection {
PASSWD field that follows. The PASSWD field contains the password PASSWD field that follows. The PASSWD field contains the password
association with the given UNAME. association with the given UNAME.
*/ */
index = 0; index = 0;
buf[index++] = 1; buf[index++] = 1;
buf[index++] = (byte) user.length(); buf[index++] = (byte) user.length();
byte[] userBytes = user.getBytes(StandardCharsets.UTF_8); byte[] userBytes = user.getBytes(StandardCharsets.UTF_8);
System.arraycopy(userBytes, 0, buf, index, System.arraycopy(userBytes, 0, buf, index,
user.length()); user.length());
index += user.length(); index += user.length();
byte[] passwordBytes = passwd.getBytes(StandardCharsets.UTF_8); byte[] passwordBytes = passwd.getBytes(StandardCharsets.UTF_8);
buf[index++] = (byte) passwordBytes.length; buf[index++] = (byte) passwordBytes.length;
System.arraycopy(passwordBytes, 0, buf, index, System.arraycopy(passwordBytes, 0, buf, index,
passwd.length()); passwd.length());
index += passwd.length(); index += passwd.length();
out.write(buf, 0, index);
out.write(buf, 0, index);
/* /*
The server verifies the supplied UNAME and PASSWD, and sends the The server verifies the supplied UNAME and PASSWD, and sends the
@ -159,19 +151,18 @@ public class Socks5ProxySocketConnection implements ProxySocketConnection {
`failure' (STATUS value other than X'00') status, it MUST close the `failure' (STATUS value other than X'00') status, it MUST close the
connection. connection.
*/ */
fill(in, buf, 2); fill(in, buf, 2);
if (buf[1] == 0) { if (buf[1] == 0) {
check = true; check = true;
} }
break; break;
default: default:
} }
if (!check) { if (!check) {
CloseableUtil.maybeClose(socket, LOGGER); throw new ProxyException(ProxyInfo.ProxyType.SOCKS5,
throw new ProxyException(ProxyInfo.ProxyType.SOCKS5, "fail in SOCKS5 proxy");
"fail in SOCKS5 proxy"); }
}
/* /*
The SOCKS request is formed as follows: The SOCKS request is formed as follows:
@ -199,21 +190,21 @@ public class Socks5ProxySocketConnection implements ProxySocketConnection {
order order
*/ */
index = 0; index = 0;
buf[index++] = 5; buf[index++] = 5;
buf[index++] = 1; // CONNECT buf[index++] = 1; // CONNECT
buf[index++] = 0; buf[index++] = 0;
byte[] hostb = host.getBytes(StandardCharsets.UTF_8); byte[] hostb = host.getBytes(StandardCharsets.UTF_8);
int len = hostb.length; int len = hostb.length;
buf[index++] = 3; // DOMAINNAME buf[index++] = 3; // DOMAINNAME
buf[index++] = (byte) len; buf[index++] = (byte) len;
System.arraycopy(hostb, 0, buf, index, len); System.arraycopy(hostb, 0, buf, index, len);
index += len; index += len;
buf[index++] = (byte) (port >>> 8); buf[index++] = (byte) (port >>> 8);
buf[index++] = (byte) (port & 0xff); buf[index++] = (byte) (port & 0xff);
out.write(buf, 0, index); out.write(buf, 0, index);
/* /*
The SOCKS request information is sent by the client as soon as it has The SOCKS request information is sent by the client as soon as it has
@ -250,35 +241,25 @@ public class Socks5ProxySocketConnection implements ProxySocketConnection {
o BND.PORT server bound port in network octet order o BND.PORT server bound port in network octet order
*/ */
fill(in, buf, 4); fill(in, buf, 4);
if (buf[1] != 0) { if (buf[1] != 0) {
CloseableUtil.maybeClose(socket, LOGGER); throw new ProxyException(ProxyInfo.ProxyType.SOCKS5,
throw new ProxyException(ProxyInfo.ProxyType.SOCKS5, "server returns " + buf[1]);
"server returns " + buf[1]); }
}
switch (buf[3] & 0xff) { switch (buf[3] & 0xff) {
case 1: case 1:
fill(in, buf, 6); fill(in, buf, 6);
break; break;
case 3: case 3:
fill(in, buf, 1); fill(in, buf, 1);
fill(in, buf, (buf[0] & 0xff) + 2); fill(in, buf, (buf[0] & 0xff) + 2);
break; break;
case 4: case 4:
fill(in, buf, 18); fill(in, buf, 18);
break; break;
default: default:
}
}
catch (RuntimeException e) {
throw e;
}
catch (Exception e) {
CloseableUtil.maybeClose(socket, LOGGER);
// TODO convert to IOException(e) when minimum Android API level is 9 or higher
throw new IOException(e.getLocalizedMessage());
} }
} }

View File

@ -606,6 +606,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
try { try {
proxyInfo.getProxySocketConnection().connect(socket, host, port, timeout); proxyInfo.getProxySocketConnection().connect(socket, host, port, timeout);
} catch (IOException e) { } catch (IOException e) {
CloseableUtil.maybeClose(socket, LOGGER);
hostAddress.setException(e); hostAddress.setException(e);
failedAddresses.add(hostAddress); failedAddresses.add(hostAddress);
continue; continue;