i2ptunnel: Add outproxy plugin support to SOCKS (ticket #1824)

Make some classes package private
Move some fields to SocksServer superclass
This commit is contained in:
zzz
2016-08-12 16:41:42 +00:00
parent b21b953ef2
commit cdab6f8b76
8 changed files with 256 additions and 72 deletions

View File

@@ -47,7 +47,7 @@ public class I2PSOCKSIRCTunnel extends I2PSOCKSTunnel {
protected void clientConnectionRun(Socket s) {
try {
//_log.error("SOCKS IRC Tunnel Start");
SOCKSServer serv = SOCKSServerFactory.createSOCKSServer(s, getTunnel().getClientOptions());
SOCKSServer serv = SOCKSServerFactory.createSOCKSServer(_context, s, getTunnel().getClientOptions());
Socket clientSock = serv.getClientSocket();
I2PSocket destSock = serv.getDestinationI2PSocket(this);
StringBuffer expectedPong = new StringBuffer();

View File

@@ -48,7 +48,7 @@ public class I2PSOCKSTunnel extends I2PTunnelClientBase {
protected void clientConnectionRun(Socket s) {
try {
SOCKSServer serv = SOCKSServerFactory.createSOCKSServer(s, getTunnel().getClientOptions());
SOCKSServer serv = SOCKSServerFactory.createSOCKSServer(_context, s, getTunnel().getClientOptions());
Socket clientSock = serv.getClientSocket();
I2PSocket destSock = serv.getDestinationI2PSocket(this);
Thread t = new I2PTunnelRunner(clientSock, destSock, sockLock, null, null, mySockets,

View File

@@ -19,6 +19,9 @@ import java.util.Properties;
import net.i2p.I2PAppContext;
import net.i2p.I2PException;
import net.i2p.app.ClientApp;
import net.i2p.app.ClientAppManager;
import net.i2p.app.Outproxy;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.client.streaming.I2PSocketOptions;
import net.i2p.data.DataFormatException;
@@ -32,10 +35,8 @@ import net.i2p.util.Log;
*
* @author zzz modded from SOCKS5Server
*/
public class SOCKS4aServer extends SOCKSServer {
private final Log _log;
class SOCKS4aServer extends SOCKSServer {
private final Socket clientSock;
private boolean setupCompleted;
/**
@@ -49,15 +50,12 @@ public class SOCKS4aServer extends SOCKSServer {
* @param clientSock client socket
* @param props non-null
*/
public SOCKS4aServer(Socket clientSock, Properties props) {
this.clientSock = clientSock;
this.props = props;
_log = I2PAppContext.getGlobalContext().logManager().getLog(SOCKS4aServer.class);
public SOCKS4aServer(I2PAppContext ctx, Socket clientSock, Properties props) {
super(ctx, clientSock, props);
}
public Socket getClientSocket() throws SOCKSException {
setupServer();
return clientSock;
}
@@ -211,12 +209,8 @@ public class SOCKS4aServer extends SOCKSServer {
I2PSocket destSock;
try {
if (connHostName.toLowerCase(Locale.US).endsWith(".i2p") ||
connHostName.toLowerCase(Locale.US).endsWith(".onion")) {
// Let's not do a new Dest for every request, huh?
//I2PSocketManager sm = I2PSocketManagerFactory.createManager();
//destSock = sm.connect(I2PTunnel.destFromName(connHostName), null);
Destination dest = I2PAppContext.getGlobalContext().namingService().lookup(connHostName);
if (connHostName.toLowerCase(Locale.US).endsWith(".i2p")) {
Destination dest = _context.namingService().lookup(connHostName);
if (dest == null) {
try {
sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out);
@@ -236,6 +230,7 @@ public class SOCKS4aServer extends SOCKSServer {
sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out);
} catch (IOException ioe) {}
throw new SOCKSException(err);
/****
} else if (connPort == 80) {
// rewrite GET line to include hostname??? or add Host: line???
// or forward to local eepProxy (but that's a Socket not an I2PSocket)
@@ -246,30 +241,43 @@ public class SOCKS4aServer extends SOCKSServer {
sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out);
} catch (IOException ioe) {}
throw new SOCKSException(err);
****/
} else {
List<String> proxies = t.getProxies(connPort);
if (proxies == null || proxies.isEmpty()) {
String err = "No outproxy configured for port " + connPort + " and no default configured either - host: " + connHostName;
_log.error(err);
Outproxy outproxy = getOutproxyPlugin();
if (outproxy != null) {
try {
sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out);
} catch (IOException ioe) {}
throw new SOCKSException(err);
destSock = new SocketWrapper(outproxy.connect(connHostName, connPort));
} catch (IOException ioe) {
try {
sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out);
} catch (IOException ioe2) {}
throw new SOCKSException("connect failed via outproxy plugin", ioe);
}
} else {
List<String> proxies = t.getProxies(connPort);
if (proxies == null || proxies.isEmpty()) {
String err = "No outproxy configured for port " + connPort + " and no default configured either - host: " + connHostName;
_log.error(err);
try {
sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out);
} catch (IOException ioe) {}
throw new SOCKSException(err);
}
int p = _context.random().nextInt(proxies.size());
String proxy = proxies.get(p);
Destination dest = _context.namingService().lookup(proxy);
if (dest == null) {
try {
sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out);
} catch (IOException ioe) {}
throw new SOCKSException("Outproxy not found");
}
if (_log.shouldDebug())
_log.debug("connecting to port " + connPort + " proxy " + proxy + " for " + connHostName + "...");
// this isn't going to work, these need to be socks outproxies so we need
// to do a socks session to them?
destSock = t.createI2PSocket(dest);
}
int p = I2PAppContext.getGlobalContext().random().nextInt(proxies.size());
String proxy = proxies.get(p);
Destination dest = I2PAppContext.getGlobalContext().namingService().lookup(proxy);
if (dest == null) {
try {
sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out);
} catch (IOException ioe) {}
throw new SOCKSException("Outproxy not found");
}
if (_log.shouldDebug())
_log.debug("connecting to port " + connPort + " proxy " + proxy + " for " + connHostName + "...");
// this isn't going to work, these need to be socks outproxies so we need
// to do a socks session to them?
destSock = t.createI2PSocket(dest);
}
confirmConnection();
_log.debug("connection confirmed - exchanging data...");

View File

@@ -21,6 +21,9 @@ import java.util.Properties;
import net.i2p.I2PAppContext;
import net.i2p.I2PException;
import net.i2p.app.ClientApp;
import net.i2p.app.ClientAppManager;
import net.i2p.app.Outproxy;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.client.streaming.I2PSocketOptions;
import net.i2p.data.DataFormatException;
@@ -37,12 +40,10 @@ import net.i2p.util.Log;
*
* @author human
*/
public class SOCKS5Server extends SOCKSServer {
private final Log _log;
class SOCKS5Server extends SOCKSServer {
private static final int SOCKS_VERSION_5 = 0x05;
private final Socket clientSock;
private boolean setupCompleted = false;
private final boolean authRequired;
@@ -57,14 +58,12 @@ public class SOCKS5Server extends SOCKSServer {
* @param clientSock client socket
* @param props non-null
*/
public SOCKS5Server(Socket clientSock, Properties props) {
this.clientSock = clientSock;
this.props = props;
public SOCKS5Server(I2PAppContext ctx, Socket clientSock, Properties props) {
super(ctx, clientSock, props);
this.authRequired =
Boolean.parseBoolean(props.getProperty(I2PTunnelHTTPClientBase.PROP_AUTH)) &&
props.containsKey(I2PTunnelHTTPClientBase.PROP_USER) &&
props.containsKey(I2PTunnelHTTPClientBase.PROP_PW);
_log = I2PAppContext.getGlobalContext().logManager().getLog(SOCKS5Server.class);
}
public Socket getClientSocket() throws SOCKSException {
@@ -366,7 +365,7 @@ public class SOCKS5Server extends SOCKSServer {
// Let's not do a new Dest for every request, huh?
//I2PSocketManager sm = I2PSocketManagerFactory.createManager();
//destSock = sm.connect(I2PTunnel.destFromName(connHostName), null);
Destination dest = I2PAppContext.getGlobalContext().namingService().lookup(connHostName);
Destination dest = _context.namingService().lookup(connHostName);
if (dest == null) {
try {
sendRequestReply(Reply.HOST_UNREACHABLE, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
@@ -399,27 +398,40 @@ public class SOCKS5Server extends SOCKSServer {
throw new SOCKSException(err);
****/
} else {
List<String> proxies = t.getProxies(connPort);
if (proxies == null || proxies.isEmpty()) {
String err = "No outproxy configured for port " + connPort + " and no default configured either";
_log.error(err);
Outproxy outproxy = getOutproxyPlugin();
if (outproxy != null) {
// In HTTPClient, we use OutproxyRunner to run a Socket,
// but here, we wrap a Socket in a I2PSocket and use the regular Runner.
try {
sendRequestReply(Reply.CONNECTION_NOT_ALLOWED_BY_RULESET, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
} catch (IOException ioe) {}
throw new SOCKSException(err);
}
int p = I2PAppContext.getGlobalContext().random().nextInt(proxies.size());
String proxy = proxies.get(p);
if (_log.shouldLog(Log.DEBUG))
_log.debug("connecting to proxy " + proxy + " for " + connHostName + " port " + connPort);
try {
destSock = outproxyConnect(t, proxy);
} catch (SOCKSException se) {
destSock = new SocketWrapper(outproxy.connect(connHostName, connPort));
} catch (IOException ioe) {
try {
sendRequestReply(Reply.HOST_UNREACHABLE, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
} catch (IOException ioe2) {}
throw new SOCKSException("connect failed via outproxy plugin", ioe);
}
} else {
List<String> proxies = t.getProxies(connPort);
if (proxies == null || proxies.isEmpty()) {
String err = "No outproxy configured for port " + connPort + " and no default configured either";
_log.error(err);
try {
sendRequestReply(Reply.CONNECTION_NOT_ALLOWED_BY_RULESET, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
} catch (IOException ioe) {}
throw new SOCKSException(err);
}
int p = _context.random().nextInt(proxies.size());
String proxy = proxies.get(p);
if (_log.shouldLog(Log.DEBUG))
_log.debug("connecting to proxy " + proxy + " for " + connHostName + " port " + connPort);
try {
sendRequestReply(Reply.HOST_UNREACHABLE, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
} catch (IOException ioe) {}
throw se;
destSock = outproxyConnect(t, proxy);
} catch (SOCKSException se) {
try {
sendRequestReply(Reply.HOST_UNREACHABLE, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
} catch (IOException ioe) {}
throw se;
}
}
}
confirmConnection();
@@ -466,7 +478,7 @@ public class SOCKS5Server extends SOCKSServer {
Properties overrides = new Properties();
overrides.setProperty("option.i2p.streaming.connectDelay", "1000");
I2PSocketOptions proxyOpts = tun.buildOptions(overrides);
Destination dest = I2PAppContext.getGlobalContext().namingService().lookup(proxy);
Destination dest = _context.namingService().lookup(proxy);
if (dest == null)
throw new SOCKSException("Outproxy not found");
I2PSocket destSock = tun.createI2PSocket(dest, proxyOpts);

View File

@@ -20,4 +20,9 @@ public class SOCKSException extends Exception {
public SOCKSException(String s) {
super(s);
}
}
/** @since 0.9.27 */
public SOCKSException(String s, Throwable t) {
super(s, t);
}
}

View File

@@ -9,14 +9,20 @@ package net.i2p.i2ptunnel.socks;
import java.net.Socket;
import java.util.Properties;
import net.i2p.I2PAppContext;
import net.i2p.app.ClientApp;
import net.i2p.app.ClientAppManager;
import net.i2p.app.Outproxy;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.i2ptunnel.I2PTunnelHTTPClient;
import net.i2p.util.Log;
/**
* Abstract base class used by all SOCKS servers.
*
* @author human
*/
public abstract class SOCKSServer {
abstract class SOCKSServer {
private static final String PROP_MAPPING_PREFIX = "ipmapping.";
@@ -25,7 +31,18 @@ public abstract class SOCKSServer {
protected int connPort;
protected int addressType;
protected Properties props;
protected final I2PAppContext _context;
protected final Socket clientSock;
protected final Properties props;
protected final Log _log;
/** @since 0.9.27 */
protected SOCKSServer(I2PAppContext ctx, Socket clientSock, Properties props) {
_context = ctx;
this.clientSock = clientSock;
this.props = props;
_log = ctx.logManager().getLog(getClass());
}
/**
* IP to domain name mapping support. This matches the given IP string
@@ -68,4 +85,26 @@ public abstract class SOCKSServer {
*/
public abstract I2PSocket getDestinationI2PSocket(I2PSOCKSTunnel t) throws SOCKSException;
/**
* @since 0.9.27
*/
private boolean shouldUseOutproxyPlugin() {
return Boolean.parseBoolean(props.getProperty(I2PTunnelHTTPClient.PROP_USE_OUTPROXY_PLUGIN, "true"));
}
/**
* @return null if disabled or not installed
* @since 0.9.27
*/
protected Outproxy getOutproxyPlugin() {
if (shouldUseOutproxyPlugin()) {
ClientAppManager mgr = _context.clientAppManager();
if (mgr != null) {
ClientApp op = mgr.getRegisteredApp(Outproxy.NAME);
if (op != null)
return (Outproxy) op;
}
}
return null;
}
}

View File

@@ -12,13 +12,14 @@ import java.io.IOException;
import java.net.Socket;
import java.util.Properties;
import net.i2p.I2PAppContext;
import net.i2p.data.DataHelper;
import net.i2p.i2ptunnel.I2PTunnelHTTPClientBase;
/**
* Factory class for creating SOCKS forwarders through I2P
*/
public class SOCKSServerFactory {
class SOCKSServerFactory {
private final static String ERR_REQUEST_DENIED =
"HTTP/1.1 403 Access Denied - This is a SOCKS proxy, not a HTTP proxy\r\n" +
@@ -38,7 +39,7 @@ public class SOCKSServerFactory {
* @param s a Socket used to choose the SOCKS server type
* @param props non-null
*/
public static SOCKSServer createSOCKSServer(Socket s, Properties props) throws SOCKSException {
public static SOCKSServer createSOCKSServer(I2PAppContext ctx, Socket s, Properties props) throws SOCKSException {
SOCKSServer serv;
try {
@@ -53,11 +54,11 @@ public class SOCKSServerFactory {
props.containsKey(I2PTunnelHTTPClientBase.PROP_PW)) {
throw new SOCKSException("SOCKS 4/4a not supported when authorization is required");
}
serv = new SOCKS4aServer(s, props);
serv = new SOCKS4aServer(ctx, s, props);
break;
case 0x05:
// SOCKS version 5
serv = new SOCKS5Server(s, props);
serv = new SOCKS5Server(ctx, s, props);
break;
case 'C':
case 'G':

View File

@@ -0,0 +1,119 @@
package net.i2p.i2ptunnel.socks;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.channels.SelectableChannel;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.client.streaming.I2PSocketOptions;
import net.i2p.data.DataFormatException;
import net.i2p.data.Destination;
/**
* Wrapper around the Socket obtained from the Outproxy, which is a
* wrapper around the Orchid Stream.
*
* @since 0.9.27
*/
class SocketWrapper implements I2PSocket {
private final Socket socket;
private static final Destination DUMMY_DEST = new Destination();
static {
try {
DUMMY_DEST.fromByteArray(new byte[387]);
} catch (DataFormatException dfe) {
throw new RuntimeException(dfe);
}
}
public SocketWrapper(Socket sock) {
socket = sock;
}
/**
* @return the Destination of this side of the socket.
*/
public Destination getThisDestination() {
return DUMMY_DEST;
}
/**
* @return the destination of the peer.
*/
public Destination getPeerDestination() {
return DUMMY_DEST;
}
public InputStream getInputStream() throws IOException {
return socket.getInputStream();
}
public OutputStream getOutputStream() throws IOException {
return socket.getOutputStream();
}
/**
* @return null always
*/
@Deprecated
public SelectableChannel getChannel() {
return null;
}
/**
* @return null always
*/
public I2PSocketOptions getOptions() {
return null;
}
/**
* Does nothing
*/
public void setOptions(I2PSocketOptions options) {}
public long getReadTimeout() {
return -1;
}
public void setReadTimeout(long ms) {}
public void close() throws IOException {
socket.close();
}
public boolean isClosed() {
return socket.isClosed();
}
/**
* Deprecated, unimplemented, does nothing
*/
public void setSocketErrorListener(SocketErrorListener lsnr) {}
/**
* The remote port.
* @return Default I2PSession.PORT_UNSPECIFIED (0) or PORT_ANY (0)
*/
public int getPort() {
try {
return socket.getPort();
} catch (UnsupportedOperationException uoe) {
// prior to 1.2.2-0.2
return 0;
}
}
/**
* The local port.
* @return 0 always
*/
public int getLocalPort() {
return 0;
}
}