mirror of
https://codeberg.org/Mercury-IM/Smack
synced 2024-11-22 14:22:05 +01:00
Make Socks5TestProxy a subclass of Socks5Proxy
to reduce the duplicated code. This also means we are now testing the real implementation.
This commit is contained in:
parent
6e1193edaf
commit
9bb36fc63c
2 changed files with 42 additions and 236 deletions
|
@ -72,7 +72,7 @@ import org.jivesoftware.smack.util.CloseableUtil;
|
||||||
*
|
*
|
||||||
* @author Henning Staib
|
* @author Henning Staib
|
||||||
*/
|
*/
|
||||||
public final class Socks5Proxy {
|
public class Socks5Proxy {
|
||||||
private static final Logger LOGGER = Logger.getLogger(Socks5Proxy.class.getName());
|
private static final Logger LOGGER = Logger.getLogger(Socks5Proxy.class.getName());
|
||||||
|
|
||||||
private static final List<Socks5Proxy> RUNNING_PROXIES = new CopyOnWriteArrayList<>();
|
private static final List<Socks5Proxy> RUNNING_PROXIES = new CopyOnWriteArrayList<>();
|
||||||
|
@ -95,7 +95,7 @@ public final class Socks5Proxy {
|
||||||
private int localSocks5ProxyPort = -7777;
|
private int localSocks5ProxyPort = -7777;
|
||||||
|
|
||||||
/* reusable implementation of a SOCKS5 proxy server process */
|
/* reusable implementation of a SOCKS5 proxy server process */
|
||||||
private Socks5ServerProcess serverProcess;
|
private final Socks5ServerProcess serverProcess;
|
||||||
|
|
||||||
/* thread running the SOCKS5 server process */
|
/* thread running the SOCKS5 server process */
|
||||||
private Thread serverThread;
|
private Thread serverThread;
|
||||||
|
@ -111,12 +111,20 @@ public final class Socks5Proxy {
|
||||||
|
|
||||||
private final Set<InetAddress> localAddresses = new LinkedHashSet<>(4);
|
private final Set<InetAddress> localAddresses = new LinkedHashSet<>(4);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If set to <code>true</code>, then all connections are allowed and the digest is not verified. Should be set to
|
||||||
|
* <code>false</code> for production usage and <code>true</code> for (unit) testing purposes.
|
||||||
|
*/
|
||||||
|
private final boolean allowAllConnections;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Private constructor.
|
* Private constructor.
|
||||||
*/
|
*/
|
||||||
Socks5Proxy() {
|
Socks5Proxy() {
|
||||||
this.serverProcess = new Socks5ServerProcess();
|
this.serverProcess = new Socks5ServerProcess();
|
||||||
|
|
||||||
|
allowAllConnections = false;
|
||||||
|
|
||||||
Enumeration<NetworkInterface> networkInterfaces;
|
Enumeration<NetworkInterface> networkInterfaces;
|
||||||
try {
|
try {
|
||||||
networkInterfaces = NetworkInterface.getNetworkInterfaces();
|
networkInterfaces = NetworkInterface.getNetworkInterfaces();
|
||||||
|
@ -136,6 +144,22 @@ public final class Socks5Proxy {
|
||||||
replaceLocalAddresses(localAddresses);
|
replaceLocalAddresses(localAddresses);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor a Socks5Proxy with the given socket. Used for unit test purposes.
|
||||||
|
*
|
||||||
|
* @param serverSocket the server socket to use
|
||||||
|
*/
|
||||||
|
protected Socks5Proxy(ServerSocket serverSocket) {
|
||||||
|
this.serverProcess = new Socks5ServerProcess();
|
||||||
|
this.serverSocket = serverSocket;
|
||||||
|
|
||||||
|
allowAllConnections = true;
|
||||||
|
|
||||||
|
startServerThread();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the local Socks5 proxy should be started. Default is true.
|
* Returns true if the local Socks5 proxy should be started. Default is true.
|
||||||
*
|
*
|
||||||
|
@ -229,12 +253,7 @@ public final class Socks5Proxy {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.serverSocket != null) {
|
if (this.serverSocket != null) {
|
||||||
this.serverThread = new Thread(this.serverProcess);
|
startServerThread();
|
||||||
this.serverThread.setName("Smack Local SOCKS5 Proxy [" + this.serverSocket + ']');
|
|
||||||
this.serverThread.setDaemon(true);
|
|
||||||
|
|
||||||
RUNNING_PROXIES.add(this);
|
|
||||||
this.serverThread.start();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (IOException e) {
|
catch (IOException e) {
|
||||||
|
@ -245,6 +264,15 @@ public final class Socks5Proxy {
|
||||||
return this.serverSocket;
|
return this.serverSocket;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private synchronized void startServerThread() {
|
||||||
|
this.serverThread = new Thread(this.serverProcess);
|
||||||
|
this.serverThread.setName("Smack Local SOCKS5 Proxy [" + this.serverSocket + ']');
|
||||||
|
this.serverThread.setDaemon(true);
|
||||||
|
|
||||||
|
RUNNING_PROXIES.add(this);
|
||||||
|
this.serverThread.start();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stops the local SOCKS5 proxy server. If it is not running this method does nothing.
|
* Stops the local SOCKS5 proxy server. If it is not running this method does nothing.
|
||||||
*/
|
*/
|
||||||
|
@ -490,7 +518,7 @@ public final class Socks5Proxy {
|
||||||
String responseDigest = new String(connectionRequest, 5, connectionRequest[4], StandardCharsets.UTF_8);
|
String responseDigest = new String(connectionRequest, 5, connectionRequest[4], StandardCharsets.UTF_8);
|
||||||
|
|
||||||
// return error if digest is not allowed
|
// return error if digest is not allowed
|
||||||
if (!Socks5Proxy.this.allowedConnections.contains(responseDigest)) {
|
if (!allowAllConnections && !Socks5Proxy.this.allowedConnections.contains(responseDigest)) {
|
||||||
connectionRequest[1] = (byte) 0x05; // set return status to 5 (connection refused)
|
connectionRequest[1] = (byte) 0x05; // set return status to 5 (connection refused)
|
||||||
out.write(connectionRequest);
|
out.write(connectionRequest);
|
||||||
out.flush();
|
out.flush();
|
||||||
|
|
|
@ -16,22 +16,9 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smackx.bytestreams.socks5;
|
package org.jivesoftware.smackx.bytestreams.socks5;
|
||||||
|
|
||||||
import java.io.DataInputStream;
|
|
||||||
import java.io.DataOutputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.ServerSocket;
|
import java.net.ServerSocket;
|
||||||
import java.net.Socket;
|
|
||||||
import java.net.SocketException;
|
|
||||||
import java.net.UnknownHostException;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import org.jivesoftware.smack.SmackException;
|
|
||||||
import org.jivesoftware.smack.util.CloseableUtil;
|
|
||||||
import org.jivesoftware.smack.util.NetworkUtil;
|
import org.jivesoftware.smack.util.NetworkUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -40,225 +27,16 @@ import org.jivesoftware.smack.util.NetworkUtil;
|
||||||
*
|
*
|
||||||
* @author Henning Staib
|
* @author Henning Staib
|
||||||
*/
|
*/
|
||||||
public final class Socks5TestProxy implements AutoCloseable {
|
public final class Socks5TestProxy extends Socks5Proxy implements AutoCloseable {
|
||||||
private static final Logger LOGGER = Logger.getLogger(Socks5TestProxy.class.getName());
|
|
||||||
|
|
||||||
/* reusable implementation of a SOCKS5 proxy server process */
|
public Socks5TestProxy(ServerSocket serverSocket) {
|
||||||
private Socks5ServerProcess serverProcess;
|
super(serverSocket);
|
||||||
|
}
|
||||||
|
|
||||||
/* thread running the SOCKS5 server process */
|
public Socks5TestProxy() throws IOException {
|
||||||
private Thread serverThread;
|
|
||||||
|
|
||||||
/* server socket to accept SOCKS5 connections */
|
|
||||||
private final ServerSocket serverSocket;
|
|
||||||
|
|
||||||
/* assigns a connection to a digest */
|
|
||||||
private final Map<String, Socket> connectionMap = new ConcurrentHashMap<String, Socket>();
|
|
||||||
|
|
||||||
private boolean startupComplete;
|
|
||||||
|
|
||||||
Socks5TestProxy() throws IOException {
|
|
||||||
this(NetworkUtil.getSocketOnLoopback());
|
this(NetworkUtil.getSocketOnLoopback());
|
||||||
}
|
}
|
||||||
|
|
||||||
Socks5TestProxy(ServerSocket serverSocket) {
|
|
||||||
this.serverSocket = serverSocket;
|
|
||||||
this.serverProcess = new Socks5ServerProcess();
|
|
||||||
this.serverThread = new Thread(this.serverProcess);
|
|
||||||
this.serverThread.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stops the local SOCKS5 proxy server. If it is not running this method does nothing.
|
|
||||||
*/
|
|
||||||
public synchronized void stop() {
|
|
||||||
if (!isRunning()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.serverSocket.close();
|
|
||||||
}
|
|
||||||
catch (IOException e) {
|
|
||||||
// do nothing
|
|
||||||
LOGGER.log(Level.SEVERE, "exception", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.serverThread != null && this.serverThread.isAlive()) {
|
|
||||||
try {
|
|
||||||
this.serverThread.interrupt();
|
|
||||||
this.serverThread.join();
|
|
||||||
}
|
|
||||||
catch (InterruptedException e) {
|
|
||||||
// do nothing
|
|
||||||
LOGGER.log(Level.SEVERE, "exception", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.serverThread = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the host address of the local SOCKS5 proxy server.
|
|
||||||
*
|
|
||||||
* @return the host address of the local SOCKS5 proxy server
|
|
||||||
*/
|
|
||||||
public static String getAddress() {
|
|
||||||
try {
|
|
||||||
return InetAddress.getLocalHost().getHostAddress();
|
|
||||||
}
|
|
||||||
catch (UnknownHostException e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the port of the local SOCKS5 proxy server. If it is not running -1 will be returned.
|
|
||||||
*
|
|
||||||
* @return the port of the local SOCKS5 proxy server or -1 if proxy is not running
|
|
||||||
*/
|
|
||||||
public int getPort() {
|
|
||||||
if (!isRunning()) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return this.serverSocket.getLocalPort();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the socket for the given digest.
|
|
||||||
*
|
|
||||||
* @param digest identifying the connection
|
|
||||||
* @return socket or null if there is no socket for the given digest
|
|
||||||
* @throws InterruptedException
|
|
||||||
*/
|
|
||||||
public Socket getSocket(String digest) throws InterruptedException {
|
|
||||||
synchronized (this) {
|
|
||||||
long now = System.currentTimeMillis();
|
|
||||||
final long deadline = now + 5000;
|
|
||||||
while (!startupComplete && now < deadline) {
|
|
||||||
wait(deadline - now);
|
|
||||||
now = System.currentTimeMillis();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!startupComplete) {
|
|
||||||
throw new IllegalStateException("Startup of Socks5TestProxy failed within 5 seconds");
|
|
||||||
}
|
|
||||||
return this.connectionMap.get(digest);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the local SOCKS5 proxy server is running, otherwise false.
|
|
||||||
*
|
|
||||||
* @return true if the local SOCKS5 proxy server is running, otherwise false
|
|
||||||
*/
|
|
||||||
public boolean isRunning() {
|
|
||||||
return !this.serverSocket.isClosed();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Implementation of a simplified SOCKS5 proxy server.
|
|
||||||
*
|
|
||||||
* @author Henning Staib
|
|
||||||
*/
|
|
||||||
class Socks5ServerProcess implements Runnable {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
while (true) {
|
|
||||||
Socket socket = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// TODO: Add !serverSocket.isClosed() into the while condition and remove the following lines.
|
|
||||||
if (Socks5TestProxy.this.serverSocket.isClosed()
|
|
||||||
|| Thread.currentThread().isInterrupted()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// accept connection
|
|
||||||
socket = Socks5TestProxy.this.serverSocket.accept();
|
|
||||||
|
|
||||||
// initialize connection
|
|
||||||
establishConnection(socket);
|
|
||||||
|
|
||||||
synchronized (this) {
|
|
||||||
startupComplete = true;
|
|
||||||
notifyAll();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (SocketException e) {
|
|
||||||
/* do nothing */
|
|
||||||
LOGGER.log(Level.FINE, "Socket exception in Socks5TestProxy " + this, e);
|
|
||||||
}
|
|
||||||
catch (Exception e) {
|
|
||||||
LOGGER.log(Level.SEVERE, "exception", e);
|
|
||||||
CloseableUtil.maybeClose(socket, LOGGER);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Negotiates a SOCKS5 connection and stores it on success.
|
|
||||||
*
|
|
||||||
* @param socket connection to the client
|
|
||||||
* @throws SmackException if client requests a connection in an unsupported way
|
|
||||||
* @throws IOException if a network error occurred
|
|
||||||
*/
|
|
||||||
private void establishConnection(Socket socket) throws IOException, SmackException {
|
|
||||||
DataOutputStream out = new DataOutputStream(socket.getOutputStream());
|
|
||||||
DataInputStream in = new DataInputStream(socket.getInputStream());
|
|
||||||
|
|
||||||
// first byte is version should be 5
|
|
||||||
int b = in.read();
|
|
||||||
if (b != 5) {
|
|
||||||
throw new SmackException.SmackMessageException("Only SOCKS5 supported");
|
|
||||||
}
|
|
||||||
|
|
||||||
// second byte number of authentication methods supported
|
|
||||||
b = in.read();
|
|
||||||
|
|
||||||
// read list of supported authentication methods
|
|
||||||
byte[] auth = new byte[b];
|
|
||||||
in.readFully(auth);
|
|
||||||
|
|
||||||
byte[] authMethodSelectionResponse = new byte[2];
|
|
||||||
authMethodSelectionResponse[0] = (byte) 0x05; // protocol version
|
|
||||||
|
|
||||||
// only authentication method 0, no authentication, supported
|
|
||||||
boolean noAuthMethodFound = false;
|
|
||||||
for (int i = 0; i < auth.length; i++) {
|
|
||||||
if (auth[i] == (byte) 0x00) {
|
|
||||||
noAuthMethodFound = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!noAuthMethodFound) {
|
|
||||||
authMethodSelectionResponse[1] = (byte) 0xFF; // no acceptable methods
|
|
||||||
out.write(authMethodSelectionResponse);
|
|
||||||
out.flush();
|
|
||||||
throw new SmackException.SmackMessageException("Authentication method not supported");
|
|
||||||
}
|
|
||||||
|
|
||||||
authMethodSelectionResponse[1] = (byte) 0x00; // no-authentication method
|
|
||||||
out.write(authMethodSelectionResponse);
|
|
||||||
out.flush();
|
|
||||||
|
|
||||||
// receive connection request
|
|
||||||
byte[] connectionRequest = Socks5Utils.receiveSocks5Message(in);
|
|
||||||
|
|
||||||
// extract digest
|
|
||||||
String responseDigest = new String(connectionRequest, 5, connectionRequest[4], StandardCharsets.UTF_8);
|
|
||||||
|
|
||||||
connectionRequest[1] = (byte) 0x00; // set return status to 0 (success)
|
|
||||||
out.write(connectionRequest);
|
|
||||||
out.flush();
|
|
||||||
|
|
||||||
// store connection
|
|
||||||
Socks5TestProxy.this.connectionMap.put(responseDigest, socket);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
stop();
|
stop();
|
||||||
|
|
Loading…
Reference in a new issue