diff --git a/smack-bosh/src/main/java/org/jivesoftware/smack/bosh/XMPPBOSHConnection.java b/smack-bosh/src/main/java/org/jivesoftware/smack/bosh/XMPPBOSHConnection.java
index 8dff0e6e8..14d7c0e90 100644
--- a/smack-bosh/src/main/java/org/jivesoftware/smack/bosh/XMPPBOSHConnection.java
+++ b/smack-bosh/src/main/java/org/jivesoftware/smack/bosh/XMPPBOSHConnection.java
@@ -21,6 +21,7 @@ import java.io.IOException;
import java.io.PipedReader;
import java.io.PipedWriter;
import java.io.Writer;
+import java.net.InetAddress;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -314,6 +315,11 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
}
}
+ @Override
+ public InetAddress getLocalAddress() {
+ return null;
+ }
+
@Override
protected void shutdown() {
instantShutdown();
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/XMPPConnection.java b/smack-core/src/main/java/org/jivesoftware/smack/XMPPConnection.java
index 6b6c40334..0ef97910a 100644
--- a/smack-core/src/main/java/org/jivesoftware/smack/XMPPConnection.java
+++ b/smack-core/src/main/java/org/jivesoftware/smack/XMPPConnection.java
@@ -16,6 +16,7 @@
*/
package org.jivesoftware.smack;
+import java.net.InetAddress;
import java.util.concurrent.TimeUnit;
import javax.xml.namespace.QName;
@@ -160,6 +161,14 @@ public interface XMPPConnection {
*/
EntityFullJid getUser();
+ /**
+ * Returns the local address currently in use for this connection, or null
if
+ * this is invalid for the type of underlying connection.
+ *
+ * @return the local address currently in use for this connection
+ */
+ InetAddress getLocalAddress();
+
/**
* Returns the stream ID for this connection, which is the value set by the server
* when opening an XMPP stream. This value will be null
if not connected to the server.
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnection.java b/smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnection.java
index eb37ca60c..b24b00704 100644
--- a/smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnection.java
+++ b/smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnection.java
@@ -17,6 +17,7 @@
package org.jivesoftware.smack.c2s;
import java.io.IOException;
+import java.net.InetAddress;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.Collection;
@@ -1128,6 +1129,11 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
walkStateGraph(walkStateGraphContext);
}
+ @Override
+ public InetAddress getLocalAddress() {
+ return null;
+ }
+
private Map getFilterStats() {
Collection filters;
synchronized (this) {
diff --git a/smack-core/src/testFixtures/java/org/jivesoftware/smack/DummyConnection.java b/smack-core/src/testFixtures/java/org/jivesoftware/smack/DummyConnection.java
index b85ce62b5..7e105d58b 100644
--- a/smack-core/src/testFixtures/java/org/jivesoftware/smack/DummyConnection.java
+++ b/smack-core/src/testFixtures/java/org/jivesoftware/smack/DummyConnection.java
@@ -17,6 +17,7 @@
package org.jivesoftware.smack;
import java.io.IOException;
+import java.net.InetAddress;
import java.util.Date;
import java.util.Random;
import java.util.concurrent.BlockingQueue;
@@ -139,6 +140,11 @@ public class DummyConnection extends AbstractXMPPConnection {
}
}
+ @Override
+ public InetAddress getLocalAddress() {
+ return null;
+ }
+
/**
* Returns the number of packets that's sent through {@link #sendStanza(Stanza)} and
* that has not been returned by {@link #getSentPacket()}.
diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5BytestreamManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5BytestreamManager.java
index 66e221c49..673f7a989 100644
--- a/smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5BytestreamManager.java
+++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5BytestreamManager.java
@@ -656,13 +656,23 @@ public final class Socks5BytestreamManager extends Manager implements Bytestream
*/
public List getLocalStreamHost() {
// Ensure that the local SOCKS5 proxy is running (if enabled).
- Socks5Proxy.getSocks5Proxy();
+ Socks5Proxy socks5Proxy = Socks5Proxy.getSocks5Proxy();
List streamHosts = new ArrayList<>();
XMPPConnection connection = connection();
EntityFullJid myJid = connection.getUser();
+ // The default local address is often just 'the first address found in the
+ // list of addresses read from the OS' and this might mean an internal
+ // IP address that cannot reach external servers. So wherever possible
+ // use the same IP address being used to connect to the XMPP server
+ // because this local address has a better chance of being suitable.
+ InetAddress xmppLocalAddress = connection.getLocalAddress();
+ if (xmppLocalAddress != null) {
+ socks5Proxy.replaceLocalAddresses(Collections.singletonList(xmppLocalAddress));
+ }
+
for (Socks5Proxy socks5Server : Socks5Proxy.getRunningProxies()) {
List addresses = socks5Server.getLocalAddresses();
if (addresses.isEmpty()) {
diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5ByteStreamManagerTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5ByteStreamManagerTest.java
index cfa59197a..00e721e5b 100644
--- a/smack-extensions/src/test/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5ByteStreamManagerTest.java
+++ b/smack-extensions/src/test/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5ByteStreamManagerTest.java
@@ -24,12 +24,16 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.ServerSocket;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeoutException;
@@ -1002,6 +1006,72 @@ public class Socks5ByteStreamManagerTest {
protocol.verifyAll();
}
+ /**
+ * Invoking {@link Socks5BytestreamManager#getLocalStreamHost()} should return only a local address
+ * from XMPP connection when it is connected and has a socket with a bound non-localhost IP address.
+ *
+ * @throws InterruptedException if the calling thread was interrupted.
+ * @throws SmackException if Smack detected an exceptional situation.
+ * @throws XMPPErrorException if an XMPP protocol error was received.
+ */
+ @Test
+ public void shouldUseXMPPConnectionLocalAddressWhenConnected() throws InterruptedException, XMPPErrorException, SmackException {
+ final Protocol protocol = new Protocol();
+ final XMPPConnection connection = ConnectionUtils.createMockedConnection(protocol, initiatorJID);
+
+ // prepare XMPP local address
+ Inet4Address xmppLocalAddress = mock(Inet4Address.class);
+ when(xmppLocalAddress.getHostAddress()).thenReturn("81.72.63.54");
+ when(connection.getLocalAddress()).thenReturn(xmppLocalAddress);
+
+ // get Socks5ByteStreamManager for connection
+ Socks5BytestreamManager byteStreamManager = Socks5BytestreamManager.getBytestreamManager(connection);
+
+ List localStreamHost = byteStreamManager.getLocalStreamHost();
+
+ // must be only 1 stream host with XMPP local address IP
+ assertEquals(1, localStreamHost.size());
+ assertEquals("81.72.63.54", localStreamHost.get(0).getAddress().toString());
+ assertEquals(initiatorJID, localStreamHost.get(0).getJID());
+ }
+
+ /**
+ * Invoking {@link Socks5BytestreamManager#getLocalStreamHost()} should return all non-localhost
+ * local addresses when its XMPP connection's socket is null.
+ *
+ * @throws InterruptedException if the calling thread was interrupted.
+ * @throws SmackException if Smack detected an exceptional situation.
+ * @throws XMPPErrorException if an XMPP protocol error was received.
+ * @throws UnknownHostException if address cannot be resolved.
+ */
+ @Test
+ public void shouldUseSocks5LocalAddressesWhenNotConnected() throws InterruptedException, XMPPErrorException, SmackException, UnknownHostException {
+ final Protocol protocol = new Protocol();
+ final XMPPConnection connection = ConnectionUtils.createMockedConnection(protocol, initiatorJID);
+
+ // No XMPP local address
+ when(connection.getLocalAddress()).thenReturn(null);
+
+ // get Socks5ByteStreamManager for connection
+ Socks5BytestreamManager byteStreamManager = Socks5BytestreamManager.getBytestreamManager(connection);
+
+ List localAddresses = new ArrayList<>();
+ for (InetAddress inetAddress : Socks5Proxy.getSocks5Proxy().getLocalAddresses()) {
+ if (!inetAddress.isLoopbackAddress()) {
+ localAddresses.add(inetAddress);
+ }
+ }
+
+ List localStreamHost = byteStreamManager.getLocalStreamHost();
+
+ // Must be the same addresses as in SOCKS5 proxy local address list (excluding loopback)
+ assertEquals(localAddresses.size(), localStreamHost.size());
+ for (StreamHost streamHost : localStreamHost) {
+ assertTrue(localAddresses.contains(streamHost.getAddress().asInetAddress()));
+ assertEquals(initiatorJID, streamHost.getJID());
+ }
+ }
+
private static void createResponses(Protocol protocol, String sessionID,
Verification streamHostUsedVerification, Socks5TestProxy socks5TestProxy)
throws XmppStringprepException {
diff --git a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java
index e093ce1a6..38d2d3e55 100644
--- a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java
+++ b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java
@@ -1938,4 +1938,20 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
this.bundleAndDeferCallback = bundleAndDeferCallback;
}
+
+ /**
+ * Returns the local address currently in use for this connection.
+ *
+ * @return the local address
+ */
+ @Override
+ public InetAddress getLocalAddress() {
+ final Socket socket = this.socket;
+ if (socket == null) return null;
+
+ InetAddress localAddress = socket.getLocalAddress();
+ if (localAddress.isAnyLocalAddress()) return null;
+
+ return localAddress;
+ }
}