mirror of
https://codeberg.org/Mercury-IM/Smack
synced 2025-12-08 22:21:08 +01:00
Rework SOCKS5 unit tests so that they can be run in parallel
As result it is now also possible to start multiple local SOCKS5 proxies with different port, which is usually not necessary in real life but useful for unit tests.
This commit is contained in:
parent
d337474a86
commit
9352225f44
10 changed files with 653 additions and 709 deletions
|
|
@ -56,6 +56,7 @@ import org.jivesoftware.smackx.disco.packet.DiscoverItems;
|
|||
import org.jivesoftware.smackx.disco.packet.DiscoverItems.Item;
|
||||
import org.jivesoftware.smackx.filetransfer.FileTransferManager;
|
||||
|
||||
import org.jxmpp.jid.EntityFullJid;
|
||||
import org.jxmpp.jid.Jid;
|
||||
|
||||
/**
|
||||
|
|
@ -149,6 +150,8 @@ public final class Socks5BytestreamManager extends Manager implements Bytestream
|
|||
/* flag to enable/disable prioritization of last working proxy */
|
||||
private boolean proxyPrioritizationEnabled = true;
|
||||
|
||||
private boolean annouceLocalStreamHost = true;
|
||||
|
||||
/*
|
||||
* list containing session IDs of SOCKS5 Bytestream initialization packets that should be
|
||||
* ignored by the InitiationListener
|
||||
|
|
@ -375,6 +378,30 @@ public final class Socks5BytestreamManager extends Manager implements Bytestream
|
|||
this.proxyPrioritizationEnabled = proxyPrioritizationEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if the bytestream manager will announce the local stream host(s), i.e. the local SOCKS5 proxy.
|
||||
* <p>
|
||||
* Local stream hosts will be announced if this option is enabled and at least one is running.
|
||||
* </p>
|
||||
*
|
||||
* @return <code>true</code> if
|
||||
* @since 4.4.0
|
||||
*/
|
||||
public boolean isAnnouncingLocalStreamHostEnabled() {
|
||||
return annouceLocalStreamHost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether or not the bytestream manager will annouce the local stream host(s), i.e. the local SOCKS5 proxy.
|
||||
*
|
||||
* @param announceLocalStreamHost
|
||||
* @see #isAnnouncingLocalStreamHostEnabled()
|
||||
* @since 4.4.0
|
||||
*/
|
||||
public void setAnnounceLocalStreamHost(boolean announceLocalStreamHost) {
|
||||
this.annouceLocalStreamHost = announceLocalStreamHost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes a SOCKS5 Bytestream with the given user and returns the Socket to send/receive
|
||||
* data to/from the user.
|
||||
|
|
@ -592,10 +619,12 @@ public final class Socks5BytestreamManager extends Manager implements Bytestream
|
|||
XMPPConnection connection = connection();
|
||||
List<StreamHost> streamHosts = new ArrayList<>();
|
||||
|
||||
// add local proxy on first position if exists
|
||||
List<StreamHost> localProxies = getLocalStreamHost();
|
||||
if (localProxies != null) {
|
||||
streamHosts.addAll(localProxies);
|
||||
if (annouceLocalStreamHost) {
|
||||
// add local proxy on first position if exists
|
||||
List<StreamHost> localProxies = getLocalStreamHost();
|
||||
if (localProxies != null) {
|
||||
streamHosts.addAll(localProxies);
|
||||
}
|
||||
}
|
||||
|
||||
// query SOCKS5 proxies for network settings
|
||||
|
|
@ -636,34 +665,33 @@ public final class Socks5BytestreamManager extends Manager implements Bytestream
|
|||
* is not running
|
||||
*/
|
||||
public List<StreamHost> getLocalStreamHost() {
|
||||
XMPPConnection connection = connection();
|
||||
// get local proxy singleton
|
||||
Socks5Proxy socks5Server = Socks5Proxy.getSocks5Proxy();
|
||||
|
||||
if (!socks5Server.isRunning()) {
|
||||
// server is not running
|
||||
return null;
|
||||
}
|
||||
List<String> addresses = socks5Server.getLocalAddresses();
|
||||
if (addresses.isEmpty()) {
|
||||
// local address could not be determined
|
||||
return null;
|
||||
}
|
||||
final int port = socks5Server.getPort();
|
||||
|
||||
List<StreamHost> streamHosts = new ArrayList<>();
|
||||
outerloop: for (String address : addresses) {
|
||||
// Prevent loopback addresses from appearing as streamhost
|
||||
final String[] loopbackAddresses = { "127.0.0.1", "0:0:0:0:0:0:0:1", "::1" };
|
||||
for (String loopbackAddress : loopbackAddresses) {
|
||||
// Use 'startsWith' here since IPv6 addresses may have scope ID,
|
||||
// ie. the part after the '%' sign.
|
||||
if (address.startsWith(loopbackAddress)) {
|
||||
continue outerloop;
|
||||
}
|
||||
|
||||
XMPPConnection connection = connection();
|
||||
EntityFullJid myJid = connection.getUser();
|
||||
|
||||
for (Socks5Proxy socks5Server : Socks5Proxy.getRunningProxies()) {
|
||||
List<String> addresses = socks5Server.getLocalAddresses();
|
||||
if (addresses.isEmpty()) {
|
||||
// local address could not be determined
|
||||
return null;
|
||||
}
|
||||
final int port = socks5Server.getPort();
|
||||
|
||||
outerloop: for (String address : addresses) {
|
||||
// Prevent loopback addresses from appearing as streamhost
|
||||
final String[] loopbackAddresses = { "127.0.0.1", "0:0:0:0:0:0:0:1", "::1" };
|
||||
for (String loopbackAddress : loopbackAddresses) {
|
||||
// Use 'startsWith' here since IPv6 addresses may have scope ID,
|
||||
// ie. the part after the '%' sign.
|
||||
if (address.startsWith(loopbackAddress)) {
|
||||
continue outerloop;
|
||||
}
|
||||
}
|
||||
streamHosts.add(new StreamHost(myJid, address, port));
|
||||
}
|
||||
streamHosts.add(new StreamHost(connection.getUser(), address, port));
|
||||
}
|
||||
|
||||
return streamHosts;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -57,12 +57,14 @@ public class Socks5BytestreamRequest implements BytestreamRequest {
|
|||
private static final Cache<String, Integer> ADDRESS_BLACKLIST = new ExpirationCache<String, Integer>(
|
||||
BLACKLIST_MAX_SIZE, BLACKLIST_LIFETIME);
|
||||
|
||||
private static int DEFAULT_CONNECTION_FAILURE_THRESHOLD = 2;
|
||||
|
||||
/*
|
||||
* The number of connection failures it takes for a particular SOCKS5 proxy to be blacklisted.
|
||||
* When a proxy is blacklisted no more connection attempts will be made to it for a period of 2
|
||||
* hours.
|
||||
*/
|
||||
private static int CONNECTION_FAILURE_THRESHOLD = 2;
|
||||
private int connectionFailureThreshold = DEFAULT_CONNECTION_FAILURE_THRESHOLD;
|
||||
|
||||
/* the bytestream initialization request */
|
||||
private Bytestream bytestreamRequest;
|
||||
|
|
@ -76,6 +78,28 @@ public class Socks5BytestreamRequest implements BytestreamRequest {
|
|||
/* minimum timeout to connect to one SOCKS5 proxy */
|
||||
private int minimumConnectTimeout = 2000;
|
||||
|
||||
/**
|
||||
* Returns the default connection failure threshold.
|
||||
*
|
||||
* @return the default connection failure threshold.
|
||||
* @see #setConnectFailureThreshold(int)
|
||||
* @since 4.4.0
|
||||
*/
|
||||
public static int getDefaultConnectFailureThreshold() {
|
||||
return DEFAULT_CONNECTION_FAILURE_THRESHOLD;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default connection failure threshold.
|
||||
*
|
||||
* @param defaultConnectFailureThreshold the default connection failure threshold.
|
||||
* @see #setConnectFailureThreshold(int)
|
||||
* @since 4.4.0
|
||||
*/
|
||||
public static void setDefaultConnectFailureThreshold(int defaultConnectFailureThreshold) {
|
||||
DEFAULT_CONNECTION_FAILURE_THRESHOLD = defaultConnectFailureThreshold;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of connection failures it takes for a particular SOCKS5 proxy to be
|
||||
* blacklisted. When a proxy is blacklisted no more connection attempts will be made to it for a
|
||||
|
|
@ -84,8 +108,8 @@ public class Socks5BytestreamRequest implements BytestreamRequest {
|
|||
* @return the number of connection failures it takes for a particular SOCKS5 proxy to be
|
||||
* blacklisted
|
||||
*/
|
||||
public static int getConnectFailureThreshold() {
|
||||
return CONNECTION_FAILURE_THRESHOLD;
|
||||
public int getConnectFailureThreshold() {
|
||||
return connectionFailureThreshold;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -98,8 +122,8 @@ public class Socks5BytestreamRequest implements BytestreamRequest {
|
|||
* @param connectFailureThreshold the number of connection failures it takes for a particular
|
||||
* SOCKS5 proxy to be blacklisted
|
||||
*/
|
||||
public static void setConnectFailureThreshold(int connectFailureThreshold) {
|
||||
CONNECTION_FAILURE_THRESHOLD = connectFailureThreshold;
|
||||
public void setConnectFailureThreshold(int connectFailureThreshold) {
|
||||
connectionFailureThreshold = connectFailureThreshold;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -234,7 +258,7 @@ public class Socks5BytestreamRequest implements BytestreamRequest {
|
|||
|
||||
// check to see if this address has been blacklisted
|
||||
int failures = getConnectionFailures(address);
|
||||
if (CONNECTION_FAILURE_THRESHOLD > 0 && failures >= CONNECTION_FAILURE_THRESHOLD) {
|
||||
if (connectionFailureThreshold > 0 && failures >= connectionFailureThreshold) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ import org.jivesoftware.smack.SmackException.NoResponseException;
|
|||
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
||||
import org.jivesoftware.smack.SmackException.SmackMessageException;
|
||||
import org.jivesoftware.smack.XMPPException;
|
||||
import org.jivesoftware.smack.util.Async;
|
||||
import org.jivesoftware.smack.util.CloseableUtil;
|
||||
|
||||
import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream.StreamHost;
|
||||
|
|
@ -111,27 +112,14 @@ public class Socks5Client {
|
|||
}
|
||||
|
||||
});
|
||||
Thread executor = new Thread(futureTask);
|
||||
executor.start();
|
||||
Async.go(futureTask, "SOCKS5 client connecting to " + streamHost);
|
||||
|
||||
// get connection to initiator with timeout
|
||||
try {
|
||||
return futureTask.get(timeout, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
catch (ExecutionException e) {
|
||||
Throwable cause = e.getCause();
|
||||
if (cause != null) {
|
||||
// case exceptions to comply with method signature
|
||||
if (cause instanceof IOException) {
|
||||
throw (IOException) cause;
|
||||
}
|
||||
if (cause instanceof SmackMessageException) {
|
||||
throw (SmackMessageException) cause;
|
||||
}
|
||||
}
|
||||
|
||||
// throw generic Smack exception if unexpected exception was thrown
|
||||
throw new IllegalStateException("Error while connecting to SOCKS5 proxy", e);
|
||||
throw new IOException("ExecutionException while SOCKS5 client attempting to connect to " + streamHost, e);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -79,8 +79,7 @@ public class Socks5ClientForInitiator extends Socks5Client {
|
|||
|
||||
// check if stream host is the local SOCKS5 proxy
|
||||
if (this.streamHost.getJID().equals(this.connection.get().getUser())) {
|
||||
Socks5Proxy socks5Server = Socks5Proxy.getSocks5Proxy();
|
||||
socket = socks5Server.getSocket(this.digest);
|
||||
socket = Socks5Proxy.getSocketForDigest(this.digest);
|
||||
if (socket == null) {
|
||||
throw new SmackException.SmackMessageException("target is not connected to SOCKS5 proxy");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
|
|
@ -73,6 +74,8 @@ import org.jivesoftware.smack.util.CloseableUtil;
|
|||
public final class Socks5Proxy {
|
||||
private static final Logger LOGGER = Logger.getLogger(Socks5Proxy.class.getName());
|
||||
|
||||
private static final List<Socks5Proxy> RUNNING_PROXIES = new CopyOnWriteArrayList<>();
|
||||
|
||||
/* SOCKS5 proxy singleton */
|
||||
private static Socks5Proxy socks5Server;
|
||||
|
||||
|
|
@ -104,7 +107,7 @@ public final class Socks5Proxy {
|
|||
/**
|
||||
* Private constructor.
|
||||
*/
|
||||
private Socks5Proxy() {
|
||||
Socks5Proxy() {
|
||||
this.serverProcess = new Socks5ServerProcess();
|
||||
|
||||
Enumeration<NetworkInterface> networkInterfaces;
|
||||
|
|
@ -188,9 +191,9 @@ public final class Socks5Proxy {
|
|||
/**
|
||||
* Starts the local SOCKS5 proxy server. If it is already running, this method does nothing.
|
||||
*/
|
||||
public synchronized void start() {
|
||||
public synchronized ServerSocket start() {
|
||||
if (isRunning()) {
|
||||
return;
|
||||
return this.serverSocket;
|
||||
}
|
||||
try {
|
||||
if (getLocalSocks5ProxyPort() < 0) {
|
||||
|
|
@ -213,6 +216,8 @@ public final class Socks5Proxy {
|
|||
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();
|
||||
}
|
||||
}
|
||||
|
|
@ -220,6 +225,8 @@ public final class Socks5Proxy {
|
|||
// couldn't setup server
|
||||
LOGGER.log(Level.SEVERE, "couldn't setup local SOCKS5 proxy on port " + getLocalSocks5ProxyPort(), e);
|
||||
}
|
||||
|
||||
return this.serverSocket;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -230,6 +237,8 @@ public final class Socks5Proxy {
|
|||
return;
|
||||
}
|
||||
|
||||
RUNNING_PROXIES.remove(this);
|
||||
|
||||
CloseableUtil.maybeClose(this.serverSocket, LOGGER);
|
||||
|
||||
if (this.serverThread != null && this.serverThread.isAlive()) {
|
||||
|
|
@ -483,4 +492,17 @@ public final class Socks5Proxy {
|
|||
|
||||
}
|
||||
|
||||
public static Socket getSocketForDigest(String digest) {
|
||||
for (Socks5Proxy socks5Proxy : RUNNING_PROXIES) {
|
||||
Socket socket = socks5Proxy.getSocket(digest);
|
||||
if (socket != null) {
|
||||
return socket;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<Socks5Proxy> getRunningProxies() {
|
||||
return RUNNING_PROXIES;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue