package io.vertx.test.core;
import io.vertx.core.Handler;
import io.vertx.core.Vertx;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.logging.Logger;
import io.vertx.core.logging.LoggerFactory;
import io.vertx.core.net.NetClient;
import io.vertx.core.net.NetClientOptions;
import io.vertx.core.net.NetServer;
import io.vertx.core.net.NetServerOptions;
import io.vertx.core.net.NetSocket;
import io.vertx.core.streams.Pump;
/**
* SOCKS4 Proxy
* <p>
* A simple SOCKS4/4a proxy for testing SOCKS functionality. Currently we only support tcp connect and
* username auth, which is enough to make the currently implemented client tests to pass.
*
* <p>
* Usually the server will be started in @Before and stopped in @After for a unit test using HttpClient or NetClient
* with the setProxyOptions method.
*
* @author <a href="http://oss.lehmann.cx/">Alexander Lehmann</a>
*/
public class Socks4Proxy extends TestProxyBase {
private static final Logger log = LoggerFactory.getLogger(Socks4Proxy.class);
private static final Buffer clientRequest = Buffer.buffer(new byte[] { 4, 1 });
private static final Buffer connectResponse = Buffer.buffer(new byte[] { 0, 90, 0, 0, 0, 0, 0, 0 });
private static final Buffer errorResponse = Buffer.buffer(new byte[] { 0, 91, 0, 0, 0, 0, 0, 0 });
private static final int PORT = 11080;
private NetServer server;
public Socks4Proxy(String username) {
super(username);
}
/**
* Start the server.
*
* @param vertx
* Vertx instance to use for creating the server and client
* @param finishedHandler
* will be called when the start has started
*/
@Override
public void start(Vertx vertx, Handler<Void> finishedHandler) {
NetServerOptions options = new NetServerOptions();
options.setHost("localhost").setPort(PORT);
server = vertx.createNetServer(options);
server.connectHandler(socket -> {
socket.handler(buffer -> {
if (!buffer.getBuffer(0, clientRequest.length()).equals(clientRequest)) {
throw new IllegalStateException("expected " + toHex(clientRequest) + ", got " + toHex(buffer));
}
log.debug("got request: " + toHex(buffer));
int port = buffer.getUnsignedShort(2);
String ip = getByte4(buffer.getBuffer(4, 8));
String authUsername = getString(buffer.getBuffer(8, buffer.length()));
if (username != null && !authUsername.equals(username)) {
log.debug("auth failed");
log.debug("writing: " + toHex(errorResponse));
socket.write(errorResponse);
socket.close();
} else {
String host;
if (ip.equals("0.0.0.1")) {
host = getString(buffer.getBuffer(9 + authUsername.length(), buffer.length()));
} else {
host = ip;
}
log.debug("connect: " + host + ":" + port);
socket.handler(null);
lastUri = host + ":" + port;
if (forceUri != null) {
host = forceUri.substring(0, forceUri.indexOf(':'));
port = Integer.valueOf(forceUri.substring(forceUri.indexOf(':') + 1));
}
log.debug("connecting to " + host + ":" + port);
NetClient netClient = vertx.createNetClient(new NetClientOptions());
netClient.connect(port, host, result -> {
if (result.succeeded()) {
log.debug("writing: " + toHex(connectResponse));
socket.write(connectResponse);
log.debug("connected, starting pump");
NetSocket clientSocket = result.result();
socket.closeHandler(v -> clientSocket.close());
clientSocket.closeHandler(v -> socket.close());
Pump.pump(socket, clientSocket).start();
Pump.pump(clientSocket, socket).start();
} else {
log.error("exception", result.cause());
socket.handler(null);
log.debug("writing: " + toHex(errorResponse));
socket.write(errorResponse);
socket.close();
}
});
}
});
});
server.listen(result -> {
log.debug("socks4a server started");
finishedHandler.handle(null);
});
}
private String getString(Buffer buffer) {
String string = buffer.toString();
return string.substring(0, string.indexOf('\0'));
}
private String getByte4(Buffer buffer) {
return String.format("%d.%d.%d.%d", buffer.getByte(0), buffer.getByte(1), buffer.getByte(2), buffer.getByte(3));
}
private String toHex(Buffer buffer) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < buffer.length(); i++) {
sb.append(String.format("%02X ", buffer.getByte(i)));
}
return sb.toString();
}
/**
* Stop the server.
*
* <p>Doesn't wait for the close operation to finish
*/
@Override
public void stop() {
if (server != null) {
server.close();
server = null;
}
}
@Override
public int getPort() {
return PORT;
}
}