mirror of
https://github.com/vanitasvitae/Smack.git
synced 2024-09-27 10:09:32 +02:00
b5f9d4d7a3
This also removes the powermock dependency. Although powermock is a fine library, it currently prevents dropping Junit4. And since we only use the Whitebox API of powermock, this simply replaced powermock's Whitebox with our own.
389 lines
18 KiB
Java
389 lines
18 KiB
Java
/**
|
|
*
|
|
* Copyright the original author or authors
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
package org.jivesoftware.smackx.bytestreams.socks5;
|
|
|
|
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
|
|
import java.io.InputStream;
|
|
import java.io.OutputStream;
|
|
import java.net.ServerSocket;
|
|
import java.net.Socket;
|
|
|
|
import org.jivesoftware.smack.XMPPConnection;
|
|
import org.jivesoftware.smack.packet.IQ;
|
|
import org.jivesoftware.smack.packet.Stanza;
|
|
import org.jivesoftware.smack.packet.StanzaError;
|
|
import org.jivesoftware.smack.test.util.NetworkUtil;
|
|
|
|
import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream;
|
|
|
|
import org.jivesoftware.util.ConnectionUtils;
|
|
import org.jivesoftware.util.Protocol;
|
|
import org.junit.jupiter.api.Test;
|
|
import org.jxmpp.jid.DomainBareJid;
|
|
import org.jxmpp.jid.EntityFullJid;
|
|
import org.jxmpp.jid.JidTestUtil;
|
|
import org.jxmpp.jid.impl.JidCreate;
|
|
|
|
/**
|
|
* Tests for the Socks5BytestreamRequest class.
|
|
*
|
|
* @author Henning Staib
|
|
*/
|
|
public class Socks5ByteStreamRequestTest {
|
|
|
|
// settings
|
|
private static final EntityFullJid initiatorJID = JidTestUtil.DUMMY_AT_EXAMPLE_ORG_SLASH_DUMMYRESOURCE;
|
|
private static final EntityFullJid targetJID = JidTestUtil.FULL_JID_1_RESOURCE_1;
|
|
private static final DomainBareJid proxyJID = JidTestUtil.MUC_EXAMPLE_ORG;
|
|
private static final String proxyAddress = "127.0.0.1";
|
|
private static final String sessionID = "session_id";
|
|
|
|
/**
|
|
* Accepting a SOCKS5 Bytestream request should fail if the request doesn't contain any Socks5
|
|
* proxies.
|
|
*
|
|
* @throws Exception should not happen
|
|
*/
|
|
@Test
|
|
public void shouldFailIfRequestHasNoStreamHosts() throws Exception {
|
|
final Protocol protocol = new Protocol();
|
|
final XMPPConnection connection = ConnectionUtils.createMockedConnection(protocol, targetJID);
|
|
|
|
assertThrows(Socks5Exception.NoSocks5StreamHostsProvided.class, () -> {
|
|
// build SOCKS5 Bytestream initialization request with no SOCKS5 proxies
|
|
Bytestream bytestreamInitialization = Socks5PacketUtils.createBytestreamInitiation(
|
|
initiatorJID, targetJID, sessionID);
|
|
|
|
// get SOCKS5 Bytestream manager for connection
|
|
Socks5BytestreamManager byteStreamManager = Socks5BytestreamManager.getBytestreamManager(connection);
|
|
|
|
// build SOCKS5 Bytestream request with the bytestream initialization
|
|
Socks5BytestreamRequest byteStreamRequest = new Socks5BytestreamRequest(
|
|
byteStreamManager, bytestreamInitialization);
|
|
|
|
// accept the stream (this is the call that is tested here)
|
|
byteStreamRequest.accept();
|
|
});
|
|
|
|
// verify targets response
|
|
assertEquals(1, protocol.getRequests().size());
|
|
Stanza targetResponse = protocol.getRequests().remove(0);
|
|
assertTrue(IQ.class.isInstance(targetResponse));
|
|
assertEquals(initiatorJID, targetResponse.getTo());
|
|
assertEquals(IQ.Type.error, ((IQ) targetResponse).getType());
|
|
assertEquals(StanzaError.Condition.item_not_found,
|
|
targetResponse.getError().getCondition());
|
|
}
|
|
|
|
/**
|
|
* Accepting a SOCKS5 Bytestream request should fail if target is not able to connect to any of
|
|
* the provided SOCKS5 proxies.
|
|
*
|
|
* @throws Exception if an exception occurs.
|
|
*/
|
|
@Test
|
|
public void shouldFailIfRequestHasInvalidStreamHosts() throws Exception {
|
|
final Protocol protocol = new Protocol();
|
|
final XMPPConnection connection = ConnectionUtils.createMockedConnection(protocol, targetJID);
|
|
|
|
assertThrows(Socks5Exception.CouldNotConnectToAnyProvidedSocks5Host.class, () -> {
|
|
// build SOCKS5 Bytestream initialization request
|
|
Bytestream bytestreamInitialization = Socks5PacketUtils.createBytestreamInitiation(
|
|
initiatorJID, targetJID, sessionID);
|
|
// add proxy that is not running
|
|
bytestreamInitialization.addStreamHost(proxyJID, proxyAddress, 7778);
|
|
|
|
// get SOCKS5 Bytestream manager for connection
|
|
Socks5BytestreamManager byteStreamManager = Socks5BytestreamManager.getBytestreamManager(connection);
|
|
|
|
// build SOCKS5 Bytestream request with the bytestream initialization
|
|
Socks5BytestreamRequest byteStreamRequest = new Socks5BytestreamRequest(
|
|
byteStreamManager, bytestreamInitialization);
|
|
|
|
// accept the stream (this is the call that is tested here)
|
|
byteStreamRequest.accept();
|
|
});
|
|
|
|
// verify targets response
|
|
assertEquals(1, protocol.getRequests().size());
|
|
Stanza targetResponse = protocol.getRequests().remove(0);
|
|
assertTrue(IQ.class.isInstance(targetResponse));
|
|
assertEquals(initiatorJID, targetResponse.getTo());
|
|
assertEquals(IQ.Type.error, ((IQ) targetResponse).getType());
|
|
assertEquals(StanzaError.Condition.item_not_found,
|
|
targetResponse.getError().getCondition());
|
|
}
|
|
|
|
/**
|
|
* Target should not try to connect to SOCKS5 proxies that already failed twice.
|
|
*
|
|
* @throws Exception should not happen
|
|
*/
|
|
@Test
|
|
public void shouldBlacklistInvalidProxyAfter2Failures() throws Exception {
|
|
final Protocol protocol = new Protocol();
|
|
final XMPPConnection connection = ConnectionUtils.createMockedConnection(protocol, targetJID);
|
|
|
|
// build SOCKS5 Bytestream initialization request
|
|
Bytestream bytestreamInitialization = Socks5PacketUtils.createBytestreamInitiation(
|
|
initiatorJID, targetJID, sessionID);
|
|
// Add an unreachable stream host.
|
|
bytestreamInitialization.addStreamHost(JidCreate.from("invalid." + proxyJID), "127.0.0.2", 7778);
|
|
|
|
// get SOCKS5 Bytestream manager for connection
|
|
Socks5BytestreamManager byteStreamManager = Socks5BytestreamManager.getBytestreamManager(connection);
|
|
|
|
// try to connect several times
|
|
for (int i = 0; i < 2; i++) {
|
|
assertThrows(Socks5Exception.CouldNotConnectToAnyProvidedSocks5Host.class, () -> {
|
|
// build SOCKS5 Bytestream request with the bytestream initialization
|
|
Socks5BytestreamRequest byteStreamRequest = new Socks5BytestreamRequest(
|
|
byteStreamManager, bytestreamInitialization);
|
|
|
|
// set timeouts
|
|
byteStreamRequest.setTotalConnectTimeout(600);
|
|
byteStreamRequest.setMinimumConnectTimeout(300);
|
|
|
|
// accept the stream (this is the call that is tested here)
|
|
byteStreamRequest.accept();
|
|
});
|
|
|
|
// verify targets response
|
|
assertEquals(1, protocol.getRequests().size());
|
|
Stanza targetResponse = protocol.getRequests().remove(0);
|
|
assertTrue(IQ.class.isInstance(targetResponse));
|
|
assertEquals(initiatorJID, targetResponse.getTo());
|
|
assertEquals(IQ.Type.error, ((IQ) targetResponse).getType());
|
|
assertEquals(StanzaError.Condition.item_not_found,
|
|
targetResponse.getError().getCondition());
|
|
}
|
|
|
|
// create test data for stream
|
|
byte[] data = new byte[] { 1, 2, 3 };
|
|
|
|
try (Socks5TestProxy socks5Proxy = new Socks5TestProxy()) {
|
|
assertTrue(socks5Proxy.isRunning());
|
|
|
|
// add a valid SOCKS5 proxy
|
|
bytestreamInitialization.addStreamHost(proxyJID, proxyAddress, socks5Proxy.getPort());
|
|
|
|
// build SOCKS5 Bytestream request with the bytestream initialization
|
|
Socks5BytestreamRequest byteStreamRequest = new Socks5BytestreamRequest(byteStreamManager,
|
|
bytestreamInitialization);
|
|
|
|
// set timeouts
|
|
byteStreamRequest.setTotalConnectTimeout(600);
|
|
byteStreamRequest.setMinimumConnectTimeout(300);
|
|
|
|
// accept the stream (this is the call that is tested here)
|
|
InputStream inputStream = byteStreamRequest.accept().getInputStream();
|
|
|
|
// create digest to get the socket opened by target
|
|
String digest = Socks5Utils.createDigest(sessionID, initiatorJID, targetJID);
|
|
|
|
// test stream by sending some data
|
|
OutputStream outputStream = socks5Proxy.getSocket(digest).getOutputStream();
|
|
outputStream.write(data);
|
|
|
|
// verify that data is transferred correctly
|
|
byte[] result = new byte[3];
|
|
inputStream.read(result);
|
|
assertArrayEquals(data, result);
|
|
|
|
// verify targets response
|
|
assertEquals(1, protocol.getRequests().size());
|
|
Stanza targetResponse = protocol.getRequests().remove(0);
|
|
assertEquals(Bytestream.class, targetResponse.getClass());
|
|
assertEquals(initiatorJID, targetResponse.getTo());
|
|
assertEquals(IQ.Type.result, ((Bytestream) targetResponse).getType());
|
|
assertEquals(proxyJID, ((Bytestream) targetResponse).getUsedHost().getJID());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Target should not not blacklist any SOCKS5 proxies regardless of failing connections.
|
|
*
|
|
* @throws Exception should not happen
|
|
*/
|
|
@Test
|
|
public void shouldNotBlacklistInvalidProxy() throws Exception {
|
|
final Protocol protocol = new Protocol();
|
|
final XMPPConnection connection = ConnectionUtils.createMockedConnection(protocol, targetJID);
|
|
|
|
// build SOCKS5 Bytestream initialization request
|
|
Bytestream bytestreamInitialization = Socks5PacketUtils.createBytestreamInitiation(
|
|
initiatorJID, targetJID, sessionID);
|
|
bytestreamInitialization.addStreamHost(JidCreate.from("invalid." + proxyJID), "127.0.0.2", 7778);
|
|
|
|
// get SOCKS5 Bytestream manager for connection
|
|
Socks5BytestreamManager byteStreamManager = Socks5BytestreamManager.getBytestreamManager(connection);
|
|
|
|
// try to connect several times
|
|
for (int i = 0; i < 10; i++) {
|
|
assertThrows(Socks5Exception.CouldNotConnectToAnyProvidedSocks5Host.class, () -> {
|
|
// build SOCKS5 Bytestream request with the bytestream initialization
|
|
Socks5BytestreamRequest byteStreamRequest = new Socks5BytestreamRequest(
|
|
byteStreamManager, bytestreamInitialization);
|
|
|
|
// set timeouts
|
|
byteStreamRequest.setTotalConnectTimeout(600);
|
|
byteStreamRequest.setMinimumConnectTimeout(300);
|
|
byteStreamRequest.setConnectFailureThreshold(0);
|
|
|
|
// accept the stream (this is the call that is tested here)
|
|
byteStreamRequest.accept();
|
|
});
|
|
|
|
// verify targets response
|
|
assertEquals(1, protocol.getRequests().size());
|
|
Stanza targetResponse = protocol.getRequests().remove(0);
|
|
assertTrue(IQ.class.isInstance(targetResponse));
|
|
assertEquals(initiatorJID, targetResponse.getTo());
|
|
assertEquals(IQ.Type.error, ((IQ) targetResponse).getType());
|
|
assertEquals(StanzaError.Condition.item_not_found,
|
|
targetResponse.getError().getCondition());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* If the SOCKS5 Bytestream request contains multiple SOCKS5 proxies and the first one doesn't
|
|
* respond, the connection attempt to this proxy should not consume the whole timeout for
|
|
* connecting to the proxies.
|
|
*
|
|
* @throws Exception should not happen
|
|
*/
|
|
@Test
|
|
public void shouldNotTimeoutIfFirstSocks5ProxyDoesNotRespond() throws Exception {
|
|
final Protocol protocol = new Protocol();
|
|
final XMPPConnection connection = ConnectionUtils.createMockedConnection(protocol, targetJID);
|
|
|
|
// start a local SOCKS5 proxy
|
|
try (Socks5TestProxy socks5Proxy = new Socks5TestProxy()) {
|
|
// create a fake SOCKS5 proxy that doesn't respond to a request
|
|
ServerSocket unresponsiveSocks5Socket = NetworkUtil.getSocketOnLoopback();
|
|
|
|
try {
|
|
// build SOCKS5 Bytestream initialization request
|
|
Bytestream bytestreamInitialization = Socks5PacketUtils.createBytestreamInitiation(
|
|
initiatorJID, targetJID, sessionID);
|
|
bytestreamInitialization.addStreamHost(proxyJID, proxyAddress, unresponsiveSocks5Socket.getLocalPort());
|
|
bytestreamInitialization.addStreamHost(proxyJID, proxyAddress, socks5Proxy.getPort());
|
|
|
|
// create test data for stream
|
|
byte[] data = new byte[] { 1, 2, 3 };
|
|
|
|
// get SOCKS5 Bytestream manager for connection
|
|
Socks5BytestreamManager byteStreamManager = Socks5BytestreamManager.getBytestreamManager(connection);
|
|
|
|
// build SOCKS5 Bytestream request with the bytestream initialization
|
|
Socks5BytestreamRequest byteStreamRequest = new Socks5BytestreamRequest(byteStreamManager,
|
|
bytestreamInitialization);
|
|
|
|
// set timeouts
|
|
byteStreamRequest.setTotalConnectTimeout(2000);
|
|
byteStreamRequest.setMinimumConnectTimeout(1000);
|
|
|
|
// accept the stream (this is the call that is tested here)
|
|
InputStream inputStream = byteStreamRequest.accept().getInputStream();
|
|
|
|
// assert that client tries to connect to dumb SOCKS5 proxy
|
|
Socket socket = unresponsiveSocks5Socket.accept();
|
|
assertNotNull(socket);
|
|
|
|
// create digest to get the socket opened by target
|
|
String digest = Socks5Utils.createDigest(sessionID, initiatorJID, targetJID);
|
|
|
|
// test stream by sending some data
|
|
OutputStream outputStream = socks5Proxy.getSocket(digest).getOutputStream();
|
|
outputStream.write(data);
|
|
|
|
// verify that data is transferred correctly
|
|
byte[] result = new byte[3];
|
|
inputStream.read(result);
|
|
assertArrayEquals(data, result);
|
|
|
|
// verify targets response
|
|
assertEquals(1, protocol.getRequests().size());
|
|
Stanza targetResponse = protocol.getRequests().remove(0);
|
|
assertEquals(Bytestream.class, targetResponse.getClass());
|
|
assertEquals(initiatorJID, targetResponse.getTo());
|
|
assertEquals(IQ.Type.result, ((Bytestream) targetResponse).getType());
|
|
assertEquals(proxyJID, ((Bytestream) targetResponse).getUsedHost().getJID());
|
|
} finally {
|
|
unresponsiveSocks5Socket.close();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Accepting the SOCKS5 Bytestream request should be successfully.
|
|
*
|
|
* @throws Exception should not happen
|
|
*/
|
|
@Test
|
|
public void shouldAcceptSocks5BytestreamRequestAndReceiveData() throws Exception {
|
|
final Protocol protocol = new Protocol();
|
|
final XMPPConnection connection = ConnectionUtils.createMockedConnection(protocol, targetJID);
|
|
|
|
// start a local SOCKS5 proxy
|
|
try (Socks5TestProxy socks5Proxy = new Socks5TestProxy()) {
|
|
// build SOCKS5 Bytestream initialization request
|
|
Bytestream bytestreamInitialization = Socks5PacketUtils.createBytestreamInitiation(
|
|
initiatorJID, targetJID, sessionID);
|
|
bytestreamInitialization.addStreamHost(proxyJID, proxyAddress, socks5Proxy.getPort());
|
|
|
|
// create test data for stream
|
|
byte[] data = new byte[] { 1, 2, 3 };
|
|
|
|
// get SOCKS5 Bytestream manager for connection
|
|
Socks5BytestreamManager byteStreamManager = Socks5BytestreamManager.getBytestreamManager(connection);
|
|
|
|
// build SOCKS5 Bytestream request with the bytestream initialization
|
|
Socks5BytestreamRequest byteStreamRequest = new Socks5BytestreamRequest(byteStreamManager,
|
|
bytestreamInitialization);
|
|
|
|
// accept the stream (this is the call that is tested here)
|
|
InputStream inputStream = byteStreamRequest.accept().getInputStream();
|
|
|
|
// create digest to get the socket opened by target
|
|
String digest = Socks5Utils.createDigest(sessionID, initiatorJID, targetJID);
|
|
|
|
// test stream by sending some data
|
|
OutputStream outputStream = socks5Proxy.getSocket(digest).getOutputStream();
|
|
outputStream.write(data);
|
|
|
|
// verify that data is transferred correctly
|
|
byte[] result = new byte[3];
|
|
inputStream.read(result);
|
|
assertArrayEquals(data, result);
|
|
|
|
// verify targets response
|
|
assertEquals(1, protocol.getRequests().size());
|
|
Stanza targetResponse = protocol.getRequests().remove(0);
|
|
assertEquals(Bytestream.class, targetResponse.getClass());
|
|
assertEquals(initiatorJID, targetResponse.getTo());
|
|
assertEquals(IQ.Type.result, ((Bytestream) targetResponse).getType());
|
|
assertEquals(proxyJID, ((Bytestream) targetResponse).getUsedHost().getJID());
|
|
}
|
|
}
|
|
}
|