forked from I2P_Developers/i2p.i2p
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:
@@ -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();
|
||||
|
@@ -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,
|
||||
|
@@ -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...");
|
||||
|
@@ -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);
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
@@ -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':
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user